有阻塞的脚本
js脚本阻塞原因?
大多数浏览器使用单一进程处理用户界面更新(页面解析,页面渲染,用户交互)和JavaScript脚本代码执行。所以同一时刻只能做其中一件事。这样做是显而易见的,因为脚本代码很可能包含了dom处理的代码。
反应到代码上就是,当script标签出现时,无论代码是内嵌还是通过外链加载,都需要等待js代码加载(内部代码就直接读取)并执行完成,才能继续解析渲染页面。
注释:外部脚步有两个例外:
- 如果 async="async":脚本加载完成可用后,会相对于页面解析异步地执行(当页面继续进行解析时,脚本将被执行)
- 如果 defer ="defer":脚本将在页面完成解析时执行
- 如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即加载(内部代码就直接读取)并执行脚本
脚本位置
通常的做法如下图:把script标签放在head标签内。由于脚本的阻塞特性,直到所有脚本加载并执行后,页面解析才会继续。
浏览器在解析到body标签之前是不会渲染页面的 任何部分。因此在处理script标签的那段时间用户只能看到空白页面,严重影响页面性能与交互。
目前IE8+的主流浏览器都支持并行下载JavaScript文件,因此script标签在加载外部资源时,不会影响其他script标签,但是脚本阻塞问题还在,因为脚本未解析完成,页面渲染还是不能进行。
由于脚本阻塞特性,因此推荐将所有的script标签尽可能放到body标签底部,来减少对整个页面下载的影响。
组织脚本
每个script标签初始下载时都会阻塞页面渲染,因此减少script标签可以改善这个状况。不仅适用于外链脚本,内嵌脚本同样适用。
可以把多个外部脚本文件合并,来减少请求次数。
link标签之后紧跟内嵌脚本时,为确保脚本执行时获取精确样式,浏览器会先加载解析link标签。因此不要在link标签后紧跟内嵌代码。
无阻塞的脚本
1.延迟脚本
async 和 defer 标注的 script 都不会暂停HTML解析,立刻下载资源,两者都支持onload事件回调来解决需要该脚本来执行的初始化。
两者的区别在于执行时的不同:
async 脚本在script文件下载完成后会立即执行,并且其执行时间一定在 window的load事件触发之前。这意味着多个async脚本很可能不会按其在页面中的出现次序顺序执行。
浏览器确保多个 defer 脚本按其在HTML页面中的出现顺序依次执行,且执行时机为DOM解析完成后,document的DOMContentLoaded 事件触发之前。
注:内嵌script的defer属性在IE4-8有用,其他浏览器会忽略,加载外部文件的script的defer属性主流浏览器都有用。
2.动态脚本元素
动态创建一个script元素,添加到head元素中。指定的文件在该元素被添加到页面开始下载。无论何时启动下载都不会阻塞页面其他进程。下载完成后会立即执行。实例代码如下:
3.XMLHttpRequest脚本注入
该技术关键是异步请求JavaScript文件,通过动态创建script元素将代码注入到页面。一旦注入,代码会立即执行。
主要优点是加载的代码什么时候执行可以灵活控制。
主要缺点是请求的JavaScript文件必须是出于同一个域。意味着不能从CDN下载js文件。
推荐非阻塞加载方式
在</body>标签前先加载最基础的代码模块,比如:require.js这种js模块化开发的基础库。
然后动态添加各种页面初始化代码、逻辑代码。
总结:
- 浏览器使用单一进程处理用户界面更新(页面解析,页面渲染,用户交互)和JavaScript脚本代码执行。
- 将所有的script标签尽可能放到body标签底部,来减少对整个页面下载的影响。
- 把多个外部脚本文件合并,来减少请求次数。
- 延迟脚本,用defer和async属性。
- 动态创建一个script元素,添加到head元素中。
- 异步请求JavaScript文件,通过动态创建script元素将代码注入到页面。