JavaScript 数据访问(翻译自High Performance Javascript 第二章)
????计算机科学中一个经典的问题是决定如何存储数据,以便进行快速的读取和写入操作。 在代码执行期间,数据如何存储将会关系到它们的检索速度。在Javascript中,由于只存在少数的操作来进行数据存储, 这个问题似乎 变得简单了。但是,与其他语言一样,Javascript中数据的存储方式将决定它们访问速度。下面是Javascript中可以进行数据存储的四种基本方式:???
?????? 对于上述数据存储位置而言,它们每个都有其特定的读写花费。虽然实际上的性能差异是强烈依赖于代码所运行的浏览器的。 但在大多数情况下,从字面量访问信息与从本地变量访问信息的性能差异是微不足道的。而数组项和对象成员的访问则较昂贵。
??
??? 虽然某些JS引擎对数组项访问进行了优化,使其能变得更快。但即使如此,通常的建议是尽可能的使用字面值和本地变量,并限制数组项和对象成员的使用. 为了达到这个目的,有如下几个模式可用来查找和避免问题,并优化你的代码.
一.管理作用域(Manaing Scope)
?? 在Javascript中, 作用域(Scope)是一个关键的概念。其不仅是从性能的角度,而且也从函数的角度解释了各种问题。作用域在Javascript中产生了诸多影响,从确定函数可以访问那些变量到this上值的分配. 在使用Javascript 作用域的时候,也有一些性能上的考虑。但为了理解其如何关联到速度上,首先需要理解作用域是如何工作的。??
-
作用域链(Scope Chain)与标识符解析
???Javascript中的每个函数都被表示成一个对象--更具体的说,是作为函数的实例. 就像其他对象一样,函数对象也可以包含属性(properties),这些属性包括可以编程访问的常规属性以及一系列Javascript引擎所使用到的内部属性。内部属性无法通过代码来访问。其中一个内部属性是在ECMA-262,第三版规范中定义的 [ [Scope] ] 属性.
? [ [Scope] ]内部属性包含了函数被创建时表示其所在作用域的对象集合(The internal [[Scope]] property contains a collection of objects representing the scope in which the function was created)。该集合被称为函数的作用域链,它决定了一个函数所能访问到的数据。函数作用域链中的每个对象都称为可变对象. 每个可变对象包含一些键值对(Key-Value Pairs). 当一个函数被创建时,它的作用域链会填充一些在其创建环境内可以访问到的数据对象。例如,请考虑下面的全局函数:
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
??? 当 add() 函数被创建时,他的作用域链将会填充一个单独的可变对象: 即全局范围内包含所有值的全局对象(global object).该全局对象包含了诸如window, navigator 和document等。下图显示了该关系(注意,图中的全局对象只显示了部分属性值,但实际上它还包含了许多其他属性):
? var total = add(5, 10);
??? 执行add函数的时候,将会创建一个称为执行上下文(execution context) 的内部对象。执行上下文定义了函数执行的环境. 每个执行上下文都是唯一的,所以对相同函数的多次调用将会产生多个执行上下文。当函数执行完成后,执行上下文将会被销毁。
?? 一个执行上下文自身也包含了作用域链,该作用域链将用来进行标识符解析。当执行上下文创建时,首先会把其执行函数的[ [Scope] ] 属性中的对象复制到自身的作用域链中。该复制过程将会以对象在 [ [Scope] ]属性中出现的位置依次进行。当该过程完毕后,将会为执行上下文创建一个称为激活对象(activation object)的新对象. 该激活对象包含了所有的本地变量,命名参数, 参数集合(arguments)以及this。接着,激活对象将会被推入作用域链的最顶端,作为该次执行中的可变对象。当执行上下文被销毁时,该激活对象也同时销毁。下图显示了前面代码中的执行上下文和作用域链.
??? 在函数执行时,每遇到一个变量,将会产生一个标识符解析的过程,该过程将决定数据检索和存储的位置。在这个过程中,将会在执行上下文的作用域链中查找一个与变量名称相同的标识符. 查找将会从作用域链的顶端开始(即激活对象),依次遍历作用域链。当找到相同名称的标识符时,将使用该标识符。而当遍历完整个作用域链后均没有找到标识符时,标识符将会被就看做是未定义的(undefined). 函数执行时,每个标识符的查找都将经历上面的过程.以前面的例子来说,add函数中的sum, num1 和 num2 将会产生这一查找过程。而正是这个搜索过程影响了性能。
?? 注意在作用域链中不同的部分可能会存在两个名称相同的变量。此时,标识符解析将会以首先找到的对象为准。而后面部分中的对象将会被遮蔽(shadow).
-
标识符解析的性能
?? 标识符解析并不是不消耗资源的,因为事实上有没哪项计算操作可以不产生性能开销。当在执行上下文的作用域链中进行深度查找时,读写操作将会变得缓慢。因此,本地变量是函数内部访问数据最快的方式。而一般情况下全局变量的访问则是最慢的(优化过的Javascript引擎会在一些条件下优化该过程)。请记住,全局变量总是处于执行上下文的作用域链中最后一个,所以总是产生最多的解析花费。下面2张图显示了标识符在作用域链上不同深度的解析速度.深度为1则表示本地变量.
读操作:
??
???
?? ?对所有浏览器而言,总的趋势是标识符在作用域链中的位置越深,它的读写操作将会变得更慢。虽然一些优化过Javascript引擎的浏览器,例如Chrome 和 Safari 4 在访问外部作用域(out-of-scope)中的标识符