《高性能JavaScript》读书笔记[2]-数据访问
数据的存储位置会影响其读取速度,这个问题对于JavaScript来说相对简单,因为它只有四种基本的数据存储位置:
- 直接量
- 变量
- 数组元素
- 对象成员
一般来说,使用直接量和局部变量的访问速度快于数组项和对象成员的访问速度(除非浏览器内部刻意优化)。
管理作用域
###作用域链和标识符解析
每一个JavaScript函数都是Function对象的一个实例。而该对象拥有可编程访问的属性和不能通过代码访问的内部属性(仅供JavaScript引擎存取)。内部属性中的Scope包含的一个函数被创建的作用域中对象的集合称为函数的作用域链。
执行函数时会创建一个运行期上下文(execution context)的内部对象,每个运行期上下文对象都有自己的作用域链用于标识符解析。正是解析标识符时在作用域链中的搜索过程影响了性能。
###标识符解析的性能
一个标识符在运行期上下文的作用域链中位置越深,它的读写速度就越慢。因为全局变量总是存在于运行期上下文作用域链的最末端,因此读写全局变量较读写局部变量来说是慢的。
###改变作用域链
可通过with语句或try-catch语句中的catch子句临时改变作用域链。
###动态作用域
动态作用域只存在于代码执行过程中,因此无法通过静态分析(查看代码结构)检测出来。with
语句、try-catch
中的catch
子句以及包含eval()
的函数,都被认为是动态作用域。
###闭包,作用域和内存
当闭包被创建时,它的Scope
属性初始化为上下文中的活动对象和全局对象。
当闭包被执行时,它的’Scope’属性中对了一个为闭包自身所创建的活动对象。所以要访问的标识符存在于作用域链第一个对象之后的位置,访问时导致性能损失。
对象成员
###原型
JavaScript中的对象是基于原型(Prototype)的。原型定义并实现了一个新对象必须包含的成员列表,并为所有对象实例所共享。
对象可以有两种成员类型:实例成员(存在于对象实例中)和原型成员(由对象原型集成而来)。
可以使用hasOwnProperty()
方法判断对象是否包含特定的实例成员(只搜索原型)。
使用in
操作符判断对象是否包含特定的属性(既搜索实例也搜索原型)。
###原型链
对象的原型决定了实例的原型(所有的对象都是Object的实例)。
访问对象成员(属性(非函数类型成员)或方法(函数类型成员))的过程就是先搜索对象的实例成员,再遍历原型链。对象成员的深度决定了访问它所需的时间。
###嵌套成员
对象成员嵌套越深,访问速度就会越慢。如访问速度:
location.href > widow.location.href > window.location.href.toString()
###缓存成员对象值
在同一个函数中没有必要多次访问同一个对象成员,除非它的值发生了更改。如果需要多次查找成员变量,可以使用局部变量来进行缓存。但这种方法并不适用于使用this来进行判断执行上下文的对象方法。因为把一个方法保存在局部变量中会导致this
绑定到window。
小结
- 访问直接量和局部变量比访问数组元素和对象成员快。
- 变量在作用域链中的位置越深,访问所需时间越长。属性或方法在原型链中的位置越深,访问它的速度也越慢。
- 由于局部变量存在于作用域链的起始位置使得访问局部变量比访问跨作用域变量快。
- 由于全局变量总处在作用域链的最末端,因此访问速度最慢。
- 避免使用
with
语句和try-catch
语句中的catch
子句,因为它们会改变运行期上下文作用域链。 - 尽量少用影响性能的嵌套的对象成员。
- 可以把常用的对象成员、数组元素、跨域变量保存在局部变量中来改善JavaScript性能。