本文深入解析LABjs库如何优化页面性能,通过动态加载机制避免冗余脚本。详细对比Script Element、XHR Injection及Cache Trick三种加载技巧,分析其在不同浏览器中的行为差异。结合伪代码与实例,阐述LABjs如何根据浏览器特性智能选择预加载策略,确保脚本并行下载与顺序执行的平衡。
LABjs 概述与核心价值
LABjs 是一个轻量级的 JavaScript 工具库,旨在实现脚本文件的按需动态加载。使用该工具能够显著提升页面加载性能,避免加载不必要的 JavaScript 文件。它支持脚本文件的动态并行加载,并能精确管理脚本的执行顺序。
简单示例
$LAB
.script("script1.js", "script2.js", "script3.js")
.block(function(){
// wait for all to load, then do something
script1Func();
script2Func();
script3Func();
});
介绍下LABJS的几个实例: 实例1:
$LAB
.script("script1.js")
.script("script2.js")
.script("script3.js")
.wait(function(){ // 等待所有script加载完再执行这个代码块
script1Func();
script2Func();
script3Func();
});
实例 2:
$LAB
.script({ src: "script1.js", type: "text/javascript" })
.script("script2.js")
.script("script3.js")
.wait(function(){ // 等待所有script加载完再执行这个代码块
script1Func();
script2Func();
script3Func();
});
实例 3:
$LAB
.script("script1.js", "script2.js", "script3.js")
.wait(function(){ // 等待所有script加载完再执行这个代码块
script1Func();
script2Func();
script3Func();
});
实例 4:
$LAB
.script( [ "script1.js", "script2.js" ], "script3.js")
.wait(function(){ // 等待所有script加载完再执行这个代码块
script1Func();
script2Func();
script3Func();
});
实例 5:
$LAB
.script("script1.js").wait() // 空的wait()只是确保script1在其他代码之前被执行
.script("script2.js") // script2 和 script3 依赖于 script1
.script("script3.js").wait() // 但是script2 和 script3 并不互相依赖,可以并行下载
.script("script4.js") // script4 依赖于 script1, script2 及 script3
.wait(function(){script4Func();});
实例 6:
$LAB
.script("script1.js") // script1, script2, and script3 之间没有依赖关系,
.script("script2.js") // 所以可以任意顺序执行
.script("script3.js")
.wait(function(){ // 如果需要,这里当然可以执行javascript函数
alert("Scripts 1-3 are loaded!");
})
.script("script4.js") // 依赖于 script1, script2 及 script3
.wait(function(){script4Func();});
实例 7:
$LAB
.setOptions({AlwaysPreserveOrder:true}) // 设置每个脚本之间等待
.script("script1.js") // script1, script2, script3, script4 互相依赖
.script("script2.js") // 并且并行下载后循序执行
.script("script3.js")
.script("script4.js")
.wait(function(){script4Func();});
实例 8:
$LAB
.script(function(){
// `_is_IE`的值ie为true ,非ie为false
if (_is_IE) {
return "ie.js"; // 如果是ie则这个js会被加载
}
else {
return null; //如果不是ie这个代码就会被略过
}
})
.script("script1.js")
.wait();
LABjs 动态加载机制解析
LABjs 加载方式
LABjs 中的动态加载脚本文件,指的是在页面 JavaScript 脚本执行过程中,通过多种技术手段加载外部 JS 文件。这主要区别于 HTML 页面中通过 <script> 标签进行的静态加载方式。
动态加载脚本的方式多种多样,各有优劣,此处不再赘述,有兴趣的读者可参考本文末尾的链接。
LABjs 主要运用了三种加载技巧,分别是 Script Element、XHR Injection 以及 Cache Trick。
首先对这三种加载方式进行简要介绍,随后在第四部分分析 LABjs 源码中针对不同场景的具体应用。
Script Element 加载方式
Script Element(LABjs 默认采用的加载方式)
这是最常见的脚本动态加载方式,具有诸多优点,包括:1、实现简单;2、支持跨域;3、不会阻塞其他资源的加载等。
在 Opera 和旧版 Firefox 浏览器中,脚本执行的顺序与节点被插入页面的顺序一致。
在 IE、Safari 和 Chrome 浏览器中,执行顺序无法得到保证。
注意:
在新版 Firefox 浏览器中,脚本执行的顺序与插入页面的顺序不一定一致,但可以通过将 script 标签的 async 属性设置为 false 来保证顺序执行。
在旧版 Chrome 浏览器中,脚本执行的顺序与插入页面的顺序不一定一致,但可以通过将 script 标签的 async 属性设置为 false 来保证顺序执行。
XHR Injection 与 Cache Trick
XHR Injection 通过 AJAX 请求加载脚本文件,然后通过以下方式执行: eval:常见方式 XHR injection:创建一个 script 元素,并将请求加载的脚本文件的内容注入。 主要限制:无法跨域。 Cache Trick(强依赖于浏览器的特性实现,不推荐使用) 当你将 script 元素的 type 属性设置为浏览器不认识的值,比如 “text/cache”、“text/casper”、“text/hellworld” 等,不同浏览器的行为如下: IE、Safari 和旧版 Chrome 浏览器中:脚本照常加载,但不会执行。假设浏览器没有禁用缓存,加载后的脚本会被浏览器缓存起来。当需要用到它的时候,只需要重新创建一个 script 标签,将 type 设为正确的值,src 指向之前请求的文件 URL 即可(相当于从缓存里读文件)。 Opera 和 Firefox:不加载。 备注: 强依赖于浏览器的特性实现,有可能随着浏览器特性实现的改变而失效,不推荐使用。 新版 Chrome 浏览器中,将 script 元素的 type 设置为非 “text/javascript”,不会再对脚本文件进行加载。
LABjs 加载策略判断逻辑
LABjs 里关于脚本加载采用方案的判断
忽略技术细节,通过一段伪代码来描述 LABjs 里面的实现,大致为: 首先判断是否对请求的脚本进行预加载(是否进行预加载的判断条件看伪代码注释); 如进行预加载,再判断浏览器是否支持真正的预加载;如支持真正的预加载,则预加载之;如否,判断请求的脚本是否跟当前页面同域,如同域,采用 XHR Injection,如不同域,采用 Cache Trick; 如不进行预加载,判断浏览器支不支持 script 元素的 async 属性(见伪代码注释),如是,设置 async 属性,并请求脚本文件;如否,直接通过 script 元素加载脚本文件;
if(ifPreloadScript){ //当请求的脚本文件是否进行预加载:1、需要预加载 2、浏览器支持预加载
if(supportRealPreloading){ //如果支持真正的预加载
if(supportPreloadPropNatively){ //支持通过设置script标签的preload属性,实现script的预加载,以及分离加载和执行
//Nicholas C. Zakas大神的美好愿望,尚未有浏览器支持:/blog/2011/02/14/separating-javascript-download-and-execution/
script.onpreload = callback;
script.newPreload = true;
script.src = targetUrl;
}else{
script.onreadystatechange = callback; //其实就是指IE浏览器,假设指定了script元素的src属性,IE浏览器里会立即加载
script.src = targetUrl; //即使script元素没有被插入页面,callback为预加载后的回调
}
}
else if(inSameDomain){ //非跨域,采用XHR Injection:请求的脚本与当前页面处于同一个域
xhr = new XMLHttpRequest(); //由于上个判断已经将IE无情地抛弃在这个条件分支之外,所以大胆地用 new XMLHttpRequest()吧
xhr.onreadystatechange = callback;
xhr.open("GET",targetUrl);
xhr.send();
}
else{ //最无奈的后招,Cache Trick,新版chromei已经不支持
script.onload = callback;
script.type = 'text/cache';
script.src = targetUrl;
}
}else{
if(canContrlExecutionOrderByAsync){ //如果能够通过script元素的async属性来强制并行加载的脚本顺序执行
//kyle大神着力推进的提案,目前已被html5小组接受并放入草案:/Dynamic_Script_Execution_Order#My_Solution
script.onload = callback;
script.async = false; //将script元素的async设为false,可以保证script的执行顺序与请求顺序保持一致
script.src = targetUrl;
}
else{
script.onload = callback;
script.src = targetUrl;
}
}
实际上,当你在页面创建一个 img 节点,并将其 src 指向一个脚本文件,在部分浏览器里同样能够起到文件预加载的作用,那么 LABjs 的作者是不是没有想到这一点呢?