![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
(Здесь собраны воедино отрывки дискуссий отсюда и отсюда.)
Судя по всему, в FireFox коллекция элементов представляет собой массив последовательных элементов, либо содержит hash table для быстрого доступа к элементу по индексу. Поэтому оператор
В IE, по-видимому, элементы коллекции хранятся в виде linked list или похожей структуре данных. Поэтому оператору
Например, для страницы с 1000-й элементов (не такая уж и большая страница) и тремя проходами по всем элементам имеем:
Теперь осталось исправить вышеуказанные функции в скрипте LJ. Это можно сделать двумя способами (в скобках указаны примеры продуктов, с помощью которых это можно осуществить):
Описание проблемы
При загрузке LJ страницы браузер IE7(IE6) зависает на несколько секунд (или минут). При этом загрузка CPU находится на отметке ~100%. Проблема проявляется особенно остро на страницах с большим количеством комментариев.Причина
Причина кроется в файле "http://www.livejournal.com/js??dom.js" и конкретно в следующих трёх функциях:DOM.filterElementsByClassName
DOM.filterElementsByAttribute
DOM.filterElementsByTagName
`DOM.filterElementsByClassName'
. Вот как выглядит эта функция:filterElementsByClassName: function( es, className ) { var filtered = []; for( var i = 0; i < es.length; i++ ) { var e = es[ i ]; if( DOM.hasClassName( e, className ) ) filtered[ filtered.length ] = e; } return filtered; }где
`es'
это коллекция DOM элементов, возвращённая из вызова `document.getElementsByTagName("*")'
. В частности, к зависанию приводит вот эта строка:var e = es[ i ];Почему же в браузерах на движке IE такие грабли? Я считаю что корень проблемы заключается в различной имплементации коллекции элементов и следовательно, различном поведении оператора
"[]"
(subscript operator). Я буду говорить IE или FireFox, подразумевая на самом деле JavaScript движок, используемый этими браузерами (MS JScript и SpiderMonkey, соответственно).Судя по всему, в FireFox коллекция элементов представляет собой массив последовательных элементов, либо содержит hash table для быстрого доступа к элементу по индексу. Поэтому оператор
"[]"
работает быстро, т.к. доступ к каждому элементу это всего лишь оффсет от первого элемента (либо готовая ссылка из hash table).В IE, по-видимому, элементы коллекции хранятся в виде linked list или похожей структуре данных. Поэтому оператору
"[]"
для доступа к каждому i
-му элементу надо сначала пройтись по всем i-1
элементам до него. Если это происходит несколько раз во время загрузки страницы (а так и происходит в скриптах LJ), то суммарное время итераций может быть вполне ощутимым.Например, для страницы с 1000-й элементов (не такая уж и большая страница) и тремя проходами по всем элементам имеем:
(1 + 2 + 3 + ... + 999 + 1000) * 3 = 1 498 500.Для грубой оценки можно считать полтора миллиона итераций по элементам. Допустим что компьютер мощный и каждая итерация занимает треть миллисекунды. Выходит что для полного пробега нужно 500 секунд. А это ~8.3 минуты. Вот вам и зависание браузера на 5-10 минут на больших страницах.
Решение
Суть решения заключается в замене способа итерации по коллекции. В IE для этой цели существует специальный объект`Enumerator'
. Вот как выглядит функция `DOM.filterElementsByClassName'
с применением этого объекта:filterElementsByClassName: function( es, className ) { var filtered = []; for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) { var e = en.item(); if( DOM.hasClassName( e, className ) ) filtered.push(e); } return filtered; }Также, добавление найденых элементов в новый массив
`filtered'
было изменено на более быстрое `filtered.push(e);'
вместо `filtered[filtered.length] = e;'
.Теперь осталось исправить вышеуказанные функции в скрипте LJ. Это можно сделать двумя способами (в скобках указаны примеры продуктов, с помощью которых это можно осуществить):
- Поставить некий фильтр на соединение с сервером вне браузера и исправить там страницу (Ad Muncher, Privoxy и т.д.).
- Поставить на браузер расширение, которое даёт доступ к содержимому страницы и написать костыль, который будет страницу чинить (IE7Pro, Turnabout и т.д.).
Решение с помощью Ad Muncher
Должен сразу предупредить что это решение грубое и может портачить всплывающее оконце (Contextual Hover Menu) над иконкой юзера. Итак:- Скопировать этот код в Notepad (или любой другой любимый текстовой редактор):
if (document.domain.indexOf('livejournal.com') != -1) { DOM = {}; DOM.filterElementsByClassName = function( es, className ) { var filtered = []; for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) { var e = en.item(); if( DOM.hasClassName( e, className ) ) filtered.push(e); } return filtered; }; DOM.filterElementsByAttribute = function( es, attr ) { if( !es ) return []; if( !defined( attr ) || attr == null || attr == "" ) return es; var filtered = []; for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) { var element = en.item(); if( !element ) continue; if( element.getAttribute && ( element.getAttribute( attr ) ) ) filtered.push(element); } return filtered; }; DOM.filterElementsByTagName = function( es, tagName ) { if( tagName == "*" ) return es; var filtered = []; tagName = tagName.toLowerCase(); for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) { var e = en.item(); if( e.tagName && e.tagName.toLowerCase() == tagName ) filtered.push(e); } return filtered; }; }
- Отформатировать вышеприведённый код так, чтобы он был в одну (очень длинную) строку. Это ограничение Ad Muncher'а, ничего не поделаешь.
- Добавить получившуюся строку в фильтры Ad Muncher'а; категория фильтра: "Add javascript to all pages".
- Всё!