脚本的加载执行及优化

分类:Javascript

Javascript及UI Render的单线程

讨论之前,首先需要理解的是js的单线程机制,即在同一时刻js只能执行一个任务,另外实际上浏览器的渲染也和js共用同一线程,这就意味着js在处理代码时浏览器会停止解析,如图所示:

js single thread

当遇到js代码时js引擎会解析执行代码,此时浏览器渲染停止,直到脚本解析完毕才继续。当脚本执行耗时过长,尤其当引入外部脚本时加上脚本的加载时间,对ui渲染的阻塞更为严重,这是性能优化的一个方面,也是这篇文章为什么讨论脚本加载执行顺序的主要原因。

两种嵌入方式

解析嵌入(Parser-inserted)

指在html源代码中通过<script src=”XXX”>等方式引入(术语上这类脚本成为 Parser-inserted script),这种方式下浏览器会以<script>标签在页面中出现的顺序加载和执行,即同步加载执行。正如前面讨论的,这种方式会阻塞页面其他元素的加载,及页面渲染。

脚本嵌入(script-inserted)

通过脚本动态载入方式(术语上这类脚本成为Parser-inserted script),动态载入脚本的方式有很多中如:document.write,xhr eval,script document Element等[1],其中通过动态创建script标签是被广泛应用推荐的一种,这里只讨论这种情况。

通过动态创建script元素可以实现无阻塞加载,但这意味着什么?一个脚本阻塞是从开始加载到执行完毕才结束,大体有三个步骤:

  1. 加载脚本
  2. 解析
  3. 执行

在这个过程中通常#1耗时最长,通过这种方式意味着不必等待脚本的#1(也许还有#2)过程,浏览器ui线程可以不受干扰继续执行,但是由于js单线程机制对于#3过程无可避免的会对ui线程阻塞。当然,浏览器解析通常很快的(尤其是新的js引擎),通常这部分影响几乎察觉的到,但是如果脚本性能不好,执行需要较长时间这也会产成性能瓶颈。

总之,无阻塞加载使浏览器不必等待脚本加载而继续渲染,两者异步执行,但脚本执行仍会阻塞渲染。

并行加载

根据上面的无阻塞方式加载多个脚本文件,这些文件是并行加载的,这样相对传统方式带来的好处是,使浏览器更快更早的请求文件并且不会阻塞浏览器渲染,但是这里仅仅是并行“加载”,并不意味着会并行执行,还是那个原因js是单线程的,同一时间只有一个js会被执行!另外带来的一个问题是,脚本并行加载其执行顺序又是怎样的呢?

对于脚本的执行顺序,目前各个浏览器执行情况还有些差异:

1.opera and firefox3.6-

执行的顺序会按照脚本被动态插入的顺序执行

2.ie,Safari,chorme and firefox4[2]

脚本加载完毕即会执行,不会保持他们被插入的顺序

浏览器的努力:defer & async

对于无阻塞脚本加载,浏览器也给出了一定的方案,包括早期ie实现(后来被firefox3.1引入)的defer属性以及HTML5定义的async属性

defer

脚本下载不会阻塞浏览器渲染,直到文档解析完成时才会执行,DOMContentLoaded触发之前(兼容性不好)。

async

这个属性由html5新引入的标准属性,这是个布尔属性,当被定义时,脚本会无阻塞加载,脚本文件加载完成立即执行(不顾及脚本出现的先后顺序),如果需要脚本保持引入的先后顺序需要显示的设置.async = false(对于script-created 脚本默认为true,目前仅firefox4支持false)。

开发者的努力:LABjs

labjs项目主要特性能够实现脚本并行加载和顺序执行,已经被多个大型网站采用,看个例子:

   $LAB
   .script("a1.js").wait()
   .script("a2.js")
   .script("a3.js").wait()
   .script("a4.js");

这样,a1a2a3a4脚本会被并行加载,但是执行顺序还是会按照a1->a2a3||a3a2->a4的顺序执行。由于前面提到的对于动态创建script元素,不同浏览器两种不同的执行顺序,其实现进行了browser sniff:opera and old firefox直接动态插入即可保证执行顺序;对于IE chrome sarfari方面通过一个trick: 预加载script文件(通过设置script type为不能识别的值,保证浏览器只加载不执行),然后按照需要的顺序插入正确的script元素,这些元素就会立刻执行并且保持顺序:

LABjs 并行下载的实现

当然,还有许多其他项目的实现如 requireJS 的order pluiginHeadJSControlJS

总结

由于javascript及ui render的单线程机制,采用无阻塞加载脚本来提高页面性能,同时看到浏览器及开发者对于这方面的努力及实现,但请注意无阻塞加载仅仅是“加载”而已,执行过程仍然会阻塞渲染,进一步的优化需要我们回归到代码本身,减少其执行时间,以获得更快的响应。

注:

  1. Steve提出了各种实现方式 loading scripts without blocking
  2. firefox4 pre7之前仍是按照原来的方式顺序执行,为了符合html5的规范改为无序执行,这里还有labjs的作者和火狐开发者的一个故事,最终firefox4通过设置async=false来保持这种执行顺序。

参考:

Update:

  1. 添加defer脚本执行的确切时间 2011-05-24
发表于:2011/05/22 06:05 | 196 views | 3条讨论

共有3条评论,发表一条新评论>>

  1. …..Over the past couple of years a lot of attention has been paid to blocking and JavaScript in browsers and with good reason. JavaScript can both block both the rendering of a web page as well as causing the page to become unresponsive.

  2. 从今天开始,做一个幸福的人/喂马,劈柴/顶顶博文。

  3. jaclon says:

    之前在用语法高亮的js时,使用动态加载js,但老是出现变量示定义的错误,就是每一个文件还没有加载完,第二个文件引用 了第一个文件的内容,出现了错误

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

回顶部