近来看到一条有意义的闭包面试题,然则看到原文的剖析,我本身以为有点含糊,所以本身重新做一下这条题目。
闭包面试题原题
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 输出的是oconsole.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 } }; }
- 接见 a 对象的 fun 属性,因为a 的 fun 属性的值保存的是一个匿名函数③,所以要运用的话须要加上
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 闭包还不错的文章作为参考运用。