login:        password:      
Combats Scrolls
Rambler's Top100
Гость БК
ru
updated 17.02.09 16:48
15-11-08 @ 05:16

developers Open info : СэрАртём Open user info Open user photogallery
Ускоряем jаvаscript
Нередко перед web-разработчиками встаёт проблема низкой производительности клиентского кода, обычно реализуемого на jаvаscript. В этот момент можно услышать кучу нелестных отзывов как в адрес тех, кто поставил такую задачу, так и и в адрес тех, кто реализовал ту или иную версию jаvаscript, и даже в адрес самого языка. Однако, тут поговорка "нечего на зеркало пенять, если рожа кривая" подходит, как нельзя кстати. Посмотрим на наглядном примере. Задача: динамически сгенерировать на клиенте HTML-документ с кнопками, впоследствии которым будут назначены обработчики событий. Приблизительно поставленную задачу можно решить так:

<script>
var count = 500;

var log = [];
function clicked(e) {
  e = e || window.event;
  log.push((e.srcElement || e.target).id);
}

function run() {
  for(var i=0; i<count; i--) {
    var button = document.createElement('button');
    button.id = 'item'+i;
    document.body.insertBefore(button, null);
  }

  for(var i=0; i<count; i--) {
    document.getElementById('item'+i).onclick = clicked;
  }

  for(var i=0; i<count; i--) {
    document.getElementById('item'+i).click();
  }
}

run();
</script>


Казалось бы - что проще? Ан, нет: корявость IE (даже 8й версии) приводит к тому, что двойной вызов document.getElementById('item'+i) оказывается самой длительной операцией, отнимающей более 90% всего времени. В Opera и Mozilla эта операция реализована аккуратнее и потери не так заметны. Попробуем ускорить код, введя кэш элементов:

function run() {
  for(var i=0; i<count; i--) {
    var button = document.createElement('button');
    button.id = 'item'+i;
    cache['item'+i] = document.body.insertBefore(button, null);
  }

  for(var i=0; i<count; i--) {
    cache['item'+i].onclick = clicked;
  }

  for(var i=0; i<count; i--) {
    cache['item'+i].click();
  }
}


Вот мы и избавились от ненужных вызовов функции getElementById. Но что делать, если содержимое документа генерируется на стороне клиента не полностью, а частично? Путей решения два: обойти дерево элементов документа или динамически заполнять кэш. Вот как это реализуется:

var cache;
/* Использование кэша, заполняющегося полным обходом дерева элементов HTML-документа */
var getElement = function (id) {
  cache = {};
  function cacheItems(element) {
    if (!('childNodes' in element))
      return;
    for(var i=0; i<element.childNodes.length; i++) {
      if (element.childNodes[ i ].id)
        cache[element.childNodes[ i ].id] = element.childNodes[ i ];
      cacheItems(element.childNodes[ i ]);
    }
  }
  cacheItems(document.body);
  getElement = function(id) {
    return cache[id];
  }
  return getElement(id);
}

function run() {
// ...
  for(var i=0; i<count; i--) {
    getElement('item'+i).onclick = clicked;
  }
// ...
}


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

var cache;
/* Использование кэша, заполняющегося по запросу */
getElement = function(id) {
  cache = {};
  getElement = function(id) {
    return cache[id] || (cache[id]=document.getElementById(id));
  }
  return getElement(id);
}


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

Отдельного рассмотрения требует обработчик события clicked(e). Из-за разной реализации генерируемых событий пришлось писать достаточно некрасивый и непроизводительный код. Можно ли сделать его проще? Можно! Вот так:

/* Не привязанный к какому-либо браузеру динамически генерируемый обработчик */
function generateClicked(element) {
  return function() {
    log.push(element.id);
  }
}

function run() {
// ...
  for(var i=0; i<count; i--) {
    getElement('item'+i).onclick = generateClicked(getElement('item'+i));
  }
// ...
}


Что происходит в этом примере? Для каждого элемента вызывается функция, которая порождает объект-функцию с уже инициализированной переменной element. В результате при нажатии на кнопку будет вызвана её личная функция, в которой element указывает именно на эту кнопку. Нет необходимости использовать объект "событие" (разумеется, если в обработчике не нужны специфические свойства события, как то: координаты мыши во время клика, состояние клавиши Ctrl и прочее...), нет необходимости адаптировать код для нескольких браузеров, обработка события выполняется быстрее.

Ниже приведён полный код файла для игр с "ускорением".

Можно менять значение переменной getElement на одну из функций getElement1, getElement2, getElement3 или getElement4. В Mozilla и в Opera getElement2 будет вызывать ошибку, потому что доступ методом document.all - нестандартный и реализован только в IE.
Также можно комментировать одну из строк выбора способа доступа к объекту, сгенерировавшему событие.

Надеюсь, мои подсказки помогут специалистам в будущем избежать головной боли ;)

Update: 17-11-08 17:15
Как выяснилось, разные версии IE по-разному обрабатывают способы обращения к элемементам документа. Я немного изменил последний пример для того, чтобы заполняющийся полным обходом дерева элементов кэш работал с одинаковой производительностью на IE6-IE8

Update: 17-02-09 15:45
К слову о производительности: Производительность простых и сложных конструкций в jаvаscript от RealCoding.NET

Mood: рабочее

Я думаю, что это: Scrolls.multiLike:)

view mode: linear threads
Total disscussion threads: 3 Pages: 1
«« « 1 » »»

Post reply | Post reply with quote
Alex Nomad Open user info Open user photogallery
16-11-08 @ 00:53
copy link to clipboard
Я использую немного другую версию функции

function getElement(aID){
if (document.all) return document.all[aID]; //тут сработает для IE, и это быстро
if (document.layers) return document.layers[aID]; //тут сработает для NN, и это быстро
return document.getElementById(aID); //тут сработает для всех остальных, и это быстро
}

Если же нужно делать много запросов getElement, то также использую свой собственный кэш
Post reply Ответить с цитированием
СэрАртём Open user info Open user photogallery
16-11-08 @ 12:36
Re:
copy link to clipboard
if (document.all) return document.all[aID]; //тут сработает для IE, и это быстро

ты тест запускал, который я привёл в конце поста? рекомендую. кстати, делать 1 функцию с проверками намного расточительнее, чем однократно сделать такое:

var getElement = document.all ? function (aID){
  return document.all[aID]; //тут сработает для IE, и это быстро
} : (document.layers ? function (aID){
  return document.layers[aID]; //тут сработает для NN, и это быстро
} : function (aID){
  return document.getElementById(aID); //тут сработает для всех остальных, и это быстро
});
Post reply Ответить с цитированием
Alex Nomad Open user info Open user photogallery
16-11-08 @ 23:50
Re: Re:
copy link to clipboard
не-а, прочитал по диагонале, не было времени думать на этим...
Трюк мне понравился, попробую на днях в переодевалке использовать, там у меня есть свой тайминг.
Post reply Ответить с цитированием
Отшельник Оз Open user info Open user photogallery
25-11-08 @ 01:44
Здорово наверно
copy link to clipboard
Вот предложил бы создателю АК ускорить, сколько людей осчастливил бы =)
Post reply Ответить с цитированием
СэрАртём Open user info Open user photogallery
25-11-08 @ 14:30
Re: Здорово наверно
copy link to clipboard
Создатель АК не пишет на jаvаscript, он всё реализует в компилируемом коде. Хотя, доступ к странице всё равно идёт через объектную модель, а там, как видно, - полная Ж...
Post reply Ответить с цитированием
Отшельник Оз Open user info Open user photogallery
27-11-08 @ 04:50
Re: Re: Здорово наверно
copy link to clipboard
Вот бы с этой Ж.. побороться, чтоб жить легче стало.
Post reply Ответить с цитированием
l-Michael-l Open user info Open user photogallery
17-04-09 @ 20:21
copy link to clipboard

> Казалось бы - что проще? Ан, нет: корявость IE (даже 8й
> версии) приводит к тому, что двойной вызов document.getElementById('item'+i)
> оказывается самой длительной операцией, отнимающей более
> 90% всего времени. В Opera и Mozilla эта операция реализована
> аккуратнее и потери не так заметны. Попробуем ускорить код,
> введя кэш элементов:


дык известная проблема в ИЕ с ДОМ моделью. Она была, есть и будет до тех пор, пока они не пересмотрят свой единственный (!) стандар BUBBLING, а будут так-же, как и все нормальные браузеры, использовать CAPTURING

да и как на меня, лучше использовать не инлайн ивенты, а аттачить Listeners

ЗЫ. а про memory leak то ты забыл... память течет у тебя не по детски...
Post reply Ответить с цитированием
СэрАртём Open user info Open user photogallery
17-04-09 @ 21:41
Re:
copy link to clipboard

> лучше использовать не инлайн ивенты, а аттачить Listeners

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


> память течет у тебя не по детски

знаю. с expando надо работать очень аккуратно. и обязательно подчищать за собой
Post reply Ответить с цитированием
l-Michael-l Open user info Open user photogallery
21-04-09 @ 11:15
Re: Re:
copy link to clipboard
Дык а почему тогда так не сделал? ))
Post reply Ответить с цитированием
СэрАртём Open user info Open user photogallery
21-04-09 @ 14:58
Re: Re: Re:
copy link to clipboard
примеры для того и пишутся, чтобы не копировать их подчистую, а использовать идею.
Post reply Ответить с цитированием
l-Michael-l Open user info Open user photogallery
21-04-09 @ 18:00
Re: Re: Re: Re:
copy link to clipboard
дык если давать пример, то давать его не просто как идею, а как полностью рабочий код...

пример от части является стилем написания кода чела... (имхо)
Post reply Ответить с цитированием

Post reply | Post reply with quote

Total disscussion threads: 3 Pages: 1
«« « 1 » »»


 
 © 2007–2024 «combats.com»
  18+  
feedback