Javascript 添加带有原型方法的新类是如何在JS中实现V8优化的?
我正在阅读温斯顿的代码库,他们的代码库中有一条评论说: 创建一个新的类派生记录器,其级别可以附加到的原型。这是一个众所周知的V8优化,可以提高原型函数的性能Javascript 添加带有原型方法的新类是如何在JS中实现V8优化的?,javascript,node.js,v8,winston,Javascript,Node.js,V8,Winston,我正在阅读温斯顿的代码库,他们的代码库中有一条评论说: 创建一个新的类派生记录器,其级别可以附加到的原型。这是一个众所周知的V8优化,可以提高原型函数的性能 根据我在这里收集的信息,他们说添加一个新类(DerivedLogger)来添加原型方法是众所周知的V8优化形式?它与只将方法添加到类的原型Logger,而不必创建新类有何不同?如果我误解了这里的评论,有人能帮助我理解这个概念或纠正我吗?谢谢大家! 这是一个有趣的问题 前言 如果没有任何关于“众所周知的”业绩提升“的细节,我们只能猜测这是什么
根据我在这里收集的信息,他们说添加一个新类(DerivedLogger)来添加原型方法是众所周知的V8优化形式?它与只将方法添加到类的原型
Logger
,而不必创建新类有何不同?如果我误解了这里的评论,有人能帮助我理解这个概念或纠正我吗?谢谢大家! 这是一个有趣的问题
前言
如果没有任何关于“众所周知的”业绩提升“的细节,我们只能猜测这是什么意思
记录器的历史记录
当我第一次看到你的问题和代码时,我意识到代码注释一定过时了
class DerivedLogger extends Logger {
/**
* 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.
* @param {!Object} options - Options for the created logger.
*/
constructor(options) {
super(options);
this._setupLevels();
}
// ...
}
module.exports = (opts = { levels: config.npm.levels }) => (
new DerivedLogger(opts)
);
这在某种程度上是错误的,因为通过构造函数中的\u setupLevels()
调用,方法是在实例上定义的,而不是在原型上定义的。有关该主题的详细信息,请参见或
所以我翻阅了历史,找到了第一次出现的代码没有改变的注释
这是添加上述注释时原始代码的样子:
// Create a new instance of a winston Logger. Creates a new
// prototype for each instance.
module.exports = function (opts) {
// ...
//
// Create a new prototypal 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.
//
function DerivedLogger(options) { Logger.call(this, options); }
util.inherits(DerivedLogger, Logger);
// ...
DerivedLogger.prototype[level] = function (msg) {
当前代码以另一种方式更改:不再使用每个记录器实例创建DerivedLogger
,而是在加载模块时仅创建一次
分析
直到这里,我才意识到Winston的作者在logger的create函数中创建了新的原型:
// Create a new instance of a winston Logger. Creates a new
// prototype for each instance.
//
module.exports = function (opts) {
因此,当要创建一个新的记录器时,不仅要创建一个实例,还要创建一个全新的原型
派生的记录器不会被重用
结论
最初的意图是在创建新的记录器实例时防止修改/污染记录器
(A)
尽管在原型上创建记录器方法以防止在重复实例方法中浪费内存(请参阅开头的链接问题)似乎因重复创建新原型而受阻
我甚至相信,创建一个原型而不是直接为实例定义日志方法所获得的性能会被原型对象的创建所吞噬
然而,我不是100%确信所讨论的解释是原意,我愿意进行更正和澄清
奖金
(我在研究中发现了这一点,可能与上述温斯顿代码无关。)
由于最初的作者声称在原型上定义方法会优化V8的性能,这让我很烦恼,因此我开始搜索有关此主题的更新,并找到了V8开发人员Mathias Bynens的一篇文章:
他正在讨论大多数Javascript引擎(不仅仅是V8!)如何在内部存储对象,以及它们如何处理属性访问。你可能还想读另一本
在这里,我不会详细地重述这一点,尽管V8中似乎有一个独特的细节,即如何处理原型链上的访问:
V8专门为此目的处理原型形状。每个原型都有一个不与任何其他对象共享的唯一形状(特别是不与其他原型共享),并且每个原型形状都有一个与之关联的特殊ValidityCell
。每当有人更改关联的原型或其上的任何原型时,此
ValidityCell
无效。[…]
下次命中内联缓存时,引擎必须检查实例的形状和
ValidityCell
。如果它仍然有效,引擎可以直接到达原型上的偏移量,跳过额外的查找
(我用粗体字。)
因此,V8的独特之处似乎在于,它们跟踪原型是否仍然“成型”。这允许V8减少原型链处理中涉及的检查。仅作记录:在852337e51f0df03fec85f955aa6b4e9833b3bd00中。在那之前,它确实改变了原型。正如前面所说的,这条评论完全是错误的。注释可能重复,重复的答案包含一个指向JSperf比较的链接:@try catch finally ok,这很有意义。我现在的问题是,是否需要创建一个新类来添加这些方法?我们不能在Logger类本身上添加这些方法吗?或者,除了代码可读性和模块性之外,这还会有什么不同吗?谢谢谢谢你的详细回答。奖金部分真的很有帮助。所以我知道他们需要一个新的派生类来允许在原型中修改每个新的DerivedLogger实例,而不必创建或接触扩展的Loggers原型?此外,当您说“不再使用每个记录器实例创建DerivedLogger,而是在加载模块时仅创建一次。”您确定是这样吗?因为底部有一个工厂函数,它在每次调用createLogger时创建一个新实例,而不是一次。是的,实例是由工厂函数创建的,而不是派生的logger类/原型。尽管这是他们最初做的,并且仍然保留在注释中:
“创建一个新的类派生记录器”
(这不再是真的)。“因此,我理解他们需要一个新的派生类,以便在原型中为每个新的DerivedLogger实例进行修改,而不必创建或触摸扩展的Logger原型”
-是的。尽管我强烈怀疑这与实例对象上的(重复)定义相比有任何性能提升(就像在两个链接的问题中)好的,我明白了。是的,所以我只是想知道为什么不把level方法添加到Logger
prototype本身?为什么要添加一个新类D
[Logger] (A)
^
|
+---------+--------+
| |
[DerivedLogger #1] [DerivedLogger #2] (B)
| |
logger #1 logger #2