加载和执行脚本的顺序

在html页面中包含JavaScript有很多不同的方法。我知道以下几种选择:

  • 内联代码或从外部URI加载
  • 包含在<head>或<body>标签[12]
  • 没有,deferasync属性(仅外部脚本)
  • 包含在静态源代码中或由其他脚本动态添加(在不同的解析状态下,使用不同的方法)

不包括来自硬盘的浏览器脚本,javascript: uri和onEvent-attributes [3.],已经有16个选项来执行JS,我确定我忘记了一些东西。

我不太关心快速(并行)加载,我更好奇的是执行顺序(这可能取决于加载顺序和文档顺序)。例如,http://www.websiteoptimization.com/speed/tweak/defer/只处理其中的6种浏览器,并且主要测试旧的浏览器。

因为我担心没有,这是我的具体问题:我有一些初始化和脚本加载的(外部)头部脚本。然后在正文的末尾有两个静态的内联脚本。第一个允许脚本加载器动态地将另一个脚本元素(引用外部js)追加到主体。第二个静态内联脚本希望使用来自添加的外部脚本的js。它可以依赖于另一个已经被执行(为什么:-)?

284009 次浏览

如果你没有动态加载脚本或将它们标记为deferasync,那么脚本将按照页面中遇到的顺序加载。不管它是一个外部脚本还是一个内联脚本——它们都是按照在页面中遇到的顺序执行的。外部脚本之后的内联脚本将被保留,直到它们之前的所有外部脚本都加载并运行。

异步脚本(不管它们是如何被指定为异步的)以不可预知的顺序加载和运行。浏览器会并行加载它们,并且可以自由地以任何它想要的顺序运行它们。

多个异步事物之间没有可预测的顺序。如果需要一个可预测的顺序,那么就必须通过注册来自异步脚本的加载通知来编码,并在适当的东西加载时手动排序javascript调用。

动态插入脚本标记时,执行顺序将取决于浏览器。你可以看到Firefox在参考文章中的行为。简而言之,Firefox的新版本默认将动态添加的脚本标记添加到async,除非脚本标记另有设置。

带有async的脚本标记可以在加载时立即运行。事实上,浏览器可能会暂停解析器,不管它正在做什么,然后运行该脚本。所以,它几乎可以在任何时候运行。如果脚本被缓存,它几乎可以立即运行。如果脚本需要一段时间才能加载,那么它可能会在解析器完成后运行。关于async要记住的一件事是它可以在任何时候运行,而且时间是不可预测的。

带有defer的脚本标记等待整个解析器完成,然后按照遇到的顺序运行所有带有defer标记的脚本。这允许你将几个相互依赖的脚本标记为defer。它们都将被推迟到文档解析器完成之后,但它们将按照遇到它们时的顺序执行,以保留它们的依赖关系。我认为defer就像脚本被放入一个队列,在解析器完成后将被处理。从技术上讲,浏览器可能随时在后台下载脚本,但在解析器完成解析页面、解析并运行任何未标记为deferasync的内联脚本之前,它们不会执行或阻塞解析器。

下面是那篇文章中的一段话:

脚本插入脚本在IE和WebKit中异步执行,但是

HTML5规范的相关部分(适用于较新的兼容浏览器)是在这里。这里有很多关于异步行为的文章。显然,该规范不适用于您可能需要测试才能确定的旧浏览器(或不符合规范的浏览器)。

引用HTML5规范:

然后,下面描述情况的第一个选项

如果元素有一个src属性,并且元素有一个defer 属性,元素被标记为“解析器插入”,并且 元素没有async属性 必须添加元素 时将执行的脚本列表的末尾 已完成与解析器的文档相关联的解析 创建元素

网络任务源放入任务队列一次的任务 获取算法完成后必须设置元素的“ready to” 被解析器执行”标志。解析器将处理脚本的执行

如果该元素有src属性,并且该元素已被标记 作为“解析器插入”,元素没有async属性 元素是Document的未决解析阻塞脚本 创建元素的解析器。(这样的事情只能有一个

. sh

. sh

网络任务源放入任务队列一次的任务 获取算法完成后必须设置元素的“ready to” 被解析器执行”标志。解析器将处理脚本的执行

如果元素没有src属性,并且元素已经被 标记为“解析器插入”,以及HTML解析器的文档或 创建脚本元素的XML解析器有一个样式表 元素是的暂挂的解析-阻塞脚本 创建元素的解析器的Document。(只有 是一个这样的脚本,每个文档一次。)

设置元素的“准备被解析器执行”标志。解析器将会

.执行脚本 如果元素有src属性,没有async属性, and没有force-async标志集
该元素必须添加 到将尽快按顺序执行的脚本列表的末尾 的脚本元素,尽可能与文档相关联

网络任务源放入任务队列一次的任务 获取算法已经完成,需要执行以下步骤:

如果元素现在不是脚本列表中的第一个元素 它将按照它被添加到的顺序尽快执行 在上面,然后将元素标记为就绪,但是在没有

.

.

执行:执行第一个脚本对应的脚本块 元素中的脚本,该脚本将按顺序在 可能的。< / p > 从将要执行的脚本列表中删除第一个元素

如果此脚本列表将按顺序尽快执行 仍然不是空的,并且第一个条目已经被标记为 准备好,然后跳回标记为执行的步骤

如果元素有src属性该元素必须添加到 一组脚本,将尽快执行的文件 的脚本元素的同时,准备一个脚本算法 开始。< / p >

网络任务源放入任务队列一次的任务 获取算法完成后必须执行脚本块和 然后从将以。方式执行的脚本集中删除该元素

否则用户代理必须立即执行脚本块, 即使其他脚本已经在执行


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模块加载

浏览器将按照找到脚本的顺序执行脚本。如果调用外部脚本,它将阻塞页面,直到脚本被加载和执行。

为了验证这一事实:

// 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>标记并开始一个新的标记。

@addyosmani的一个很好的总结

enter image description here

无耻地从https://addyosmani.com/blog/script-priorities/复制

在测试了许多选项之后,我发现以下简单的解决方案是按照在所有现代浏览器中添加脚本的顺序加载动态加载的脚本

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'])

我在理解如何在onload事件发生之前执行嵌入式模块脚本时遇到了麻烦。上面的回答很有帮助,但让我补充一个部分的答案,关于什么解决了我误解“脚本的加载和执行顺序”的特定问题。

我第一次使用……这导致了一个奇怪的问题,它在正常加载页面时工作,但在FireFox的调试器中运行时不工作。这使得调试变得非常困难。

注意:类型为"module"的脚本;总是有一个隐含的“延迟”;属性,这意味着它们不会停止html的解析,这意味着onload-event可以在脚本执行之前发生。我不想那样。但我确实想使用type="module"使我未导出的JavaScript函数和变量对同一页面上的其他脚本不可见。

我尝试了不同的选项,但多亏了上面的答案,我得到了一个见解,如果你添加async -属性到一个模块类型的脚本,这意味着脚本异步加载,但一旦加载它立即执行。

但在我的例子中,这是一个嵌入在HTML页面中的脚本。因此,这意味着不需要“异步”加载。它已经与页面一起加载,因为它是嵌入在页面中的。因此,它与这个更改立即执行—这是我想要的。

因此,我认为有必要指出这个特定的情况,因为它有点违反直觉:要立即执行一个嵌入式脚本,必须将ASYNC属性添加到它的标记中。

通常人们会认为&;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();
};

这可能会导致一些与速度相关的问题,所以你可以根据自己的需求调用函数initializeScripts() !