原文:http://www.stevesouders.com/blog/2010/12/15/controljs-part-2/

关于ControlJs一共有三篇文章,这是第二部分。ControlJS是让脚本加载更快的一个模块(a javascript module for making scripts load faster). 三篇文章的结构分别为:

1. async loading [中文翻译:http://ued.ctrip.com/blog/?p=2964]
2. delayed execution
[中文翻译:http://ued.ctrip.com/blog/?p=3017]

3. overriding document.write [中文翻译:http://ued.ctrip.com/blog/?p=3111]

ControlJS的目标是让开发者更好的控制脚本的加载。其中的关键是意识到”loading”有两个步骤:下载[download](获取内容)和执行[execution](包括解析).在ControlJS part 1(中文翻译:http://ued.ctrip.com/blog/?p=2964)中,我主要阐述了如何用ControlJS来异步加载脚本。在本篇中,我将讲一讲ControlJS在脚本执行阶段[execution]能做些什么?

The issue with execution

执行阶段的问题关键是:浏览器在解析和执行脚本的时候,它其他什么都干不了。通常的表现就是浏览器的界面无响应,页面渲染被阻止,浏览器中新资源的下载被终止。

执行阶段花费的时间可能比你想象中要长。我无法获取这个阶段具体消耗了多少时间(当然你也可以用dynaTrace Ajax Edition or Speed Tracer来收集这些数据)。如果你进行了大量密集的DOM操作,很容易呈现这种阻塞的情况。如果页面中有大量的脚本,仅仅解析的时间就会消耗很长的时间了。

当然,如果所有的脚本都是必须立即执行来渲染页面的,那么我们能做的也只有停止操作,等待这些脚本执行完毕。不过,久而久之,我们会发现大部分下载的脚本并不是需要立刻执行的。通过Alexa US Top 10 ,你会发现在window load之前,仅有29%的脚本需要被调用。(我用Page Speed‘s的”defer Javascript”的功能来做的调查)。余下的71%的代码是什么情况呢?虽然它们对页面初始渲染没有任何帮助,但是它们仍然被解析并且执行了。在我调查的页面中,平均每个页面下载229kB的脚本。这229kB的是压缩的- 压缩前应该会超过500KB了。比起在页面下载的时候执行那71%的脚本,更好的办法是在页面渲染完毕之后再去执行那些脚本。但是开发者怎么做到呢?

Loading feature code

为了让我们接下去的讨论更简单一些,我们把那29%需要去渲染页面的代码称为”immediate-code”(IC),余下的71%称为”future-code”(FC).FC一般来说是DHTML或者Ajax的功能,例如下拉菜单、弹出对话框、好友邀请……这些代码只有用户操作了某些功能之后才能触发的。

假设你已经成功的将页面的脚本分成了IC和FC.IC的那部分就可以使用async loading capabilities在页面初始化的时候下载。FC的那部分呢,在页面下载的时候也同样的方式下载。然后浏览器会将那些不需要立即使用的代码的解析和执行先锁住。我们不希望在页面初始化的时候就下载那71%的脚本。

当然,还有一个办法,那就是在页面渲染完毕之后,再去下载那些FC. 这些脚本可以在onload的回调函数中进行后台加载。不过即便这些FC在后台加载,在他们解析和执行的时候,浏览器仍然会被“冻住”。如果这个时候用户尝试操作页面,将得不到任何响应。这些没必要的延迟会带来很多麻烦,所以Gmail的移动小组有了他们自己的一套解决办法Gmail mobile team’s way

道理很简单,也就是在用户需要的时候,再去解析和执行future-code。举个例子,当用户首次点击了下拉菜单,那么我们就去解析和执行menu.js。如果用户从来没有用过下拉菜单,那么我们就免去menu.js的不必要的解析执行的开销。不过当用户点击了那个菜单,我们并不希望他们还要等待menu.js的下载-尤其是在手机上。那最好的解决方案就是在后台下载脚本,但是直到用户真正需要的时候再去执行它。

Download now, Execute later

我们接下去看一下ControlJS在这方面能做些什么。我列出了一下的几个例子

为了能够更形象的体现那71%的脚本加载带来的痛苦,我把menu的代码延迟了2秒钟(一个循环来搞定)。Menu withOUT ControlJS 是一个基本的例子,这个页面耗费了很长的时间来渲染,因为不但在下载脚本的时候被阻塞,在执行那2秒的脚本时也被阻塞了。

Menu WITH ControlJS 相比较而言渲染就快速了很多。首先脚本时异步加载的, 而且menu的脚本是一个可选的脚本,于是我们将它的执行延后了。很简单,使用”data-cjsexec=false”的属性就可以了,当然同时也要使用controlJS设置脚本的其他两个属性:type和src.

<script data-cjsexec=false type="text/cjs" data-cjssrc="jquery.min.js"></script>
<script data-cjsexec=false type="text/cjs" data-cjssrc="fg.menu.js"></script>

其中”data-cjsexec”的设置表明脚本已经被下载并且放在了缓存中,但是他们没有执行。当用户操作某些功能时才触发了相对应的脚本的执行。在这个例子中,点击exmaples的按钮就是那个触发器。

examplesbtn.onclick = function() {
   CJS.execScript("jquery.min.js");
   CJS.execScript("fg.menu.js", createExamplesMenu);
};

CJS.execScript()创建了一个SCRIPT的元素,有特定的SRC,并且插入了DOM中。menu的creation函数-createExamplesMenu,是作为onload的回调函数的最后一个脚本传入的。所以那个2秒的延迟,会在用户第一次点击菜单的时候发生,但是脚本的下载不会有阻塞,同时这种延迟也只有一次。

这种在脚本加载时分离下载阶段和执行阶段的能力,是ControlJS的一个重要的特点,也是区别其他模块的一个特性。当然,也许很多网站并不需要这个属性。但是如果网站的代码有很多的代码,而且这些代码并不是在页面初始化的时候就需要的,例如Ajax的网络应用,使用ControlJS的这个属性,就能够很好的避免大量脚本的解析和执行带来的页面阻塞。

 

本文作者:admin 转载请注明来自:携程设计委员会