web前端性能优化之编写高效的Js

作者: Timeless 更新时间: 阅读量: 79

当今的web应用程序都由大量的Javascript代码驱动,用户需求不断提高,各种新的技术层出不穷,前端工程师的地位也越来越重要。然而任何事物都是有两面性的,随着前端技术的发展,前端业务越来越繁重,这大大增加了JS代码量。因此,要提高Web的性能,我们不仅需要关注页面加载的时间,还要注重在页面上操作的响应速度。那么,接下来我们讨论几种能够提高JavaScript效率的方法。

一、从JavaScript的作用域谈起

当JavaScript代码执行时,JavaScript引擎会创建一个执行环境,又叫执行上下文。执行环境定义了变量或函数有权访问的其他数据,决定了它们的行为,每个执行环境都有一个与它关联的变量对象,环境中定义的所有函数、变量都保存在这个对象中。

全局作用域:在页面加载的时候,JavaScript引擎会创建一个全局的执行环境,所有全局变量和函数都是作为window对象(浏览器中)的属性和方法创建的。

函数作用域:每执行一个函数,JavaScript引擎都会创建一个对应的执行环境,并将该环境放入环境栈中,所以当前正在执行的函数的执行环境是在环境栈的最顶部的,当函数执行完毕之后,其执行环境会弹出栈,并被销毁,保存在其中的变量和函数定义也会被销毁。

举个栗子:

function add(a,b) {
    return a+ b;
}
var result = add(2,3);

代码执行时,add函数有一个仅包含全局变量对象的[[scope]]属性,add函数执行时,JavaScript引擎创建新的执行环境以及一个包含this、arguments、a、b的活动对象,并将其添加到作用域链中。如下图所示:

二、使用局部变量

了解了作用域链的概念,我们应该知道在查找变量会从作用域链的顶端开始一层一层的向下找。显然,查找的层数越多,花费的时间越多。所以为了提高查找的速度,我们应该尽量使用 局部变量(到目前为止,局部变量是JavaScript中读写最快的标识符)。

例如:

function createEle() {
    document.createElement("div");
}
function createEle() {
    var doc = document;
    doc.createElement("div");
}

document使用次数比较少时,可能无所谓,可是如果在一个函数的循环中大量使用document,我们可以提前将document变成局部变量。

由于doc存在于作用域链的顶层,所以解析起来比document更快。

三、避免增长作用域链

在JavaScript中,有两种语句可以临时增加作用域链:withtry-catch

with可以使对象的属性可以像全局变量来使用,它实际上是将一个新的变量对象添加到执行环境作用域的顶部,这个变量对象包含了指定对象的所有属性,因此可以直接访问。

var person = {
    name: "example",
    age: 1
}
function Info(){
    with(person){
        alert(name+" is "+age);
    }
}
Info();

这样看似很方便,但是增长了作用域链,原来函数中的局部变量不在处于作用域链的顶端,因此在访问这些变量的时候要查找到第二层才能找到它。当with语句块之行结束后,作用域链将回到原来的状态。鉴于with的这个缺点,所以不推荐使用。

try-catch中的catch从句和with类似,也是在作用域链的顶端增加了一个对象,该对象包含了由catch指定命名的异常对象。但是因为catch语句只有在放生错误的时候才执行,因此影响比较少。

四、字符串链接优化

由于字符串是不可变的,所以在进行字符串连接时,需要创建临时字符串。频繁创建、销毁临时字符串会导致性能低下。

当然,这个问题在新版本浏览器包括IE8+中都得到了优化,所以不需要担心

在低版本浏览器(IE6、IE7)中,我们可以种数组的join方法来代替。

var temp = [];
var i = 0;
temp[i++] = "Hello";
temp[i++] = " ";
temp[i++] ="World";

var outcome = temp.join("");

五、条件判断

当出现条件判断时,我们采用什么样的结构才能使性能最优?

5.1、使用if语句的情况:

1、2个以内的离散数值需要判断。

2、大量的值能容易地分到不同的区间范围,条件对应结果可以是值也可以是一系列操作

if(val == 0) {
    return result0;
}else if(val == 1) {
    return v1;
}else if(val == 2) {
    return result2;
}else if(val == 3) {
    return result3;
}else if(val == 4) {
    return result4;
}

当条件分支比较多时,我们可以斟酌哪种条件出现的概率比较大,并将对应的语句放在最上面,这样可以减少判断次数。

5.2、使用swutch语句的情况:

1、超过2个而少于10个离散值需要判断

2、条件值是非线性的,无法分离出区间范围

使用switch语句,新版的浏览器基本上都对switch做了优化,这样层数比较深时,性能比if会更好

switch(value){
    case 0:
        return result0;
    case 1:
        return result1;
    case 2:
        return result2;
    case 3:
        return result3;
    case 4:
        return result4;
}

5.3、使用数组:

1、超过10个值需要判断

2、条件对应的结果是单一值,而不是一系列操作

var array = [result0,result1,result2,result3,result4...];
return array [valeue];

使用数组查询少量的结果是不合适的,因为那比少量条件判断语句要慢。

六、快速循环

6.1、循环总次数使用局部变量

for( var i = 0;i < arr.length;i++) {

}
// 改成
var len = arr.length;
for( var i = 0;i < len;i++) {

}

这样就避免了每次循环的属性查找。这点尤其重要,因为在进行dom操作时,很多人会这样写:

var divList = document.getElementsByTagName("div");
for( var i = 0;i < divList.length;i++) {

}

查找DOM元素的属性是相对耗时的,所以应该避免这种写法。

6.2、如果可以,递减代替递增

for(var i = 0;i < arr.length;i++) {

}
// 改成
for(var i = arr.length - 1;i--;) {

}

var i = 0;while(i < arr.length) {
    i++;
}
// 改成
var i = arr.length - 1;while(i--) {

}

i=0的时候会直接跳出,循环次数比较多时还是很有用的。

七、避免运行时间过长的脚本

javascript是单线程语言,代码执行导致页面被冻结而出现假死,可能出现的原因:

1、过多的DOM交互:DOM操作比其它任何js操作的开销都高。

2、过多的循环:循环次数过多或者每次迭代中执行运算过多,会导致脚本运行时间变长

3、过多的递归:js引擎限制了脚本可递归的次数。