загружать и выполнять порядок скриптов


265

Существует так много разных способов включить JavaScript в HTML-страницу. Я знаю о следующих вариантах:

  • встроенный код или загруженный с внешнего URI
  • включается в тег <head> или <body> [ 1 , 2 ]
  • не имея ни одного, deferилиasync атрибута (только внешние скрипты)
  • включается в статический источник или динамически добавляется другими скриптами (при разных состояниях разбора, разными методами)

Не считая скрипты браузера с жесткого диска, javascript: URIs и onEvent-attributes [ 3 ], уже есть 16 альтернатив для запуска JS, и я уверен, что что-то забыл.

Меня не очень беспокоит быстрая (параллельная) загрузка, мне более интересно узнать порядок выполнения (который может зависеть от порядка загрузки и порядка документов ). Есть ли хорошая (кросс-браузерная) ссылка, которая охватывает действительно все случаи? Например http://www.websiteoptimization.com/speed/tweak/defer/ имеет дело только с 6 из них и тестирует в основном старые браузеры.

Поскольку я боюсь, что нет, вот мой конкретный вопрос: у меня есть некоторые (внешние) сценарии заголовка для инициализации и загрузки сценария. Затем у меня есть два статических встроенных скрипта в конце тела. Первый позволяет загрузчику скриптов динамически добавлять другой элемент скрипта (ссылающийся на внешние js) к телу. Второй из статических встроенных сценариев хочет использовать js из добавленного внешнего сценария. Может ли это зависеть от того, что другой был выполнен (и почему :-)?


Вы смотрели на « Загрузка сценариев без блокировок » Стива Соудерса? Это немного устарело, но все же содержит некоторые ценные сведения о поведении браузера, учитывая конкретную технику загрузки скрипта.
Джош Хабдас

Ответы:


331

Если вы не загружаете скрипты динамически или не помечаете их как deferили async, тогда скрипты загружаются в порядке, указанном на странице. Неважно, является ли это внешним скриптом или встроенным скриптом - они выполняются в порядке, в котором они встречаются на странице. Встроенные сценарии, которые идут после внешних сценариев, сохраняются до тех пор, пока все внешние сценарии, которые были до них, не загружены и запущены.

Асинхронные сценарии (независимо от того, как они указаны как асинхронные) загружаются и выполняются в непредсказуемом порядке. Браузер загружает их параллельно, и он может запускать их в любом порядке.

Там нет предсказуемого порядка среди нескольких асинхронных вещей. Если требуется предсказуемый порядок, его необходимо кодировать путем регистрации уведомлений о загрузке из асинхронных сценариев и ручной последовательности вызовов javascript при загрузке соответствующих объектов.

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

Тег скрипта с asyncможет быть запущен, как только он будет загружен. Фактически, браузер может приостановить анализатор от всего, что он делал, и запустить этот скрипт. Таким образом, он действительно может работать практически в любое время. Если сценарий был кэширован, он может запуститься практически сразу. Если сценарий загружается некоторое время, он может запуститься после того, как анализатор завершит работу. Следует помнить одну вещь async: он может работать в любое время, и это время не предсказуемо.

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

Вот цитата из этой статьи:

Сценарии с вставленными сценариями выполняются асинхронно в IE и WebKit, но синхронно в Opera и Firefox до 4.0.

Соответствующая часть спецификации HTML5 (для новых совместимых браузеров) здесь . Там много написано об асинхронном поведении. Очевидно, что эта спецификация не относится к более старым браузерам (или браузерам с плохой совместимостью), поведение которых вам, вероятно, придется проверить, чтобы определить.

Цитата из спецификации HTML5:

Затем следует использовать первый из следующих параметров, описывающих ситуацию:

Если элемент имеет атрибут src, а элемент имеет атрибут defer, и элемент помечен как «вставленный парсером», а элемент не имеет асинхронного атрибута . Элемент должен быть добавлен в конец списка. сценарии, которые будут выполняться после завершения анализа документа, связанного с документом синтаксического анализатора, создавшего элемент.

Задача, которую источник сетевой задачи помещает в очередь задач после завершения алгоритма выборки, должна установить флаг элемента «готов к выполнению синтаксическим анализатором». Парсер будет обрабатывать выполнение скрипта.

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

Задача, которую источник сетевой задачи помещает в очередь задач после завершения алгоритма выборки, должна установить флаг элемента «готов к выполнению синтаксическим анализатором». Парсер будет обрабатывать выполнение скрипта.

Если элемент не имеет атрибута src, и элемент был помечен как «вставленный синтаксический анализатор», а документ Document для анализатора HTML или синтаксического анализатора XML, который создал элемент script, имеет таблицу стилей, которая блокирует сценарии . в ожидании сценария синтаксического анализа Документа синтаксического анализатора, который создал элемент. (За один раз может быть только один такой скрипт для каждого документа.)

Установите флаг элемента "готов к выполнению парсером". Парсер будет обрабатывать выполнение скрипта.

Если элемент имеет атрибут src, не имеет атрибута async и не имеет установленного флага «force-async» Элемент должен быть добавлен в конец списка сценариев, которые будут выполняться по порядку как можно скорее с документом элемента скрипта во время запуска алгоритма подготовки скрипта.

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

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

Выполнение: Выполните блок сценария, соответствующий первому элементу сценария в этом списке сценариев, который будет выполнен по порядку как можно скорее.

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

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

Если элемент имеет атрибут src Элемент должен быть добавлен в набор сценариев, которые будут выполняться как можно быстрее документа Document элемента script в момент запуска алгоритма подготовки сценария.

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

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


Как насчет скриптов модуля Javascript type="module"?

Javascript теперь имеет поддержку загрузки модуля с синтаксисом, подобным этому:

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

Или с srcатрибутом:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

Всем сценариям type="module"автоматически присваивается deferатрибут. Это загружает их параллельно (если не встроено) с другой загрузкой страницы, а затем запускает их по порядку, но после того, как анализатор завершен.

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

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


Спасибо за ответ, но проблема в том , сценарий будет динамически добавляются на страницу, которая означает , что считается асинхронными . Или это работает только в <head>? И мой опыт также, что они выполняются в порядке документов?
Берги

@Bergi - если он добавлен динамически, то он асинхронный и порядок выполнения не определен, если вы не напишите код для управления им.
jfriend00

Просто Колинк заявляет об обратном ...
Берги

@Bergi - Хорошо, я изменил свой ответ, чтобы сказать, что асинхронные сценарии загружаются в неопределенном порядке. Они могут быть загружены в любом порядке. Если бы я был тобой, я бы не рассчитывал, что наблюдение Колинка будет таким, каким оно всегда является. Я не знаю ни одного стандарта, в котором говорится, что динамически добавляемый сценарий должен запускаться немедленно и должен блокировать запуск других сценариев, пока он не будет загружен. Я ожидаю, что это будет зависеть от браузера, а также, возможно, зависеть от факторов окружающей среды (кэшируется ли скрипт и т. Д.).
jfriend00

1
@RuudLenders - это зависит от реализации браузера. Обнаружение тега script ранее в документе, но отмеченное знаком, deferдает парсеру возможность начать его загрузку раньше, при этом все еще откладывая его выполнение. Обратите внимание, что если у вас много сценариев с одного и того же хоста, то более ранний запуск загрузки может фактически замедлить загрузку других с того же хоста (поскольку они конкурируют за пропускную способность), на котором ваша страница ожидает (но не defer), поэтому это может быть обоюдоострый меч.
jfriend00

13

Браузер выполнит сценарии в порядке их поиска. Если вы вызываете внешний скрипт, он блокирует страницу, пока скрипт не будет загружен и выполнен.

Чтобы проверить этот факт:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

Динамически добавленные сценарии выполняются, как только они добавляются в документ.

Чтобы проверить этот факт:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

Порядок оповещений «добавлен» -> «Привет!» -> "финал"

Если в скрипте вы попытаетесь получить доступ к элементу, который еще не был достигнут (пример:), <script>do something with #blah</script><div id="blah"></div>то вы получите ошибку.

В целом, да, вы можете включить внешние скрипты, а затем получить доступ к их функциям и переменным, но только если вы выйдете из текущего <script>тега и начнете новый.


Я могу подтвердить это поведение. Но на наших страницах отзывов есть подсказки, что это может сработать только тогда, когда test.php кэшируется. Знаете ли вы какие-либо ссылки на спецификации / ссылки по этому поводу?
Берги

4
link.js не блокирует Используйте скрипт, похожий на ваш php один, чтобы имитировать длительное время загрузки.
1983

14
Этот ответ неверен. Это не всегда тот случай, когда «динамически добавленные сценарии выполняются, как только они добавляются в документ». Иногда это так (например, для старых версий Firefox), но обычно это не так. Порядок выполнения, как указано в ответе jfriend00, не является определенным.
Фабио Бельтрамини

1
Не имеет смысла, что сценарии выполняются в порядке их появления на странице, независимо от того, встроены они или нет. Почему тогда у фрагмента менеджера тегов Google и многих других, которые я видел, есть код для вставки нового сценария над всеми другими тегами сценария на странице? Было бы бессмысленно делать это, если вышеперечисленные скрипты уже наверняка были загружены ?? или я что-то упустил.
user3094826


2

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

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.