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

 

前言

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

一、从JavaScript的作用域谈起

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

当代码在一个执行环境中执行时,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的活动对象,并将其添加到作用域链中。如下图所示:

二、使用局部变量

了解了作用域链的概念,我们应该知道在查找变量会从作用域链的顶端开始一层一层的向下找。显然,查找的层数越多,花费的时间越多。所以为了提高查找的速度,我们应该尽量使用 局部变量(到目前为止,局部变量是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);

这样写的优势:

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

2、在jquery压缩版本jquery.min.js中可以将局部变量window替换成单个字母,减小文件大小,提高加载速度

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

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

三、避免增长作用域链

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

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

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

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

四、字符串链接优化

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

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

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

var temp = [];
var i = 0;
temp[i++] = "Hello";
temp[i++] = " ";
temp[i++] ="everyone"; var outcome = temp.join("");

五、条件判断

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

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;

六、快速循环

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

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

七、展开循环

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次函数,大牛测出来的,这样达到最佳)

八、高效存取数据

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++) { }

 

九、事件委托

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

举个栗子:

<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引擎会直接报错,我们就可以愉快地改过来了。(只是建议)

编写高效的JavaScript的更多相关文章

  1. 编写高效的JavaScript程序

    作者: Addy Osmani  来源: CSDN  发布时间: 2013-01-10 14:15  阅读: 7952 次  推荐: 15   原文链接   [收藏] 英文原文:Writing Fas ...

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

    前言 随着计算机的发展,Web富应用时代的到来,Web 2.0早已不再是用div+css高质量还原设计的时代.自Gmail网页版邮件服务的问世开始,Web前端开发也开启了新的纪元.用户需求不断提高,各 ...

  3. 编写快速、高效的JavaScript代码

    许多Javascript引擎都是为了快速运行大型的JavaScript程序而特别设 计的,例如Google的V8引擎(Chrome浏览器,Node均使用该引擎).在开发过程中,如果你关心你程序的内存和 ...

  4. js学习笔记-编写高效、规范的js代码-Tom

    编写高效.规范的js代码: 1.变量命名空间问题,尽量使用局部变量,防止命名冲突(污染作用域中的全局变量):全局空间命名的变量可以在对应的文档域任意位置中使用window调用. 2.尽量使用单var定 ...

  5. 编写高效的jQuery代码

    http://www.css88.com/jqapi-1.9/ 编写高效的jQuery代码 最近写了很多的js,虽然效果都实现了,但是总感觉自己写的js在性能上还能有很大的提升.本文我计划总结一些网上 ...

  6. 编写高质量JavaScript代码的68个有效方法

    简介: <Effective JavaScript:编写高质量JavaScript代码的68个有效方法>共分为7章,分别涵盖JavaScript的不同主题.第1章主要讲述最基本的主题,如版 ...

  7. MySql如何编写高效的SQL

    最近应团队要求,研究整理了下,mysql相关的优化,有些是根据实际java项目中碰到的情况经验之谈.欢迎讨论~ SQL 语言是一种强大而且灵活的语言,在使用 SQL 语言来执行某个关系查询的时候,用户 ...

  8. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...

  9. 编写高效的CSS选择符(节选)

    最右边优先 css选择符是从右向左进行匹配的. 样式系统从最右边的选择符开始向左匹配规则.只要当前的选择符的左边还有其他选择符,样式系统就会继续向左移动,直到找到和匹配的元素,或者因为不匹配而退出. ...

随机推荐

  1. 提高mysql memory(heap) engine内存性能的开源补丁_XMPP Jabber即时通讯开发实践_百度空间

    提高mysql memory(heap) engine内存性能的开源补丁_XMPP Jabber即时通讯开发实践_百度空间 提高mysql memory(heap) engine内存性能的开源补丁

  2. OpenJDK1.8.0 源码解析————HashMap的实现(二)

    上一篇文章介绍了HashMap的一部分的知识,算是为下面HashMap的进一步学习做准备吧. 然后写的时候一直在思考的一个问题是,这方面的知识网上的资料也是一抓一大把,即使是这样我为什么还要花费时间去 ...

  3. enum可以做索引

    enum可以做索引 enum可以做索引, 配上虚函数,或者函数指针,可以实现上层的统一封装和快速索引. 点击(此处)折叠或打开 MoTbl.cpp #include <stdio.h> # ...

  4. java web从零单排第十六期《struts2》控制标签(2)

    1.s:subset标签概述: s:subset标签功能是从一个集合中取出部分元素合并成一个新的集合,新生成的这个集合是原来集合的子集.属性和意义如下: 属性名 是否必需 默认值 类型 说明介绍 co ...

  5. 纯C语言INI文件解析

    原地址:http://blog.csdn.net/foruok/article/details/17715969 在一个跨平台( Android .Windows.Linux )项目中配置文件用 IN ...

  6. How to write simple HTTP proxy with Boost.Asio

    How to write simple HTTP proxy with Boost.Asio How to write simple HTTP proxy with Boost.Asio Russia ...

  7. discuz清空session,导致session保存机制失败,session无法更新与解决

    <?php function userErrorHandler() { $e = func_get_args(); echo '<pre style="color:red;&qu ...

  8. 介绍一个C++奇巧淫技

    你能实现这样一个函数吗:   MyType type;   HisType htype;   serialize_3(11, type, htype);   serialize_4(type, hty ...

  9. Android内存管理

    首先Android理机制相当复杂.想要讲清楚比較困难.其次对于绝大多数用户来说.仅仅关心内存够不够用,至于内存怎样管理的这样的技术细节,不是用户须要去考虑的,写这样一个专题有没有意义?毕竟我们是用手机 ...

  10. Cocos2d-x 3.1.1 Lua实例-AccelerometerTest(重力加速计)

    Cocos2d-x 3.1.1 Lua实例-AccelerometerTest(重力加速计) 本篇博客介绍Cocos2d-x的第一个实例--重力加速计測试.效果图(注:这里无法模拟重力感应): --[ ...