ES 基础 —— 作用域链

published

在介绍 Execution Context 时,提到了 Scope Chain。这里来详细说说它。

什么是 Scope Chain?

Scope Chain 由当前 Execution Context 的 VO 与其所有父级 Execution Context 的词法(Lexical)范畴上的 VO 组成。

用一段代码来解释:

function one() {
  two()

  function two() {
    three()

    function three() {
      console.log('I am at function three');
    }
  }
}

one()

在 Global Execution Context 中调用 one()one() 调用 two()two() 调用 three()

调用函数 Scope Chain
one() [one() VO] + [Global VO]
two() [two() VO] + [one() VO] + [Global VO]
three() [three() VO] + [two() VO] + [one() VO] + [Global VO]

Scope Chain 与闭包

ES 中,闭包常常被当作只有高级开发者才能真正掌握的技术,但理解 Scope Chain 后,很容易就能理解闭包。就像 Crockford 说的:

An inner function always has access to the vars and parameters of its outer function, even after the outer function has returned…

举个闭包的例子:

function foo() {
  const a = 'private variable'

  return function bar() {
    console.log(a)
  }
}

const log = foo()

log() // private variable

上面的代码中,Global Execution Context 的 VO 中有一个函数 foo() 和一个变量 log。这个闭包的奇妙之处在于那个 a,即使 foo() 执行完毕了,a 还是能被访问到。 为什么?

// Global Execution Context when evaluated
global.VO = {
  foo: pointer to foo(),
  log: returned value of global.VO.foo,
  scopeChain: [global.VO]
}

// Foo Execution Context when evaluated
foo.VO = {
  bar: pointer to bar(),
  a: 'private variable',
  scopeChain: [foo.VO, global.VO]
}

// Bar Execution Context when evaluated
bar.VO = {
  scopeChain: [bar.VO, foo.VO, global.VO]
}

可以从以上伪代码中看到,在调用 bar() 时,Scope Chain 为 [bar.VO, foo.VO, global.VO],访问 a 时,函数会沿着作用域链搜索 aa 就在 foo.VO 中。所以就访问到了。

Scope Chain 与 Prototype Chain

当要访问一个对象的某个属性时,解释器:

  • 首先,通过 Scope Chain 定位对象。
  • 然后,通过对象的 Prototype Chain 检索属性。

看个例子:

const bar = {}

function foo() {
  bar.a = 'Set from foo()'

  return function inner() {
    console.log(bar.a)
  }
}

foo()() // 'Set from foo()'

foo()() 执行时,解释器先通过 Scope Chain 找到了对象 bar,然后在对象中找到了属性 a

const bar = {}

function foo() {
  Object.prototype.a = 'Set from prototype'

  return function inner() {
    console.log(bar.a)
  }
}

foo()() // 'Set from prototype()'

foo()() 执行时,解释器先通过 Scope Chain 找到了对象 bar,但对象bar 中不存在属性 a,于是,解释器查找 Prototype Chain,在 Object.prototype 中找到了属性 a

更多