是从工作中演变而来的面试题。这是我在工作中遇到的一个问题。感觉挺有意思的,就当成面试题了。我发现几乎没有人能够正确回答所有问题并说明原因,所以我们拿出来聊一聊。先看题目代码:functionfun(n,o){console.log(o)return{fun:function(m){returnfun(m,n);}};}vara=fun(0);A。乐趣(1);a.乐趣(2);a.fun(3);//undefined,?,?,?varb=fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?varc=乐趣(0).乐趣(1);c.乐趣(2);c.fun(3);//undefined,?,?,?//问:a,b,c三行的输出是什么?这是一个非常典型的JS闭包问题。里面嵌套了三层fun函数,所以搞清楚每一层fun的函数是哪个fun函数就显得尤为重要。你可以先把你认为的结果写在纸上或别处,然后展开看看正确答案是什么?所有答案都正确吗?如果你回答正确,恭喜你,在js闭包问题上几乎没有什么难倒你的了;如果没有答案,继续分析。JS中有几种函数首先,在此之前需要了解的是,JS中的函数可以分为两种类型,命名函数(namedfunctions)和匿名函数。区分这两个函数的方法很简单,可以通过输出fn.name来判断,有名字的函数是命名函数,没有名字的函数是匿名函数注意:命名函数的名字是无法在lower上获取到的IE的版本,将返回undefined。建议在Firefox或GoogleChrome上测试或使用获取兼容IE的函数名的方法获取函数名:/***获取指定函数的函数名(为了兼容IE)*@param{Function}fun任何函数*/functiongetFunctionName(fun){if(fun.name!==undefined)returnfun.name;varret=fun.toString();ret=ret.substr('函数'.length);ret=ret.substr(0,ret.indexOf('('));returnret;}然后用上面的函数测试是否是匿名函数:可以知道变量fn1是命名函数,fn2是匿名函数类型,你还需要知道在JS中创建函数有几种方式。1.最常见和标准的函数声明方式,包括函数名和函数体。functionfn1(){}2.创建一个匿名函数表达式一个变量,这个变量的内容是一个函数varfn1=function(){}注意这个方法创建的函数是一个匿名函数,也就是没有函数名varfn1=function(){};getFunctionName(fn1).length;//03.创建一个命名函数表达式创建一个变量,其内容是一个名字为函数的变量varfn1=functionxxcanghai(){};注意:函数名的命名函数表达式只能在创建函数内部使用方法创建的函数只能使用fn1和not函数外层xxcanhai的函数名。xxcanghai的命名只能在创建的函数内部使用,用于测试:varfn1=functionxxcanghai(){console.log("in:fn1<",typeoffn1,">xxcanghai:<",typeofxxcanghai,">");};console.log("out:fn1<",typeoffn1,">xxcanghai:<",typeofxxcanghai,">");fn1();//out:fn1xxcanghai://in:fn1xxcanghai:可以看到xxcanghai的函数名不能在函数(out)外使用,是undefined。注意:在对象中定义一个函数,比如varo={fn:function(){…}},也属于函数表达式4。Function构造函数可以传递一个函数字符串给Function构造函数,返回包含这个字符串函数,这个方法创建一个匿名函数。5、自执行函数(function(){alert(1);})();(functionfn1(){alert(1);})();自执行函数属于上述“函数表达式”,规则相同6.其他创建函数的方法当然,还有其他创建函数或执行函数的方法。这里我就不多说了。比如使用eval、setTimeout、setInterval等非常常用的方法。这里就不过多介绍了。它们是非标准方法。这里没有过度展开的三个好玩的功能是什么关系呢?说完函数类型和创建函数的方法,就可以回归正题,看这道面试题了。这段代码中有3个funfunction,所以第一步要搞清楚这3个funfunction之间的关系,哪个function和哪个function是一样的。functionfun(n,o){console.log(o)return{fun:function(m){//...}};}首先看第一个fun函数,属于标准命名函数声明,是新创建的函数,其返回值是一个对象字面量表达式,属于一个新对象。这个新对象包含一个也称为fun的属性。从上面的介绍可以知道,它属于一个匿名函数表达式,即属性fun中存放的是一个新创建的匿名函数表达式。注意:所有声明的匿名函数都是一个新函数。所以第一个fun函数和第二个fun函数不同,都是新创建的函数。函数作用域链的问题在讲第三个fun函数之前,我们需要先讲一下函数表达式内部是否可以访问存储当前函数的变量。测试1,对象内部的函数表达式:varo={fn:function(){console.log(fn);}};o.fn();//ERROR错误测试2,非对象里面的函数表达式:varfn=function(){console.log(fn);};fn();//function(){console.log(fn);};正确的结论是:使用var或者一个非对象的内部函数表达式在里面,可以访问存储当前函数的变量;无法访问对象内部的那些。原因也很简单,因为函数作用域链的问题,使用var就是在外部创建一个fn变量。函数当然不能在函数内部找到fn再去查找书本范围内的fn。在里面创建对象的时候,因为fn不是在函数作用域内创建的,所以是访问不到的。所以综上所述,可以知道最内层返回的fun函数不是第二层的fun函数,而是最外层的fun函数。所以三个fun函数之间的关系也理清了。第一个等于第三个,他们不等于第二个。调用了哪个函数?再看原题,现在知道程序中有两个funfunction(第一个和第三个是一样的),那么下一个问题就是搞清楚他在运行时执行的是哪个funfunction?functionfun(n,o){console.log(o)return{fun:function(m){returnfun(m,n);}};}vara=fun(0);a.乐趣(1);a.乐趣(2);a.fun(3);//undefined,?,?,?varb=fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?varc=乐趣(0).乐趣(1);c.乐趣(2);c.fun(3);//undefined,?,?,?//问:三行a,b,c的输出是什么?1.第一行avara=fun(0);a.乐趣(1);a.乐趣(2);a.乐趣(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,第四次也一样;即:最后的答案Forundefined,0,0,02,第二行bvarb=fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?从fun(0)开始,乍一看肯定是调用的第一级fun函数;而它的返回值是一个对象,所以第二个fun(1)调用了二级fun函数,后面几个也是二级fun调用函数。然后:第一次调用第一层的fun(0)时,o是undefined;当。。。的时候。第一次调用的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;第四次调用.fun(3)时,m为3,第三次调用关闭n,同理最后一层的fun函数调用fun(3,2);所以o是2;即最终答案:undefined,0,1,23第三行cvarc=fun(0).fun(1);c.乐趣(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;当。。。的时候。第一次调用的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之后,这段代码原本是将异步回调重写为同步调用组件的代码。发现这个坑后,我对JS的闭包有了更深的理解。网上关于什么是闭包的文章数不胜数,但是要理解什么是闭包,还得自己在代码中去发现和领悟。如果要我说什么是闭包,我认为广义上的闭包就是指变量在自己的作用域内使用,称为闭包。希望读者可以通过本文对闭包现象有更深入的了解。如有其他见解或看法,欢迎指正或留言讨论。(完)原站http://www.cnblogs.com/xxcanghai/p/4991870.html如果您觉得本文值得您花时间阅读,请点击右下角推荐。谢谢作者:小沧海来源:http://www.cnblogs.com/xxcanghai/本文地址:http://www.cnblogs.com/xxcanghai/p/4991870.html