优化 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如何给&amp;lt;textarea&amp;gt;&amp;lt;/textarea&amp;gt;赋值
Apr 20 Javascript
JavaScript下通过的XMLHttpRequest发送请求的代码
Jun 28 Javascript
jQuery向上遍历DOM树之parents(),parent(),closest()之间的区别
Dec 02 Javascript
jquery取消选择select下拉框示例代码
Feb 22 Javascript
JS中自定义定时器让它在某一时刻执行
Sep 02 Javascript
JavaScript之cookie技术详解
Nov 18 Javascript
开发Vue树形组件的示例代码
Dec 21 Javascript
vue+echarts实现可拖动节点的折线图(支持拖动方向和上下限的设置)
Apr 12 Javascript
node.js +mongdb实现登录功能
Jun 18 Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
Jul 24 Javascript
浅谈vue中get请求解决传输数据是数组格式的问题
Aug 03 Javascript
微信小程序实现打卡签到页面
Sep 21 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
推荐一款MAC OS X 下php集成开发环境mamp
2014/11/08 PHP
ThinkPHP中公共函数路径和配置项路径的映射分析
2014/11/22 PHP
PHP微信开发之微信录音临时转永久存储
2018/01/26 PHP
PHP 对象接口简单实现方法示例
2020/04/13 PHP
JavaScript 继承使用分析
2011/05/12 Javascript
jQuery随机切换图片的小例子
2013/04/18 Javascript
jQuery学习笔记之创建DOM元素
2015/01/19 Javascript
html的DOM中document对象forms集合用法实例
2015/01/21 Javascript
javascript给span标签赋值的方法
2015/11/26 Javascript
javascript和jQuery实现网页实时聊天的ajax长轮询
2016/07/20 Javascript
javascript实现瀑布流动态加载图片原理
2016/08/12 Javascript
JS 滚动事件window.onscroll与position:fixed写兼容IE6的回到顶部组件
2016/10/10 Javascript
实现点击下箭头变上箭头来回切换的两种方法【推荐】
2016/12/14 Javascript
NodeJS遍历文件生产文件列表功能示例
2017/01/22 NodeJs
JavaScript的setter与getter方法
2017/11/29 Javascript
vue项目中导入swiper插件的方法
2018/01/30 Javascript
Element Backtop回到顶部的具体使用
2020/07/27 Javascript
[44:15]DOTA2上海特级锦标赛主赛事日 - 5 败者组决赛Liquid VS EG第二局
2016/03/06 DOTA
Python中正则表达式的用法实例汇总
2014/08/18 Python
python实现得到一个给定类的虚函数
2014/09/28 Python
python数组复制拷贝的实现方法
2015/06/09 Python
Python调用ctypes使用C函数printf的方法
2017/08/23 Python
python numpy函数中的linspace创建等差数列详解
2017/10/13 Python
Python实现对字典分别按键(key)和值(value)进行排序的方法分析
2018/12/19 Python
Python I/O与进程的详细讲解
2019/03/08 Python
Python循环中else,break和continue的用法实例详解
2019/07/11 Python
使用pyhon绘图比较两个手机屏幕大小(实例代码)
2020/01/03 Python
Matplotlib使用Cursor实现UI定位的示例代码
2020/03/12 Python
Python 私有属性和私有方法应用场景分析
2020/06/19 Python
Python三维绘图之Matplotlib库的使用方法
2020/09/20 Python
python读取图片颜色值并生成excel像素画的方法实例
2021/02/19 Python
Noon埃及:埃及在线购物
2019/11/26 全球购物
《跟踪台风的卫星》教学反思
2014/04/10 职场文书
班主任评语大全
2014/04/26 职场文书
学雷锋献爱心活动总结
2015/05/11 职场文书
win10下go mod配置方式
2021/04/25 Golang