JavaScript. Оптимизация: опыт, проверенный временем

Предисловие


Давно хотел написать. Мысли есть, желание есть, времени нету… Но вот нашлось, так что привет, Хабра.
Здесь я собрал все идеи, которые помогали и помогают в разработке веб-приложений. Для удобства я разбил их на группы:
  1. Память
  2. Оптимизация операций
  3. Выделение критических участков
  4. Циклы и объектные свойства
  5. Немножко о DOM
  6. DocumentFragment как промежуточный буфер
  7. О преобразованиях в объекты
  8. Разбитие кода
  9. События перетаскивания
  10. Другие советы

Сейчас речь не пойдёт ни о каких библиотеках. Я постараюсь передать знания о механизмах самого языка, а не их реализациях в библиотеках.

Память

Хоть это и не должно волновать клиентского программиста, но не забываем, что память всё-таки не бесконечна и когда-нибудь может закончиться, например, когда запущено несколько массивных программ: офис, графический редактор, компиляция большой программы и др. Несмотря на то, что приведенный пример тривиален, у меня действительно такое случилось, хоть и не из-за браузера, но он тоже сыграл свою роль: 1,3 Гб оперативы (отладчик, около 30 вкладок), начались тормоза по перегрузке страниц ОП в файл подкачки.
Чтобы уменьшить расход памяти, я предлагаю несколько способов:

1) уменьшение числа локальных переменных.
Вы спросите, что это значит? Объясняю, на своей практике видел, как «code monkey»-студенты писали подобный код:

(function init(){ //здесь и далее я буду использовать функции-обёртки
	for(var i=0,n=1;i<10;i++) //суть не в действии внутри цикла
		n+=n;
	alert(n); //выводим результат
	for(var i=0,m=1;i<10;i++)
		m*=m;
	alert(m);
})();


Может быть, вы сразу и не видите, в чем фишка, но: зачем создавать новые переменные, если у нас есть использованные и уже хранящие в себе совершенно ненужные значения старые переменные? В данном примере для нормального решения необходимо заменить все m на n, что сэкономит память.
Этот метод лучше всего проявляет себя в рекурсивных функциях, потому что каждый вызов такой функции провоцирует создание и, обратите внимание, удаление локальных переменных после завершения работы функции, что тоже требует процессорного времени и памяти.
Для наглядного восприятия можно привести аналогию со шкафчиками: у вас есть 6 шкафчиков, три из которых могут быть заполнены; зачем тогда вам ещё три шкафчика, если в таком случае вам придётся открывать все 6, а потом и закрывать все 6?

2) уменьшение числа замыканий.
Замыкания вызывают ощутимый расход памяти (3 Мб на 1000 объектов для хрома, возможно, в новых версиях другой объем), поэтому используйте их как можно реже. Я использую их в двух случаях:
  1. Необходимо скрыть данные внутри некоторого интерфейса, не дать доступа извне;
  2. При рекурсии, когда нужно сделать какие-то пометки в одном общем объекте (например, при обходе HTML занести в массив все узлы, которые имеют пользовательское свойство «dragndrop») в том случае, если выборка по селекторам не подходит.

Оба случая подразумевают какие-то частные, уникальные, случаи. Имеется в виду, что создаются единичные интерфейсы.
Пример первого случая:
(function init(){
    var INTERNAL_NUMBER=0;//замкнутая переменная
    return {
        get:function get(){return INTERNAL_NUMBER;},//функция, возвращающая значенеи замкнутой переменной
        set:function set(value){ //функция, фильтрующая передаваемые значения и устанавливающая замкнутую переменную
            if(typeof value==”number”)
                INTERNAL_NUMBER=value;
            return INTERNAL_NUMBER;
        }
    }
})();


Вот таким образом я и создаю ЕДИНИЧНЫЕ интерфейсы.
Если с первым уже, я думаю, всё понятно, то второй случай следует пояснить: фактически он относится к предыдущему пункту, потому что мы заменяем переменную, которую можно было передать в качестве аргумента в функцию, замыканием, тем самым уменьшая количество локальных переменных внутри этой функции и в то же время соблюдаем принцип минимума замыканий: это замыкание характерно только для это рекурсивной функции (хотя, это уже как вы захотите), ведь при рекурсии используется одна и та же функция (новые замыкания не создаются).
Пример:

(function init(){
    var found=[];
    (function traverse(html){
    for(var i=html.firstChild;i;i=i.nextSibling)
        arguments.callee(i);
    if(typeof html.dragndrop==”object”)
        found.push(html);
    })(document.body);
    return found;
})();


Как видно из примера, в рекурсивной функции содержатся 2 локальные переменные (html, i) вместо трёх (html, i, found). На практике выигрыш в скорости несущественен (по крайней мере, от замыкания всего лишь одной переменной), зато даёт знать о себе выигрыш в памяти.
И, пожалуйста, не упрекайте за nextSibling, а не за nextElementSibling, все делалось в первую очередь ради разъяснения сути замыкания внутри рекурсивной функции.
ВНИМАНИЕ: Никогда не делайте замыкания посредством цикла — это вызывает чрезмерный расход памяти. Исключения составляют случаи, когда логика скрипта требует безоговорочного сокрытия данных (но в любом случае, если у меня есть отладчик, я и туда доберусь? ). Пример неправильного использования замыканий:

function addEvents2(divs) {
    for(var i=0; i<divs.length; i++) {	
        divs[i].innerHTML = i
        divs[i].onclick = function(x) {
            return function() { alert(x) }
        }(i);
    }
}


Да-да. Это тот самый пример из статьи Ильи Кантора про замыкания. Для разъяснения сути — да, нормально, но для практического использования это абсолютно неправильно: создаются несколько функций, каждая со своим замыканием. Ладно, если несколько штук. Но если тысячи… Лучший выход в данном случае — создавать только одну функцию БЕЗ ЗАМЫКАНИЯ и использовать свойство this:
function addEvents2(divs) {
    var f=function f(){alert(this.$i)};
    for(var i=0; i<divs.length; i++) {	
        divs[i].innerHTML = i;
        divs[i].$i=i;
        divs[i].onclick = f;
    }
}

А лучше всего использовать обработчик на родительском элементе (замечание egorinsk).

Оптимизация операций

Я когда-то об этом писал, но повторюсь еще раз: для каждого типа операции среди всех возможных вариантов существует один наибыстрейший, который и предпочтительно использовать.
Пускай у нас есть переменная v, содержание которой зависит от контекста рассмотрения; также есть переменная k, имеющая тот же смысл.
Операция Исходный код Комментарий
Приведение к boolean !!v Наверное, это знают все
Приведение к целому числу v-0 Просто вычитаем ноль
Приведение к дробному числу v-0.0 Небольшой, но выигрыш
Приведение к строке v+”” Прибавляем пустую строку
Создание объекта {} Действительно быстрее, чем через оператор new. Выигрышем является и возможность указать свойства
Создание массива [] Массив тоже является объектом, поэтому именно такое создание быстрее
Сравнение v===k Сравнение именно без приведения (если логика скрипта это допускает)
Операция ин-/декремента, операции присваивания с арифметическим действием v+=1;v/=5; Может это и покажется странным, но именно такой способ более быстрый, причем во всех браузерах
Операции деления/умножения на числа, являющиеся степенью двойки (замечание Dzuba) v<<2 Операции заменяются побитовым сдвигом. Выигрыш характерен и для других языков

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

Выделение критических участков

Я бы посоветовал теоретически прикидывать, насколько данный участок (например, цикл) будет критичным для данной области видимости, т.е. при увеличении числа итераций для этого участка так же линейно будет возрастать время его выполнения или зависимость будет уже в виде степенной функции, где степень больше единицы. И после оптимизации всех операций на данном участке, нужно оптимизировать участок в целом, например, ввести дополнительную переменную, которая бы хранила промежуточный результат, используемый более чем один раз.
Проще говоря, критический участок — это код, над которым процессор будет трудиться дольше всего, причём ощутимо для страницы. Например, 100k операций при интерполировании, если приложение для прикладной математики.
В подразделах я приведу пример и укажу в чем суть.

Циклы и объектные свойства

По скорости действия циклы for и while примерно одинаковы. Однако их различие наиболее проявляется в IE, где цикл for в разы быстрее, поэтому я рекомендую использовать именно его. Цикл for-in НИКОГДА не применяйте для массивов, в таком случае вы резко потеряете производительность: мало того, что он работает медленнее из-за обращения к таблице свойств, так еще будут потери и из-за перечисления ненужных в цикле свойств, таких как, например, length.
Наверное, вы уже знаете о технике обратного прохода по массиву, за счёт которой реально уменьшается время выполнения прохода. Она применима не всегда, но в большинстве случаев допустима. Предлагаю рассмотреть пример, чтобы понять, из-за чего происходит «ускорение»:

var arr=[];
arr.length=100500;
for(var i=0;i<arr.length;i++)
    …;//что-то происходит


Здесь нужно ввести понятие объектного обращения — это получение или установка указанного свойства текущего объекта либо значения какой-либо переменной.
Таким образом, в приведенном выше примере за одну итерацию цикла (без учета действий внутри цикла) происходит 4 объектных обращения (получение i, получение arr, получение arr.length, увеличение i). В наилучшем обратном проходе эти 4 обращения заменены одним:

for(var i=arr.length;i--;)
    …;//что-то происходит


Стоит отметить, что оператор постдекремента сразу же возвращает значение, в результате чего отпадает необходимость обращаться к i еще раз.
Что касается логики циклов, то старайтесь завершить цикл как можно раньше: например, идёт проверка на истинность каждого объекта массива (по условию все должны быть истинными); делайте break как только достигли первый неистинный элемент.

Немножко о DOM

Лирическое отступление: перед тем, как написать эту статью, проверил, есть ли здесь подобные. Оказалось, что есть. Я зашёл почитать, и, О БОЖЕ, что я там увидел:

document.getElementById('elem').propertyOne = 'value of first property';
document.getElementById('elem').propertyTwo = 'value of second property';
document.getElementById('elem').propertyThree = 'value of third property';


А теперь суть: НИКОГДА НЕ ПОВТОРЯЙТЕ УЖЕ ВЫПОЛНЕННЫЕ ДЕЙСТВИЯ, особенно, если они связаны с DOM!
Под выполненным действием в данном случае я понимаю получение элемента по идентификатору. Чем больше документ, тем медленнее производится поиск, а в данном случае поиск проводится трижды. Правильное решение:

var item=document.getElementById('elem');
item.propertyOne = 'value of first property';
item.propertyTwo = 'value of second property';
item.propertyThree = 'value of third property';


Во-первых, увеличивается быстродействие, во-вторых, сокращается код. Такую же конструкцию можно привинтить к циклам:

for(var i=arr.length,c;i--;){
     c=arr[i];
     …
}


---

Развивая тему циклов: как быстрее всего удалить подэлементы узла, зная, что существуют объектные обращения? Правильно, вот так:

var z,node=document.body;
while(z=node.lastChild)
	node.removeChild(z);


Фактически, этот код и будет являться критическим участком внутри функции, что обусловлено большим количеством обращений к DOM-объектам.
Стоит отметить, что интерфейсы DOM в десятки раз медленнее встроенных объектов JavaScript. Поэтому, если идёт речь о выполнении обращений к DOM-свойствам, то количество таких обращений необходимо свести к минимуму, особенно, если они происходят в цикле. В предыдущем примере в цикле всего 2 DOM-обращения: к свойству lastChild и функции removeChild; обращения к z и node сюда не относятся, потому что это обычные переменные. Но, возможно, я заблуждаюсь.
Еще производительность теряется, если в предыдущем случае в документе были расставлены обработчики DOM-событий. Старайтесь расставлять их как можно меньше и делать их как можно проще. В отдельных случаях их код может оказаться критичным.

DocumentFragment как промежуточный буфер

Если у вам необходимо вставить в элемент несколько подэлементов один за одним, не спешите делать это напрямую. Ведь при каждой вставке будет генерироваться DOM-событие. Для обхода этой проблемы существует DocumentFragment — промежуточный буфер, который позволяет коллекционировать элементы и вставлять их в нужное место ОДНИМ разом, что существенно повышает производительность. Для того, чтобы убедиться в этом создайте таблицу 200 на 100 с использованием DocumentFragment и без его использования. За этот опыт отдельная благодарность Илье Кантору.
И все операции над объектами (присваивание класса, айди, установка атрибутов) тоже лучше делать именно в DocumentFragment. Такой подход не допускает генерацию событий уже внутри документа, что не вызывает нагрузки.

О преобразованиях в объекты

На больших объемах JSON-данных eval работает ОЧЕНЬ медленно, но так как значения undefined и функции не входят в JSON, то создать такой объект можно только с помощью eval.
Для настоящего JSON-формата используйте JSON.parse. Но недостаток последнего в том, что он требует полного соответствия JSON спецификации: двойные кавычки для ключей, отсутствие комментариев (хотя они и должны быть).

Разбитие кода

Для понимания текста пониже, вам нужно будет знать о стеке вызовов JS (замечание spmbt): грубо говоря — это стек, куда помещаются все выполняющиеся в данный момент функции в порядке вызова. Помещение первой функции в стек может быть вызвано несколькими способами: 1) вызовом из глобальной области выполнения; 2) запуском функции по таймеру/таймауту; 3) выполнением обработчика пользовательского события. Когда функция завершает свою работу, то она удаляется из стека. Также она удаляется из стека, если внутри нее произошла ошибка, которая не была обработана.

А знаете ли вы, что перерисовка документа (например, когда вы изменили некоторые стили элемента) происходит только когда очищается стек вызовов? Теперь точно знаете. JavaScript не позволяет создавать конструкции типа wait(2000); с продолжением выполнения кода той же функции без ущерба производительности. Поэтому код разбивается на отдельные функции, которые и управляют элементами. Совет заключается в том, что старайтесь при таком разбитии кода на функции равномерно распределить нагрузку по разным функциям. Ведь может получиться, что одна функция изменяет чуть ли не все стили большинства элементов документа, другая вычисляет сложную математическую задачу, а остальные почти ничего не делают, в результате страница «висит».
Равномерность может быть достигнута с помощью установки интервала выполнения. Что касается эффектов, то чем больше интервал между запуском функций, тем больше времени на перерисовку документа и тем меньше нагрузка на процессор. Но если интервал слишком большой, то будет заметно «подергивание эффекта». Оптимальным является интервал в 20мс. Минимальный интервал — 4 мс, кроме Opera (1мс) и IE (15 мс). Даже если вы установите интервал в 0, то реальный вызов функции все равно произойдёт через минимальный интервал.

События перетаскивания

Такие события являются критическими, ведь они вызываются КАЖДЫЙ РАЗ, как только обнаружено перетаскивание. Для снижения нагрузки их лучше заменять функциями по таймауту с помощью замыкания (замечание egorinsk):

(function init(){//в отличие от прошлой версии, именно такая запись работает в полтора раза быстрее.
	//Неверящих профайлер исправит
	var MODE_MOUSE_MOVE=true;
		var move_listener=function move_listener (evnt){
		if(!MODE_MOUSE_MOVE)
			return;
		ev=evnt||window.event;//for IE8
		timeout=setTimeout(move_handler,10);
	},
	ev=null,
	move_handler=function  move_handler(){
		timeout=0;
		MODE_MOUSE_MOVE=true;
		if(typeof document.$onmousemove=="function")
			document.$onmousemove(ev);
	},
	timeout=0;
	document.onmousemove=move_listener;
})();


Если назначить функцию document.$onmousemove, то она будет выполняться каждый раз при обнаружении перетаскивания, причем ее критичность будет снижена с помощью таймера. Айдишник таймера назначен на случай, если вдруг придется сделать возможность прерывания этого таймера.
Сюда же относится и событие изменения размеров окна.

Другие советы

Старайтесь использовать функции-обёртки: они придают модульность коду и изолируют локальные переменные одного модуля от локальных переменных другого модуля. Таким образом снижается вероятность использовать уже объявленную переменную, которой уже отведена некоторая роль.
В принципе, всё сказано. Может быть только, оптимизируйте не только код, но и его читаемость. В необфускированных исходниках имена функций и их параметров делайте логичными, префиксуйте типом (s — строка, n — число и т.п.). Я так и делаю, и код понимается даже через год.

Послесловие


Я рад, что вы дочитали эту статью. Я надеюсь, что она вам понравилась в плане изложения и доступности. Может быть плохо, что здесь нет примеров работы с популярными библиотеками, но я считаю, что нужно уметь пользоваться JS и без библиотек, особенно если вы — клиентский разработчик.

Может, большинство советов уже успели стать для вас «боянами», но я думаю, что что-то полезное вы отсюда всё-таки вынесли. Спасибо

UPD 1. Учитывая высказанные советы в комментах, исправляю все найденные недостатки
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

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

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

                  олсо, вам на будущее. командные конвейеры процессоров устроены гораздо сложнее, чем вы думаете. то, что в коде идет пять последовательных команд, не значит, что все пять будут выполнены последовательно в том же виде.
                  • –3
                    Проверил в файрбаге. Имеем вот такой код:
                    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

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

                        у вас в этот момент мог процесс переключиться, или какая-нибудь задача по расписанию в ОС заработать.
                        вы просто непробиваемый…
                        • –3
                          Я тоже об этом подумал. Эх, замерить бы результаты в монопольном режиме… Сойдемся на том что все в руках оптимизатора движка js внутри браузера. Прийти к более точной оценке какой из циклов быстрее можно лишь изучив исходные коды движка v8
                          • –3
                            Хм, попробовал узнать для 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

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

                                ps: омфг так тестировать… Попытка сэкономить на спичках.
                                • –1
                                  Странные результаты. Я ставил ff приоритет реального времени и постусловие быстрее, а у вас наоборот. В чем подвох?
                                  • 0
                                    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, приоритеты не выставлял.

                                    Какой бы тут результат ни был – это попытка сэкономить на спичках.
                                    • +1
                                      Я с вами абсолютно согласен, это больше концептуальный спор был.
                                      • НЛО прилетело и опубликовало эту надпись здесь
        • +3
          А знаете ли вы, что перерисовка документа — только когда очищается стек вызовов?

          Если это для начинающих, то не все поймут. Я бы сформулировал:
          А знаете ли вы, что перерисовка документа начнётся только после окончания выполнения всех функций, хотя бы на короткое время? Поэтому для перерисовки используют setTimeout( function(){...}, 1), где функция — продолжение необходимых нам действий. Иногда пишут задержку даже 0. Это неочевидно, но тоже означает минимальную паузк в любом браузере. ...
          • 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
              У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.
              По стандарту W3, минимальная задержка для setTimeout — 4мс, для setInterval — 10мс.
              Если минимально возможное разрешение — 4мс, то нет разницы между setTimeout(..,1) и setTimeout(..,4).

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

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

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

                      Пример не совсем из оптимизации, но у меня на практике было, что за мной дописывали код и использовали уже созданную переменную (не зная этого), что привело к неожидаенным результатам.
                      Еще они нужны, чтобы локальные переменные не просочились в объект window.
                      Например, с помощью функции-обёртки инициализируется JQuery.
                      • 0
                        Вообще локальные переменные фукции в любом случае не видны из другой функции, на то они и локальные.
                        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();
                        

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

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

                            var i;
                            for(i = 0…

                            и вот это:

                            for(var i = 0…

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

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

                            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, так как когда юзер ресайзит окно, пересчитывается и перерисовывается вся страница, и если вы еще будете свои скрипты пересчитыать, это может убить отзывчивость браузера окончательно.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                            Ситуация напоминает анекдот «Мужики, мы же взрослые люди! Давайте просто достанем и померяемся».
                                            Действительно, к чему все эти дурацкие споры? Я взял и проверил:
                                            (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.
                                            Если я не прав — укажите, где.
                                            • 0
                                              Во-первых, ваш пример №1 нечестный, так как там не 52 локальные переменные, а 52 *аргумента* функции. Аргумент ф-и и локальная переменная — разные вещи, согласитесь? Для объективной оценки вы должны вписать var a,b,c… в тело внутренней функции (function recourse), иначе это мы чем-то не тем меряемся.

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

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

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

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

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

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

                                                      Кстати. К циклам добавьте 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%.
                                                      • 0
                                                        Ну, если бы еще кто поддержал, а то, знаете, самомотивация не бесконечна.
                                                    • +1
                                                      По-моему, советы спорные.

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

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

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

                                                      Единственный полезный совет на всю статью — использование DocumentFragments — но, если вы используете для DOM-манипуляций jQuery, то там об этом уже позаботились за вас.
                                                      • –1
                                                        Я понимаю, что советы могут казаться безосновательными, потому что почти нету их оказательств. Устраняю этот недостаток. Следующий код я использовал для определения времени выполнения того или иного кода:
                                                        	(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, чтобы убедиться в этом.
                                                      • 0
                                                        Как раз недавно столкнулся с тем, что jquery.hide() работает в 9 раз медленнее чем jquery.remove() для моего примера.
                                                        Я обходил циклом объект и на его основе генерировал html код, так вот простая замена hide на remove для чекбокса, который не нужен сократила время выполнения с 18 секунд до 2-х
                                                        • +1
                                                          > 3 Мб на 1000 объектов для хрома

                                                          в V8 никогда замыкание на занимало 3kb. (разве что в том случае, если взять большую функцию и суммировать размер тех структур, которые разделяются между замыканиями, создаваемыми из одного литерала, как неразделяемый).
                                                          • 0
                                                            Пример я описывал здесь: javascript.ru/tutorial/object/thiskeyword#comment-8399
                                                            • +4
                                                              вы используете очень неаккуратное приближение для замеров потребления памяти. если воспользуетесь heap snapshot в devtools или посмотрите код в V8, то обнаружите, что замыкание с одной переменной стоит 32 + 28 = 60 байт (на 32битной архитектуре).
                                                              • 0
                                                                Спасибо, теперь я знаю :)
                                                          • 0
                                                            Стоит отметить, что оператор постдекремента сразу же возвращает значение, в результате чего отпадает необходимость обращаться к i еще раз.

                                                            Тут, наверное, всё же меняет значение переменной, а не возвращает его.
                                                            • 0
                                                              постдекремент возвращает старое значение. Так что сразу возвращает, а потом уже меняет.
                                                            • +2
                                                              А что за ерунда с приведением к целому 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)
                                                              • +2
                                                                Почти все — незначимые мелочи (микрооптимизации, которые не дают ничего, но при этом сильно ухудшают читабельность кода). Из действительно значимого — меньше обращаться к ДОМ + использование DocumentFragment, и использование setTimeout при выполнении долгих операций частями.
                                                                • +1
                                                                  Создание массива
                                                                  Если размер массива известен заранее, вполне может быть, что new Array(length) будет эффективней.

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

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

                                                                  inline-оптимизации появились только в FF10, так что это еще актуально.
                                                                  • 0
                                                                    Если размер массива известен заранее, вполне может быть, что 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');


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

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

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

                                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.