Fork me on GitHub

高性能JavaScript之—— 数据存取

数据存取

 

数据的存储位置会很大程度上影响其读取速度。Javascript 中有四种基本的数据存取位置

 

字面量

字面量只代表自身,不存储在特定位置。Javascript 中的字面量有:字符串、数字、布尔值、对象、数组、函数、正则表达式,以及特殊的 null 和 undefined 值。

 

本地变量

开发人员使用关键字 var 定义的数据存储单元。

 

数组元素

存储在 Javascript 数组对象内部,以数字作为索引

 

对象成员

存储在 Javascript 对象内部,以字符串作为索引。

 

每一种数据存储的位置都有不同的读写消耗。大多数情况下,从一个字面量和一个局部变量中存取数据的性能差异是微不足道的。访问数组元素和对象成员的代价则高一些。总的来说,字面量和局部变量的访问速度快于数组项和对象成员的访问速度,如果在乎运行速度,那么尽量使用字面量和局部变量,减少数组项和对象成员的使用。

 

管理作用域

Javascript 作用域同样关系到性能,要理解速度和作用域的关系,首先要正确地理解作用域的工作原理。

作用域链和标识符解析

每一个 Javascript 函数都是 Function 对象的一个实例。 Function 对象同其他对象一样,拥有可编程访问的属性和仅供 Javascript 引擎存取的内部属性。其中一个内部属性是[[Scope]]。

 

内部属性[[Scope]]包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定哪些数据能被函数访问。函数作用域中的每个对象被称为一个可变对象每个可变对象都以“键值对”的形式存在。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象所填充

 

执行函数时会创建一个称为执行环境(execution context)的内部对象。一个执行环境定义了一个函数执行时的环境。函数每次执行时对应的执行环境都是独一无二的,所以多次调用同一个函数就会导致创建多个执行环境。当函数执行完毕,执行环境就被销毁

 

每个执行环境都有自己的作用域链,用于解析标识符。当执行环境被创建时,它的作用域链初始化为当前运行函数的[[Scope]]属性中的对象。这些值按照它们出现在函数中的顺序被复制到执行环境的作用域链中。这个过程一完成,一个被称为“活动对象(activation  object)”的新对象就为执行环境创建好了。活动对象作为函数运行时的变量对象,包含了所有局部变量,命名参数,参数集合以及 this。然后此对象被推入作用域链的最前端。当执行环境被销毁,活动对象也随之销毁。

 

标识符解析的性能

 

标识符解析是有代价的,事实上没有哪种计算机操作可以不产生性能开销。在执行环境的作用域链中,一个标识符所在的位置越深,它的读写速度也就越慢。因此,函数中读写局部变量总是最快的,而读写全局变量通常是最慢的(优化 Javascript 引擎在某些情况下能有所改善)。请记住,全局变量总是存在于执行环境作用域链的最末端,因此它也是最远的

 

改变作用域链

 

一个执行环境的作用域链是不会改变的。但是,有两个语句可以在执行时临时改变作用域链。

 

第一个是 with 语句。

当代码执行到 with 语句时,执行环境的作用域链临时被改变了。一个新的变量对象被创建,它包含了参数指定的对象的所有属性。

这个对象被推入作用域链的首位,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问的代价更高了.

最好避免使用 with 语句。综前所述,只要简单地把 document 存储在一个局部变量中,就可以提升性能。

 

try-catch 语句中的  catch 子句也具有同样的效果。

 

当 try 代码块中发生错误,执行过程会自动跳转到 catch 子句,然后把异常对象推入一个变量对象并置于作用域的首位。在 catch 代码块内部,函数所有局部变量将会放在第二个作用域链对象中。

 

如果使用得当,try-catch 是个非常有用的语句,因此不建议完全弃用。如果你准备使用  try-catch,请确保了解可能会出现的错误。try-catch 语句不应该被用来解决 Javascript 错误。如果你知道某个错误经常出现,那说明是代码本身有问题,应该尽早被修复。

 

你尽量简化代码来使得 catch 子句对性能的影响最小化。一种推荐的做法是将错误委托给个函数来处理

 

动态作用域

 

无论是 with 语句还是 try-catch 语句的 catch 子句,或是包含 eva1(的函数,都被认为是动态作用域。动态作用域只存在于代码执行过程中,因此无法通过静态分析(査看代码结构)检测出来。

 

闭包、作用域和内存

 

由于闭包的[[Scope]]属性包含了与执行环境作用域链相同的对象的引用,因此会产生副作

用。

通常来说,函数的活动对象会随着执行环境一同销毁。但引入闭包时,由于引用仍然存在于闭包的Scope属性中,因此激活对象无法被销毁。

这意味着脚本中的闭包与非闭包函数相比,需要更多的内存开销。

在大型 Web 应用中,这可能是个问题,尤其在 E 浏览器中需要关注。由于 E 使用非原生 Javascript 对象来实现 DOM 对象,因此闭包会导致内存泄漏。

 

在脚本编程中,最好小心地使用闭包,它同时关系到内存和执行速度。不过,你可以遵照本章节中早先对跨作用域变量的处理建议,来减轻闭包对执行速度的影响:将常用的跨作用域变量存储在局部变量中,然后直接访问局部变量。

posted @ 2020-10-27 20:21  思考的大腿  阅读(231)  评论(0编辑  收藏  举报