关于脚本载入器的思考
原文:http://www.nczonline.net/blog/2010/12/21/thoughts-on-script-loaders/
==
上周,Steve Sounder 发布了 ControlJS 项目,目的是让开发者可以更好地控制 JavaScript 的载入和执行。实现上借鉴了 Stoyan Stefanov 关于预载入但不执行的方法,同时开启平行下载还带来了一些愉快的”副作用”。更多详情见 Steve 的三篇文章。
第一篇日志的评论中有来自 Kyle Simpson 的批评 (Kyle 是另一个脚本载入器 LABjs 的创建者)。LABjs 目标和 ControlJS 有些不同:开启 JavaScript 文件的平行下载,同时管理执行顺序。为了要做到这点,LABjs 需要知道哪些浏览器支持平行下载,然后为不支持的浏览器提供其他解决方案。
然 而,LABjs 和 ControlJS 都有个问题:他们用了多种浏览器探测技术来决定如何脚本载入的方式。有些人觉得 LABjs 用的浏览器接口(探测)比 ControlJS 的 user-agent 探测安全点,我不同意。浏览器接口(探测)其实是特征探测加假设,天生就有缺陷(更甚)。 浏览器接口(探测)不比 user-agent 探测准确,也并非更稳定。我没有说 user-agent 探测是个好东西,但至少他明确地知道要探测什么。我总是选择清晰的而非含糊的,因为这可以让我们减少错误,或者当错误发生时,可以更快地定位。这个讨论有 点偏离本文的主题了。
LABjs 其实已经证明了,“为不同浏览器使用不同的脚本加载技术”不是个好办法。他在面对浏览器的突然更新时不堪一击,所以我不建议使用尝试征服浏览器的脚本载入器。Kyle 曾由于 Firefox 4 每夜版更新后破坏了 LABjs 的实现,面临了严重的问题。 这个问题是:Firefox 4 动态插入脚本元素不再保持执行顺序,而这正式 LABjs 的实现所依赖的。这个变更使 Firefox 和 HTML5 规范及其他浏览器保持一致。毫无疑问,面对浏览器的高速发展,ControlJS 也将会面临同样的问题。对应解决方案的维护成本就变得很高。
真正的问题
有些人在讨论 LABjs 和 ControlJS 尝试解决的真正问题。事实上,他们面临着三个问题。
第一,两者都尝试实现 JavaScript 的平行下载。这很有意义,但很多新的浏览器已经自己处理了。虽然可以通过变通的方式让老浏览器也实现 JavaScript 的平行下载,在学术上很有价值,但我不认为值得去做。浏览器正在为我们解决这个问题,脚本载入器不要做了。
第 二,LABjs 专注于保持脚本执行的顺序。我们要先假设:你希望下载多个 JavaScript 文件,且他们之间有依赖关系。我并不推荐这样做,但其他有些人觉得这很重要。ControlJS 并不关心这个。但不管怎样,这是个没有被浏览器合理解决的问题,如果你要用他,就必须用脚本载入器。
第三,ControlJS 非常关注 JavaScript 下载和执行的分离。为实现这个想法,需要可以下载 JavaScript 文件但不执行他,直到想执行时再执行。这是个很有趣的概念,且在社区里做了很多实现(Steve 在他的博文中提到)。但这需假设你的页面是渐进增强的,比如并不一定要有 JavaScript 。LABjs 没有处理这个问题,浏览器也没有。
战斗的号角
尽 管 Kyle 和我在很多事情上都有不同的看法,但他对问题 #2 通用解决方案的呼吁我非常认同:我们需要的不是脚本载入器,而是实现所有开发者处理 JavaScript 文件所需要的原生方法。脚本载入器展示了解决性能问题的一些方法,下一步应该由浏览器开发商来实现。Kyle 整理了问题 #2 相关一些问题的测试和提案 (注意:问题 #3 还没有提案)。当时 Kyle 也问了我对此的看法,但我被一些项目绊住,直到现在也没时间去深入研究。
async=false?
Kyle 的提案里有关于 script 标签 async 属性的增强,有点奇怪。async 属性是个布尔值属性,意味着只要他出现就开启这个功能,所以下面三行是等价的:
<script async src="foo.js"></script> <script async="true" src="foo.js"></script> <script async="false" src="foo.js"></script>
这和 HTML5 规范表现一致:马上开始下载且在下载完成时立即执行,不考虑原来的顺序。你可以在 JavaScript 中通过设置 script 元素的 async 属性来开启或关闭这个特细功能:
var script = document.screateElement("script"); script.async = true; // enable async per HTML
Kyle 的提案中还提到,在 JavaScript 中设置 script 元素的 async 属性会触发新的模式。所以下面这行代码的意义就变了:
var script = document.screateElement("script"); script.async = false; // switch into new mode (WebKit nightly, Firefox 4)
以前,设置 async 为 false 没什么效果。而现在,在支持他的浏览器里设置 async 为 false 会让脚本通过非阻塞的方式下载且保持执行顺序不变。
当我为 Kyle 推送提案的坚持鼓掌时,也感到了一些不妥。对于我来说,这行代码读起来是 “此脚本非异步”,而不是 “此脚本是异步的,但需要保持执行顺序”。多说一边,我喜欢清晰的而非含糊的,这可以帮助我们避免错误。
他的 twiki 里还提到另一个备选提案,创建 scriptgroup 元素从逻辑上让脚本文件捆在一起:
<scriptGroup id="group1" ordered="true"> <script src="foo.js"></script> <script src="bar.js"></script> <script> somethingInline(); </script> </scriptGroup>
我非常喜欢这个提案。非常清晰,对他能做什么没有任何疑问,且可以添加事件处理器到 元素,监听全部文件下载完成。虽然引入了一个新元素,但为了思路上的清晰,我想这个开销会被开发者接受的。
分离下载和执行
分离 JavaScript 的下载和执行非常必要,但目前还没有一个非常好的方案。不仅仅是针对页面载入时初始脚本,还有页面载入完成后动态地插入的脚本。在我的演讲《Performance on the YAHOO! Homepage》中,我介绍了页面载入后如何缓缓执行 JavaScript ,而不影响用户进行其他操作。预载入 JavaScript 而不执行的的能力正变得越来越重要,而这正是 ControlJS 尝试解决的。
理想的情况下,我期望能这样写:
var script = document.createElement("script"); script.type = "text/cache"; script.src = "foo.js"; script.onload = function(){ //script has been loaded but not executed }; document.body.insertBefore(script, document.body.firstChild); //at some point later script.execute();
这已经是我所有想要的了,不想先发请求去下载一个文件,然后再发另一个请求并期望他在缓存里 —— 这是非常不可靠的解决方案。我所想要的是下载一个文件,就让他待在缓存里,然后只要调一个方法就可以执行代码。而这正是 ControlJS 的模型。
最后
LABjs 和 ControlJS 都在尝试解决 JavaScript 载入的问题,方法略有不同。Kyle 和 Steve 都非常聪明,研究了不同的方案来解决类似的问题。好消息是现在我们有两个脚本载入器,他们展示了开发者尝试在页面里载入脚本的不同方法,并希望浏览器开发 商能实现原生的解决方案,这样在不远的将来就再不需要脚本载入器了。
而短期来说,Kyle 和 Steve 两位,非常抱歉,两者我都不推荐使用。两个类库都展示了脚本载入不同的有趣的方式,但依赖于浏览器探测就意味着需要不停监控,且当浏览器更新时跟进更新。 对于大型的 web 应用来说可维护性非常重要,而两个类库看似都增加了额外的维护开销。
我知道这是最近的热门话题,所以请在评论时保持冷静。
本文出自 传播、沟通、分享,转载时请注明出处及相应链接。
本文永久链接: https://www.nickdd.cn/?p=1305