一、什么是闭包
关于闭包的解释有很多,而我个人理解的闭包,就是引用了局部作用域变量的函数。他由以下两部分构成:
- 可以被外部访问到的函数
- 被函数引用的局部变量
下面的例子是闭包的典型例子:
function a() {
var value = 1;
function fun() {
console.log(value); // 引用了局部变量
}
return fun; // return了该函数,使该函数可以被外部访问
}
a()();
很多人错误的以为闭包一定要return一个函数才可以,但并不是这样的。判断标准应该是函数是否可被外部访问,函数是否保持了内部局部变量的引用。如下面例子返回的对象也构成了闭包。
var car = (function() {
var speed = 50; // 被引用的局部变量
function getSpeed() {
return speed;
}
function setSpeed(value) {
speed = value;
}
return {
getSpeed: getSpeed, // 可被外部访问到的函数
setSpeed: setSpeed
}
})();
car.getSpeed();
即使不return,只要函数能被外部访问到还是闭包,如下:
(function(){
var speed = 50; // 被引用的局部变量
window.getSpeed = function() { // 可被外部访问的函数
return speed;
}
})()
window.getSpeed();
二、闭包的作用
js中,由于作用域链的存在,使得内部作用域可以访问外部变量。但是,外部作用域是无法访问内部作用域里的变量的。那如果想要让外部访问内部的变量,就只有通过内部提供一个开放函数,然后该函数再去访问内部变量。这样就形成了闭包。所以闭包的作用就是使外部作用域能够访问内部变量。这是我认为闭包最直接的作用,其他都是通过这点延伸出来的。看看下面的例子:
var func = [];
for (var i=0;i<4;i++) {
func[i] = function() {
return i;
}
}
console.log( func[2]() ); // 4
这个例子最后输出的结果是4。为什么呢?
- 代码从上往下执行,当for括号中i变成了4时,不满足i<4条件,for执行完毕此时i为4。
- 此时执行
console.log(func[2]())
,函数会返回i。 - 这个func[2]函数内部本身没有i,就会向上级的作用域里面去找i,此时上级作用域里面有i为4则返回。
但其实本意我们是想让他输出对应的i即2,应该如何实现呢?
- 首先,为了输出对应的i,所以我们并不能使用for循环里面的i,而要单独创建一个i传到对应的函数里。而为了创建一个单独的作用域使i不被污染,就要使用到立即执行函数表达式。
- 使用了立即执行函数表达式将i封存在函数内部之后,则不再随外部改变而改变,但外部也访问不到这个i了。为了使外部作用域能够访问内部变量,此时就要使用闭包,返回一个引用局部变量i的函数。
根据上面的分析,可将代码改写如下:
var func = [];
for (var i=0;i<4;i++) {
func[i] = (function(j) { // 立即执行函数表达式传i进来,对于该函数内的j就是传进来当时的i,不会受新的i影响
return function() {
return j;
};
})(i);
}
console.log( func[2]() ); // 2
// 或者如下,原理一样,都是利用 立即执行函数表达式的独立作用域 和 闭包的通过返回函数使内部变量可访问
for (var i=0;i<4;i++) {
(function(j) { // j换成i没影响,只是为了方便区分所以写j
func[j] = function() {
return j;
}
})(i);
}
console.log( func[2]() ); // 2
闭包有啥用?什么情况下需要用闭包?通过上面的例子和分析,相信这两个问题你已经有了答案。当你需要让外部作用域访问内部的变量时,就是你使用闭包的时候了。也有人说闭包的作用是隐藏一个变量,使该变量私有化只能通过内部函数来访问。其实我个人觉得并不是,真正起到隐藏变量作用的其实是那个函数而不是闭包。
三、闭包的注意点
当我们在使用闭包的时候有一点要注意:
- 一般函数执行完毕之后,内部的变量已经没用就会被销毁,从而释放内存。但由于闭包中(即返回的函数)保持了对函数内部变量的引用,所以函数内部变量不会被销毁,会一直存在内存中,内存消耗大时会对网页性能产生一定的影响(ie还会造成内存泄漏,但这是ie的bug,所以不是对性能太过高要求也可以不考虑,只是不要滥用就行),若要优化,可在不需要该闭包时将其赋值为null,如上述代码
func[2] = null
。
四、总结
- 闭包就是引用了局部变量的函数。
- 闭包的作用就是使外部作用域能访问内部变量。
- 闭包中被引用的变量会一直存在内存中,除非人为销毁。
五、一道有意思的面试题
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?