Статьи

Перевод: Николай Мациевский aka sunnybear
Опубликована: 4 марта 2008

Создаем высокопроизводительные HTML-страницы для IE

Примечание: ниже находится перевод статьи с MSDN "Building High Performance HTML Pages", в которой приводится ряд советов от экспертов из Microsoft по оптимизации времени загрузки страниц. Очень интересно сравнить их подход с аналогичным для Yahoo!. На мой взгляд, большая часть советов уже не является такой актуальной (спасибо  Zeroglif, все приведенные советы, скорее всего, десятилетней давности), но в свое время все они были весьма действенны. Мои комментарии далее курсивом.

Интернет, интранеты (внутренние корпоративные порталы) и экстранеты (все то, что находится в свободном доступе) содержат уйму информации. Сейчас значительная часть этой информации являет собой HTML в том или ином виде.

Коммуникационные возможности, обеспечиваемые Microsoft Internet Explorer 4.0 и более поздними версиями, помогли превратит Веб в мощное пространство, где можно как работать, так и играть. Число HTML-страниц и их сложность вместе с общим количеством потребителей конечных продуктов существенно увеличили общий интернет-трафик. Вместе со всеми выгодами, которые получили разработчики приложений, это привело и к ряду проблем. Среди этих проблем можно выделить:

  • Доставка содержания через интернет (across the wire).
  • Будучи доставленным, содержание должно быстро отобразиться на экране.

В этой статьи предлагается несколько советов, как добиться максимальной производительности ваших страниц.

Содержание

Повторное использование загруженных HTML-компонентов и внешних скриптов

Michael Wallent в своей заметке Frequent Flyers: Boosting Performance on DHTML Pages рекомендовал использование .js файлов для объединения часто используемых скриптов. Возможность обращаться к внешним файлам скриптов (как Microsoft JScript, так и Microsoft Visual Basic Scripting Edition, VBScript) существовала уже со времен Internet Explorer 3.02. Используя эту возможность пользователю нужно было скачать .js файл только единожды: при его первом обнаружении на странице. Когда Internet Explorer загружает другие страницы, которые ссылаются на этот .js файл, он может быть получен из локального кеша. Дополнительным преимуществом в использовании внешних .js файлов является удобство поддержки. Процедура, которая заключена в одном-единственном .js файле, должна быть изменена только в одном месте. Все страницы, на которых эта процедура используется, автоматически «подхватят» изменения в скрипте при своей следующей загрузке.

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

И наконец, последний совет по повторному использованию кода: Dynamic HTML (DHTML) behaviors. DHTML-поведения (behaviors), представленные в Internet Explorer 5, являются логическим продолжением техники повторного использования определенных кусков кода. Так же, как и .js файлы, DHTML-поведения могут быть определены вне тех страниц, где будут впоследствии подключаться. В отличие от .js файлов, но позволяют более прозрачно использовать все преимущества внешнего подключения кода в силу своего простого синтаксиса определения, который более естественен для HTML-кодеров. Разработчики, которые озабочены обратной совместимостью их кода, оценят возможность добавления функциональности без необходимости защиты кода от рассмотрения старыми браузерами с помощью условных конструкций (скорее всего, имеется в виду метод условных комментариев (conditional comments) и условной компиляции (conditional compilation), которые присутствуют в IE).

На данный момент большинство веб-разработчиков привыкли писать код, который проверят версию браузера перед выполнением последующих действий, например, так:

var sUA = window.navigator.userAgent;
var bIsIE4 = -1 != sUA.indexOf("MSIE 4"); 
if (bIsIE4)
{
   // исполняем код, предназначенный для Internet Explorer 4.x
}

Удобство использования сайта для пользователя можно последовательно наращивать, если аккуратно использовать поведения для перехвата событий и прикреплять их к элементам при помощи атрибутов STYLE и class. Более подробную информацию можно получить из Using HTML Components to Implement DHTML Behaviors in Script.

Отложенная (DEFER) загрузка скриптов

defer является достаточно неясным атрибутом для элемента script, однако, авторы веб-страниц, которые заботятся об их производительности, могут использовать его для уведомления Internet Explorer 4+, что тег script не содержит никакого кода, который требует моментального исполнения и повлияет на внешний вид документа, прежде чем сработает событие load. Если использовать его совместно с атрибутом src, то DEFER для тега script можно применить в качестве индикатора для Internet Explorer, что весь файл может быть загружен в фоновом режиме, пока все остальные компоненты страницы загружаются и анализируются.

В спецификации HTML 4.0 присутствует специальное указание касательно метода write у объекта document. Если вы помечаете ваши скрипты как отложенные, то в них не должно содержаться никаких вызовов document.write из-за того, что обычно они происходят сразу же, как встречаются при загрузке страницы. Также вы не можете объявить глобальные переменные, если они могут использоваться текущими скриптами, в отложенном режиме.

<SCRIPT DEFER>
function UsedLater()
{
    // полезная функция, вызывая при действиях пользователя на странице
}
</SCRIPT>

<SCRIPT>
    // Нельзя отложить загрузку этого блока,
    // потому что он затрагивает внешний вид документа
    document.write("<H1>Immediate Gratification</H1>");
</SCRIPT>

Кеш Internet Explorer регистрозависимый. Это означает, что должны выбирать названия для ваших ссылок (URL'ов), имея в виду различия строчных и прописных букв. Давайте рассмотрим следующий пример.

<A HREF="/workshop/author/dhtml/reference/dhtmlrefs.asp">DHTML References</A>

<A HREF="/Workshop/Author/DHTML/Reference/DHTMLRefs.asp">DHTML References</A>

Обе ссылки указывают на одну и ту же страницу. Или нет? На UNIX системе эти ссылки будут, скорее всего, вести на две различных страницы. Таким образом, Internet Explorer будет различать их и отправит независимые запросы на сервер, позволяя серверу самому решить, что делать с такими запросами.

Если вы будете аккуратны в наименовании ваших ссылок, то сэкономите место в пользовательском кеше, и, что, на самом деле, более важно, уменьшите число запросов от Internet Explorer к серверу для открытия одного и того же ресурса.

Использование таблиц с фиксированной шириной

Таблицы замечательно подходят для организации информации. До того, как CSS-позиционирование появилось в Internet Explorer 4.0, многие авторы HTML-страниц использовали таблицы для расположения элементов согласно макету. Если вы до сих пор используете таблицы для верстки, то стоит перейти на позиционирование при помощи Каскадных Таблиц Стилей (Cascading Style Sheets, CSS). Начиная с Internet Explorer 5 производительность страниц, использующих CSS вместо таблиц, заметно выше.

Естественно, это не является абсолютным аргументом для полного отказа от использования таблиц. Они по-прежнему могут использоваться, особенно, если вы определите соответствующий table-layout — CSS-атрибут, используемый для достижения оптимальной производительности для Internet Explorer 5+. Следующие действия позволят Internet Explorer начать отображение таблицы еще до того, как будет получена вся информация о ней.

  • Установите для table CSS-атрибут table-layout в значение fixed.
  • Явно определите объекты col для каждого столбца.
  • Установите для каждого элемента col атрибут width.

Для более подробной информации по поводу данной методике можно ознакомиться со статьей Enhancing Table Presentation.

Оптимизация скриптов

Рассуждая об оптимизации, не достаточно просто советов по написанию стабильного кода, который не будет зависеть от платформы или языка. При фокусировке на быстродействии Internet Explorer 4.0+ также требуется хорошее понимание принципов и возможностей DHTML Object Model. Спецификация DHTML Object Model и методы работы с нею являются достаточно объемным материалом для изучения, а полное ее обсуждение лежит за пределами данной статьи. Для рассмотрения ситуации с точки зрения оптимизации давайте обратимся к типичным DHTML-операциям: доступу к набору элементов.

В DHTML итерация при переборе массива объектов является типичной операцией. Давайте предположим, что вы разрабатываете HTML-приложение, которое индексирует содержание страниц. Вашей задачей является сбор всех элементов H1 на текущей странице, чтобы затем использовать их в проиндексированном массиве.

Ниже приведен пример, как это можно осуществить:

function Iterate(aEntries)
{
    for (var i=0; i < document.all.length; i++)
    {
	if (document.all(i).tagName == "H1")
	{
	    aEntries[aEntries.length] = document.all(i).innerText;
	}
    }
}

Что плохого в приведенном примере? Он содержит три вызова, обращающихся к массиву all документа. На каждой итерации внутри цикла скрипт будет:

  • Вычислять размер массива.
  • Получать tagName текущего объекта в массиве.
  • Получать значение свойства innerText для текущего элемента в массиве.

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

function Iterate2(aEntries)
{
    var oAll = document.all;
    var iLength = oAll.length;
    for (var i=0; i < iLength; i++)
    {
	if (oAll(i).tagName == "H1")
	{
	    aEntries[aEntries.length] = oAll(i).innerText;
	}
    }
}

В этой версии создаются две локальные переменные, в которых кешируются массив all и его размер. Но мы можем еще улучшить данный пример:

function Iterate3(aEntries)
{
    var oH1Coll = document.all.tags("H1");
    var iLength = oH1Coll.length;
    for (var i=0; i < iLength; i++)
    {
	aEntries[aEntries.length] = oH1Coll(i).innerText;
    }
}

Используя метод tags вместо массива all мы возлагаем нагрузку на выбор всех необходимых элементов на DHTML Object Model, что позволяет избавиться от дополнительного условия на каждой итерации цикла (при этом, скорее всего, накладные издержки уменьшаются еще сильнее, ибо вместо N обращений к свойству tagName мы получаем один-единственный вызов tags).

Замечание: избегайте разветвлений при кешировании DHTML-массивов. DHTML-массивы являются полностью динамическими, и любой ваш код, который создает новые элементы и добавляет их в документ, может как увеличивать эти массивы, так и уменьшать их.

Рациональное использование областей видимости

JScript и VBScript являются интерпретирующими языками. Из-за того, что все действия производятся в момент исполнения (в противовес компилируемым языкам), они весьма медленные. Внутри скрипта каждая ссылка на объект выливается в 2 вызова из скрипта к DHTML Object Model. Если эти подробности реализации вам неизвестны или вы хотите ознакомиться более детально с Автоматизацией, стоит обратиться к документации по IDispatch::GetIDsOfNames и IDispatch::Invoke в Microsoft Platform software development kit (SDK).

В следующем примере мы определяем div, у которого задан ID в значение div1.

<DIV ID="div1">

Когда Internet Explorer анализирует страницу, он дополнительно учитывается все объекты, у которых дополнительно выставлен атрибут ID. Затем он добавляет все такие элементы в глобальную область видимости скрипта, позволяя общаться к ним напрямую. Это означает, что следующий пример избыточен.

<SCRIPT>
    var sText = document.all.div1.innerText;
</SCRIPT>

Вдобавок к небольшому количеству байтов, которые будут дополнительно переданы через Интернет и проанализированы скриптом, им будет сделано 4 дополнительных вызова к Internet Explorer для получения свойства innerText объекта div. Чуть далее выписан весь код, которого достаточно для получения innerText от div.

<SCRIPT>
    var sText = div1.innerText;
</SCRIPT>

Существенными исключениями из этого минималистического подхода будут:

  • Обращение к элементам, находящимся внутри form.
  • Обращение к свойствам iframe.
  • Обращение к свойствам объекта window.

Элементы, содержащиеся внутри form, находятся только в пределах области видимости form. Например:

<FORM ID=form1>
    <INPUT NAME="txtUrl">
</FORM>

<SCRIPT>
    txtUrl.value = ... // выдаст ошибку скрипта
    form1.txtUrl.value = ... // все нормально
</SCRIPT>

Для обеспечения безопасности при передаче данных между фреймами свойства iframe properties must be fully scoped as shown in the following example.

<IFRAME ID="oFrame1" SRC="http://www.msn.com">
</IFRAME>

<SCRIPT>
    // document.all требуется для изменения SRC у IFRAME
    document.all.oFrame1.src = 	form1.txtUrl.value;
</SCRIPT>

Когда скрипт находит ссылку на объект, выходящий за границы видимости текущего кода, он разрешает ее, начиная с самой левой части этой ссылки в поисковой таблице (look-up table). На скорость осуществления скриптом поиска влияют два фактора:

  • Число записей в поисковой таблице.
  • Область видимости ссылки.

Число записей в таблице совпадает с числом глобальных переменных, именованных объектов и функций, которые определены на вашей странице. Следовательно, стоит объявлять атрибут ID только для тех атрибутов, которыми вы хотите манипулировать из скрипта. (Парадоксально, но при объявлении ID для назначения элементу специфических стилей (#footer, #header), стили анализируются, в среднем, быстрее. Наверное, есть какая-то «золотая середина», но если на странице используется как большое количество стилей, так и скриптов, стоит отдать предпочтение производительности скриптов.)

Как было указано выше, есть правильный и неправильный путь для задания областей видимости ссылок (references). При назначении атрибута ID элементу, нет никакой нужды получать его через document.all. Применяя этот подход, вы, наверное, будете считать, что вы должны минимизировать обращения к объектной модели во всех случаях. Однако, давайте взглянем еще на один распространенный пример, когда это правило не применимо:

var sUA = navigator.userAgent;

Этот код работает. Он сохраняет строку HTTP_USER_AGENT в переменной sUA. Но для того, чтобы скрипт разрешил эту ссылку, ему нужно, в первую очередь, найти объект navigator в глобальной поисковой таблице. После просмотра всей таблицы, если там не обнаружится такого объекта, скрипту придется обойти ее еще раз и запросить каждый объект, есть ли у него ссылка на navigator. Когда он доступится, наконец, до глобального объекта window, у которого обнаружится свойство navigator, то скрипт сможет получить у него свойство userAgent. В данном случае самым лучшим способом разрешить эту ситуацию (особенно на страницах с большим количеством глобальных объектов) будет объявление в ссылке полного пути:

var sUA = window.navigator.userAgent;

Если все эти разговоры по поводу поисковых таблиц и глобальных объектов заставили вас задуматься, стоит просто запомнить простое правило: полностью именуйте ссылки на членов объекта window. Полный список таких членов доступен в DHTML Reference.

Закрытие тегов

В отличие от XML, в HTML существует понятие неявно закрытых тегов. Они включают frame, img, li и p. Если вы не закроете эти теги, Internet Explorer нормально отобразит вашу страницу. Если вы закроете их, Internet Explorer отобразит вашу страницу даже быстрее.

Очень заманчиво писать код примерно в таком стиле.

<P>Следом идет список ингредиентов.
<UL>
<LI>мука
<LI>сахар
<LI>масло
</UL>

Однако, следующий образец будет проанализирован гораздо быстрее, потому что он оформлен правильно (с точки зрения XML-синтаксиса), и Internet Explorer не будет искать по коду, где же заканчивается параграф или элемент списка.

<P>Следом идет список ингредиентов.</P>

<UL>
<LI>мука</LI>
<LI>сахар</LI>
<LI>масло</LI>
</UL>

Настройка заголовка HTTP Expires

Заголовок Expires является частью спецификации HTTP 1.0. Когда HTTP-сервер отправляет ресурс (например, HTML-страницу или изображение) браузеру, он может дополнительно с ответом отправить этот заголовок с меткой времени. Браузеры обычно хранят ресурс вместе с информацией об истечении его срока действия в локальном кеше. При последующих пользовательских запросах к одному и тому же ресурсу браузер может сравнить текущее время и метку времени у закешированного ресурса. Если метка времени указывает на дату в будущем, то браузер может просто загрузить ресурс из кеша вместо запроса его с сервера.

Даже если дата истечения срока действия ресурса находится в будущем, браузеры (например, включая Internet Explorer 4.0) по-прежнему выполнят дополнительный GET-запрос на сервер для определения, является ли закешированная версия ресурса такой же, как на сервере. После тщательной анализа разработчики Internet Explorer установили, что издержки на дополнительный запрос не являются ни оптимальными, ни необходимыми. По этой причине поведение Internet Explorer 5 было изменено следующим образом: если окончание срока действия закешированного ресурса позже, чем время запроса, то Internet Explorer загрузит ресурс напрямую из кеша без запросов к серверу. Сайты, которые используются заголовок Expires для часто посещаемых, но редко обновляемых страниц получат ощутимый выигрыш в трафике (за счет кеширования), а у пользователей, которые используют Internet Explorer 5, страницы отобразятся быстрее.

Для более подробных сведений о заголовке Expires можно ознакомиться с RFC 2068. Информацию о выставлении этого заголовка для ресурсов на вашем сервера стоит прочитать документацию вашего HTTP-сервера. (Достаточно подробная информация о кешировании и дополнительно о заголовке Expires)

Использование расширений Cache-Control

При разработке веб-сайта частота изменения страниц сильно колеблется. Некоторые страницы будут изменяться ежедневно, некоторые не будут изменяться с самого момента своего создания. Для того, чтобы позволить менеджеру веб-сайт регулировать частоту, с которой браузер должен запрашивать HTTP-сервер об изменениях в ресурсе, в Internet Explorer 5 представлено 2 расширения HTTP-заголовка Cache-Control: pre-check и post-check.

Вводя эти расширения, Internet Explorer уменьшает сетевой трафик, так как отправляет меньше запросов к серверу. Дополнительно Internet Explorer улучшает пользовательское восприятие (user experience), когда отображает ресурсы из кеша и проверяет обновления в фоновом режиме после специального интервала.

Расширения post-check и pre-check для Cache-Control определены следующим образом:

  • post-check

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

  • pre-check

    Определяет интервал времени в секундах, после которого проверка актуальности ресурса должна быть произведена перед его отображением для пользователя.

Когда к браузеру поступает запрос на открытие ресурса, который находится в кеше, при этом кеш содержит расширения Cache-Control (отправленные с сервера как часть заголовка HTTP-ответа), браузер использует эти расширения и следующую логику для получения последней версии страницы с сервера.

  • Если еще не закончился интервал времени post-check, то просто отобразим страницу из кеша.
  • Если с момента последнего запроса страницы прошло время, лежащее между интервалами post-check и pre-check, то отобразим страницу из кеша. При этом в фоновом режиме запросим HTTP-сервер, изменялась ли страница с момента последнего запроса браузером. Если страница изменилась, то запросим ее и сохраним в кеше (при этом, я так понимаю, на клиенте ничего не изменится: у него будет более старая версия, полученная изначально из кеша, однако, ее загрузка произойдет максимально быстро).
  • Если уже истекло время, отмеченное интервалом pre-check, то при запросе страницы пользователем сначала спросим у HTTP-сервера, изменилась ли страница со времени ее последней загрузки браузером. Если страницы изменилась, загрузим ее и отобразим обновленную версию (если страница не изменилась, то ее кеш и расширения Cache-Control, в любом случае, будут обновлены).

Заметим, что кнопка «Обновить» (включая клавишу F5) не запускает данный механизм, потому что «Обновить» всегда отправляет на сервер запрос if-modified-since. Однако, все ссылки будут открываться с помощью описанной выше логики.

В следующем примере сервер уведомляет Internet Explorer, что содержание не будет меняться в течение 1 часа (pre-check=3600) и что его можно загружать прямо из локального кеша. В том же случае, если страница все же изменится, если пользователь запросит ее по истечению 15 минут, Internet Explorer должен отобразить локальный кеш, но при этом в фоновом режиме проверить, является ли сохраненная копия страницы актуальной, и по необходимости загрузить ее с сервера.

Cache-Control: post-check=900,pre-check=3600

Попробуйте сделать следующее:

  1. Создайте на машине с запущенным Microsoft Internet Information Server (IIS) 4.0+ файл, в котором будет следующее.
    <%@ LANGUAGE="jscript" %>
    <%
    Response.AddHeader("Cache-Control", "post-check=120,pre-check=240");
    %>
    
    <H1>Привет, медвед!</H1>

    Эта возможность является языко-независимой и будет работать как в JScript, так и VBScript.

  2. Сохраните все это в файле, скажем, check.pl, в папке, в которой Internet Information Server может запускать скрипты.
  3. Запустите Internet Explorer 5 и загрузите страницу, используя HTTP-протокол. В браузере вы должны увидеть отобразившуюся строку "Привет, медвед!". Закройте браузер.
  4. Измените на сервере .asp файл, заменив в нем содержание внутри H1 на «Adios, mundo!». Сохраните файл.
  5. Перезапустите браузер и введите снова URL, относящийся к первоначальному .asp файлу. Когда страница отобразится, вы должны увидеть первоначальный текст, «Привет, медвед!».
  6. В течение двух минут, прошедших с первоначального запроса файла, вы будете видеть то же самое содержание. Однако, в следующий раз, когда вы загрузите страницу, Internet Explorer загрузит ее в фоновом режиме, и вы увидите обновление (я так понимаю, обновление будет через раз по истечению времени post-check: сначала браузер загрузит страницу, а покажет ее только в следующий раз). После четырех минут после последнего запроса страницы Internet Explorer должен отправить запрос If-Modified-Since на сервер и, получив ответ, что страница изменилась, получить последнюю версию и отобразить ее в браузере.

В предыдущем примере мы рассмотрели, как добавить расширения Cache-Control для одиночной страницы, но, на самом деле, вы можете назначить их для группы страниц, включив в них соответствующий скрипт на стороне сервера. Также на стороне сервера можно выставить эти заголовки не изменяя сами страницы в индивидуальном режиме. В IIS 4.0 для администратора существует возможность определять дополнительные заголовки ответа при помощи Microsoft Management Console. Для подробностей стоит ознакомиться с документацией по последнему IIS.

Читать дальше

Все комментарии (habrahabr.ru)