一道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 闭包还不错的文章作为参考运用。

    原文作者:线上猛如虎
    原文地址: https://segmentfault.com/a/1190000016062504
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞