闭包

所谓闭包,就是嵌套函数。

内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。

但是,外部函数却不能够访问定义在内部函数中的变量和函数。

当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。

嵌套函数和闭包

调用外部函数并为外部函数和内部函数指定参数:

1
2
3
4
5
6
7
8
9
10
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8

多层嵌套函数:

1
2
3
4
5
6
7
8
9
10
11
// 这个类似的函数经常出现在面试题中! 主要考察的是闭包、作用域
function A(x) {
function B(y) {
function C(z) {
console.log(x + y + z);
}
C(3);
}
B(2);
}
A(1); // logs 6 (1 + 2 + 3)

在这个例子里面,C可以访问B的y和A的x。这是因为:

  1. B形成了一个包含A的闭包,B可以访问A的参数和变量
  2. C形成了一个包含B的闭包
  3. B包含A,所以C也包含A,C可以访问B和A的参数和变量。换言之,C用这个顺序链接了B和A的作用域

反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。

命名冲突

当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。

1
2
3
4
5
6
7
8
9
10
// 这个页经常出现在面试题中! 主要考察的是闭包、作用域、调用方式的理解(我之前就搞不太懂这种调用方式)、命名冲突
function outside() {
var x = 5;
function inside(x) { // inside的x具有最高优先权
return x * 2;
}
return inside;
}

outside()(10); // 直接给 inside 传参 [返回 20]

复杂的闭包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var createPet = function(name) {
var sex;

return {
setName: function(newName) {
name = newName;
},

getName: function() {
return name;
},

getSex: function() {
return sex;
},

setSex: function(newSex) {
if(typeof newSex == "string"
&& (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
sex = newSex;
}
}
}
}

var pet = createPet("Vivie");
pet.getName(); // Vivie

pet.setName("Oliver");
pet.setSex("male");
pet.getSex(); // male
pet.getName(); // Oliver

⚠️注意:如果一个闭包的函数用外部函数的变量名定义了同样的变量,那在外部函数域将再也无法指向该变量

更加深入的闭包解析 MDN 中阶