Я исследовал производительность 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 в трех разных браузерах. Эти результаты в миллисекундах.
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 несколько раз?