22 декабря 2015
12 июля 2010
20
Как тестировать и оптимизировать JS скрипты
Всем привет!
Сегодня мы посмотрим как можно тестировать производительность своего JS кода и, соответственно, оптимизировать его.
Пару месяцев назад я написал статью про Firebug и console, в которой мы рассматривали как пользоваться этой самой консолью. Сегодня мы будем использовать метод console.profile() и увидим как с его помощью можно протестировать и затем улучшить скрипты.
Статья основана на статье Сидхарта «How jQuery beginners can test and improve their code».
Песочница
Для начала создадим небольшой HTML документ, в котором будут располагаться несколько элементов и код, который мы будем тестировать.
<!DOCTYPE html>
<html lang="ru-RU">
<head>
<title>Тестирование производительности JS скриптов</title>
</head>
<body>
<div id="container">
<div class="block">
<p id="first">Какой-то текст</p>
<ul id="someList">
<li class="item"></li>
<li class="item selected" id="mainItem">Какой-то элемент с текстом</li>
<li class="item"></li>
<li class="item"></li>
</ul>
</div>
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
console.profile() ;
// Здесь будем размещать наш код
console.profileEnd();
</script>
</body>
</html>
Сам код будем помещать между console.profile() и console.profileEnd().
Проверка на существование элемента
Часто бывает так, что для всех страниц сайта подгружаются одни и теже скрипты, соответственно, иногда может не быть нужных элементов. Несмотря на то, что jQuery не выполнит код для несуществующих элементов, лучше все-таки делать проверку на существование элементов. Рассмотрим два варианта кода и обратим внимание на время их выполнения. Первый код не делает проверку:
console.profile();
var ele = $("#somethingThatisNotHere");
ele.text("Some text").slideUp(300).addClass("editing");
$("#mainItem");
console.profileEnd();
В результате выполнения этого кода получим вот такую картинку в консоли Фаербага:
Второй код проверяет, существуют ли элементы, над которыми нужно выполнить действие:
console.profile() ;
var ele = $("#somethingThatisNotHere");
if ( ele[0] ) {
ele.text("Some text").slideUp(300).addClass("editing");
}
$("#mainItem");
console.profileEnd();
И выдает вот это в консоль:
В итоге делаем вывод, что лучше проверять на существование элемента — скорость выполнения будет больше. Но это не означает, что нужно проверять все подряд: обычно, есть основной элемент группы, без которого все остальные все равно не могут быть. Вот его-то и надо проверять.
Эффективное использование селекторов
Скорее всего вы читали мою статью про оптимизацию CSS. Если нет, то прочитайте. Там я рассказывал о том, как браузеры разбирают селекторы и какие селекторы с какой скорость работают. Если кратко, то быстрее всех работает селектор по id, медленнее всех — универсальные.
Итак, давайте проведем эксперимент. Замечу, что скорости выполнения скриптов могут отличаться на разных компьютерах и браузерах.
Для начала попробуем выбрать элементы по классу:
console.profile() ;
$(".selected");
console.profileEnd();
Результат 0.308ms. Далее уточним селектор, указав что это должны быть только элементы списка <li>:
console.profile() ;
$("li.selected");
console.profileEnd();
Результат 0.291ms — уменьшили на 0.027ms. Теперь еще немного уточним селектор: нужные нам элементы должны находиться внутри контейнера с id="someList":
console.profile() ;
$("#someList .selected");
console.profileEnd();
0.283ms — небольшое улучшение. Уточним селектор именем тега:
console.profile() ;
$("#someList li.selected");
console.profileEnd();
Получили 0.275ms. Теперь выберем элемент прямо по id ради интереса:
console.profile() ;
$("#mainItem");
console.profileEnd();
0.165ms — наш новый рекорд. Теперь думаю ясно как лучше всего писать селекторы.
Избегание излишних операций
Иногда, в коде могут встретиться конструкции типа:
// Какой-то код
$(element).doSomething();
// Потом еще код
$(element).doSomethingElse();
// И еще код
$(element).doMoreofSomethingElse();
Никогда так не делайте. Один элемент запрашивается снова и снова. Это слишком затратно с точки зрения производительности.
Возьмем нашу песочницу и проведем в ней какой-то похожий процесс:
console.profile() ;
$("#mainItem").hide();
$("#mainItem").val("Привет");
$("#mainItem").html("Привет!");
$("#mainItem").show();
console.profileEnd();
Вышеприведенный код так же можно сделать в виде цепочки:
console.profile();
$("#mainItem").hide().val("Привет").html("Привет!").show();
console.profileEnd();
При использовании цепочки, элемент запрашивается один раз и далее методам передается ссылка на него. Это снижает время выполнения.
Так же можно закешировать элемент и производить действия уже над закешированным:
console.profile() ;
var elem = $("#mainItem");
elem.hide();
elem.val("Hello");
elem.html("Oh, hey there!");
elem.show();
console.profileEnd();
Как видно из примеров, кеширование и использование цепочек действия снижает время выполнения скриптов.
Умное манипулирование DOM
Как известно, операции с DOM (объектной моделью документа), например получение или вставка элементов, являются очень ресурсоемкими. Давайте посмотрим, как можно ускорить эти операции.
Создадим и добавим в наш документ 50 элементов списка <li>. В первом коде будем добавлять их прямо внутри цикла:
console.profile() ;
var list = $("#someList");
for (var i = 0; i < 50; i ++) { list.append('<li>Item #' + i + '</li>');
}
console.profileEnd();
Медленно, не правда ли? Теперь немного изменим алгоритм: в цикле соберем все нужные нам элементы и потом добавим их одним разом в документ:
console.profile() ;
var list = $("#someList");
var items = "";
for (var i = 0; i < 50; i ++){
items += '<li>Item #' + i + '</li>';
}
list.append(items);
console.profileEnd();
Как видно, время выполнения существенно уменьшилось.
Заключение
Вы заметили, что время выполнения оптимизированного и неоптимизированного скриптов различается буквально доляи миллисекунд. Это связано с тем, что наш документ очень мал (содержит мало узлов).
Так же заметьте, что в примерах мы просто получали элементы их DOM. Если бы мы начали применять к ним события или что-то еще, разница во времени была бы более существенной.
По поводу маленького DOM'а так сделали бы его большим ) взяли бы хотя бы страницу с этой статьей, результаты поиска в яндексе или гугле или какой нить популярный крупный сервис -- было бы нагляднее мне кажется.
А пример с 50-ю LI по моему слегка притянут за уши.
чёрт его знает, сколько оно там выполняется..
спасибо!