优化脚本文件的加载提升页面的加载速度,一直是前端工程师提升页面加载速度很重要的一条。由于涉javascript
及到各个浏览器对解析脚本文件的不一样机制,以及加载脚本会阻塞其余资源和文件的加载。当浏览器解析器遇到<script>时,会当即加载(加载:下载,解析和执行),浏览器对其余资源和文档的加载会中止。为了提升页面的加载速度,得让JS不阻塞其余资源的加载。css
Webkit 和 Firefox 对JS的执行过程进行了优化,增长了“预解析”这个过程,“预解析”过程不会修改DOM树,因此能够跟其余解析过程并行,该过程由预解析器去完成,而可能会改变DOM树执行过程则由主解析器来完成,在经过解析过程了解JavaScript文章中有提到的JS的“预解析”过程,此过程应该就是由浏览器的预解析器完成,预解析器还负责解析样式表和图片。html
另外一方面,浏览器同事请求http的数量也是有必定限制的,加载js不像加载样式那样是并行的。样式表是构建呈现树的一部分,浏览器在解析页面结构是由DOM树和呈现树两部分组成,而解析执行样式表只会改变样式表不会更改DOM树,呈现树跟DOM树虽然是相对应的,但并不是一一对应。所以,也就没有必要中止对其余资源和文档的加载了。前端
提升页面加载速度的最简单快速的方法就是将脚本文件放到body底部。但这并非提升页面加载速度最优方案的方案,接下来咱们介绍其余方案。html5
首先来介绍一下<script>时能让脚本延迟和异步执行的两个属性:defer和async。java
defer是html4.0中定义的,该属性使得浏览器能延迟脚本的执行,等文档完成解析完成后会按照他们在文档出现顺序再去下载解析。也就是说defer属性的<script>就相似于将<script>放在body的效果。ajax
async是HTML5新增的属性,IE10和浏览器都是支持该属性的。该属性的做用是让脚本能异步加载,也就是说当浏览器遇到async属性的<script>时浏览器加载css同样是异步加载的。跨域
支持async属性的浏览器貌似没什么问题,可是defer属性在各个浏览器中支持程度有点不一样。测试代码以下浏览器
<script type="text/javascript" defer> alert('defer')</script><script type="text/javascript"> alert('script')</script><script type="text/javascript"> window.onload = function(){ alert('onload') }</script>defer测试代码,可将代码复制到本地本身测试,外部脚本src引入,内联脚本直接粘帖
运行以上代码,得出如下结论:网络
外部JS在各个浏览器里运行结果跟定义的执行顺序正常,alert信息会按照 script->defer->onload顺序弹出;
内联脚本,若是脚本都是IE9/8/7/6按照定义的顺序弹出信息,其余浏览器则按照 defer->script->onload 顺序弹出信息,表示defer失效。
而若是有多个内联defer脚本、在body和head都有分布或者在iframe中也有内联defer脚本,则在IE6中表现一致。
若是想给脚本增长defer属性让其延迟加载的话,最好是外部脚本,内联的defer不只多数浏览器不支持,并且IE6的表现也不一致。
因此将脚本放在body底部比给脚本增长defer属性让脚本延迟加载更好,就像yslow建议的那样:put style top,put script bottom。
浏览器的在遇到defer和async属性的<script>的浏览器执行过程以下(如下摘自javascript权威指南):
WEB浏览器建立Document对象,而且开始解析WEB页面,解析HTML元素和它们的文本内容后添加Element对象和Text节点到文档中。这个过程的readystate的属性值是“loading”
当HTML解析器遇到没有async和defer属性的<script>时,它把这些元素添加到文档中,而后执行行内或外部脚本。这些脚本会同步执行,而且在脚本下载(若是须要)和执行解析器会暂停。这样脚本就能够用document.write()来把文本插入到输入流中。解析器恢复时这些文本会成为文档的一部分。同步脚本常常单定义函数和注册后面使用的注册事件处理程序,但它们能够遍历和操做文档树,由于在它们执行时已经存在了。这样同步脚本能够看到他本身的<script>元素和它们以前的文档内容
当解析器遇到了设置async属性的<script>元素时,它开始下载脚本,并继续解析文档。脚本会在它下载完成后尽快执行,可是解析器没有停下来等他下载。异步脚本禁止document.write()方法。它们能够看到本身的<script>元素和它以前的全部文档元素,而且可能或干脆不可能访问其余的文档内容。
当文档完成解析,document.readyState属性变成“interactive”。
全部有defer属性的脚本,会被它们在文档的里的出现顺序执行。异步脚本可能也会在这个时间执行。延迟脚本能访问完整的文档树,禁止使用document.write()方法。
浏览器在Document对象上触发DOMContentLoaded事件。这标志着程序执行从同步脚本执行阶段转到异步事件驱动阶段。但要注意,这时可能还有异步脚本没有执行完成。
这时,文档已经彻底解析完成,可是浏览器可能还在等待其余内容载入,如图片。当全部这些内容完成载入时,而且全部异步脚本完成载入和执行,document.readyState属性变为“complete”,WEB浏览器出发Window对象上的load事件。
今后刻起,会调用异步事件,以异步响应用户输入事件,网络事件,计算器过时等。
了解浏览器在遇到async、defer属性的脚本执行顺序,咱们能够利用这两个属性来改善JS的阻塞问题,使用这两个属性会有几种可能的状况:
defer为true:延迟加载脚本,在文档完成解析完成开始执行,而且在DOMContentLoaded事件以前执行完成。
async为true:异步加载脚本,下载完毕后再执行,在window的load事件以前执行完成
利用这两个属性异步加载js,还得了解它们的毛病:
使用defer属性,最好是外部的script
使用defer、async的脚本禁止使用document.write()方法
当脚本尝试访问的样式属性可能还没有加载的样式表时,浏览器会禁止该脚本等待样式表加载完成,这等于样式表阻塞了脚本的执行。因此使用defer、async的脚本最好不要请求样式信息时。
不论是使用defer仍是async属性,都须要首先将页面中的js文件进行整理,各个脚本文件之间的依赖性,哪些文件能够延迟加载等等,作好js代码的合并和拆分,而后再根据页面须要合理的使用这两个属性。defer属性声明这个脚本中将不会有 document.write 或 dom 修改。
当全部脚本解析完成后,JavaScript进入第二个阶段,这个阶段的是异步的,而且由事件驱动的。在事件驱动阶段,WEB浏览器调用事件处理程序函数,来响应异步发生的事件。调用事件处理函数一般是用户输入,网络活动,运行和JavaScript中的错误来触发。
经过注册事件处理程序函数来处理程序,注册的事件在发生时异步调用这些函数,setTimeout()和setInterval()也都是异步的。因此页面内容中有内联script放在setTimeout()执行是异步JS的一种方法,固然将代码程序放在DOMReady内执行也是异步加载的方法。二者都将代码执行阶段放在了事件驱动阶段。
在dom中建立的script标签在浏览器中则是异步,以下:
function delay_js(src){ var objScript = document.createElement('script'); objScript.setAttribute('src', src); objScript.setAttribute('type', 'text/javascript'); document.body.appendChild(objScript); return objScript;}异步加载JS
以上代码异步加载的JS下载是跟其余同样是并行的,可是执行阶段仍是会阻止页面渲染,延长了window.onload的事件。怎么样才能下载和执行JS都不阻塞页面的渲染呢,以下:
function loadjs(src, succ) { var elem = delay_js(src); if ((navigator.userAgent.indexOf('MSIE') == -1) ? false: true) { elem.onreadystatechange = function() { if (/loaded|complete/.test(this.readyState)){ succ() } }; }else{ elem.onload = function(){ succ(); } } elem.onerror = function() {};}JS异步下载+执行方案
代码分析:
非IE浏览器能捕捉到script的 script.onload 事件,因此只能借助script.onreadystatechange.
检测onreadystatechange状态中,IE7/8最后一个状态就只是loaded,而IE6中最后一个状态可能 complete 也多是loaded,因此用正则loaded|complete两个状态都检测。
异步加载JS的问题是没法使用 document.write 输出文档内容,由于根本没法肯定 document.write 应该输出到什么位置,但仍是能够在DOMReady以后执行操做dom
除了DOMContentLoaded 与 OnLoad 事件、async属性以及defer属性script能解决JS异步加载外,还有其余方法能够异步加载JS:
经过ajax获取js内容,而后eval执行。
var xhrObj = getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState != 4 ) return; eval(xhrObj.responseText); }; xhrObj.open('GET', 'A.js', true); xhrObj.send('');
经过建立iframe:建立并插入iframe元素。
var iframe = document.createElement('iframe');document.body.appendChild(iframe);var doc = iframe.contentWindow.document;doc.open().write('<body onload="insertJS()">');doc.close();
此方法存在跨域问题,若是父页面域名修改,则经过javascript协议执行一样域名升级语句。
页内 js 的内容被注释不会执行,可是在须要的时候去掉注释,eval执行js,
在页面中document.write Script Tag
用 setTimeout 延迟0秒 与 其它方法组合