天天看点

Javascript中如何高效的数据存取

    在程序设计中,数据的存取速度是程序性能体现的重要特征,同样,在JavaSrcipt中研究数据的存取机制,不仅对于JS性能,而且对于JS的原理理解十分必要。

1. JavaSrcipt的数据存储位置

    JavaScript有4种数据的存储位置,通过对于不同存储位置数据存取速度的对比,能够使我们在JavaScript程序设计时,优先使用性能高的存储位置形式,以达到JavaScript存取性能的最大化

在JavaScript中有4种数据的存储位置,如下:

1)字面量

    字面量只代表自身,不存储在特定的位置,包括:字符串,数字,布尔,对象,数组,函数,正则表达式和null,undefined

2)本地变量

    使用var定义的数据存储单元

3)数组元素

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

4)对象成员

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

    那么四种变量,谁读取效率最高?

    在存取数据性能方面:字面量和本地变量>数组元素和对象元素

    所以除非必须使用数组和对象,在一般情况下建议使用字面量和本地对象。

2.作用域和作用域链

    作用域和作用域链是JavaScript中的关键概念,在性能和功能上,都有很大的影响,理解了作用域和作用域链,那个关于函数访问时,那些变量会被访问到;this的内存赋值等问题都会迎刃而解。

1)什么是作用域?

    在JavaScript中,作用域就是定义一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找

    这里的标识符,指的是变量名或者函数名

① 全局作用域

在任何代码中都能够访问的就是全局作用域:

  • 最外层函数和最外层函数外面定义的变量
  • 所有未定义直接赋值的变量
  • 所有window对象的属性
② 局部作用域

只有在固定的代码片段才能访问到的作用域:

function x(){
    var y = ;//局部作用域
}
alert(y)//外面访问不到
           

2)什么是作用域链?

    当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问

①执行上下文[Execution context]

在JavaScript中有三种代码运行环境:

- Global Code

    最外围的一个执行环境。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

- Function Code

    当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。

- Eval Code

    当代码执行的时候,Javascript解释器会进入不同的上下文进行解析,比如:

var a = "Global variable";
function add(a,b){
    var sum = a+b;
    return sum;
}
var x = add(,);
(function bar(){}) 
baz = "property of global object"
           

那么js解析的上下文如下:

    Clobal Execution Context -> add()

    每个Execution Context都有三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this,如下图所示:

Javascript中如何高效的数据存取
②变量对象[Variable object]

    变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。也就是说,一般VO中会包含以下信息:

- 函数的形参:

变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined;

- 函数声明:

变量对象的一个属性,其属性名和属性值都是函数对象创建出来的,如果变量对象已经办好了相同名字的属性,则替换它的值

- 变量声明:

变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名,则不会影响已经存在的属性

当JavaScript代码运行中,如果试图寻找一个变量的时候,就会首先查找VO。对于前面例子中的代码,VO的表示如下:

Javascript中如何高效的数据存取

其中:

函数表达式(与函数声明相对),没有使用var声明的变量,不包含在VO之中

③活动对象

    全局上下文的变量对象允许通过VO的属性名称间接访问;在函数执行上下文中,VO是不能直接访问的,此时由激活对象(Activation Object,缩写为AO)扮演VO的角色

④如何分析作用域链

    当一段JavaScript代码执行的时候,JavaScript解释器会创建Execution Context,其实这里会有两个阶段:

  • 创建阶段(当函数被调用,但是开始执行函数内部代码之前)
    • 创建Scope chain
    • 创建VO/AO(variables, functions and arguments)
    • 设置this的值
  • 激活/代码执行阶段
    • 设置变量的值、函数的引用,然后解释/执行代码
    • 这里想要详细介绍一下”创建VO/AO”中的一些细节,因为这些内容将直接影响代码运行的行为.

对于”创建VO/AO”这一步,JavaScript解释器主要做了下面的事情:

  • 根据函数的参数,创建并初始化arguments object
  • 扫描函数内部代码,查找函数声明(Function declaration)
    • 对于所有找到的函数声明,将函数名和函数引用存入VO/AO中
    • 如果VO/AO中已经有同名的函数,那么就进行覆盖
  • 扫描函数内部代码,查找变量声明(Variable declaration)
    • 对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为”undefined”
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

所以上述例子形成的scope-chain如下:

Javascript中如何高效的数据存取

    所以从上述的关系图可以看出,访问AO中的对象速度要比VO快的多,为了保证性能,我们尽量使用VO,减少数据的读取开销。在并没有优化javascript引擎的浏览器中,尽可能的使用局部变量,即:如果某个跨作用域的值在函数中被引用一次以上,那么就把他存储到局部变量中。

使用with,try-catch,eval会增加一个活动作用域,一般都会增加作用域链的深度,所以不建议

4)什么是闭包?闭包,作用域和内存之间的关系?

闭包是javascript最强大的特性,他的特点:

    能够允许函数访问局部作用域之外的数据

    * 就是让这些变量的值始终保持在内存中 *

所以

    1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便

3.原型和原型链

1)什么是原型?

    在JavaScript中对象时基于原型的。原型是其他对象的基础。它定义并实现了一个新创建的对象所必须包含的成员列表。

对象通过一个内部属性绑定到它的原型,在Firefox,Safri和Chorme浏览器中,属性为proto。正如java中所有的对象继承于Object,在javascript中,所有的对象也共同继承于Object对象。如下例子:

<script type="text/javascript">
   var book = {
       title:"a",
       publisher:"b"
   }
   alert(book.toString())
</script>
           

    book对象的结构图如下:

Javascript中如何高效的数据存取

     book对象有title,publisher两个成员属性,也有Object的原型对象,在book中没有toString()方法属性,所以javascript解释器会通过原型链去寻找Object中的方法。

2) 什么是原型链?

    原型的概念用于javascript的继承,比如A继承自B,B继承自C,那么A,B,C组成了原型链,如下所示:

<script type="text/javascript">
function BOOK(title,publisher){
    this.title = title;
    this.publisher=publisher;
}
   var book = new BOOK("A","B");
   alert(book.toString())
</script>
           

    book对象的结构图

Javascript中如何高效的数据存取

    book继承自一个BOOK的object对象,而BOOK对象继承自object对象,book->BOOK->object组成一个原型链。

    在JavaScript中,对一个对象属性(成员和方法)的调用,JavaScript会首先去找本对象属性中是否存在,如果不存在会依次按照原型链向上追溯,所以对于频繁调用的属性,建议在本对象中直接的定义,减少对于原型链的搜索

总结

  1. 访问字面量和局部变量的速度最快,相反访问数组元素和对象成员的相对较慢
  2. 由于局部变量存在与作用链的起始位置,因此访问局部变量要比跨作用域变量快的多
  3. 避免使用with,try-catch和eval(),他们会改变执行环境的作用域,把局部变量的作用域滞后,在一定程度上降低性能
  4. 嵌套对象成员会明显的影响性能;方法和属性在原型链中位置越深,访问的速度也会越慢
  5. 通常来讲,我们可以将常用的对象成员,数组元素,跨域变量保存在局部变量中来改善javascript的性能

相关好文

http://www.jianshu.com/p/21a16d44f150

http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

http://www.cnblogs.com/wilber2013/p/4909430.html

继续阅读