高性能javascript

 

javascript脚本执行过程中会中断页面加载,直到脚本执行完毕,此操作阻塞了页面加载,造成性能问题。
 
脚本位置和加载顺序:
如果将脚本放在head内,那么再脚本执行完毕之前,显示给用户的始终是一片空白,用户只能傻傻的看着屏幕等待脚本执行完毕。
而且,如果页面引入多个脚本,那么后面的脚本文件必须等待前面的脚本文件下载完毕并且执行完毕之后才能开始下载并运行。不过IE8,FF,SAFARI,CHROME已经允许脚本文件可以同时下载,不过尽管如此,javascript脚本仍然会阻塞其他脚本下载进程,页面仍旧要等待所有javascript脚本下载并执行完毕之后才可以开始加载渲染。
因此,尽可能的将脚本文件放置在body标签的底部,以减少脚本阻塞对页面性能的影响,这也是Yahoo性能优化的第一条定律。
成组脚本加载:
我们知道较少HTTP请求数可以有效提高页面加载速度,泽卡斯说:“下载一个100KB的JS文件要比下载4个25KB的JS文件速度快”。毕竟有请求和响应的时间。所以我们可以将多个JS文件打包压缩成一个来提升性能。YUI通过CDN网络将客户端请求的多个JS文件在服务器端合并并压缩成一个返回给客户端,从而提高加载效率。例如:

1 <script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js "></script>

通过这个请求就可以把min.js和event-min.js两个文件在服务器端合并压缩成一个返回。
非阻塞脚本和延迟脚本(Deferred scripts):
虽然将多个javascript脚本合并并且将脚本放在body底部降低了HTTP请求数并且部分解决了阻塞问题。但如果脚本文件很大, 而且在每个脚本中都有功能函数运行,那么在脚本文件加载时,会占用浏览器很长一段时间,这段时间用户也只能傻傻的看着屏幕玩弄着没有任何反应的浏览器。为了避开这种情况,就出现了模块化加载和按需加载。
HTML4为<script>标签扩展了defer属性,设置过该属性的script脚本可以放在页面的任何位置,并且不会阻塞页面其他资源的下载,也就是说可以实现页面内容的并行加载。但下载完成后代码不会执行,只有等到DOM完全加载完毕后onload事件发生之前被执行。在《高性能JS》中,作者提到只有IE4和FF3.5的更高版本支持,不过在这篇文章中说webkit内核在HTML5也已经支持deffer和async。考虑到浏览器兼容性和更加强大和灵活的脚本控制,我们就需要引入按需加载。
我们可以通过动态创建script标记,更改其属性,并添加至head内,完成对script加载顺序、时间、依赖关系的控制。

1 var script= document.createElement("script");
2 script.type = "text/javascript";
3 script.src="file1.js";
4 document.getElementsByTagName("head")[0].appendChild(script);

这样做的好处在于:无论在什么地方加载file1.js,都不会阻塞和影响页面其他内容的加载,当然,会影响HTTP请求。一般情况下,通过动态节点下载脚本文件时,file1.js被加载完毕之后,往往会立即执行(除了FF和Opera,他们会等待此前的所有动态节点脚本执行完毕)。当脚本文件是“自执行(function(){})()”时,一切都很正常,但若只是一般函数命名定义或者只是提供了相关接口,就会有一些问题(至于什么问题,我没有验证,但估计是脚本加载程度的问题,书中只说在这种情况下需要跟踪脚本下载完成并准备妥善的情况.若你有相关资料或者见解,非常希望能给出,并给予指点。)。针对这一情况,《高性能JS》中给出了一些解决方法:FF,Chrome,Safari,Opera会在<script>节点脚本接收完成后发出一个load事件,IE会给出readystatechanage事件(script元素有一个readyState属性,它的值随文件的下载状态而改变,共有5中取值,不在一一列出,我们在这里使用loaded和complete来表示下载完成和所有数据已经准备好)。我们可以通过这两个事件判断脚本是否加载完毕,下面是文中提供的一个封装好的函数,兼容各主流浏览器:

1 function loadScript(url, callback){
2 var script = document.createElement("script")
3 script.type = "text/javascript";
4 if (script.readyState){//IE
5 script.onreadystatechange = function(){
6 if (script.readyState == "loaded" || script.readyState == "complete"){
7 script.onreadystatechange = null;
8 callback();
9 }
10 };
11 } else {//Others
12 script.onload = function(){
13 callback();
14 };
15 }
16 script.src = url;
17 document.getElementsByTagName("head")[0].appendChild(script);
18 }

两个参数@url:javascript文件URL、@callback:javascript接收完成时的回调函数,最后设置src属性,将<script>元素添加至页面。这里为什么不是先设置src,然后才判断script加载情况呢?如果先设置了,还判断干毛?
如果我们要按顺序和依赖关系加载多个javascript脚本文件,浏览器在此时并不保证文件执行顺序。所有浏览器中只有FF和Opera保证脚本按照下载顺序执行,其他浏览器将按照服务器返回的顺序加载运行。那么我们就需要运用上面的例子在回调函数中按顺序加载执行脚本。

1 loadScript("file1.js", function(){
2 loadScript("file2.js", function(){
3 loadScript("file3.js", function(){
4 alert("All files are loaded!");
5 });
6 });
7 });

不过这样明显很麻烦,而且在速度上也是个问题。还有一种非阻塞脚本的方法是XHR脚本注入,也是异步加载,不会影响页面其他内容的加载进程。
推荐的脚本依赖和加载模式:
在多个javascript脚本之间存在依赖关系时,必须将依赖性最小的放在最前面,将依赖性最大的放在最后面。若加上我们刚才的脚本阻塞和异步加载问题,下面给出可行性较高的解决方案:
第一步:先向页面中加入“动态引入脚本(就像上面的loadScript)”的函数或库文件,因为这部分代码可能很少,所以下载运行非常迅速,不会对页面性能造成很大干扰。
第二步:按需动态加载其他模块所需的脚本代码。
例如:

1 <script type="text/javascript" src="loader.js"></script>
2 <script type="text/javascript">
3 loadScript("the-rest.js", function(){
4 Application.init();
5 });
6 </script>

记得将此代码放在</body>标记之前。这样不仅可以保证脚本不会影响页面其他内容,而且也不需要用额外的window.onload事件做判断。我们甚至可以将loader.js的内容直接放在页面中,这样可以减少一次http请求。
可以参考和使用的案例:
YUI 3:
YUI 3的核心设计理念为:用一段很小的初始代码,下载其他的功能模块代码。
例子:
//引入YUI 3

<script type="text/javascript"src="http://yui.yahooapis.com/combo?3.0.0/build/yui/yui-min.js"></script>

//如果想使用dom功能,就可以给出此功能的名字,传递给use函数,并给出一个回调函数,当dom模块加载完毕后,就会执行回调函数中的内容,
回调函数中的参数Y代表了YUI实例,我们可以在回调函数中使用刚加载完成的dom模块中的功能。实际上,在加载dom模块之前,如果dom模块依赖其他为
加载的模块,当我们在use函数中指定过dom参数后,YUI会自动创建一个加载dom模块所需要的所有依赖模块,并创建一个“联合句柄URL”,(毛的联合
句柄URL,实际上就是把所需的脚本模块的url写在一个URL上,然后通过CDN合并压缩),然后按顺序下载和执行所需模块。

1 YUI().use("dom", function(Y){
2 Y.DOM.addClass(docment.body, "loaded");
3 });

LazyLoad:
Yahoo!Search的Ryan Grove 创建了LazyLoad库,精缩之后只有1.5K。
例子:

1 <script type="text/javascript" src="lazyload-min.js"></script>
2 <script type="text/javascript">
3 LazyLoad.js("the-rest.js", function(){
4 Application.init();
5 });
6 </script>

//若需要加载多个脚本文件,并保证执行顺序,可以这样:

1 <script type="text/javascript" src="lazyload-min.js"></script>
2 <script type="text/javascript">
3 LazyLoad.js(["first-file.js", "the-rest.js"], function(){
4 Application.init();
5 });
6 </script>

虽然非阻塞动态加载,但尽量减少文件数量,因为每一次下载仍是一个单独的HTTP请求,回调函数直到所有文件下载完毕之后才会执行。
 
SeaJS:(其实我觉得应该把这个放在最前面)
国内淘宝达人玉伯的SeaJs,据用过的人说比前面几种都要好用,但自己没有研究过,所以请童鞋们多多发表自己的见解,多多指教。。。
例子:

1 <script src="sea.js"></script>
2 <script>
3 seajs.use('./example', function(example) {
4 example.sayHello();
5 });
6 </script>

下面给出一些SeaJS的参考和使用资料:
 SeaJs首页:http://seajs.com/
 初识SeaJS:http://ghsky.com/2011/05/seajs-first-view.html
 使用SeaJS实现模块化开发:http://cnodejs.org/blog/?p=1203
 岁月如歌在javaeye上SeaJS1.0正式发布的博文:http://www.iteye.com/topic/1112630
 
LABjs:
Kyle Simpson写的开源库LABjs,精缩后4.5K,据说对并行下载和精确控制依赖关系更有针对性。

1 <script type="text/javascript" src="lab.js"></script>
2 <script type="text/javascript">
3 $LAB.script("the-rest.js")
4 .wait(function(){
5 Application.init();
6 });
7 </script>

//LAB支持链式操作,每个函数默认返回一个$LAB对象的引用,要加载多个脚本,可以这样:

1 <script type="text/javascript" src="lab.js"></script>
2 <script type="text/javascript">
3 $LAB.script("first-file.js")
4 .script("the-rest.js")
5 .wait(function(){
6 Application.init();
7 });
8 </script>

//如果想管理依赖关系,可以通过wait函数,这样:

1 <script type="text/javascript" src="lab.js"></script>
2 <script type="text/javascript">
3 $LAB.script("first-file.js").wait()
4 .script("the-rest.js")
5 .wait(function(){
6 Application.init();
7 });
8 </script>

此时,虽然文件是并行下载,但first-file.js一定会在the-rest.js之前执行。
 
RequireJS:
jrburke 的 RequireJS。
例子:

1 <script data-main="scripts/main" src="scripts/require.js"></script>
2 require(["helper/util"], function() {
3 //This function is called when scripts/helper/util.js is loaded.
4 });
5
6 //加载多个JS:
7 require(["helper/util","helper/util1","helper/util2","helper/util3"], function() {
8 //This function is called when scripts/helper/util.js is loaded.
9 });

高性能javascript 文件加载阻塞的更多相关文章

  1. Javascript文件加载:LABjs和RequireJS

    传统上,加载Javascript文件都是使用<script>标签. 就像下面这样: <script type="text/javascript" src=&quo ...

  2. js文件加载太慢,JavaScript文件加载加速

    原文出自:https://blog.csdn.net/seesun2012 js脚本加载太慢,JavaScript脚本加载加速(亲测有效) 测试背景: JS文件大小:6.1kB 传统形式加载js文件: ...

  3. JavaScript文件加载器LABjs API详解

    在<高性能JavaScript>一书中提到了LABjs这个用来加载JavaScript文件的类库,LABjs是Loading And Blocking JavaScript的缩写,顾名思义 ...

  4. 高性能JavaScript之加载和执行

    JS在浏览器中的性能,可以认为是开发者所面临的最重要的可行性问题.这个问题因JS的阻塞特性变得复杂,也就是说当浏览器在执行JS代码时,不能同时做其他任何事情.事实上,大多数浏览器都使用单一进程来处理U ...

  5. (转)高性能JavaScript:加载和运行(动态加载JS代码)

    浏览器是如何加载JS的 当浏览器遇到一个<script>标签时,浏览器首先根据标签src属性下载JavaScript代码,然后运行JavaScript代码,继而继续解析和翻译页面.如果需要 ...

  6. javascript文件加载模式与加载方法

    加载方式 形象图像化方法,见 http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html 1. script标签, ...

  7. 高性能JavaScript(加载和执行)

    当浏览器遇到 <script> 标签时,它是没办法知道 JavaScript 是否会向DOM中添加内容或引入其他元素,甚至关闭某一个标签.因此这个时候浏览器就会停止处理页面,先执行Java ...

  8. Javascript的加载

    最新博客站点:欢迎来访 1. 浏览器加载     (1) 同步加载 在网页中,浏览器加载js文件的方式是通过<script>标签.如下所示: //内嵌脚本 <script type= ...

  9. JavaScript动态加载js文件

    /********************************************************************* * JavaScript动态加载js文件 * 说明: * ...

随机推荐

  1. 用squid配置代理服务器(基于Ubuntu Server 12.04)

    怀揣着为中小企业量身定做一整套开源软件解决方案的梦想开始了一个网站的搭建.http://osssme.org/ 1. 安装squid $sudo apt-get install squid -y 注: ...

  2. [Java基础]List,Map集合总结

    java.util包下: Collection    |--List 接口 |----ArrayList |----LinkedList |----Vector |-----Stack |---Set ...

  3. Postman---html中get和post的区别和使用

    get和post的区别和使用 Html中post和get区别,是不是用get的方法用post都能办到? Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DEL ...

  4. python 高级语法

    #coding:utf-8 #定义一个装饰器函数 def doc_func(func): #包裹函数(闭包) def warpfunc(): #做一些额外的事情 print "%s call ...

  5. python list插入、拼接

    1可以使用"+"号完成操作 输出为: [1, 2, 3, 8, 'google', 'com'] 2.使用extend方法 . 输入相同 3使用切片 输出相同 PS:len(l1) ...

  6. C#Project不生成.vhost.exe和.pdb文件的方法

    编译C#工程时,在C#的Project的属性界面的Build选项卡中当Configuration : Relese 时,依然会生成扩展名为.vhost.exe和.pdb文件. 其中.pdb是debug ...

  7. 180508 - 解决有关VIVO的2018-04-01安全补丁导致的APP闪退问题

    解决有关VIVO的2018-04-01安全补丁导致的APP闪退问题 [√]问题原因猜测4: 最终解决方案 [√]问题原因猜测3: 尝试解决 [√成功] [×]问题原因猜测2: 尝试解决 [×失败] [ ...

  8. Ubuntu编绎 Objective C程序

    1.安装如下组件 sudo apt-get install如下组件:build-essential gobjc gobjc++ gnustep-devel 2.在工作目录建立下如Shell脚本,并更改 ...

  9. C# 一个长度为100的int数组,插入1-100的随机数,不能重复,如何写

    int[] intArr = new int[100]; ArrayList myList = new ArrayList(); Random rnd = new Random(); while (m ...

  10. 绕过云盾找真实IP-找真实IP-绕过CDN

    目标站:www.chinaparkview.cn 云盾挡着了 查一下历史IP 查出3月9号的IP是103.249.104.114 当然查出来的不一定准确 修改本地host文件 PS:不要加http 然 ...