Несколько источников для JS-рекомендаций по производительности побуждают разработчиков сократить "поиск по цепочке цепей". Например, IIFE рекламируются как имеющие бонусное преимущество "уменьшения поиска цепочки объектов" при доступе к глобальным переменным. Это звучит вполне логично, возможно, даже воспринимается как должное, поэтому я не стал подвергать сомнению мудрость. Как и многие другие, я с радостью использовал IIFE, думая, что помимо предотвращения глобального загрязнения пространства имен, это будет повышение производительности по любому глобальному коду.
Что мы ожидаем сегодня:
(function($, window, undefined) {
// apparently, variable access here is faster than outside the IIFE
})(jQuery, window);
Упрощая/распространяя это на обобщенный случай, можно было бы ожидать:
var x = 0;
(function(window) {
// accessing window.x here should be faster
})(window);
Основываясь на моем понимании JS, в глобальной области нет разницы между x = 1;
и window.x = 1;
. Поэтому логично ожидать, что они будут одинаково результативными, не так ли? НЕПРАВИЛЬНО. Я провел несколько тестов и обнаружил существенную разницу в времени доступа.
Хорошо, возможно, если я поместил window.x = 1;
внутри IIFE, он должен работать еще быстрее (хотя бы немного), правильно? НЕПРАВИЛЬНО.
Хорошо, может быть, это Firefox; попробуйте Chrome вместо этого (V8 является эталоном скорости JS, да?) Он должен бить Firefox для простых вещей, таких как доступ к глобальной переменной напрямую, не так ли? НЕПРАВИЛЬНО еще раз.
Итак, я решил выяснить, какой именно метод доступа является самым быстрым, в каждом из двух браузеров. Итак, скажем, мы начинаем с одной строки кода: var x = 0;
. После того, как x
был объявлен (и с радостью присоединен к window
), какой из этих методов доступа будет самым быстрым и почему?
-
Непосредственно в глобальной области
x = x + 1;
-
Непосредственно в глобальной области, но с префиксом
window
window.x = window.x + 1;
-
Внутри функции неквалифицирован
function accessUnqualified() { x = x + 1; }
-
Внутри функции с префиксом
window
function accessWindowPrefix() { window.x = window.x + 1; }
-
Внутри функции, окно кеша как переменная, префикс доступа (имитирует локальный параметр IIFE).
function accessCacheWindow() { var global = window; global.x = global.x + 1; }
-
Внутри IIFE (окно как параметр), префикс доступа.
(function(global){ global.x = global.x + 1; })(window);
-
Внутри IIFE (окно как параметр), неквалифицированный доступ.
(function(global){ x = x + 1; })(window);
Предположим, что контекст браузера, т.е. window
- глобальная переменная.
Я написал быстрый тест времени, чтобы цикл операции увеличения в миллион раз, и был удивлен результатами. Что я нашел:
Firefox Chrome
------- ------
1. Direct access 848ms 1757ms
2. Direct window.x 2352ms 2377ms
3. in function, x 338ms 3ms
4. in function, window.x 1752ms 835ms
5. simulate IIFE global.x 786ms 10ms
6. IIFE, global.x 791ms 11ms
7. IIFE, x 331ms 655ms
Я повторил тест несколько раз, и цифры кажутся показательными. Но они меня смущают, поскольку они, кажется, предлагают:
- префикс с
window
намного медленнее (# 2 vs # 1, # 4 vs # 3). Но ПОЧЕМУ? - доступ к глобальной функции (возможно, дополнительный просмотр области) быстрее (# 3 vs # 1). Почему??
- Почему результаты # 5, # 6, # 7 отличаются друг от друга в двух браузерах?
Я понимаю, что есть те, кто считает, что такие тесты бессмысленны для настройки производительности, и это вполне может быть правдой. Но, пожалуйста, ради знания, просто юмористируйте меня и помогите улучшить мое понимание этих простых понятий, таких как доступ к переменной и цепочка видимости.
Если вы прочли это, спасибо за ваше терпение. Извиняюсь за длинный пост и за то, что он, возможно, объединяет несколько вопросов в один - я думаю, что они все связаны друг с другом.
Изменить: Совместное использование моего тестового кода в соответствии с запросом.
var x, startTime, endTime, time;
// Test #1: x
x = 0;
startTime = Date.now();
for (var i=0; i<1000000; i++) {
x = x + 1;
}
endTime = Date.now();
time = endTime - startTime;
console.log('access x directly - Completed in ' + time + 'ms');
// Test #2: window.x
x = 0;
startTime = Date.now();
for (var i=0; i<1000000; i++) {
window.x = window.x + 1;
}
endTime = Date.now();
time = endTime - startTime;
console.log('access window.x - Completed in ' + time + 'ms');
// Test #3: inside function, x
x =0;
startTime = Date.now();
accessUnqualified();
endTime = Date.now();
time = endTime - startTime;
console.log('accessUnqualified() - Completed in ' + time + 'ms');
// Test #4: inside function, window.x
x =0;
startTime = Date.now();
accessWindowPrefix();
endTime = Date.now();
time = endTime - startTime;
console.log('accessWindowPrefix()- Completed in ' + time + 'ms');
// Test #5: function cache window (simulte IIFE), global.x
x =0;
startTime = Date.now();
accessCacheWindow();
endTime = Date.now();
time = endTime - startTime;
console.log('accessCacheWindow() - Completed in ' + time + 'ms');
// Test #6: IIFE, window.x
x = 0;
startTime = Date.now();
(function(window){
for (var i=0; i<1000000; i++) {
window.x = window.x+1;
}
})(window);
endTime = Date.now();
time = endTime - startTime;
console.log('access IIFE window - Completed in ' + time + 'ms');
// Test #7: IIFE x
x = 0;
startTime = Date.now();
(function(global){
for (var i=0; i<1000000; i++) {
x = x+1;
}
})(window);
endTime = Date.now();
time = endTime - startTime;
console.log('access IIFE x - Completed in ' + time + 'ms');
function accessUnqualified() {
for (var i=0; i<1000000; i++) {
x = x+1;
}
}
function accessWindowPrefix() {
for (var i=0; i<1000000; i++) {
window.x = window.x+1;
}
}
function accessCacheWindow() {
var global = window;
for (var i=0; i<1000000; i++) {
global.x = global.x+1;
}
}