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

问题描述 投票:4回答:1

出于个人学习的目的,我试图了解Winston的软件包设计结构以及每个模块背后的目的,但我无法理解这一点。

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

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

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

谢谢!

javascript node.js winston
1个回答
5
投票

这个非常有趣,多亏了指出来!


简而言之:它与代码结构无关,它是一种性能优化。评论说明了这一点:

创建一个新的类派生记录器,其级别可以附加到原型。这是一个众所周知的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之后设置断点,我们会看到以下内容:

enter image description here

正如你所看到的,testDirect方法直接附加到test,而testInherit是多级下来。


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

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

不要乱用原型


至于模块化:对于所有扩展都有明确的基类,有一些东西可以说。

使用比JavaScript更严格的语言,这样的类可以提供仅用于扩展的特定方法,这些方法对于消费者来说是公共API隐藏的。然而,在这个特定情况下,Logger本身就可以了。

© www.soinside.com 2019 - 2024. All rights reserved.