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. python中的scapy模块

    scapy模块是干嘛用的? 答:Scapy的是一个强大的交互式数据包处理程序(使用python编写).它能够伪造或者解码大量的网络协议数据包,能够发送.捕捉.匹配请求和回复包等等.它可以很容易地处理一 ...

  2. kuangbin带你飞 匹配问题 二分匹配 + 二分图多重匹配 + 二分图最大权匹配 + 一般图匹配带花树

    二分匹配:二分图的一些性质 二分图又称作二部图,是图论中的一种特殊模型. 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j ...

  3. UVALIVE 3562 Remember the A La Mode!

    费用流 建图很简单直接上代码 #include <map> #include <set> #include <list> #include <cmath> ...

  4. hdu 1669(二分图多重匹配)

    Jamie's Contact Groups Time Limit: 15000/7000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/ ...

  5. 使用jsonp进行跨域请求

    使用jsonp进行跨域请求 在实际的业务中很多时候需要用到跨域请求,然而jsonp为我们提供了一种非常方便的跨域请求的方式,具体实现代码如下: $.ajax({ type:"get" ...

  6. OfficeAddin基础

    运行的机器制

  7. sonarQube Github pull request扫描代码

    参考官方地址:https://docs.sonarqube.org/display/PLUG/GitHub+Plugin 运行环境:sonarQube6.2 + sonarScanner2.8 近来, ...

  8. [scrapy] spider object has no attribute '_rules'

    这是因为__init__方法没有继承父类 解决办法: # -*- coding:utf-8 -*- from selenium import webdriver from scrapy.contrib ...

  9. [xunsearch] 在thinkphp中使用xunsearch

    file: XunSearchController.class.php <?php namespace Home\Controller; include '/opt/xunsearch/sdk/ ...

  10. CodeForces - 990G GCD Counting

    Discription You are given a tree consisting of nn vertices. A number is written on each vertex; the ...