出于个人学习的目的,我试图了解Winston的软件包设计结构以及每个模块背后的目的,但我无法理解这一点。
在Winstons包中,Logger
模块中有核心logger.js类,它实现了记录器的主要功能,并提供了一些公共方法,如logger.log
方法。它还实现了内部使用的转换流方法。
然后在create-logger.js模块中有一个名为DerivedLogger
的派生类,它扩展了Logger类,它的唯一目的似乎是为记录器原型添加优化的级别方法。然后将该DerivedLogger
类实例化并导出到模块底部的工厂函数中。
我的问题是,为什么需要DerivedLogger
课程?如果将这些级别方法添加到Logger
类原型本身然后让工厂函数直接实例化Logger
类,那么性能会有什么不同吗?我能想到的唯一原因是,为了模块化目的,可能只增加了DerivedLogger
类吗?有人可以帮我理解原因吗?
谢谢!
这个非常有趣,多亏了指出来!
简而言之:它与代码结构无关,它是一种性能优化。评论说明了这一点:
创建一个新的类派生记录器,其级别可以附加到原型。这是一个众所周知的V8优化,可以提高原型功能的性能。
就个人而言,我认为这需要引用(我不会在代码审查中接受它)。幸运的是,我认为我找到了作者所说的“优化”:
Mathias(一位在V8上工作的Google工程师)的This article讨论了如何通过正确使用prototype
来加速JavaScript执行。这篇文章有很多细节,如果你正在学习,真的值得一读。
Winston中的优化归结为:
getAttribute()
方法在Element.prototype
上找到。这意味着每次调用anchor.getAttribute()
时,JavaScript引擎都需要......
- 检查
getAttribute
不在锚对象本身上,- 检查直接原型是
HTMLAnchorElement.prototype
,- 声称在那里没有
getAttribute
,- 检查下一个原型是
HTMLElement.prototype
,- 声称在那里没有
getAttribute
,- 最终检查下一个原型是
Element.prototype
,- 并且那里有
getAttribute
。总共有7张支票!由于这种代码在Web上很常见,因此引擎会应用技巧来减少原型上属性加载所需的检查次数。
这大致适用于温斯顿,如下:
prototype
对象上定义prototype
。prototype
上找到被调用的方法prototype
链向上走到下一个类并查看那里prototype
链的末尾(并抛出错误)。通过在构造函数中运行_setupLevels()
,level-methods直接附加到特定logger-implementation实例的原型。这意味着类层次结构可以任意增长:prototype
链查找只需要一步就可以找到方法
这是另一个(简化)示例:
class A {
constructor() {
this.setup();
}
testInherit() {
console.log("Inherited method called");
}
setup() {
this["testDirect"] = () => console.log("Directly attached method called");
}
}
class B extends A {
constructor() {
super();
}
}
const test = new B();
test.testInherit();
test.testDirect();
如果我们在实例化test
之后设置断点,我们会看到以下内容:
正如你所看到的,testDirect
方法直接附加到test
,而testInherit
是多级下来。
我个人认为这是不好的做法:
不要乱用原型
至于模块化:对于所有扩展都有明确的基类,有一些东西可以说。
使用比JavaScript更严格的语言,这样的类可以提供仅用于扩展的特定方法,这些方法对于消费者来说是公共API隐藏的。然而,在这个特定情况下,Logger
本身就可以了。