本文是阅读《高性能JavaScript》一书后,从 编程实践 模块对JavaScript性能优化做了部分总结,记录一下。可能总结的不好,不是很完整,也希望各位大佬能多给出一些建议。万分感谢!


避免双重求值

当你在 JavaScript 代码中执行另一段 JavaScript 代码时,都会导致双重求值的性能消耗。此代码首先会以正常的方式求值,然后在执行过程中对包含于字符串找哪个的代码发起另一个求值运算。双重求值是一项低价昂贵的操作,它比直接包含的代码执行速度慢许多。

  • 尽量不使用 eval() 函数 和 Function() 构造函数

  • 使用 setTimeout() 和 setInterval() 第一个参数尽量传入函数而不是字符串

使用原生代码和 eval() 分别读取 10 000 个数组项的速度对比:

浏览器类别 原生代码(毫秒) eval()(毫秒)
Firefox 3 10.57 822.62
Firefox 3.5 0.72 141.54
Chrome 1 5.7 106.41
Chrome 2 5.17 54.55
Internet Explorer 7 31.25 5086.13
Internet Explorer 8 40.06 420.55
Opera 9.64 2.01 402.82
Opera 10 Beta 10.52 315.16
Safari 3.2 30.37 306.6
Safari 4 22.16 54.47

优化后的 JavaScript 引擎通常会魂村住那些使用了 eval() 且重复运行的代码。如果你在 Safari 4 和所有版本 Chrome 中对同一段代码字符串反复求值,你会看到显著的性能提升。


使用 Object/Array 直接量

对象属性和数组项的数量越多,使用直接量的好处就越明显。


避免重复工作

先来看一段基本代码,之后对其进行优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function addHandle(target, eventType, handler) {
if (target.addEvenetListener) { // DOM2 Events
target.addEvenetListener(eventType, handler, false);
} else { // IE
target.attachEvent("on" + eventType, handler);
}
}

function removeHandle(target, eventType, handler) {
if (target.removeEvenetListener) { // DOM2 Events
target.removeEvenetListener(eventType, handler, false);
} else { // IE
target.detachEvent("on" + eventType, handler);
}
}

使用延迟加载进行优化

调用延迟加载函数时,第一次总会消耗较长的时间,因为它必须运行监测接着再调用另一个函数完成任务。然随后调动相同的函数会更快,因为不需要再执行检测逻辑。当一个函数在页面中不会立刻调用时,延迟加载是最好的选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function addHandle(target, eventType, handler) {
if (target.addEvenetListener) { // DOM2 Events
addHandle = function(target, eventType, handler) {
target.addEvenetListener(eventType, handler, false);
};
} else { // IE
addHandle = function(target, eventType, handler) {
target.attachEvent("on" + eventType, handler);
};
}

// 调用新函数
addHandle(target, eventType, handler)
}

function removeHandle(target, eventType, handler) {
if (target.removeEvenetListener) { // DOM2 Events
removeHandle = function(target, eventType, handler) {
target.removeEvenetListener(eventType, handler, false);
}
} else { // IE
removeHandle = function(target, eventType, handler) {
target.detachEvent("on" + eventType, handler);
}
}
// 调用新函数
removeHandle(target, eventType, handler)
}

使用条件预加载进行优化

条件预加载确保所有函数调用消耗的时间相同。其代价是需要在脚本加载时就检测,而不是加载后。预加载适用于一个函数马上就要被用到,并且在整个页面的生命周期中频繁出现的场合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var addHandler = document.body.addEvenetListener ? 
function(target, eventType, handler) {
target.addEvenetListener(eventType, handler, false);
} :
function(target, eventType, handler) {
target.attachEvent("on" + eventType, handler);
};
var removeHandler = document.body.removeEvenetListener ?
function(target, eventType, handler) {
target.removeEvenetListener(eventType, handler, false);
} :
function(target, eventType, handler) {
target.detachEvent("on" + eventType, handler);
};


使用速度快的部分

位操作符

使用位操作符比 JavaScript 其他数学运算和布尔操作相比要快很多。

  • 按位与 &
    • 两个操作数的对应位数都是 1 时,则在该位返回 1
  • 按位或 |

    • 两个操作数的对应位数只要一个为 1 时,则在该位返回 1
  • 按位异或 ^

    • 两个操作数的对应位数只有一个为 1,则在该位返回 1
  • 按位取反 ~

    • 遇 0 则返回 1,反之亦然
范例
  • 位运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 普通写法
    for (var i = 0, len = rows.length; i < len; i ++) {

    if (i % 2) {
    className = "even";
    } else {
    className = "odd";
    }

    // 增加class
    ……
    }

    // 使用位操作符进行优化
    for (var i = 0, len = rows.length; i < len; i ++) {

    if (i & 1) {
    className = "odd";
    } else {
    className = "even";
    }

    // 增加class
    ……
    }

    // 优化后的版本比普通版本快了 50%
  • 位掩码

    用于处理同时存在多个布尔值的情形。期数即使用单个数字的每一位来判断是否选项成立,从而有效的把数字转换成有不二指标及组成的数组。掩码中的每个选项的值都等于 2 的幂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var OPTION_A = 1;
    var OPTION_B = 2;
    var OPTION_C = 4;
    var OPTION_D = 8;
    var OPTION_E = 16;

    // 通过这些选项,可以用按位或运算创建一个数字来包含多个设置选项
    var options = OPTIONS_A | OPTIONS_C | OPTIONS_D;

    // 如果该选项未设置则运算结果为 0, 如果已设置则运算结果为 1

    // 选项 A 是否在列表中?
    if (options & OPTIONS_A) {
    // 代码处理
    }

    // 选项 B 是否在列表中?
    if (options & OPTIONS_B) {
    // 代码处理
    }

    // 像这样的位掩码运算速度非常快,原因正如前面提到的,计算操作发生在系统底层。

JavaScript 也支持按位左移(<<), 按位右移(>>),和无符号右移(>>>)等位运算符

原生方法

无论你的 JavaScript 代码如何优化,都永远不会比 JavaScript 引擎提供的原生方法更快

  • 当你想进行数学运算时,请先查看 Math 对象

  • 选择器 API,即使 JQuery 提供的方法也比原生方法要慢

  • 当原生方法可用时,尽量使用它们。特别是数学运算和 DOM 操作。用编译后的代码做更多的事情,你的代码就会越快


小结

  • 通过避免使用 eval() 和 Function() 构造器来避免双重求值带来的性能消耗。同样的,给 setTimeout 和 setInterval() 传递函数而不是字符串作为参数

  • 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快

  • 避免做重复的工作。当需要检测浏览器时,可使用延迟加载或条件预加载

  • 在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算

  • JavaScript 的原生方法总会比你写的任何代码都要快。尽量使用原生方法