作用域及执行环境

变量的作用域有全局变量和局部变量两种,这一点与其他语言(如C)的概念是非常相似的。

执行环境(execution context)定义了变量或者函数有权访问其他数据,决定他们各自的行为。每个执行环境都有关联的变量对象(VO),环境中定义的变量和函数都会保存在这个对象中。

作用域链(scope chain)保证对执行环境有权访问的所有变量和函数的有序访问

全局执行环境(GO)在web浏览器中通常为window对象,始终处于作用域链的最后一位。

执行函数的时候,会创建一个活动对象(AO)作为变量对象置于作用域链的前端。

预解析

预解析的简单规则:

  1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。

  2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。

  3. 先提升var,在提升function

理解函数提升的关键,就是理解函数声明与函数表达式之间的区别。  ——《JavaScript高级程序设计》 

预解析的详细步骤:

以上提到的GO和AO,GO是始终存在并处于作用域底端的,当一个函数开始预解析的时候严格按照以下顺序:

  1. 创建AO对象AO{     }   (AO也称执行期上下文)
  2. 找形参和变量声明;将变量和形参名作为AO的属性名,值为undefined
  3. 将实参值与形参统一
  4. 在函数体里面找函数声明,值赋予函数体

 

JavaScript作用域链、预解析、闭包初步理解

也就是说,在作用域顶端创建了AO,于是函数在执行查找变量的时候,按照作用域顺序开始查找,从0开始,从上到下。 

一般来讲,当函数执行完毕之后,局部活动对象AO就会被销毁,内存中仅保存全局作用域。但是嵌套函数的情况又有所不同(在函数中定义另外一个函数),于是闭包出现了。

闭包

先不着急说什么是闭包,上文提到,在函数内部定义函数,内部函数会将外部函数的AO添加到自己的作用域中,因此内部函数的作用域链包含了外部函数的AO。即使外部函数被销毁,倘若内部函数被返回了,在其他地方被调用了,那么它仍然可以访问外部函数的AO中的变量,直到内部函数也被销毁。

JavaScript作用域链、预解析、闭包初步理解

注意上图,a的AO被销毁后,作用域链的指针不再指向AO,但是b仍然包含a的AO。 

说到这里,已经对闭包的概念有所认识了,我们给出闭包的定义:

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。

闭包的用途

  1. 可以读取函数内部的变量,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
  2. 让变量的值始终保持在内存中

延长作用域链

有些语句可以在作用域链前端添加一个变量对象,该变量对象会在代码执行后被移除。

  • try-catch语句的catch块

对于catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

  • with语句

with的作用是将代码的作用域设置到一个特定的对象中。

这一部分以后在详细说。

JS没有块级作用域

JS中没有花括号块级作用域

if(true) {
var color = "red" ;
}

alert (color) ; //red

花括号执行完毕之后并不会销毁变量。

尤其记住for语句中声明的变量for(var i=0;i<10;i++) {  }   ,变量i在for执行结束的时候仍然会存在于循环外部的执行环境中。 

 

相关文章: