在派生 class 而不是 class 本身上添加新方法有什么好处?
What are the benefits of adding new methods on a derived class rather than the class itself?
出于个人学习目的,我试图了解 Winston 的包设计结构及其每个模块背后的目的,但我无法理解这一点。
在Winstons包中,logger.js模块中有核心Logger
class,实现了logger的主要功能,并提供了一些public方法,例如logger.log
方法。它还实现了供内部使用的转换流方法。
然后在 create-logger.js 模块中有一个派生的 class 名为 DerivedLogger
扩展了 Logger class 并且它的唯一目的似乎是添加优化级别方法到记录器原型。 DerivedLogger
class 然后在模块底部的工厂函数中实例化和导出。
我的问题是,为什么需要 DerivedLogger
class?如果将这些级别方法添加到 Logger
class 原型本身,然后让工厂函数直接实例化 Logger
class,性能会有什么不同吗?我能想到的唯一原因是,也许添加 DerivedLogger
class 只是为了模块化目的?有人可以帮我理解原因吗?
谢谢!
这个很有趣,谢谢指出!
简而言之:与代码结构无关,是性能优化。评论说:
Create a new class derived logger for which the levels can be attached to the prototype of. This is a V8 optimization that is well know to increase performance of prototype functions.
就我个人而言,我认为这需要引用(我不会在代码审查中接受它)。幸运的是,我认为我找到了作者所说的"optimization":
Mathias 的 This article(一位从事 V8 工作的 Google 工程师)谈到通过正确使用 prototype
来加速 JavaScript 执行。这篇文章有 很多 的详细信息,如果您正在学习,真的值得一读。
Winston 中发现的优化归结为:
The getAttribute()
method is found on the Element.prototype
. That means each time we call anchor.getAttribute()
, the JavaScript engine needs to…
- check that
getAttribute
is not on the anchor object itself,
- check that the direct prototype is
HTMLAnchorElement.prototype
,
- assert absence of
getAttribute
there,
- check that the next prototype is
HTMLElement.prototype
,
- assert absence of
getAttribute
there as well,
- eventually check that the next prototype is
Element.prototype
,
- and that
getAttribute
is present there.
That’s a total of 7 checks! Since this kind of code is pretty common on the web, engines apply tricks to reduce the number of checks necessary for property loads on prototypes.
这大致适用于Winston如下:
- 类 上的方法 是在上述 class 的
prototype
对象上定义的
- 每次在 实例 上调用 方法 时,引擎需要找到被调用的
prototype
方法附于.
- 在这样做时,它会获取您正在调用该方法的 实例 -class 的原型,并检查以找到被调用的 方法 是
prototype
- 如果找不到它(例如因为 方法 是 继承的 ),它会向上走
prototype
-链接到下一个 class 并查看那里
- 这一直持续到找到方法(并按顺序执行)或到达
prototype
链的末尾(并抛出错误)。
通过 运行 _setupLevels()
在构造函数中,级别方法直接附加到特定记录器实现的原型 实例 。这意味着 class-层次结构可以增长到任意大: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();
如果我们在 test
实例化后设置断点,我们会看到以下内容:
如您所见,testDirect
-方法直接附加到 test
,而 testInherit
向下多级。
我个人认为这是不好的做法:
- 现在可能是这样,但将来可能就不是这样了。如果 V8 在内部对此进行优化,当前的 "optimization" 可能会显着变慢。
- 如果没有分析和来源,声称这是一种优化就没有任何价值。
- 理解起来比较复杂(看这个问题)
- 这样做实际上会影响性能。链接文章的结论是:
don’t mess with prototypes
至于模块化:对于所有扩展都有一个明确的基础-class 有话要说。
在比 JavaScript 更严格的语言中,这样的 class 可以提供仅用于扩展的特定方法,这些方法对 public API 隐藏消费者。然而,在这种特定情况下,Logger
本身就没问题。
出于个人学习目的,我试图了解 Winston 的包设计结构及其每个模块背后的目的,但我无法理解这一点。
在Winstons包中,logger.js模块中有核心Logger
class,实现了logger的主要功能,并提供了一些public方法,例如logger.log
方法。它还实现了供内部使用的转换流方法。
然后在 create-logger.js 模块中有一个派生的 class 名为 DerivedLogger
扩展了 Logger class 并且它的唯一目的似乎是添加优化级别方法到记录器原型。 DerivedLogger
class 然后在模块底部的工厂函数中实例化和导出。
我的问题是,为什么需要 DerivedLogger
class?如果将这些级别方法添加到 Logger
class 原型本身,然后让工厂函数直接实例化 Logger
class,性能会有什么不同吗?我能想到的唯一原因是,也许添加 DerivedLogger
class 只是为了模块化目的?有人可以帮我理解原因吗?
谢谢!
这个很有趣,谢谢指出!
简而言之:与代码结构无关,是性能优化。评论说:
Create a new class derived logger for which the levels can be attached to the prototype of. This is a V8 optimization that is well know to increase performance of prototype functions.
就我个人而言,我认为这需要引用(我不会在代码审查中接受它)。幸运的是,我认为我找到了作者所说的"optimization":
Mathias 的This article(一位从事 V8 工作的 Google 工程师)谈到通过正确使用 prototype
来加速 JavaScript 执行。这篇文章有 很多 的详细信息,如果您正在学习,真的值得一读。
Winston 中发现的优化归结为:
The
getAttribute()
method is found on theElement.prototype
. That means each time we callanchor.getAttribute()
, the JavaScript engine needs to…
- check that
getAttribute
is not on the anchor object itself,- check that the direct prototype is
HTMLAnchorElement.prototype
,- assert absence of
getAttribute
there,- check that the next prototype is
HTMLElement.prototype
,- assert absence of
getAttribute
there as well,- eventually check that the next prototype is
Element.prototype
,- and that
getAttribute
is present there.That’s a total of 7 checks! Since this kind of code is pretty common on the web, engines apply tricks to reduce the number of checks necessary for property loads on prototypes.
这大致适用于Winston如下:
- 类 上的方法 是在上述 class 的
prototype
对象上定义的 - 每次在 实例 上调用 方法 时,引擎需要找到被调用的
prototype
方法附于. - 在这样做时,它会获取您正在调用该方法的 实例 -class 的原型,并检查以找到被调用的 方法 是
prototype
- 如果找不到它(例如因为 方法 是 继承的 ),它会向上走
prototype
-链接到下一个 class 并查看那里 - 这一直持续到找到方法(并按顺序执行)或到达
prototype
链的末尾(并抛出错误)。
通过 运行 _setupLevels()
在构造函数中,级别方法直接附加到特定记录器实现的原型 实例 。这意味着 class-层次结构可以增长到任意大: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();
如果我们在 test
实例化后设置断点,我们会看到以下内容:
如您所见,testDirect
-方法直接附加到 test
,而 testInherit
向下多级。
我个人认为这是不好的做法:
- 现在可能是这样,但将来可能就不是这样了。如果 V8 在内部对此进行优化,当前的 "optimization" 可能会显着变慢。
- 如果没有分析和来源,声称这是一种优化就没有任何价值。
- 理解起来比较复杂(看这个问题)
- 这样做实际上会影响性能。链接文章的结论是:
don’t mess with prototypes
至于模块化:对于所有扩展都有一个明确的基础-class 有话要说。
在比 JavaScript 更严格的语言中,这样的 class 可以提供仅用于扩展的特定方法,这些方法对 public API 隐藏消费者。然而,在这种特定情况下,Logger
本身就没问题。