Высокопроизводительные AJAX-приложения. Часть 3

Высокопроизводительный JavaScript

Уменьшаем число запросов для разрешения ссылок: цепочка областей видимости (½)

  • Разрешение (look-up) ссылки выполнятся каждый раз, когда запрашивается переменная.
  • Переменные разрешаются в обратном порядке: от более частной области видимости к более общей.
var g = 7; function f(a) {     var v = 8;     x = v + a + g; } f(6);<span id="more-74"></span>

Уменьшаем число запросов для разрешения ссылок: цепочка областей видимости (2/2)

  • Поэтому старайтесь использовать переменные максимально близко к области их объявления (с помощью ключевого слова var) и избегайте использования глобальных переменных любой ценой.
  • Никогда не используйте ключевое слово with, так как оно не дает компилятору генерировать код для быстрого доступа к локальным переменным (ему приходится сначала пробежаться по цепочке прототипа объекта, затем по цепочке вышестоящей области видимости и т.д.)
  • Кешируйте ресурсоемкие вызовы в локальные переменные:
    // так хуже	 var arr = ...; var globalVar = 0; (function () {     var i;     for (i = 0; i &lt; arr.length; i++) {     globalVar++; } })();

    // так лучше var arr = ...; var globalVar = 0; (function () {     var i, l, localVar;     l = arr.length;     localVar = globalVar;     for (i = 0; i &lt; 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 () {...};<span>
</span>
// быстрее для обращения к свойствам объектов 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 &lt; 10000; i++) {     s += "x"; }<span>
    </span>
    // быстрее var i, s = []; for (i = 0; i &lt; 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])*(?:(?:(?:[^()&lt;&gt;@,;:\\".\[\]\000-\031]+ (?:(?:(?:\r\n)?[\t])+|\Z|(?=[\["()&lt;&gt;@,;:\\".\[\]]))|"(?: [^\"\r\\]|\\.|(?:(?:\r\n)?[\t]))*"(?:(?:\r\n)?[\t])*)(?: \.(?:(?:\r\n)?[\t])*(?:[^()&lt;&gt;@,;:\\".\[\]\000-\031]+(?:( ?:(?:\r\n)?[\t])+|\Z|(?=[\["()&lt;&gt;@,;:\\".\[\]]))|"(?:[^\" \r\\]|\\.|(?:(?:\r\n)?[\t]))*"(?:(?:\r\n)?[\t])*))*@(?:( ?:\r\n)?[\t])*(?:[^()&lt;&gt;@,;:\\".\[\]\000-\031]+(?:(?:(?:\ r\n)?[\t])+|\Z|(?=[\["()&lt;&gt;@,;:\\".\[\]]))|\[([^\[\]\r\\] |\\.)*\](?:(?:\r\n)?[\t])*)(?:\.(?:(?:\r\n)?[\t])*(?:[^( )&lt;&gt;@,;:\\".\[\]\000-\031]+(?:(?:(?:\r\n)?[\t])+|\Z|(?=[\ ["()&lt;&gt;@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)? [\t])*))*|(?:[^()&lt;&gt;@,;:\\".\[\]\000-\031]+(?:(?:(?:\r\n) ?[\t])+|\Z|(?=[\["()&lt;&gt;@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.| (?:(?:\r\n)?[\t]))*"(?:(?:\r\n)?[\t])*)*\&lt;(?:(?:\r\n)?[\ t])*(?:@(?:[^()&lt;&gt;@,;:\\".\[\]\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()&lt;&gt;@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)* \](?:(?:\r\n)?[\t])*)(?:\.(?:(?:\r\n)?[\t])*(?:[^()&lt;&gt;@,; :\\".\[\]\000-\031]+(?:(?:(?:\r\n)?[\t])+|\Z|(?=[\["()&lt;&gt; @,;:\\".\[\]]))|\[([^\[\]\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-процессами, которые долго выполняются (½)

  • В время работы процесса, который долго исполняется, весь 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); 	}      })();  }

Разные советы (½)

  • Элементарные операции часто быстрее, чем вызовы соответствующих функций:

    var a = 1, b = 2, c; // медленнее c = Math.min(a, b); // быстрее c = a &lt; b ? a : b;
    // медленнее myArray.push(value); // быстрее myArray[myArray.length] = value; // еще быстрее myArray[idx++] = value;
  • По возможности избегайте использования try...catch в тех частях, в которых критична производительность:
    // медленнее var i; for (i = 0; i &lt; 100000; i++) {     try {         ...     } catch (e) { 	...     }  }
    // быстрее var i; try {     for (i = 0; i &lt; 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 &lt; length; i++) {     value = myArray[i];     ... }
  • Делайте ветвление, по возможности, на самом высоком уровне (относительно условия ветвления):

    // медленнее	 function fn () {     if (...) {         ...     } else { 	...     } }
    // быстрее var fn; if (...) {     fn = function () {...}; } else {     fn = function () {...}; }


Запись навигация

Top