1.作用域和执行环境

作用域链是JS中一个很重要的概念。它通常与执行环境和变量对象一起来说明一个闭包。

执行环境定义了变量或函数有权访问的其它数据。每个执行环境中都有一个相关的变量对象,里面存放着环境中的变量和函数。

在这里插入图片描述

这个图比较清晰,执行环境是老大,里面包含着作用域链,作用域链连接着各种变量对象和活动对象,变量对象里面存放着变量和函数。根据这些连接,来进行访问各种变量和函数。

环境的结构是一个栈。当执行某个函数时,该函数的环境就会入栈,在最上面,执行完之后,再弹出来,控制权返回之前的执行环境。在执行的同时,会将当前函数环境的变量对象连接到作用域链的最前面。所以全局变量对象永远是作用域链最后一个连接的对象。

作用域链是向上搜索访问的,即最前面连接的对象可以访问后面的,但后面的不能访问前面的。和C++一样,不同的是js没有块级作用域,就是花括号里面的变量外面也可以访问,所以有一点需要注意,for循环语句执行完访问i的值就成了最大的那个值,即使你再在循环语句后面重新声明i,也还是最大的那个值。

然后JS用一个匿名函数立即执行的方式模仿了块级作用域。

1
2
3
(function(){
//块级作用域
})();

原因是并没有指向匿名函数的引用,所以它一旦执行完,就可以立即销毁其执行环境,包括里面所有的作用域链和变量对象。

2.闭包

闭包是指有权访问另一个函数作用域中变量的函数。常见的形式是一个函数里有另一个函数。

理一下函数执行的流程,当函数被调用,首先会创建一个它的执行环境,里面有它的作用域链,然后arguments(是对形参的一个映射,但是值是通过索引来获取)和其它实参的值会来初始化它的形参,初始化这个函数的活动对象,它自己的活动对象在它自己的作用域链的最前面。

举个例子。

1
2
3
4
function compare(val1,val2){
if(val1<val2)
...
}

首先在创建compare这个函数时,就会创建一个预先包含全局变量对象的作用域链,这个作用域链保存在函数内部[[scope]]属性中。

然后调用这个函数时,先创建它的执行环境,然后复制函数的[[scope]]属性中的对象,构建起执行环境的作用域链。然后实参和arguments初始化活动对象,把活动对象推入执行环境作用域链的前端。

关于活动对象和变量对象的区别:最直观的是,活动对象除了包含变量对象中变量和函数声明外,还有形参和arguments对象。所以变量对象是执行环境中定义的变量和函数,活动对象是函数被调用时创建的“变量对象”?(活动对象只是正在被执行和引用的变量对象。)

当函数访问一个变量时,就会从作用域链中从前往后搜索,有的话就能访问到。这就导致一个问题,当一个函数的活动对象被另一个函数的作用域链连接时,它执行完成后理应销毁的活动对象还会存在,因为还被引用着,这样会浪费内存。

举个例子

1
2
3
4
5
6
7
function aaa(){
return function(){
return
}
}
a = aaa()
a = null

a被赋值后其实还一直保持着对匿名函数的引用,就导致aaa的活动对象一直在,可以通过=null来解除对匿名函数的引用。

闭包中有个值得关注的对象,this。最重要的是记住this与执行环境绑定,匿名函数无指针,严格模式下this为undefined。

闭包可以用来做特权方法,模块方法等来实现私有变量。