闭包

闭包

函数式编程的世界中,闭包的作用等价于面向对象编程中的类!

  • 闭包可以认为是携带状态的函数,它能让其中的临时变量的生命周期得到无限的延长,因为闭包函数的执行周期是无限的!
  • 闭包每次返回的函数都是一个全新的函数实例对象,但是对于这些新的函数实例对象,外部函数的临时变量是唯一的,即是所有函数实例对象所共享的!
  • 闭包能够对临时变量进行封装,使其达到私有变量的效果,外部代码无法访问到(函数科里化)!

    外部函数变量的临时状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function count() {
// 保存三个匿名函数的数组
var arr = [];
for (var i=1; i<=3; i++) {
// 虽然在push时,i的值处于临时循环值状态,但匿名函数中并没有保存该状态
arr.push(function () {
return i * i;
});
}
return arr;
}

var results = count();
var f1 = results[0]; // 16
var f2 = results[1]; // 16
var f3 = results[2]; // 16

上面调用三个匿名函数的结果都是16,而不是1,3, 9。原因在于闭包函数中并没有保存外部函数中变量i在循环时的临时状态,当循环结束后,arr才被返回,此时循环已经执行结束了,i的值为4。

保存外部函数的临时变量的临时状态,通过参数传递进来,保存进行闭包函数中!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
// 将外部函数变量的临时状态通过参数传递给闭包函数进行保存
// 此处多创建了一层闭包函数,并立即调用
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

科里化:函数封装私有变量

JavaScript中没有class机制,所以无法封装自己的private属性的成员变量。借助闭包函数的科里化,可以实现在外部函数中对临时变量进行隐藏!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict';

function create_counter(initial) {
// x被封装进外部函数中,外部代码无法访问到x
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

科里化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';

function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

闭包函数每次调用都会创建一个新函数

1
2
3
4
5
6
7
8
9
10
11
12
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false