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

tit*_*300 4 javascript node.js winston

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

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

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

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

谢谢!

Luk*_*uth 5

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


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

创建一个新的类派生记录器,其级别可以附加到原型.这是一个众所周知的V8优化,可以提高原型功能的性能.

就个人而言,我认为这需要引用(我不会在代码审查中接受它).幸运的是,我认为我找到了作者所说的"优化":

Mathias(一位在V8上工作的Google工程师)的这篇文章讨论了如何通过正确使用来加速JavaScript执行prototype.这篇文章有很多细节,如果你正在学习,真的值得一读.

Winston中的优化归结为:

getAttribute()方法见于Element.prototype.这意味着每次调用时anchor.getAttribute(),JavaScript引擎都需要......

  • 检查getAttribute锚对象本身不是,
  • 检查直接原型是HTMLAnchorElement.prototype,
  • 断言没有getAttribute,
  • 检查下一个原型是HTMLElement.prototype,
  • 声称没有getAttribute那里,
  • 最终检查下一个原型是Element.prototype,
  • 而且getAttribute那里有.

总共有7张支票!由于这种代码在Web上很常见,因此引擎会应用技巧来减少原型上属性加载所需的检查次数.

大致适用于温斯顿,如下:

  • 方法是在定义prototype所述类别的-object
  • 每当一个方法被调用上的情况下,发动机需要找到prototype被叫方法连接到.
  • 在这样做时,它需要的原型实例 -Class你正在调用该方法并检查,以寻找被叫方法上它的prototype
  • 如果它找不到它(例如因为该方法继承的),它会将prototype-chain转到下一个并查看那里
  • 这种情况一直持续到找到方法(并按顺序执行)或prototype达到-chain 的结尾(并抛出错误).

通过_setupLevels()在构造函数中运行,level-methods直接附加到特定记录器实现实例的原型.这意味着,类层次结构可以尽量增大:prototypeα链查询只需要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();
Run Code Online (Sandbox Code Playgroud)

如果我们在test实例化之后设置断点,我们会看到以下内容:

在此输入图像描述

正如您所看到的,testDirect-method直接附加到test,而testInherit多个级别下降.


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

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

不要乱用原型


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

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