经典闭包面试题
这是一道非常经典的闭包面试题。
原题
1 | function fun(n,o) { |
解析
知识点
具名函数和匿名函数
在JavaScript中函数可分为两种:具名函数和匿名函数。
可通过fun.name
来判断,有name
属性的就是具名函数,没有则是匿名函数。如下
1 | console.log((function fun1() {console.log("it is a Named function");}).name) |
不过在低版本的IE无法获取具名函数的name
,会返回undefined
。可以用获取指定函数名称的函数兼容IE:
1 | /** |
函数的声明
JavaScript有三种声明函数的方法。
function 命令
1
function fn1() {}
函数表达式
将一个匿名函数赋值给变量。1
var fn1 = function () {}
此时,这个匿名函数又称函数表达式,因为赋值语句的等号右侧只能放表达式。
1 | // var fn1 = function () {console.log("it is a Anonymous function")} |
采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
1 | var print = function x(){ |
这样写有两个用处:
- 在函数体内部调用自身
- 方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。
所以常有这种写法:1
var fn = function fn() {};
注意:函数表达式需要在语句的结尾加上分号,表示语句结束。而函数声明的结尾大括号后面不用加分号。
- Function构造函数
1 | var add = new Function( |
你可以传递任意数量的参数给Function
构造函数,除了最后一个参数会被当做函数体,其他参数都会作为新建函数的参数。
如果只有一个参数,那这个参数就是函数体。1
2
3
4
5
6
7
8var foo = new Function(
'return "hello world"'
);
// 等同于
function foo() {
return 'hello world';
}
题目解析
题目有三个fun
函数。
第一个fun
函数,是用function
命令创建的具名函数,返回一个对象,这个对象内部包含一个也叫fun
的属性,存放着一个匿名函数表达式。所以第一个fun
函数与第二个fun
函数不同。
在说第三个fun
函数前需要先补充一下知识点:在函数表达式内部能否访问存放当前函数的变量。
对象内部的函数表达式内部不能访问存放当前函数的变量。1
2
3
4
5
6var o={
fn:function (){
console.log(fn);
}
};
o.fn();//ERROR报错
非对象内部的函数表达式可以访问存放当前函数的变量1
2
3
4var fn=function (){
console.log(fn);
};
fn();//function (){console.log(fn);};正确
为什么会这样呢?
是因为JavaScript
特有的”链式作用域”。
在非对象的例子中,var
在外部创建了一个fn
全局变量。当在函数内部作用域找不到fn
时,向上寻找;而在对象内部的例子中,var
在全局创建对象时,fn
包含在对象o
中,所以fn
变量既不在函数作用域中也不在全局作用域中,所以找不到。
所以综上可得:最里面的return
的fun
函数不是第二个fun
函数,而是最外层的fun
函数,即第一个等于第三个,且都不等于第二个。
第一行a
1 | var a = fun(0); a.fun(1); a.fun(2); a.fun(3); //undefined,?,?,? |
var a = fun(0);
很显然,是在执行第一个fun
函数:此时参数n=0,o=undefined
,所以输出undefined
,然后return
给变量a
一个对象,这个对象里有一个方法fun
,这里的fun
方法返回第一个fun
函数,即fun(m,0)
。a.fun(1);
执行a对象里的fun
方法,即函数fun(1,0)
,此时参数1
2
3
4
5
6
7
8
9
10
11
12
13
3. a.fun(2);
执行a对象里的``fun``方法,即函数``fun(2,0)``,此时参数```n=2,o=0``,所以输出``0``。
4. a.fun(3);
执行a对象里的``fun``方法,即函数``fun(3,0)``,此时参数```n=3,o=0``,所以输出``0``。
最终答案:``undefined,0,0,0``
#### 第二行b
``` JavaScript
var b = fun(0).fun(1).fun(2).fun(3); //undefined,?,?,?
fun(0)
即第一个fun
函数:此时参数n=0,o=undefined
,所以输出undefined
,然后return
一个对象,这个对象里有一个方法fun
,这里的fun
方法返回第一个fun
函数,即fun(m,0)
;
fun(1)
即调用上一个fun(0)
返回的对象的fun
方法,即第二个fun
,又因为这个fun
方法返回第一个fun
函数,即fun(1,0)
,所以输出0
,返回一个对象,对象里有一个方法fun
,这里的fun
方法返回第一个fun
函数,即fun(m,1)
;
fun(2)
即调用上一个fun(1)
返回的对象的fun
方法,即第二个fun
,又因为这个fun
方法返回第一个fun
函数,即fun(2,1)
,所以输出1
,返回一个对象,对象里有一个方法fun
,这里的fun
方法返回第一个fun
函数,即fun(m,2)
;
fun(3)
即调用上一个fun(2)
返回的对象的fun
方法,即第二个fun
,又因为这个fun
方法返回第一个fun
函数,即fun(3,2)
,所以输出2
。
最终答案:undefined,0,1,2
第三行c
1 | var c = fun(0).fun(1); c.fun(2); c.fun(3); ////undefined,?,?,? |
fun(0)
即第一个fun
函数:此时参数n=0,o=undefined
,所以输出undefined
,然后return
一个对象,这个对象里有一个方法fun
,这里的fun
方法返回第一个fun
函数,即fun(m,0)
;
fun(1)
即调用上一个fun(0)
返回的对象的fun
方法,即第二个fun
,又因为这个fun
方法返回第一个fun
函数,即fun(1,0)
,所以输出0
,然后return
给变量c
一个对象,这个对象里有一个方法fun
,这里的fun
方法返回第一个fun
函数,即fun(m,1)
;
c.fun(2)
即调用变量c
的fun
方法return
的fun
函数,即fun(2,1)
,所以输出1
。
c.fun(3)
即调用变量c
的fun
方法return
的fun
函数,即fun(3,1)
,所以输出1
。
最终答案:undefined,0,1,1