ФорумПрограммированиеПыхнуть хотите?F.A.Q. → Эффективное программирование на JS

Эффективное программирование на JS

  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 5:58

    Разъяснения и приёмы для самых маленьких и не очень.

    Времена, когда javascript-сценарии находились в весовой категории "зачем здесь оптимизация?" проходят, поэтому желательно знать хотя бы самые основы эффективного программирования на JS.

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

    Ссылки по теме
    Eval явный и неявный
    Не мешаем оптимизации контекста
    Разрешение имен — помогаем интерпретатору
    Складываем строки
    Объекты против примитивов
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 5:58, спустя 14 секунд

    [size=14]Ссылки по теме[/size]

    Efficient JavaScript, By Mark 'Tarquin' Wilton-Jones (2006 год)
    Читать всем, кто хоть немного программирует на JS и более-менее дружит с английским.

    Высокопроизводительные AJAX-приложения
    Пока писал, на Хабре появился неплохой обзор. Хотя название несколько пафосное.
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 5:59, спустя 16 секунд

    [size=14]eval явный и не явный[/size]

    Стараемся не использовать eval

    Первая заповедь эффективного программиста на JS: не поминай eval всуе.

    eval разбирает, компилирует и выполняет переданный ему код. Поэтому нещадно тормозит. Кроме этого с ним связаны еще несколько неприятных вещей, о которых ниже.
    Кроме непосредственного вызова eval(), он неявно присутствует еще во многих местах: в конструкторе new Function, при передаче строки в setTimeout() и setInterval(), при установке строки в атрибут-обработчик события (element.onclick = "alert('click')";) и везде точно так же тормозит.

    Заповедь вторая: не слушайте тех, кто говорит, что eval нельзя использовать ни в коем случае, так как это противоречит нормам "правильного" программирования.
    Генерация и исполнение кода на этапе работы сценария, это мощнейшая и приятнейшая возможность. Другое дело нужно понимать, чем она грозит и какие есть альтернативы. Большинство проблем, которые заставляют начинающих использовать eval, решаются гораздо легче, правильнее и эффективнее другими методами.


    Многие просто не понимают, как можно вешать какой-то код на обработчики или таймеры, иначе как простой строкой. Почитайте документацию по языку и статью "JS:функции, контексты, замыкания". Анонимные функции или просто передача переменной-функции в качестве параметра поможет в большинстве случаев.

    Как в качестве обработчика устанавливать не просто функции, а её вызов с какими-то параметрами? В той же статье читайте раздел "применение замыканий".

    Многие не знают, как получить свойство объекта с заранее неизвестым именем и пытаются делать что-то вроде:

    var x = 2;
    var name = "name" + x;
    var result = eval("object." + name);


    В этом случае, опять-таки, помогает вдумчивое чтение документации:
    var result = object[name];
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 5:59, спустя 9 секунд

    [size=14]Не мешаем оптимизации контекста[/size]

    По прежнему стараемся не использовать eval

    Как писалось в соседней статье динамичность локального контекста функции на самом деле видимая.
    Человеческим языком - мы не можем просто так создавать в момент выполнения функции новые локальные переменные.
    Локальными переменными становятся все переменные, определенные с помощью "var" или "function" внутри определения функции. Становятся уже на этапе инициализации контекста.
    Таким образом, движок JavaScript уже на этапе разбора исходного кода может определить набор переменных для контекста каждой функции и построить цепочки вложенных контекстов. А следовательно, провести начальную оптимизацию контекстов, с тем, чтобы обращение к переменным шло эффективнее.

    eval же может создавать абсолютно любые локальные переменные:


    var x = "global";
    function f()
    {
    alert(x); // global
    eval("var x = 'local'");
    alert(x); // local
    }
    f();


    Делая внутри функции вызов eval, мы не позволяем движку провести оптимизацию контекста.

    Если же eval все-таки необходим (и при этом не устанавливает локальные переменные), то из функция с большим количеством локальных переменных и сложной логикой, лучше его вынести в другую:


    function myEval(code)
    {
    return eval(code);
    }
    function f()
    {
    /* … */
    myEval(evalCode);
    /* … */
    }


    В этом случае не будет оптимизироваться контекст внутри myEval (а там его всё-равно не густо), c f же всё будет в порядке.


    Полностью отказываемся от with

    Еще один способ помешать оптимизации — использование with. При его использовании, к цепочке областей видимости добавляется еще набор свойств объекта (и не забудьте про цепочку прототипов). Как следствие - затраты на создание/уничтожение нового контекста, дополнительный шаг при разрешении имен, невозможность оптимизации нового контекста (заранее неизвестно, какие свойства будут у объекта).

    Еще много вещей против with.

    Обычно, with используется для уменьшения записи "многоэтажных" объектов (например, "one.two.three.four"). В JavaScript это можно решить присвоением объекта временной переменной:

    var tmp = one.two.three.four;
    tmp.x = 1;
    tmp.y = 2;
    tmp.z = 3;
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 5:59, спустя 18 секунд

    [size=14]Разрешение имен — помогаем интерпретатору[/size]

    Уменьшаем "многоэтажные" имена

    Приведенная в предыдущем посте замена with на временную переменную, экономит не только силы программиста, но и интерпретатора.

    Пример:

    for (var i = 0; i < 1000; i++) {
    one.two.three.four.summ += i;
    }


    Здесь, кажется, и вводить временную переменную излишне - имя объекта пишется только один раз.
    Однако, интерпретатору по этому имени нужно определить конкретное свойство, которое нужно увеличить на i. То есть произвести разрешение имени.
    В нашем случае ему нужно найти локальную переменную "one". Не нашел - запросить вышестоящий контекст и так далее.
    В полученом объекте найти свойство "two". Нет такого - пойти по цепочке прототипов.
    Повторить поиск для "three", "four" и "summ".
    И так тысячу раз одно и тоже.

    Поэтому, лучше либо скопировать объект во временную переменную, уменьшив количество "этажей" до двух - "tmp.summ".
    Либо вообще избавиться от поиска свойства в объекте:

    var summ = 0;
    for (var i = 0; i < 1000; i++) {
    summ += i;
    }
    one.two.three.four.summ += summ;



    Укорачиваем поиск по контекстам

    Как вы уже знаете, при использовании переменной, её значение сначала ищется в локальном контексте, потом в вышележащем и так до глобального. Как, наверное, понятно, чем ближе будет эта переменная, тем лучше. В идеале она должна быть локальной.
    Если нужно производить множество манипуляций с переменной из вышележащего контекста, лучше создать её локальную копию:


    var summ = 0;
    function bad() // Не слишком здорово
    {
    for (var i = 0; i < 1000; i++) {
    summ += i;
    }
    }
    function good() // Уже лучше
    {
    var lsumm = 0;
    for (var i = 0; i < 1000; i++) {
    lsumm += i;
    }
    summ += lsumm;
    }



    Стараемся не использовать глобальные переменные

    Глобальные переменные, мало того, что лежат в самом дальнем контексте (глобальном), так еще и по совместительству являются свойствами объекта window. Вследствии чего, его невозможно оптимизировать.


    Укорачиваем поиск по прототипам

    С одной стороны общие функции для объектов эффективнее держать в единственном экземпляре внутри прототипа.
    С другой - те же проблемы, что и с исполнительными контекстами. Чем ближе наше свойство, тем быстрее мы его найдем. Лучше всего, если оно будет в самом объекте. Еще лучше в локальной переменной.

    Там, где можно, переносите свойства непосредственно в объект или кэшируйте в локальных переменных.
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 5:59, спустя 7 секунд

    [size=14]Складываем строки[/size]

    Складывайте строки за меньшее количество операций

    Складываем три строки:

    var str += one;
    var str += two;

    В первой строке производится конкатентация двух строк. При этом вычисляется длина новой строки (сумма длин двух слагаемых), ищется место в памяти под неё, выделяется, происходит копирование двух строк в новое место, старое значение str уничтожается.

    При сложении с two (вторая строка) происходит всё тоже самое (за исключением того, что str на этот раз больше).

    Гораздо более эффективно будет:

    var str += one + two;

    Здесь сразу будет выделена память под новую строку и произведено копирование каждой строки по одному разу, то есть примерно вдвое быстрее.


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


    var str = "";
    var A = [];
    for (var i = 0; i < 1000; i++) {
    var s = getNextString();
    // str += s; - не эффективно
    A.push(s);
    }
    str = A.join(""); // Объеденяем все строки в одну


    Некоторый выигрыш скорости здесь заметен в большинстве браузеров.
    Но значительный (в тысячи раз на относительно больших строках), только в одном (догадайтесь в каком).
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 10 марта 2008 г. 6:28, спустя 28 минут 29 секунд

    [size=14]Объекты против примитивов[/size]

    Иногда эффективнее одно, иногда другое

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

    В большинстве случаев примитивы, конечно, эффективнее объектов. Хотя бы тем, что занимают меньше памяти. Но не всегда.

    У примитивных значений можно вызывать методы, как у объектов:

    var str = "string"; // Примитивная строка
    alert(str.toUpperCase()); // Вызов метода


    На самом деле при доступе к "свойству" примитивного объекта создается временный объект-над-примитивом, с которым и производятся все "объектные" манипуляции (вызов метода). После чего он уничтожается.


    var str = "qwertyuiopasdfghjklzxcvbnm";
    var summ = 0;

    for (var i = 0; i < 10000; i++) {
    for (var j = 0; j < str.length; j++) {
    summ += str.charCodeAt(j);
    }
    }

    Здесь на каждой итерации порождается и уничтожается новый объект-обертка.
    Гораздо лучше сразу же использовать объект:

    var str = new String("qwertyuiopasdfghjklzxcvbnm");


    По крайней мере в IE изменение быстродействия очень заметно.

Пожалуйста, авторизуйтесь, чтобы написать комментарий!