同步加载和执行JS的情况
在HTML页面的</body>表情之前添加的所有<script>标签,无论是直接嵌入JS代码还是引入外部js代码都是同步执行的,这里的同步执行指的是在加载好和执行完JS代码之前整个浏览器的界面都是阻塞的。
静态加载时
内嵌代码和引入js代码都是同步加载。
动态加载时
通过document.write('<script src="xxx.js"></script>');的方式加入的代码也是同步加载的,但是要注意一点,这里开始加载和解析xxx.js是在执行完成当前的<script>标签之后,所以下面的代码会报错(我们假设test.js中创建了全局的p对象,且该对象存在sayHi的方法):
1 ');4 console.log(p.sayHi());5
根据我们说的开始加载和解析xxx.js是在执行完成当前的<script>标签之后,可以把上面的代码转换如下:
1 5
这样必然会出错,如下则可以正常执行:
1 ');4 5
document.write方式同步加载外部js文件无论是内嵌的写法还是写在外部js文件中,效果都是一样的,都会同步加载和执行。
异步加载和执行JS的情况
静态加载时
对于内嵌的JS代码只能同步执行,对于通过<script>标签引入的外部js文件,可以通过设定defer和async两个属性改变为异步加载执行。
defer
代码如下:<script src="xxx.js" defer></script>,该属性在HTML4.01引入,告诉浏览器立即下载该脚本(下载期间不阻塞页面),但是等待遇到</html>标签之后再执行,同时执行顺序按照标签出现顺序进行执行。该脚本的执行一定会在页面的load事件之前。
async
代码如下:<script src="xxx.js" async></script>,该属性在HTML5引入,告诉浏览器立即下载该脚本(下载期间不阻塞页面),但是执行脚本时不能保证按照标签出现顺序执行。该脚本的执行一定会在页面的load事件之前。
最佳实践
由于浏览器的支持不同,所以最好的方法仍然是将<script src="xxx.js"></script>脚本放在页面的最下方引入。
动态加载时
如果我们希望在页面任意时刻动态异步加载js文件,可以通过使用DOM元素创建方法来实现:
1 var script = document.createElement("script");2 script.type = "text/javascript";3 script.src = "xxx.js";4 document.getElementsByTagName("head")[0].appendChild(script);
当script元素被添加到页面之后便开始下载脚本文件。该技术的优点是:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。当script文件下载完成后,返回的代码通常被立即执行(除了FF和Opera,它们将等待此前的所有动态脚本节点执行完毕。)
唯一的问题是我们需要知道动态加载的脚本何时完成加载并可以使用,FF, Opera, Chrome和Safari3+会在节点接收完成后发出一个load事件;IE则是发出一个readystatechange事件,<script>元素有一个readyState属性,它的值随着下载过程而改变。readyState有5种取值:uninitialized(默认状态),loading(下载开始),loaded(下载完成),interactive(下载完成但尚不可用),complete(所有数据已准备好)。
最终的代码如下:
1 function loadScript(url, callback){ 2 var script = document.createElement("script"); 3 script.type = "text/javascript"; 4 if (script.readyState) { // IE 5 script.onreadystatechange = function() { 6 if (script.readyState == "loaded" || script.readyState == "complete") { 7 script.onreadystatechange = null; 8 callback(); 9 }10 };11 } else { // FF, Chrome, Opera, ...12 script.onload = function() {13 callback();14 };15 }16 script.src = url;17 document.getElementsByTagName("head")[0].appendChild(script);18 }
如果要加载多个文件,并且要保证顺序,则可以采用将上述函数串联的方式实现。
1 loadScript("../scripts/test1.js", function(){2 loadScript("../scripts/test2.js", function(){3 loadScript("../scripts/test3.js", function(){4 test3.print("hi, i'm abc.");5 })6 });7 });
如果觉得这种嵌入的写法太不爽,也可以稍作修改改为Promise的写法。