资讯专栏INFORMATION COLUMN

一道js闭包面试题的学习

plus2047 / 3127人阅读

摘要:然后最外层这个函数会返回一个新对象,对象里面有一个属性,名为,而这个属性的值是一个匿名函数,它会返回。

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

闭包面试题原题
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 闭包还不错的文章作为参考使用。

大部分人都会做错的经典JS闭包面试题.pdf

JavaScript 的闭包原理与详解 - CSDN博客.pdf

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108345.html

相关文章

  • 一道面试题认识函数柯里化

    摘要:函数柯里化在函数式编程中,函数是一等公民。函数柯里化的主要作用和特点就是参数复用提前返回和延迟执行。可能在实际应用场景中,很少使用函数柯里化的解决方案,但是了解认识函数柯里化对自身的提升还是有帮助的。 最近在整理面试资源的时候,发现一道有意思的题目,所以就记录下来。 题目 如何实现 multi(2)(3)(4)=24? 首先来分析下这道题,实现一个 multi 函数并依次传入参数执行,...

    13651657101 评论0 收藏0
  • 一道面试题谈谈函数柯里化(Currying)

    摘要:忍者秘籍一书中,对于柯里化的定义如下在一个函数中首先填充几个参数然后再返回一个新函数的技术称为柯里化。回到我们的题目本身,其实根据测试用例我们可以发现,函数的要求就是接受单一函数,例如但是与柯里化不同之处在于,柯里化返回的一个新函数。   欢迎大家再一次来到我的文章专栏:从面试题中我们能学到什么,各位同行小伙伴是否已经开始了悠闲的春节假期呢?在这里提前祝大家鸡年大吉吧~哈哈,之前有人说...

    cppprimer 评论0 收藏0
  • 前端--通用知識 - 收藏集 - 掘金

    摘要:闭包有多重前端知识点大百科全书前端掘金,,技巧使你的更加专业前端掘金一个帮你提升技巧的收藏集。 Vue全家桶实现还原豆瓣电影wap版 - 掘金用vue全家桶仿写豆瓣电影wap版。 最近在公司项目中尝试使用vue,但奈何自己初学水平有限,上了vue没有上vuex,开发过程特别难受。 于是玩一玩本项目,算是对相关技术更加熟悉了。 原计划仿写完所有页面,碍于豆瓣的接口API有限,实现页面也有...

    王笑朝 评论0 收藏0
  • 一道面试题,到“我可能看了假源码[2]

    摘要:函数是这样子声明的使用了系统自己的构造函数来声明,第一个参数是,函数体内又。构造函数调用情况正常方式调用无穷无尽当然,里还归纳了几项比较简单,我就不再翻译了。 上一篇从一道面试题,到我可能看了假源码中,由浅入深介绍了关于一篇经典面试题的解法。最后在皆大欢喜的结尾中,突生变化,悬念又起。这一篇,就是为了解开这个悬念。 如果你还没有看过前传,可以参看前情回顾: 回顾1. 题目是模拟实现ES...

    chanthuang 评论0 收藏0
  • 一道面试题,到“我可能看了假源码”

    摘要:返回的绑定函数也能使用操作符创建对象这种行为就像把原函数当成构造器。同时,将第一个参数以外的其他参数,作为提供给原函数的预设参数,这也是基本的颗粒化基础。 今天想谈谈一道前端面试题,我做面试官的时候经常喜欢用它来考察面试者的基础是否扎实,以及逻辑、思维能力和临场表现,题目是:模拟实现ES5中原生bind函数。也许这道题目已经不再新鲜,部分读者也会有思路来解答。社区上关于原生bind的研...

    Carson 评论0 收藏0

发表评论

0条评论

plus2047

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<