闭包

文章目录

闭包一直都理解的比较零散,而且在实际运中中并不是很熟练,在此记录一下,并且希望在以后能够更加深入。

红宝书上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数的关键在于以下两点:

  • 是一个函数
  • 能访问另外一个函数作用域的变量(即使外部函数已经返回)仍能访问外部变量。

闭包主要有 2 个应用场景:

  • 函数作为返回值
  • 函数作为参数传递
1
2
3
4
5
6
7
8
9
function F1() {
var a = 100;
return function() {
console.log(a);
};
}
var f1 = F1();
var a = 200;
f1(); //100

闭包

闭包的定义非常简单:函数 A 返回一个函数 B,并且函数 B 中使用函数 A 中的变量,函数 B 被称为闭包。

1
2
3
4
5
6
7
function A() {
let a = 1;
function B() {
console.log(a);
}
return B;
}

你是否会疑惑为什么函数 A 已经弹出调用栈了,为什么函数 B 还能引用函数 A 中的变量,因为函数 A 中的变量这时候是存储在堆上的。现在的 JS 引擎可以通过逃逸分析辨别出那些变量需要存储在堆上,哪些需要存储在栈上。

创建闭包最常见的方式就是在一个函数内部创建另一个函数。通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。

1
2
3
4
5
6
7
8
9
10
11
function test() {
for (var i = 0; i < 4; i++) {
console.log('i:' + i);
(function(e) {
setTimeout(function() {
console.log(e);
}, 0);
})(i);
}
}
test(); //i:0 => i:1 => i:2=> i:3=> 0 => 1=> 2 => 3

循环当中,匿名函数会立即执行,并且会将循环当前的 i 作为参数传入,将其作为当前匿名函数中的形参 e 的指向,即会保存对 i 的引用,它是不会被循环改变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function makeAdder(x) {
console.log('x:' + x);
return function(y) {
console.log('y:' + y);
return x + y;
};
}

var add5 = makeAdder(5);
//x:5 undefined
add5;
//f(y){ console.log('y:' + y) return x + y}
var add10 = makeAdder(10);

console.log(add5(2)); // y:2 => 7
console.log(add10(2)); // 12

// 释放对闭包的引用
add5 = null;
add10 = null;

add5 和 add10 都是闭包。他们共享相同的函数定义,但是保存了不同的环境。在 add5 中,x 为 5,在 add10 中,x 则为 10 ,最后通过 null 释放对闭包的引用。

在 javascript 中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收; 如果两个对象互相引用,而不再被第 3 者所引用,那么这两个互相引用的对象也会被回收。

闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test() {
var arr = [];
for (var i = 0; i < 10; i++) {
//作用域1
arr[i] = function() {
//作用域2 声明arr[]()
return i;
};
}
for (var a = 0; a < 10; a++) {
console.log(arr[a]());
}
}
test();
// 毫无疑问连续打印 10 个 10。因为在for循环中 a[i] 为一个函数声明
//1.执行完for之后,在for作用域中i的值为10

改动一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test() {
var arr = [];
//块1作用域
for (let i = 0; i < 10; i++) {
//块2作用域
arr[i] = function() {
//块3作用域
return i;
};
}
//块1作用域
for (var a = 0; a < 10; a++) {
console.log(arr[a]());
}
}
test(); // 连续打印 0 到 9

实现原因 :

  • 当用 var 的时候 函数 2 作用域中没有 i 就向函数作用域 1 中去找,而执行到 console.log(arr[a]()) 时 i 已经循环完毕,因此 i 全为 10。
  • 当使用 let 时,每次迭代 i 都被重新声明,即每层迭代会生成一个块作用域,并且变量 i 被定义为上一次结算的值。
  • var 是函数作用域,for 循环无论执行多少次,都是去最近的函数里面找,而不是块中找,所以只有一个 i,现在的 i 是 10。

闭包中的 this 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = 'The Window';

var obj = {
name: 'My Object',

getName: function() {
return function() {
return this.name;
};
}
};

console.log(obj.getName()()); // The Window

obj.getName()() 实际上是在全局作用域中调用了匿名函数,this 指向了 window。这里要理解函数名与函数功能(或者称函数值)是分割开的,不要认为函数在哪里,其内部的 this 就指向哪里。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

闭包的应用

应用闭包的主要场合是:设计私有的方法和变量。 闭包的作用:

  • 访问函数的内部变量
  • 让被引用的变量值始终保存在内存中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,0
c.fun(2); // 1
c.fun(3); // 1
1
2
3
4
5
6
7
8
9
10
11
12
function fn1() {
var a = 1;
return function() {
console.log(++a);
};
}

var fn2 = fn1();

fn2(); //输出2

fn2(); //输出3
分享到:
network