JS 中的【闭包】
从字面上来理解闭包就是封闭的包裹
可以理解为受到保护的区域
即: 函数内部定义的变量,函数外部无法访问
产生闭包的原因:js的词法作用域
有时候往往在一个作用域中需要访问另一作用域中的变量
即:跨链访问
闭包的作用的就是进行跨链访问
走一个demo:1
2
3function fn(){
var num = Math.random();
}
现在需要在函数外部获取num
的值,该怎么做呢???
可以改一下函数:1
2
3
4
5
6function fn(){
var num = Math.random();
return num;
}
var a = fn();
console.log(a); // => 0.23493962781503797
那么如果在函数外部需要获取两次或多次num
的值呢???1
2
3
4
5
6
7
8function 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
11function 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
21function 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
17function 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
16function 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
和对象o
的oNum
的属性链接在一起
通过闭包可以解决什么呢???
利用闭包的特点可以实现数据缓存
接下来通过闭包来实现斐波那契数列(兔子数列):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
16function 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
- 实现闭包可以通过返回一个函数或是一个对象(对象中包含功能函数)来实现
- 闭包解决了跨链访问问题
- 闭包可以实现数据缓存
- 闭包实现了数据的私有化