Higanbana

深入理解闭包(五)——作用域、作用域链和执行上下文

作用域,作用域链,执行上下文三者之间有着密切的关系,相信有一部分人对这三者只是有一种模糊的概念,今天我们就来理一理,首先我们要回顾一张图片,由这张图,你可以清楚地看到这三者分别是在什么时候创建的,其中又有什么基本的联系。

作用域

作用域是在代码编译阶段确定的,一旦代码写好,它的作用域就确定了。js中只有两种作用域,即全局作用域和局部作用域。

全局作用域

如果一个对象在代码的任何一个地方都可以被访问到,那么这个对象所在的作用域是全局作用域。

  1. 最外层函数和在最外层函数外面定义的变量。
  2. 所有末定义直接赋值的变量将被自动添加到全局作用域(但是严格模式下,初始化未经声明的变量会导致错误)。
  3. 所有window对象的内置属性,例如window.name、window.location、window.top等等都是全局变量,并且所有属于全局作用域的变量都是window对象的属性。

局部作用域

js中的局部作用域是按照函数划分的,因此也称函数作用域,其他一些语言是按照花括号{}划分的,称为块级作用域,区别如下:

  1. 在Java、C或C++中,花括号{}内封闭的代码块有自己的作用域,在{}外部无法访问{}内部的变量,这个作用域就称为块级作用域。
  2. 在javascript中不存在块级作用域,而是把每个函数作为一个作用域,在函数外部无法访问函数内部的变量。

更多关于作用域的问题参考这篇文章:理解javascript作用域

执行上下文

执行上下文就是为代码执行做的准备工作。主要分为两类:

  1. 全局执行上下文:代码刚开始运行就会创建的执行上下文。
  2. 函数执行上下文:函数被调用时创建的执行上下文。

执行上下文前面两篇文章已经讲得非常透彻了,这里也就不再多说了。

更多关于执行上下文的问题参考这两篇文章:变量对象 执行上下文栈

作用域链

作用域链本质上是一个指向当前环境与上层环境的一系列变量对象的指针列表(它只引用但不实际包含变量对象),作用域链保证了当前执行环境对符合访问权限的变量和函数的有序访问。
举个栗子:

1
2
3
4
5
6
7
8
9
10
var a = 1;
function out() {
var b = 2;
function inner() {
var c = 3;
console.log(a+b+c);
}
inner();
}
out();

首先,代码开始运行时就创建了全局上下文环境,接着运行到out()时创建out函数的执行上下文,最后运行到inner()时创建inner函数的执行上下文,这三个执行上下文中的变量对象分别是:

其中,由于函数调用时都没有传入参数,所以arguments都没有值,reference表示该函数的地址引用。

  1. 全局的作用域链:由于它只含全局作用域,没有上级,因此它的作用域链只指向本身的全局变量对象(因为只有一个,甚至可以理解为全局环境不存在作用域链)。查找标识符时只能从本身的全局变量对象中查找。
  2. 函数out的作用域链:可以引用函数out本身的变量对象以及全局的变量对象。查找标识符时,先在函数out变量对象中寻找,找不到的话再去上一级全局变量对象查找。
  3. 函数inner的作用域链:可以引用函数inner本身的变量对象和上一级out函数的变量对象以及全局的变量对象。查找标识符时依次从inner,out,全局变量对象中查找。

更多关于作用域链的问题参考这篇文章:理解javascript作用域