我一直在研究JavaScript的性能。我瞭解到,當訪問不止一次時,通常最好將閉包變量和類成員複製到本地範圍以加快速度。例如:微調性能時,多次調用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行時,這指的是窗口對象,this.value可能是未定義的。而不是直接調用objFn並丟失作用域,我可以通過objFn.call(obj)將它的作用域傳回給它,但是這樣做會比原來的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運行此操作。這些結果以毫秒爲單位。
火狐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方法的最佳方式?
可以添加此鏈接在你的問題,以便其他用戶可以運行測試本身? - http://jsfiddle.net/Lbbx5/ – Anurag 2010-08-28 21:34:42
相關閱讀:[JavaScript Widgets Without「this」](http://michaux.ca/articles/javascript-widgets-without-this) – 2010-08-28 21:41:38
僅供參考,使用Chromium(Linux)在我的(相對較慢的)系統上,經過幾次測試後,結果與Firefox中的結果大致相同(僅更快):組合1/4和2/5或多或少接近,2和5比1和4. – 2010-08-28 21:50:24