В книге "Разгони свой сайт" уже затрагивалась тема быстрого добавления стилевых правил в исходный документ динамически, не затрагивая при этом стадию предзагрузки (когда у нас еще белый экран в браузере). В ней однако не был рассмотрен следующий вопрос: какой метод использовать для добавления массива CSS-пра-вил в сам HTML.
Естественно, что таких вариантов существует несколько, и дальше они все будут рассмотрены с точки зрения производительности в клиентском браузере.
Поскольку скорость загрузки отдельного CSS-файла достаточно велика, а нам требуется рассмотреть, как его содержимое может повлиять на скорость его динамического применения к документу, — следовательно, нам нужны сотни или даже тысячи правил. В качестве отправной точки была опять взята главная страница Яндекса, стили которой были вынесены в отдельный файл и скопированы 10 раз. Это дало необходимую задержку (которая существенно больше погрешности, вносимой браузерами) и не сильно увеличило сжатый с помощью gzip файл.
Выполняется к CSS-файлу, затем содержимое последнего вставляется через innerHTML в <body> документа. Случай был выбран просто как базовый, потому что большое число узлов в DOM-дере-ве делает такую операцию сразу менее эффективной, чем вставка в <head>. Да и стили внутри тела документа запрещены стандартами.
// чтобы не копировать всем известный код, запишем так:
var xhr = new XMLHttpRequest;
if (xhr) {
xhr.onreadystatechange = function() {
try {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
// вставим полученные данные прямо в <body>
document.body.innerHTML +=
'<style type="text/css">' + xhr.responseText +
'</style>';
}
}
} catch(e){}
};
xhr.open("GET", 'styles.css?'+Math.random(), true);
xhr.send(null);
}
Тут сразу возникает вопрос: как считать применение стилей? Ведь у браузера нет такого события. Немного подумаем и вспомним всем известный и раскритикованный подход с использованием offsetHeight. Он, конечно, будет не самым лучшим выбором, так как добавляет небольшую задержку (пропорциональную числу узлов), но это будет допустимо в рамках выбранного метода (число узлов постоянно для всех измерений).
Итак, для снятия времени применения CSS-правил использовалась следующая конструкция (которая запускалась сразу после обращения к внешнему файлу):
var start = new Date();
var _timer = setInterval(function(){
// находим известный элемент документа и проверяем,
// применились ли к нему стили
if (document.getElementById('neck').offsetHeight < 300) {
// сообщаем о применении
alert('CSS files loaded in '+(new Date() - start));
// убиваем таймер
clearInterval(_timer);
}
}, 10);
Конечно, на время загрузки влияют сетевые задержки. Для борьбы с ними (и не только) бралась серия из 15 замеров, и значения, превосходящие текущее среднее более чем в 2 раза, просто отбрасывались (для контроля 3-дельта-выбросов).
Все результаты приведены в конце раздела.
В этом случае код вставлялся уже в <head> и применялся ряд методов для разных браузеров (ибо не все хотели через innerHTML или innerText вставлять полученные данные).
var text = xhr.responseText;
var head = document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
// для IE
if (style.styleSheet) {
style.styleSheet.cssText = text;
} else {
// для Safari/Chrome
if (style.innerText == '') {
style.innerText = text;
// для остальных
else {
style.innerHTML = text;
}
}
head.appendChild(style);
В следующем варианте проверялось прямое добавление стилевых правил к innerHTML в <head> (для тех браузеров, которые это поддерживают). Оказалось, что это вариант даже медленнее, чем предыдущий.
Если осуществлять это относительно обычного HTML-документа, то DOM-дерево изменяется быстрее (в IE6/7), поэтому на данный момент в общем случае практикуется именно такой подход.
var text = xhr.responseText;
var head = document.getElementsByTagName('head')[0];
if (/WebKit|MSIE/i.test(navigator.userAgent)) {
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = text;
} else {
style.innerText = text;
}
head.appendChild(style);
} else {
head.innerHTML += '';
}
И, наконец, хит сезона. Добавляем новый файл стилей прямо в <head> при помощи DOM-методов.
var link = document.createElement('link');
document.getElementsByTagName('head')[0].appendChild(link);
link.setAttribute('type','text/css');
link.setAttribute('rel','stylesheet');
link.setAttribute('href','style.css');
Ниже приведена таблица по исследованным браузерам для всех вариантов. В ней указано время в миллисекундах, прошедшее от начала вызова внешнего файла до окончания применения всех стилей.
| Браузер | XHR в <body> | XHR в <head> | Быстрый XHR в <head> | DOM |
|---|---|---|---|---|
| IE 6 | 482 | 379 | 342 | 335 |
| IE 7 | 532 | 364 | 391 | 353 |
| IE 8b2 | 370 | 326 | 301 | 284 |
| FX 3 | 420 | 294 | 300 | 282 |
| Opera 9 | 892 | 894 | 1287 | 764 |
| Safari 3 | - | 308 | 286 | 296 |
| Chrome | - | 349 | 335 | 367 |
Как хорошо видно из таблицы, наиболее быстрым способом для динамического добавления стилей в документ являются DOM-методы почти во всех случаях. Для Safari/Chrome вставка через XHR и специальные методы оказываются быстрее (но не намного). Отдельно хочется отметить довольно медленную работу Opera в таких задачах: по возможности стоит избегать динамических стилей для этого браузера.
Естественно, тут речь идет о выигрышах лишь в десятки и сотни миллисекунд. Но если с самого начала применять самые оптимальные методы при разработке, то ситуаций, когда веб-приложение уже тормозит на несколько секунд (просто загружая процессор на пустом месте), можно будет с легкостью избежать. Ведь на том этапе, когда задержки станут явными, находить и устранять их намного сложнее, чем при изначальном проектировании.
Этот раздел написан после прочтения статей Steve Souders — don't use @import (http:/www.stevesouders.com/bLog/2009/04/09/dont-use-import/) и
Существует два способа загрузки файлов стилей: использовать тег <link>:
<link rel='stylesheet' href='a.css'/>
или импортировать файлы с помощью @import:
<style type='text/css'>
@import url('a.css');
</style>
Стоит использовать <link> для удобства, но вы должны помнить, что @import нужно размещать всегда в самом верху блока стилей, в противном случае они не импортируются.
В приведенном ниже примере подключаются два файла стилей: a.css и b.css. Каждый файл по загрузке занимает ровно 2 секунды, чтобы было удобно отследить влияние на скорость загрузки в дальнейшем. В первом примере применяется @import для загрузки обоих файлов стилей. Здесь HTML-документ содержит следующий блок стилей:
<style type='text/css'> @import url('a.css'); @import url('b.css'); </style>
Если вы всегда будете использовать только @import для загрузки стилей, то проблем с производительностью не будет, хотя, как мы увидим ниже, это может привести к ошибке с JavaScript. Оба файла загружаются параллельно (рис. 5.1) Но проблемы начинают появляться, если применять @import внутри файла стилей либо вместе с <link>.
(рис 5.1) Параллельная загрузка стилей. Источник: getincss.ru
В следующем примере используется тег <link> для загрузки a.css и @import для b.css:
<link rel='stylesheet' type='text/css' href='a.css'/> <style type='text/css'> @import url('b.css'); </style>
В IE (тестировалось в 6, 7 и 8) это привело к тому, что файлы загружаются последовательно друг за другом, как показано на рис. 5.2. Соответственно, время загрузки страницы в IE увеличится.
(рис 5.2) @import блокирует <link> в IE. Источник: getincss.ru
Тут файл a.css загружается через <link> и содержит внутри правило @import для b.css: В документе:
<link rel='stylesheet' type='text/css' href='a.css'/>
в a.css:
@import url('b.css');
Этот способ также приводит к тому, что файлы загружаются последовательно (рис. 5.3), а не параллельно, и теперь это происходит не только в IE, но и в остальных браузерах. Если подумать — все логично: браузер загружает a.css и начинает анализировать его. Как только внутри обнаружено правило @import, начинается загрузка файла b.css.
(рис 5.3) @import блокирует <link> не только в IE. Источник: getincss.ru
Незначительное отличие от предыдущего примера привело к удивительным результатам: в IE. <link> используется для вызова a.css и для нового файла proxy.css, который содержит только @import для b.css.
В HTML-коде:
<link rel='stylesheet' type='text/css' href='a.css'> <link rel='stylesheet' type='text/css' href='proxy.css'>
В proxy.css:
@import url('b.css');
Результаты эксперимента в IE показаны на рис. 5.4. Первый запрос — HTML-документ. Второй запрос — a.css (2 секунды). Третий — proxy.css. Четвертый — b.css (2 секунды). И вот что удивительно, IE не хочет начинать загрузку b.css, пока файл a.css не будет загружен полностью. Во всех других браузерах такого сюрприза не происходит, что приводит к более быстрой загрузке страницы (см. рис. 5.5).
(рис 5.4) Результаты в IE. Источник: getincss.ru
(рис 5.5) Результаты в других браузерах. Источник: getincss.ru
Применение сразу нескольких правил @import в IE приводит к тому, что файлы загружаются не в том порядке, в котором они указаны в коде. В этом примере используется 6 файлов стилей (каждый из которых загружается по 2 секунды), за которыми следует JS-скрипт (4 секунды для загрузки).
<style type='text/css'>
@import url('a.css');
@import url('b.css');
@import url('c.css');
@import url('d.css');
@import url('e.css');
@import url('f.css');
</style>
(рис 5.6) Много @import. Источник: getincss.ruНа рис. 5.6 мы увидим, что самый долгий по загрузке — это скрипт. Несмотря на то, что он указан после стилей, в IE он загружается первым. Если в скрипте содержится код, который зависит от применяемых стилей ( getElementsByClassName, и т. п.), это может привести к ошибкам работы скрипта, так как он загружается раньше, чем стили.
Проще и безопасней задействовать <link> для загрузки стилей:
<link rel='stylesheet' type='text/css' href='a.css'> <link rel='stylesheet' type='text/css' href='b.css'>
Использование <link> обеспечивает параллельную загрузку файлов во всех браузерах (см. рис. 5.7). Применение <link> также гарантирует, что файлы будут загружены именно в том порядке, который указан в коде документа.
(рис 5.7) Использование <link>. Источник: getincss.ruДля нас особенно плохо то, что ресурсы могут быть загружены в другом порядке, нежели указано в документе. Все браузеры должны заглядывать вперед перед загрузкой стилей для извлечения правил @import и начинать их загрузку немедленно. До тех пор, пока браузеры не реализуют это, стоит избегать использования @import и загружать стили только с помощью <link>.
Для большинства сайтов возможный выигрыш в производительности после оптимизации CSS-селекторов будет крайне незначительным и не будет стоить потраченного времени. Есть несколько типов CSS-правил (например, expression для IE) и взаимодействий с JavaScript, которые могут существенно замедлить страницу. Именно на них и нужно концен-тировать усилия.
Большая часть информации о быстродействии CSS-селекторов может быть получена из статьи David Hyatt Пишем эффективный CSS для интерфейсов в MoziLLa (https://deveLoper.moziLLa.org/en/Writing Efficient CSS). Стоит привести оттуда одну цитату:
"Система стилей находит элемент, соответствующий CSS-правилу, начиная с правого конца селектора и двигаясь налево. Пока мы еще находим элементы, соответствующие селектору, мы продолжаем двигаться. Остановка возможна только в случае нахождения элемента, соответствующего всему CSS-селектору, или невозможности такой элемент найти для какой-то части селектора".
Не очень понятно, как устроен движок CSS-селекторов в других браузерах, например, в IE (а если принять во внимание тесты CSS-производи-тельности из книги "Разгони свой сайт", то возникает предположение, что в Opera движок разбора CSS-селекторов работает как раз слева направо). Может быть, он использует комбинированный подход: как справа налево, так и слева направо. Переключение может происходить по какому-то признаку (например, по наличию #id).
Однако по заявлению Виталия Харисова, руководителя группы HTML-верстки в Яндексе, тесты показывают (http://clubs.ya.ru/yacf/ replies.xml?item no=338ncrnd=3604), что все браузеры применяют селекторы одинаково — справа налево.
Благодаря вышесказанному мы можем теперь сфокусировать оптимизационные усилия на тех CSS-селекторах, правая часть которых будет соответствовать большому числу элементов на странице. Давайте рассмотрим для примера DIV DIV DIV P A.class0007 {}. У этого селектора присутствует 5 уровней вложенности потомков, для которых необходимо найти соответствие в DOM-дереве. Это выглядит очень ресурсоемко, однако при взгляде на самую правую часть этого селектора, A.class0007, мы прекрасно понимаем, что ей соответствует только 1 элемент на странице, поэтому браузеру очень легко установить точное соответствие.
Ключевым моментом в оптимизации CSS-селекторов является самая правая часть, часто также называемая "ключевым селектором". Вот пример гораздо более ресурсоемкого селектора: A.class0007 * {}. Хотя он может и выглядеть просто, но для браузера очень тяжело его вычислить. Поскольку браузер будет двигаться справа налево, он начнет со всех элементов, которые подходят под ключевой селектор (*). Это означает, что браузер попытается проверить все элементы на странице и установить, нужно ли к ним применить этот стиль. На следующей диаграмме показана разница во времени между тестами для универсального селектора и прошлыми тестами для наследственных селекторов.
(рис 5.8) Разница во времени загрузки (в мс) для универсального селектора. Источник: stevesouders.comОчевидно, что CSS-селекторы с ключевым элементом, который соответствует большому числу узлов, будут вычисляться существенно дольше и заметно тормозить загрузку страницы. В качестве других примеров селекторов, которые могут привести к большим вычислениям со стороны браузера, можно отнести:
A.class0007 DIV {} #id0007 > A {} .class0007 [href] {} DIV:first-child {}
Не все CSS-селекторы существенно влияют на производительность, даже те, которые могут выглядеть сложно. При оптимизации стоит фокусироваться на устранении CSS-правил с ключевым селектором, соответствующим большому числу элементов. Это становится особенно важно для Веб2.0-приложений с большим числом узлов DOM-дерева, огромным количеством CSS-правил и отрисовок страниц.
Некоторой проблемой является то, что почти все известные
В книге "Разгони свой сайт" тема производительности CSS-селек-торов уже поднималась. Несмотря на то, что выводы были подкреплены значительным объемом исследований, основной вопрос — как же должна выглядеть эффективная таблица стилей, которая обеспечивает наискорейшее отображение документа на экране, — так и остался без ответа.
Для прояснения этой ситуации были проведены дополнительные исследования, базирующиеся на уже известных фактах: "наиболее эффективны селекторы, не использующие тегов", и "классы работают быстрее, чем идентификаторы".
Также известно, что селекторы обладают различной сложностью (например, селектор . class1 .class2, очевидно, должен отрабатывать медленнее, чем просто . class2).
Для уточнения существенных факторов и относительного ранжирования известных правил по написанию эффективного CSS-кода была взята за основу следующая формула:
Время отрисовки = Размер DOM-дерева * Число CSS-селекторов * Сложность стилевых правил * *Время отрисовки одного правила + Время создания документа
При взгляде на эту формулу становится очевидным, что нам нужно брать усредненную сложность правил по всей таблице стилей, т. е. подставлять в формулу сумму сложностей всех CSS-селекторов, разделенную на их число.
Также практически сразу становится ясно, что в формуле фигурирует не размер всего DOM-дерева, а число элементов, на которые влияет данный селектор (это, в частности, объясняет, почему
Еще не стоит забывать о наличии у браузеров собственной таблицы стилей, которая применяется к каждой странице, выводящейся на экран. Размер этой таблицы можно выяснить достаточно просто: нужно всего лишь открыть две страницы с разным (и достаточно большим) числом CSS-правил, но одинаковым DOM-деревом и проверить, насколько замедлилась загрузка. Зная отношение размера двух таблиц стилей, можно вычислить неизвестный размер таблицы стилей самого браузера (для основных браузеров он составил в районе 30-50 правил, для IE — порядка 200).
И еще один момент, который всплыл по ходу расследования: имеет значение размер полного DOM-дерева, не только число тегов, но и число текстовых узлов, хотя это никак и не влияет на основные выводы.
В ходе проведения различных тестов модель удалось уточнить и показать, что время создания документа зависит от числа узлов в дереве (что вполне очевидно). Это можно проверить двумя наборами тестов: на увеличении числа CSS-селекторов без увеличения DOM-дерева и на увеличении DOM-дерева без увеличения числа CSS-селекторов. При этом хорошо видно, что время создания документа не является постоянным и несколько увеличивается при увеличении размера документа.
Также было проверено, насколько составные селекторы отрабатывают медленнее своих элементарных собратьев (имеется в виду разница между . classl, . classl .class2 и . classl .class2 . class3). Разница была зафиксирована, но оказалась несущественной (каждое звено прибавляет примерно 10-20% к общей сложности селектора).
Итак, после всех уточнений, формула приобрела следующий вид:
T = (сумма(DOM1 * K) + DOM2 * In) * t + DOM2 * L
Здесь:
Данная модель позволила аппроксимировать время отображения страницы с точностью 10% (в редких случаях 20%, — видимо, есть еще много неучтенных факторов, например, особенности выделения памяти различными браузерами). Тестирование проводилось на документах от 5000 DOM-узлов и от 0 CSS-правил.
Анализ предложенной и проверенной модели позволяет сделать огромное количество весьма интересных выводов. Давайте остановимся на некоторых из них.
Также стоит отметить, что по результатам тестирования Виталия Харисова (http://cLubs.ya.ru/yacf/repLies.xmL7item no=338) неиспользуемые CSS-правила добавляют некоторую задержку в отображении страницы (до 10% от времени отрисовки), поэтому их тоже стоит избегать.
Для средней HTML-страницы время ее отображения (размер DOM-дерева — 1000 элементов, CSS-правил — порядка 500, каждое из них в среднем применяется к 40% элементов) составит порядка 100 мс. Простой оптимизацией можно уменьшить этот показатель вдвое (например, сузив область воздействия самих селекторов, если DOM-дерево уменьшить не получается).
Этот раздел написан под впечатлением от статьи Steve Souders (автора знаменитых советов Yahoo! касательно клиентской производительности) "
Если скрипты загружаются в обычном порядке ( <script src="..."> ), то они блокируют загрузку всех остальных компонентов страницы (в последних версиях Firefox и в Safari это не так, но речь идет в основном про 70% пользователей с IE) и блокируют отрисовку всей той части страницы, которая расположена ниже вызова скриптов по HTML-коду. Асинхронная загрузка скриптов (например, при помощи динамического создания объектов после срабатывания комбинированного события window.onload) предотвращает такое поведение браузера, что ускоряет загрузку страницы.
Единственная проблема с асинхронной загрузкой скриптов заключается в их взаимодействии с внутренними скриптами страницы (а также с другими внешними скриптами), которые используют переменные, определенные во внешнем скрипте. Если внешний скрипт загружается асинхронно безо всякого представления о внутреннем коде HTML-страницы, то вполне возможна ситуация (и она будет возникать в большинстве случаев), когда некоторые переменные будут не определены на момент их использования. Поэтому необходимо убедиться, что внешние скрипты, загруженные асинхронным образом, и внутренние скрипты страницы состыкованы: внутренние скрипты не выполняются до тех пор, пока асинхронные скрипты полностью не загрузятся.
Существует несколько стандартных путей для стыковки асинхронно загружаемых скриптов с другим JavaScript-кодом.
В этом разделе параллельно освещаются два вопроса: как асинхронные скрипты ускоряют загрузку страницы и как можно состыковать асинхронные скрипты и внутренние, используя модифицированный вариант загрузчика от Джона Ресига (автора <script>.
Если добавить скрипт на страницу обычным способом (через <script src="..."> ), диаграмма загрузки будет примерно следующей.
Хотя скрипт и функционирует, но это не сделает нас намного счастливее, ибо загрузка страницы замедлится. На рис. 5.9 хорошо видно, как скрипт (по имени sorttable-async.js ) блокирует все остальные HTTP-запросы на странице (в частности, arrow-right-20x9.gif ), что замедляет загрузку страницы. Все диаграммы загрузки сняты при помощи onload (а синяя линия соответствует событию domcontentloaded ). Для версии с обычным вызовом скрипта событие onload срабатывает на 487-й миллисекунде.
(рис 5.9) Диаграмма загрузки скриптов в обычном случае.Источник: stevesouders.com
Скрипт sorttable-async.js в данном случае не является необходимым для первоначального отображения страницы. Такая ситуация (внешние скрипты, которые не используются для первоначального отображения страницы) является кандидатом номер 1 для внедрения асинхронной загрузки. Вариант с асинхронной загрузкой скриптов подключает этот скрипт, используя DOM-методы для создания нового тега <script>:
var script = document.createElement("script"); script.src = "sorttable-async.js";
script.text = "sorttable.init()"; // это объясняется чуть ниже document.getElementsByTagName("head")[0].appendChild(script);
Диаграмма HTTP-загрузки для асинхронной загрузки скриптов изображена на рис. 5.10. Стоит обратить внимание, как асинхронный подход предотвращает блокирующее поведение: sorttable-async.js и arrow-right-20x9.gif загружаются параллельно. Это снижает общее время загрузки дл 429 мс.
(рис 5.10) Диаграмма загрузки скриптов в асинхронном случае, источник:stevesouders.com
Асинхронная загрузка скриптов позволяет ускорить загрузку страницы, но в этом случае остается, что улучшить. По умолчанию скрипт вызывает "сам себя" при помощи прикрепления sorttable.init() к обработчику события onload для этого скрипта. Некоторое улучшение производительности (и уменьшение кода) может быть достигнуто, если вызвать sorttable.init() внутри тега <script>, чтобы вызвать его сразу же после загрузки внешнего скрипта (подключенного через src).
Выше описано три способа по стыковке внутреннего кода с асинхронной загрузкой внешних скриптов: window onload, onreadystatechange у скрипта и встроенный в скрипт обработчик. Вместо всего этого можно использовать технику от Джона Ресига — шаблон двойного тега <script>. John описывает, как связать внутренние скрипты с загрузкой внешнего файла, следующим образом:
<script src="jquery.js" type="text/javascript">
jQuery("p").addClass("pretty"); </script>
В этом случае код внутри <script> срабатывает только после того, как загрузка и инициализация внешнего скрипта завершилась. У этого подхода по стыковке скриптов есть несколько очевидных преимуществ:
<script> вместо двух;Это замечательный шаблон для асинхронной загрузки внешних скриптов. Однако чтобы его использовать, нам придется внести изменения как во внутренний код, так и во внешний файл. Для внутреннего кода придется добавить уже упомянутую выше третью строку, которая выставляет свойство script.text. Для завершения процесса стыковки нужно добавить в конец sorttable-async.js:
var scripts = document.getElementsByTagName("script"); var cntr = scripts.length; while ( cntr ) {
var curScript = scripts[cntr-1];
if ( -1 != curScript.src.indexOf("sorttable-async.js") ) { eval( curScript.innerHTML ); break;
}
cntr—;
}
Этот код проходится по всем скриптам на странице, находит необходимый блок, который должен загрузить сам себя (в этом случае это скрипт с src, содержащим sorttable-async.js ). Затем он выполняет код, который добавлен к скрипту (в этом случае —sorttable.init()), и таким образом вызывает сам себя. (Небольшое замечание: хотя призагрузке скрипта текст в нем был добавлен при помощи свойства text, обращение к нему происходит при помощи свойства innerHTML. Это необходимо для обеспечения кроссбраузерности.) При помощи такой оптимизации мы можем загрузить внешний файл скрипта, не блокируя загрузку других ресурсов, и максимально быстро выполнить привязанный к данному скрипту внутренний код.
Стоит также заметить, что описываемая техника может быть заменой только прямой модификации внешнего скрипта. В случае невозможности такой модификации мы можем использовать только установление проверки загрузки по интервалу:
var _on_ready_execution = setInterval(function() { if (typeof urchinTracker === 'function') { urchinTracker();
clearInterval(_on_ready_execution);
}
}, 10);
Этот подход был уже описан в книге "Разгони свой сайт" (http://speedupyourwebsite.ru/books/speed-up-your-website/), однако он предполагает дополнительную нагрузку на процессор для постоянной проверки готовности искомого скрипта и не срабатывает в случае недоступности внешнего файла: проверка продолжает выполняться.
Однако в случае проверки по интервалу нам совсем не нужно модифицировать внешний файл, в случае же двойного использования тега <script> это просто необходимо. Проверку по интервалу можно улучшить, если по истечении некоторого времени (5—10 секунд, например) перезапускать загрузку внешнего файла (меняя исходный тег <script> при помощи уникального GET-параметра), а после нескольких неудачных перезапусков вообще прекращать загрузку (возможно, с каким-то сообщением об ошибке).
Общее время загрузки может быть уменьшено еще больше, если использовать "ленивую загрузку" скрипта (загружать его динамически как часть обработчика события onload ). Мы можем просто оборачивать заявленный выше код в обработчик события onload:
window.onload = function() {
var script = document.createElement("script"); script.src = "sorttable-async.js"; script.text = "sorttable.init()";
document.getElementsByTagName("head")[0].appendChild(script);
}
В этом случае нам жизненно необходима техника стыковки скриптов. Преимущество данного подхода заключается в том, что событие onLoad срабатывает раньше — это подтверждается на рис. 5.11. Событие onLoad, отмеченное красной линией, срабатывает примерно на 320-й миллисекунде. Стоит отметить, что в таком случае мы увеличиваем общее время загрузки страницы (за счет некоторой синхронизации загрузки скриптов с общим процессом загрузки страницы), но ускоряем появление первоначальной картинки в браузере пользователя.
(рис 5.11) Диаграмма загрузки в случае "ленивой" загрузки скриптов, источник stevesouders.com
Асинхронная и "ленивая" загрузка скриптов уменьшает общее время загрузки страницы тем, что предотвращает обычное блокирующее поведение скриптов (буквально "клин клином вышибает"). В качестве демонстрации этого можно привести различные варианты добавления скрипта на тестовую страницу:
Выше показано время, после которого срабатывает событие onload. Для других веб-приложений применение асинхронной загрузки для улучше ния производительности может привести к гораздо более впечатляющим результатам и быть намного более приоритетным. В нашем случае асинхронная загрузка скриптов немного лучше (~400 мс против 417 мс). В обоих случаях нам нужно каким-то образом связывать внутренние скрипты с внешними.
После вышеописанного материала можно задуматься и о модульной загрузке какого-либо сложного JavaScript-приложения. Предложенный подход в таком случае будет довольно громоздким: нам нужно будет в конец каждого модуля вставлять загрузчик следующих модулей. А если нам на разных страницах требуются различные наборы модулей и разная логика их загрузки? Тупик?
Ан нет. Не зря упоминается в самом начале прошлого раздела о событии onload / onreadystatechange для скриптов. Используя их, мы можем однозначно привязать некоторый код к окончанию загрузки конкретного модуля. Дело за малым: нам нужно определить этот самый код каким-либо образом.
В качестве наиболее простого способа определить порядок загрузки модулей на конкретной странице можно предложить глобальный массив, содержащий в себе дерево зависимостей. Например, такое:
var modules = [
[0, 'iteml', function(){
alert('item1 is loaded'); }],
[1, 'item2', function(){
alert('item2 is loaded'); }],
[1, 'item3', function(){ alert('item3 is loaded');
];
В качестве элемента этого массива у нас выступает еще один массив. Первым элементом идет указание родителя (0 в том случае, если элемент является корнем и должен быть загружен сразу же), далее имя файла или его алиас. Последней идет произвольная функция, которую можно выполнить по загрузке.
Давайте рассмотрим, каким образом можно использовать данную структуру.
/* перебор и загрузка модулей */ function load_by_parent (i) { i = i || 0;
var len = modules.length, module;
/* перебираем дерево модулей */ while (len—) {
module = modules[len]; /* и загружаем требуемые элементы */ if (!module[0]) { loader(len);
}
}
}
/* объявляем функцию-загрузчик */ function loader (i) {
var module = modules[i]; /* создаем новый элемент script */
var script = document.createElement('script'); script.type = 'text/javascript'; /* задаем имя файла */
script.src = module[1] + '.js'; /* задаем текст внутри тега для запуска по загрузке */
script.text = module[2]; /* запоминаем текущий индекс модуля */
script.title = i + 1; /* выставляем обработчик загрузки для IE *
/ script.onreadystatechange = function() { if (this.readyState === 'loaded')
{ /* перебираем модули и ищем те, которые нужно загрузить */ load_by_parent(this.title);
}
};
/* выставляем обработчик загрузки для остальных */
script.onload = function (e) { /* исполняем текст внутри тега (нужно только для Opera)
*/ if (/opera/i.test(navigator.userAgent)) { eval(e.target.innerHTML);
}
/* перебираем модули и ищем те, которые нужно загрузить */ load_by_parent(this.title);
};
/* прикрепляем тег к документу */
document.getElementsByTagName('head')[0].appendChild(script);
}
/* загружаем корневые элементы */ load_by_parent();
Мы можем вынести загрузку корневых элементов в событие загрузки страницы, а сами функции — в какую-либо библиотеку, либо объявить прямо на странице. Задавая на каждой странице свое дерево, мы получаем полную гибкость в асинхронной загрузке любого количества JavaScript-модулей. Стоит отметить, что зависимости в таком случае разрешаются "от корня — к вершинам": мы сами должны знать, какие базовые компоненты загрузить сначала, а потом загрузить более продвинутые.
Кроме того, подобной проблемой уже занимался Андрей Сумин и даже предложил свое решение в виде библиотеки JSX (http://jsx.ru/), которая позволяет назначать список зависимостей через DOM-дерево. Для этого у элементов, которые требуют загрузки каких-либо модулей для взаимодействия с пользователем, назначается класс с префиксом jsx-compo-nent, а далее идет уже список компонентов. Сама библиотека обходит DOM-дерево, находит все модули, которые нужно загрузить, и последовательно их загружает. Просто замечательно.
Но что, если нам требуется поменять обработчик по загрузке этого модуля? Как его задавать? Сама JSX использует атрибуты искомых узлов DOM-дерева сугубо для определения параметров этих модулей. Это достаточно удобно: ведь таким образом можно назначить и
Также библиотека позволяет отслеживать повторную загрузку модулей, осуществлять догрузку модулей в случае плохого соединения и даже объединять разные модули в один исходный файл через систему алиасов. Таким образом, проблема асинхронной загрузки произвольного дерева модулей оказывается решенной. В случае JSX задача разрешается в обратном порядке: мы указываем основной файл (вершину дерева зависимостей), а он уже загружает все необходимые ему модули либо проверяет, что модули загружены. Это все?
Почти. После недолгих раздумий JSX была взята за основу для построения модульной системы, которая могла бы стать основой для гибких и динамических клиентских приложений. Удалось совместить оба описанных выше подхода, что обеспечило все видимые функциональные требования к такого рода системе.
Для примера можно рассмотреть следующий участок HTML-кода:
<div id="item1" class="yass-module-utils-base-dom">
<span id="item2" class="yass-module-dom"
title="_('#item2')[0].innerHTML = 'component is loading...';"> </span> </div>
Давайте разберемся, какую логику загрузки он обеспечивает.
yass-module-*.dom-base-utils и для dom.Причем в последнем случае загрузки фактически не будет: загрузчик дождется, пока состояние компонента dom будет выставлено в loaded, и только потом запустит (черeз eval ) код, записанный в title этого элемента (в данном случае это span ).yass.dom.js, yass.base.js и yass.utils.js. По загрузке всех этих модулей (ибо они вызваны в цепочке зависимостей, и в данном случае dom зависит от base, который зависит от utils ) будут вызваны соответствующие инициализационные функции (если они определены). Таким образом, возможны два типа обработчиков: непосредственно по загрузке компонента (будет вызвано для всех компонентов в цепочке) и после загрузки всей заданной цепочки компонентов (в нашем случае это dom-base-utils ).base-callbacks ), которая "заморозит" загрузку модуля base до получения _.load('callbacks-base')
;yass-module-callbacks -base. Это добавит в дерево зависимостей искомую цепочку.Для большей ясности описанное выше конечное дерево загружаемых модулей можно представить так:
dom-> base -> utils -> callbacks
Очевидно, что возможно и гораздо более глубокое дерево зависимостей, загружаемых асинхронно. Но при его реализации не стоит забывать о накладных издержках на передачу и инициализацию каждого файла: может оказаться так, что выгоднее объединить часть модулей в один большой файл.
Естественно, весь указанный функционал уже добавлен в последнюю версию YASS (http://yass.webo.in/).
В заключение лекции давайте рассмотрим применение аппаратных решений для оптимизации структуры веб-страниц вашего сайта.
После общения с множеством специалистов возникла следующая проблема: все знают, что
(рис 5.12) Схема CDN, источник: ngenix.netНередко бывает, что каждый час простоя сайта интернет-магазина измеряется в весьма значительных суммах. Если отказ сервера, на котором расположен сайт, произошел ночью (по времени технической поддержки), а магазин должен быть доступен 24 часа в сутки (например, в связи с широким географическим покрытием пользователей), то это может вылиться в значительные финансовые потери.
Распределенная сеть серверов позволяет легко решить эту проблему: если отказывает один из имеющихся серверов, то его место тут же занимает самый близкий из сетевых соседей. Таким образом, конечный пользователь не замечает каких-либо перебоев в работе сайта.
Дополнительно данные в распределенной сети еще и резервируются на большом количестве серверов, что сводит вероятную возможность их потери практически к нулю: для этого необходимо, чтобы разом отказала вся сеть, а такое статически невозможно, если только не будет заранее тщательно спланировано злоумышленниками с привлечением весьма дорогих технологических средств.
Благодаря тому, что сетевой маршрут между конечными пользователями и серверами с информацией снижен до минимума (а сами серверы отвечают крайне быстро), скорость прохождения запросов будет весьма значительной.
В качестве примера стоит привести следующие цифры: время ответа обычного сервера, расположенного на VPS (даже не на shared-хостинге), составляет 50-200 мс (в зависимости от различных условий, в том числе от загруженности канала). Для
Пользовательская нагрузка при использовании
Таким образом, вся серверная нагрузка может быть возложена на
В некоторых случаях (хостинг программного обеспечения или медиа-материалов, создание интерактивных промо-сайтов) исходный сервер с информацией может быть загружен "медленными" запросами (которые могут длиться минутами и часами, расходуя серверные ресурсы и не давая осуществить более быстрые запросы для получения остальной информации на сайте). В этом случае также выход один — использовать
Сеть
Сеть распределенных серверов обладает еще одним преимуществом: она позволяет легко наращивать мощности, от обслуживания 1000 человек день до нескольких миллионов и более. Таким образом, резкое увеличение нагрузки (выход нового продукта, статья в известном издании, промо-кампания) никак не скажется на доступности вашего сайта, его содержимое будет отдаваться по-прежнему быстро и без каких-либо перебоев.
Компания NGENIX первой в России начала предоставлять услуги распределенной доставки и дистрибуции цифрового контента с применением технологии
Сегодня решения и сервисы NGENIX помогают медиа-компаниям и контент-провайдерам распространять в Интернете тяжелый мультимедийный контент на высокой скорости с широким охватом российской Интернет-аудитории. Сеть NGENIX
Уникальная распределенная инфраструктура NGENIX
Итак, вы готовы использовать Google для хостинга своих файлов? Тогда вперед!
(рис 5.13) Загружаем Google App Engine SDK
(рис 5.14) Создание нового приложения в Google App Engineapplication: ваш_идентификатор_приложения version: 1 runtimee: python api_version: 1 handlers: - url: /favicon.ico static_files: favicon.ico upload: favicon.ico - url: /.* script: cacheheaders.pyВ случае нашего примера идентификатор был просто webo, он соответствует адресу webo.appspot.com, а version соответствует версии приложения. Очень удобно отлаживать новую версию, в то время как более старая замечательно работает. Переключение между версиями происходит из панели управления GoogLe Apps (http://appengine.googLe.com/ depLoyment).
favicon .ic o от рабочего сайта и создаем еще один файл, cacheheaders.py: (сразу стоит отметить, что в качестве отбивки во всех Python-скриптах используется не табуляция, а двойной пробел):import wsgiref.handlers from google.appengine.ext import webapp
class MainPage(webapp.RequestHandler):
def output_file(self, path, lastmod): import datetime try:
self.response.headers['Last-Modified'] = lastmod.strftime("%a, %d %b %Y %H:%M:%S GMT")
expires=lastmod+datetime.timedelta(days=365)
self.response.headers['Expires'] = expires.strftime("%
a, %d %b %Y %H:%M:%S GMT")
self.response.headers['Cache-Control'] = 'public,
max-age=31536000'
fh = open(path, 'r')
self.response.out.write(fh.read())
fh.close
return
except IOError:
self.error(404)
return
def get(self, dir, file, extension):
if (dir != 'i' and extension != 'jpg' and extension !=
'png' and extension != 'gif'):
self.error(404)
return
if extension == "jpg":
self.response.headers['Content-Type'] =
"image/jpeg"
elif extension == "gif":
self.response.headers['Content-Type'] =
"image/gif"
elif extension == "png":
self.response.headers['Content-Type'] =
"image/png"
try:
import os
import datetime
path = dir+"/"+file+"."+extension
info = os.stat(path)
lastmod = datetime.datetime.fromtimestamp(info[8])
if self.request.headers.has_key('If-Modified-Since'):
dt = self.request.headers.get('If-Modified-
Since').split(';')[0]
modsince = datetime.datetime.strptime(dt,
"%a, %d %b %Y %H:%M:%S %Z")
if modsince >= lastmod:
# Файл более старый, чем закэшированная копия
(или такой же)
self.error(304)
return
else:
# Файл более новый
self.output_file(path, lastmod)
else:
self.output_file(path, lastmod)
except:
self.error(404)
return
def main():
application =
webapp.WSGIApplication([(r'/(.*)/([^.]*).(.*)',
MainPage)], debug=False)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == "__main__":
main()
По поводу этого файла — небольшое лирическое отступление. Как выяснилось в ходе исследования, Google Apps по умолчанию не поддерживает Last-Modified / ETag (только Expires, который настраивается простой строкой в app.yaml — defaultexpiration: "365d" ). Чтобы обеспечить поддержку этого необходимого для cacheheaders.py.Конечно, можно обойтись простым кэшированием, но мы же хотим максимально правильный cacheheaders.py просто проверяет, что запрос пришел к папке i для нашего приложения и расширение у файла . png, . gif или .jpg, после этого он отдает либо сам файл, либо 304-ответ (сравнивая заголовок браузера If-Modified-Since с меткой времени файла).
upload.bat. (если вы собираетесь загружать файлы из-под другой операционной системы, нежели Windows, то логику файла придется переписать на соответствующем скриптовом языке). В файле записываем:"путь_к_установленному_Python_из_пункта_1" "C:\ProgramFiles\Google\google_appengine\appcfg.py" update "путь_к_рабочей_папочке_с_файлами"
Если в пункте 2 вы выбрали нестандартную директорию для Google App Engine SDK, то ее придется подставить вместо C:\Program Files\Google\google_appengine.
http://webo.appspot.com/i/b.png ). Радуемся!Если ваш проект не создает большой статической нагрузки (оценочно не более 250—500 Кб/с), то вы с легкостью можете воспользоваться серверами Google для выдачи своих файлов.
Отмеченные минусы:
Last-Modified требует дополнительной логики и нагрузки на процессор (может стать критичной при большом количестве мелких файлов);Во всем остальном — это идеальный выбор. Например, webo.in уже использует эту
Также стоит отметить, что существует возможность полностью прикрепить домен к Google App Engine и применять для обслуживания его содержания какое-либо приложение App Engine. Это позволит (в случае полностью статического сайта) загружать его максимально быстро, совершенно бесплатно используя мощности Google (в разумных пределах для среднего сайта это порядка 20 тысяч посетителей в день).
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.