作用域及执行环境
变量的作用域有全局变量和局部变量两种,这一点与其他语言(如C)的概念是非常相似的。
执行环境(execution context)定义了变量或者函数有权访问其他数据,决定他们各自的行为。每个执行环境都有关联的变量对象(VO),环境中定义的变量和函数都会保存在这个对象中。
作用域链(scope chain)保证对执行环境有权访问的所有变量和函数的有序访问。
全局执行环境(GO)在web浏览器中通常为window对象,始终处于作用域链的最后一位。
执行函数的时候,会创建一个活动对象(AO)作为变量对象置于作用域链的前端。
预解析
预解析的简单规则:
-
把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
-
把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
-
先提升var,在提升function
理解函数提升的关键,就是理解函数声明与函数表达式之间的区别。 ——《JavaScript高级程序设计》
预解析的详细步骤:
以上提到的GO和AO,GO是始终存在并处于作用域底端的,当一个函数开始预解析的时候严格按照以下顺序:
- 创建AO对象AO{ } (AO也称执行期上下文)
- 找形参和变量声明;将变量和形参名作为AO的属性名,值为undefined
- 将实参值与形参统一
- 在函数体里面找函数声明,值赋予函数体
也就是说,在作用域顶端创建了AO,于是函数在执行查找变量的时候,按照作用域顺序开始查找,从0开始,从上到下。
一般来讲,当函数执行完毕之后,局部活动对象AO就会被销毁,内存中仅保存全局作用域。但是嵌套函数的情况又有所不同(在函数中定义另外一个函数),于是闭包出现了。
闭包
先不着急说什么是闭包,上文提到,在函数内部定义函数,内部函数会将外部函数的AO添加到自己的作用域中,因此内部函数的作用域链包含了外部函数的AO。即使外部函数被销毁,倘若内部函数被返回了,在其他地方被调用了,那么它仍然可以访问外部函数的AO中的变量,直到内部函数也被销毁。
注意上图,a的AO被销毁后,作用域链的指针不再指向AO,但是b仍然包含a的AO。
说到这里,已经对闭包的概念有所认识了,我们给出闭包的定义:
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。
闭包的用途
- 可以读取函数内部的变量,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
- 让变量的值始终保持在内存中。
延长作用域链
有些语句可以在作用域链前端添加一个变量对象,该变量对象会在代码执行后被移除。
- 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执行结束的时候仍然会存在于循环外部的执行环境中。