闭包

闭包

闭包是什么?

一个函数和对其周围状态的引用捆绑在一起,或者说函数被引用包围,这样的组合就是闭包。闭包让我们可以在一个内层函数中访问到其外层函数的作用域。

「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。

举例:

1
2
3
4
5
6
7
8
9
10
11
function init() {
let name = "andy8421"; // name 是一个被 init 创建的局部变量
function displayName() {
alert(name);
//displayName使用了父函数中声明的变量name,二者共同构成一个闭包。
}
return displayName;
}

let myDetail = init();
myDetail();

注意,最后两句和init()();这种调用方法并不相同,后者并没有发挥闭包保存内部变量的作用,这不叫使用了闭包。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function a(){
let m=1;
let n=2;
function c(){
console.log(`m:${m}`);
console.log(`n:${n}`);
m++;
n++;
}
return c;
};

a()(); //m:1, n:2
a()(); //m:1, n:2
a()(); //m:1, n:2

let f = a();
f(); //m:1, n:2
f(); //m:2, n:3
f(); //m:3, n:4

闭包的作用是什么?

  1. 访问块级作用域、封装私有变量、模拟私有方法

举例:

1
2
3
4
5
6
for(let i=0;i<5;i++){
setTimeout(()=>{
console.log(i);
},0);
}
//控制台打印0,1,2,3,4

当计时器(私有方法)开始执行时,for循环已经结束,每一次的i都是该次所在的块(块级作用域)中的i,所以打印的是当时的i(私有变量)值。

以上代码相当于:

1
2
3
4
5
6
7
8
for(var i=0;i<5;i++){
let j=i
//const j=i
setTimeout(()=>{
console.log(j);
},0);
}
//控制台打印0,1,2,3,4

此处详细解释见:JS函数的执行时机

  1. 暴露操作函数而隐藏局部变量

有时,直接使用全局变量很不妥,会出先某些安全问题。我们不能直接访问这些变量,此时就要用到闭包来隐藏局部变量,而仅暴露一个访问器(函数)来间接访问这些变量。

​ 详见:JS 中的闭包是什么?

闭包中的this对象

问题引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
var color = 'red';
// window.color = 'red';

let obj = {
color: 'blue',
sayColor() {
return function color(){
return this.color;
}
}
};

obj.sayColor()(); // 'red'

为什么obj.sayColor()();返回的是window.color的值red,而不是obj的blue?

以上代码中,首先创建了一个全局变量color,之后又创建了包含color属性的对象obj。obj对象还包含一个名为sayColor的函数,而这个函数本身又返回的是一个函数color()。

由于函数内部的代码在访问变量时,会沿作用域链查找变量。当函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。因此内部函数永远不可能直接访问外部函数的this和arguments变量

当执行obj.sayColor()后,在函数color()所处的执行上下文中,局部活动对象obj已经被销毁。此时color()只能访问全局作用域中的color变量。

以上代码中,全局变量color和函数color()构成闭包。

要想使obj.sayColor()();返回obj的blue,则应该让obj.color和函数color()构成闭包。

解决方法:

将this保存到闭包可以访问的另一个变量that中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var color = 'red';
// window.color = 'red';

let obj = {
color: 'blue',
sayColor() {
let that = this;
return function color(){
return that.color;
}
}
};

obj.sayColor()(); // 'blue'

尽管当执行obj.Color()后,其执行上下文的作用域已经被销毁,但函数color()仍然引用了外部函数sayColor的变量(即this),所以他的活动对象obj仍然保存在内存中,进而obj.color仍然可以访问到。

obj是在color()最终执行完毕才被销毁的。

闭包的缺点是什么?

  1. 因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存。过度使用闭
    包可能导致内存过度占用 。
  2. 闭包会在父函数外部改变父函数内部变量的值

版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!