四种基本的数据存储位置
数据的存储位置会很大程度上影响其读取速度。JavaScript 有四种基本的数据存储的位置:
- 字面量。字面量只代表自身,不存储在特定位置。JavaScript 中的字面量有:字符串、数字、布尔值、对象、数组、函数、正则表达式,以及特殊的 null 和 undefined 值。
- 本地变量。开发人员使用关键字 var 定义的数据存储单元。
- 数组元素。存储在 JavaScript 数组对象内部,以数字做为索引。
- 对象成员。存储在 JavaScript· 对象内部,以字符串作为索引。
存取消耗的时间比较:
字面量 ≈ 本地变量 < 数组元素 ≈ 对象成员
建议:如果在乎运行速度,那么尽量使用字面量和局部变量,减少数组项和对象成员的使用。
管理作用域
作用域链和标识符解析
执行函数时会创建一个称为执行环境(execution context)的内部对象。一个执行环境定义了一个函数执行时的环境。函数每次执行时对应的执行环境都是独一无二的,所以多次调用同一个函数就会导致创建多个执行环境。当函数执行完毕,执行环境就会被摧毁。
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以及决定从哪里获取或存储数据。该过程搜索执行环境的作用域链,查找同名的标识符。搜索过程从作用域链头部开始,也就是当前运行函数的活动对象。如果没找到,则继续搜素作用域链中的下一个对象。正是这个搜索过程影响了性能。
标识符解析的性能
在执行环境中的作用域链中,一个标识符位置越深,它的读写速度就越慢。因此函数中读写局部变量总是最快的,而读写全局变量通常是最慢的,因为全局变量总是存在于执行环境作用域链的最末端。
建议:尽量使用局部变量。一个好的经验法则是:如果某个跨作用域的值在函数中被引用一次以上,那么就把它储存到局部变量里。
闭包性能问题
思考以下代码,尝试理解与闭包有关的性能问题:
function assignEvent() {
var id = "xdi9592";
document.getElementById("save-btn").onclick = function(event) {
saveDocument(id);
};
}
onclick
事件是一个闭包,它在 assignEvent() 执行时创建。
当 assignEvent() 函数执行时,一个包含变量 id
以及其他数据的活动对象被创建。他成为执行环境作用域链中的第一个对象,而全局变量紧随其后。当闭包被创建时,它的[[scope]]属性被初始化为这些对象
由于闭包的[[scope]]属性包含了与执行环境作用域链相同的对象的引用,因此会产生副作用。通常来说,函数的活动对象会随着执行环境一同摧毁,但引入闭包后,由于引用闭包仍然存在闭包的[[scope]]属性中,因此激活对象无法被摧毁。这意味着脚本中的闭包与非闭包函数相比,需要更多的内存开销。
当闭包代码被执行时,会创建一个执行环境,它的作用域链与属性[[scope]]中所引用的两个相同的作用域链对象一起被初始化,然后一个活动对象为闭包自身对象所创建。
注意闭包中用到的两个标识符,id 和 saveDocument,它们的位置在作用域链的第一个对象之后。这就是使用闭包最需要关注的性能点:在频繁访问跨作用域的标识符时,每次访问都会带来性能损失。
建议:将常用的跨作用域变量存储在局部变量中,然后直接访问局部变量。