Сценарии JavaScript блокируют загрузку файлов, следующих за ними. Но имеется несколько приемов, предотвращающих это:
- • Загрузка сценариев в память с помощью запросов XHR и следующим их выполнением с помощью evalQ. Этот прием подвержен ограничениям политики единого домена-источника, и кроме того, вовлекает в работу функцию evаl (), применение которой считается антишаблоном.
- • Использование атрибутов dеfer и async, правда они действуют не во всех броузерах.
- • Использование динамических элементов <script>. Последний прием является наиболее привлекательным. Он напоминает описанный выше шаблон использования JSONP — вы создаете новый элемент <script>, определяете значение его атрибута src и добавляете этот элемент в страницу.
Следующий пример загрузит файл JavaScript асинхронно, не блокируя загрузку остальной части страницы:
vаr script = document.crеateElement ( «sсript»);
script.src = «all_20100426.js»;
document.documеntElement.firstChiId.appendChiId (script);
Недостаток такого шаблона заключается в том, что его нельзя использовать для загрузки других сценариев, зависящих от main.js. Файл main.js в этом примере загружается асинхронно, поэтому нет никаких гарантий, что он будет загружен к тому моменту, когда дело дойдет до выполнения других сценариев, следующих за ним и использующих объекты, определяемые в нем.
Чтобы преодолеть этот недостаток, можно все встроенные сценарии не выполнять немедленно, сразу после загрузки, а собирать в массив в виде функций. А после загрузки основного сценария можно будет выполнить все функции, собранные в буферный массив. Это решение предусматривает три этапа.
Сначала как можно выше на странице необходимо создать массив для сохранения в нем встроенных сценариев:
var mynamespace = { inline_scripts: [] };
Затем необходимо обернуть все встроенные сценарии функциями и добавить их в массив inline_scripts. Другими словами:
// было: //
<script>
console.log ( «I am inline»);</script>
// стало:
<script> mynamespace.inline_scripts.push (function () {
consоle.log ( «I am inline»); });
</scriрt>
И наконец, предусмотреть в главном сценарии цикл полного обхода буфера встроенных сценариев и их выполнения:
vаr i, scripts = mynamespace.inline_scripts, mаx — scripts.length;
fоr (i = 0; i < max; mаx += 1) { scripts[i] (); }
Добавление элемента
<script>
Часто сценарии добавляются в раздел <head> документа, однако их можно добавлять в любые элементы, включая <body> (как показано в примере использования JSONP).
Для добавления сценария в раздел <head> в предыдущем примере было использовано свойство documentElement, потому что оно представляет элемент <html>, первым вложенным элементом в котором является элемент
<head>: document.documentElement.firstChiId.appendChiId (script);
Тоже постоянно используется следующий способ:
dоcument.getElеmentsByTagNаme ( «head»)[0].appendChiId (script);
Данные приемы отлично подходят в таких случаях, когда абсолютно вся разметка находится под полнейшим вашим контролем, правда представьте, что Вы создаете виджет или рекламный элемент и не знаете о том, на каких страницах он будет размещаться. С технической стороны страница может не иметь ни раздела <head>, ни раздела <bоdy> — хотя свойство document.bоdy будет доступно абсолютно всегда, даже без тега
<body>: document.body.appendChi ld (script);
Но, как бы то ни было, на странице, на которой выполняется сценарий, всегда будет присутствовать как минимум единственный тег — тег <script>. Если на странице не будет тега <script> (со встроенным сценарием или со ссылкой на внешний файл), то ваш сценарий просто не будет выполнен. Вы можете воспользоваться этим фактом и с помощью метода insertBeforeO добавить свой элемент <script> перед первым доступным элементом <script> на странице:
var first_script = document.getElementsByTagNameC script')[0];
first_script.pаrentNode.insertBefоre (script, first_script);
Здесь f irst_script — это элемент <script>, гарантированно присутствующий на странице, a script — новый элемент <script>, созданный вами.