Published on

JS的加载、执行顺序

Authors

我们通常会认为,在html中,位置靠前的js,会优先加载、执行。这句话大部分情况下正确,但是随着浏览器的升级调整,新特性的加入,这个加载、执行顺序也越来越复杂。

首先我们看一个最简单的情况

<script src="a.js"></script>
<script src="b.js"></script>

chrome 下,为了减少等待时间,a,b 会并行加载,但是执行时则会先 a 后 b;并不是 a 加载,a 执行,b 加载,b 执行的顺序。

async、defer 的加入

关于 async、defer 功能及异同的介绍:点击前往

  • async 属性会让 js 并行加载,并在 js 加载完成后立即执行,也就是说执行顺序由加载速度定,而不是 html 中的先后顺序
  • defer 属性 js 同样会并行加载,而执行时间点为文档解析完成后,按照 html 中的顺序执行,也就是说 defer 不影响执行顺序

所以如果代码是这样的

<script src="a.js" async></script>
<script src="b.js"></script>

a,b 同样并行加载,但此时如果是 b 先加载完,则 b 会优先执行;

动态插入脚本的执行顺序

例:

<script type="text/javascript">
var snode = document.createElement('script')
snode.src = 'a.js'
document.head.appendChild(snode)
</script>
<script type="text/javascript">
var snodeb = document.createElement('script')
snodeb.src = 'b.js'
document.head.appendChild(snodeb)
</script>

对于支持 async 属性的浏览器,动态插入的外链脚本, 相当于默认具有 async=true;也就是 a.js, b.js 执行顺序不确定的;

如果需要让动态插入的脚本按插入顺序执行,可以显式设置 async = false;

<script type="text/javascript">
var snode = document.createElement('script')
snode.src = 'a.js'
snode.async = false
document.head.appendChild(snode)
</script>
<script type="text/javascript">
var snodeb = document.createElement('script')
snodeb.src = 'b.js'
snodeb.async = false
document.head.appendChild(snodeb)
</script>

动态插入内联脚本

特殊场景下,我们会使用 js 动态往页面插入脚本,比如:

<script type="text/javascript">
var dn = document.createElement('script')
dn.text = 'console.log("dn")'
document.head.appendChild(dn)
</script>

此动态插入的脚本并不会执行;

但是如果我们换一种方式,将其改成外链,但是 url 用一个 data url 来代替,则可以执行,比如:

<script type="text/javascript">
var dn = document.createElement('script')
dn.src = 'data:application/javascript,' + 'console.log("dn")'
document.head.appendChild(dn)
</script>

脚本的执行与其 onload 事件执行的先后

例:

<script type="text/javascript">
function track(evt) {
console.log(evt.type + ' : ' + evt.target.src)
}
</script>
<script src="c.js" onload="track(event)"></script>

执行后可以看出 js 加载完成后会优先完成解析,执行,然后才触发 onload 事件。内联脚本没有 onload 事件触发。如果我们要动态加载一个脚本,并在其加载执行完成后有回调,onload 就可以派上用场了。