一道js闭包面试题的学习

最近看到一条有意思的闭包面试题,但是看到原文的解析,我自己觉得有点迷糊,所以自己重新做一下这条题目。

闭包面试题原题

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

// 第一个例子
var a = fun(0); // 返回undefined
a.fun(1); // 返回 ?
a.fun(2); // 返回 ?
a.fun(3); // 返回 ?

// 第二个例子
var b = fun(0)
  .fun(1)
  .fun(2)
  .fun(3); //undefined,?,?,?

// 第三个例子
var c = fun(0).fun(1);
c.fun(2);
c.fun(3); //undefined,?,?,?

一、关于这个函数的执行过程

先大致说一下这个函数的执行过程:

① 初始化一个具名函数,具名函数就是有名字的函数,名字叫 fun。

② 第一个 fun 具名函数执行之后会返回一个对象字面量表达式,即返回一个新的object对象。

{  // 这是一个对象,这是对象字面量表达式创建对象的写法,例如{a:11,b:22}
  fun: function(m) { 
    return fun(m, n); 
  }
}

③ 返回的对象里面含有fun这个属性,并且这个属性里面存放的是一个新创建匿名函数表达式function(m) {}

④ 在③里面创建的匿名函数会返回一个叫 fun 的具名函数return fun(m, n);,这里需要说明一下这个 fun 函数返回之后的执行过程:

1. 返回 fun 函数,但默认不执行,因为在 js 里面,函数是可以保存在变量里面的。

2. 如果想要执行 fun 函数,那么首先会在当前作用域寻找叫fun 名字的具名函数,但是因为当前作用域里 fun 名字的函数是没有被定义的,所以会自动往上一级查找。
    2.1 注解:当前的作用域里是一个新创建的对象,并且对象里面只有 fun 属性,而没有 fun 具名函数
    2.2 注解:js 作用域链的问题,会导致他会不断地往上级链查找。

3. 在当前作用域没找到,所以一直往上层找,直到找到了顶层的 fun函数,然后执行这个顶层的 fun 函数。

4. 然后这两个 fun 函数会形成闭包,第二个 fun 函数会不断引用第一个 fun 函数,从而导致一些局部变量例如 n,o 得以保存。

所谓闭包:各种解释都有,但都不是很接地气,简单的来说就是在 js 里面,有一些变量(内存)可以被不断的引用,导致了变量(内存)没有被释放和回收,从而形成了一个独立的存在,这里涉及了js 的作用域链和 js 回收机制,结合两者来理解就可以了。

二、第一个例子的输出结果分析

1. var a = fun(0); // 返回 undefined

注解:

  • 因为最外层的fun 函数fun(n, o)是有2个参数的,如果第二个参数没有传,那么默认就会被转换为 undefined,所以执行之后输出 undefined,因为 console.log 输出的是o console.log(o);
  • 然后最外层这个 fun 函数会返回一个新对象,对象里面有一个属性,名为 fun,而这个fun 属性的值是一个匿名函数,它会返回fun(m, n);
function fun(n, o) { // ① 
  console.log(o);  // 这里首先输出了  n 的值为undefined
  return { // ②  
    fun: function(m) { // ③ 
      return fun(m, n); // ④  
    }
  };
}

2. a.fun(1); // 返回 0

注解:

  • 由于之前运行了var a = fun(0);,返回了一个对象,并且赋值给了变量a,所以 a 是可以访问对象里面的属性的,例如a.fun

  • a.fun(1);这里意思是:

    • 访问 a 对象的 fun 属性,因为a 的 fun 属性的值保存的是一个匿名函数③,所以要使用的话需要加上()
    • a.fun() 实际上调用的是 fun 属性里面的匿名函数,由于匿名函数返回的fun(m, n); 无法在当前作用域找到(因为当前作用域没有这个定义这个函数),所以会往上找,找到了顶层的函数fun(n, o),这样就会出现闭包的状态,顶层的fun 函数被内层的 fun 函数引用,之前①的fun(0)的0被保存下来了,作为 n 参数的值。
    • a.fun(1)这里传入了第一个参数1,所以就是 m=1,(因为③接收一个参数)。
    • 所以④的fun(m,n)就会是fun(1,0),所以输出0
    // 已经执行过一次var a = fun(0)
    
    function fun(n, o) { // ① 
      console.log(o);
      return { // ② 
        fun: function(m) { // ③ m=1
          return fun(m, n); // ④ 不断引用①,闭包生成,①的n 的值被保存为0
        }
      };
    }
    

3. a.fun(2); // 返回 0

注解:

  • 这里传入一个参数,参数的值为2,跟上面的a.fun(1);是一样的流程执行。
  • 最终是fun(2,0)执行,那么输出 o 就是0了
function fun(n, o) { // ① 
  console.log(o);
  return { // ② 
    fun: function(m) { // ③ 
      return fun(m, n); // ④ 
    }
  };
}

4. a.fun(3); // 返回 0

跟上面雷同,所以不做解释了。

二、第二个例子的输出结果分析

第二个例子其实是一个语句,只是进行了链式调用,所以会有一些不一样的处理。

1. 第一个返回 undefined

var b = fun(0) // 返回 undefined

注解:

  • 第一个返回 undefined 毋容置疑了,所以不说。

2. 第二个返回 0

 fun(0).fun(1) // 返回 0

注解:

  • 执行fun(0)的时候返回了一个对象,对象里面有 fun 属性,而这个 fun 属性的值是一个匿名函数,这个匿名函数会返回一个 fun 函数。
  • 当执行完fun(0)后,再链式直接执行.fun(1)的时候,它是会调用前者返回的对象里的 fun 属性,并且传入了1作为第一个参数,即m=1,并且返回的 fun 函数跟前者形成闭包,会不断引用前者,所以 n=0 也被保存下来了。
  • 所以最终执行的时候是fun(m, n)fun(1,0),所以返回0

3. 第三个返回1

fun(0).fun(1).fun(2)

注解:

  • 执行fun(0)的时候返回了一个对象,对象里面有 fun 属性,而这个 fun 属性的值是一个匿名函数,这个匿名函数会返回一个 fun 函数。
  • 当执行完fun(0)后,再链式直接执行.fun(1)的时候,它是会调用前者返回的对象里的 fun 属性,并且传入了1作为第一个参数,即m=1,并且返回的 fun 函数跟前者形成闭包,会不断引用前者,所以 n=0 也被保存下来了。
  • 当再次链式直接执行.fun(2)的时候,这里使用的闭包是.fun(1)返回的闭包,因为每次执行 fun 函数都会返回一个新对象,而.fun(2)引用的是.fun(1),所以 n 的值被保留为1
    • .fun(2)返回的是fun(m, n),而这里会跟.fun(1)(即fun(1, o))形成闭包,所以1为 n 的值被保留。
    • 需要注意的是,js 作用域链只要找到可以使用的,就会马上停止向上搜索,所以.fun(2)找到.fun(1)就马上停止向上搜索了,所以引用的是.fun(1)的值。

4. 第四个返回是2

跟第三个返回类似,所以不做解释了。

第三个例子的输出结果分析

// 这里已经无需多说了,跟第二个例子类似。
var c = fun(0).fun(1); // 返回 undefined 和0

1. 第三个返回是1,第四个返回是1

c.fun(2); // 第三个返回 1
c.fun(3); // 第四个返回 1

注解:

  • 基于第一个返回和第二个返回,n 已经被赋值为1了。

  • 然后这里虽然多次执行了 fun 函数,但是因为没有再次形成闭包,n 的值没有再次被改变,所以一直保持着1.


为了避免原文被吃掉,所以我这里保留了截图,并且加了一篇解释 js 闭包还不错的文章作为参考使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 作者:小小沧海原文地址:http://www.cnblogs.com/xxcanghai/p/4991870.ht...
    IT程序狮阅读 2,886评论 5 75
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,325评论 0 2
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,118评论 0 13
  • 什么是闭包 「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。 有些人说闭包就是函数套函数,然...
    落花的季节阅读 202评论 0 1
  • 2017年9月24日 星期日 天气晴 八虚部位拍痧之三拍两髀。 拍两髀(大腿内侧与小腹交接处的腹股沟部位)治疗一切...
    饶爱兰阅读 5,264评论 1 3