如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度。 这种情况下决定程序速度的另一个重要因素就是代码本身。

在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍。

目录

变量查找优化

变量声明带上var

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

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

2. 基于上面逻辑,性能方面不带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

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

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语句将一个新的可变对象推入作用域链的头部,函数的所有局部变量现在处于第二个作用域链对象中,从而使局部变 量的访问代价提高。

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操作符可以删除实例中的属性。

例如以下代码以及相应的内存中原型表示如下,

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内存泄漏可以参考

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. 添加属性访问方法只是增加了一层重定向 ,对于访问控制没有意义。

使用属性访问方法示例

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

直接访问属性示例

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

for (var i = 0; i < 200; i++) {
try {} catch (e) {}
}

循环外使用try-catch

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

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

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:

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

使用原始操作代替方法调用

方法调用一般封装了原始操作,在性能要求高的逻辑中,可以使用原始操作代替方法调用来提高性能。

原始操作

var min = a < b ? a : b;

方法实例

var min = Math.min(a, b);

关于方法调用和原始操作的测试参考http://jsperf.com/operator-function

传递方法取代方法字符串

一些方法例如setTimeout()/setInterval(),接受字符串或者方法实例作为参数。直接传递方法对象作为参数来避免对字 符串的二次解析。

传递方法

setTimeout(test, 1);

传递方法字符串

setTimeout('test()', 1);

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

脚本装载优化

使用工具精简脚本

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

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

常用的工具如下,

例如Closure Compiler效果如下,

启用Gzip压缩

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

Gzip的工作流程为

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

设置Cache-Control和Expires头

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

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渲染,可以通过异步加载方式来避免渲染阻塞。

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

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就是绘制页面元素

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

减少DOM元素数量

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

    document.getElementsByTagName('*').length 

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

优化CSS样式转换

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

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

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

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

element.className = 

'selectedAnchor';

具体的测试结果如下,

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

优化节点添加

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

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

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

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与原始节点互换。

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在特定范围查询。

例如以下示例,

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

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

var elements = document.getElementById

('canvas').getElementsByTagName('*');
for (i = 0; i < elements.length; i++) {
if (elements[i].hasAttribute('selected')) {}
}

相关测试可以参考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

示例代码如下

<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.                  -- Donald Knuth

												

JavaScript性能优化的更多相关文章

  1. javascript性能优化-repaint和reflow

    repaint(重绘) ,repaint发生更改时,元素的外观被改变,且在没有改变布局的情况下发生,如改变outline,visibility,background color,不会影响到dom结构渲 ...

  2. 摘:JavaScript性能优化小知识总结

    原文地址:http://www.codeceo.com/article/javascript-performance-tips.html JavaScript的性能问题不容小觑,这就需要我们开发人员在 ...

  3. Javascript 性能优化的一点技巧

    把优秀的编程方式当成一种习惯,融入到日常的编程当中.下图是今天想到的一点Javascript 性能优化的技巧,分享一下,抛砖引玉.

  4. JavaScript性能优化小窍门汇总(含实例)

    在众多语言中,JavaScript已经占有重要的一席之地,利用JavaScript我们可以做很多事情 , 应用广泛.在web应用项目中,需要大量JavaScript的代码,将来也会越来越多.但是由于J ...

  5. JavaScript性能优化小知识总结(转)

    JavaScript的性能问题不容小觑,这就需要我们开发人员在编写JavaScript程序时多注意一些细节,本文非常详细的介绍了一下JavaScript性能优化方面的知识点,绝对是干货. 前言 一直在 ...

  6. JavaScript性能优化篇js优化

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

  7. JavaScript 性能优化技巧分享

    JavaScript 作为当前最为常见的直译式脚本语言,已经广泛应用于 Web 应用开发中.为了提高Web应用的性能,从 JavaScript 的性能优化方向入手,会是一个很好的选择. 本文从加载.上 ...

  8. javascript性能优化之避免重复工作

    javascript最重要也最根本的性能优化标准之一是避免工作,避免工作又包括两点,第一,不做不必要的工作,第二,不做重复的已经完成的工作.第一部分可以通过代码重构完成,第二部分不做重复的工作有时候难 ...

  9. javascript性能优化:创建javascript无阻塞脚本

    javaScript 在浏览器中的运行性能,在web2.0时代显得尤为重要,成千上万行javaScript代码无疑会成为性能杀手, 在较低版本的浏览器执行JavaScript代码的时候,由于浏览器只使 ...

随机推荐

  1. 2015 西雅图微软总部MVP峰会记录

    2015 西雅图微软总部MVP峰会记录 今年决定参加微软MVP全球峰会,在出发之前本人就已经写这篇博客,希望将本次会议原汁原味奉献给大家 因为这次是本人第一次写会议记录,写得不好的地方希望各位园友见谅 ...

  2. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  3. HTML kbd键盘元素

    1. 说明 kbd :即Keyboard Input Element(键盘输入元素).表示键盘按键的语义元素,常用于网页上对快捷键.按键说明的场景. 样式规格:内联样式. 为了在页面上突出显示,可以给 ...

  4. 前端常用的WindowsCMD命令

    前面的话   在网上找了一些关于命令提示符CMD的资料,但是很多资料都是把所有的功能罗列出来,大部分都不会用到.所以,自己把常用的CMD命令总结如下,方便查阅 操作类 help 列出所有支持的指令及说 ...

  5. 创建 OVS Local Network - 每天5分钟玩转 OpenStack(129)

    上一节我们完成了 OVS 的准备工作,本节从最基础的 local network 开始学习.local network 不会与宿主机的任何物理网卡连接,流量只被限制在宿主机内,同时也不关联任何的 VL ...

  6. 谈谈一些有趣的CSS题目(六)-- 全兼容的多列均匀布局问题

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  7. javascript动画系列第二篇——磁性吸附

    × 目录 [1]范围限定 [2]拖拽范围 [3]磁性吸附 前面的话 上一篇,我们介绍了元素拖拽的实现.但在实际应用中,常常需要为拖拽的元素限定范围.而通过限定范围,再增加一些辅助的措施,就可以实现磁性 ...

  8. C++整数转字符串的一种方法

    #include <sstream> //ostringstream, ostringstream::str() ostringstream stream; stream << ...

  9. 简单酷炫的canvas动画

    作为一个新人怀着激动而紧张的心情写了第一篇帖子还请大家多多支持,小弟在次拜谢. 驯鹿拉圣诞老人动画效果图如下 html如下: <div style="width:400px;heigh ...

  10. BPM SharePoint解决方案分享

    一.需求分析 SharePoint作为微软推出的协同类平台产品,为客户提供了门户.内容.文档.流程.社区.搜索.BI等一系列的解决方案,然而其流程功能由于设计理念差异,不能完全满足客户的需求,主要原因 ...