##0 前置知识:
基于js的函数作用域,局部变量的特性,函数外部是无法访问函数内部的变量的。
而有时我们恰恰有这样的需求,这就有了闭包的方法。
##1 闭包的概念:
闭包是指可以访问另一函数内部变量的函数
通常构造闭包的方式是在函数中定义一个函数,用做访问函数内部变量的**“钩子”,或者可以理解为开辟了外部访问函数内部变量的“绿色通道”**。
##2 闭包的原理分析:
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
遵循js作用域链的规则,上述代码中,f2可以访问f1的变量n。因此我们只需要将f2返回出来,就可以实现闭包。
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
###注意:
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
我们发现,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
另外,nAdd也是一个闭包,因此可以访问函数f1内的n,若是换成在函数外定义一个nAdd函数,浏览器会报错,无法访问到n。
result(); // 999
var nAdd = function() {
n += 1
}
nAdd();
##3 闭包的优缺点:
1.通过闭包获取到的变量会永久的保留在内存中,不会被回收,因此闭包要慎用,否则会给浏览器带来较大的负担,影响性能。严重的会导致内存泄漏,解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 通过闭包访问的,函数内部的变量不再隐私,可以在函数外对其进行修改,因此在对闭包获取的变量进行操作时,不要随便更改其值。
##4 闭包的常见使用方法:
1.通过将函数return
出去,使其可在外部访问。
function foo(){
var a = 2;
function bar(){
console.log(a); //2
}
return bar;
}
foo()();
2 通过将其作为想要执行的函数的参数。
//在foo()函数的作用域中声明,在bar()函数的作用域中被调用的baz()函数是闭包
function foo(){
var a = 2;
function baz(){
console.log(a); //2
}
bar(baz);
}
function bar(fn){
fn();
}
foo() // 2
3 将内部函数赋值给一个外部变量
var inner;
var F = function(){
var b = 'local';
var N = function(){
return b;
};
inner = N;
};
F();
console.log(inner());
##5 通过闭包嵌套解决常见的for循环i问题
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(){
return i;
};
}
return result;
}
console.log(createFunctions());
以上代码,for循环执行中,每次匿名函数调用完后,执行环境的作用域链被销毁,但是活动对象仍然保留。
因此,createFunction()
函数会返回一个函数数组,每个元素都是一个函数指针,在调用执行时找i,此时i均为i=10。
通过闭包解决上述问题:
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){ //i将作为参数传进来,保存在作用域内,因此每个num都在不同的作用域中,通过闭包访问的值就是正常我们需要的。
return function (){
return num
};
}(i);
//或者采用自执行函数解决
//(function (){
// result[i] = i
//})()
}
return result;
}
console.log(createFunctions());
##6 闭包实现私有变量封装
借助闭包,同样可以封装一个类,保留其私有变量。
在使用时不用担心交叉污染的问题!
function create_counter(initial) {
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
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
参考文章:
闭包,阮一峰
JavaScript闭包