我一直在研究JavaScript性能。我已经了解到,在不止一次访问时,通常最好将闭包变量和类成员复制到本地范围以加快速度。例如:
var i = 100;
var doSomething = function () {
var localI = i;
// do something with localI a bunch of times
var obj = {
a: 100
};
var objA = obj.a;
// do something with objA a bunch of times
};
我理解这一点;它为解释器添加了一个快捷方式,用于按名称查找值。在处理方法时,这个概念变得非常不清楚。起初,我认为它会以同样的方式工作。例如:
var obj = {
fn: function () {
// Do something
return this.value;
},
value: 100
};
var objFn = obj.fn
objFn();
// call objFn a bunch of times
实际上,这根本不起作用。访问这样的方法会将其从其范围中删除。当它到达this.value行时,这指的是window对象,this.value可能是未定义的。我可以使用objFn.call(obj)将其范围传回到它中,而不是直接调用objFn和丢失范围,但这是否比原始的obj.fn()更好或更差?
我决定编写一个脚本来测试这个,我得到了非常令人困惑的结果。该脚本对多个测试进行迭代,这些测试循环执行上述函数调用多次。每次测试的平均时间输出到身体。
使用许多简单方法创建对象。额外的方法用于确定解释器是否必须更加努力地找到特定的方法。
测试1只需调用this.a();
测试2创建一个局部变量a = this.a然后调用a.call(this);
测试3使用YUI的绑定函数创建局部变量以保留范围。我评论了这一点。 YUI创建的额外函数调用使这种方式变慢。
测试4,5和6是1,2,3的副本,除了使用z代替a。
YUI的后续功能用于防止失控的脚本错误。时间在实际测试方法中完成,因此setTimeouts不应影响结果。每个函数总共调用10000000次。 (如果要运行测试,可以轻松配置。)
这是我以前测试的整个XHTML文档。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr">
<head>
<script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var o = {
value: '',
a: function () {
this.value += 'a';
},
b: function () {
this.value += 'b';
},
c: function () {
this.value += 'c';
},
d: function () {
this.value += 'd';
},
e: function () {
this.value += 'e';
},
f: function () {
this.value += 'f';
},
g: function () {
this.value += 'g';
},
h: function () {
this.value += 'h';
},
i: function () {
this.value += 'i';
},
j: function () {
this.value += 'j';
},
k: function () {
this.value += 'k';
},
l: function () {
this.value += 'l';
},
m: function () {
this.value += 'm';
},
n: function () {
this.value += 'n';
},
o: function () {
this.value += 'o';
},
p: function () {
this.value += 'p';
},
q: function () {
this.value += 'q';
},
r: function () {
this.value += 'r';
},
s: function () {
this.value += 's';
},
t: function () {
this.value += 't';
},
u: function () {
this.value += 'u';
},
v: function () {
this.value += 'v';
},
w: function () {
this.value += 'w';
},
x: function () {
this.value += 'x';
},
y: function () {
this.value += 'y';
},
z: function () {
this.value += 'z';
},
reset: function () {
this.value = '';
},
test1: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.a();
}
return new Date().getTime() - time;
},
test2: function (length) {
var a = this.a,
time = new Date().getTime();
while ((length -= 1)) {
a.call(this);
}
return new Date().getTime() - time;
},
test3: function (length) {
var a = Y.bind(this.a, this),
time = new Date().getTime();
while ((length -= 1)) {
a();
}
return new Date().getTime() - time;
},
test4: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.z();
}
return new Date().getTime() - time;
},
test5: function (length) {
var z = this.z,
time = new Date().getTime();
while ((length -= 1)) {
z.call(this);
}
return new Date().getTime() - time;
},
test6: function (length) {
var z = Y.bind(this.z, this),
time = new Date().getTime();
while ((length -= 1)) {
z();
}
return new Date().getTime() - time;
}
},
iterations = 100, iteration = iterations, length = 100000,
t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body');
body.set('innerHTML', '<span>Running ' + iterations + ' Iterations…</span>');
while ((iteration -= 1)) {
Y.later(1, null, function (iteration) {
Y.later(1, null, function () {
o.reset();
t1 += o.test1(length);
});
Y.later(1, null, function () {
o.reset();
t2 += o.test2(length);
});
/*Y.later(1, null, function () {
o.reset();
t3 += o.test3(length);
});*/
Y.later(1, null, function () {
o.reset();
t4 += o.test4(length);
});
Y.later(1, null, function () {
o.reset();
t5 += o.test5(length);
});
/*Y.later(1, null, function () {
o.reset();
t6 += o.test6(length);
});*/
if (iteration === 1) {
Y.later(10, null, function () {
t1 /= iterations;
t2 /= iterations;
//t3 /= iterations;
t4 /= iterations;
t5 /= iterations;
//t6 /= iterations;
//body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 3: a();</dt><dd>' + t3 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd><dt>Test 6: z();</dt><dd>' + t6 + '</dd></dl>');
body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd></dl>');
});
}
}, iteration);
}
});
</script>
</head>
<body>
</body>
</html>
我在三种不同的浏览器中使用Windows 7运行此操作。这些结果以毫秒为单位。
Firefox 3.6.8
Test 1: this.a();
9.23
Test 2: a.call(this);
9.67
Test 4: this.z();
9.2
Test 5: z.call(this);
9.61
Chrome 7.0.503.0
Test 1: this.a();
5.25
Test 2: a.call(this);
4.66
Test 4: this.z();
3.71
Test 5: z.call(this);
4.15
Internet Explorer 8
Test 1: this.a();
168.2
Test 2: a.call(this);
197.94
Test 4: this.z();
169.6
Test 5: z.call(this);
199.02
Firefox和Internet Explorer产生了我的预期结果。测试1和测试4相对接近,测试2和测试5相对接近,测试2和测试5比测试1和测试4花费更长时间,因为有一个额外的函数调用处理。
Chrome我根本不懂,但速度要快得多,也许不需要调整亚毫秒的性能。
有没有人对结果有很好的解释?多次调用JavaScript方法的最佳方法是什么?
答案 0 :(得分:2)
只是理论化,所以带上一粒盐......
Chrome的Javascript引擎V8使用了一种称为隐藏类的优化技术。基本上,它构造了影响动态Javascript对象的静态对象,其中每个属性/方法都映射到一个固定的内存地址,可以立即引用而不需要昂贵的表查找操作。每次Javascript对象添加/删除属性时,都会创建一个新的隐藏类。
我对Chrome测试结果的理论是,在自由局部变量中引用该函数会破坏隐藏的类关系。虽然引用局部变量可能也不需要查表,但现在必须在重新分配'this'变量时执行额外的步骤。对于隐藏类的方法,'this'是固定值,因此可以在没有此步骤的情况下调用它。
再次理论化。测试局部变量引用与Chrome中的object.member引用之间的差异可能值得进行测试,以查看后者对性能的影响是否显着低于其他浏览器,可能是因为隐藏类。
答案 1 :(得分:1)
嗯,只要您的网站将IE8用户作为访问者,这就无关紧要了。使用1或3(用户不会看到差异)。
“为什么”问题可能没有一个好的答案。在优化方面,这些脚本引擎可能会专注于优化他们认为在现实生活中发生很多事件的场景,其中优化可以被证明是正确的,并且它会产生影响,并且会使其失效。最少量的测试。
答案 2 :(得分:1)
一般来说,如果你没有知道,那么你做的任何事情都不会有所作为。 (“很多”我的意思是很大比例。)
Here's an easy way to find out which code is responsible for much time.