V8 开发人员在这里。
是的,一般来说,第一个版本是值得的最佳实践。
这样做的原因并不是对象创建本身会更快。相反,很明显,不做任何工作的构造函数至少会比做一些工作的构造函数快一点。
推荐第一个版本的原因是因为它确保Foo
应用程序中的所有对象都具有相同的“形状”,而在第二个版本中,可能会发生其中一些具有.bar
属性而另一些没有的情况。有时存在但有时不会迫使 JavaScript 引擎远离它可以使用的最快状态/代码路径的属性;当有多个这样的属性时,效果会更大。
举个例子:
class Foo() {
constructor() {}
addBar(x) { this.bar = x; }
addBaz(x) { this.baz = x; }
addQux(x) { this.qux = x; }
}
var foo1 = new Foo(); foo1.addBar(1);
var foo2 = new Foo(); foo2.addBaz(10); foo2.addBar(2);
var foo3 = new Foo(); foo3.addQux(100); foo3.addBaz(20); foo3.addBar(3);
function hot_function(foo) {
return foo.bar; // [1]
}
hot_function(foo1);
hot_function(foo2);
hot_function(foo3);
在标记为 的行[1]
处,使用此版本的构造函数,可以看到至少三种不同形状的对象。所以 JavaScript 引擎会bar
在对象中至少三个不同的地方找到属性。根据其内部实现细节,它可能每次都必须搜索所有对象的属性,或者它可以缓存它以前见过的对象形状,但是缓存几个比缓存一个更昂贵,并且缓存尝试会有限制. 但是,如果构造函数已将所有属性初始化为undefined
,那么这里所有传入foo
的对象都将具有相同的形状,并且该bar
属性将始终是它们的第一个属性,并且引擎可以使用非常快速的代码来处理这种非常简单的情况。
不仅仅是这样的负载:addBar()
根据它是否可以简单地覆盖现有属性(非常快),是否必须添加新属性(可能慢得多,可能需要分配和复制对象),引擎盖下的内容也会有所不同,或者必须在两种情况之间动态决定(当然是最慢的)。
另一个影响是每个独特的对象形状都需要一些内部元数据。因此,避免不必要的不同对象形状将节省一些内存。
当然对于这样一个小例子,任何影响都会很小。但是,一旦您拥有一个包含数千个对象且每个对象具有数十个属性的大型应用程序,它就会产生很大的不同。谨防误导性的微基准!