在派生类而不是类本身上添加新方法有什么好处?

时间:2018-12-28 21:04:40

标签: javascript node.js winston

出于个人学习目的,我试图了解Winston的包装设计结构及其每个模块背后的目的,但我无法弄清楚这一点。

在Winstons软件包中,logger.js模块中有一个核心Logger类,该类实现了记录器的主要功能并提供了一些公共方法,例如logger.log方法。它还实现了内部使用的转换流方法。

然后在create-logger.js模块中有一个名为DerivedLogger的派生类,它扩展了Logger类,似乎其唯一目的是在记录器原型中添加优化级别的方法。然后,该DerivedLogger类将在模块底部的工厂函数中实例化并导出。

我的问题是,为什么需要DerivedLogger类?如果将这些级别的方法添加到Logger类原型本身上,然后让工厂函数直接实例化Logger类,性能会有所不同吗?我能想到的唯一原因是,可能仅出于模块化目的添加了DerivedLogger类?有人可以帮助我了解原因吗?

谢谢!

1 个答案:

答案 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对象上定义了上的
  • 方法
  • 每次在实例上调用方法时,引擎都需要找到被调用 method 是的prototype附着。
  • 这样做,它将获取您正在调用方法的 instance 类的原型,并检查其{{1上的被调用 method }}
  • 如果找不到它(例如,因为方法继承的),它将沿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(); 之后设置断点,则会看到以下内容:

enter image description here

如您所见,test方法直接附加到testDirect,而test则向下多层。


个人认为这是不好的做法:

  • 现在可能是真实的,但将来可能并非如此。如果V8在内部对此进行优化,则当前的“优化”可能会明显变慢。
  • 声明是一种优化,如果没有概要分析和资料来源,则没有任何价值。
  • 理解起来很复杂(请参阅此问题)
  • 这样做实际上会损害性能。链接文章的结论如下:
  

不要弄乱原型


关于模块化:对于所有扩展都具有清晰的基类,可以说。

在比JavaScript更严格的语言中,此类可以提供仅用于扩展的特定方法,这些方法对于消费者而言是公共API所隐藏的。但是,在这种情况下,testInherit本身就可以了。