变量查找优化
变量声明带上var
- 如果声明变量忘记了var,那么js引擎将会遍历整个作用域查找这个变量,结果不管找到与否,都是悲剧。
- 如果在上级作用域找到了这个变量,上级作用域变量的内容将被无声的改写,导致莫名奇妙的错误发生。
- 如果在上级作用域没有找到该变量,这个变量将自动被声明为全局变量,然而却都找不到这个全局变量的定义。
- 基于上面逻辑,性能方面不带var声明变量自然要比带var速度慢
具体可以参考http://jsperf.com/withvar-withoutvar。下面是个简单的结果截图,蓝色为带var的情况,越长说明 速度越快。

慎用全局变量
-
全局变量需要搜索更长的作用域链。
-
全局变量的生命周期比局部变量长,不利于内存释放。
-
过多的全局变量容易造成混淆,增大产生bug的可能性。
全局变量与局部变量的测试可以参考http://jsperf.com/local-global-var
以上两条还可以得出一条JavaScript常用的编程风格 ,具有相同作用域变量通过一个var声明 。
这样方便查看该作用域所有的变量,JQuery源代码中就是用了这种风格。例如下面源代码
https://github.com/jquery/jquery/blob/master/src/core.js
|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 | jQuery.extend = jQuery.fn.extend = ``function``() { var options, name, src, copy, copyIsArray, clone,target = arguments[0] || {},i = 1,length = arguments.length,deep = ``false``; |
缓存重复使用的全局变量
-
全局变量要比局部变量需要搜索的作用域长
-
重复调用的方法也可以通过局部缓存来提速
-
该项优化在IE上体现比较明显
缓存与不缓存变量的测试可以参考http://jsperf.com/localvarcache
JQuery源代码中也是用了类似的方法,https://github.com/jquery/jquery/blob/master/src/selector-native.js
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 | var docElem = window.document.documentElement, selector_hasDuplicate, matches = docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector, selector_sortOrder = ``function``( a, b ) { // Flag for duplicate removal if ( a === b ) { ``selector_hasDuplicate = ``true``; ``return 0; } |
避免使用with
with语句将一个新的可变对象推入作用域链的头部,函数的所有局部变量现在处于第二个作用域链对象中,从而使局部变 量的访问代价提高。
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 | var person = { ``name: "Nicholas", ``age: 30 } function displayInfo() { ``var count = 5; ``with (person) { ``alert(name + ``' is ' + age); ``alert(``'count is ' + count); ``} } |
以上代码的结果将name和age两个变量推入第一个作用域,如下图所示,

使用with与不使用with的测试可以参考http://jsperf.com/with-with
核心语法优化
通过原型优化方法定义
-
如果一个方法类型将被频繁构造,通过方法原型从外面定义附加方法,从而避免方法的重复定义。
-
可以通过外 部原型的构造方式初始化值类型的变量定义。(这里强调值类型的原因是,引用类型如果在原型中定义, 一个实例对引用类型的更改会影响到其他实例。)
这条规则中涉及到JavaScript中原型的概念,
- 构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。我们可 以把那些不变的属性和方法,直接定义在prototype对象上。
- 可以通过对象实例访问保存在原型中的值,不能通过对象实例重写原型中的值。
- 在实例中添加一个与实例原型同名属性,那该属性就会屏蔽原型中的属性。
- 通过delete操作符可以删除实例中的属性。
例如以下代码以及相应的内存中原型表示如下,
|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 | function Person(){} Person.prototype.name = ``"Nicholas"``; Person.prototype.age = 29; Person.prototype.job = ``"Software Engineer"``; Person.prototype.sayName = ``function``(){ ``alert(``this``.name); }; var person1 = ``new Person(); person1.sayName(); ``//"Nicholas" var person2 = ``new Person(); person2.sayName(); ``//"Nicholas" |

原型附加方法测试可以参考http://jsperf.com/func-constructor
原型附加值类型变量测试可以参考http://jsperf.com/prototype2
避开闭包陷阱
-
闭包是个强大的工具,但同时也是性能问题的主要诱因之一。不合理的使用闭包会导致内存泄漏。
-
闭包的性能不如使用内部方法,更不如重用外部方法。
由于IE浏览器的DOM是用COM来实现的, COM的内存管理是通过引用计数的方式,引用计数有个难题就是循环引用,一旦DOM 引用了闭包(例如event handler),闭包的上层元素又引用了这个DOM,就会造成循环引用从而导致内存泄漏。

关于Js内存泄漏可以参考
http://msdn.microsoft.com/en-us/library/bb250448%28v=vs.85%29.aspx
闭包与非闭包的测试http://jsperf.com/closure2
避免使用属性访问方法
-
JavaScript不需要属性访问方法,因为所有的属性都是外部可见的。
-
添加属性访问方法只是增加了一层重定向 ,对于访问控制没有意义。
使用属性访问方法示例
|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function Car() { ``this``.m_tireSize = 17; ``this``.m_maxSpeed = 250; ``this``.GetTireSize = Car_get_tireSize; ``this``.SetTireSize = Car_put_tireSize; } function Car_get_tireSize() { ``return this``.m_tireSize; } function Car_put_tireSize(value) { ``this``.m_tireSize = value; } var ooCar = ``new Car(); var iTireSize = ooCar.GetTireSize(); ooCar.SetTireSize(iTireSize + 1); |
直接访问属性示例
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 | function Car() { ``this``.m_tireSize = 17; ``this``.m_maxSpeed = 250; } var perfCar = ``new Car(); var iTireSize = perfCar.m_tireSize; perfCar.m_tireSize = iTireSize + 1; |
使用属性访问与不使用属性访问的测试http://jsperf.com/property-accessor
避免在循环中使用try-catch
-
try-catch-finally语句在catch语句被执行的过程中会动态构造变量插入到当前域中,对性能有一定影响。
-
如 果需要异常处理机制,可以将其放在循环外层使用。
循环中使用try-catch
|-------|----------------------------------------------------------------------------|
| 1 2 3 | for (``var i = 0; i < 200; i++) { ``try {} ``catch (e) {} } |
循环外使用try-catch
|-------|----------------------------------------------------------------------------|
| 1 2 3 | try { ``for (``var i = 0; i < 200; i++) {} } ``catch (e) {} |
循环内与循环外使用try-catch的测试http://jsperf.com/try-catch
使用for代替for...in...遍历数组
for...in...内部实现是构造一个所有元素的列表,包括array继承的属性,然后再开始循环。相对for循环性能要慢。
StackOverflow上对这个for和for in的问题有个经典的回答,直接原文引用,
Q: I've been told not to use "for...in" with arrays in JavaScript. Why not?
A: The reason is that one construct...
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 | var a = []; a[5] = 5; ``// Perfectly legal JavaScript that resizes the array. for (``var i=0; i<a.length; i++) { ``// Iterates over numeric indexes from 0 to 5, as everyone expects. } |
can sometimes be totally different from the other...
|-----------|------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 | var a = []; a[5] = 5; for (``var x ``in a) { ``// Shows only the explicitly set index of "5", and ignores 0-4 } |
Also consider that JavaScript libraries might do things like this, which will affect any array you create:
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 | // Somewhere deep in your JavaScript library... Array.prototype.foo = 1; // Now you have no idea what the below code will do. var a = [1,2,3,4,5]; for (``var x ``in a){ ``// Now foo is a part of EVERY array and ``// will show up here as a value of 'x'. } |
关于for和for...in...的测试可以看http://jsperf.com/forin
使用原始操作代替方法调用
方法调用一般封装了原始操作,在性能要求高的逻辑中,可以使用原始操作代替方法调用来提高性能。
原始操作
|---|------------------------------|
| 1 | var min = a < b ? a : b; |
方法实例
|---|-------------------------------|
| 1 | var min = Math.min(a, b); |
关于方法调用和原始操作的测试参考http://jsperf.com/operator-function
传递方法取代方法字符串
一些方法例如setTimeout()/setInterval(),接受字符串或者方法实例作为参数。直接传递方法对象作为参数来避免对字 符串的二次解析。
传递方法
|---|------------------------|
| 1 | setTimeout(test, 1); |
传递方法字符串
|---|--------------------------------|
| 1 | setTimeout(``'test()'``, 1); |