各浏览器JS引擎进行字符串连接效率研究及探讨

前言

其实本篇讲的东西在平时可能很常见,也或许很偶然遇见,或许有心之人会发现话题在高性能javascript这本书上是有提过的,当时小熊在看完该书后觉得是这么回事,不过今儿偶在自己机子上写了回demo,发现确有一定的不符。当然这性能在绝大多数情况下不会对客户端有太大的影响,即便是上千的字符串连接也都还好说(稍微注意一下就好了),这里我专门开一篇文章来尝试各种情况下的字符串连接效率对比。

(由于,之前写过一次测试环境没有顾及chakra引擎的感受(IE9),现更新IE9效率以及为求测试结果准确,所有浏览器测试基数一视同仁。)

准备

首先我们命个命题好了,比如我们通过ajax从后台获取一些更新,拉回一个充斥着万级以上数据的json表之类,现在我们必须将这些数据放在一个列表里,那么我想,通常方法很可能是使用LI标记并最终配合UL的innerHTML来载入内部节点,以此为例,先准备调试结构:

结构

  1. <p>耗时:<id="time"></b>ms</p>

做一个计时器准备的标记,让我们输出脚本耗时时间,接着我们准备JS:

  1. var doc = document, //传个引用,在这段代码除了缩写在性能上其实没啥体现,   
  2. count = 3000, //循环次数(测试基数)   
  3. text = '偶是内容',   
  4. innerHTML = ''//一个存储Str   
  5. time = doc.getElementById('time'), //耗时对象获取   
  6. timeop; //开始时间   
  7.   
  8. timeop = new Date(); //存储开始时间戳      
  9. /**    
  10. * 这儿将是各种loop代码栖息之处    
  11. **/     
  12. time.innerHTML = new Date() - timeop; //计时结束,与开始时间互减获取差值(ms)  

这儿偶放出第一个版本,也是相当亲切滴平民版本:

  1. //1-传统版本
  2. while(count--) {
  3.     innerHTML += '<li>' + text + '</li>';
  4. }

这是再正常不过的方式了,不断地自连接,用高性能JS一书的话来说,这里经过5个步骤:

  1. 从内存哥中劈开空间以创建临时字符串(以下称临时哥);
  2. '<li>' 作为基础字符串(即第一个字符串,以下称基础兄),基础兄在字符串连接时会享受优待(执行机制将会在分配内存时更多的分配给基础字符串)先拷贝 text 至基础兄末端,再拷贝'</li>'至基础兄末端,然后将值传给临时哥;
  3. 临时哥再与innerHTML进行字符串连接;
  4. 将连接结果传给innerHTML;

ok,这里放出小熊这儿的浏览器性能耗时测试结果:

性能排名榜(本榜排名由于测试基数有差距,仅作参考讨论):

FF , SF < IE9 < OP < GG < IE8 < IE7 < IE6

可以见得大多数JS引擎对最传统的字符串拼接性能还是不错滴,除了IE6/7俩哥们较为奇葩,但是对于咱来说,如果大量数据又必须照顾IE6/7的话,这种做法显然(至少IE6/7市场还有点大)不可取。

 

哎,不管了,偶来第二个版本,根据高性能js一书所说,如果右侧复制段只有一个值,则不会产生临时哥,这就让大伙儿来看看第二个版本

  1. //2-省去临时字符串
  2. while(count--) {
  3.     innerHTML += '<li>';
  4.     innerHTML += text;
  5.     innerHTML += '</li>';
  6. }

这里由于每次右边只有一个哥们等着被innerHTML连接,所以自然而然不会创建临时字符串。送上结果图:

性能排名榜:

FF < IE9 < SF < GG < OP < IE8 < IE6 < IE7

偶...偶笑了,哈哈,在高性能js书上也指明IE6/7的执行连接方式与IE8+还有其他标准浏览器不一样,用这种方式在IE6/7下确实更慢!慢了将近3倍的境界思密达啊(这两位倒数的哥们,能慢成这样,确实如高性能JS书上所说:“这是其执行连接操作的底层机制决定“)!

除此此外,这次排名跟上次排名除了IE6/7两个互相暧昧以外,几乎没特别变化,但在耗时结果上,SF/OP反而慢了起来,IE9/GG快了一丁点儿,这结论看来跟书上结论尚有所冲突。

 

咱先抛开这个不提,接着对维护及可读性有大爱的亲们一定会说偶滴:“得这么写三条语句,太TMD麻烦思密达了”,好好好,咱用一条语句来达到同样效应,上第三个版儿

  1. //3-方便同时也省去临时字符串
  2. while(count--) {
  3.     innerHTML = innerHTML + '<li>' + text + '</li>';
  4. }

好了,这里注意”+=“操作符在这里被换成了”=“操作符,原因是这里将innerHTML作为基础兄(本身就是个字符串变量了),所以不再需要临时字符串,紧接着,后面三个依次被拷贝到基础兄的末端,最后返回给innerHTML:

性能排名榜:

FF , SF , IE9 < GG < IE8 < OP < IE6 < IE7

如果有看过上次测试基数不同的亲可能知道OP用这个方法在处理长字符的时候会变得异常慢,这次的基数没有太大,所以OP这次没有挂掉,不过看来OP对这方案不大感冒。

另外,SF/GG/IE9/IE8比刚才的快了点儿,FF无变化,除了OP可能慢以外,主要这个方法可读性较好,如果OP在亲的站市场份额较低的话,可以采取本方案。

 

接下来到了技巧而出名的数组连接字符串方式了,废话不说,上码(第四版):

  1. //4-数组栈方式连接字符串
  2. innerHTML = [innerHTML]; //重载为数组类型
  3. while(count--) {
  4.     innerHTML.push('<li>');
  5.     innerHTML.push(text);
  6.     innerHTML.push('</li>');
  7. }
  8. innerHTML = innerHTML.join(''); //数组无缝转字符串

本次不存在啥基础字符串,直接利用栈方法将字符串一个个push到数组内部,看效率图:

性能排名榜:

IE9 < SF < GG < FF < OP < IE8 < IE7 < IE6

唷~~~IE6/7相当给力,给强上去了,其他浏览器稍微慢了一点毫秒级,这点毫秒级完全可以忽视了,这方法如同书上所说,对IE6/7有着相当显著的效果,不过要写上三句push有点麻烦,偶一开始还忘了,push是可以一次推入多几个的数据入栈滴,换下一段(第五个版本):

  1. //5-方便的数组栈方式连接字符串
  2. innerHTML = [innerHTML]; //重载为数组类型
  3. while(count--) {
  4.     innerHTML.push('<li>', text, '</li>');
  5. }
  6. innerHTML = innerHTML.join(''); //数组无缝转字符串

看上去感觉舒服了些,结果图:

性能排名榜:

IE9 < SF < GG < FF < OP < IE8 < IE7 < IE6

噢,算是个比较匀称的结果,IE集体大放异彩!SF/GG跟上次无变化,FF变慢了点儿,OP兄快了,嗯,总而言之要照顾IE6/7的话,这还算是个不错的方法,可以用来使用之。

 

接着,记得数组还有个concat,不过想想concat的实现过程,可能效率会很低下,不过不试试咋知道呢?第六版上演:

  1. 组连接字符串concat版
  2. innerHTML = [innerHTML]; //重载为数组类型
  3. while(count--) {
  4.     innerHTML = innerHTML.concat('<li>', text, '</li>');
  5. }
  6. innerHTML = innerHTML.join(''); //数组无缝转字符串

。。。。。。。。。。。。。。。结果图:无;

性能排名榜:

...All Die...

啥?为啥?真的 - -,没想到还真然是”大屠杀“啊!!!,全浏览器die,一片”鲜红“的屏幕(还好没给偶蓝屏了),这方法看来行不通吗?难道偶代码哪错了,根据底层实现猜测和多次调整基数检验,确定是因为Array.concat开销实在过大,偶降低了测试基数,最后得到个大致的答案:

  1. IE6、7用此方法效率较第一版24239倍;
  2. IE8此方法效率较第一版2344倍;
  3. IE91653倍;
  4. FF1398倍;
  5. SF4193倍;
  6. GG2619倍;
  7. OP5627倍。

 

一个理所当然又恐怖无比的答案,好吧,劳资不用这方法了,果断舍弃,第七版

  1. //7-数组非栈方式连接字符串
  2. innerHTML = [innerHTML]; //重载为数组类型
  3. var i = innerHTML.length;
  4. while(count--) {
  5.     innerHTML[i++] = '<li>';
  6.     innerHTML[i++] = text;
  7.     innerHTML[i++] = '</li>';
  8. }
  9. innerHTML = innerHTML.join(''); //数组无缝转字符串

结果大快人心,脱离栈这玩意儿还能有这等效率啊:

性能排名榜:

IE9 < FF , SF < GG < OP < IE8 < IE7 < IE6

结果与第五版比基本是伯仲之间,IE6/7没有丢下,性能不错,GG/SG/FF/IE8/IE9相比第四版和第五版只有进步没有退步,除了OP相对慢了点,不过完全是可忍受的区域(高性能JS书上用的就是这段,如果不涉及OP的访客这段性能最佳,十全十美的亲还请选第五版)。

 

最后一个研究的,字符串concat方法制作,这第八版效果不大理想(想早点离席的可直接撤了 - -):

  1. //8-字符连接字符串concat版
  2. while(count--) {
  3.     innerHTML = innerHTML.concat('<li>', text, '</li>');
  4. }

GG哥,你肿么了...:

性能排行榜:

IE9 < FF < SF < OP < IE8 < GG < IE7 < IE6

一开始被吓着了,看到GG哥的测试结果有点不相信,但却是从这点上可以看出GG对于字符串concat方法拼接上有一定性能开销,再加上IE6/7这两基友的结果回到一开始那不堪入目的样子了,这条索性废弃(高性能JS一书也有指明这条方案,不过似乎也不大理想,小熊额外话:”只是没想到居然这么不理想 - -“)。

 

总结

综上所述,综合IE6/7结果,第七版第五版的在平衡性上较其他几个比较有优势,当然有人指出,这年头浏览器会不断更新(比如Chakra引擎(IE9)的拼接能力在本次测试中多次荣获第一名),真的有必要在乎这细节性能吗?

是的,现代浏览器按照第一版是即简单便捷又具备可读性的,通常的现代浏览器在引擎底层对连接已经有了很优越的性能,不过若访客多用老版IE的情况下(D版XP效应...),确实咱不可避免的要注意IE6/7的拼接问题,建议亲们根据自己的客户群体终端,以及代码实际有无可能遇到大量字符串拼接的情况来选择最适合自己的方式(第一版第五版第七版)。

  1. avatar
    suN#32

    - -这是微博吗

    • avatar
      ibearB1

      @suN
      仿围脖的主题,呵呵~~

  2. avatar
    CtrlEnter#31

    狠直接也狠真实啊,IE6,7最少也有几十倍的差距,当存储量和次数几何上涨。。。心碎啊

    • avatar
      ibearB1

      @CtrlEnter
      哈哈哈,我这不知道差距并不大,你那是怎么测试IE6/7的,我这用的IE Collection,不知道准确性。

  3. avatar
    小咖啡豆子#30

    学习了,牛!

  4. avatar
    西门#29

    这几天就碰到这个问题,用第五版解决了拼接问题,但是渲染出来之后 大量插入的DOM 要从新绑定事件,也是个耗时的过程。除了冒泡,延时。还有别的方案能解决绑定事件慢的问题么

    • avatar
      ibearB1

      @西门
      嗯,跟你说的一样,利用冒泡做事件委派,使得target父层配合target来处理了,jq下delegate(1.7后用$.on)

  5. avatar
    耗子#28

    好东西啊,研究得真深入

    • avatar
      ibearB1

      @耗子
      哥,你滴HTML5玩得不比偶深多啦?

  1. 目前还没有trackbacks.

  2. Trackbacks被禁用了

发表评论 进楼快捷键: ctrl+Enter取消回复

电子邮件地址不会被公开。

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>