вторник, 23 апреля 2013 г.

Правила ускорения JavaScript

1. Помещайте все тэги <script> в конец HTML-кода прямо перед закрывающим тэгом </body>.

<html>
<head>
    <title>Script Example</title>
    <link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <p>Hello world!</p>
    <!-- Пример рекомендуемого местоположения сценариев -->
    <script type="text/javascript" src="file1.js"></script>
    <script type="text/javascript" src="file2.js"></script>
    <script type="text/javascript" src="file3.js"></script>
</body>
</html>

2. Не помещайте тэги <script> сразу после ссылки на внешний CSS-файл.

<link href="styles.css" rel="stylesheet" type="text/css" />
<script type="text/javascript">
    alert('OK');
</script>

3. Уменьшайте количество внешних подключаемых файлов JavaScript, объединив их в один большой файл. Используйте для этого системы сборки.

4. Минимизируйте код с помощью YUI Compressor.

5. Загружайте ваши сценарии динамический, подставляя их внутрь тэга <head>.

function loadScript(url, callback){

    var script = document.createElement('script');
          script.type = 'text/javascript';
          script.src = url;

          // Выполнение сценария после полной загрузки файла     
          if (script.readyState){ // В Internet Explorer
              script.onreadystatechange = function(){
                  if (script.readyState=="loaded" || script.readyState=="complete"){
                      script.onreadystatechange = null;
                      callback();
                 }
              };
          } else { // Другие браузеры
              script.onload = function(){
                  callback();
              };
          }

    document.getElementsByTagName("head")[0].appendChild(script);

}

loadScript('file1.js', function(){alert('Script loaded!');});

Пример динамической загрузки файла.

<script type="text/javascript" src="loadScript.js"></script>
<script type="text/javascript">
    loadScript("file1.js", function(){
        Application.init();
    });
</script>
</body>
</html>

Или можно написать так.

<script type="text/javascript">

    function loadScript(url, callback){

       var script = document.createElement('script');
             script.type = 'text/javascript';
             script.src = url;

              // Выполнение сценария после полной загрузки файла     
              if (script.readyState){ // В Internet Explorer
                 script.onreadystatechange = function(){
                      if (script.readyState=="loaded" || script.readyState=="complete"){
                          script.onreadystatechange = null;
                          callback();
                     }
                 };
              } else { // Другие браузеры
                  script.onload = function(){
                      callback();
                  };
             }

        document.getElementsByTagName("head")[0].appendChild(script);

    }

    loadScript("file1.js", function(){
        Application.init();
    });

</script>
</body>
</html>

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

loadScript('file1.js', function(){
    loadScript('file2.js', function(){
        loadScript('file3.js', function(){
           alert('All scripts loaded!');
        });
    });
});

7. Можно загружать файлы динамически через AJAX. Но стоит помнить, что такой метод загрузки можно применять только к файлам, раположенным в пределе вашего домена! Из-за этого данный метод используется редко.

var xhr = new XMLHttpRequest();

      xhr.open(”get", "filel.js", true);

      xhr.onreadystatechange = function(){
          if (xhr.readyState == 4){
              if (xhr.status >= 200 && xhr.status <300 || xhr.status == 304){

                  var script = document.createElement("script");
                        script.type = "text/javascript";
                        script.text = xhr.responseText;
                  document.body.appendChild(script);

              }
          }
     };

     xhr.send(null);

8. Для загрузки JavaScript-файлов рекомендуется использовать уже разработанные библиотеки загрузчики: RequireJS, LazyLoad, LABjs.

9. Рекомендуется данные хранить преимущественно внутри переменных и литеральных значений, поскольку доступ к данным в литеральных значениях и локальных переменных выполняется быстрее, чем доступ к данным в элементах массивов и членах объектов.

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

function init(){
    var doc = document, // глобальный объект помещается в локальную переменную
            bd = doc.body,
        links = doc.getElementsByTagName("a");
                    doc.getElementByl('go-btn').onclick = function(){
                        start();
                    };
}

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

function hasEitherClass(element, classNamel, className2){
    var currentClassName = element.className;
    return currentClassName == classNamel || currentClassName == className2;
}

Никогда не следует внутри функции искать член объекта более одного раза, если его значение не изменяется между обращениями.

Однако, этот прием не рекомендуется использовать для методов объектов.
Многие методы объектов используют ссылку this для определения контекста вызова, а сохранение ссылки на метод в локальной переменной автоматически свяжет ссылку this с объектом window. Изменение значения this приведет к появлению ошибок.

Пример кэширования элементов в локальных переменных при многократном обращении к DOM.

// самая быстрая функция с кэшированием элементов
function collectionNodesLocal() {
    var coll = document.getElementsByTagName(’div’),
         len = coll.length,
         name = '',
         el = null;

    for (var count = 0; count < len; count++) {
        el = coll[count];
        name = el.nodeName;
        name = el.nodeType;
        name = el.tagName;
    }

    return name;
};

11. Не используйте инструкцию with(){}

12. Для ускорения работы кода внутри try-catch используйте только вызов внешних функций.

try {
    methodThatMightCauseAnError();
} catch (error) {
    handleError(error); // передать обработку внешней функции
}

13. Динамическую вставку элементов в HTML-код производите только в конце всех действий.

14. Не  обращайтесь  непосредственно к свойству length массива в условном выражении цикла.

for (var i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

Выносите значение length в переменную.

var arrLength = arr.length;
for (var i = 0; i < arrLength; i++) {
    console.log(arr[i]);
}

Или можно написать так.

for (var i = 0, arrLength = arr.length; i < arrLength; i++) {
    console.log(arr[i]);
}

15. Для выбора элементов DOM используйте querySelector() и querySelectorAll().

Используйте
var elements = document.querySelectorAll('#menu a');
вместо
var elements = document.getElementById('menu').getElementsByTagName('а');

16. Использование:
• offsetTop, offsetLeft, offsetWidth, offsetHeight
• scrollTop, scrollLeft, scrollWidth, scrollHeight
• clientTop, clientLeft, clientWidth, clientHeight
• getComputedStyle (currentStyle в IE)
вызывает перерисовку страницы.

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

var computed,
      tmp = '',
      bodystyle = document.body.style;

if (document.body.currentStyle) { // IE, Opera
    computed = document.body.currentStyle;
} else { // Другие браузеры
    computed = document.defaultView.getComputedStyle(document.body, '');
}

bodystyle.color = ’red’;
bodystyle.color = 'white';
bodystyle.color = 'green';

tmp = computed.backgroundColor;
tmp = computed.backgroundlmage;
tmp = computed.backgroundAttachment;

17. Выбор элементов по id происходит быстрее, чем по class.

18. Стили элементов лучше изменять следующим образом

var el = document.getElementById('mydiv');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';

19. Добавление новых элементов в DOM лучше осуществлять через вставку фрагмента документа createDocumentFragment()

var fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
document.getElementById(’mylist').appendChild(fragment);

20. Использование :hover в больших таблицах и списках значительно тормозит работу Internet Explorer.

21. Цикл for работает быстрее, чем цикл for-in. Никогда не используйте цикл for-in для обхода элементов массива. За исключением цикла for-in, все остальные типы циклов имеют одинаковую производительность.

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

23. В JavaScript изменение порядка движения цикла на обратный позволяет получить небольшой прирост производительности циклов за счет устранения лишних операций:

// уменьшение числа операций поиска свойства и обход в обратном направлении
for (var i=items.length; i--; ){
    process(items[i]);
}

var j = items.length;
while (j--){
    process(items[j]);
}

var k = items.length-1;
do {
    process(items[k]);
} while (k--);

Циклы в этом примере выполняют обход массива в обратном порядке и совмещают в одном выражении проверку условия продолжения цикла и операцию уменьшения. Результат условного выражения сравнивается со значением true, а так как любое число, отличное от нуля, автоматически преобразуется в true, это делает нулевое значение эквивалентом значения false.

24. Чтобы обеспечить максимальную скорость выполнения if-else, проверка условий в инструкциях if-else всегда должна выполняться в порядке от наиболее вероятных к наименее вероятным.

25. При большом количестве условий if-else можно для ускорения при менять метод половинного деления.

if (value < 6){
    if (value < 3){
        if (value == 0){
            return result0;
        } else if (value == 1){
            return result1;
        } else {
            result2;
        }
    } else {
        if (value == 3){
            return result3;
        } else if (value == 4){
            return result4;
        } else {
            return result5;
        }
    }
} else {
    if (value < 8){
        if (value == 6){
            return result6;
        } else {
            return result7;
        }
    } else {
        if (value == 8){
            return result8;
        } else if (value == 9){
            eturn result9;
        } else {
           return result10;
        }
    }
}

26. При большом количестве необходимых сравнений вместо switch-case используйте поисковые таблицы.

// определить хэш-массив результатов
var results = {'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5};

// вернуть нужный результат
results['tue'];

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

function memfactorial(n){

    if (!memfactorial.cache){
        memfactorial.cache = {
            '0': 1,
            '1': 1
         };
    }

    if (!memfactorial.cache.hasOwnProperty(n)){
        memfactorial.cache[n] = n * memfactorial(n-1);
    }

    return memfactorial.cache[n];

}

Другой пример.

function memoize(fundamental, cache){
    cache = cache || {};
    var shell = function(arg){
        if (!cache.hasOwnProperty(arg)){
            cache[arg] = fundamental(arg);
        } return cache[arg];
    };
    return shell;
}

// мемоизация функции factorial
var memfactorial = memoize(factorial, { '0': 1, '1': 1 });

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

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

Используйте такой поход:

var regex1 = /regex1/,
      regex2 = /regex2/;

while (regex1.test(str1)) {
    regex2.exec(str2);
}

30. Разбивайте сложные регулярные выражения на более простые составляющие.
Пусть каждое последующее регулярное выражение выполняет поиск по результатам, полученным от предыдущего.

31. используйте быструю функцию trim() для удаления пробелов в начале и конце строки.

if (!String.prototype.trim) {
    String.prototype.trim = function {
        return this.replace(/^\s+/, "").replace(/\s+$/, "");
    }
}

// В начале строки присутствуют
// символ табуляции (\t) и символ перевода строки (\n).
var str = " \t\n test string ".trim();
alert(str == "test string"); // выведет "true"

32. Длительность одной операции, выполняемой JavaScript-cценарием, не должна превышать 100 мс (но лучше, чтобы не превышали даже 50 мс). Измерения длительности операций следует производить в самом медленном браузере, который предстоит поддерживать.

33. Таймеры в JavaScript не отличаются высокой точностью и могут ошибаться на несколько миллисекунд в ту или иную сторону.
Большинство браузеров показывает снижение точности срабатывания таймеров при использовании задержек менее 10 мс. В общем случае для выполнения маленьких задержек рекомендуется использовать значение не менее 25 мс.

34. Обрабатывайте большие массивы данных с помощью таймеров.

Пример асинхронного цикла обработки массива по таймеру.

function processArray(items, process, callback){
    var todo = items.concat(); // создать копию оригинала
    setTimeout(function(){
        process(todo.shift()); // извлечь очередной элемент массива и обработать его
        // если еще остались элементы для обработки, создать другой таймер
        if (todo.length > 0){
            setTimeout(arguments.callee, 25);
        } else {
            callback(items);
        }
    }, 25);
}

var items = [123, 789, 323, 778, 232, 654, 219, 543, 321, 160];

function outputValue(value){
    console.log(value);
}

processArray(items, outputValue, function(){console.log('Done!');});

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

Основная идея этого шаблона состоит в том, чтобы создать копию оригинального
массива и использовать ее как очередь обрабатываемых элементов. Первый вызов setTimeout() создаст таймер, обрабатывающий первый элемент массива. Вызов todo.shift() вернет первый элемент и одновременно удалит его из массива. Затем это значение будет передано функции process(). После обработки элемента проверяется наличие других элементов. Если в массиве todo еще остались элементы, создается другой таймер. Поскольку следующий таймер должен вызвать ту же функцию, что и предыдущий, в первом аргументе функции setTimeout() передается arguments.callee. Это значение указывает на анонимную функцию, в которой находится поток выполнения в данный момент. Если в массиве не осталось элементов для обработки, вызывается функция callback().

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

35. Разделяйте большие функции на более мелкие.
Нередко одно большое задание можно разделить на последовательность
более мелких заданий. Если единственная функция выполняется слишком долго, то следует проверить возможность разделить ее на последовательность более мелких функций, выполняющихся быстрее. Часто деление легко можно выполнить, если рассматривать каждую строку программного кода как некоторое атомарное задание, даже когда несколько
строк программного кода сгруппированы для решения единой задачи. Некоторые функции легко можно разбить по функциям, которые они вызывают.

Пример.

function saveDocument(id){
    openDocument(id);
    writeText(id);
    closeDocument(id);
    updateUI(id);
}

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

function saveDocument(id){

    var tasks = [openDocument, writeText, closeDocument, updateUI];

    setTimeout(function(){

        // выполнить следующее задание
        var task = tasks.shift();
        task(id);

        // проверить наличие других заданий
        if (tasks.length > 0){
            setTimeout(arguments.callee, 25);
        }

    }, 25);

}

Этот шаблон можно заключить в функцию для многократного использования:

function multistep(steps, args, callback){

    var tasks = steps.concat(); // скопировать массив

    setTimeout(function(){

        // выполнить следующее задание
        var task = tasks.shift();
        task.apply(null, args || []);

        // проверить наличие других заданий
        if (tasks.length > 0){
            setTimeout(arguments.callee, 25);
        } else {
            callback();
        }

    }, 25);

}

function saveDocument(id){

    var tasks = [openDocument, writeText, closeDocument, updateUI];

    multistep(tasks, [id], function(){
        alert('Save completed!');
    });

}

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

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

36. Продолжительность выполнения программного кода можно определить с помощью встроенного объекта Date.

Оператор сложения (+) преобразует объект Date в числовое представление, благодаря чему исключаются все последующие арифметические преобразования.

var start = +new Date(),
     stop;

someLongProcess();

stop = +new Date();

if(stop - start < 50){
    alert("Just about right.");
} else {
    alert(”Taking too long.");
}

Метод processArray() можно дополнить пакетной обработкой элементов массива, добавив проверку времени:

function timedProcessArray(items, process, callback){

    var todo = items.concat(); // создать копию оригинала

    setTimeout(function(){

        var start = +new Date();

        do {
            process(todo.shift());
        } while (todo.length > 0 && (+new Date() - start < 50));

        if (todo.length > 0){
            setTimeout(arguments.callee, 25);
        } else {
            callback(items);
        }

    }, 25);

}

Дополнительный цикл do-while в этой функции проверяет время после обработки каждого элемента.

Также для вычисления времени выполнения кода можно использовать объект Timer.

var Timer = {

    _data: {},

    start: function(key) {
        Timer._data[key] = new Date();
    },

    stop: function(key) {
        var time = Timer._data[key];
        if (time) {
            Timer._data[key] = new Date() - time;
        }
    },

    getTime: function(key) {
        return Timer._data[key];
    }

};

Timer.start('createElement');

for (i = 0; i < count; i++) {
    element = document.createElement('div');
}

Timer.stop('createElement');

alert('created ' + count + ' in ' + Timer.getTime('createElement');

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

console.time("cache node");

myFunction();

console.timeEnd("cache node");

Код таймера console.time()д ля переноса его в консоли других браузеров.

if (console && !console.time) {
    console.timers = {};
    console.time = function(name) {
        console._timers[name] = new Date();
    };
    console.timeEnd = function(name) {
        var time = new Date() - console._timers[name];
        console.info(name + ' + time + 'ms');
    };
}

37. Необходимо ограничивать количество таймеров многократного срабатывания с короткими задержками. И вместо них создать единственный таймер многократного срабатывания, выполняющий множество операций при каждом срабатывании.

38. Прикладной интерфейс фоновых потоков выполнения (Web Workers API) изменил положение дел, предоставив интерфейс, посредством которого можно выполнять программный код, не отнимая время у главного потока выполнения. Поэтому всякий раз, когда обработка занимает более 100 мс, следует подумать, не является ли решение на основе фоновых потоков выполнения более предпочтительным, чем решение на основе таймеров.

39. Быстрое получение данных с другого домена JSONP.

Переданные данные с другого домена обязательно должны содержаться внутри файла JavaScript и быть обернуты в функцию callback. Данные загружаются на страницу в результате подстановки тэга <script>.

var scriptElement = document.createElement('script');
      scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName('head')[0].appendChild(scriptElement);

function jsonCallback(data) {
    // Обработать данные...
    alert(data);
}

Содержание файла lib.js

jsonCallback({ "status": 1, "colors": [ "#fff", "#000", "#ff0000" ] });

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

40. Кэшируйте ответы при AJAX-запросах.
Если необходимо, чтобы браузер кэшировал Ajax-ответы, запросы должны выполняться методом GET. Но простого использования GET-запросов недостаточно; необходимо также, чтобы в ответе сервера присутствовали нужные HTTP-заголовки. Заголовок Expires сообщает браузеру, как долго должен храниться ответ в кэше. Его значением является дата, по истечении которой любые запросы к этому URL-адресу будут направляться не в кэш, а на сервер.

Ниже показано, как выглядит заголовок Expires:
Expires: Mon, 28 Jul 2014 23:30:00 GMT

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

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

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

Ниже приводится пример обертки для объекта XHR, которая сначала проверяет наличие в кэше данных для указанного URL-адреса и только потом выполняет запрос:

var localCache = {};

function xhrRequest(url, callback) {

    // Проверить наличие в локальном кэше данных для указанного URL.
    if (localCache[url]) {
        callback.success(localCache[url]);
        return;
    }

    // Если данные в кэше отсутствуют, выполнить запрос.
    var req = createXhrObject();

    req.onerror = function() {
        callback.error();
    };

    req.onreadystatechange = function() {

        if (req.readyState == 4) {
   
            if (req.responseText === ’’ || req.status == ’404') {
                callback.error();
                return;
            }

            // Сохранить ответ в локальном кэше.
            localCache[url] = req.responseText;
            callback.success(req.responseText);
        }

    };

    req.open("GET", url, true);
    req.send(null);

}

Для удаления данных из кэша можно использовать delete.

delete localCache['/user/friendlist/'];
delete localCache['/user/contactlist/'];

41. Используйте литералы {} и [] для создания объектов и массивов, так как они интерпретируются намного быстрее.

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

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

for (var i=0, len=rows.length; i < len; i++){
    if (i & 1) {
        className = "odd";
    } else {
        className = "even";
    }
    // применить класс
}

Комментариев нет:

Отправить комментарий