闭包这个词其实很难准确的定义,个人在其他网站上看到关于它的定义挺挺不错的:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
在正常情况下,一个函数访问完后,其内部定义的局部变量就会被销毁,例如:
function fun() {
var a = 'hello';
b = 'world';
}
fun();
console.log(b);//输出:world
console.log(a);//输出:Uncaught ReferenceError: a is not defined
上面的例子中,变量a是一个局部变量,变量b是一个全局变量(非严格模式(不在第一行加'use strict')下,在函数内部不加var关键字定义变量时,自动声明为全局变量)。调用完fun函数后,b仍能输出fun中定义的值,a却不能,说明调用完fun函数后,变量a被销毁了,而变量b因为是全局变量,所以没有被销毁。
但是闭包不会出现上面那种情况,即使调用完毕,它内部定义的局部变量也不会被销毁,因为内部函数有外部函数变量的引用,例如:
function fun() {
var count = 0;
function son() {
count ++;
console.log(count);
}
return son;
}
var C = fun();
C();// 输出:1
C();// 输出:2
C();// 输出:3
上面的示例中,内部函数son引用了其外部函数的变量count,而外部函数直接将内部函数son返回了,这个时候内部函数son是没有执行的,正是因为还没有执行而又引用了外部函数的变量,导致外部函数中的变量无法销毁,从而一直存在内存中。
大多数情况下,闭包会与匿名函数结合在一起使用,例如:
(function (fn) {
fn();
})(function () {
var count = 0;
var _gobal = {
add : function () {
++count;
console.log(count);
}
}
window.gobal = _gobal;
});
gobal.add();//输出:1
gobal.add();//输出:2
gobal.add();//输出:3
上面的示例中,定义了一个匿名自执行函数,入参是一个匿名函数,即:
function () {
var count = 0;
var _gobal = {
add : function () {
++count;
console.log(count);
}
}
window.gobal = _gobal;
}
在匿名自执行函数中调用了该匿名函数,这里没有像之前那样返回一个函数,但注意这一句“window.gobal = _gobal;”,它在 window 全局对象定义了一个变量 gobal,并将这个变量指向 _gobal对象,即全局变量 gobal引用了 _gobal. 而 _gobal对象中的函数又引用了匿名函数中的变量 count,因此匿名函数中的变量 count 不会被 GC 回收,count 会一直保存到内存中,所以这种写法满足了闭包的条件。
其实很多js插件都采用了闭包与匿名函数结合在一起使用的方式,例如jquery-1.12.4.js,下面是我精简后的代码:
(function (global, factory) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
//省略代码片段
} else {
factory( global );
}
}(typeof window !== "undefined" ? window : this, function(window, noGlobal) {
//省略代码片段
var
version = "1.12.4",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
},
// Support: Android<4.1, IE<9
// Make sure we trim BOM and NBSP
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
// Matches dashed string for camelizing
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,
// Used by jQuery.camelCase as callback to replace()
fcamelCase = function( all, letter ) {
return letter.toUpperCase();
};
//省略代码片段
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}
}));
注意到没有?它就是采用的这种方式,我们平常使用的$就是jQuery通过这种方式在匿名函数中暴露出来给我们使用的。
使用这种方式的好处就是避免全局污染,没有这种方式时,如果我想使得一些变量不在调用完之后被销毁,我可能要全部定义成全局变量,而这些变量又很容易被外部修改,采用这种闭包的方式后,我就可以将这些变量定义在函数内部却不在调用完后被销毁。