是否保证 <script> 标记上的加载事件总是在脚本执行时立即触发?

Posted

技术标签:

【中文标题】是否保证 <script> 标记上的加载事件总是在脚本执行时立即触发?【英文标题】:Is it guaranteed that load event on a <script> tag is always fired immediately the script is executed? 【发布时间】:2019-06-29 22:20:45 【问题描述】:

假设有两个 javascript 文件。

一个.js

window.a=1;

两个.js

window.a=2;

还有装载机: loader.html

<body></body>
<script>
const s1=document.createElement("script");
s1.src="one.js";
s1.addEventListener("load",()=>console.log("one.js",window.a));
const s2=document.createElement("script");
s2.src="two.js";
s2.addEventListener("load",()=>console.log("two.js",window.a));
document.body.appendChild(s1);
document.body.appendChild(s2);
</script>

它总是产生 [Out-1] 或 [Out-2] 吗? (编辑:它既不意味着“我想要总是产生 [Out-1] 的代码”,也不意味着“我想要总是产生 [Out-2] 的代码”。[Out-1] 和 [Out-2] 都是可以接受的.)

[Out-1](按s1-exec-> s1-onload -> s2-exec -> s2-onload的顺序运行,就OK了):

"one.js" 1
"two.js" 2

[Out-2](s2-exec-> s2-onload -> s1-exec -> s1-onload. 也可以):

"two.js" 2
"one.js" 1

我担心的是浏览器是否可以按 s1-exec -> s2-exec -> s1-onload -> s2-onload 的顺序运行并生成为 [Out-3]

[Out-3](不正常):

"one.js" 2
"two.js" 2

我检查了 HTML5 规范 https://www.w3.org/TR/2008/WD-html5-20080610/tabular.html#script

它说:

如果加载成功

    如果脚本元素的 Document 是其浏览上下文中的活动文档,则用户代理必须执行脚本: .... 然后,用户代理必须在脚本元素处触发加载事件。

是否保证“其他脚本标签永远不会中断第1步和第2步之间的过程”?。

(已编辑)换句话说,我想知道 HTML 规范是否要求浏览器同步运行 step1 到 step2(标题“立即”就是这个意思)。

【问题讨论】:

developer.mozilla.org/en-US/docs/Web/Events/load 看起来答案是否定的。 load 事件在完成加载时触发。下一个问题是,这行代码是同步的吗?我怀疑它,因为它是一个事件监听器......试图找到它,因此我没有留下答案......(还)。顺便说一句,好问题。 感谢您的信息。至少,上面的规范似乎保证在“执行”之后触发“加载”(它在执行完成之前永远不会发生)。但并不是说整个过程应该是同步的。 【参考方案1】:

至少按照规范,是的,它保证加载事件触发在"execute the script block"算法结束时。

所以,你可以面对 [out-1] 和 [out-2],但不能面对 [out-3]。

当您在文档中附加

// scr1 will load an external script
const scr1 = document.createElement('script');
scr1.onload = e => console.log('1', window.$);
scr1.src = 'https://cdn.jsdelivr.net/gh/jquery/jquery@3.2/dist/jquery.js?' + Math.random();
// scr2 will load a dataURL script
const scr2 = document.createElement('script');
scr2.onload = e => console.log('2', window.$);
scr2.src = 'data:application/javascript,$="script 2"';

// even though scr1 is appended first
document.head.appendChild(scr1);
document.head.appendChild(scr2);

但是,当他们在执行脚本块算法结束时向fire请求事件时,没有任何竞争条件的位置,事件的回调将同步执行。

(注意:如果他们要求“queue a task 到 fire an event”,就像他们在其他地方所做的那样,那么回调将异步触发,但仍然没有竞争条件,因为两个任务将在同一个队列中排队任务队列)。

【讨论】:

我的观点是,步骤的非交错由添加成功获取的脚本的list of scripts that will execute in order as soon as possible 强制执行。由于脚本的执行是通过处理该列表中的脚本来驱动的,因此它们永远不会交错。但是请注意经典脚本和模块脚本之间的区别。 load 事件在运行经典脚本后立即触发,但任务排队以触发模块上的事件。 @Alohci,是的,我也是这么读的,我更关心事件回调何时执行。我不确定 [event fires => event callbacks execution] 是否总是同步的。可以将回调的执行延迟到下一帧,并且它们可能是在第一个事件触发之后但在处理回调之前立即执行下一个“尽快执行的脚本”的情况。虽然我真的不确定这种情况,但试图强制它永远不会导致 [out-3]。

以上是关于是否保证 <script> 标记上的加载事件总是在脚本执行时立即触发?的主要内容,如果未能解决你的问题,请参考以下文章

如何修改timepicker的加减按钮

脚本标记中何时需要CDATA部分?

jQuery选择当前脚本标记

来自php和mysql的googlemaps上的多个标记

我应该将 <script> 标签放在 HTML 标记中的啥位置?

在加载 <script> 标记之前等待 turbolinks 加载