优化 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 相关文章推荐
List Installed Hot Fixes
Jun 12 Javascript
ASP.NET MVC中EasyUI的datagrid跨域调用实现代码
Mar 14 Javascript
jQuery JSON实现无刷新三级联动实例探讨
May 28 Javascript
10分钟学会写Jquery插件实例教程
Sep 06 Javascript
全面解析JavaScript中“&amp;&amp;”和“||”操作符(总结篇)
Jul 18 Javascript
超详细的JS弹出窗口代码大全
Apr 18 Javascript
jQuery实现动态文字搜索功能
Jan 05 Javascript
axios学习教程全攻略
Mar 26 Javascript
在Vue中使用echarts的实例代码(3种图)
Jul 10 Javascript
详解Vue用自定义指令完成一个下拉菜单(select组件)
Oct 31 Javascript
用Cordova打包Vue项目的方法步骤
Feb 02 Javascript
微信小程序实现文件预览
Oct 22 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
PHPMYADMIN导入数据最大为2M的解决方法
2012/04/23 PHP
php中判断文件存在是用file_exists还是is_file的整理
2012/09/12 PHP
php警告Creating default object from empty value 问题的解决方法
2014/04/02 PHP
PHP实现递归复制整个文件夹的类实例
2015/08/03 PHP
php验证手机号码
2015/11/11 PHP
laravel如何开启跨域功能示例详解
2017/08/31 PHP
thinkphp5框架调用其它控制器方法 实现自定义跳转界面功能示例
2019/07/03 PHP
PHP实现简易用户登录系统
2020/07/10 PHP
js客户端快捷键管理类的较完整实现和应用
2010/06/08 Javascript
关于js类的定义
2011/06/28 Javascript
求数组最大最小值方法适用于任何数组
2013/08/16 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
2014/02/21 Javascript
页面元素绑定jquery toggle后元素隐藏的解决方法
2014/03/27 Javascript
jquery实现漫天雪花飞舞的圣诞祝福雪花效果代码分享
2015/08/20 Javascript
JQuery点击行tr实现checkBox选中的简单实例
2016/05/26 Javascript
Bootstrap中data-target 到底是什么
2017/02/14 Javascript
js实现二级导航功能
2017/03/03 Javascript
微信小程序商品详情页规格属性选择示例代码
2017/10/30 Javascript
ElementUI Tree 树形控件的使用并给节点添加图标
2020/02/27 Javascript
vue.js实现双击放大预览功能
2020/06/23 Javascript
JavaScript this指向相关原理及实例解析
2020/07/10 Javascript
jQuery实现简单评论区功能
2020/10/26 jQuery
通过数据库对Django进行删除字段和删除模型的操作
2015/07/21 Python
Python及PyCharm下载与安装教程
2017/11/18 Python
Python中判断输入是否为数字的实现代码
2018/05/26 Python
python将秒数转化为时间格式的实例
2018/09/16 Python
解决python文件双击运行秒退的问题
2019/06/24 Python
python安装virtualenv虚拟环境步骤图文详解
2019/09/18 Python
flask框架自定义过滤器示例【markdown文件读取和展示功能】
2019/11/08 Python
jupyter notebook插入本地图片的实现
2020/04/13 Python
JD Sports芬兰:英国领先的运动鞋和运动服饰零售商
2018/11/16 全球购物
一夜的工作教学反思
2014/02/08 职场文书
暑期社会实践心得体会
2014/09/02 职场文书
创业计划书之密室逃脱
2019/11/08 职场文书
MySQL数据库如何使用Shell进行连接
2022/04/12 MySQL
python热力图实现的完整实例
2022/06/25 Python