На самом деле хочется, конечно, знать общую скорость (точнее, время) вывода на экран какой-либо информации. В нашем случае – информации, получаемой с помощью браузеров (то есть скорость вывода веб-страниц). Чтобы понять, отчего скорость низкая и как её можно повысить.
Некоторые виды работ браузера нельзя измерить с помощью javascript. Пример: на странице http://irkutsk.ir2.ru/ есть флажок (элемент checkbox), отметив который, можно развернуть все объявления на странице. Функция javascript (minmax()) там очень проста; если не считать небольшой подготовки и запоминания значения в куки, она выполняет всего одно действие – добавляет или удаляет селектор "lh14" к атрибуту class основного элемента страницы (maindiv):
Интуитивно ясно, что работа такой функции не может продолжаться дольше 1-2 миллисекунд. Но простым глазом так же ясно видно, что изменения на странице отображаются далеко не мгновенно. Потому что это простое действие – переключение селектора – просто только для javascript (отдать команду и забыть), а браузеру потом приходится пересчитывать весь DOM. Мало того, потом новый, персчитанный вид страницы нужно вывести на экран – а эта скорость уже зависит даже не от браузера, а больше от операционной системы (браузер тоже – пересчитал DOM, отдал новый рисунок ОС и забыл).
Из всего этого нагромождения мы можем узнать время исполнения только маленького кусочка – самой функции minmax(), но зато очень точно. Для этого обычно используют простой приём – запускают функцию много-много раз подряд в цикле, считают общее время и делят на всех. В нашем случае достаточно 15 раз написать (прямо внутри функции):
– и уже можно наблюдать некоторые поучительные вещи даже без инструментов (не зная миллисекунд): в Firefox как была небольшая задержка при отрисовке, так она и осталась (разницы не видно), а в старом ИЕ (6, 7) первая версия (без лишней нагрузки) отрисовывает практически мгновенно (быстрее, чем ФФ), а вторая, с повторами addClass тормозит почти на секунду.
В нашем сортировщике таблиц мы выводим на экран (вверху, на чёрной полоске) время работы разных участков javascript-кода в миллисекундах. Для этого нужна функция addLod(), добавляющая к чёрной лог-полоске буквы и цифры, и сами значения времени, получить которые можно так:
Поскольку все названия функций мы стараемся делать «говорящими», "Текст сообщения" в приведённом примере у нас выглядит однообразным до тупости – это просто названия функций: addLod(d1 – d0, "makeSortArr"), addLod(d1 – d0, "sort")... Налицо материал для оптимизации – неоднократное повторение одних и тех же действий: фиксация времени (d1 = new Date()) и запись свойства "Имя функции".
Оптимизация невозможна изнутри функции: если имя функции ещё как-то можно получить в её тексте (arguments.callee.name), то зафиксировать время нельзя без вызова new Date() дважды. То есть нельзя уменьшить в теле функции количество строк, необходимых для измерения времени. Это можно сделать, если вызывать функцию через «обёртку».
Идея достаточно известная. Вместо простого вызова функции makeSortArr() вызывать другую функцию, которой передавать основную в качестве параметра: fixTime(makeSortArr). Сама функция fixTime могла бы выглядеть примерно так:
В нашем сортировщике таблиц есть функции, которые возвращают значение. Тогда и обёртка должна его возвращать. Например, надо вызвать функцию, подготавливающую заголовк таблицы; вызов будет выглядеть так: var irr = fixTime(makeThead), а сама функция в этом случае:
А тут и ещё одна, кстати, проблема: функции makeThead необходим параметр – таблица, в которой нужно оформлять заголовок. Значит, этот параметр надо тоже передавать обёртке. А он может быть не один. Функция-обёртка заметно усложнится:
Мы делаем в ней комментарий-напоминание, но не верим, что его всегда будут читать, и поэтому проверяем тип первого получаемого функцией параметра (если этот параметр не функция, прекращаем работу). Вызывать функцию надо будет так: var irr = fixTime(makeThead, t).
Мы незатейливо пишем во всех вариантах обёртки f.name, но свойство name есть не у каждой функции. Например, у нашей функции, выводящей отсортированный массив на страницу, его нет. Потому что она была создана так:
То есть, говоря попросту, она – анонимная. И не одна она. Это полбеды, этому горю легко помочь, если знать заранее, что мы будем использовать имя функции – надо просто всегда создавать функции (даже анонимные!) с указанием имени, да и всё:
Настоящая беда в другом – как всегда, в проделках говно микрософтверов: в Интернет Эксплорере у функций javascript нет свойства name никогда, даже у именованных функций, так что все наши хитро...пые page = function page() всё равно коту (Биллу) под хвост, и ни хрена у нас с нашей обёрткой не получится.
Или придётся в очередной раз схитрить, извернуться. Наморщите ум: имени у функции нет; но как-то ведь мы к ней обращаемся при вызове, называем (вызываем) её по ...? Что вот это такое – "makeThead" в контексте всего исполнения кода javascript? Это – свойство. Свойство какого-то объекта. Чаще всего, глобального объекта window.
То есть функцию можно вызвать так: window["makeThead"](t). А значит, в нашу обёртку можно передавать не ссылку на функцию, а строку – имя свойства какого-то объекта, которое является функцией. Объект тоже на всякий случай будем указывать (а то вдруг функция была создана как свойство какого-то самодельного объекта, а не window). Тогда вызывать обёртку надо будет с ещё одним параметром: var irr = fixTime(window, "makeThead", t). А сама функция-обёртка станет такой:
Дополнительная оптимизация: вместо приведения аргументов к массиву через цикл for мы использовали вызов метода самого массива (метода slice, которого так-то у объекта arguments нет) – с помощью конструкции Array.prototype.slice.call (спасибо Илье Кантору, который не поленился поместить лишний пример в свой справочник на javascript.ru).
Нельзя не упомянуть здесь интересное решение, которое делает вызов тестируемых функций более красивым:
Это краткий конспект, выжимка из скрипта benchmark.js на стр. with-love-from-siberia. После добавления к прототипу Function нового метода eval, вызывать функции с измерителем времени можно так: var irr = makeThead.eval(t).
Идея красивая, но пока менее универсальная, чем предложенная в нашей статье. Во-первых, остаётся вопрос с именем функции – придётся выдирать его из текста функции регэкспом (предварительно добавив имена всем анонимным функциям). А во-вторых, конструкция this.apply просто не работает в нашем Сортировщике: там все основные действия происходят внутри анонимной функции в круглых скобках: (function(){})(), а в этом месте то ли нет контекста (this), то ли он исчезает после выполнения функции – в общем, попытка использовать eval из прототипа выдаёт ошибку: "1 is not a function".
Приведите, пожалуйста, пример, который выводит сообщение "1 is not a function". Спасибо.