简介
闭包是「函数」和「函数内部能访问到的变量(即作用域)」的总和,更通俗来说,闭包也可以理解为有权访问另一个函数作用域中的变量的函数。一般来说会是函数内嵌套函数,然后 return 函数的呀
js 之所以会有闭包,是因为 js 不同于其他规范的语言,js 允许一个函数中再嵌套子函数,正是因为这种允许函数嵌套,导致 js 出现了所谓闭包
function a(){
function b(){
};
b();
}
a();
在 js 正常的函数嵌套中,父函数 a 调用时,嵌套的子函数 b 的结构在内存中产生,然后子函数又接着调用了,子函数 b 作用域中的数据就注销了,此时父函数 a 也就执行到尾,父函数 a 也会把自己函数体内调用时生成的数据从内存都注销
function a(){
function b(){
//
}
return b;
}
var fun=a();
而在这个例子中,父函数 a 调用时,函数体内创建了子函数 b,但是子函数 b 并没有立即调用,而是返回了函数指针,以备“日后再调用”,正是因为“准备日后调用”,此时父函数 a 执行完了,就不敢注销自己的作用域中的数据了,因为一旦注销了,子函数b日后再调用时,沿着函数作用域链往上访问数据时,就没有数据可以访问了,这就违背了 js 函数作用域链的机制。这就导致父函数要维持自己的作用域链,而不敢注销自己的作用域,那么这个子函数就是“闭包函数”
闭包并不是 js 为了创造而创造的,完全只是因为 js 允许函数嵌套,js函数嵌套还有个函数作用域链的机制,让父函数不敢注销自己作用域中的数据,才产生了所谓的闭包
闭包的用途
1、希望一个变量长期驻扎在内存中,实现缓存
一般函数执行完毕后,局部变量就会被销毁,内存中仅仅保存全局作用域。但闭包的情况不同!
不带缓存的阶乘的计算函数:
function factorial(num){
var result = 1;
for(var i = num; i>0; i--){
result *= i;
}
return result;
}
console.log(factorial(6))//控制台输出:720
console.log(factorial(6))//控制台输出:720
每次运行都要计算一次,可以使用缓存策略进行优化:
function factorial(num){
var arr = [];
return function(num){
if(num in arr){
console.log("这次数据来自缓存:")
return arr[num];
}else{
var result = 1;
for(var i = num; i>0; i--){
result *= i;
}
arr[num] = result;//将结果存入缓存
return result;
}
}
}
var func = factorial();
console.log(func(6));
console.log(func(6));
控制台输出:
720
这次数据来自缓存:
720
2、模块化代码,减少全局变量的污染
如果我们把一些只用到一两次的变量放在全局作用域中,最后肯定是容易出错且不可维护的,如下方例子:
var count = 0;
function getCount(){
count++;
console.log(count);
}
getCount();//控制台输出:1
getCount();//控制台输出:2
通过闭包,我们可以实现在函数外部访问一个函数内的局部变量,从而避免了对全局作用域的污染
function getCount(){
var count = 0;
return function(){
count++;
console.log(count);
}
}
var a = getCount();
a();//控制台输出:1
a();//控制台输出:2
3、实现封装
js 没有 private 之类关键字,如果要实现 OOP 中的封装,可以利用闭包模拟出私有属性
var person = (function(){
var name = 'default';//变量作用域为函数内部,外部无法访问到
return{
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
})()
console.log(person.name)
console.log(person.getName())
person.setName('Leo')
console.log(person.getName())
控制台输出:
undefined
default
Leo