我有一个空的html页面,里面有两个脚本:
<script src="./scripta.js"></script>
<script src="./scriptb.js"></script>
scripta.js
:
console.log("from a")
setTimeout(() => {
console.log("from a timeout")
}, 0)
scriptb.js
console.log("from b")
现在,当我刷新页面时,我主要在控制台中看到这些日志:
from a
from b
from a timeout
根据我的理解,应该是这样的。
然而,在多次重新加载页面后,我也注意到了这些日志:
from a
from a timeout
from b
有人能解释或给我一个答案吗?为什么我会看到日志的第二个变体?谢谢
作为前言,请记住,当浏览器遇到纯ol脚本元素时,如<script>/* some script */</script>
或<script src="someScript.js"></script>
,浏览器会立即运行(或下载并运行(脚本,而如果<script>
具有src=""
属性,且具有defer
或async
属性,则脚本的执行将延迟。
-
(作为一个附加说明:我确实认为我们不能将
defer
与内联脚本一起使用是愚蠢的,尽管解决方法只是将代码封装在DOMContentLoaded
事件侦听器回调中(
虽然setTimeout
的规范确实保证回调的一些顺序,但它并不要求浏览器在使用setTimeout( func, 0 )
的脚本完成后立即运行func
。
根据我对HTML+DOM规范的理解,以下两个场景应该被认为大致等效,并且应该始终具有相同的结果(假设所有内容都被缓存(,并具有以下输出:
foo
bar
myCallback
示例1
<html>
<head>
<script>
console.log("foo");
window.setTimeout( myCallback, 0 );
function myCallback() {
console.log("myCallback");
}
</script>
<script>
console.log("bar");
</script>
</head>
</html>
示例2
(其中scriptA.js
和scriptB.js
具有与其在前一示例中各自的内联<script>
相同的脚本主体(
<html>
<head>
<script src="scriptA.js"></script>
<script src="scriptB.js"></script>
</head>
</html>
在您的情况下,会发生以下情况:
浏览器下载页面的HTML并开始解析。
当浏览器遇到第一个
<script src="scriptA.js"></script>
时,它将暂停处理HTML的其余部分,直到运行scriptA.js
。浏览器运行
scriptA.js
,其中:3.1.呼叫
console.log("from a")
。3.2.匿名函数(语法上位于
setTimeout
调用站点内的=>
胖箭头函数(被添加到浏览器的任务队列中,以便在下一个事件周期执行,这通常在浏览器感觉时发生,即它是不确定的:- CCD_ 24的CCD_;立即执行";也不意味着";同时执行";,相反,它具体地表示";在下一个事件周期中执行">
- 浏览器中的JavaScript始终是单线程的:只有通过服务工作者才能进行并发执行
3.3.
scriptA.js
完成后,浏览器可以根据运行时条件自由选择下一步操作(请继续阅读(。优化,假设第二个
<script>
元素的HTML文本已经下载(几乎总是这样,因为并不是每个HTML元素都在自己的HTTP/TCP数据包中发送(,那么浏览器的HTML解析器将看到它需要下载并运行scriptB.js
-,如果scriptB.js
已经缓存在浏览器中,则浏览器可以立即执行它,这意味着scriptB.js
将在scriptA.js
的setTimeout
回调之前运行。然而,如果
scriptB.js
尚未下载(缓存、解析并准备执行(,则浏览器知道必须首先等待,这会引入阻塞等待,但浏览器仍然可以使用该阻塞等待来运行任何挂起的JavaScript任务,包括setTimeout
添加的任务。