欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

controljs 三部分 有大用

shiping1 的头像

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

关于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]

关于第一部分的异步加载,这个的关键在于尽快将页面作为html绘制出来,然后再用javascript进行优化,或者说用js进一步渲染。我们看到过很多网页,展示给用户一片空白,只是因为在等待几百KB的js文件下载、解析、执行,而这些js是用来绘制页面中展示给用户看的DOM元素。

 

其实上述的情况,很容易理解–但是真正实现改进恐怕就困难的多。好几百KB的js必须要拆散重组。那些通过js来创建网页DOM的逻辑代码必须全部合成到后台的服务器端去生成html。即使使用最新的服务器端JS来实现,也是一项很大的重构工程。

我又回到了Opera的Delayed Script Execution选项。一旦这个选项生效,js会被搁置起来(move aside),让页面首先进行渲染。而这个选项确实很棒,而且没有网站因为开启了这个选项而导致页面报错的。我也一直持续在和其他浏览器的供应商联系,让他们也实现这个功能,我迫切的希望开发人员能够尽快的使用上这个选项。

近些年来,一些网页加速器(例如Aptimize, Strangeloop, FastSoft, CloudFlare, Torbit,和最近的mod_pagespeed)犹如雨后春笋般脱颖而出。他们修改了页面中的HTML标签,用来改善网页的性能。从这些模式中,我摸索出了一个方法,比起延迟加载页面渲染的JS,我们可以更容易的改变HTML标记来实现。

于是ControlJS诞生了!

Controlling download and execution

ControlJS的目标是让开发员更好的控制JS的加载。关键是要意识到“加载”氛围两个步骤:下载[download](即获取内容)和执行[execution](包含解析).为了更好的性能,这两个步骤有必要分离控制。

在对网页性能敏感的程序员眼中,如何控制脚本下载是一个很广泛流行的话题。当脚本使用普通的方式(也就是<script src=”" …的形式),脚本会阻塞其他资源的下载(在新版本浏览器中稍微好一些),同时也会阻塞页面的渲染(所有浏览器都有这种情况)。使用异步脚本加载(asynchronous script loading)的技术在很大程序上缓解了这个问题。我在Even Faster Web Sites中提及过几种异步加载的技术。LABjs 和 HeadJS是提供异步加载的js模块。虽然它们的异步技术解决了脚本下载阶段的阻塞问题,但是并没有解决脚本执行时发生的情况。

在脚本执行阶段,页面渲染和新资源下载会被阻塞。在如今网页中的脚本越来越多,在脚本执行阶段的阻塞问题就更为严重,尤其是在移动设备上。事实上,Gmail移动设备组考虑到脚本执行带来的问题,它们完善了new async technique that downloads JavaScript wrapped in comments。这项技术可以使得脚本执行和下载分离。脚本会下载到客户端(在浏览器的缓存中),但是因为它是脚本注释,所以在执行阶段不会阻塞(因为不会执行)。当用户使用了相关功能的时候,需要某些脚本时,会将注释符号删除,然后eval执行代码。

Stoyan Stefanov, 一名优秀的性能优化前驱者,最近发布了一篇博文preloading JavaScript without execution.他用IMAGE或者OBJECT(这取决于浏览器)来下载脚本。假设这些脚本可以缓存在客户端中,随后可以使用SCRIPT标签插入脚本。这个也是在ControlJS中使用的技术。

ControlJS: how to use it

使用ControlJS涉及到三个步骤的修改

修改点#1:添加control.js

我想关于脚本加载模块的使用,最讽刺的地方就在于,加载这些异步脚本加载种子文件是会阻塞页面的。所以从一开始我就必须确认ControlJS是要能被异步加载的。

var cjsscript = document.createElement('script');
cjsscript.src = "control.js";
var cjssib = document.getElementsByTagName('script')[0];
cjssib.parentNode.insertBefore(cjsscript, cjssib);

修改点#2:修改外链的脚本
下一步就是将所有外链脚本的标签改为ControlJS的形式。原本的脚本引用如下

<script type="text/javascript" src="main.js"><script>

SCRIPT元素的TYPE要改为”text/cjs”,SRC改为“data-cjssrc”,如下:

<script type="text/cjs" data-cjssrc="main.js"><script>

修改点#3 修改内联的脚本
大多数的页面都会有内联的脚本。这些脚本有一些依赖性:内联脚本中的变量一般依赖于外链的脚本,反之亦然。很重要的一点是:内敛脚本和外链脚本的执行顺序是需要保证的。因此,内联脚本必须改变TYPE属性的值

<script type="text/javascript">
var name = getName();
<script>

修改“text/javascript”为”text/cjs”,如下:

<script type="text/cjs">
var name = getName();
<script>

这样就ok了!后续的一系列工作就交给ControlJS担心吧!

ControlJS: how it works

现在脚本不会阻塞页面了,因为TYPE属性已经改为浏览器无法识别的值了。这也就让ControlJS可以用一种高性能的方法更好的控制脚本加载。让我们大概看看ControlJS是如何运作的呢?当然,你也可以查看control.js 的代码来更详细的了解。

我们都希望能够尽快的开始下载脚本。因为我们使用IMAGE或者OBJECT来下载脚本,所以它们不会在下载阶段阻塞页面。而且他们并不是作为SCRIPT下载的,所以它们也不会执行。ControlJS首先找出所有SCRIPT的,并且type为”text/cjs”的标签。如果脚本有一个DATA-CJSSRC,那么IMAGE(IE和Opera浏览器)或者OBJECT(其他浏览器)就会根据它们的URL动态的创建出来。(你可以查看Stoyan’s post去了解更详细的内容)。

一般来说,ControlJS会在window load的时间之后进入执行阶段(当然也可以立刻执行代码或者某个DOM元素加载完毕了)。ControlJS会第二次遍历所有的脚本,确保它们都正确的出现在页面中。如果这个脚本是一个内联脚本,那么它的代码会被执行。如果这个脚本是一个外链脚本,由IMAGE或者OBJECT动态下载下来的,那么它会作为一个SCRIPT标签插入到页面中,那么它的代码也会随之解析并且执行。如果IMAGE或者OBJECT并没有下载结束,那么会在一个短暂的时间间隔之后(a short timeout),重新进入刚才的遍历流程。

后续的文章中我还会讨论关于document.write的功能,以及跳过执行阶段。这篇中,我们首先来看一个简单的异步加载的例子。

Async example

为了能更好展示异步加载的情况,我创建了一个文件,文件顶部包含三个脚本:

  • main.js – 耗费4秒下载
  • 一段内联脚本会使用main.js的一个变量
  • page.js – 耗费2秒下载,使用内联脚本的一个变量

我让page.js的下载时间比main.js的下载时间短,是为了确认这些脚本可以以正确的顺序执行(尽管page.js下载更快)。我同事也包含了内联的脚本,因为这也是很多页面的实际情况(例如Google Analytics),但是很多脚本加载器并不支持内联脚本的执行顺序。

Async withOUT ControlJS是一个基本范例。它使用了最普通的方式加载脚本。可以看一下如下图所示的IE8下的HTTP瀑布图(使用HttpWatch).IE8比IE6和IE7要好 – 能够使main.js和page.js并行加载。但是所有的IE都会因为脚本加载而阻塞图片的下载。所以images1-4都延迟了。在所有浏览器中,页面渲染都会被阻塞。在图示中也可以看出,main.js阻塞渲染长达4秒钟(绿色的竖线表示页面开始渲染时间)

Async WITH ControlJS 演示了ControlJS是如何解决由于脚本引发的阻塞问题。不像上面的例子,脚本和图片会并行加载。同时页面渲染也是立刻开始的。如果你同时在浏览器中演示这两个页面,你会发现ControlJS的页面会快很多。当然,你也发现在下面的图示中多了3个请求,一个是Control.js – 这个是用来异步加载脚本用的代码。另外两个请求时因为main.js和page.js下载了两次。第一次是使用IMAGE或者OBJCET下载,第二次是使用SCRIPT标签加入页面用来执行代码的。因为main.js和page.js已经存在在浏览器缓存中了,所以他们不需要额外的下载时间。只是短暂的缓存读取时间(也就是那两根很细的蓝色线)。

The ControlJS project

ControlJS是在Apache License下面的开源项目。你可以在ControlJS Google Code project中找到control.js的代码。关于它的讨论可以在ControlJS Google Group.关于上面提到的例子放在我的站点 ControlJS Home Page.我只在大多数的浏览器中测试了,如果要投入到大规模的产品环境中,请进行更详尽的测试。

Only part1

这只是ControlJS的第一部分。目前看来似乎已经解决了所有问题了。如果我们只是需要异步加载,那么其他的一些技术其实也已经实现了。不过我的目标是不能让脚本阻塞页面的渲染。我们就必须面临一个问题,如果处理document.write.另一个方面就是类似于Gmail的移动组实现的- 下载脚本同时在执行阶段不会阻塞页面渲染。我们会在下面的两篇博文中详细分析。
来自 http://ued.ctrip.com/blog/?p=2964
 

原文: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

 

 

普通分类: