优化 JavaScript 代码的方法小结


Posted in Javascript onJuly 16, 2009

优化 JavaScript 代码
作者: Gregory Baker, GMail 软件工程师 和 Erik Arvidsson, Google Chrome 软件工程师
需要的经验: JavaScript 相关工作知识
客户端脚本能让你的应用更加地动态和活跃, 但是浏览器对代码的解析可能造成效率问题, 而这种性能差异在客户端之间也不尽相同. 这里我们讨论和给出一些优化你的 JavaScript 代码的提示和最佳实践.
使用字符串
字符串连接操作会对 Internet Explorer 6 和 7 的垃圾收集带来很大的影响. 尽管这个问题在 Internet Explorer 8 里面得到解决 -- 字符串连接在 IE8 和其它非 IE 浏览器(如 Chrome)中稍微更有效率一点 -- 如果你的用户中有很大一部分在使用 Internet Explorer 6 或 7, 你就需要非常注意你构建字符串的方式了.
有如下示例代码:

var veryLongMessage = 
'This is a long string that due to our strict line length limit of' + 
maxCharsPerLine + 
' characters per line must be wrapped. ' + 
percentWhoDislike + 
'% of engineers dislike this rule. The line length limit is for ' + 
' style purposes, but we don't want it to have a performance impact.' + 
' So the question is how should we do the wrapping?';

比起用连接的方式, 尝试使用 join():
var veryLongMessage = 
['This is a long string that due to our strict line length limit of', 
maxCharsPerLine, 
' characters per line must be wrapped. ', 
percentWhoDislike, 
'% of engineers dislike this rule. The line length limit is for ', 
' style purposes, but we don't want it to have a performance impact.', 
' So the question is how should we do the wrapping?' 
].join();

相似的, 用连接的方式在条件语句和循环中构建字符串是很低效的. 错误的方式:
var fibonacciStr = '前 20 个斐波那契数 '; 
for (var i = 0; i < 20; i++) { 
fibonacciStr += i + ' = ' + fibonacci(i) + ' 
'; 
}

正确的方法:
var strBuilder = ['前 20 个斐波那契数:']; 
for (var i = 0; i < 20; i++) { 
strBuilder.push(i, ' = ', fibonacci(i)); 
} 
var fibonacciStr = strBuilder.join('');

构建通过辅助函数生成的字符串
通过传递字符串构建器(可以是数组或者辅助类)到函数中构建长字符串, 以避免出现存放临时结果的字符串.
例如, 假定 buildMenuItemHtml_ 需要用文字串和变量构建一个字符串, 并且会在内部使用一个字符串构建器, 与其使用:
var strBuilder = []; 
for (var i = 0; i < menuItems.length; i++) { 
strBuilder.push(this.buildMenuItemHtml_(menuItems[i])); 
} 
var menuHtml = strBuilder.join();

不如用:
var strBuilder = []; 
for (var i = 0; i < menuItems.length; i++) { 
this.buildMenuItem_(menuItems[i], strBuilder); 
} 
var menuHtml = strBuilder.join();

定义类的方法
下面的代码效率不高, 因为每次构造 baz.Bar 的实例时, 都会为 foo 创建一个新函数和闭包(closure):
baz.Bar = function() { 
// 构造函数代码 
this.foo = function() { 
// 方法代码 
}; 
}

推荐的方式为:
baz.Bar = function() { 
// 构造函数代码 
}; 
baz.Bar.prototype.foo = function() { 
// 方法代码 
};

用这种方式, 无论构造了多少个 baz.Bar 实例, 只会创建一个函数给 foo, 同时不会创建任何闭包.
初始化实例变量
将带有值类型(非引用的)的初始化值(例如类型为数字, 布尔值, null, undefined 或字符串的值)的变量声明/初始化代码直接放在 prototype 原型中. 这可以避免每次调用构造函数时不必要地运行初始化代码. (这个方法无法应用到初始化值由构造器参数决定或构造时状态不确定的实例变量上.)
例如, 比起写:
foo.Bar = function() { 
this.prop1_ = 4; 
this.prop2_ = true; 
this.prop3_ = []; 
this.prop4_ = 'blah'; 
};

不如写:
foo.Bar = function() { 
this.prop3_ = []; 
}; 
foo.Bar.prototype.prop1_ = 4; 
foo.Bar.prototype.prop2_ = true; 
foo.Bar.prototype.prop4_ = 'blah';

谨慎地使用闭包(closure)
闭包是 JavaScript 中一个强大而有用的特性; 但是, 它们也有不好的地方, 包括:
它们是最常见的内存泄漏源头.
创建一个闭包比创建一个没有闭包的内联函数明显要慢, 比起重用一个静态函数则更慢. 例如:
function setupAlertTimeout() { 
var msg = '要显示的消息'; 
window.setTimeout(function() { alert(msg); }, 100); 
}

比下面的代码慢:
function setupAlertTimeout() { 
window.setTimeout(function() { 
var msg = '要显示的消息'; 
alert(msg); 
}, 100); 
}

更比下面的代码慢:
function alertMsg() { 
var msg = '要显示的消息'; 
alert(msg); 
} 
function setupAlertTimeout() { 
window.setTimeout(alertMsg, 100); 
}

他们增加了作用域链(scope chain)的层级. 当浏览器解析属性时, 作用域链的每一个层级都必须被检查一次. 在下面的例子中:
var a = 'a'; 
function createFunctionWithClosure() { 
var b = 'b'; 
return function () { 
var c = 'c'; 
a; c; 
}; 
} 
var f = createFunctionWithClosure(); 
f();

当 f 被调用时, 引用 a 比引用 b 慢, 它们都比引用 c 要慢.

查看 IE+JScript Performance Recommendations Part 3: JavaScript Code inefficiencies 获得更多有关在 IE 中使用闭包的信息.

避免使用 with

在你的代码中避免使用 with. 它对性能有非常坏的影响, 因为它修改了作用域链, 让查找在其它作用域的变量变得代价高昂.

避免浏览器内存泄漏

内存泄漏对 Web 应用而言是个很普遍的问题, 它会带来严重的性能问题. 当浏览器的内存使用上升时, 你的 Web 应用, 连同用户系统的其他部分, 都会变慢. Web 应用最常见的内存泄漏原因是: 在 JavaScript 脚本引擎和浏览器 DOM 的 C++ 对象实现间的循环引用(例如, 在 JavaScript 脚本引擎和 Internet Explorer 的 COM 基础架构间, 或者 JavaScript 引擎和 Firefox 的 XPCOM 基础架构间).

下面是避免内存泄漏的一些经验法则:

使用一个事件系统来附加事件处理函数

最常见的循环引用模式 [ DOM 元素 --> 事件处理函数 --> 闭包作用域 --> DOM ] 在 这篇 MSDN 的 Blog 文章中讨论过了. 为避免这个问题, 可以使用一个经过严格测试的事件系统来附件事件处理函数, 例如 Google doctype, Dojo, or JQuery.

另外, 在 IE 中使用内联(inline)的事件处理函数会导致另外一类泄漏. 这不是通常的循环引用泄漏, 而是内存中临时匿名脚本对象的泄漏. 详情请查看 理解和解决 IE 泄漏模式(Understanding and Solving Internet Explorer Leak Patterns) 的 "DOM 插入顺序泄漏模型(DOM Insertion Order Leak Model)" 一节, 另外在 JavaScript Kit 教程 中还有一个例子.

避免使用扩展(expando)属性

扩展属性是附加到 DOM 元素上的任意 JavaScript 属性, 也是循环引用的常见原因. 你能够在使用扩展属性时不导致内存泄漏, 但是很容易不小心就引入一个泄漏. 这个泄漏的模式是 [ DOM 元素 --> 扩展属性 --> 中间对象 --> DOM 元素 ]. 最好的方法就是避免使用它们. 如果你要使用它们, 就只使用简单的值类型. 如果你要非简单的类型, 那么在不再需要扩展属性时将它设为空(null). 参见 理解和解决 IE 泄漏模式(Understanding and Solving Internet Explorer Leak Patterns) 中的 "循环引用(Circular References)" 一节.

Javascript 相关文章推荐
JavaScript高级程序设计(第3版)学习笔记11 内建js对象
Oct 11 Javascript
jQuery寻找n以内完全数的方法
Jun 24 Javascript
JS模拟实现方法重载示例
Aug 03 Javascript
JavaScript数组去重由慢到快由繁到简(优化篇)
Aug 26 Javascript
AngularJs  unit-testing(单元测试)详解
Sep 02 Javascript
Bootstrap常用组件学习(整理)
Mar 24 Javascript
静态页面实现 include 引入公用代码的示例
Sep 25 Javascript
vue实现手机号码抽奖上下滚动动画示例
Oct 18 Javascript
微信小程序 wepy框架与iview-weapp的用法详解
Apr 10 Javascript
JS中的算法与数据结构之字典(Dictionary)实例详解
Aug 20 Javascript
vue+ts下对axios的封装实现
Feb 18 Javascript
JS的深浅复制详细
Oct 16 Javascript
Javascript 事件流和事件绑定
Jul 16 #Javascript
js 对象是否存在判断
Jul 15 #Javascript
js 实现无缝滚动 兼容IE和FF
Jul 15 #Javascript
兼容IE/Firefox/Opera/Safari的检测页面装载完毕的脚本Ext.onReady的实现
Jul 14 #Javascript
JavaScript 继承详解(四)
Jul 13 #Javascript
JavaScript 继承详解(三)
Jul 13 #Javascript
JavaScript 继承详解(二)
Jul 13 #Javascript
You might like
详解php的魔术方法__get()和__set()使用介绍
2012/09/19 PHP
PHP获取当前执行php文件名的代码
2017/03/02 PHP
php 删除指定文件夹的实例讲解
2017/07/25 PHP
PHP实现将几张照片拼接到一起的合成图片功能【便于整体打印输出】
2017/11/14 PHP
php设计模式之适配器模式实例分析【星际争霸游戏案例】
2020/04/07 PHP
Javascript开发包大全整理
2006/12/22 Javascript
JavaScript学习笔记之内置对象
2015/01/22 Javascript
JavaScript生成随机数的4种自定义函数分享
2015/02/28 Javascript
实例详解JSON数据格式及json格式数据域字符串相互转换
2016/01/07 Javascript
浅谈JS中的!=、== 、!==、===的用法和区别
2016/09/24 Javascript
浅谈vue-lazyload实现的详细过程
2017/08/22 Javascript
vue-ajax小封装实例
2017/09/18 Javascript
JSONP解决JS跨域问题的实现
2020/05/25 Javascript
[05:04]完美世界携手游戏风云打造 卡尔工作室地图界面篇
2013/04/23 DOTA
python基础入门详解(文件输入/输出 内建类型 字典操作使用方法)
2013/12/08 Python
python数据清洗系列之字符串处理详解
2017/02/12 Python
Python中表达式x += y和x = x+y 的区别详解
2017/06/20 Python
django 删除数据库表后重新同步的方法
2018/05/27 Python
Python中的X[:,0]、X[:,1]、X[:,:,0]、X[:,:,1]、X[:,m:n]和X[:,:,m:n]
2020/02/13 Python
Keras-多输入多输出实例(多任务)
2020/06/22 Python
Python sklearn中的.fit与.predict的用法说明
2020/06/28 Python
Baracuta官方网站:Harrington夹克,G9,G4,G10等
2018/03/06 全球购物
南京迈特望C/C++面试题
2012/07/09 面试题
电大学习个人自我评价范文
2013/10/04 职场文书
计算机应用职专应届生求职信
2013/11/12 职场文书
个人对照检查材料
2014/02/12 职场文书
安全生产汇报材料
2014/02/17 职场文书
初一学生期末评语
2014/04/24 职场文书
新农村建设典型材料
2014/05/31 职场文书
班级出游活动计划书
2014/08/15 职场文书
预备党员群众路线教育实践活动思想汇报2014
2014/10/25 职场文书
招商引资工作汇报材料
2014/10/28 职场文书
单位考核聘任报告
2015/03/02 职场文书
共青团员自我评价
2015/03/10 职场文书
银行服务理念口号
2015/12/25 职场文书
python实现简易自习室座位预约系统
2021/06/30 Python