High Performance Javascript读书笔记1

Nicholas C. Zakas大神今年3月协同他Yahoo团队成员出了一本《High Performance Javascript》的书,在这记录读书笔记

 1.优化javascript加载和执行

 

<html>
<head>
   
<title>Script Example</title>
   
<-- Example of inefficient script positioning -->
   
<script type="text/javascript" src="file1.js"></script>
   
<script type="text/javascript" src="file2.js"></script>
   
<script type="text/javascript" src="file3.js"></script>
   
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  
<p>Hello world!</p>
</body>
</html>

  为什么说这样的方式是inefficient?因为每个<script>标签都会阻塞页面的渲染直到js脚本被完全下载然后执行完毕。而页面的渲染是从浏览器解析到<body>标签开始,如果<script>放到body前面意味着页面的在js被完全下载,然后执行完毕前,页面将会出现空白(这个应该很多人深有体会);同时以前的浏览器再脚本的下载过程中都是同步的,意味着一个脚本下载后紧接着执行,执行完毕后后面的脚本才开始下载并执行,如果网速很慢的情况下,可以知道这样的用户体验有多糟糕。

   但是从Internet Explorer 8, Firefox 3.5, Safari 4, and Chrome 2起,这些浏览器都开始支持脚本的并行下载,因此多个脚本可以并发的进行下载,然而不幸的是对于页面上其他资源如图片的下载,脚本的下载中对其仍会阻塞;更重要的是,虽然下载的过程可以并发的执行,可是js代码的执行依然会互相阻塞而导致后面的js要等待前面的js执行完毕才能开始执行(因为javascript引擎是单线程

  为了尽量提高性能,我们应该进可能的将js代码放到body的尾部,如

 

<html>
<head>
   
<title>Script Example</title>
   
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  
<p>Hello world!</p>
   
<-- Example of recommended script positioning -->
   
<script type="text/javascript" src="file1.js"></script>
   
<script type="text/javascript" src="file2.js"></script>
   
<script type="text/javascript" src="file3.js"></script>
</body>
</html>

这也是YAHOO团队强烈推荐的准则之一:put scripts at the bottom

 

PS:Steve Souders has also found that an inline script placed after a <link>
tag referencing an external stylesheet caused the browser to block while
waiting for the stylesheet to download. This is done to ensure that the
inline script will have the most correct style information with which to
work. Souders recommends never putting an inline script after a
<link> tag for this reason.

 

  当引用的外部script标签很多的时候,我们应该尽可能的缩减这些js代码(比如将它们放到一起),因为每一个外部的js文件被引用的时候,浏览器都会发送http请求过去,而发送http请求是有额外的开销,下载一个100KB的js文件始终会被下载4个25KB的文件快。

  为了不阻塞浏览器页面的渲染,我们如果能在页面的window.onload事件后再来下载js和执行的话那是最好不过的了,对于IE和FIREFOX,script标签中有个defer属性是可用的,当浏览器解析到<script>标签的时候,浏览器会下载此文件,但是并不会立即执行,会推迟到window的onload事件之前才执行

  另外一种方式是动态添加执行脚本,如

  

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

这种方式的好处依然是可以实现异步的下载js文件,即js的下载不回阻塞进程,但是需要注意的是仅当脚本被添加到DOM中才开始执行下载但是如果是img元素,则指定了src属性就会立即下载

 

Firefox, Opera, Chrome, and Safari 3+ 都支持script的load事件,因此可以监听

var script = document.createElement("script")
script.type 
= "text/javascript";
//Firefox, Opera, Chrome, Safari 3+
script.onload = function(){
    alert(
"Script loaded!");
};
script.src 
= "file1.js";
document.getElementsByTagName(
"head")[0].appendChild(script);


而IE支持 readystatechange事件,并且有一个readyState属性在脚本下载的时候可以使用来进行判断,有5种状态

"uninitialized"
The default state
"loading"
Download has begun
"loaded"
Download has completed
"interactive"
Data is completely downloaded but isn’t fully available
"complete"
All data is ready to be used 

 即便如此,IE还是恶心的让你搞不清楚在不同的情况下loaded和complete哪个会先执行,哪个后执行,所以比较安全的方式是监听readystatechange事件,如果遇到这2个状态其中一个,再移除掉事件处理函数,如

var script = document.createElement("script")
script.type 
= "text/javascript";
//Internet Explorer
script.onreadystatechange = function(){
    
if (script.readyState == "loaded" || script.readyState == "complete"){
        script.onreadystatechange 
= null;
        alert(
"Script loaded.");
    }
};
script.src 
= "file1.js";
document.getElementsByTagName(
"head")[0].appendChild(script);


因此如果考虑浏览器兼容性的前提下,实现动态添加执行脚本的方式可以修改为

function loadScript(url, callback){
    
var script = document.createElement("script")
    script.type 
= "text/javascript";
    
if (script.readyState){  //IE
        script.onreadystatechange = function(){
            
if (script.readyState == "loaded" || script.readyState == "complete"){
                script.onreadystatechange 
= null;
                callback();
            }
        };
    } 
else {  //Others
        script.onload = function(){
            callback();
        };
    }
    script.src 
= url;
    document.getElementsByTagName(
"head")[0].appendChild(script);
}

需要注意的是:仅有firefox和opera能保证js执行的顺序和你指定的顺序相同,其他的浏览器根据从服务器返回的顺序执行

因此要保证顺序的话可以

loadScript("file1.js"function(){
    loadScript(
"file2.js"function(){
        loadScript(
"file3.js"function(){
            alert(
"All files are loaded!");
        });
    });
});

如果有很多js文件的话,这种方式仍然显得难以管理,实在是对顺序有要求的话最好还是尽量将多个js文件合并

 

 

 PS:Zakas大神说javascript单线程引擎是被javascript执行和页面渲染所共享的,并行下载JS并不意味着能并行执行,js仍然要先后依次执行,浏览器采取js并行下载的目的是为了解决高延迟的网络下的性能问题

  正常情况下一个阻塞的脚本意味着浏览器无法渲染直到脚本完成下面3个阶段 

  1. Completely downloaded
  2. Parsed
  3. Executed
很多情况下,阶段1才是最耗时间的,而2和3阶段对于如今的浏览器引擎来说影响不是最大的,使用动态添加脚本的意义在于当js脚本下载时,浏览器仍然可以渲染(下载是异步的),但是执行的时候会阻塞UI渲染进程的运行。

 

 

posted on 2010-08-12 22:34  MoonWalker  阅读(317)  评论(0编辑  收藏  举报

导航