随着网页越做越复杂,页面上要用到的 JavaScript 也越来越多,一次性把所有可能会用到的 js 全包含在页面中显然不是一个好主意,于是各种各样动态按需加载 js 的方法逐渐流行起来,LABjs 就是这样一个有趣的项目,目前,包括 twitter 在内的很多网站都是 LABjs 的用户。下面将对 LABjs 1.0.4 版的实现原理做一些分析。
限于篇幅,关于 LABjs 的使用说明这儿就不写了,需要的话请看它的官方文档。
所谓动态加载 js ,指的是在页面上的某一些 js 执行时,由这些 js 动态加载外部的 js (有时候也包含执行页面上已经定义的一些模块函数)。
粗略整理,加载外部 js 的方法大致有这么几种:
XHR Eval | 通过 Ajax 方式获取代码,并通过 eval 方式执行代码。 |
XHR Injection | 通过 Ajax 方式获取代码,并在页面上创建一个 script 元素,将 Ajax 取得的代码注入。 |
Script in Iframe | 通过 iframe 加载 js。 |
Script DOM Element | 使用 JavaScript 动态创建 script DOM 元素并设置其 src 属性。 |
Script Defer/Async | 严格来说,这一条不算是动态加载外部脚本的方法,但很多动态加载外部脚本的方法里都会用到 sctipt 的 defer 或 async 属性,所以也把它单独列在这儿。这个方法利用 script 的 defer 属性,让脚本“推迟”执行,不阻塞页面加载,或者设置 async 属性,让脚本异步执行。遗憾的是这两个属性不是所有浏览器都支持。 |
document.write Script Tag | 通过 document.write 把 HTML 标签 script 写入到页面中。 |
cache trick | 先使用自定义的 script 的 type 属性(如 <script type=”text/cache” …),甚至使用 Image、Object 等 HTML 对象将 js “预下载”(下载到浏览器缓存里),等真正需要执行对应代码时再将它真正地插入页面中。 |
Web Worker | 部分浏览器支持 web worker 功能,可以创建一个 worker 在后台工作,包括加载外部脚本。 |
各种方式各有优缺点,比如能否跨域、是否会阻塞其它资源的下载(能否并行下载)、能否管理控制执行顺序、耗费的资源、是否兼容各大浏览器等(部分方法的特性可参考这儿)。事实上,如果仅仅只是想把外部 js 动态加载到页面上的话还是很简单的,但如果可能要同时加载多个 js ,希望它们能尽可能快地下载(并行下载),并且有时候可能希望它们能保证执行顺序,而且要兼容各大主流浏览器,就会有一点复杂了。为了实现这样的目标,LABjs 使用了上面方法中的三种,分别是:Script DOM Element、cache trick、XHR Injection。下面我们先详细看一下这三种加载 js 方式的优缺点。
Script DOM Element
这是最常用的方式,它的优点很多:可以跨域、可以加载任何格式的外部 js(不需要对外部 js 进行重构)、不会阻塞其它资源的下载、实现简单。并且,在 Firefox/Opera 下,通过这种方式插入多个 js 脚本,浏览器会并行下载这些 js (同时下载几个取决于浏览器的并行连接数),同时还能保证它们的执行顺序与它们被插入页面的顺序相同。不过,在 IE(以及 Safari/Chrome)下,如果用这种方式同时插入多个 js,这些 js 也会并行下载,但浏览器不能保证这些 js 的执行顺序,哪个先下载完浏览器就会先执行哪个。
从上面可以看到,Script DOM Element这种方式在 Firefox/Opera 下的表现近乎完美,事实上,在这些浏览器下,Script DOM Element 也是 LABjs 默认使用的方式。
cache trick
上面我们看到,Script DOM Element方式可以满足 Firefox/Opera 下的需求了,那么 IE/Safari/Chrome 下怎么办呢?LABjs 中使用了一种 cache trick 的手段,它创建一个 script 标签,并将其 type 设为 “text/cache” ,把它插入页面。这种 trick 似乎是 LABjs 的作者 @getify 首先提出来的。这个 “text/cache” 只是为了语义上有意义,你完全可以把 type 属性设为任何不为 “text/javascript” 的值,比如 “love/oldj” 等。
根据 LABjs 作者博客上的文章,在 IE/Safari/Chrome 这三个浏览器下,如果一个 script 元素的 type 属性为一个类似 “text/cache” 这样的浏览器不认识的值,浏览器仍然会正常下载这些 js ,并且下载完成后正常触发 onload 事件,但是它们将不会执行这些脚本。于是,通过这样的方式可以先将 script 加载到浏览器缓存中,等对应的 js 需要被执行时,再创建一个新的 script 元素,设置其 type 为正确的值,src 为刚才“预下载”的脚本的值,将其插入页面,这样,这个 script 将会从缓存(而不是网络)瞬间加载对应的 js,并立即执行。通过这样的方式,LABjs 在 IE/Safari/Chrome 等浏览器下实现了脚本的预加载以及执行顺序管理。
另外,”text/cache” 这种 trick 在 Firefox/Opera 下是不能工作的,因为这两种浏览器会拒绝下载它们不认识的 type 的 script,这样也就无法“预加载”了。不过这不会造成问题,因为这两种浏览器可以直接通过上面的 Script DOM Element 的方式来加载外部脚本。(Firefox 4 曾试图更改相关特性,结果导致 getify 的强烈不满,具体事件可看这里。)同时,这种方法需要浏览器支持并且开启缓存,如果浏览器禁用或不支持缓存,也就无法“预加载”了,而且更糟糕的是,几乎没有 js 方法能检查用户浏览器是否支持并开启了缓存。
XHR Injection
cache trick 方式虽然可以实现并行下载、管理执行顺序,但毕竟是一种很依赖于特定浏览器特定版本的特性的方法,如果万一哪一天某个浏览器某个新版本改变了它的特性,这种方式可能就失效了,作者本人也觉得它很丑。所以,如果 LABjs 检测到要下载的外部 js 与当前页面是同域并且浏览器不为 Firefox/Opera(不能保证执行顺序与插入顺序一致)的话,它会优先采用 XHR Injection 的方式。
XHR Injection 的原理很简单,就不多说了。下面我们来看一看有了这三种方式后, LABjs 是如何实现的。
LABjs 的内部逻辑比较复杂,变量也比较多。不过简单来看,LABjs 的结构主要如下图所示。
可以看到,LABjs 对外公开的 API 一共有 4 个方法,分别为 setGlobalDefaults() 方法,setOptions() 方法,script() 方法以及 wait() 方法。其中最常用的是 script() 以及 wait()。LABjs 的内部变量中,最重要的是一个叫 engine 的对象,它负责 script() 以及 wait() 方法的具体实现以及内部变量的维护。
engine 对象主要有这么几个方法:script()、wait()、loadScript() 以及 waitFunc(),另外还有两个主要的属性:queueExec 及 exec。下面简单解释一下这几个方法及属性的作用。
script() 公共 API 中的 script() 事实上是生成一个 engine 对象,再调用这个 engine 中的 sctipt() 方法。这个方法根据 queueExec 的值,调用 loadScript() 方法对传入的 js 地址进行加载或预加载操作。
wait() 公共 API 中的 wait() 事实上是生成一个 engine 对象,再调用这个 engine 中的 wait() 方法。
loadScript() 加载 js 的方法。根据具体浏览器情况,以及是否同域,这个方法会调用上面提到的三种方式之一来加载 js,或者预加载 js ,或者将预加载完成的 js 最终加载到页面中。
waitFunc() 每一个 engine 内部有一个 ready 变量,默认为 false,如果这个 engine 要加载的 js 都已加载完成了,则 ready 为 true,同时执行 waitFunc(),这个 waitFunc() 里一般包含你想要在指定 js 加载完成后执行的函数,接下来则是触发下一个 engine(如果有的话)。
queueExec 这是一个布尔值属性,用于标明当前 engine 加载 js 时是否需要预加载。默认状态下,queueExec为 false,表示直接加载指定的 js,当使用了 wait() 方法后,queueExec 在新生成的 engine 里的值为 true,表示这个 engine 中的 js 先预加载。
exec 这是一个数组,其中的元素都是待执行的函数。预加载 js 时,loadScript() 会立即执行以便预加载指定 js,同时同样的 loadScript() 也会被 push 一份到当前 engine 对象的 exec 数组中,并在当前 engine 的waitFunc() 执行时被依次调用,loadScript() 这次执行时则会将刚才预加载的 js 真正加载到页面上。
整个流程大致如下:
上面这个流程图只是一个简化版(比如有关浏览器判断,以及选择哪种方式下载 js 的部分全部省略了),LABjs 内部的完整的逻辑要更加复杂。
图中的 ready 表示当前 engine 中引入的 js 是否都已加载完成(LABjs 中有一个同名的内部变量)。可以看到,一开始执行 $LAB.script 时,LABjs 会生成一个 engine 对象,在此之后的链式调用的所有的 script() 方法实际上调用的都是这个 engine 内部的 script() 方法,直到链条上出现 .wait() 为止。wait() 方法将生成一个新的engine,之后所有通过的 script() 方法引入的 js 都将先被预加载,等上一个 engine 完成时才被触发正式加载。
关于 LABjs 的实现原理就介绍到这儿了。这篇帖子写了很久,在电脑里及脑子里反复修改了很多次,一直觉得说得不够清楚而不敢放上来,不过貌似现在这个版本依然写得很复杂,要写好技术文章果然不是一件容易的事,呵呵。
LABjs 中用到了很多技巧,比如用 setTimeout(fn, 0) 的方式把一个耗时的操作“丢”到另一个“线程”上去。如果有兴趣,不妨花一些时间阅读一下它的源代码。与其它动态加载外部 js 的解决方案相比,LABjs 是我见过的最小巧同时最强大的一个,它的最大特点是能尽可能并行地下载待加载的 js ,但需要时又能保证 js 的执行顺序。其它 js loader 也有很多,但它们默认情况下要么不支持并行下载,要么虽然支持并行下载但在部分浏览器下不能保证 js 的执行顺序,从这个角度来看,LABjs 是它们中最出色的。但其它解决方案也各有特色,比如RequireJS 等,与 LABjs 相比,它不仅能加载外部 js ,还能动态地定义、添加 js 模块,在某些应用场景下,它可能更合适或更为强大。
另外,LABjs 也有一些不足,比如一些 trick 方法过于信赖特定浏览器特定版本的非标准(或标准中没有定义)的特性,但这些特性是无法通过 js 来检测的,只能通过判断浏览器版本信息以及以往的经验来假设它具有或不具有某种特性。这种方式比较丑陋,而且是不安全的,如果某一天某个浏览器的新版本改变了相关的特性,js 代码将完全无法知道,于是相关的功能或 trick 就失效了。最好的办法是能直接通过检测浏览器特性来完成相关的判断,但谁让有些特性真的难以取到呢?而且,更好的方式,大概是一起推动更合理的标准,让将来某一天在新的浏览器下能非常轻松自然地实现 js loader,让 LABjs 等解决方案彻底地退出前端开发的历史舞台,或许这才是前端工程师们最美好的未来。
相关推荐
LABjs,全称为"Load And Balance JavaScript",是一款强大的JavaScript库,专为了解决Web页面中的脚本阻塞问题而设计。在传统的HTML文档中,脚本通常按顺序执行,这意味着一个脚本的加载和执行会阻塞后续脚本的加载...
LABjs,全称为Load And Block JavaScript,是一款前端JavaScript库,主要功能是优化页面中的脚本加载,提升网页性能。它的核心理念是异步加载,旨在解决浏览器在处理多个JavaScript文件时可能出现的阻塞问题,从而...
在提供的"LABjs-master--js有序加载.rar"压缩包中,可能包含以下内容: 1. LAB.js的核心库文件(如`lab.min.js`或`lab.js`):这是实现脚本有序加载的关键文件,包含了LAB.js的全部功能。 2. 示例代码或HTML文件:...
前端项目-labjs,labjs(加载和阻塞javascript)是一个由getify解决方案支持的开源(mit许可证)项目。LabJS的核心目的是成为一个通用的、按需的JavaScript加载程序,能够随时从任何位置将任何javascript资源加载到...
LABjs 是一个很小的 JavaScript 工具,用来根据需要加载 JavaScript 文件,通过使用该工具可以提升页面的性能,避免加载不需用到的 JavaScript 文件,可以实现动态并行加载脚本文件,以及管理加载脚本文件的执行顺序...
一、LABjs 的核心是 LAB(Loading and Blocking):Loading 指异步并行加载,Blocking 是指同步等待执行。LABjs 通过优雅的语法(script 和 wait)实现了这两大特性,核心价值是性能优化。LABjs 是一个文件加载器。 ...
见run_script.sh对于如何启动一个实例RScript过程,在这种情况下, sqlite_to_csv.R从labjs分布。 建立 您可以构建一个名为r.sif的本地奇点图像,但这需要root: sudo singularity build r.sif r.def 或者,在...
本文将对几款常用的JavaScript(JS)延迟加载框架进行对比测试,包括Controljs、LABjs、Sea.js、In.js以及Do.js。 #### Controljs Controljs是一种模块化脚本加载器,允许开发者以声明的方式加载依赖项。通过`...
更像是LABjs requireJS的结合增强版,但打包源文件只有7kb左右; 已知兼容性 IE 5.5 (添加ieload插件,若不添加则IE10 ) Opera 10 Chrome 8 Firefox 3.6 Safari 5 其中Chrome、Firefox...
目前流行的几个加载器,像 curljs、LABjs 和 RequireJS 使用都很广泛。他们功能强大的,但有些情况下可以有更简单的方案。 如果你正在使用 jQuery,有一个内置的方法可以用来加载脚本。如果你想延迟加载插件或任何...
网站我们新的GitHub存储库关键的未来简单易用的API,例如Labjs。 支持localStorage / Web SQL数据库/ IndexedDB。 通过nginx进行实时差异计算。 通过webpack构建差异计算。装载机参考< script src =" ./patchjs-...
国外的像基于jQuery的RequireJs,YUI Loader,LabJs,RunJs,国内也有淘宝的SeaJs,豆瓣的DoJs等,这些都是一些十分优秀的模块加载器。但是本文将会向大家介绍一个新的开源的轻量级“多线程”异步模块加载器In.js,...
比如,LABjs库被推荐用于解决JavaScript文件的加载顺序和执行的问题,并且它支持通过JavaScript来控制脚本加载的时机,从而提高页面的性能。 总结来说,为了提高JavaScript的性能,可以采取以下策略: 1. 将所有的...
在看LABjs源代码时,发现里面有个将相对地址转为绝对地址的函数,将其拿出纪录如下: 代码如下: function canonical_uri(src, base_path) { var root_page = /^[^?#]*\//.exec(location.href)[0], root_...
lab.js它能够动态并行加载脚本文件 以及 管理加载脚本文件的执行顺序
- **现代**:动态并行加载、模块依赖管理、自动化压缩打包技术成熟,如LabJS、YUI3/Kissy和Closure Library等工具应运而生。 其中,**Kissy框架**的模块化实践尤为突出。Kissy借鉴了YUI3的模块化设计理念,采用`S....
- 对于需要兼容老旧浏览器的情况,可以使用脚本加载器如yepnope或labjs等来实现异步加载。 #### 八、总结 前端编码规范的制定和实施对于保证项目的长期成功至关重要。通过遵循这些规范,不仅可以提高代码质量,还...
Yslow是一款由Yahoo开发的网页性能分析工具,它根据Yahoo的网页性能最佳实践为你的网站打分。本文将详细探讨如何通过优化策略在Yslow中获得A级评分,并对网站进行全面的性能调优。 首先,了解Yslow的评分标准至关...