Closure的本质问题其实就是词法作用域的问题, 或者说是JavaScript引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找.

JavaScript引擎查找标识符位置的规则, 简而言之, 就是:

作用域查找会在找到第一个匹配的标识符时停止

换句话说是: 作用域查找始终从运行时所处的最内部作用域开始, 逐级向外或者说向上进行, 知道遇见第一个匹配的标识符为止

带着以上的结论, 我们看看这个例子来验证一下:

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}

//Question 1
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);

//Question 2
var b = fun(0).fun(1).fun(2).fun(3);

//Question 3
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);

Question 1的答案和解释

var a = fun(0); //undefined
a.fun(1); //0
a.fun(2); //0
a.fun(3); //0

Question 1.1
传入n=0,并没有传入o, 所以是undefined。这个时候a是含有fun的object:

{
    fun:function(m){
      return fun(m,n);
    }
}

Question 1.2
a包含的fun被trigger了, 传入了n=1并且最终trigger了fun(m, n).
那么问题来了, m和n分别是什么?
m是刚刚传进来的1, 这个很简单;
n只能根据作用域网上查找, 发现时上次传进来的一个形参, 它的值就应该是上次传进来的0;
所以, 最终a.fun(1)是trigger了fun(1, 0), 最后的答案是0.

对于Question 1.3和1.4来说, 同理最终的答案也是0.

Question 2的答案和解释

b = fun(0).fun(1).fun(2).fun(3)
//undefined
//0
//1
//2

根据Question 1.2的解释 就很好理解这个答案了

Question 3的答案和解释

var c = fun(0).fun(1); c.fun(2); c.fun(3);
//undefined
//0
//1
//1

根据Question 1.2的解释 var c = fun(0).fun(1)会分别log出undefined0
这个时候c应该是含有fun的object:

{
    fun:function(m){
      return fun(m,n);
    }
}

当c.fun(2)的时候 其实也是trigger了fun(m, n).
m是当前传入的2. n由于没有被传入, 只能向外作用域寻找, 发现上次传入的n且n=1.
所以最后log的值为1.

同理对于c.fun(3) 最后log的值也为1.

总结
通过理解JavaScript引擎向上查找作用域的规则 可以更好的让我们理解Closure

Reference:
你不一定能做对的JavaScript闭包面试题
You Don't Know JS: Scope & Closures


superdtx
4 声望2 粉丝