您的位置:首页 > Web前端 > JavaScript

编写高效的JavaScript【well】

2012-02-17 14:41 267 查看
这两天在读《高性能网站建设进阶指南:Web开发者性能优化最佳实践》
第七章讲的是JavaScript的优化
关于JavaScript的代码优化之前还没看见过,感觉不错,跟大家一起分享下~


1.作用域
执行JavaScript代码时,JavaScript引擎会创建一个全局的执行上下文,而执行每一个函数时,也会创建一个对应的执行上下文,最终建立一个执行上下文的堆栈。每一个执行上下文对应一个作用域链,用于解析标识符。JavaScript在作用域链中查找标识符。标识符在作用域链中的位置越深,查找和访问它所需的时间也就越长。(只有基于V8 JavaScript引擎的Google chrome和基于Nitro JavaScript引擎的Safari4+例外,它们的存取速度之快,使标识符深度的影响微乎其微了)标识符在作用域链的位置越靠上,存取速度就越快。
局部变量是目前为止JavaScript中读写速度最快的标识符。因为它们存在于执行函数的活动对象中,解析标识符只需查找作用域链中的单个对象。请尽可能的使用局部变量。任何非局部变量在函数中的使用超过一次时,都应该将其存储为局部变量。

例如:

function createChildFor(elementId){
var element=document.getElementById(elementId),
newElement=document.createElement('div');
element.appendChild(newElement);
}


这个函数调用了两次document全局变量,为了更快的引用,应该把它存储到一个局部变量中。
全局变量对象始终是作用域链中最后一个对象,所以对全局标识符的解析总是最耗时的!
另外,存取数组中的某个值时,需要通过索引来查询数据存储的位置,而存取对象中的某个值时,需要通过属性值来查询数据存储的的位置。所以,请将函数中使用超过一次的对象属性或数组元素也存储为局部变量。
再另外一下,在代码执行过程中,作用域链通常是保持不变的,但是with语句和try-catch语句中的catch从句会临时增长执行上下文的作用域链。
2.流控制
条件判断优化:
在if语句中,随着条件总数的增加,条件越深,性能损失也越大。

可以将条件按频率降序排列。出现频率较高的条件放在前几位。

优化语句条件,可以借鉴二分查找算法的精髓

如果条件均为离散值,而非区间范围,则改写为switch语句

但是,并不是在条件均为离散值的情况下,switch语句的性能都由于if语句。在JavaScript中,当仅判断一到两个条件是,if语句通常比switch语句更快
优化条件判断还有另一个办法,数组查询(假设要判断的值value取值范围为1-10):

var results=[result0,result1,result2,result3,result3,result4,result5,result6,result7,result8,result9,result10];
return results[value];


简单的用数组索引映射value变量,虽然查询数组的耗时也会随着进入数组的深度而增加,但是和每个条件都用if语句或switch语句来判断相比,增加的时间还是要小的很多。
循环优化:
简单提升循环性能:

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

for(var i=0;i<values.length;i++){//反复的比较计数变量和数组长度,这样做效率很低
values[i]++;
}

var length=values.length;//使用局部变量
for(var i=0;i<length;i++){//加快循环效率
values[i]++;
}

for(var i=length;i--;){
values[i]++;
}//结束条件被改造为与0比较,一旦循环变量等于0,结束条件就会变为假,节约多达50%的执行时间!


for-in循环比其他循环慢,请在处理需要遍历的对象属性集或JSON对象的未知属性集时才采用它
进一步优化循环–展开循环:
使一次循环完成多次循环的工作

var iterations=Math.ceil(values.length/8);//根据经验,8是最佳数值,确定循环次数
var startAt=values.length%8;//要额外处理的数组项数量,即8的余数,仅在第一次循环中使用,使用完后,重置为0
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(--iteration>0)


3.字符串优化
字符串连接:
字符串连接一直是JavaScript中性能最低的操作之一。
通常情况下的加法运算:

var text='hello';
text+=' ';
text+='world';


这意味着要创建中间字符串来存储连接的结果,频繁地在后台创建和销毁字符串会导致字符串连接的性能异常底下。
开发者利用了JavaScript的Array数组来补救:

var buffer=[],i=0;
buffer[i++]='hello';
buffer[i++]=' ';//通过相应的索引值直接添加元素比调用push方法略快一点。
buffer[i++]='world';
var text=buffer.join('');


不过,如今浏览器对字符串的优化已经改变了字符串连接的局面。IE8+、ff、safari、chrome、opera已经对加法运算做了优化使其比数组运算法更加快速。当字符串相对较小(少于20个字符)且连接的数量也较少时(少于1000个),所有浏览器使用加法运算符都能在不到1毫秒之内轻松完成连接。但增加连接字符串的数量或大小时,在IE7中性能会明显下降。在FF下,当字符串大小增加时,加法运算符和数组技术的性能差异会变小。在Safari下,当字符串连接数量增加时,这两种技术的性能差异也会同样变小。只有在chrome和opera下,加法运算符一直保持着显著的性能优势。所以,需要基于用户的浏览器来权衡使用哪种技术。当然,大多数情况下,加法运算符是首选。
裁剪字符串:
JavaScript没有用于移除字符串头尾空白的原生修剪方法。
用于弥补的函数:

function trim(text){
return text.replace(/^\s+|\s+$/g,"");
}


但是,这个函数可以在正则表达式上优化性能。
对性能的影响来自于正则表达式的两个方面:一个方面是指明有两个匹配模式的管道运算符,另一方面是指明全局应用该模式的g标记。保持正则表达式尽可能的简单,可以提高性能。
Steven Levithan提出的在JavaScript中执行速度最快的裁剪字符串方式:

function trim(text){
text=text.replace(/^\s+/,"");//删除头部的空白
for(var i=text.length-1;i>=0;i--){//清除尾部的空白
if(/\S/.test(text.charAt(i))){
text=text.substring(0,i+1);
break;
}
}
return text;
}


4.避免运行时间过长的脚本
JavaScript是单线程语言。所以在代码执行时会导致页面被冻结而出现假死。在一个时间段中,每个窗口或标签页都只能执行一个脚本,同时,所有用户的交互被中断。如果JavaScript代码未经过细心的设计,有可能长时间的冻结页面,而导致浏览器停止响应。最常见的脚本执行时间过长的原因包括:过多的DOM交互、过多的循环、过多的递归。

在复杂的WEB应用程序中,为了避免长时间的页面冻结而导致不能进行交互,需要人为地插入中断。
解决方案:使用定时器挂起。

window.onload=function(){
//页面加载完成
//创建第一个定时器
setTimeout(function(){
//被延迟的脚本1
setTimeout(function(){
//被延迟的脚本2
},100);
//被延迟的脚本1,继续执行
},100);
}


实际上就是把某些代码排到JavaScript引擎队列中稍后执行,而页面可以利用这段引擎挂起的时间来进行交互。同样也可将定时器应用于大型数组处理中
最后,推荐下这本书:


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: