编写高效JavaScript

       当今的Web应用程序都是由大量的JavaScript代码驱动。每次用户在界面上操作时,都要执行成千上万行的Js代码。因此,关注性能,不仅要关注页面的加载时间,也要关注在页面上操作时的响应速度。要实现快速而友好的用户界面,最好的办法就是编写的JavaScript代码能在浏览器中高效的运行。

       在编写代码的过程中,如果多注意一下自己的代码质量,就可以得到更流畅的用户体验。身为一名前端开发攻城狮,一手漂亮的代码也是成长进阶的必备技能。下面是看书的几点总结:

    1.使用局部变量

         到目前为止,局部变量是JavaScript中读写最快的标识符。因为它存在于执行函数的活动对象中(上一篇文章已经进行总结),解析标识符只需要查找作用域链中的单个对象。读取变量值的总耗时随着查找作用域链的逐层深入而不断增长,所以标识符越深读取速度越慢。在编写代码时,能用局部变量的就尽量使用局部变量,不但可以提升代码执行速度,还可以避免全局变量的污染,一举两得。

    2.高效的存取数据

数据在脚本中存储的位置直接影响脚本执行的总耗时。一般而言,在脚本中有4种地方可以存取数据:

  • 字面量值;

  • 变量;

  • 数组元素;

  • 对象属性;

在大多数浏览器中,从字面量中读取值和从局部变量中读取值的开销差异很小,以至于可以忽略;真正的差异在于从数组或对象中读取数据。存储这些数据结构中的某个值,需要通过索引(对于数组)或属性值(对于对象)来查询数据存储的位置。请看下面代码:

function process(data){
    if(data.count > 0){
        for(var i = 0; i< data.count ; i++){
            processData(data.item[i]);
        }
    }
    
}

这段代码多次读取data.count,如果将值存储到局部变量中,函数将运行的更快:

function process(data){
    Var count = data.count;
    if(count > 0){
	for(var i = 0; i < count ; i++){
	    processData(data.item[i]);
        }
    }
}

改写后的执行过程中读取data.count仅有一次。由于减少了查找对象属性的次数,函数的执行效率将比之前的更高(当然还可以进一步优化,将在下面进行说明)。随着数据结构的增加,它对数据存储速度的影响也跟着变大,例如,存取data.count比data.item.count快,存储data.item.count比data.item.subitem.count快。在处理属性时,点符号(.)的使用次数直接影响存取该属性的总耗时。

        在处理HTMLCollection对象(比如getElementsByTagName这样的DOM方法返回的值,或element.childNodes这样的属性值)时使用局部不变量特别重要。实际上,每次存取HTMLCollection对象的属性时,都会对DOM文档进行动态查询。例如:

var divs = document.getElementsByTagName("div");
for (var i = 0; i< divs.length ; i++){    //避免!
    var div = divs[i];
    process(div);
}

这段代码每次通过属性名或索引存取divs属性时,DOM实际上重复执行了一次对页面上所有

元素的查询;在这段代码中,每次读取divs.length或divs[i]都会对页面查询一次。因此应尽可能把这类值存储在局部变量中,避免多次重复插叙,例如:

var divs = document.getElementsByTagName("div");
for( var i = 0, len = divs.length; i< len; i++ ){    //更好的方式
    var div = divs[i];
    process(div);
}

这样就减少了直接存取该对象的次数。

    3.流控制

           除了数据存取之外,流控制或许是提升JavaScript性能最重要的一环。在一个if-else语句中,语句的执行流越深,需要判断的条件就越多。

3.1快速条件判断

           if语句:看下面的例子:

if(value == 0){
    return result0;
}else if(value == 1){
    return result1;
}else if(value == 2){
    return result2;
}else if(value == 3){
    return result3;
}else if(value == 4){
    return result4;
}else if(value == 5){
    return result5;
}else if(value == 6){
    return result6;
}else if(value == 7){
    return result7;
}else if(value == 8){
    return result8;
}else if(value == 9){
    return result9;
}else {
    return result10;
}

通常这样类型的结构是不被推荐的,它的主要问题在于:语句的执行流越深,需要判断的条件就越多。当value为9是,执行完成的事件会比value为0时要长,因为它之前的所有条件都需要判断。尽管使用大量的if条件语句是不可取的,但我们可以采取以下几个方法来提高整体性能:

第一种方法是将条件按照频率降序排列。由于最快的运算在第一个条件语句后就退出,所以要确保这种情况尽可能多地出现。假设前面例子中的最常见的情况时value 等于5,其次是value等于9,这就意味着,执行流在达到最常见的情况之前要进行5次条件判断,在达到第二常见的情况之前要进行9次判断。尽管按数字递增的方式排序更容易阅读,但实际上把它写成下面这个样子执行效率更高:

if(value == 5){
    return result5;
}else if(value == 9){
    return result9;
}else if(value == 0){
    return result0;
}else if(value == 1){
    return result1;
}else if(value == 2){
    return result2;
}else if(value == 3){
    return result3;
}else if(value == 4){
    return result4;
}else if(value == 6){
    return result6;
}else if(value == 7){
    return result7;
}else if(value == 8){
    return result8;
}else{
    return result10;
}

另一种方法是优化if语句的条件,将条件分成几个分支,下面的二分查找算法可以逐步找出有效的条件,在条件数量众多,且没有出现频率特别高的条件,从而不能简单的按频率排列的情况下,这个方法可取。

if(value < 6){
    if(value < 3){
        if(value == 0){
            return result0;
        }else if(value == 1){
            return result1;
        }else if(value == 2){
            return result2;
        }
    }else{
        if(value == 3){
            return result3;
        }else if(value == 4){
            return result4;
        }else{
            return result5;
        }
    
    }
}else{
    if(value < 8){
        if(value == 6){
            return result6;
        }else{
            return result7;
        }
    }else{
        if(value == 8){
            return resdult8;
        }else if(value == 9){
            return result9;
        }else{
            reutrn result10;
        }
    }

}

这段代码确保在任何情况下,都不会超过4次的条件判断。

然而问题依然存在,每增加一个条件最终都会导致执行时间变长,这影响性能和可维护性。这是switch语句就有了用武之地。

switch语句:switch语句简化了多重条件判断的结构,并提升了性能:

switch(value){
    case 0:
        return result0;
        break;
    case 1:
        return result1;
        break;
    case 2:
        return result2;
        break;
    case 3:
        return result3;
        break;
    case 4:
        return result4;
        break;
    case 5:
        return result5;
        break;
    case 6:
        return result6;
        break;
    case 7:
        return result7;
        break;
    case 8:
        return result8;
        break;
    case 9:
        return resutl9;
        break;
    default:
        return result10;
}

Firefox可以很好的处理switch语句,每个条件判断执行的时间大致相同,与他们定义的顺序无关。然而其他浏览器做的不怎么好。在IE、Opera、Safari和Chrome中,switch语句越深,执行的时间增加越明显。不过这比每个条件都用if语句要增加的少。

在JavaScript中,当仅判断一两个条件时,if语句通常比switch语句更快。当有两个以上条件且条件比较简单(不是进行范围判断)时,switch语句往往更快。这是因为大多数情况下,switch语句中执行单个条件所需时间比在if语句中短。所以当有大量的条件判断时,使用switch语句更合适

           数组查询:在JavaScript中,处理条件判断的解决方案不止两种。除了if语句和switch语句外,还有第三种方法:在数组中查询值:

//定义数组results
var results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10];
//返回正确结果
return results[value];

不使用条件语句,而是把所有的结果都存储在数组中,并用数组的索引映射value变量。检索对应的结果就是简单的查询数组值。虽然查询数组的耗时也会随进入数组的深度而增加,但是和每个条件都用if语句或者switch语句来判断相比,增加的时间还是要小得多。使用数组查询的理想情况时有大量的条件存在,并且这些条件都能用数字或字符串(对于字符串,可以使用对象而非数组来存储)这样的离散值来表示。

使用数组查询少量的结果是不合适的,因为数组查询往往比少量的条件判断语句慢。当要查询的条件范围很大时,数组查询才会变得非常有用。

最快的条件判断

前面列出的3种技术:if语句、switch语句和数组查询,在代码优化执行方面都有各自的用处:

  • 使用if语句

两个之内的离散值需要判断;

大量的值能容易地分到不同的区间范围中。

  • 使用switch语句

超过两个而少于10个离散值需要判断;

条件值是非线性的,取法分离出区间范围。

  • 使用数组查询

超过10个值需要判断;

条件对应的结果是单一值而不是一些列操作。

3.2快速循环

在 JavaScript中,循环是导致性能问题的常见起因,编写循环的方式能彻底改变它的执行时间。看下面的例子:

//未优化的代码
var values = [1,2,3,4,5];

//for循环

for(var i = 0; i< values.length; i++){
    process(values[i]);
}

最显眼的是循环中反复比较变量与数组的长度,在前面的例子中已经提到,查找属性要比存取局部变量更耗时优化一下是这样:

//第一步优化

var values = [1,2,3,4,5];
var length = values.length;

for(var i = 0; i< length; i++){
    process(values[i]);
}

另外一种提高性能的简单方式是将循环变量递减到0,而不是递增到总长度。根据每个循环的复杂度不同,这个简单的改变可以比原来节约多达50%的执行时间。例如:

var vaules = [1,2,3,4,5];
var length = values.length;

for(var i = length; i--; ){
    process(values[i]);
}

因为结束条件被改造为与0进行比较(注意一旦循环变量等于0,结束条件就会变为假)。执行效率更高了。

避免 使用for-in循环

JavaScript中的for循环,while循环,do-while循环的执行效率差不多。但是for-in循环的效率差异还是很大的。它用来遍历JavaScript对象的可枚举属性。典型的使用方法:

for(var prop in object){
    if(object.hasOwnProperty(prop)){    //确保只处理实例自身的属性
        process(object[prop]);
    }
}

这段代码遍历了给定对象的属性。它的结束条件无法改变(除非使用throw抛异常,然后手动捕获),而且遍历属性的顺序也无法改变。此外for-in循环需要从对象中解析每个可枚举的属性,为了提取这些属性需要检查对象的原型和整个原型链。遍历原型链就像遍历作用域链,它会增加耗时,从而降低整个循环的性能。因此尽可能地避免使用for-in循环可以让代码执行的更快速。

你可能感兴趣的:(技术笔记,javascript,性能,优化,高效)