原文: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的这个属性,就能够很好的避免大量脚本的解析和执行带来的页面阻塞。
来自 http://ued.ctrip.com/blog/?p=3017
原文:http://www.stevesouders.com/blog/2010/12/15/controljs-part-3/
关于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 part 2 (中文翻译:http://ued.ctrip.com/blog/?p=3017)中,我们展示了通过延缓脚本的执行使页面更快速的加载,尤其是Ajax的网络应用,下载了大量的脚本,而这些脚本不需要立刻使用到的。在本篇中,我们将讨论一下在脚本异步加载时,document.write引发的问题。
我很欣赏ControlJS,但是我不得不承认,目前为止我还没有找到很完美的解决document.write的问题。不过,我将描述一下在document.write这个问题上我做出的尝试,然后给出两种替代方案,可以解决异步加载时document.write引发的问题。
Async and document.write
ControlJS是异步加载脚本的。默认情况下,这些一步脚本会在window的onload事件之后再执行。如果这些异步脚本中有一个调用了document.write,那么页面就会被清空。因为在页面load之后再调用document.write会自动调用document.open事件(automatically calls document.open).任何write的内容都会替代目前页面中的内容,这当然不是我们期待的结果。
我希望ControlJS可以正常运行页面的所有脚本。广告的脚本基本都使用了document.write,但是我也发现,很多开发员也使用了document.write.我不希望这些脚本因为使用了ControlJS,不经意间就使页面乱掉了。
这个问题对我而言会相对简单一些,因为ControlJS在每一个内联和外链的脚本执行时都是有顺序的。我的解决方案是重写document.write,然后每次都捕获到每一个脚本的输出。如果当前脚本没有document.write的输出,那么就顺延到下一个脚本。如果有输出,那么我会创建一个元素(目前是SPAN), 插入到目前这个脚本的前面,将这个SPAN的innerHTML设置为document.write的输出。
这个方案相当不错。我把CNN.com的源代码拷贝到本地,然后转换成ControlJS的方案。页面中有两个document.write(包含广告), 这两个都能运行正常。但是仍然有一些问题。其中一个就是将输出写到SPAN中会丢失一些行为,例如hover的方法。不过,最大的问题是,浏览器会忽略用innerHTML写入的script脚本,而广告主要的方法之一就是插入scripts脚本。我有一个补丁的方法就是解析输出中的script标签,然后创建一个SCRIPT的dom节点。这样运行正常了,但是并不是非常的强壮。
ControlJS虽然对于document.write没有很优美的解决方案,但是它仍然很有价值。你并不需要将页面中每一个脚本都转成ControlJS的方式.如果你知道页面中有控件或者广告是使用document.write,那么你可以按照正常的方式加载他们。或者你们可以使用ControlJS来测试一下,是否可以正常运行。
Real solutions for async document.write
在结束关于ControlJS的系列文章之前,我需要提到两个解决异步加载时document.write的替代方案: Aptimize 和 GhostWriter.
Aptimize出售网站加速器,更新HTML的标记来提高网站性能。在11月,他们发布了可以使脚本异步加载的方案(包括广告)。这是我迄今为止了解到的第一个提供此特征的产品。我和Aptimize的En和Derek吃过午餐,了解到他们更详细的解决方案,那听上去很不错。
另一个解决方案是来自 Digital Fulcrum的GhostWriter。Will联系到我,我们有过一次电话会议,也有一些邮件的沟通,是关于他利用HTML的解析来解决document.write的问题。你可以免费尝试一下这个方案[ry the code ]
Wrapping it up
ControlJS有很多特点,有一些是其他脚本加载模块所没有的:
- 异步加载脚本
- 能够同时处理内联和外链脚本
- 在页面渲染结束之后再执行脚本
- 允许脚本下载但暂时先不执行
- 结合了HTML标记的属性修改-不需要修改代码
- 能够解决一些document.write引发的问题
- control.js本身是异步加载的
ControlJS是一个开源的。我希望其中的一些特点可以利用在其他的脚本加载器中。当然,它也仍然有一些功能需要完善(异步样式文件,更好的document.write的解决方案),并且ControlJS并没有经过大量的测试。如果你要增加功能或者修复bug,请看一下 ControlJS project on Google Code , 或者通过 ControlJS discussion list 来联系我们。
来自 http://ued.ctrip.com/blog/?p=3111