深入理解JS函数作用域链与闭包问题

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?

//问:三行a,b,c的输出分别是什么?

  这是一道非常典型的JS闭包问题。其中嵌套了三层fun函数,搞清楚每层fun的函数是那个fun函数尤为重要。可以先在纸上或其他地方写下你认为的结果,然后展开看看正确答案是什么?

//答案:
//a: undefined,0,0,0
//b: undefined,0,1,2
//c: undefined,0,1,1

  要分析此问题,我们需要了解:

一、三个fun函数的关系是什么?

  这段代码中出现了三个fun函数,所以第一步先搞清楚,这三个fun函数的关系,哪个函数与哪个函数是相同的。

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

  先看第一个fun函数,属于标准具名函数声明,是新创建的函数,他的返回值是一个对象字面量表达式,属于一个新的object。这个新的对象内部包含一个也叫fun的属性,属于匿名函数表达式,即fun这个属性中存放的是一个新创建匿名函数表达式。

  注意:所有声明的匿名函数都是一个新函数。

  所以第一个fun函数与第二个fun函数不相同,均为新创建的函数。

二、函数作用域链的问题

  再说第三个fun函数之前需要先说下,在函数表达式内部能不能访问存放当前函数的变量。

  测试1,对象内部的函数表达式:

var o={
  fn:function (){
    console.log(fn);
  }
};
o.fn();//ERROR报错:fn is not defined

  测试2,非对象内部的函数表达式:

var fn=function (){
  console.log(fn);
};
fn();//function (){console.log(fn);};正确

  结论是:使用 var 或是非对象内部的函数表达式内,可以访问到存放当前函数的变量;在对象内部的则不能访问到。

  原因也非常简单,因为函数作用域链的问题,采用var的是在外部创建了一个fn变量,函数内部当然可以在内部寻找不到 fn 后向上级作用域查找 fn,而在创建对象内部时,因为没有在函数作用域内创建 fn,所以无法访问

  所以综上所述,可以得知,最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数

  所以,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。

 三、到底在调用哪个函数?

  再看下原题,现在知道了程序中有两个fun函数(第一个和第三个相同),遂接下来的问题是搞清楚,运行时他执行的是哪个fun函数?

1、第一行a

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

  可以得知,第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。因此:

  在第一次调用fun(0)时,o为undefined;

  第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;

  第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0

  第四次同理;

  即:最终答案为undefined,0,0,0

2、第二行b

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

  先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。因此:

  在第一次调用第一层fun(0)时,o为undefined;

  第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;

  第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;然后闭包了第三次的n就为2了

  第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;

  即最终答案:undefined,0,1,2

3、第三行c

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

  根据前面两个例子,可以得知:

  fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,因此c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的是fun(1)返回的第二层fun函数。因此:

  在第一次调用第一层fun(0)时,o为undefined;

  第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n(即n=1了),也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;

  第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;

  第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1

  即最终答案:undefined,0,1,1

总结:

  分析该问题主要需要了解函数作用域链的问题,这样就能清楚第一个fun其实是和第三个fun是一样的;

  其次就是需要了解闭包的知识,如果要我说什么是闭包,我认为,广义上的闭包就是指一个变量在他自身作用域外被使用了,就叫发生了闭包。所以当第三个fun执行时,闭包将m的值绑定到了n上

原文地址:https://www.cnblogs.com/goloving/p/8584987.html