浏览器环境下JavaScript脚本加载与执行探析之defer与async特性( 二 )


浏览器环境下JavaScript脚本加载与执行探析之defer与async特性


根据标准中的定义,内部脚本不支持defer,而IE9及以下的浏览器则提供了内部脚本的defer支持 。
2.2 defer的浏览器支持情况
下面我们来看一下defer特性的浏览器支持情况:
浏览器环境下JavaScript脚本加载与执行探析之defer与async特性


IE9及以下的浏览器存在一个bug,这个bug将在稍后的DEMO中进行详细的说明 。
2.3 DEMO:defer特性的功能验证
我们模仿在Olivier Rochard在《the script defer attribute》使用的方式来验证一下defer特性的功能:
首先我们准备了6个外部脚本:
1.js:
test= "我是head外部脚本";
2.js
test= "我是body外部脚本";
3.js
test= "我是底部外部脚本";
defer1.js
test= "我是head外部延迟脚本";
defer2.js
test= "我是body外部延迟脚本";
defer3.js
test= "我是底部外部延迟脚本";
HTML中的代码为:
浏览器环境下JavaScript脚本加载与执行探析之defer与async特性


代码中,为了方便实现DOMContentLoaded事件,我们引入了jQuery(之后的文章还会再介绍如何自己实现兼容的DOMContentLoaded),然后,我们在脚本的head内、body内部和body外部分别引入延迟脚本和正常脚本,并且通过一个全局的字符串来记录每一段代码的执行状态,我们看一下各个浏览器中的执行结果:
浏览器环境下JavaScript脚本加载与执行探析之defer与async特性


从输出的结果中我们可以确定,只有IE9及以下浏览器支持内部延迟脚本,并且defer后的脚本都会在DOMContentLoaded事件之前触发,因此也是会堵塞DOMContentLoaded事件的 。
2.4 DEMO:IE<=9的defer特性bug
从2.3节中的demo可以看出,defer后的脚本还是能够保持执行顺序的,也就是按照添加的顺序依次执行 。而在IE<=9中,这个问题存在一个bug:假如我们向文档中增加了多个defer的脚本,而且之前的脚本中有appendChild,innerHTML,insertBefore,replaceChild等修改了DOM的接口调用,那么后面的脚本可能会先于该脚本执行 。可以参考github的issue:https://github.com/h5bp/lazyweb-requests/issues/42
我们通过DEMO验证一下,首先修改1.js的代码为(这段代码只为模拟,事实上这段代码存在极大的性能问题):
document.body.innerHTML = "
我是后来加入的
";


document.body.innerHTML= "
我是后来加入的
";


document.body.innerHTML= "
我是后来加入的
";


document.body.innerHTML= "
我是后来加入的
";


document.body.innerHTML= "
我是后来加入的
";


document.body.innerHTML= "
我是后来加入的
";


document.body.innerHTML= "
我是后来加入的
";


alert("我是第1个脚本");
2.js
alert("我是第2个脚本");
修改HMTL中的代码为:
浏览器环境下JavaScript脚本加载与执行探析之defer与async特性


正常情况下,浏览器中弹出框的顺序肯定是:我是第1个脚本-》我是第2个脚本,然而在IE<=9中,执行结果却为:我是第2个脚本-》我是第1个脚本,验证了这个bug 。
2.5 defer总结
在总结之前,首先要说一个注意点:正如标准中提到的,defer的脚本中不应该出现document.write的操作,浏览器会直接忽略这些操作 。
总的来看,defer的作用一定程度上与将脚本放置在页面底部有一定的相似,但由于IE<=9中的bug,如果页面中出现多个defer时,脚本的执行顺序可能会被打乱从而导致代码依赖可能会出错,因此实际项目中很少会使用defer特性,而将脚本代码放置在页面底部可以替代defer所提供的功能 。

推荐阅读