Загрузить и выполнить порядок скриптов
Существует так много разных способов включить JavaScript в HTML-страницу. Я знаю о следующих вариантах:
- встроенный код или загруженный с внешнего URI
- включается в тег или [ 1, 2]
- не имея ни одного,
defer
или жеasync
атрибут (только внешние скрипты) - включается в статический источник или динамически добавляется другими скриптами (при разных состояниях разбора, разными методами)
Не считая браузерные скрипты с жесткого диска, javascript:URI и onEvent
-attributes [ 3], уже есть 16 альтернатив для запуска JS, и я уверен, что что-то забыл.
Меня не очень беспокоит быстрая (параллельная) загрузка, мне более интересно узнать порядок выполнения (который может зависеть от порядка загрузки и порядка документов). Есть ли хорошая (кросс-браузерная) ссылка, которая охватывает действительно все случаи? Например, http://www.websiteoptimization.com/speed/tweak/defer/ имеет дело только с 6 из них и тестирует в основном старые браузеры.
Поскольку я боюсь, что это не так, вот мой конкретный вопрос: у меня есть несколько (внешних) головных скриптов для инициализации и загрузки скриптов. Затем у меня есть два статических встроенных скрипта в конце тела. Первый позволяет загрузчику скриптов динамически добавлять другой элемент скрипта (ссылаясь на внешние js) к телу. Второй из статических встроенных сценариев хочет использовать js из добавленного внешнего сценария. Может ли это зависеть от того, что другой был выполнен (и почему:-)?
6 ответов
Если вы не загружаете скрипты динамически или не помечаете их как отложенные или асинхронные, тогда скрипты загружаются в порядке, указанном на странице. Неважно, является ли это внешним скриптом или встроенным скриптом - они выполняются в том порядке, в котором они встречаются на странице. Встроенные сценарии, которые приходят после внешних сценариев, сохраняются до тех пор, пока все внешние сценарии, которые были до них загружены и запущены.
Асинхронные сценарии (независимо от того, как они указаны как асинхронные) загружаются и запускаются в непредсказуемом порядке. Браузер загружает их параллельно, и он может запускать их в любом порядке.
Там нет предсказуемого порядка среди нескольких асинхронных вещей. Если бы нужен был предсказуемый порядок, он должен был бы быть закодирован путем регистрации уведомлений о загрузке из асинхронных сценариев и ручной последовательности вызовов javascript при загрузке соответствующих объектов.
Когда тег сценария вставляется динамически, поведение порядка выполнения будет зависеть от браузера. Вы можете увидеть, как Firefox ведет себя в этой справочной статье. Короче говоря, более новые версии Firefox по умолчанию динамически добавляют тег сценария для асинхронного, если тег сценария не был установлен иначе.
Тег сценария с async
может быть запущен, как только он будет загружен. Фактически, браузер может приостановить анализатор от всего, что он делал, и запустить этот скрипт. Таким образом, он действительно может работать практически в любое время. Если сценарий был кэширован, он может запуститься практически сразу. Если сценарий загружается некоторое время, он может запуститься после того, как анализатор завершит работу. Одна вещь, чтобы помнить с async
является то, что он может работать в любое время, и это время не предсказуемо.
Тег сценария с defer
ожидает завершения работы всего синтаксического анализатора и запускает все сценарии, отмеченные defer
в порядке их встречи. Это позволяет пометить несколько сценариев, которые зависят друг от друга, как defer
, Все они будут отложены до тех пор, пока анализатор документов не будет завершен, но они будут выполняться в том порядке, в котором они были найдены, сохраняя свои зависимости. Я думаю о defer
как сценарии помещаются в очередь, которая будет обработана после выполнения синтаксического анализатора. Технически браузер может загружать сценарии в фоновом режиме в любое время, но он не будет выполнять или блокировать синтаксический анализатор до тех пор, пока синтаксический анализатор не завершит синтаксический анализ страницы и синтаксический анализ и запуск любых встроенных сценариев, которые не помечены как отложенные или асинхронные.
Вот цитата из этой статьи:
Сценарии с вставленными сценариями выполняются асинхронно в IE и WebKit, но синхронно в Opera и Firefox до 4.0.
Соответствующая часть спецификации HTML5 (для новых совместимых браузеров) находится здесь. Там много написано об асинхронном поведении. Очевидно, что эта спецификация не распространяется на старые браузеры (или браузеры с плохим подтверждением), чье поведение вам, вероятно, придется проверить, чтобы определить.
Цитата из спецификации HTML5:
Затем следует использовать первый из следующих параметров, описывающих ситуацию:
Если элемент имеет атрибут src, а элемент имеет атрибут defer, и элемент помечен как "вставленный в синтаксический анализатор", и элемент не имеет асинхронного атрибута. Элемент должен быть добавлен в конец списка. сценарии, которые будут выполняться, когда документ завершит анализ, связанный с Document синтаксического анализатора, создавшего элемент.
Задача, которую источник сетевой задачи помещает в очередь задач после завершения алгоритма выборки, должна установить флаг элемента "готов к выполнению анализатором". Парсер будет обрабатывать выполнение скрипта.
Если элемент имеет атрибут src, и элемент помечен как "вставленный синтаксическим анализатором", и элемент не имеет асинхронного атрибута, то этот элемент является ожидающим сценарием блокировки синтаксического анализа документа анализатора, который создал элемент. (За один раз может быть только один такой скрипт для каждого документа.)
Задача, которую источник сетевой задачи помещает в очередь задач после завершения алгоритма выборки, должна установить флаг элемента "готов к выполнению анализатором". Парсер будет обрабатывать выполнение скрипта.
Если элемент не имеет атрибута src, и элемент помечен как "вставленный парсером", и документ HTML-анализатора или XML-анализатора, который создал элемент script, имеет таблицу стилей, которая блокирует сценарии. в ожидании сценария синтаксического анализа Документа синтаксического анализатора, который создал элемент. (За один раз может быть только один такой скрипт для каждого документа.)
Установите флаг элемента "готов к выполнению парсером". Парсер будет обрабатывать выполнение скрипта.
Если элемент имеет атрибут src, не имеет атрибута async и не имеет установленного флага "force-async" Элемент должен быть добавлен в конец списка сценариев, которые будут выполняться по порядку как можно скорее с документом элемента скрипта во время запуска алгоритма подготовки скрипта.
Задача, которую источник сетевой задачи помещает в очередь задач после завершения алгоритма выборки, должна выполнить следующие шаги:
Если элемент не является первым элементом в списке сценариев, который будет выполняться по порядку, как только возможно, к которому он был добавлен выше, то пометьте элемент как готовый, но прервите эти шаги, пока не выполнив сценарий.
Выполнение: Выполните блок сценария, соответствующий первому элементу сценария в этом списке сценариев, который будет выполнен по порядку как можно скорее.
Удалите первый элемент из этого списка сценариев, которые будут выполняться по порядку как можно скорее.
Если этот список сценариев, которые будут выполняться по порядку в кратчайшие сроки, все еще не пуст и первая запись уже помечена как готовая, вернитесь к шагу, помеченному как выполнение.
Если элемент имеет атрибут src Элемент должен быть добавлен в набор сценариев, которые будут выполняться как можно быстрее документа Document элемента script в момент запуска алгоритма подготовки сценария.
Задача, которую источник сетевой задачи помещает в очередь задач после завершения алгоритма выборки, должна выполнить блок сценария, а затем удалить элемент из набора сценариев, которые будут выполнены как можно скорее.
В противном случае пользовательский агент должен немедленно выполнить блок сценария, даже если другие сценарии уже выполняются.
Браузер выполнит сценарии в порядке их поиска. Если вы вызываете внешний скрипт, он будет блокировать страницу, пока скрипт не будет загружен и выполнен.
Чтобы проверить этот факт:
// 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>
пометить и начать новый.
После тестирования многих опций я обнаружил, что следующее простое решение загружает динамически загружаемые скрипты в том порядке, в котором они добавляются во всех современных браузерах.
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'])
У меня возникли проблемы с пониманием того, как заставить встроенный модуль-скрипт выполняться до того, как произойдет событие загрузки. Приведенные выше ответы очень помогли, но позвольте мне добавить частичный ответ о том, что устранило мою конкретную проблему непонимания «Порядка загрузки и выполнения сценариев».
Сначала я использовал ..., что вызвало странную проблему, которая работала при обычной загрузке страницы, но не при запуске ее в отладчике в FireFox. Это очень усложняло отладку.
Примечание. Скрипты типа «модуль» всегда имеют неявный атрибут «отложенный», что означает, что они не останавливают синтаксический анализ html, а это означает, что событие onload может произойти до того, как скрипт будет выполнен. Я этого не хотел. Но я хотел использовать type="module", чтобы сделать мои неэкспортированные функции и переменные JavaScript невидимыми для других скриптов на той же странице.
Я пробовал разные варианты, но благодаря приведенным выше ответам я понял, что если вы добавите async -attribute в скрипт модуля типа, это означает, что скрипт загружается асинхронно, НО после загрузки он выполняется немедленно.
Но в моем случае это был скрипт, встроенный в HTML-страницу. ПОЭТОМУ это означало, что ничего не нужно для загрузки «асинхронно». Он уже был загружен со страницей, так как был встроен в нее. Поэтому с этим изменением оно сразу же выполнялось — чего я и хотел.
Поэтому я думаю, что стоит указать на этот конкретный случай, потому что он несколько нелогичен: чтобы встроенный скрипт выполнялся НЕМЕДЛЕННО, вы должны добавить атрибут ASYNC к его тегу.
Обычно можно подумать, что «асинхронность» означает, что что-то происходит асинхронно, в неопределенном порядке, а не сразу. Но нужно понимать, что «асинхронный» вызывает асинхронную ЗАГРУЗКУ, но немедленное ВЫПОЛНЕНИЕ после завершения загрузки. А когда скрипт встроен, загружать не нужно, и поэтому вы получаете немедленное выполнение.
Резюме: использование
<script type="module" async> ... </script>
чтобы модуль-скрипт, встроенный в HTML-страницу, выполнялся немедленно.
Идеально подходит для вашего запроса!
Если ни одно из решений не сработало для вас, обратитесь к приведенному ниже моему решению, которое я разработал со своей стороны.
Я также искал решение, но после долгих поисков я резюмировал свой код, как показано ниже, который отлично работает для меня!
Это полезно, когда вы хотите, чтобы после полной загрузки предыдущего скрипта загружался только следующий скрипт!
просто создайте файл с именем jsLoadScripts.js и вставьте его в заголовок или в конец тела.
//From Shree Aum Software Solutions
//aumsoftwaresolutions@gmail.com
//script incrementor for array
scriptIncrementor = 0;
//define your script urls here
let scripts = [
"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
"jsGlobalFunctions.js",
"jsDateParser.js",
"jsErrorLogger.js",
"jsGlobalVariables.js",
"jsAjaxCalls.js",
"jsFieldsValidator.js",
"jsTableClickEvent.js",
"index.js",
"jsOnDocumentReady.js",
];
//it starts with the first script and then adds event listener to it. which will load another script on load of it. then this chain goes on and on by adding dynamic event listeners to the next scripts!
function initializeScripts() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = scripts[scriptIncrementor];
document.head.appendChild(script);
script.addEventListener("load", function () {
loadNextScript();
scriptIncrementor++;
});
}
// this function adds event listener to the scripts passed to it and does not allow next script load until previous one has been loaded!
function loadNextScript() {
if (scriptIncrementor != scripts.length - 1) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = scripts[scriptIncrementor + 1];
document.head.appendChild(script);
script.addEventListener("load", function () {
loadNextScript();
scriptIncrementor++;
});
}
}
// start fetching your scripts
window.onload = function () {
initializeScripts();
};
Это может вызвать некоторые проблемы, связанные со скоростью, поэтому вы можете вызвать функцию