JavaScript性能优化

变量查找优化

变量声明带上var

  1. 如果声明变量忘记了var,那么js引擎将会遍历整个作用域查找这个变量,结果不管找到与否,都是悲剧。
  • 如果在上级作用域找到了这个变量,上级作用域变量的内容将被无声的改写,导致莫名奇妙的错误发生。
  • 如果在上级作用域没有找到该变量,这个变量将自动被声明为全局变量,然而却都找不到这个全局变量的定义。
  1. 基于上面逻辑,性能方面不带var声明变量自然要比带var速度慢

具体可以参考http://jsperf.com/withvar-withoutvar。下面是个简单的结果截图,蓝色为带var的情况,越长说明 速度越快。

慎用全局变量

  1. 全局变量需要搜索更长的作用域链。

  2. 全局变量的生命周期比局部变量长,不利于内存释放。

  3. 过多的全局变量容易造成混淆,增大产生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``; |

缓存重复使用的全局变量

  1. 全局变量要比局部变量需要搜索的作用域长

  2. 重复调用的方法也可以通过局部缓存来提速

  3. 该项优化在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

核心语法优化

通过原型优化方法定义

  1. 如果一个方法类型将被频繁构造,通过方法原型从外面定义附加方法,从而避免方法的重复定义。

  2. 可以通过外 部原型的构造方式初始化值类型的变量定义。(这里强调值类型的原因是,引用类型如果在原型中定义, 一个实例对引用类型的更改会影响到其他实例。)

这条规则中涉及到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

避开闭包陷阱

  1. 闭包是个强大的工具,但同时也是性能问题的主要诱因之一。不合理的使用闭包会导致内存泄漏。

  2. 闭包的性能不如使用内部方法,更不如重用外部方法。

由于IE浏览器的DOM是用COM来实现的, COM的内存管理是通过引用计数的方式,引用计数有个难题就是循环引用,一旦DOM 引用了闭包(例如event handler),闭包的上层元素又引用了这个DOM,就会造成循环引用从而导致内存泄漏。

关于Js内存泄漏可以参考

Memory Leaks

http://msdn.microsoft.com/en-us/library/bb250448%28v=vs.85%29.aspx

闭包与非闭包的测试http://jsperf.com/closure2

避免使用属性访问方法

  1. JavaScript不需要属性访问方法,因为所有的属性都是外部可见的。

  2. 添加属性访问方法只是增加了一层重定向 ,对于访问控制没有意义。

使用属性访问方法示例

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

  1. try-catch-finally语句在catch语句被执行的过程中会动态构造变量插入到当前域中,对性能有一定影响。

  2. 如 果需要异常处理机制,可以将其放在循环外层使用。

循环中使用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); |

相关推荐
凯瑟琳.奥古斯特1 小时前
二分查找解力扣1011最优运载能力
开发语言·c++·算法·leetcode·职场和发展
Tim_101 小时前
【C++】014、左值与右值的区别
开发语言·c++
凯小默1 小时前
10.JavaScript 中的类和对象以及继承
javascript·继承
AI视觉网奇1 小时前
BambuStudio 自定义加载页面
服务器·前端·javascript
天空'之城1 小时前
Linux 系统编程 14:Reactor 反应堆模式
linux·开发语言·网络编程·reactor 反应堆模式
Zhou1411362 小时前
Java常见面试题7
java·开发语言
^yi2 小时前
【C++】类和对象(全)
开发语言·c++
漂流瓶jz2 小时前
理解Webpack插件机制:从插件使用、各类编译对象、Tapable到自定义插件与钩子开发
前端·javascript·webpack
Dovis(誓平步青云)2 小时前
《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——HTML文件的清洗(上篇)》
开发语言·数据库·c++·搜索引擎·开源