从字面上来理解闭包就是封闭的包裹

可以理解为受到保护的区域

即: 函数内部定义的变量,函数外部无法访问

产生闭包的原因:js的词法作用域

有时候往往在一个作用域中需要访问另一作用域中的变量

即:跨链访问

闭包的作用的就是进行跨链访问

走一个demo:

1
2
3
function fn(){
var num = Math.random();
}

现在需要在函数外部获取num的值,该怎么做呢???

可以改一下函数:

1
2
3
4
5
6
function fn(){
var num = Math.random();
return num;
}
var a = fn();
console.log(a); // => 0.23493962781503797

那么如果在函数外部需要获取两次或多次num的值呢???

1
2
3
4
5
6
7
8
function fn(){
var num = Math.random();
return num;
}
var a = fn();
var b = fn();
console.log(a); // => 0.059904436115175486
console.log(b); // => 0.8948184980545193

很明显调用了两次fn获取了两个不一样的值

如果需要获取多次num的值并且需要值一样又该怎么做呢???

1
2
3
4
5
6
7
8
9
10
11
function fn(){
var num = Math.random();
return function(){
return num;
};
}
var f = fn();
var a = f();
var b = f();
console.log(a); // => 0.36699223099276423
console.log(b); // => 0.36699223099276423

实现原理:

通过调用fn函数,让其返回一个匿名函数

该匿名函数在fn函数内部,有权访问num

让匿名函数返回num

在每次需要获取num的值时只需要调用匿名函数

fn没有被再次调用即num的值不会改变

那么如果在fn函数中有两个值或多个值,该怎么获取???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function fn(){
var num1 = Math.random();
var num2 = Math.random();
return {
num1: function(){
return num1;
},
num2: function(){
return num2;
}
};
}
var f = fn();
var a1 = f.num1();
var a2 = f.num2();
var b1 = f.num1();
var b2 = f.num2();
console.log(a1); // => 0.859178748447448
console.log(b1); // => 0.859178748447448
console.log(a2); // => 0.18789607286453247
console.log(b2); // => 0.18789607286453247

那么如果既要可以获取又要可以修改fn中的值该怎么做呢???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fn(){
var num;
return {
getNum: function(){
return num;
},
setNum: function(n){
num = n;
}
};
}
var f = fn();
var get1 = f.getNum();
console.log(get1); // => undefined
f.setNum(123);
var get2 = f.getNum();
console.log(get2); // => 123

通过返回一个对象,该对象包含不同的操作函数,来实现复杂的闭包

也可以直接返回匿名函数,通过匿名函数再返回一个对象,来实现闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fn(){
var num;
var o = {
oNum: num
};
num = o.oNum;
return function(){
return o;
};
}
var f = fn();
var get1 = f().oNum;
console.log(get1); // => undefined
f().oNum = 123;
var get2 = f().oNum;
console.log(get2); // => 123

这个实现闭包的方法是通过值类型和引用类型的特性来实现的

即:将num和对象ooNum的属性链接在一起

通过闭包可以解决什么呢???

利用闭包的特点可以实现数据缓存

接下来通过闭包来实现斐波那契数列(兔子数列):

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
33
34
35
// 0, 1, 2, 3, 4, 5, 6...n
// 1, 1, 2, 3, 5, 8, 13...

var count1 = 0;
var count2 = 0;
// 首先用递归思想实现
function fn(n){
count1 ++;
if(n < 0) return ;
if(n === 1 || n === 0) return 1;
return arguments.callee(n - 2) + arguments.callee(n - 1);
}
console.log(fn(5) + ',执行了' + count1 + '次'); // => 8,执行了15次

// 采用闭包来实现:
var foo = (function(){
var arr = []; // 用于缓存数据
return function(n){
count2 ++;
var res = arr[n];
if(n < 0) return ;
if(res){
return res;
}else{
if(n === 1 || n === 0){
res = 1;
}else{
res = arguments.callee(n - 2) + arguments.callee(n - 1);
}
arr[n] = res;
return res;
}
}
})();
console.log(foo(5) + ',执行了' + count2 + '次'); // => 8,执行了9次

通过闭包缓存数据的效率明显比递归高多了

extend:

再介绍一种通过函数名缓存数据,来实现斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fib(n){
var res = [];
res = fib[n];
if(res){
return res;
}else{
if(n === 1 || n === 0){
res = 1;
}else{
res = arguments.callee(n - 2) + arguments.callee(n - 1);
}
fib[n] = res;
return res;
}
}
console.log(fib(5)); // => 8

summarize

  • 实现闭包可以通过返回一个函数或是一个对象(对象中包含功能函数)来实现
  • 闭包解决了跨链访问问题
  • 闭包可以实现数据缓存
  • 闭包实现了数据的私有化
文章目录
  1. 1. summarize