出于个人学习目的,我试图了解Winston的包装设计结构及其每个模块背后的目的,但我无法弄清楚这一点。
在Winstons软件包中,logger.js模块中有一个核心Logger
类,该类实现了记录器的主要功能并提供了一些公共方法,例如logger.log
方法。它还实现了内部使用的转换流方法。
然后在create-logger.js模块中有一个名为DerivedLogger
的派生类,它扩展了Logger类,似乎其唯一目的是在记录器原型中添加优化级别的方法。然后,该DerivedLogger
类将在模块底部的工厂函数中实例化并导出。
我的问题是,为什么需要DerivedLogger
类?如果将这些级别的方法添加到Logger
类原型本身上,然后让工厂函数直接实例化Logger
类,性能会有所不同吗?我能想到的唯一原因是,可能仅出于模块化目的添加了DerivedLogger
类?有人可以帮助我了解原因吗?
谢谢!
答案 0 :(得分:5)
这个非常有趣,感谢您指出!
简而言之:与代码结构无关,它是一种性能优化。该评论指出:
创建一个新的类派生记录器,可以将其级别附加到其原型。这是V8优化,众所周知可以提高原型功能的性能。
我个人认为这需要引用(并且我不会在代码审查中接受)。幸运的是,我思考我发现了作者正在谈论的“优化”:
Mathias(致力于V8的Google工程师)的 This article讨论了如何正确使用prototype
来加速JavaScript执行。这篇文章有很多详细信息,如果您正在学习的话,真的很值得阅读。
在温斯顿发现的优化归结为:
在
getAttribute()
上找到Element.prototype
方法。这意味着每次调用anchor.getAttribute()
时,JavaScript引擎都需要...
- 检查
getAttribute
不在锚对象本身上,- 检查直接原型是否为
HTMLAnchorElement.prototype
- 在此处断言
getAttribute
的缺失- 检查下一个原型是
HTMLElement.prototype
,- 也确认那里没有
getAttribute
,- 最终检查下一个原型是否为
Element.prototype
,- 并且那里有
getAttribute
。总共7张支票!由于这类代码在网络上非常普遍,因此引擎运用了一些技巧来减少为原型加载属性所需的检查次数。
这大致上适用于Winston,如下所述:
prototype
对象上定义了类上的prototype
附着。prototype
链向上移动到下一个 并在那里查看prototype
链的末尾(并引发错误)为止。通过在构造函数中运行prototype
,将级别方法直接附加到特定记录器实现 instance 的原型。这意味着类层次结构可以任意增大: _setupLevels()
链查找只需要第一步就可以找到方法
这是另一个(简化的)示例:
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
则向下多层。
我个人认为这是不好的做法:
不要弄乱原型
关于模块化:对于所有扩展都具有清晰的基类,可以说。
在比JavaScript更严格的语言中,此类可以提供仅用于扩展的特定方法,这些方法对于消费者而言是公共API所隐藏的。但是,在这种情况下,testInherit
本身就可以了。