到底什么是执行环境、作用域和作用域链

执行上下文(也称执行环境)

执行环境也成为执行上下文(Execution context,EC)。就是当js代码执行的时候,会进入不同的执行上下文,这些执行上下文就构成了一个执行上下文栈(Execution context stack,ECS)

  • 全局执行环境:在浏览器环境中全局执行环境就是windows对象
  • 函数执行环境:当某函数被调用时,首先会创建一个执行环境及相应的作用域链,然后使用arguments和其他命名参数的值来初始化执行环境的变量对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = "tom"
function foo(){console.log(a)}
function outer(){
var b = "jack"
console.log(b)
function inner(){
var c = "heihei"
console.log(c)
foo()
}
inner()
}
outer()

js代码运行时,首先进入Global Execution Context全局执行上下文,然后依次进入outer,inner,foo的执行上下文。执行上下文栈如下图。

  • js代码执行的时候,第一个进入的总是Global Execution Context ,所以他总是在ECS的最底部。
  • 函数的每次调用都会创建一个新的执行上下文。当执行流进入一个函数时,函数的环境就会被推入Execution Context Stack顶部。在函数执行完后,栈将其环境弹出,把控制权返回给之前的执行环境。

Execution Context

每个Execution Context都有3个重要的属性

  • 变量对象(Variable object,VO):变量对象是与执行上下文相关的特殊的对象,存储了在上下文中定义的变量和函数声明。当js运行代码的时候如果要寻找变量,首选会查找VO。VO包含以下信息

    • 变量
    • 函数声明
    • 函数的形参
  • 作用域链(Scope chain):作用域即是变量对象,作用域链是一个由变量对象组成的带有头结点的单向链表,作用是用来进行有序地查找变量内部环境可通过作用域链访问所有的外部环境,外部环境不能访问内部环境中的任何变量和函数。

    [[Scope]]属性是一个指向单向链表的头结点的指针。

    1
    当js代码进行变量查找时,首先从作用域链的第一个变量对象上查找,如果找不到就到第二个环境变量上查找,沿着作用域脸一级一级地搜索,最后到全局对象,如果在全局对象也找不到,就会抛出undefined的错误
  • this:指向一个环境对象

活动对象(Activation object,AO)
  • 在global全局上下文中,变量对象也是全局对象自身。
  • 在函数上下文中,VO是不能直接访问的,则将其活动对象(activation object)作为变量对象。活动对象是在进入函数上下文的时候被创建的,它通过函数的arguments属性初始化

Arguments Objects 是函数上下文里的活动对象AO中的内部对象,它包括下列属性:

  1. callee:指向当前函数的引用
  2. length: 真正传递的参数的个数
  3. properties-indexes:就是函数的参数值(按参数列表从左到右排列)

当上面代码outer()执行的时候,就有一个outer的AO被创建

Activation object
arguments 参数对象 {}
b 变量 “var in outer”
inner 函数声明 \

Execution Context的内部过程

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

  • 函数被调用,但是开始执行函数内部代码之前
    • 创建VO/AO
      • 根据函数的参数,创建并初始化参数对象arguments object
      • 扫描内部代码,查找函数声明,并将函数名函数引用存入VO/AO中
      • 扫描内部代码,查找变量声明,并把变量名存入VO/AO中,并初始化为undefined
    • 创建Scope chain,作用域链
    • 确定this指向
  • 函数内部代码执行阶段
    • 设置变量的值,函数的引用,然后执行代码
执行环境的生命周期

作用域(Scope)

作用域分为全局作用域局部作用域

  • 全局作用域:
    • 最外层函数及在最外层函数外定义的变量
    • 没有通过关键字生命的变量
    • 浏览器中,window对象的属性
1
2
3
4
5
6
7
8
9
10
var data = [];
for(var i = 0 ; i < 3; i++){
data[i]=function() {
console.log(i);
}
}
data[0]();// 3
data[1]();// 3
data[2]();// 3
// 这里变量i是存放在全局VO中的,循环结束后i的值变为3。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function counter() {
var x = 0;
return {
increase: function increase() { return ++x; },
decrease: function decrease() { return --x; }
};
}
var ctor = counter();
console.log(ctor.increase());
console.log(ctor.decrease());
#1.当代码进入全局上下文是,会创建Global VO
#2.当执行到var ctor = counter()时,会创建新的函数上下文环境,并设置AO,创建Scope chain
#3.counter()函数执行完毕后退出执行上下文栈,但是此时ctor仍然引用着counter函数的活动对象AO,所以counter里面的活动对象不会被销毁
#4.当执行ctor.increase()时候,进入它的函数执行上下文,创建VO/AO,scope chain和this。这是ctor.increas的AO指向counter AO