Pull to refresh

Comments 99

По-моему, большую часть из советов по оптимизации должны еще в школе учить к пример что быстрее цикл с постусловием или с предусловием и так далее. Касательно тонкостей работы с DOM:
1) Никогда не встречался с минисуми если удалять все подузлы простым innerHTML=''
2) С событием перетаскивания и таймаутом не всегда все так гладко, так как setTimeout имеет свои особенности…
> что быстрее цикл с постусловием или с предусловием
что-что, простите?
do {} while() всегда быстрее чем while(){}. Причина в том что while(){} использует два «goto», а do{}while() 1.
Зря минуснули. По поводу таймаутов — в старых браузерах часто были зависания при перетаскивании…
во-первых, не используется там два goto. если говорить в терминах ассемблера:
вот do — while:
label1:
… code…
cmp condition
jnz label1

и вот while:
label1:
cmp condition
jz label2
...code…
jmp label1
label2:…

если вы пройдётесь карандашом по коду, то поймёте, что хоть во втором случае и написано два джампа, сами джампы производятся одинаковое количество раз.

а во-вторых, плюс-минус один такт процессора на итерацию? делать такие вшивые оптимизации не смотря на их контекст — первый признак неопытности оптимизатора.
и это я уже молчу про то, что делать такие оптимизации в интерпретируемых языках — это вообще курам на смех.
Вы заблуждаетесь, в первом случае на один раз меньше джамп выполнется. Вы написали статью по оптимизации, вот я и описал один из способов. Конечно же данный способ не нужен в мелких задачках типо написать модальное окно на js или сделать аналог jui.sortable. Однако, данная оптимизация пригодится если вы используете цикл для обработки больших объемов данных внутри web workers. Понятное дело что задача нетривиальная и очень редкая, но, все же это азы оптимизации.
джамп выполняется на один раз меньше _за_весь_цикл_. если вы о проходе через оба джампа — это херня. у вас в первом случае при каждом проходе по JNZ будет проходить просмотр FLAGS и смена EIP. во втором случае на проходе по
по JS будет проверяться FLAGS, на проходе по JMP меняться EIP. это ни хрена не оптимизация.
Совсем не фигня на 100000 итерациях мы 100000=10^5 тактов выйграем. Понятное дело что когда этих тактов = 3*10^9 в сек разница в долю секунды мало заметно, однако если у нас 10^7 итераций, то разница думаю заметна будет. Однако это тема для отдельной статьи и не для холивара.
читайте внимательнее. я вам объяснил, что там нет лишнего такта на итерацию.

олсо, вам на будущее. командные конвейеры процессоров устроены гораздо сложнее, чем вы думаете. то, что в коде идет пять последовательных команд, не значит, что все пять будут выполнены последовательно в том же виде.
Проверил в файрбаге. Имеем вот такой код:
var i=2000000,j=0,start=new Date().getTime();
while(i--){
j++;
}
console.log('Предусловие:'+(new Date().getTime()-start));

var i=2000000,j=0,start=new Date().getTime();
do{
j++;
}while(i--);
console.log('Постусловие:'+(new Date().getTime()-start));


Результат:
Предусловие:3074
Постусловие:3038

Это при двух милиардах итераций. А если их больше ?) Так что давайте не спорить. Цифры не врут.
Причем второй вариант на одну итерацию больше выполняется и все равно быстрее…
фейспалм.жпг
Предусловие:11321
Постусловие:11321

у вас в этот момент мог процесс переключиться, или какая-нибудь задача по расписанию в ОС заработать.
вы просто непробиваемый…
Я тоже об этом подумал. Эх, замерить бы результаты в монопольном режиме… Сойдемся на том что все в руках оптимизатора движка js внутри браузера. Прийти к более точной оценке какой из циклов быстрее можно лишь изучив исходные коды движка v8
Хм, попробовал узнать для 1000 итераций:
var first=0,second=0;
for(var k=0;k<1000;k++){
var i=2000,j=0,start=new Date().getTime();
while(i--){
j++;
}
first+=(new Date().getTime()-start);

var i=1999,j=0,start=new Date().getTime();
do{
j++;
}while(i--);
second+=(new Date().getTime()-start);
}
console.log('Предусловие:'+(first/1000));
console.log('Постусловие:'+(second/1000));


Результат:
Предусловие:3.017
Постусловие:2.999

Какие у вас результаты?
Да ну, хрень это все, к истине мы не придем. Хотя бы упомяните в статье что with нельзя использовать. А то многие новички его используют как и я в свое время.
Предусловие:1.581
Постусловие:1.612

ps: омфг так тестировать… Попытка сэкономить на спичках.
Странные результаты. Я ставил ff приоритет реального времени и постусловие быстрее, а у вас наоборот. В чем подвох?
var first=0,second=0;
for(var k=0;k<10000;k++){
var i=20000,j=0,start=new Date().getTime();
while(i--){
j++;
}
first+=(new Date().getTime()-start);

var i=19999,j=0,start=new Date().getTime();
do{
j++;
}while(i--);
second+=(new Date().getTime()-start);
}
console.log('Предусловие:'+(first/1000));
console.log('Постусловие:'+(second/1000));


Предусловие:172.749
Постусловие:172.436
— Предусловие:164.661
Постусловие:165.123

Mac OS, приоритеты не выставлял.

Какой бы тут результат ни был – это попытка сэкономить на спичках.
Я с вами абсолютно согласен, это больше концептуальный спор был.
UFO just landed and posted this here
А знаете ли вы, что перерисовка документа — только когда очищается стек вызовов?

Если это для начинающих, то не все поймут. Я бы сформулировал:
А знаете ли вы, что перерисовка документа начнётся только после окончания выполнения всех функций, хотя бы на короткое время? Поэтому для перерисовки используют setTimeout( function(){...}, 1), где функция — продолжение необходимых нам действий. Иногда пишут задержку даже 0. Это неочевидно, но тоже означает минимальную паузк в любом браузере. ...
Если честно, я думаю вообще переписать этот раздел, потому что он действительно сложен для понимания тем, кто недавно знаком с языком.
Просто когда начинаю собирать информацию, там на отдельную статью тянет :)

А насчёт паузы я писал отдельно, но могу еще уточнить: чтобы понять, сколько раз выполняется функция, выполните следующий код:
    var counter=0,
    handle=setInterval(function f(){counter++;},0);
    setTimeout(function f2(){
        clearTimeout(handle);
        alert(counter);
    },1000);

Смысл в том, что смотрится, сколько раз за одну секунду запустится функция с использованием таймаута. Это значение значение будет скорее всего с дробной частью (которая возникает из-за того, что было отдано недостаточно процессорного времени браузеру), которую нужно будет округлить вниз (floor). Вы сможете убедиться в этом, когда проведёте тест несколько раз: все значения будут меньше либо равны некоторому числу, если на которое разделить 1000 мс из теста, то получится число, очень близкое к целому. Если вы не поняли вышесказанного, то на примере разберётесь.

Пример: у меня Firefox показал 246 раз, Chrome 16 — 197 раз, IE9 — 247 раз, Opera12.0a — 99.
Если разделить 1000 на полученное количество раз, то получим актуальный интервал.
1000 мс/246≈4 мс — для Firefox
1000 мс/197≈5 мс — для Chrome (видимо, минимальный интервал чуть повысили)
1000 мс/247≈4 мс — для IE9 (прошлый тест проводился на восьмой версии)
1000 мс/99≈10 мс — для Opera12.0a

Тесты будут достоверными, если кроме браузера больше никакая программа не будет грузить процессор.
У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.
По стандарту W3, минимальная задержка для setTimeout — 4мс, для setInterval — 10мс.
Если минимально возможное разрешение — 4мс, то нет разницы между setTimeout(..,1) и setTimeout(..,4).

learn.javascript.ru/settimeout-setinterval
По поводу первого пункта, я ввожу новые переменные, чтобы было понятно их назначение, и после меня человек мог понять зачем она нужна(думайте о людях, которые будут читать ваш код), хотя да в Вашем примере, переменные счетчики, конечно можно использовать дважды. По поводу замыканий, я конечно придираюсь, но сказать используйте меньше замыканий не есть правильно, замыкание у нас создается в любом случае при вызове функции, а вот количество переменных в нем, кончено может быть разным. А можно по подробнее, что вы имеете ввиду под «функции-обёртки», и как они относятся к оптимизации?
Да переиспользование переменных хорошо далеко не всегда, и часто усложняет отладку и ухудшает читабельность кода, не лучше ли просто в конце вызвать delete нужных переменных?
* delete переменных, после того как они стали не нужны?

P.S. Давно хабра отправляет коменты после Ctrl+Enter?
Вы ошибаетесь, delete не удаляет переменные. Попробуйте выполнить такой код:
(function(){
    var n = 1;
    delete n;
    alert(n);
  }())
Если в короток, то он удаляет объект, свойство объекта или элемент массива по указанному индексу.
А вы попробуйте без замыкания свой код запустить…
Во-первых, там замыканий нет.
Во-вторых, без обёртки код выполнится в глобальной области видимости, т.е. переменные будут записаны в window.
Имею в виду случай без var.
Вот попробовал learn.javascript.ru/play/HkUa0b. Не удаляться, а вот если объявить без var, тогда удалиться. Но это другой случай.
Хм… Фаербаг и отладчик IE9 патерится на неопределенный n, в то время как Chrome выводит 1.
Очень хочется ругнуться, но сдержался.
Вы хотя бы знаете, в какой области видимости выполняется код?
1) Особенность переменных, объявленных не внутри функции, в том, что они становятся глобальными (т.е. записываются в window);
2) На основе предыдущей особенности у вас фактически получается код:
    alert(some_undefined_var);//обращаемся к несуществующей переменной
т.е. после удаления глобальной переменной уже не играет роли, создавалось ли что-то ранее с таким именем или нет. Её нет и всё.
Вот и получаем ошибку.
А речь шла именно о ЛОКАЛЬНЫХ переменных.
Все верно, но кстати есть интересный момент что хром в случае описанном господином windowmaker работает, что в глобальной, что в локальной области одинаково. (Опять господа программисты из разых браузеров вносят недопонимания)
Свойства объектов не могут быть удалены если стоит флаг DontDelete. Объявляя переменную в любом контексте без var, мы создаем свойство объекта window без флага DontDelete и значит его удалить можно. Объявляя переменные через var, ставится флаг DontDelete, и не важно в каком контексте, удалить её нельзя. Ваш пример jsfiddle.net/FB94D/, чтобы увидеть результат, откройте консоль.
Давно хабра отправляет коменты после Ctrl+Enter?

Очень давно. Юзерскрипт HabrAjax с этим борется со дня своего рождения и вполне успешно. У меня с того момента прекратились ложные отправки.
1) Замыкание создаётся ПРИ СОЗДАНИИ ФУНКЦИИ, которая будет содержать замыкание;
2) Функции-обёртки — это функции, которые сами по себе не несут важного смысла, но используются, чтобы единожды (по умолчанию) запустить изолированный код. Вот вам пример (хоть и тривиальный):
    (function init(){//функция-обёртка
        var JUST_A_VAR=0;
        ...
    })();
    (function init2(){//функция-обёртка 2
        var JUST_A_VAR=0;//используется переменная с таким же именем, но она локальная только для этой функции, поэтому не имеет никакого отношения к переменной с таким же именем в прошлой функции
        ...
    })();

Пример не совсем из оптимизации, но у меня на практике было, что за мной дописывали код и использовали уже созданную переменную (не зная этого), что привело к неожидаенным результатам.
Еще они нужны, чтобы локальные переменные не просочились в объект window.
Например, с помощью функции-обёртки инициализируется JQuery.
Вообще локальные переменные фукции в любом случае не видны из другой функции, на то они и локальные.
function init() {
	var JUST_A_VAR = 1;
	alert(JUST_A_VAR);
}
function init2() {
	var JUST_A_VAR = 2;
	alert(JUST_A_VAR);
}
init3 = function () {
	var JUST_A_VAR = 3;
	alert(JUST_A_VAR);
};
init2();
init3();
init();

Обертки делаются, чтобы функция сразу запускалась, и не нужно было её вызывать отдельно.
Обёртки как раз делаются, чтобы переменные стали локальными, а не распространялись на всё приложение. А чтобы функция сразу запускалась — это IIFE, которая является инструментом для создания обёрток.
Хм… что-то я запутался, а в приведенных мной выше функциях переменные разве не локальные? Есть какой-нибудь пример демонстрирующий различие?
Анонимные функции-обёртки нужны когда у вас много переменных и функций, и их нужно отделить от остального кода, чтобы не было конфликтов имён. В приведённых выше функциях конечно JUST_A_VAR локальны. Но если вы хотите все эти три функции объединить в модуль, то нужно сделать функцию-обёртку для них. Тогда названия функций не будут видны в глобальной области видимости.
Я конечно не особо занимаюсь JS, но сдается мне то, что вы называете обертками, делается для того чтобы не захламлять объект window методами init, init2, init3… И переменные тут ни при чем.
Я думаю, не все поняли мою идею: я не призываю использовать те же переменные для новых целей.
Суть в том, что если вы насоздаёте новых переменных внутри рекурсивной функции, то смотрите, чтобы стек не переполнился уже на сотом вызове.
А можно и еще больше оптимизировать: описать i до самого цикла. Тогда его придется объявлять только один раз. Что-то типа:
var i;
for (i =0; i < 10; i++)
{

}
По поводу использования обратного цикла (от конца к началу) — ну ведь можно спокойно один раз посчитать переменную, потом сравнивать с ней:
var SizeOf = str.lenght()
for (i = 0; i < SizeOf; i++)
Прирост скорости виден, в основном, при работе с объектами, где длина, например, высчитывается динамически.
Просто иногда нужно, чтобы цикл был именно от 0 к концу, поскольку нужно вывести числа (элементы) по порядку
Вот это:

var i;
for(i = 0…

и вот это:

for(var i = 0…

— одинаковый код с точки зрения JS. Ему вообще не важно где объявлена переменная. Оно при парсинге выпаливает какие есть локальные переменные и сразу их лепит (можешь считать что все var переносятся наверх функции).

Можно написать, скажем (function() { x = 10; var x; })(), и x все равно будет локальной переменной.
Вы уверены, что в JS операции побитового сдвига действительно работают быстрее аналогов (умножения и деления на 2)?
Понятно, что это характерно для Си, но с JS возникают вопросы…
Померил только что — разницы нет.
В браузерах уже кучу оптимизаций на эту тему сделано, так что скорее всего разница минимальная или отсутствует.
Большинство идей нездоровые.

1) Про память — бред, если вам надо освободить память, напишите n = null; но НЕ ИСПОЛЬЗУЙТЕ одну переменную для разных целей, отлаживать замучаетесь.

2) пример неудачный. Вместо того, чтобы ставить 100/1000 обработчиков onclick, лучше поставить один на родительском элементе.

3) Оптимизация операций — копеечные оптимизации, кстати «Приведение к целому числу» — проще делать через +, например var now = +new Date() (подсмотрел этот код в яндекс-метрике)

4) Проход по массиву. Вместо нечитаемого

> for(var i=arr.length;i--;)

Пишите

> for (var i = 0, l = arr.length; i< l; i++)

выглядит в разы проще и читабельнее, и оптимизация присутствует.

Мысли про медленность обращений к DOM и использование DocumentFragment — здравые. Например, вместо того, чтобы писать $('ul#list li').css({ color: 'red' }) пишите $('ul#list').addClass('withRedChildren') — должно работать быстрее.

Насчет интервала выполнения (разбить длинную задачу на куски и выполнять их каждые 20 мс) — в общем, тоже плохая идея, так как браузер будет все равно тормозить, и пользователь это будет видеть. Лучше упростить код или хотя бы вывести бегунок, что мол, жди, юзер, ничего не поделаешь. Особенно хорошо на скриптах тормозит ИЕ.

Про события перетаскивания — эпический бред. Вы учли, что навеивание/снятие обработчика —это тоже обращение к ДОМу, перестроение внутренних структур данных в браузере, и если вы будет это делать с частотой 100 раз в секунду, это не очень правильно. Гораздо правильнее ждать нажатия кнопки мыши (начало перетаскивания) на нужном объекте, и при нажатии — ставить обработчик onmousemove, при отпускании — снимать.

Троттлить (ограничивать частоту вызова) стоит функцию onresize, так как когда юзер ресайзит окно, пересчитывается и перерисовывается вся страница, и если вы еще будете свои скрипты пересчитыать, это может убить отзывчивость браузера окончательно.

Со всем Вашим сообщением согласен, одно «но». onmousemove хотелось бы как-то ограничить, т.к. вызывается она значительно чаще, чем страница перерисовывается, афаик.
$('myElement').addEvent('onmousemove:throttle(200)', function(){
    // Will only fire once every 200 ms
});

Клик
Вот потом у меня новички в команде приносят ссылки на подобные статьи пытаясь доказать что они правы и их код вообще сделан в лучших традициях js программирования.
Честно — это их проблемы. Данная статья содержит советы в первую очередь необходимые для критических участков кода. Их использование совершенно необязательно в коде, который не будет вызывать больших нагрузок.
Еще можно цикл писать так
for (var i = 0, ii = arr.length; i < ii; ++i)

Преинкремент где-то на 3% ускоряет выполнение пустых циклов, а переменные ii, jj, kk во вложенных циклах удобнее, чем l, ll, lll
1. n = null не освободит память, поставьте эксперимент и убедитесь
4. for (var i = 0, l = arr.length; i< l; i++) будет быстрее только по DOM-коллекциям. Для обычных массивов for (var i = 0; i< arr.length; i++) либо соптимизирован так, что будет быстрее вашего варианта, либо будет незначительно медленнее (в старых ИЕ).
быстрее он будет, потому что в некоторых движках это special case, и длина закешируется jit-ом + он избавится от лишних bounds check’ов
Я переменные цикла называю как-нибудь типа rowN, colN, eventN и типа того. Потому что в процессе допиливания написать вложенный цикл с той же самой n — нефиг делать.
1) Про память — бред, если вам надо освободить память, напишите n = null; но НЕ ИСПОЛЬЗУЙТЕ одну переменную для разных целей, отлаживать замучаетесь

Перечитайте статью. Там написано, что лучше всего это проявляется в рекурсивных функциях.

2) пример неудачный. Вместо того, чтобы ставить 100/1000 обработчиков onclick, лучше поставить один на родительском элементе.

Суть была в том, чтобы показать, как замыкание меняется функцией без замыкания. Но насчёт родительского — эта идея лучше.

Про события перетаскивания — эпический бред. Вы учли, что навеивание/снятие обработчика —это тоже обращение к ДОМу, перестроение внутренних структур данных в браузере, и если вы будет это делать с частотой 100 раз в секунду, это не очень правильно. Гораздо правильнее ждать нажатия кнопки мыши (начало перетаскивания) на нужном объекте, и при нажатии — ставить обработчик onmousemove, при отпускании — снимать.

Фэйспалм.жпг. Вы мыслите слишком узко. Как по-вашему анимировать элемент, когда по нему происходит просто пробегание курсора, например, как в нижней панели MacOS. Совет про отслеживание нажатие мыши, может быть и дельный, но только если действительно требуется нажатие. Но в целом замечание учту.

Троттлить (ограничивать частоту вызова) стоит функцию onresize, так как когда юзер ресайзит окно, пересчитывается и перерисовывается вся страница, и если вы еще будете свои скрипты пересчитыать, это может убить отзывчивость браузера окончательно.

Ограничение делается таким же способом, как и для движения мыши.
> Перечитайте статью. Там написано, что лучше всего это проявляется в рекурсивных функциях.

Эффект от нескольких сэкономленных переменных проявится более-менее заметно, если у вас десятки и сотни тысяч вызовов. Но делать рекурсию такой глубины — крайне глупое, неэффективное и непродуманное решение. У вас оверхед на вход/выход в функцию и расход памяти на стеке перевесит все оптимизации. От такой рекурсии надо избавляться.

Следовательно, эффект от этой оптимизации в общем случае нулевой.

> Суть была в том, чтобы показать, как замыкание меняется функцией без замыкания.

Говоря строго, в JS все функции являются замыканиями, но я понял идею.

> Как по-вашему анимировать элемент, когда по нему происходит просто пробегание курсора, например, как в нижней панели MacOS.

Либо через onmouseover/out на каждом элементе, либо через mousemove на каждом элементе, либо ставить эти обработчики на всю панель сразу — тут надо сравнить, чтобы понять, что лучше. Но точно не снимать\вешать их 100 раз в секунду — вот это действительно бред.

Если вы хотите ограничить частоту вызова, погуглите function throttling — там просто в обработчике события проверяется время последнего вызова, и если прошло меньше N мс, вызов откладывается, но ставить/снимать обработчик в DOM — такое я первый раз вижу. Не говоря, что при этом вы можете потерять событие выхода за пределы элемента например.
Эффект от нескольких сэкономленных переменных проявится более-менее заметно, если у вас десятки и сотни тысяч вызовов. Но делать рекурсию такой глубины — крайне глупое, неэффективное и непродуманное решение. У вас оверхед на вход/выход в функцию и расход памяти на стеке перевесит все оптимизации. От такой рекурсии надо избавляться.

Ситуация напоминает анекдот «Мужики, мы же взрослые люди! Давайте просто достанем и померяемся».
Действительно, к чему все эти дурацкие споры? Я взял и проверил:
(function init(){
	COUNTER=0;
	(function recourse(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,q1,r1,s1,t1,u1,v1,w1,x1,y1,z1){//легкая шизофазия в виде 52-х локальных переменных
		COUNTER++;
		arguments.callee(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,q1,r1,s1,t1,u1,v1,w1,x1,y1,z1);
	});
})();

Chrome упал на 2217, IE — на 1867, Firefox вообще завис. Маразм, конечно, маразмом, но ресурсы тоже не резиновые.
Во вторых — опять же, читайте следующий совет: для рекурсии используйте замыкания.
Подтверждаем кодом:
(function init(){
	COUNTER=0;
	var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,q1,r1,s1,t1,u1,v1,w1,x1,y1,z1;
	(function recourse(){
		COUNTER++;
		arguments.callee();
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,a1,b1,c1,d1,e1,f1,g1,h1,i1,j1,k1,l1,m1,n1,o1,p1,q1,r1,s1,t1,u1,v1,w1,x1,y1,z1;
	})();
})();

Chrome — 18k+, IE — 4377.
Если я не прав — укажите, где.
Во-первых, ваш пример №1 нечестный, так как там не 52 локальные переменные, а 52 *аргумента* функции. Аргумент ф-и и локальная переменная — разные вещи, согласитесь? Для объективной оценки вы должны вписать var a,b,c… в тело внутренней функции (function recourse), иначе это мы чем-то не тем меряемся.

Потому ваш тест оказался неправильный. Давайте повторим попытку.

Кстати, заодно вы доказали, что использование глубокой рекурсии в JS — крайне глупая вещь. О чем я и написал выше.
Аргумент ф-и и локальная переменная — разные вещи, согласитесь?

Абсолютно несогласен.
Мой главный довод: аргументы функций ведут себя так же, как и локальные переменные: 1) они уничтожатся по завершению работы функции (если не замкнуты); 2) их можно замкнуть; 3) var a,b,c… даёт ТОТ же результат; 4) их нельзя удалить.
Приведите хотя бы один аргумент против.
Аргументы добавляются в array-like объект arguments, а локальные переменные — нет. Соотвестственно, на хранение аргументов требуется дополнительная память. Также, в целях оптимизации, движки могут использовать стек для хранения аргументов, что резко ускоряет аварийное завершение скрипта.
Я не буду переходить на личности, потому что троллинг слишком толстый. Сейчас вы сказали какую-то х**ту. Мне интересно вы хоть сами понимаете о чем говорите?
1) Под аргументами в данном имелись в виду именованные параметры функций (и вы это знали, когда сказали, что пример нечестен), а вы переходите на вообще не в тему объект arguments.
2) Движки ВСЕГДА используют стек для работы с аргументами. А пример доказал, что это происходит и для локальных переменных.
3) Код:
	function f(){
		var a=arguments[0],
			b=arguments[1],
			c=arguments[2];
	};

равен по смыслу (но не по скорости) коду:
	function f(a,b,c){
	};

Кто не согласен — приводите доказательства.
Не равен, потому-что во втором случае a будет связанным с arguments[0]. Это особенность, о которой знает очень мало людей:
function f1(){
    var a = arguments[0];
    a = 123;
    console.log( arguments[0] ); // 1
};

f1(1)

function f2(a){
    a = 123;
    console.log( arguments[0] ); // 123
};

f2(1);

Объект arguments — очень хитрая штука и это нифига не то же самое, что объявить локальную переменную.
Просто ремарка относительно аргументов, без вникания в суть спора.
Это единственное отличие. Но замечание дельное.
Выдержка из книги Д. Флэнагана «JavaScript. Подробное руководство» 5-е изд.:
«Параметры функций также считаются локальными переменными, определенными только в теле этой функции».
Связь arguments с именованными параметрами происходит через getter/setter. Так что если мой пример дополнить кодом:
...//для каждой переменной
var a=arguments[0];
delete arguments[0];//необходимо из-за отсутствия флага "настраиваемый"
Object.defineProperty(arguments,"0",{get:function(){return a;},set:function(v){a=v;}});
...
то получится ситуация, описанная вами.
Но смысл в том же: параметры функций также являются локальными переменными.
В первом примере вы 2 раза переменную i объявили.
Вообще-то перед кодом написано: "на своей практике видел, как «code monkey»-студенты писали"
так что это не к автору. Да и реально это не более чем плохой стиль кода, при последующих декларациях браузер не создает новые копии переменных, так что на производительность это не влияет
Просто автор прокомментировал m и n и не обратил внимание на i. Ну и оформлять код правильно тоже важно. Отсутствие отступов тоже на производительность не влияет, но код читать невозможно, согласны? :)
это не более, чем _хороший_ стиль кода.
разное назначение = разные переменные. это повышает читабельность кода. а следить за количеством переменных стоит хотя бы если это массивы или объекты. экономить 4-байтовое целое в рамках среднестатистического сайта с DOM в памяти — это даже смешно.
Не очень понятно, какие будут результаты после минификации уже «оптимизированного» кода. В этом случае нужно провести именно алгоритмические оптимизации (например, с обработчиками событий ресайза и скролла окна браузера) и остановиться на этом.
>Приведение к целому числу v-0
думал проще, быстрее и наглядней var x=+v;
проблем с приведением в браузерах не наблюдал…
или я ошибаюсь, и в этом есть какой-то подвох?
Видимо, имеется в виду использование при операциях сравнения. Да и запись получается однотипной (с v+0.0 и др.)
Проблем с приведением нет, но есть порядок выполнения операций при приведении типов, поэтому v+0 всегду будет быстрее parseInt(v). Погоняйте тесты, увидите сами.
Никогда так не делайте! Только v-0. Не забывайте, что + применяется для конкатенации строк, что приведет к дописыванию нуля.
Меня терзают смутные сомнения насчет оптимизации операций. К рекомендациям хотелось бы увидеть результаты тестов в различных движках, чтобы однозначно понять, стоит ли жертвовать понятностью кода ради этого. Многие языки программирования в прошлом веке научились самостоятельно оптимизировать деление на степени двойки сдвигами. Неужели javascript этого не делает?
arguments.callee(i); :(

Ну и давайте уж подтвердим слова делом и сходим на JSPerf?
Если вы меня поддержите, я напишу вообще отдельную статью про переменные в JavaScript. Поверьте, информация есть и её действительно хватает на целую статью. Причем это не будет копипастой с javascript.ru или из книжки Д. Флэнагана. Я думаю, даже матёрым знатокам будет что оттуда вынести.
Может быть, вот так и будет положено начало серии моих статей по обучению JS :)
Поддержали же уже. Пишите! )

Кстати. К циклам добавьте 2 детали:
var a= [];
a.length= 50000;

for(var i=0,l=a.length; l>i; i+=1)
{
    // ...
}


* Переменная l ускоряет доступ к свойству length (для больших массивов это актуально)
* Использование i+=1 быстрее i++ (из вашего же поста)

Протестировал на FF (величина массива 50 000) и на голом V8 (величина массива 1 000 000). Выигрыш составляет около 25%.
Ну, если бы еще кто поддержал, а то, знаете, самомотивация не бесконечна.
По-моему, советы спорные.

Во-первых, написано, что «так работает быстрее, чем эдак», а ссылок на тесты jsperf.com, подтверждающих все сказанное, нет. Все ваши домыслы относительно производительности могут иметь лишь отдаленное отношение к тому, что на самом деле происходит внутри JS VM. Исходить надо из того, что в браузерах «горячий код», который в основном и влияет на производительность, проходит через оптимизационный JIT, который и так весьма и весьма хорош.

Во-вторых, спорным является усложнение кода и уход от общепринятых идиом в угоду некому минимальному приросту производительности. Самое важное для любого кода — легкость поддержки.

В-третьих, все разговоры о производительности нужно начинать со слов «запускаем профайлер.» Профайлер есть доже в F12 tools IE8, и очень жаль, что знают о его существовании и умеют им пользоваться только единицы.

Единственный полезный совет на всю статью — использование DocumentFragments — но, если вы используете для DOM-манипуляций jQuery, то там об этом уже позаботились за вас.
Я понимаю, что советы могут казаться безосновательными, потому что почти нету их оказательств. Устраняю этот недостаток. Следующий код я использовал для определения времени выполнения того или иного кода:
	(function init(){
		var x=document.getElementById("x");
		var test=function test(fnc){
			var d=new Date;
			for(var i=0;i<10000;i+=1)
				fnc();
			alert(new Date()-d);
		};
		test(function yourfunc(){
			//здесь ваш код который нужно сравнить с другим кодом по скорости
		});
		test(function yourfunc2(){
			//здесь ваш код который нужно сравнить с другим кодом по скорости
		});
	})();

Насколько я могу представить, этот код действительно даст знать, который из приведенных кодов быстрее. Правда, необходимо провести тестов этак 10, чтобы убедиться в этом.
Как раз недавно столкнулся с тем, что jquery.hide() работает в 9 раз медленнее чем jquery.remove() для моего примера.
Я обходил циклом объект и на его основе генерировал html код, так вот простая замена hide на remove для чекбокса, который не нужен сократила время выполнения с 18 секунд до 2-х
> 3 Мб на 1000 объектов для хрома

в V8 никогда замыкание на занимало 3kb. (разве что в том случае, если взять большую функцию и суммировать размер тех структур, которые разделяются между замыканиями, создаваемыми из одного литерала, как неразделяемый).
вы используете очень неаккуратное приближение для замеров потребления памяти. если воспользуетесь heap snapshot в devtools или посмотрите код в V8, то обнаружите, что замыкание с одной переменной стоит 32 + 28 = 60 байт (на 32битной архитектуре).
Спасибо, теперь я знаю :)
Стоит отметить, что оператор постдекремента сразу же возвращает значение, в результате чего отпадает необходимость обращаться к i еще раз.

Тут, наверное, всё же меняет значение переменной, а не возвращает его.
постдекремент возвращает старое значение. Так что сразу возвращает, а потом уже меняет.
А что за ерунда с приведением к целому vs к дробному? Операция v-0 абсолютно эквивалентна v-0.0, и приводит к Number, т.е. дробному числу (см спеку). Согласен с предыдущими комментами, что обычно для этого используется +v.

Для приведения к целому нужно использовать что-то из ~~v, v<<0, v>>0, v|0, v^0 (приведение к int 32), v>>>0 (приведение к unsigned int 32). Если заранее известно, что в int32/uint32 не влезает, то использовать parseInt(v, 10), хоть это и медленнее.

Источник: es5.github.com (ctrl-f ToInt32)
Почти все — незначимые мелочи (микрооптимизации, которые не дают ничего, но при этом сильно ухудшают читабельность кода). Из действительно значимого — меньше обращаться к ДОМ + использование DocumentFragment, и использование setTimeout при выполнении долгих операций частями.
Создание массива
Если размер массива известен заранее, вполне может быть, что new Array(length) будет эффективней.

Операции деления/умножения на числа, являющиеся степенью двойки
и на ней легко получить signed int32 overflow

Еще могу добавить, что в тяжёлых вычислениях дорогим могут оказаться именно вызовы функций (если вы привыкли писать короткий код и делать маленькие функции). Не далее как месяц назад я ускорил реализацию bzip в несколько (~5) раз просто за-inline-в (подставив тело на место вызова) код.

inline-оптимизации появились только в FF10, так что это еще актуально.
Если размер массива известен заранее, вполне может быть, что new Array(length) будет эффективней.

У меня в Хроме:
var times = 1000000, i, a;

console.time('index');
a = new Array(times);
for (i = times; i--;) a[i] = i; // 1237ms
console.timeEnd('index');

console.time('push');
a = [];
for (i = times; i--;) a.push(i); // 988ms
console.timeEnd('push');


Вот почему такие «пальцем в небо оптимизации» бессмысленны. На практике вообще все оптимизации на уровне синтаксиса бессмысленные — всегда может быть среда, где они дадут обратный эффект, при этом читабельность ухудшена.
Хром — самый быстрый в JS браузер (тесты FF10 Не смотрел еще), при этом с долей всего 25%.
Именно потому, что «всегда может быть среда, где они дадут обратный эффект»,
я считаю, что ориентироваться надо на среднячков.

НО тестирование, например, на FF8 Ubuntu показывает десятикратное преимущество способа без объявления размера. Так что проверку «Вот в Хроме...» я считаю недостаточной, но вывод, скорей всего, оказался правильным.
Операции деления/умножения на числа, являющиеся степенью двойки
и на ней легко получить signed int32 overflow

Вот тут вы правы. Своими бессмысленными оптимизациями автор не только ухудшил читабельность кода, но и изменил поведение сам того не замечая.
Скажите пожалуйста, какое влияние оказывают уменьшение числа локальных переменных, использование трюков с операциями, по сравнению с оптимизацией работы с тем же DOMом?
Есть ощущение, что это влияние мало…
Sign up to leave a comment.

Articles