作者:

js写法优化

转自百度文章

1.使用局部变量避免使用全局变量
比如
function test(){
var s = document.getElementById(‘aaa’);
s.innerHTML = document.body.clientHeight;
}
改成
function test(){
var d = document,
s = d.getElementById(‘aaa’);
s.innerHTML = d.body.clientHeight;
}
局部变量的好处就是减少了作用域链的查找
我建议要是有两次的引用就用局部变量

2.避免使用with(这个估计地球人都知道)
我理解原因就是with会创建自己的作用域,这样就加长了原来的作用域链,使得在with块中执行的代码反而变慢了,在书写上好像省了代码,其实在访问上反而变长变繁琐了,性能下降了
例子
使用with
function test(){
with(document.body){
clientHeight = ‘200px’;
clientWidth = ‘200px’
}
}
其实都可以写成
function test(){
var ds = document.body;
ds.clientHeight = ‘200px’;
ds.clientWidth = ‘200px’
}
3. 遍历nodelist的方式
一般的方式都是
var as = document.getElementsByTagName(‘div’);
for(var i=0,l 我的方式一次都不用
for(var i=0,ci;ci=as[i++];){}当nodeList完结时就为false就结束了
好处,没计算长度,省了在循环里赋值,代码更加少了,i++到了判断里
(注意:这个方式用在nodelist里可以,如果你用到array里,可会有问题的,数组里有个0后者null什么的就瞎了)

4.别用那么多个var,一个加逗号就搞定了
var a =1;
var b = 1;
var c =1;
代码长,性能差
拆成
var a=1,
b=1,
c=1;

5.innerHTML是最好的选择
往元素添加元素时,最好用innerHTML

6.ie的removeChild不好用
一般咱们删除一个元素会用
elm.removeChild(subElm)
这个在ie下不好用,因为在ie下这个只是把这个元素从dom树中断开了,但并没用真正删除,它现在变成了孤立的节点了,
要想真正删除,可以这样
var ryc = document.createElement(‘div’);
div.appendChild(subElm);
div.innerHTML = ”;
div = null;
这样就真的删除了,除了ie外别的都可以用removeChild达到效果

7.为多个同级元素绑定事件时,不用为每个都绑定,为他们的父级绑定就行了
比如

  • sdf
  • sdf
  • sdf
  • sdf
  • sdf
  • sdf

可能你要为每个li添加click
为每个添加那可繁琐且容易出现溢出(ie)
其实只要为 ul一个添加就行了,因为事件是冒泡向上的
var ul = document.getElementById(‘a’);
ul.onclick = function (e){
!e&&(e=event);
var target = e.srcElement||e.target;
if(target.tagName==’LI’){
//your code
}
}

8.尽量用原生的方法,因为原生的都是用c/c++编译而成的他们执行的要比用js写的方法快多了

9.appendChild用的多时一定要用docuemntfragment
比如
for(var i=0;i var o = document.createElement(‘div’);
document.body.appendChild(o);
}
用documentFragment
var f = document.createDocumentFragment();
for(var i=0;i var o = document.createElement(‘div’);
f.appendChild(o);
}
document.body.appendChild(f);

10. if else用的>=3个了,那用switch吧,好阅读,性能好

11. if

12. if==1,if改&&
if(a==1)a=2

a==1&&(a=2);

13.计算元素位置,while()offsetParent
这个方式是老方式了,现在的浏览器ie6以上,ff3.1以上,chrome,opera(我只测了最新的)都支持这个
el.getBoundingClientRect返回一个对像,分别是top,left,right,bottom的值

14.正则的查找没有indexOf快
var s= ‘sdfsdfsdfAAAsdfdsfs’;
for(var i=0;i s.indexOf(‘AAA’)
}
比这个快
var s= ‘sdfsdfsdfAAAsdfdsfs’;
for(var i=0;i /AAA/.test(s)
}

15.在正则中多用非捕获(?:)这样快

16.设置某个元素的style时用cssText简单些
el.style.cssText +=”;postion:absolute;”
(注意:position前;不能去了,因为ie没有这个;position认不出来了就,比的浏览器没这个毛病)

17.在new 时,没有参数时函数名后边的括号可以去了
new fn()==>new fn
new Image()==>new Image

首先,与其他语言不同,JS的效率很大程度是取决于JS engine的效率。除了引擎实现的优劣外,引擎自己也会为一些特殊的代码模式采取一些优化的策略。例如FF、Opera和Safari的JS引擎,都对 字符串的拼接运算(+)做了特别优化。显然,要获得最大效率,就必须要了解引擎的脾气,尽量迎合引擎的口味。所以对于不同的引擎,所作的优化极有可能是背 道而驰的。

而如果做跨浏览器的web编程,则最大的问题是在于IE6(JScript 5.6)!因为在不打hotfix的情况下,JScript引擎的垃圾回收的bug,会导致其在真实应用中的performance跟其他浏览器根本不在一个数量级上。因此在这种场合做优化,实际上就是为JScript做优化!

所以第一原则就是只需要为IE6(未打补丁的JScript 5.6或更早版本)做优化!

如果你的程序已经优化到在IE6下可以接受的性能,那基本上在其他浏览器上性能就完全没有问题。

因此,注意我下面讲的许多问题在其他引擎上可能完全不同,例如在循环中进行字符串拼接,通常认为需要用Array.join的方式,但是由于 SpiderMonkey等引擎对字符串的“+”运算做了优化,结果使用Array.join的效率反而不如直接用“+”!但是如果考虑IE6,则其他浏 览器上的这种效率的差别根本不值一提。

JS优化与其他语言的优化也仍然有相同之处。比如说,不要一上来就急吼吼的做优化,那样毫无意义。优化的关键,仍然是要把精力放在最关键的地方, 也就是瓶颈上。一般来说,瓶颈总是出现在大规模循环的地方。这倒不是说循环本身有性能问题,而是循环会迅速放大可能存在的性能问题。

所以第二原则就是以大规模循环体为最主要优化对象。

以下的优化原则,只在大规模循环中才有意义,在循环体之外做此类优化基本上是没有意义的。

目前绝大多数JS引擎都是解释执行的,而解释执行的情况下,在所有操作中,函数调用的效率是较低的。此外,过深的prototype继承链或者多 级引用也会降低效率。JScript中,10级引用的开销大体是一次空函数调用开销的1/2。这两者的开销都远远大于简单操作(如四则运算)。

所以第三原则就是尽量避免过多的引用层级和不必要的多次方法调用。

特别要注意的是,有些情况下看似是属性访问,实际上是方法调用。例如所有DOM的属性,实际上都是方法。在遍历一个NodeList的时候,循环 条件对于nodes.length的访问,看似属性读取,实际上是等价于函数调用的。而且IE DOM的实现上,childNodes.length每次是要通过内部遍历重新计数的。(My god,但是这是真的!因为我测过,childNodes.length的访问时间与childNodes.length的值成正比!)这非常耗费。所以 预先把nodes.length保存到js变量,当然可以提高遍历的性能。

同样是函数调用,用户自定义函数的效率又远远低于语言内建函数,因为后者是对引擎本地方法的包装,而引擎通常是c,c++,java写的。进一步,同样的功能,语言内建构造的开销通常又比内建函数调用要效率高,因为前者在JS代码的parse阶段就可以确定和优化。

所以第四原则就是尽量使用语言本身的构造和内建函数。

这里有一个例子是高性能的String.format方法。 String.format传统的实现方式是用String.replace(regex, func),在pattern包含n个占位符(包括重复的)时,自定义函数func就被调用n次。而这个高性能实现中,每次format调用所作的只是一 次Array.join然后一次String.replace(regex, string)的操作,两者都是引擎内建方法,而不会有任何自定义函数调用。两次内建方法调用和n次的自定义方法调用,这就是性能上的差别。

同样是内建特性,性能上也还是有差别的。例如在JScript中对于arguments的访问性能就很差,几乎赶上一次函数调用了。因此如果一个 可变参数的简单函数成为性能瓶颈的时候,可以将其内部做一些改变,不要访问arguments,而是通过对参数的显式判断来处理。

比如:
Java代码
1. function sum() {
2. var r = 0;
3. for (var i = 0; i < arguments.length; i++) {
4. r += arguments[i];
5. }
6. return r;
7. }

这个sum通常调用的时候个数是较少的,我们希望改进它在参数较少时的性能。如果改成:
Java代码
1. function sum() {
2. switch (arguments.length) {
3. case 1: return arguments[0];
4. case 2: return arguments[0] + arguments[1];
5. case 3: return arguments[0] + arguments[1] + arguments[2];
6. case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3];
7. default:
8. var r = 0;
9. for (var i = 0; i < arguments.length; i++) {
10. r += arguments[i];
11. }
12. return r;
13. }
14. }

其实并不会有多少提高,但是如果改成:
Java代码
1. function sum(a, b, c, d, e, f, g) {
2. var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0;
3. if (g === undefined) return r;
4. for (var i = 6; i < arguments.length; i++) {
5. r += arguments[i];
6. }
7. return r;
8. }

就会提高很多(至少快1倍)。

最后是第五原则,也往往是真实应用中最重要的性能障碍,那就是尽量减少不必要的对象创建。

本身创建对象是有一定的代价的,但是这个代价其实并不大。最根本的问题是由于JScript愚蠢之极的垃圾回收调度算法,导致随着对象个数的增加,性能严重下降(据微软的人自己说复杂度是O(n^2))。

比如我们常见的字符串拼接问题,经过我的测试验证,单纯的多次创建字符串对象其实根本不是性能差的原因。要命的是在对象创建期间的无谓的垃圾回收的开销。而Array.join的方式,不会创建中间字符串对象,因此就减少了那该死的垃圾回收的开销。

因此,如果我们能把大规模对象创建转化为单一语句,则其性能会得到极大的提高!例如通过构造代码然后eval——实际上PIES项目中正在根据这个想法来做一个专门的大规模对象产生器……

好了上面就是偶总结的JS优化五大原则。

一、正则表达式的创建代码
---
这样的创建代码实在冗余:
var fnRE = /functor_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}/i;
var objRE = /object_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}$/i;
var objRE_r = /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_r/i;
var objRE_a = /object_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_a/i;
var objRE_m = /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_m/i;
var objRE_d = /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_d/i;

仔细读来,其实就是一个添加前后缀的GUID。那么可否写成如下:
var GUID = ‘([0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12})’;
var fnRE = new RegExp(‘(functor_)’+ GUID, ‘i’);
var objRE = new RegExp(‘(object_)’ + GUID + ‘$’, ‘i’);
var objRE_r = new RegExp(‘(radio_)’ + GUID + ‘_(r)’, ‘i’);
var objRE_a = new RegExp(‘(object_)’ + GUID + ‘_(a)’, ‘i’);
var objRE_m = new RegExp(‘(radio_)’ + GUID + ‘_(m)’, ‘i’);
var objRE_d = new RegExp(‘(radio_)’ + GUID + ‘_(d)’, ‘i’);

这里看起来是用了字符串连接运算,但由于变量声明只运算一次,因此对效率没什么影响。而且可读性强了,修改起来也方便。
读注意这里用到了正则表达式中的分组'( )’,这在后面是会很有用的。
二、正则使用中的分组
---
代码总是通过
aryAList[_match[0].split(“_”)[1]] = “a_”;
这样的形式来从匹配中分离GUID,但如果使用上面的分组,那么这项运算就不必要了。简单的使用
aryAList[_match[2]] = “a_”;
就可以得到结果。
三、应注意DOM引用的耗时
---
代码中,在循环内不断地访问DOM对象的成员,然而DOM对象的成员存取是耗时的。更细的说,每一个成员
都会通过COM组件封装,因此效率是差的。所以下面的代码:
else if ((_match = _obj.name.match(objRE_m)) != null) {
}
else if ((_match = _obj.name.match(objRE_d)) != null) {
}
应当被改作:
var name = _obj.name;
else if ((_match = name.match(objRE_m)) != null) {
}
else if ((_match = name.match(objRE_d)) != null) {
}
四、过于复杂的逻辑
---
代码过于依赖其它语言的编程经验,而忽略了JavaScript的自身特性。因此实现的逻辑中规中矩,但是难以
扩展,而且复杂。例如循环中的大量if..else if …连用。后文单独给出这部分的优化。
五、从StringBuilder()接口来看,优化程度不够
---
文章提到StringBuilder是一个字符串构建的高效对象。我留意到它的使用是:
objectListEx.append(_id + “:” + _r + “:” + _a + “:” + _m + “:” + _d + “;”);
那么可以说这个对象的优化是不够的。因为这里传入一个字符串参数,而传入参数又用字符串连接运算,
效率提升很有限。
如果StringBuilder是用array.join来实现字符串拼接的话,那么更加良好的方式是允许在append中使用多
个参数。例如:
objectListEx.append = function() {
this.push.apply(this, arguments);
}
objectListEx.toString = function() {
return this.join(”);
}
那么,上例的添加就可以写成:
objectListEx.append(_id , “:” , _r , “:” , _a , “:” , _m , “:” , _d , “;”);
这就避免了多余的字符串连接运算。
六、新的优化后版本
---
// optimized version
var functorListEx = new StringBuilder();
var objectListEx = new StringBuilder();
var coll = document.getElementsByTagName(“INPUT”);

// regular expression for matching
var GUID = ‘([0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12})’;
var fnRE = new RegExp(‘(functor_)’+ GUID, ‘i’);
var objRE = new RegExp(‘(object_)’ + GUID + ‘$’, ‘i’);
var objRE_r = new RegExp(‘(radio_)’ + GUID + ‘_(r)’, ‘i’);
var objRE_a = new RegExp(‘(object_)’ + GUID + ‘_(a)’, ‘i’);
var objRE_m = new RegExp(‘(radio_)’ + GUID + ‘_(m)’, ‘i’);
var objRE_d = new RegExp(‘(radio_)’ + GUID + ‘_(d)’, ‘i’);
// helper data structures used by optimized algorithm
var aryObjList = new Array();
var aryRList = new Array();
var aryAList = new Array();
var aryMList = new Array();
var aryDList = new Array();
var aryList = {
r: aryRList,
a: aryAList,
m: aryMList,
d: aryDList
}
// one pass scan to traverse the nodes collection (coll) to build functorListEx
// and intermediate arrays
for (var i=0,imax=coll.length; i var _obj = coll[i];
if (!_obj.checked) continue; // if (_obj.type != “checkbox” && _obj.type != “radio”) continue;
var id = _obj.id, name = _obj.name;
var _match = id.match(fnRE) || name.match(objRE_r) || id.match(objRE_a) ||
name.match(objRE_m) || name.match(objRE_d) || id.match(objRE);
if (!_match) continue;
var tag = _match[3], tag2 = tag+’_’, guid = _match[2];
switch (tag) {
‘a’: aryList[tag][guid] = tag2; break;
‘r’, ‘m’, ‘d’:
aryList[tag][guid] = tag2 + _obj.value; break;
default :
if (_match[1]==’functor_’) {
functorListEx.append(guid, “;”)
}
else { // for _match[1]==’object_’
aryObjList.push(guid)
}
}
}
// further process to build objectListEx from the intermediate arrays
for (var i=0, imax=aryObjList.length; i var id = aryObjList[i];
var r = aryRList[id] || “”;
var a = aryAList[id] || “”;
var m = aryMList[id] || “”;
var d = aryDList[id] || “”;
objectListEx.append(id , “:” , r , “:” , a , “:” , m , “:” , d , “;”);
}
七、又一处小的优化
---
刚才想了想,其实上面代码中的switch还是啰嗦了。下面做一下下小的优化:
switch (_match[1] + tag) {
‘functor_undefined’: functorListEx.append(guid, “;”); break;

‘object_undefined’: aryObjList.push(guid); break;

‘object_a’: aryList[tag][guid] = tag2 ; break;

default: // for r,m,d
aryList[tag][guid] = tag2 + _obj.value;

发表评论

评论