Опубликован: 16.02.2009 | Уровень: специалист | Доступ: платный
Лекция 7:

Оптимизация JavaScript

В будущем

В будущие версии Safari и IE8 уже внесены изменения, которые коснулись способа загрузки скриптов. Идея заключается в том, чтобы загружать скрипты параллельно, но исполнять в той последовательности, в которой они находятся на странице. По всей видимости, в один прекрасный день проблема блокирующих скриптов при загрузке станет попросту неактуальной, потому что будет касаться только пользователей IE 7 и младше или Firefox 3 и младше. Пока же наиболее простым способом решения данной проблемы является применение динамического тега <script>.

Основы "ненавязчивого" JavaScript

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

Давайте рассмотрим, как научиться использовать все преимущества современных браузеров, не ограничивая при этом пользователей более старых версий.

JavaScript: храним отдельно

Одна из сильных сторон JavaScript заключается в том, что его можно поместить в отдельный файл. Как и для CSS это означает, что можно присвоить одну коллекцию функций для каждой страницы сайта, и если нам нужно изменить ее функциональность, можно сделать все это в одном документе, что предпочтительнее, чем перебирать все страницы и менять какой-то один участок кода на другой.

Например, мы можем подключить только один такой файл, который будет обеспечивать всю логику функционирования страницы в клиентском браузере:

<script type="text/javascript" src="scripts.js"></script>

JavaScript - это расширение

Мы используем JavaScript, только чтобы улучшить уже существующую функциональность, так как мы не можем на него полностью полагаться. JavaScript может быть выключен или отфильтрован прокси-серверами и файерволлами компаний, обеспечивающих безопасность. Мы никогда не можем принимать JavaScript за данноcть. Это не означает, что мы совсем не можем использовать Javascript, это лишь значит, что мы можем добавлять его только как дополнительную возможность. Страницы должны работать и при выключенном JavaScript - это одно из основных правил "ненавязчивого" JavaScript.

Давайте рассмотрим для примера следующий HTML-код:

<form action="/">
 <p><label for="login">Логин:</label> 
 <input type="text" name="login" id="login"/></p>
 <p><label for="password">GПароль:</label>
 <input type="password" name="password" id="password"/></p>
 <p><input type="button" onclick="checkform()" value="Войти"/></p>
</form>

Если у пользователя отключен JavaScript (или он отрабатывает некорректно в силу особенностей различных браузеров), то зайти на сайт будет просто невозможно. И это только по причине неверного проектирования, а не ошибки на стороне сервера или неверного расположения элементов в дизайне.

Мы можем исправить этот фрагмент, заменив button на submit и добавив обработчик события submit для формы:

<p><input type="submit" value="Войти"/></p>
...
<script type="text/javascript">
	document.forms[0].onsubmit = checkform;
</script>

Доверять, но проверять

Множество ошибок в JavaScript происходит просто потому, что разработчики слишком ленивы, чтобы проверить, доступен или нет данный объект или метод. Всегда при использовании какой-либо внешней переменной (которая не определена явно в этой функции) либо функционала, зависящего от браузера (например, DOM-методов), необходимо быть уверенным, что используемый объект или метод существует.

В качестве примера правильных проверок можно привести следующий код:

function color(object, color) {
 if(object) {
	if (color) {
		if (object.style) {
			object.style.color = color;
		}
	}
 }
}

Если мы хотим убедиться, что браузер поддерживает W3C DOM, то достаточно провести следующую проверку:

if (document.getElementById) {
}

В общем случае нет никакой необходимости полагаться на передаваемую браузерами строку агента.

Доступ к элементам

Каждый XML- (а также и HTML-) документ - это дерево узлов. Узел - часть этого дерева (в качестве аналогии можно привести дерево файлов и директорий на жестком диске). Давайте посмотрим, какие функции и атрибуты мы можем использовать, чтобы перемещаться по дереву документа и выбирать необходимые нам узлы.

getElementById('elementID')
возвращает элемент с идентификатором, равным elementID
getElementsByTagName('tag')
возвращает массив элементов с именем tag

Естественно, мы можем смешивать и сочетать эти два варианта. Несколько примеров:

document.getElementById('nav').getElementsByTagName('a')[1]; 
//	возвращает вторую ссылку внутри элемента, который имеет ID 'nav'

document.getElementsByTagName('div')[1].getElementsByTagName('p')[3];
//	возвращает четвертый параграф внутри второго div в документе.

Полный перечень всех DOM-методов, которые поддерживаются сейчас практически всеми браузерами, здесь приводить не имеет смысла. При желании с ними можно ознакомиться на сайте w3.org.

Полезные советы

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

  • Прежде чем обращаться к элементу, нужно убедиться, что он существует.
  • JavaScript-код не должен быть привязан к верстке, только к DOM-дереву. Лишний перевод строки может быть прочитан как новый текстовый узел, разметка может поменяться, а менять скрипты при каждом изменении дизайна не очень хочется.
  • HTML, полученный через DOM-методы, в большинстве случаев невалиден. Если мы хотим его повторно использовать, лучше всего привести его к валидному виду или применять innerHTML для всех таких случаев (задавая в качестве параметра часть валидного документа).
  • Следует избегать частого перебора элементов. Каждая операция (особенно getElementesByTagName ) довольно ресурсоемка. Стоит кэшировать наиболее часто используемые элементы (подробнее о кэшировании в JavaScript рассказывается чуть далее в этой главе).
  • Не стоит проверять атрибуты, которых нет (если мы знаем верстку и знаем JavaScript-код, то в нем не должны появиться неизвестные атрибуты).
  • Нужно подходить осторожно к верстке с других сайтов. Например, при проверке className на наличие определенной строки нельзя использовать точное соответствие, только регулярные выражения (ибо атрибут этот может содержать несколько классов, разделенных пробелом).

Добавляем обработчики событий

Главная техника, которую мы используем, чтобы сохранить наш JavaScript "ненавязчивым", - это хранение скрипта в отдельном файле, что предпочтительней, чем смешивать его с разметкой страницы. Чтобы исполнить функции в нашем .js -файле, нам надо вызвать их, когда страница загружена (подробнее о событии загрузки страницы было рассказано в начале этой главы).

В некоторых случаях (например, при экстремальной оптимизации, глава четвертая) весь JavaScript-код может находиться в HTML-документе, заключенный в <script type="text/javascript">...</script> . Но это не будет означать, что мы смешиваем разметку страницы с ее обработкой, а содержание - с функциональностью. В этих случаях JavaScript-код будет полностью отделен от содержания, для которого он предназначен.

Существует возможность добавлять обработчики событий в комплект к уже существующим обработчикам. При вызове функции мы передаем ей объект, который нужно привязать к событию, тип события и имя функции.

function addEvent(object, eventType, function){
 if (object.addEventListener){
 	object.addEventListener(eventType, function, false);
 	return true;
 } else {
 	if (object.attachEvent){
 		var r = object.attachEvent(\"on\"+eventType, function);
 		return r;
 	} else {
 		return false;
 	}
 }
}

События - довольно сложная тема в Javascript. Для разработки простых веб-сайтов указанных примеров достаточно, но если мы переходим к разработке веб-приложений, тут ситуация многократно усложняется. Поэтому стоит быть внимательным к их функционированию в условиях отключенного или неподдерживаемого JavaScript.

Ускоряем обработку событий

Давайте рассмотрим, как можно использовать методы "ненавязчивого" JavaScript для максимального ускорения обработки событий в браузере. Мы можем уменьшить число приемников событий, которые назначены документу, путем определения одного приемника для контейнера и проверки в обработчике, из какого дочернего элемента всплыло это событие.

Предположим, что у нас основная навигация по сайту включает шесть ссылок сверху, четырем из которых требуются обработчики событий, чтобы поменять у них атрибут href. У этих четырех ссылок атрибут class выставлен в bundle.

Скорее всего, ситуация будет выглядеть следующим образом.

var MenuNavigation = {
 init: function() {
 	var navigation = document.getElementById('mainNav');
 	var links = navigation.getElementsByTagName('a');
 	for ( var i = 0, j = links.length; i < j; ++i ) {
 		if ( /bundle/i.test(links[i].className) ) {
 			links[i].onclick = this.onclick;
 		}
 	}
 },
 onclick: function() {
 	this.href = this.href + '?name=value';
 	return true;
 }
}

В этом фрагменте довольно много лишнего. Во-первых, метод getElementsByTagName просматривает каждый дочерний DOM-узел в элементе mainNav, чтобы найти все ссылки. Затем мы еще раз пробегаем по всему найденному массиву, чтобы проверить имя класса каждой ссылки. Это пустая трата процессорного времени на каждом этапе. И это замедление загрузки страницы на уровне JavaScript-логики.

Дарья Билялова
Дарья Билялова
Россия
Елена Петрушевская
Елена Петрушевская
Россия, г. Нижневартовск