function fun(n, o) { // ①
console.log(o);
return { // ②
fun: function(m) { // ③
return fun(m, n); // ④
}
}
}
// 第一个例子
var a = fun(0);// 返回undefined
a.fun(1); // 返回 0
a.fun(2); // 返回 0
a.fun(3); // 返回 0
// 第二个例子
var b = fun(0).fun(1).fun(2).fun(3); // undefined 0 1 2
// 第三个例子
var c = fun(0).fun(1); // undefined
c.fun(2); // 1
c.fun(3); // 1
# 关于这个函数的执行过程
先大致说一下这个函数的执行过程
①初始化一个具名函数,具名函数就是有名字的函数,名字叫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 得以保存。
TIP
所谓闭包:各种解释都有,但都不是很接地气,简单的来说就是在 js 里面,有一些变量(内存)可以被不断的引用,导致了变量(内存)没有被释放和回收,从而形成了一个独立的存在,这里涉及了js 的作用域链和 js 回收机制,结合两者来理解就可以了。
# 第一个例子的输出结果
var a = fun(0); // 返回undefined
- 因为最外层的fun函数fun(n, o)是有2个参数的,如果第二个参数没有穿,那么默认就会转换为undefined,所以执行之后输出undefined,因为console.log输出的是o
- 然后最外层这个fun函数会返回一个新对象,对象里面有一个属性,名为fun,而这个fun属性的值是一个匿名函数,它会返回fun(m, n)
function(n, o) { // ① console.log(o); //这里首先输出了,n的值为undefined return { // ② fun: function(m) { // ③ return fun(m, n); // ④ } } }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(2); // 返回 0
- 这里传入一个参数,参数的值为2,跟上面的a.fun(1);是一样的流程执行。
- 最终是fun(2,0)执行,那么输出 o 就是0了
a.fun(3); // 返回0
# 第二个例子的输出结果分析
第二个例子其实是一个语句,只是进行了链式调用,所以会有不一样的处理
第一个返回undefined
var b = fun(0); // 返回undefined第二个返回0
fun(0).fun(1)- 执行fun(0)的时候返回了一个对象,对象里面有fun属性,而这个fun属性的值是一个匿名函数,这个匿名函数会返回一个fun函数
- 当执行完fun(0)后,在链式直接执行.fun(1)的时候,它是会调用前者返回的对象里的fun属性,并且传入了1作为第一个参数,即m = 1,并且返回的fun函数跟前者形式闭包,会不断引用前者,所以n = 0也被保存下来了
- 所以最终执行的时候是fun(m, n)即fun(1, 0),所以返回0
第三个返回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)的值。
第四个返回的是2 跟第三个返回类似,所以不用解释了
# 第三个例子的输出结果分析
// 这里已经无需多说了,跟第二个例子类似
var c = fun(0).fun(1); // 返回undefined 和 0
# 第三个返回是1,第四个返回是1
c.fun(2); // 第三个返回1
c.fun(3); // 第四个返回1
基于第一个返回和第二个返回,n 已经被赋值为1了。
然后这里虽然多次执行了 fun 函数,但是因为没有再次形成闭包,n 的值没有再次被改变,所以一直保持着1.