Опубликован: 16.02.2009 | Доступ: свободный | Студентов: 1430 / 138 | Оценка: 4.26 / 4.17 | Длительность: 16:10:00
ISBN: 978-5-9963-0024-2
Лекция 7:

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

Аннотация: Отложенная загрузка скриптов волнует общественность уже давно, почти 10 лет, - атрибут defer, призванный ее обеспечить, был добавлен в спецификацию еще в прошлом столетии. Однако проблема так и остается актуальной. Данная лекция посвящена о корректной работе и выгодном использовании тегов и скриптов с целью оптимизации JavaScript. Кроме того освещен и вопрос о клиентской оптимизации использования рекламы на сайтах.

Кроссбраузерный window.onload

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

Событие window.onload используется программистами для старта их веб-приложения. Это может быть что-то довольно простое, например выпадающее меню, а может быть и совсем сложное - скажем, запуск почтового приложения. Суть проблемы заключается в том, что событие onload срабатывает только после того, как загрузится вся страница (включая все картинки и другое бинарное содержимое). Если на странице много картинок, то можно заметить значительную задержку между загрузкой страницы и тем моментом, когда она начнет фактически работать. На самом деле, нам нужен только способ определить, когда DOM полностью загрузится, чтобы не ждать еще и загрузку картинок и других элементов оформления.

Firefox впереди планеты всей

В Firefox есть событие специально для этих целей: DOMContentLoaded. Следующий образец кода выполняет как раз то, что нам нужно, в Mozilla-подобных браузерах (а также в Opera 9 и старше):

// для Firefox
if (document.addEventListener) {
	document.addEventListener("DOMContentLoaded", init, false);
}

А Internet Explorer?

IE поддерживает замечательный атрибут для тега <script>: defer. Присутствие этого атрибута указывает IE, что загрузку скрипта нужно отложить до тех пор, пока не загрузится DOM. Однако это работает только для внешних скриптов. Следует также заметить, что этот атрибут нельзя выставлять, используя другой скрипт. Это означает, что нельзя создать <script> с этим атрибутом, используя DOM-методы, - атрибут будет просто проигнорирован.

Используя этот удобный атрибут, можно создать мини-скрипт, который и будет вызывать наш обработчик onload:

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

Содержание этого внешнего скрипта будет состоять только из одной строчки кода:

init();

Условные комментарии

С этим подходом есть некоторая проблема. Другие браузеры проигнорируют атрибут defer и загрузят этот скрипт сразу же. Существует несколько способов, как можно с этим побороться. Можно воспользоваться условными комментариями, чтобы скрыть "отложенный" скрипт:

<!--[if IE]><script defer="defer" src="ie_onload.js"></script><![endif]-->

IE также поддерживает условную компиляцию. Следующий код будет JavaScript-эквивалентом для заявленного выше HTML-кода:

// для Internet Explorer
/*@cc_on @*/
/*@if (@_win32)
document.write("<script defer=\"defer\"
	src=\"ie_onload.js\"><\/script>");
/*@end @*/

Все так просто?

И конечно же, нам нужно обеспечить поддержку для остальных браузеров. У нас есть только один выход - стандартное событие window.onload:

// для остальных браузеров
window.onload = init;

Двойное выполнение

Избавляемся от внешнего файла

У описанного решения существует пара минусов:

  • Для IE нам требуется внешний JavaScript-файл.
  • Не поддерживается Safari (Opera 9 поддерживает DOMContentLoaded).

Однако есть решение и для Internet Explorer, которое не зависит от внешних файлов (к сожалению, на данный момент вызывает предупреждение безопасности в IE7 при использовании защищенного соединения):

// для Internet Explorer (используем условную компиляцию)
/*@cc_on @*/
/*@if (@_win32)
document.write("<script id=\"__ie_onload\" defer=\"defer\" 
src=\"javascript:void(0)\">
<\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
 if (this.readyState == "complete") {
	init(); // вызываем обработчик для onload
	}
};
/*@end @*/

И для Safari!

if (/WebKit/i.test(navigator.userAgent)) { // условие для Safari
 var _timer = setInterval(function() {
 	if (/loaded|complete/.test(document.readyState)) {
		clearInterval(_timer);
		init(); // вызываем обработчик для onload
	}
 }, 10);
}

Полное решение

function init() {
	// выходим, если функция уже выполнялась
	if (arguments.callee.done) return;

	// устанавливаем флаг, чтобы функция не выполнялась дважды
	arguments.callee.done = true;

	// что-нибудь делаем
};

/* для Mozilla/Firefox/Opera 9 */
if (document.addEventListener) {
	document.addEventListener("DOMContentLoaded", init, false);
}

/* для Internet Explorer */
/*@cc_on @*/
/*@if (@_win32)
	document.write("<script id=\"__ie_onload\"
		defer=\"defer\" src=\"javascript:void(0)\">
<\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
	if (this.readyState == "complete") {
		init(); // вызываем обработчик для onload
	}
};
/*@end @*/

/* для Safari */
if (/WebKit/i.test(navigator.userAgent)) { // условие для Safari
 var _timer = setInterval(function() {
	if (/loaded|complete/.test(document.readyState)) {
		clearInterval(_timer);
		init(); // вызываем обработчик для onload
	}
 }, 10);
}

/* для остальных браузеров */
window.onload = init;