Skip to content

提高JavaScript性能

写在前:去了一个没有网的地方好多天,为了这次出行买了联通的3g上网卡加套餐,结果没有3g信号,杯具的3g。所以说广大童鞋们,用无线上网卡的时候一定要看当地有没有3g覆盖了。

------------------------------------------------------分割线----------------------------------------------------------

JavaScript是一种解释型的语言,执行速度肯定比编译型的语言要慢得多:

  • 比编译好的C慢5000倍* 比解释型的java慢100倍* 比解释型的perl慢10倍

而且JavaScript相比桌面应用程序只能访问较少的内存和CPU周期。虽然从2005年开始,浏览器在JavaScript执行性能方面做了很多工夫,但它还是比其它语言慢得多。不过,还是有改进代码的整体性能的方法的。

1、作用域上做文章

我们知道JavaScript的作用域以及作用域链的运作,随着作用域链中的作用域数量的增加,访问当前作用域外的变更的时间也是在增加的。因为访问全局变量需要遍历作用域链,所以要比访问局部变量要慢。也就是说,减少花费在遍历作用域链上的时间就能增加整体脚本的性能了。

function updateUI(){
    var imgs = documetn.getElementsByTagName("img");
    for (var i = 0,len = imgs.lengs; i < len; i++){
        imgs[i].title = document.title + " image " + i;
    }
    var msg = document.getElementById("msg");
    msg.innerHTML = "Update complete.";
}

function updateUI2(){
    var doc = document;
    var imgs = doc.getElementsByTagName("img");
    for (var i = 0,len = imgs.lengs; i < len; i++){
        imgs[i].title = document.title + " image " + i;
    }
    var msg = document.getElementById("msg");
    msg.innerHTML = "Update complete.";
}

上面的函数如果页面上有多个图片,那么for循环的document引用就会被执行多次,每次都会进行作用域链的查找。下面的函数通过 创建一个指向document对象的局部变量,就可以限制一次全局查找来改进函数的性能。

还有就是避免使用with语句。因为和函数类似,这个语句会创建自己的作用域,因此会增加其中执行的作用域链的长度。

2、选择正确的算法

性能问题一部分是和用于解决问题的算法是有关的,成熟的开发人员根据经验可以很好的选择算法获得更好的性能。

一般的数组访问以及简单的变量查找都是效率最高的(O(1)),当然了遍历整个数组中的元素的复杂度为O(n),访问对象上的属性也是O(n)的复杂度。所以说,一旦多次用到对象的属性,应该将其存储在局部变量中,用局部变量将属性查找替换为变量的查找,从而减少算法的复杂度。

再有就是优化循环:

循环是编程中是最为常见的结构,优化循环是性能优化中很重要的一个部分。

  1. 减值迭代:大多数循环使用一个从0开始、增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效。2. 简化终止条件:如前所说,属性查找或者其它O(n)的操作不应该出现在终止条件这个地方。3. 简化循环体:循环体是执行最多的,一定要确保没有某些可以被很容易移出循环的密集计算。4. 使用后测试循环:最常用的for循环和while循环都是前测试循环,而如do-while为后测试循环,可以避免最初终止条件的计算,因此运行更快。

当循环的次数是确定的,不用循环往往更快。如:数组有三个元素,直接对数组操作,展开循环可以消除建立循环和处理终止条件的额外开销。如果循环中的迭代数不能事先确定,那可以使用一种叫做Duff装置的技术。这个技术是以其创建者Tom Duff命名的,最早在C语言中使用这项技术。Jeff Greenberg用javascript实现了Duff装置。基本概念是通过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句。

//credit: Jeff Greengerg for JS implementation of Duff's Device
var iterations = Math.floor(values.length / 8 );
var startAt = values.length % 8 ;
var i = 0;

do {
    switch(startAt){
        case 0:process(values[i++]);
        case 7:process(values[i++]);
        case 6:process(values[i++]);
        case 5:process(values[i++]);
        case 4:process(values[i++]);
        case 3:process(values[i++]);
        case 2:process(values[i++]);
        case 1:process(values[i++]);
    }
    startAt = 0;
}while(-- iterations > 0 );

Duff装置的实现是通过将values数组中元素个数除以8来计算出循环需要进行多少次迭代,然后使用取整的下限函数确保结果是整数。当然,可能会有一些不能被处理到的元素,这个数量保存在startAt变量中,首次执行会对其进行额外的调用 。

由Andrew B. King在Speed Up Your Site(New Riders,2003)提出更快的Duff装置:

//credit :Speed Up Your Site (New Riders,2003)
var iterations = Math.floor(values.length /8 );
var leftover = value.length % 8;
var i=0;

if(leftover >0){
    do{
        process(values[i++]);
    }while(--leftover >0);
}
do {
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
}while(--iterations > 0);

这个方法几乎比原始的Duff装置实现快40%。

对于大数值量使用展开循环可以节省很多时间,小数据量,额外的开销则划不来了。

当使用eval()函数或者是Function构造函数以及使用setTimeout()传入字符串参数时都会出现双重解释。这些操作是不能在初始的解析过程中完成的,需要在代码运行的时候新启动一个解析器来解析新的代码。

eval("alert('hello')");
var sayHello = new Function("alert('hello')");
setTimeout("alert('hello')",500);

如非必须,应该尽量避免使用。

最后,应该尽量使用原生方法,如果有方法可以使用,就不要自己写一个方法了,因为这些方法是c/c++之类的编译语言写出来的;如果有一系列复杂的if-else语句,应该换成switch,这样比较快;运算的时候,如果可以使用位运算的,尽量使用位运算。

3、最小化语句语句

JavaScript代码中的语句数量也会影响所执行的操作的速度,所以可以组合在一起的语句,以减少脚本整体的执行时间。

var a = "a";
var b= "b";
var c="c";

var d="d",
    e="e",
    f="f";

声明变量时,使用单个var声明比多个var声明好。

var name = values[i];
i++;

//better
var name = values[i++];

可以合并的语句应该尽量合并。

var values = new Array();
value[0] = "a";
value[1] = "b";
value[2] = "c";

//better
var value = ["a","b","c"]

var person = new Object();
person.name = "welpher";
person.age = "25";
person.sayHello = function(){
    alert("hello");
};

//better
var person = {
    name : "welpher",
    age  : "25",
    sayHello:function(){
        alert("hello");
    }
};

使用数组和对象字面量,不过在ie6及更早的版本中使用字面量有微小的性能惩罚。

4、优化DOM交互

我们知道,DOM是最慢的一部分。DOM交互要消耗大量时间,因为它们往往要重新渲染整个页面或者某一部分。这部分后面有时间再详细研究。

PS:《JavaScript高级程序设计》学习笔记