作用域链-闭包-执行上下文

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

分析代码

第一段代码:

var scope = "global scope"
function checkscope() {
  var scope = "local scope"
  function f() {
    return scope
  }
  return f()
}
checkscope()

1.执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈

ECStack = [globalContext]

2.全局上下文初始化

globalContext = {
  VO: [global],
  Scope: [globalContext.VO],
  this: globalContext.VO
}

3.初始化的同时,checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]]

checkscope.[[scope]] = [
    globalContext.VO
];

3.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈

ECStack = [checkscopeContext, globalContext]

4.checkscope 函数执行上下文初始化:

复制函数 [[scope]] 属性创建作用域链, 用 arguments 创建活动对象, 初始化活动对象,即加入形参、函数声明、变量声明, 将活动对象压入 checkscope 作用域链顶端。 同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope: undefined,
        f: reference to function f(){}
    },
    Scope: [AO, globalContext.VO],
    this: undefined
}

5.执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈

ECStack = [fContext, checkscopeContext, globalContext]

6.f 函数执行上下文初始化, 以下跟第 4 步相同:

复制函数 [[scope]] 属性创建作用域链 用 arguments 创建活动对象 初始化活动对象,即加入形参、函数声明、变量声明 将活动对象压入 f 作用域链顶端

fContext = {
  AO: {
    arguments: {
      length: 0
    }
  },
  Scope: [AO, checkscopeContext.AO, globalContext.VO],
  this: undefined
}

7.f 函数执行,沿着作用域链查找 scope 值,返回 scope

8.f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

ECStack = [checkscopeContext, globalContext]

9.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出

ECStack = [globalContext]

分析第二段代码

var scope = "global scope"
function checkscope() {
  var scope = "local scope"
  function f() {
    return scope
  }
  return f
}
checkscope()()

1.执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈:

ECStack = [globalContext]

2.开始执行代码,全局上下文初始化:

globalContext = {
  VO: [global],
  Scope: [globalContext.VO],
  this: globalContext.VO
}

3.初始化的同时,checkscope函数被创建,保存作用域链到内部属性[[scope]]:

checkscope.[[scope]] = [
      globalContext.VO
];

4.开始执行checkscope函数,创建checkscope函数执行上下文,并将checkscope函数上下文压入执行上下文栈:

ECStack = [checkscopeContext, globalContext]

5.checkscope函数上下文初始化:

  1. 复制函数 [[scope]] 属性创建作用域链,
  2. 用 arguments 创建活动对象,
  3. 初始化活动对象,即加入形参、函数声明、变量声明,
  4. 将活动对象压入 checkscope 函数作用域链顶端。
checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
}

初始化的同时, f函数被创建,保存作用域链到 f函数的内部属性[[scope]]:

f.[[scope]] = [checkscopeContext.AO, globalContext.VO]

6.checkscope函数执行,随着函数的执行,修改AO的值,所以此时checkscopeContext变更为:

checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: "local scope",
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: checkscopeContext.AO
}

接着返回了f函数.

7.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出:

ECStack = [globalContext]

8.开始执行f 函数,创建f 函数执行上下文,并将f 函数上下文压入执行上下文栈:

ECStack = [fContext, globalContext]

9.f 函数上下文初始化:

  1. 复制函数 [[scope]] 属性创建作用域链,
  2. 用 arguments 创建活动对象,
  3. 初始化活动对象,即加入形参、函数声明、变量声明,
  4. 将活动对象压入 f 函数 作用域链顶端。
fContext = {
  AO: {
    arguments: {
      length: 0
    }
  },
  Scope: [AO, checkscopeContext.AO, globalContext.VO],
  this: undefined
}

10.f 函数执行,沿着作用域链查找scope 的值,找到并返回了scope.

可是当 f 函数执行的时候,checkscope 函数上下文已经被销毁了(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

这是因为 checkscope 函数执行上下文初始化时,f 函数同时被创建,保存作用域链到 f 函数的内部属性[[scope]],所以即使checkscope函数执行完毕,被弹出执行上下文栈,但是checkscopeContext.AO 依然存在于 f 函数的内部属性[scope]]中:

f.[[scope]] = [checkscopeContext.AO, globalContext.VO]

所以在f 函数执行的时候仍然可以通过 f 函数的作用域链能找到scope.所以这里就产生了闭包:

  • 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)

  • 在代码中引用了自由变量

    11.f 函数执行完毕,f 执行上下文从执行上下文栈中弹出:

ECStack = [globalContext]