function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(); a.fun(); a.fun(); a.fun();//undefined,?,?,?
var b = fun().fun().fun().fun();//undefined,?,?,?
var c = fun().fun(); c.fun(); c.fun();//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();  a.fun();  a.fun();  a.fun();

  可以得知,第一个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().fun().fun().fun();

  先从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().fun();  c.fun();  c.fun();

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

  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上

深入理解JS函数作用域链与闭包问题的更多相关文章

  1. 个人理解的javascript作用域链与闭包

    闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: function f1(){ n=100; function f2(){ alert(n); } retu ...

  2. JS 之作用域链和闭包

    1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(nam ...

  3. js之作用域链到闭包

    一.作用域 全局作用域和函数作用域(局部作用域). 一个变量的作用域就是源代码中定义这个变量的区域. 二.作用域链和闭包 全局变量只有一个(window,globel),全局环境下每一个函数都会形成一 ...

  4. js深入(三)作用域链与闭包

    在之前我们根绝对象的原型说过了js的原型链,那么同样的js 万物皆对象,函数也同样存在这么一个链式的关系,就是函数的作用域链 作用域链 首先先来回顾一下之前讲到的原型链的寻找机制,就是实例会先从本身开 ...

  5. 1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

    参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p ...

  6. JS详细图解作用域链与闭包

    JS详细图解作用域链与闭包 攻克闭包难题 初学JavaScript的时候,我在学习闭包上,走了很多弯路.而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战. 闭包有多重要?如果你 ...

  7. js 作用域,作用域链,闭包

    什么是作用域? 作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围.全局变量拥有全局作用域,局部变量则拥有局部作用域. js是一种没有块级作用域的语言(包括if.for等语句的 ...

  8. 前端高质量知识(四)-JS详细图解作用域链与闭包

    攻克闭包难题 初学JavaScript的时候,我在学习闭包上,走了很多弯路.而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战. 闭包有多重要?如果你是初入前端的朋友,我没有办法 ...

  9. JavaScript系列----作用域链和闭包

    1.作用域链 1.1.什么是作用域 谈起作用域链,我们就不得不从作用域开始谈起.因为所谓的作用域链就是由多个作用域组成的.那么, 什么是作用域呢? 1.1.1作用域是一个函数在执行时期的执行环境. 每 ...

随机推荐

  1. dd命令测试硬盘I/O速度

    [root@bogon ~]# dd if=/dev/zero of=/home/tom.a.txt bs=200MB count=1记录了1+0 的读入记录了1+0 的写出200000000字节(2 ...

  2. MSP432P401R时钟入门

    拿到msp432的板子差不多一年了,刚刚进行了开机点亮LED工程:         首先是msp432的时钟模块(CS),个人理解msp432最特色的功能应该是超低功耗和高性能的组合.432系列的时钟 ...

  3. Android源码的BUG

    在Android系统移植过程中,遇到很多源码上的BUG.但是我们看到市面上都是没有这些问题的.难道这些BUG在每个开发商都要经历一次解BUG的过程吗?Android释放的源码是否是最新的?暂时没有想法 ...

  4. python基础===pip安装模块失败

    此情况只用于网络不畅的安装模块背景: 总出现红色的 Could not find a version that satisfies the requirement pymongo(from versi ...

  5. libssh2

    http://www.cnblogs.com/lzrabbit/p/4298794.html shell脚本实现ssh自动登录远程服务器示例: #!/usr/bin/expect spawn ssh ...

  6. 【 Zabbix 】— 基础知识

    zabbix基础 zabbix是一个高度集成的网络监控套件.通过一个软件包即可提供如下特性: 1.数据收集 (1)可用性及性能检测 (2)支持SNMP.IPMI.JMX监控 (3)自定义检测 (4)自 ...

  7. 《Java编程思想》笔记 第三章 操作符

    1.操作符种类: 运算顺序1-7 一元操作符(单目操作符)  - 负号, + 正号,--递减,++递增 算术操作符 + - *  /  % 移位操作符  <<左移(低位补0),>&g ...

  8. PostgreSQL教程

    https://www.yiibai.com/postgresql/ https://blog.csdn.net/zhangzeyuaaa/article/details/77941039

  9. AC日记——Collectors Problem uva 10779

    UVA - 10779 思路: 最大流: s向所有的贴纸的种类连边,流量为Bob拥有的数量: 然后,Bob的朋友如果没有这种贴纸,则这种贴纸向bob的朋友连边,容量1: 如果bob的朋友的贴纸很多大于 ...

  10. 使用Postman访问OAuth2保护的WebAPI

    Instantnoodle现时的WebAPI已经受Azure AD保护,平时直接输入URL的方式已经不能够正常访问到WebAPI 所有API都可以Swagger页面找到 http://getazdev ...