日期:2014-05-16  浏览次数:20354 次

JavaScript 数据访问(翻译自High Performance Javascript 第二章)

????计算机科学中一个经典的问题是决定如何存储数据,以便进行快速的读取和写入操作。 在代码执行期间,数据如何存储将会关系到它们的检索速度。在Javascript中,由于只存在少数的操作来进行数据存储, 这个问题似乎 变得简单了。但是,与其他语言一样,Javascript中数据的存储方式将决定它们访问速度。下面是Javascript中可以进行数据存储的四种基本方式:???

  • ?字面量值(Literal values)
    ??? 任何仅仅描述自身,且没有被存储在一个特定位置上的值。Javascript可以将字符串,数字,布尔值,对象,数组,函数,正则表达式 以及特殊值null和undefined 作为字面量。

  • 变量
    ??? 任何开发者使用var关键字定义的数据存储位置.
  • 数组项
    ??? 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)中的标识符