经典闭包面试题

Author Avatar
Klein 4月 08, 2018

这是一道非常经典的闭包面试题。

原题

1
2
3
4
5
6
7
8
9
10
11
12
13
function fun(n,o) {
console.log(o);
return {
fun: function(m) {
return fun(m,n);
}
};
}

var a = fun(0); a.fun(1); a.fun(2); a.fun(3); //undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3); //undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3); ////undefined,?,?,?
//问:三行a,b,c的输出分别是什么?

解析

知识点

具名函数和匿名函数

在JavaScript中函数可分为两种:具名函数和匿名函数。

可通过fun.name来判断,有name属性的就是具名函数,没有则是匿名函数。如下

1
2
3
console.log((function fun1() {console.log("it is a Named function");}).name)

console.log((function () {console.log("it is a Anonymous function");}).name)

不过在低版本的IE无法获取具名函数的name,会返回undefined。可以用获取指定函数名称的函数兼容IE:

1
2
3
4
5
6
7
8
9
10
11
12
/**
***获取指定函数名称(兼容IE)
**/
function getFunctionName(fun) {
if (fun.name !== undefined) {
return fun.name;
}
var ret = fun.toString();
ret = ret.substr('function'.length);
ret = ret.substr(0,ret.indexOf('('));
return ret;
}

函数的声明

JavaScript有三种声明函数的方法。

  1. function 命令

    1
    function fn1() {}
  2. 函数表达式
    将一个匿名函数赋值给变量。

    1
    var fn1 = function () {}

此时,这个匿名函数又称函数表达式,因为赋值语句的等号右侧只能放表达式。

1
2
// var fn1 = function () {console.log("it is a Anonymous function")}
// getFunctionName(fn1).length; //0

采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

1
2
3
4
5
6
7
8
9
var print = function x(){
console.log(typeof x);
};

x
// ReferenceError: x is not defined

print()
// function

这样写有两个用处:

  • 在函数体内部调用自身
  • 方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。

所以常有这种写法:

1
var fn = function fn() {};

注意:函数表达式需要在语句的结尾加上分号,表示语句结束。而函数声明的结尾大括号后面不用加分号。

  1. Function构造函数
1
2
3
4
5
6
7
8
9
10
var add = new Function(
'x',
'y',
'return x + y'
);

// 等同于
function add(x, y) {
return x + y;
}

你可以传递任意数量的参数给Function构造函数,除了最后一个参数会被当做函数体,其他参数都会作为新建函数的参数。

如果只有一个参数,那这个参数就是函数体。

1
2
3
4
5
6
7
8
var foo = new Function(
'return "hello world"'
);

// 等同于
function foo() {
return 'hello world';
}

题目解析

题目有三个fun函数。

第一个fun函数,是用function命令创建的具名函数,返回一个对象,这个对象内部包含一个也叫fun的属性,存放着一个匿名函数表达式。所以第一个fun函数与第二个fun函数不同。

在说第三个fun函数前需要先补充一下知识点:在函数表达式内部能否访问存放当前函数的变量。

对象内部的函数表达式内部不能访问存放当前函数的变量。

1
2
3
4
5
6
var o={
fn:function (){
console.log(fn);
}
};
o.fn();//ERROR报错

非对象内部的函数表达式可以访问存放当前函数的变量

1
2
3
4
var fn=function (){
console.log(fn);
};
fn();//function (){console.log(fn);};正确

为什么会这样呢?
是因为JavaScript特有的”链式作用域”。
在非对象的例子中,var在外部创建了一个fn全局变量。当在函数内部作用域找不到fn时,向上寻找;而在对象内部的例子中,var在全局创建对象时,fn包含在对象o中,所以fn变量既不在函数作用域中也不在全局作用域中,所以找不到。

所以综上可得:最里面的returnfun函数不是第二个fun函数,而是最外层的fun函数,即第一个等于第三个,且都不等于第二个。

第一行a

1
var a = fun(0); a.fun(1); a.fun(2); a.fun(3); //undefined,?,?,?
  1. var a = fun(0);
    很显然,是在执行第一个fun函数:此时参数n=0,o=undefined,所以输出undefined,然后return给变量a一个对象,这个对象里有一个方法fun,这里的fun方法返回第一个fun函数,即fun(m,0)

  2. 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)即调用变量cfun方法returnfun函数,即fun(2,1),所以输出1

c.fun(3)即调用变量cfun方法returnfun函数,即fun(3,1),所以输出1

最终答案:undefined,0,1,1

参考

大部分人都会做错的经典JS闭包面试题
函数 – JavaScript 标准参考教程(alpha)