Web前端性能优化——编写高效的JavaScript

前言css

随着计算机的发展,Web富应用时代的到来,Web 2.0早已再也不是用div+css高质量还原设计的时代。自Gmail网页版邮件服务的问世开始,Web前端开发也开启了新的纪元。用户需求不断提升,各类新的技术层出不穷,前端工程师的地位也愈来愈重要。然而任何事物都是有两面性的,随着前端技术的发展,前端业务愈来愈繁重,这大大增长了JS代码量。所以,要提升Web的性能,咱们不只须要关注页面加载的时间,还要注重在页面上操做的响应速度。那么,接下来咱们讨论几种可以提升JavaScript效率的方法。前端

 

1、从JavaScript的做用域谈起node

当JavaScript代码执行时,JavaScript引擎会建立一个执行环境,又叫执行上下文。执行环境定义了变量或函数有权访问的其余数据,决定了它们的行为,每一个执行环境都有一个与它关联的变量对象,环境中定义的全部函数、变量都保存在这个对象中。在页面加载的时候,JavaScript引擎会建立一个全局的执行环境,全部全局变量和函数都是做为window对象(浏览器中)的属性和方法建立的。在此以后,每执行一个函数,JavaScript引擎都会建立一个对应的执行环境,并将该环境放入环境栈中,因此当前正在执行的函数的执行环境是在环境栈的最顶部的,当函数执行完毕以后,其执行环境会弹出栈,并被销毁,保存在其中的变量和函数定义也会被销毁。jquery

当代码在一个执行环境中执行时,JavaScript引擎会建立变量对象的一个做用域链,它能够保证对执行环境有权访问的变量和函数的有序访问。做用域链的前端始终是当前执行的代码所在的环境的变量对象。全局环境的做用域链中只有一个变量对象,它定义了全部可用的全局变量和函数。当函数被建立时,JavaScript引擎会把建立时执行环境的做用域链赋给函数的内部属性[[scope]];当函数被执行时,JavaScript引擎会建立一个活动对象,最开始时这个活动对象只有一个变量,即arguments对象。该活动对象会出如今执行环境做用域链的顶端,接下来是函数[[scope]]属性中的对象。数组

当须要查找某个变量或函数时,JavaScript引擎会经过执行环境的做用域链来查找变量和函数,从做用域链的顶端开始,若是没找到,则向下寻找直至找到为止。若一直到全局做用域都没有找到,则该变量或函数为undefined。浏览器

举个栗子:前端工程师

function add(a,b) {
    return a + b;
}

var result = add(2,3);

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

 

 

2、使用局部变量dom

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

例如:

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

当document使用次数比较少时,可能无所谓,但是若是在一个函数的循环中大量使用document,咱们能够提早将document变成局部变量。

来看看jquery怎么写的:

(function(window, undefined) {
     var jQuery = function() {}
     // ...
     window.jQuery = window.$ = jQuery;
})(window);

这样写的优点:

一、window和undefined都是为了减小变量查找所通过的scope做用域。当window经过传递给闭包内部以后,在闭包内部使用它的时候,能够把它当成一个局部变量,显然比原先在window scope下查找的时候要快一些。(原来的window处于做用域链的最顶端,查找速度慢)

二、在jquery压缩版本jquery.min.js中能够将局部变量window替换成单个字母,减少文件大小,提升加载速度

三、undefined也是JavaScript中的全局属性。将undefined做为参数传递给闭包,由于没给它传递值,它的值就是undefined,这样闭包内部在使用它的时候就能够把它当作局部变量使用,从而提升查找速度。undefined并非JavaScript的保留字或者关键字。

四、undefined在某些低版本的浏览器(例如IE八、IE7)中值是能够被修改的(在ECMAScript3中,undefined是可读/写的变量,能够给它赋任意值,这个错误在ECMAScript5中作了修正),将undefined做为参数而且不给它传值能够防止因undefined的值被修改而产生的错误。

 

3、避免增加做用域链

在JavaScript中,有两种语句能够临时增长做用域链:with、try-catch

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

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

try-catch中的catch从句和with相似,也是在做用域链的顶端增长了一个对象,该对象包含了由catch指定命名的异常对象。可是由于catch语句只有在放生错误的时候才执行,所以影响比较少。

 

4、字符串连接优化

因为字符串是不可变的,因此在进行字符串链接时,须要建立临时字符串。频繁建立、销毁临时字符串会致使性能低下。

固然,这个问题在新版本浏览器包括IE8+中都获得了优化,因此不须要担忧

在低版本浏览器(IE六、IE7)中,咱们能够种数组的join方法来代替。

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

var outcome = temp.join("");

 

5、条件判断

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

if(val == 0) {
    return v0;
}else if(val == 1) {
    return v1;
}else if(val == 2) {
    return v2;
}else if(val == 3) {
    return v3;
}else if(val == 4) {
    return v4;
}

当条件分支比较多时,咱们能够斟酌哪一种条件出现的几率比较大,并将对应的语句放在最上面,这样能够减小判断次数。

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

使用数组:

var v = [v0,v1,v2,v3,v4];
return v[valeue];

要求:对应的结果是单一值,而不是一系列操做

另外,其余方面的优化,譬如

if(condition1) {
    return v1;
}
else {
    return v2
}
// 改为
if(condition1) {
    return v1;
}
return v2;

 

6、快速循环

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

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元素的属性是相对耗时的,因此应该避免这种写法。

二、若是能够,递减代替递增

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的时候会直接跳出,循环次数比较多时仍是颇有用的。

 

7、展开循环

var i = arr.length - 1;
while(i--) {
    dosomething(arr[i]);
}

遇到这样的状况时,执行一次循环的时候咱们能够选择不止执行一次函数。

var interations = Math.floor(arr.length / 8);
var left = arr.length % 8;
var  i = 0;

if(left) {
    do {
        dosomething(arr[i++]);
    } while(--left);
}
do {
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
} while(--interations);

当遇到大数组,减小循环的开销,性能不就提上去了嘛。(至于为何是每次循环,调8次函数,大牛测出来的,这样达到最佳)

 

8、高效存取数据

JavaScript中4种地方能够存取数据:

字面量值;变量;数组元素;对象属性

字面量值和变量中存取数据是最快的,从数组元素和对象属性中存取数据相对较慢,而且随着深度增长,存取速度会愈来愈慢,譬如obj.item.value就比obj.item慢。

某些状况下咱们能够将对象、数组属性存成局部变量来提升速度,譬如:

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

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

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

}
// 改为
// 
var divList = document.getElementsByTagName("div");
for( var i = 0,len = divList.length;i < len;i++) {

}

 

9、事件委托

事件委托就是利用冒泡的原理,将本来应该添加在某些元素身上的监听事件,添加到其父元素身上,来达到提升性能的效果。

举个栗子:

<div>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
</div>
<script>
window.onload = function() {
    var ul = document.getElementsByTagName('ul')[0];
    var liList = document.getElementsByTagName('li');

    for(var i = 0,len = liList.length;i < len;i++) {
        liList[i].onclick = function() {
            alert(this.innerHTML);
        }
    }
}
</script> 

这样咱们就为每一个li添加了监听事件了。

显然,咱们经过循环为每一个li添加监听事件是不优化的。这样不只浪费了内存,在新的li加入的时候咱们还要从新为它添加监听事件。

咱们能够这样写:

<div>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
</div>
<script>
window.onload = function() {
    var ul = document.getElementsByTagName('ul')[0];
    var liList = document.getElementsByTagName('li');

    ul.onclick = function(e) {
        var e = e || window.event;
        var target = e.target || e.srcElement;

        if(target.nodeName.toLowerCase() == "li") {
            alert(target.innerHTML);
        }
    }
}
</script>

这样写的好处:

只添加一个监听事件,节省了内存;新加入li的时候咱们也不用为它单独添加监听事件;在页面中添加事件处理程序所需的时候更少,由于咱们只须要为一个DOM元素添加事件处理程序。

 

若是是原创文章,转载请注明出处:http://www.cnblogs.com/MarcoHan

 

最后,提一点不会提升性能的建议

if( 2 == value) {

}

相似这种判断的时候推荐常量放在左边,这样就能够预防相似 if( value = 2){} 的错误了,由于若是少写了一个等号, if( value = 2) {} 是合法的语句,并且代码量变大的时候不容易检查出来。if( 2 = value) {} 这样少写了等号JavaScript引擎会直接报错,咱们就能够愉快地改过来了。(只是建议)

相关文章
相关标签/搜索