从字符串拼接看JS优化原则

来自知乎的问题:JavaScript 怎样高效拼接字符串?html

请把如下用于链接字符串的JavaScript代码修改成更高效的方式:前端

var htmlString ='< div class=”container” > ' + '< ul id=”news-list” > ';
for (var i = 0; i < NEWS.length; i++) {
htmlString += '< li > < a href="' +NEWS[i].LINK +'" > +NEWS[i].TITLE + '< /a > < /li >';
}
htmlString += '< /ul > < /div > ';

zhiyelee的回答:
java

 
JS优化已经讨论了不少了,最近又看到 aimingoo的一篇。大致上,aimingoo的说法都是很是正确的。 

除了像aimingoo作个案研究外,这里我想从更通常的角度总结在浏览器编程中JS优化的几个原则。 

首先,与其余语言不一样,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,而是经过对参数的显式判断来处理。 

好比: 
function sum() {  
    var r = 0;  
    for (var i = 0; i < arguments.length; i++) {  
        r += arguments[i];  
    }  
    return r;  
}  

 
这个sum一般调用的时候个数是较少的,咱们但愿改进它在参数较少时的性能。若是改为: 
 
function sum() {  
    switch (arguments.length) {  
        case 1: return arguments[0];  
        case 2: return arguments[0] + arguments[1];  
        case 3: return arguments[0] + arguments[1] + arguments[2];  
        case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3];  
        default:  
            var r = 0;  
            for (var i = 0; i < arguments.length; i++) {  
                r += arguments[i];  
            }  
            return r;  
    }  
}  
其实并不会有多少提升,可是若是改为: 
function sum(a, b, c, d, e, f, g) {  
    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;  
    if (g === undefined) return r;  
    for (var i = 6; i < arguments.length; i++) {  
        r += arguments[i];  
    }  
    return r;  
}  
就会提升不少(至少快1倍)。 

最后是第五原则,也每每是真实应用中最重要的性能障碍,那就是 尽可能减小没必要要的对象建立。 

自己建立对象是有必定的代价的,可是这个代价其实并不大。最根本的问题是因为 JScript愚蠢之极的垃圾回收调度算法,致使随着对象个数的增长,性能严重降低(据微软的人本身说复杂度是O(n^2))。  好比咱们常见的字符串拼接问题,通过个人测试验证,单纯的屡次建立字符串对象其实根本不是性能差的缘由。要命的是在对象建立期间的无谓的垃圾回收的开销。而Array.join的方式,不会建立中间字符串对象,所以就减小了那该死的垃圾回收的开销。  所以,若是咱们能把大规模对象建立转化为单一语句,则其性能会获得极大的提升!例如经过构造代码而后eval——实际上PIES项目中正在根据这个想法来作一个专门的大规模对象产生器……  好了上面就是偶总结的JS优化五大原则。  除了这些原则之外,还有一些特殊状况,如DOM的遍历,之后有时间再作讨论。
相关文章
相关标签/搜索