基准:
不变量:
var f = function() { };
var g = function() { return this; }
测试:
以下按预期速度的顺序
new f;
g.call(Object.create(Object.prototype));
new (function() { })
(function() { return this; }).call(Object.create(Object.prototype));
实际速度:
new f;
g.call(Object.create(Object.prototype));
(function() { return this; }).call(Object.create(Object.prototype));
new (function() { })
问题:
f
和g
交换为内联匿名函数时。为什么new
(测试4.)测试速度较慢?更新
在new
和f
内联时,g
会导致{{1}}更慢的原因。
我对ES5规范的引用或对JagerMonkey或V8源代码的引用感兴趣。 (请随意链接JSC和Carakan源代码。哦,如果他们愿意,IE团队可以泄漏Chakra源。)
如果你链接任何JS引擎源,请解释它。
答案 0 :(得分:15)
#4和所有其他情况之间的主要区别在于,第一次使用闭包作为构造函数总是非常昂贵。
它始终在V8运行时(不在生成的代码中)处理,并且编译的JS代码和C ++运行时之间的转换非常昂贵。后续分配通常在生成的代码中处理。您可以查看Generate_JSConstructStubHelper
中的builtins-ia32.cc
,并注意当关闭没有初始地图时,Runtime_NewObject
会落到{{1}}。 (见http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138)
当闭包第一次用作构造函数时,V8必须创建一个新的地图(也称为隐藏类)并将其指定为该闭包的初始地图。见http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266。这里重要的是地图分配在一个单独的内存空间中。快速部分清除收集器无法清除此空间。当地图空间溢出时,V8必须执行相对昂贵的完整标记扫描 GC。
第一次使用闭包作为构造函数时,还有其他一些事情发生,但是1和2是测试用例#4缓慢的主要原因。
如果我们比较表达式#1和#4,那么差异是:
如果我们比较#3和#4,那么差异是:
这里的底线是,与相同闭包的后续构造调用相比,第一次使用闭包作为构造函数是昂贵的,因为V8必须设置一些管道。如果我们立即丢弃闭包,我们基本上抛弃了V8为加速后续构造函数调用所做的所有工作。
答案 1 :(得分:5)
问题是您可以检查各种引擎的当前源代码,但它对您没有多大帮助。不要试图超越编译器。无论如何,他们会尝试优化最常见的用法。我认为(function() { return this; }).call(Object.create(Object.prototype))
调用1,000次并不具备真正的用例。
“应编写程序供人们阅读,并且只是偶然让机器执行。”
Abelson& Sussman,SICP,第一版序言
答案 2 :(得分:3)
我想以下扩展解释了V8中发生了什么:
t(exp4):t(对象创建)+ t(功能对象创建)+ t(类对象创建)[在Chrome中]
答案 3 :(得分:0)
嗯,这两个电话并没有做同样的事情。考虑这种情况:
var Thing = function () {
this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;
var Rock = function () {
this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;
var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);
newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false
因此,我们可以看到调用Rock.call(otherRock)
不会导致otherRock
继承原型。这必须考虑至少一些增加的缓慢。虽然在我的测试中,即使在这个简单的例子中,new
结构也慢了近30倍。
答案 4 :(得分:0)
new f;
现在这个:
g.call(Object.create(Object.prototype));
Object
- 便宜吗?prototype
- 通用create
- 通用call
- 一般情况create
函数 - 通用call
函数 - 通用而且:
new (function() { })
如你所见,案例#1耗费最少。