这个很有意思,谢谢指出!
简而言之:它与代码结构无关,它是一种性能优化。评论说:
创建一个新的类派生记录器,其级别可以附加到其原型。这是一种众所周知的 V8 优化,可以提高原型函数的性能。
就我个人而言,我认为这需要引用(我不会在代码审查中接受它)。幸运的是,我认为我找到了作者所说的“优化”:
This article Mathias(一位从事 V8 工作的 Google 工程师)谈到了通过正确使用 prototype 来加速 JavaScript 执行。这篇文章有很多的细节,如果你正在学习,真的值得一读。
在 Winston 中发现的优化归结为:
getAttribute() 方法可在 Element.prototype 上找到。这意味着每次我们调用anchor.getAttribute(),JavaScript 引擎都需要……
- 检查
getAttribute 不在锚对象本身上,
- 检查直接原型是
HTMLAnchorElement.prototype,
- 在那里断言
getAttribute 不存在,
- 检查下一个原型是
HTMLElement.prototype,
- 在那里也断言
getAttribute 不存在,
- 最终检查下一个原型是
Element.prototype,
- 并且
getAttribute 存在于那里。
总共有 7 次检查!由于这种代码在网络上很常见,引擎会使用一些技巧来减少原型属性加载所需的检查次数。
这大致适用于 Winston,如下所示:
-
类上的方法在所述类的
prototype-object上定义
- 每次在实例上调用方法时,引擎都需要找到被调用的方法附加到的
prototype .
- 这样做时,它会获取您正在调用该方法的 instance 类的原型,并检查在其
prototype 上找到被调用的方法
- 如果找不到它(例如,因为 方法 是继承),它会沿着
prototype 链向上移动到下一个类 看看那里
- 此过程一直持续到找到方法(并按子顺序执行)或到达
prototype-chain 的末尾(并引发错误)。
通过在构造函数中运行_setupLevels(),级别方法直接附加到特定记录器实现的原型实例。这意味着类层次结构可以任意增长:prototype-chain 查找只需 1 步即可找到方法
这是另一个(简化的)示例:
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 是多个层次。
我个人认为这是不好的做法:
- 这可能现在是真的,但将来可能不是真的。如果 V8 在内部对此进行优化,当前的“优化”可能会明显变慢。
- 如果没有分析和来源,声称它是一种优化是毫无价值的。
- 理解起来很复杂(请参阅此问题)
- 这样做实际上会损害性能。链接文章的结论是:
不要乱用原型
至于模块化:对于所有扩展都有一个清晰的基类是有好处的。
在比 JavaScript 更严格的语言中,这样的类可以提供仅用于扩展的特定方法,这些方法对消费者的公共 API 是隐藏的。但是,在这种特定情况下,Logger 本身就可以了。