Высокопроизводительный JavaScript
Уменьшаем число запросов для разрешения ссылок: цепочка областей видимости (1/2)
- Разрешение (look-up) ссылки выполнятся каждый раз, когда запрашивается переменная.
- Переменные разрешаются в обратном порядке: от более частной области видимости к более общей.
var g = 7; function f(a) { var v = 8; x = v + a + g; } f(6);
Уменьшаем число запросов для разрешения ссылок: цепочка областей видимости (2/2)
- Поэтому старайтесь использовать переменные максимально близко к области их объявления (с помощью ключевого слова
var
) и избегайте использования глобальных переменных любой ценой. - Никогда не используйте ключевое слово
with
, так как оно не дает компилятору генерировать код для быстрого доступа к локальным переменным (ему приходится сначала пробежаться по цепочке прототипа объекта, затем по цепочке вышестоящей области видимости и т.д.) - Кешируйте ресурсоемкие вызовы в локальные переменные:
// так хуже var arr = ...; var globalVar = 0; (function () { var i; for (i = 0; i < arr.length; i++) { globalVar++; } })();
// так лучше var arr = ...; var globalVar = 0; (function () { var i, l, localVar; l = arr.length; localVar = globalVar; for (i = 0; i < l; i++) { localVar++; } globalVar = localVar; })();
Уменьшаем число запросов для разрешения ссылок: цепочка прототипов
- Доступ к членам объекта, определенным в нем самом, примерно на 25% быстрее, чем доступ к любому члену в цепочке прототипов.
- Чем больше цепочка прототипов (и путь до вызываемого свойства или метода), тем медленнее работает скрипт.
function A () {} A.prototype.prop1 = ...; function B () { this.prop2 = ...; } B.prototype = new A(); var b = new B();
Оптимизируем вызовы объектов
- Если вам требуется создать много объектов, стоит рассмотреть добавление их к прототипу родительского объекта вместо создания индивидуальных свойств в текущем объекте (для прототипа свойства будут созданы только один раз, а затем этот прототип будет использоваться для создания новых объектов).
- Это также уменьшит объем выделяемой памяти.
- Однако, это замедлит обращение к членам объекта (ведь придется просматривать цепочку прототипов).
// быстрее для создания большого количества одинаковых объектов function Foo () {...} Foo.prototype.bar = function () {...};
// быстрее для обращения к свойствам объектов function Foo () { this.bar = function () {...}; }
Не используейте eval
!
- Строка, которая передается
eval
(и всем родственным ему методам: конструкторуFunction
, функциямsetTimeout
иsetInterval
), должна быть скомилирована и выполнена. Это очень медленно! - Никогда не передавайте строку в вызовы функций
setTimeout
иsetInterval
. Вместо это используйте анонимную функция, например:
setTimeout(function () { // код, который нужно выполнить с задержкой }, 50);
- Никогда не используйте
eval
и конструкторFunction
(кроме, может быть, некоторых очень редких случаев, и только в тех блоках, где производительность не является критичной, т.е. расширяемость или логичность модели, например, будут важнее, чем производительность).
Оптимизируем объединение строк
- В Internet Explorer (JScript) объединение двух строк порождает создание новой строки, в которую обе они копируются:
var s = "xxx" + "yyy"; s += "zzz";
- Следовательно, для Internet Explorer будет значительно быстрее добавлять строки в массив, а затем, используя,
Array.join
, объединить (не используйте это для простых объединений, работает сильно медленее!)
// медленнее var i, s = ""; for (i = 0; i < 10000; i++) { s += "x"; }
// быстрее var i, s = []; for (i = 0; i < 10000; i++) { s[i] = "x"; } s = s.join("");
- Другие JavaScript-движки (WebKit, SpiderMonkey) уже оптимизированы, чтобы использовать
realloc
+memcpy
во всех возможных случаях объединения строк. - Используйте YUI Compressor!
Оптимизируйте регулярные выражения
- Не используйте конструктор
RegExp
, если вы не собираетесь создавать регулярное выражение «на лету». Вместо этого используйте постоянные регулярные выражения (regular expression literals). - Используйте метод
test
, если вам нужно просто проверить, соответствует ли строка шаблону (методexec
немного более ресурсоемкий)
if (/loaded|complete/.test(document.readyState)) {...}
- Используйте нефиксирующие (non-capturing) группы (
?:...
) - Придерживайтесь простых шаблонов. Стоить пересмотреть ваши регулярные выражения, если они выглядит примерно так…
(?:(?:\r\n)?[\t])*(?:(?:(?:[^()<>@,;:\\".\[\]\000-\031]+ (?:(?:(?:\r\n)?[\t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?: [^\"\r\\]|\\.|(?:(?:\r\n)?[\t]))*"(?:(?:\r\n)?[\t])*)(?: \.(?:(?:\r\n)?[\t])*(?:[^()<>@,;:\\".\[\]\000-\031]+(?:( ?:(?:\r\n)?[\t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\" \r\\]|\\.|(?:(?:\r\n)?[\t]))*"(?:(?:\r\n)?[\t])*))*@(?:( ?:\r\n)?[\t])*(?:[^()<>@,;:\\".\[\]\000-\031]+(?:(?:(?:\ r\n)?[\t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\] |\\.)*\](?:(?:\r\n)?[\t])*)(?:\.(?:(?:\r\n)?[\t])*(?:[^( )<>@,;:\\".\[\]\000-\031]+(?:(?:(?:\r\n)?[\t])+|\Z|(?=[\ ["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)? [\t])*))*|(?:[^()<>@,;:\\".\[\]\000-\031]+(?:(?:(?:\r\n) ?[\t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.| (?:(?:\r\n)?[\t]))*"(?:(?:\r\n)?[\t])*)*\<(?:(?:\r\n)?[\ t])*(?:@(?:[^()<>@,;:\\".\[\]\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)* \](?:(?:\r\n)?[\t])*)(?:\.(?:(?:\r\n)?[\t])*(?:[^()<>@,; :\\".\[\]\000-\031]+(?:(?:(?:\r\n)?[\t])+|\Z|(?=[\["()<> @,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[\t])* ))*(?:,@(?:(?:\r\n)?[\t]))
Кеширование
- Кеширование оправдано в следующих случаях:
- Высокие издержки (CPU или сетевые задержки), связанные с получением значения
- Значение будет прочитано много раз
- И не будет часто меняться!
- Увеличивает потребление памяти -> нужно найти компромисс
- Схемы хранения в памяти:Модульный шаблон
var fn = (function () { var b = false, v; return function () { if (!b) { v = ...; b = true; } return v; }; })();
Сохранение значение в объекте функции
function fn () { if (!fn.b) { fn.v = ...; fn.b = true; } return fn.v; }
«Ленивое» объявление функции
var fn = function () { var v = ...; return (fn = function () { return v; })(); };
Что делать с JavaScript-процессами, которые долго выполняются (1/2)
- В время работы процесса, который долго исполняется, весь UI браузера «замораживается»
- Следовательно, для обеспечения более-менее комфортного восприятия со стороны пользователя, нужно убедиться, чтобы каждый JavaScript-поток не исполнялся более ~ 300 мс (самое большое).
- Можно разбить длительные процесс на ряд более маленьких порций работы и объединить их в цепочку, используя
setTimeout
. - Также можно обрабатывать все данные на стороне сервера.
- Больше информации доступно по адресу http://www.julienlecomte.net/blog/2007/10/28 (перевод)
- Демонстрационная версия
Что делать с JavaScript-процессами, которые долго выполняются (2/2)
function doSomething (callbackFn) { // Здесь инициализируем данные... (function () { // Делаем некоторую работу... if (termination condition) { // мы закончили callbackFn(); } else { // обрабатываем следующую порцию setTimeout(arguments.callee, 0); } })(); }
Разные советы (1/2)
- Элементарные операции часто быстрее, чем вызовы соответствующих функций:
var a = 1, b = 2, c; // медленнее c = Math.min(a, b); // быстрее c = a < b ? a : b;
// медленнее myArray.push(value); // быстрее myArray[myArray.length] = value; // еще быстрее myArray[idx++] = value;
- По возможности избегайте использования
try...catch
в тех частях, в которых критична производительность:
// медленнее var i; for (i = 0; i < 100000; i++) { try { ... } catch (e) { ... } }
// быстрее var i; try { for (i = 0; i < 100000; i++) { ... } } catch (e) { ... }
Разные советы (2/2)
- По возможности избегайте использования
for...in
в тех частях, в которых критична производительность:
// медленнее var key, value; for (key in myArray) { value = myArray[key]; ... }
// быстрее var i, value, length = myArray.length; for (i = 0; i < length; i++) { value = myArray[i]; ... }
- Делайте ветвление, по возможности, на самом высоком уровне (относительно условия ветвления):
// медленнее function fn () { if (...) { ... } else { ... } }
// быстрее var fn; if (...) { fn = function () {...}; } else { fn = function () {...}; }