关于JavaScript脚本加载的问题,相信大家碰到很多。主要在几个点——
1> 同步脚本和异步脚本带来的文件加载、文件依赖及执行顺序问题
2> 同步脚本和异步脚本带来的性能优化问题
深入理解脚本加载相关的方方面面问题,不仅利于解决实际问题,更加利于对性能优化的把握并执行。
先看随便一个script标签代码——
复制代码 代码如下:
<script src="https://www.jb51.net/js/myApp.js"></script>
如果放在<head>上面,会阻塞所有页面渲染工作,使得用户在脚本加载完毕并执行完毕之前一直处于“白屏死机”状态。而<body>末尾的打脚本只会让用户看到毫无生命力的静态页面,原本应该进行客户端渲染的地方却散布着不起作用的控件和空空如也的方框。拿一个测试用例——
复制代码 代码如下:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>异步加载script</title>
<script src="https://www.jb51.net/js/test.js"></script>
</head>
<body>
<div>我是内容</div>
<img src="https://www.jb51.net/img/test.jpg">
</body>
</html>
其中,test.js中的内容——
复制代码 代码如下:
alert('我是head里面的脚本代码,执行这里的js之后,才开始进行body的内容渲染!');
我们会看到,alert是一个暂停点,此时,页面是空白的。但是要注意,此时整个页面已经加载完毕,如果body中包含某些src属性的标签(如上面的img标签),此时浏览器已经开始加载相关内容了。总之要注意——js引擎和渲染引擎的工作时机是互斥的(一些书上叫它为UI线程)。
因此,我们需要——那些负责让页面更好看、更好用的脚本应该立即加载,而那些可以待会儿再加载的脚本稍后再加载。
一、脚本延迟执行
现在越来越流行把脚本放在页面<body>标签的尾部。这样,一方面用户可以更快地看到页面,另一方面脚本可以直接操作已经加载完成的dom元素。对于大多数脚本而言,这次“搬家”是个巨大的进步。该页面模型如下——
复制代码 代码如下:
<!DOCTYPE html>
<html>
<head lang="en">
<!--metadata and scriptsheets go here-->
<script src="https://www.jb51.net/headScript.js"></script>
</head>
<body>
<!--content goes here-->
<script src="https://www.jb51.net/bodyScript.js"></script>
</body>
</html>
这确实大大加快了页面的渲染时间,但是注意一点,这可能让用户有机会在加载bodyScript之前与页面交互。源于浏览器在加载完整个文档之前无法加载这些脚本,这对那些通过慢速连接传送的大型文档来说会是一大瓶颈。
理想情况下,脚本的加载应该与文档的加载同时进行,并且不影响DOM的渲染。这样,一旦文档就绪就可以运行脚本,因为已经按照<script>标签的次序加载了相应脚本。
我们使用defer便能够完成这样的需求,即——
复制代码 代码如下:
<script src="https://www.jb51.net/deferredScript.js"></script>
添加defer属性相当于告诉浏览器:请马上开始加载这个脚本吧,但是,请等到文档就绪且此前所有具有defer属性的脚本都结束运行之后再运行它。
这样,在head标签里放入延迟脚本,技能带来脚本置于body标签时的所有好处,又能让大文档的加载速度大幅提升。此时的页面模式便是——
复制代码 代码如下:
<!DOCTYPE html>
<html>
<head lang="en">
<!--metadata and scriptsheets go here-->
<script src="https://www.jb51.net/headScript.js"></script>
<script src="https://www.jb51.net/deferredScript.js" defer></script>
</head>
<body>
<!--content goes here-->
</body>
</html>
但是并非所有的浏览器都支持defer(对于一些modern浏览器,如果声明defer,其内部脚本将不会执行document.write及DOM渲染操作。IE4+均支持defer属性)。这意味着,如果想确保自己的延迟脚本能在文档加载后运行,就必须将所有延迟脚本的代码都封装在诸如jQuery之$(document).ready之类的结构中。这是值得的,因为差不多97%的访客都能享受到并行加载的好处,同时另外3%的访客仍然能使用功能完整的JavaScript。
二、脚本的完全并行化