JavaScript性能优化

标签: javascript 性能优化 | 发表时间:2013-12-30 11:25 | 作者:
出处:http://www.iteye.com

小编推荐: 互联网泡沫让投资者长了记性:态度更加谨慎

 

        如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度。 这种情况下决定程序速度的另一个重要因素就是代码本身。 在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍。

 

 

目录

变量查找优化

变量声明带上var

1. 如果声明变量忘记了var,那么js引擎将会遍历整个作用域查找这个变量,结果不管找到与否,都是悲剧。

  • 如果在上级作用域找到了这个变量,上级作用域变量的内容将被无声的改写,导致莫名奇妙的错误发生。
  • 如果在上级作用域没有找到该变量,这个变量将自动被声明为全局变量,然而却都找不到这个全局变量的定义。

2. 基于上面逻辑,性能方面不带var声明变量自然要比带var速度慢

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

image

慎用全局变量

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两个变量推入第一个作用域,如下图所示,

image

使用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”

image

原型附加方法测试可以参考 http:// jsperf.com/func-constructor

原型附加值类型变量测试可以参考 http:// jsperf.com/prototype2

避开闭包陷阱

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

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

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

Figure 2 Circular References with Closures

关于Js内存泄漏可以参考

http://www.crockford.com/javascript/memory/leak.html

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

can sometimes be totally different from the other...

1
2
3
4
5
<code> var a = [];
a[5] = 5;
for ( var x in a) {
     // Shows only the explicitly set index of "5", and ignores 0-4
}</code>

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
<code> // 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'.
}</code>

关于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);

对应的测试可以参考 http:// jsperf.com/string-function

脚本装载优化

使用工具精简脚本

精简代码就是将代码中的空格和注释去除,也有更进一步的会对变量名称混淆+精简。

根据统计精简后文件大小会平均减少21%,即使Gzip之后文件也会减少5%。

常用的工具如下,

例如Closure Compiler效果如下,

image

启用Gzip压缩

Gzip通常可以减少70%网页内容的大小,包括脚本、样式表、图片等文件。Gzip比deflate更高效,主流服务器都有相应的 压缩支持模块。

Gzip的工作流程为

  • 客户端在请求Accept-Encoding中声明可以支持gzip
  • 服务器将请求文档压缩,并在Content-Encoding中声明该回复为gzip格式
  • 客户端收到之后按照gzip解压缩

image

设置Cache-Control和Expires头

通过Cache-Control和Expires头可以将脚本文件缓存在客户端或者代理服务器上,可以减少脚本下载的时间。

1
2
3
4
5
6
7
8
9
Expires格式:
Expires = "Expires" ":" HTTP-date
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Note: if a response includes a Cache-Control field with the max-age directive that directive overrides the
Expires field.
 
Cache-Control格式:
Cache-Control   = "Cache-Control" ":" 1#cache-directive
Cache-Control: public

具体的标准定义可以参考http1.1中的定义,简单来说Expires控制过期时间是多久,Cache-Control控制什么地方可以缓存 。

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

异步加载脚本

脚本加载与解析会阻塞HTML渲染,可以通过异步加载方式来避免渲染阻塞。

异步加载的方式很多,比较通用的方法是通过类似下面的代码实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function loadjs
 
(script_filename){
     var script = document.createElement( 'script' );
     script.setAttribute( 'type' , 'text/javascript' );
     script.setAttribute( 'src' , script_filename);
     script.setAttribute( 'id' , 'script-id' );
 
     scriptElement = document.getElementById( 'script-id' );
     if (scriptElement){
         document.getElementsByTagName( 'head' )[0].removeChild(scriptElement);
     }
     document.getElementsByTagName( 'head' )[0].appendChild(script);
}
var script = 'scripts/alert.js' ;
loadjs(script);

DOM操作优化

DOM操作性能问题主要有以下原因,

  • DOM元素过多导致元素定位缓慢
  • 大量的DOM接口调用
  • DOM操作触发频繁的reflow(layout)和repaint

关于reflow(layout)和repaint可以参考下图,可以看到layout发生在repaint之前,所以layout相对来说会造成更多性能 损耗。

  • reflow(layout)就是计算页面元素的几何信息
  • repaint就是绘制页面元素

image

以下是一个wikipedia网站reflow的过程录像,

 

 

减少DOM元素数量

1. 在console中执行命令查看DOM元素数量

1
     document.getElementsByTagName( '*' ).length

2. Yahoo首页DOM元素数量在1200左右。正常页面大小一般不应该超过 1000。 
3. DOM元素过多会使DOM元素查询效率,样式表匹配效率降低,是页面性能最主要的瓶颈之一。

优化CSS样式转换

如果需要动态更改CSS样式,尽量采用触发reflow次数较少的方式。

例如以下代码逐条更改元素的几何属性,理论上会触发多次reflow

1
2
3
element.style.fontWeight = 'bold' ;
element.style.marginLeft= '30px' ;
element.style.marginRight = '30px' ;

可以通过直接设置元素的className直接设置,只会触发一次reflow

1
2
3
element.className =
 
'selectedAnchor' ;

具体的测试结果如下,

image

测试用例可以参考 http://jsperf.com/css-class

优化节点添加

多个节点插入操作,即使在外面设置节点的元素和风格再插入,由于多个节点还是会引发多次reflow。优化的方法是创建 DocumentFragment,在其中插入节点后再添加到页面。

例如JQuery中所有的添加节点的操作如append,都是最终调用documentFragment来实现的,

http://code.jquery.com/jquery-1.10.2.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function
 
createSafeFragment( document ) {
     var list = nodeNames.split( "|" ),
         safeFrag = document.createDocumentFragment();
 
     if ( safeFrag.createElement ) {
         while ( list.length ) {
             safeFrag.createElement(
                 list.pop()
             );
         }
     }
     return safeFrag;
}

关于documentFragment对比直接添加节点的测试 http://jsperf.com/fragment2

优化节点修改

对于节点的修改,可以考虑使用cloneNode在外部更新节点然后再通过replace与原始节点互换。

1
2
3
4
5
6
7
8
9
var orig = document.getElementById( 'container' );
var clone = orig.cloneNode( true );
var list = [ 'foo' , 'bar' , 'baz' ];
var contents;
for ( var i = 0; i < list.length; i++) {
   content = document.createTextNode(list[i]);
   clone.appendChild(content);
}
orig.parentNode.replaceChild(clone, orig);

对应的测试可以参考 http://jsperf.com/clone-node2

减少使用元素位置操作

一般浏览器都会使用增量reflow的方式将需要reflow的操作积累到一定程度然后再一起触发,但是如果脚本中要获取以下 属性,那么积累的reflow将会马上执行,已得到准确的位置信息。

  • offsetLeft
  • offsetTop
  • offsetHeight
  • offsetWidth
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • getComputedStyle()

具体讨论可以参考这个链接 http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css- performance-making-your-javascript-slow/#comment-13157

避免遍历大量元素

避免对全局DOM元素进行遍历,如果parent已知可以指定parent在特定范围查询。

例如以下示例,

1
2
3
4
var elements = document.getElementsByTagName( '*' );
for (i = 0; i < elements.length; i++) {
   if (elements[i].hasAttribute( 'selected' )) {}
}

如果已知元素存在于一个较小的范围内,

1
2
3
4
5
6
var elements = document.getElementById
 
( 'canvas' ).getElementsByTagName( '*' );
for (i = 0; i < elements.length; i++) {
   if (elements[i].hasAttribute( 'selected' )) {}
}
1
 

相关测试可以参考 http://jsperf.com/ranged-loop

事件优化

使用事件代理

1. 当存在多个元素需要注册事件时,在每个元素上绑定事件本身就会对性能有一定损耗。
2. 由于DOM Level2事件模 型中所有事件默认会传播到上层文档对象,可以借助这个机制在上层元素注册一个统一事件对不同子元素进行相应处理。

捕获型事件先发生。两种事件流会触发DOM中的所有对象,从document对象开始,也在document对象结束。

http:// www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html

image

示例代码如下

<ul id="parent-list">
	<li id="post-1">Item 1
	<li id="post-2">Item 2
	<li id="post-3">Item 3
	<li id="post-4">Item 4
	<li id="post-5">Item 5
	<li id="post-6">Item 6
</li></ul>
// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click",function(e) {
	// e.target is the clicked element!
	// If it was a list item
	if(e.target && e.target.nodeName == "LI") {
		// List item found!  Output the ID!
		console.log("List item ",e.target.id.replace("post-")," was clicked!");
	}
});

对应的测试可以参考 http://jsperf.com/event- delegate

动画优化

动画效果在缺少硬件加速支持的情况下反应缓慢,例如手机客户端

特效应该只在确实能改善用户体验时才使用,而不应用于炫耀或者弥补功能与可用性上的缺陷

至少要给用户一个选择可以禁用动画效果

设置动画元素为absolute或fixed

position: static 或position: relative元素应用动画效果会造成频繁的reflow

position: absolute或position: fixed 的元素应用动画效果只需要repaint

关于position的具体介绍可以参考

http://css- tricks.com/almanac/properties/p/position /

使用一个timer完成多个元素动画

setInterval和setTimeout是两个常用的实现动画的接口,用以间隔更新元素的风格与布局。

动画效果的帧率最优化的情况是使用一个timer完成多个对象的动画效果,其原因在于多个timer的调用本身就会损耗一定 性能。

setInterval(function() {
  animateFirst('');
}, 10);
setInterval(function() {
  animateSecond('');
}, 10);

使用同一个timer,

setInterval(function() {
  animateFirst('');
  animateSecond('');
}, 10);

 

以上是JavaScript性能提高的技巧总结,基本上都能够通过测试验证,但是限于篇幅没有把所有的测试结果都 贴出来。

最后再引用一句名人名言,不要急于优化,在性能与可维护性之间找到平衡。

Premature optimization is the root of all evil.                 &nb sp;  -- Donald Knuth



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [javascript 性能优化] 推荐:

JavaScript性能优化

- - ITeye博客
互联网泡沫让投资者长了记性:态度更加谨慎.         如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍.

javaScript的性能优化

- - JavaScript - Web前端 - ITeye博客
随着网络的发展,网速和机器速度的提高,越来越多的网站用到了丰富客户端技术. 而现在Ajax则是最为流行的一种方式. JavaScript是一种解释型语言,所以能无法达到和C/Java之类的水平,限制了它能在客户端所做的事情,为了能改进他的性能,我想基于我以前给JavaScript做过的很多测试来谈谈自己的经验,希望能帮助大家改进自己的JavaScript脚本性能.

也谈JavaScript代码性能优化

- 可乐加糖 - Fdream&#39;s Blog
差不多两年前写了个选择器whiz,除在DOM查找方面做了许多优化工作之外,还在代码优化上做了很多工作,一直没有分享. 抽空总结一下,基本上在jQuery、Mootools和YUI的源码里面都可以看到这些写法. 有些是已经在网上分享很多遍了,众所周知的,也有一些可能写了多年的JavaScript的开发人员也不一定想得到的.

javascript性能优化-repaint和reflow

- - 博客园_首页
repaint(重绘) ,repaint发生更改时,元素的外观被改变,且在没有改变布局的情况下发生,如改变outline,visibility,background color,不会影响到dom结构渲染. reflow(渲染),与repaint区别就是他会影响到dom的结构渲染,同时他会触发repaint,他会改变他本身与所有父辈元素(祖先),这种开销是非常昂贵的,导致性能下降是必然的,页面元素越多效果越明显.

JavaScript性能优化小知识总结

- - 码农网
JavaScript的性能问题不容小觑,这就需要我们开发人员在编写JavaScript程序时多注意一些细节,本文非常详细的介绍了一下JavaScript性能优化方面的知识点,绝对是干货. 一直在学习javascript,也有看过《犀利开发Jquery内核详解与实践》,对这本书的评价只有两个字犀利,可能是对javascript理解的还不够透彻异或是自己太笨,更多的是自己不擅于思考懒得思考以至于里面说的一些精髓都没有太深入的理解.

JavaScript的性能优化:加载和执行

- - ITeye资讯频道
 随着Web2.0技术的不断推广,越来越多的应用使用 JavaScript 技术在客户端进行处理,从而使JavaScript在浏览器中的性能成为开发者所面临的最重要的可用性问题. 而这个问题又因JavaScript的阻塞特性变的复杂,也就是说当浏览器在执行JavaScript代码时,不能同时做其他任何事情.

JavaScript 的性能优化:加载和执行

- - Web前端 - ITeye博客
文章源自:http://www.ibm.com/developerworks/cn/web/1308_caiys_jsload/index.html. JavaScript 的性能优化:加载和执行. 无论当前 JavaScript 代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成.

MySQL性能优化

- sun - IT程序员面试网
在笔试面试中,尤其是像百度,淘宝这些数据量非常大,而且用LAMP架构的公司,数据库优化方面就显得特别重要了. 此外,除了数据库索引之外,在LAMP结果如此流行的今天,数据库(尤其是MySQL)性能优化也是海量数据处理的一个热点. 下面就结合自己的经验,聊一聊MySQL数据库优化的几个方面. 首先,在数据库设计的时候,要能够充分的利用索引带来的性能提升,至于如何建立索引,建立什么样的索引,在哪些字段上建立索引,上面已经讲的很清楚了,这里不在赘述.

Hebernate 性能优化

- - 企业架构 - ITeye博客
文章分为十三个小块儿对Hibernate性能优化技巧进行总结性分析,分析如下:. 一、在处理大数据量时,会有大量的数据缓冲保存在Session的一级缓存中,这缓存大太时会严重显示性能,所以在使用Hibernate处理大数 据量的,可以使用session. clear()或者session. evict(Object) 在处理过程中,清除全部的缓存或者清除某个对象.

Hbase 性能优化

- - CSDN博客云计算推荐文章
因 官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果. 所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正. 默认值:3分钟(180000ms). 说明:RegionServer与Zookeeper间的连接超时时间.