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

比如:

  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通常调用的时候个数是较少的,我们希望改进它在参数较少时的性能。如果改成:

  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. }

其实并不会有多少提高,但是如果改成:

  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优化五大原则。

js优化原则的更多相关文章

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

    来自知乎的问题:JavaScript 怎样高效拼接字符串? 请把以下用于连接字符串的JavaScript代码修改为更高效的方式: var htmlString ='< div class=”co ...

  2. JavaScript性能优化篇js优化

    JavaScript性能优化篇js优化   随着Ajax越来越普遍,Ajax引用的规模越来越大,Javascript代码的性能越来越显得重要,我想这就是一个很典型的例子,上面那段代码因为会被频繁使用, ...

  3. 前端Yslow的23个优化原则

    前端Yslow的23个优化原则 最常遇见的前端优化问题. Yslow是雅虎开发的基于网页性能分析浏览器插件,可以检测出网页的具体性能值,并且有著名的Yslow 23条优化规则,这23条,就够我们玩的了 ...

  4. Oracle SQL 优化原则(实用篇)

    由于SQL优化优化起来比较复杂,并且还受环境限制,在开发过程中,写SQL必须遵循以下几点原则: 1.Oracle 采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他Where ...

  5. oracle优化原则(二)

    SQL优化原则 二.SQL语句编写注意问题 www.2cto.com 下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍.在这些where子句中,即使某些列存在索引,但是由于编写了劣质 ...

  6. require.js优化器

    项目发布前,require.js优化器可以合并require.js各个模块. 官网: http://requirejs.org/docs/optimization.html 安装 npm instal ...

  7. 我的mysql数据库sql优化原则

    原文 我的mysql数据库sql优化原则 一.前提 这里的原则 只是针对mysql数据库,其他的数据库 某些是殊途同归,某些还是存在差异.我总结的也是mysql普遍的规则,对于某些特殊情况得特殊对待. ...

  8. 关于js优化和css优化

    css优化: 1.css代码的压缩. 2.css文件的合并. 3.不滥用float,因为float在渲染时计算量比较大,所以尽量减少使用float. 4.避免在html标签中写style属性. js优 ...

  9. 前端js优化方案(连续更新)

    最近在读<高性能javascript>,在这里记录一下读后的一些感受,顺便加上自己的一些理解,如果有兴趣的话可以关注的我的博客http://www.bloggeng.com/,我会不定期发 ...

随机推荐

  1. 使用 Flex 库项目---打包swc

    来源:http://help.adobe.com/zh_CN/flashbuilder/using/WSe4e4b720da9dedb5-1a92eab212e75b9d8b2-7ffe.html   ...

  2. git 使用整理

    git使用 Ubuntu 14.04 安装 apt-get install git 版本查看 git --version git version 配置(全局变量,默认值.可在具体仓库中设置改仓库使用的 ...

  3. Android Studio 如何将包名按层级展示

    在project视图右上角有个“设置”的按钮,点开,然后将上图所圈部分去勾选就可以了.

  4. nuget pack 时不包含依赖包(而不是引用项目的dll,区别于IncludeReferencedProjects)

    Excluding development dependencies when creating packages Some NuGet packages are useful as developm ...

  5. spring xsd

    http://ljhzzyx.blog.163.com/blog/static/3838031220132239471608/ spring配置文件报找不到xsd文件错误 2013-03-23 10: ...

  6. 1.2 eclipse使用 :working set

    working set可以是相当于文件夹~~有多个project时, 分别存放在不同的 workingset下,可以方便管理 新建或编辑 working set时,需要记住选择project *可参照 ...

  7. 非常简单的oracle和mysql数据互传

    工具是navicat,我用的是Navicat Premium 10: 这个工具可以同时连接mysql和oracle,如图: 同时连接上这两个库之后 工具->数据传输 左边是数据源,右边是导入目标 ...

  8. java解析XML获取城市代码

    运行前先导入dom4j架包,由于我们公司用的代理服务器所以下面我设置了代理ip,不需要的可直接忽略 package com.chengshidaima.tools; import java.io.Bu ...

  9. Servlet事件监听器

    监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行. 面试题:请描述一下java事件监听机 ...

  10. DZY Loves Partition

    问题描述 DZY喜欢拆分数字.他想知道能否把nn拆成恰好kk个不重复的正整数之和. 思考了一会儿之后他发现这个题太简单,于是他想要最大化这kk个正整数的乘积.你能帮帮他吗? 由于答案可能很大,请模10 ...