闭包是JS中一个重要概念,很有用处,但不好理解。这里从what、why、how三个方面来总结闭包知识。
1 WHAT (闭包是什么?)
闭包:函数(A)中的函数(B),可以访问外部函数(A)内部的所有变量。
2 WHY (为什么闭包能访问函数变量?)
2.1 作用域链
要理解闭包原理,需要先了解作用域链的细节。
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
var result = compare(5,10);
上面代码,创建compare()时,会创建包含全局变量对象(含this、compare、result)的作用域链。
而第一次调用compare()时,会创建一个局部执行环境。再创建compare()活动对象(包含this、arguments、value1、value2),推入执行环境作用域链前端。故compare()活动对象处于作用域链第一位(0),而全局变量对象处于作用域链的第二位(1)。
通过作用域链,函数可以访问局部变量和全局变量。函数执行后,局部活动对象会被销毁,内存只保存全局变量对象。但闭包的情况又不一样了。
2.2闭包的作用域链
function createComparisonFunction(propertyName){
return function(object1, object2){ //闭包
var value1 = object1[propertyName];//访问外部函数的变量
var value2 = object2[propertyName];
if(value1 > value2){
return 1;
}else if(value1 < value2){
return -1;
}else{
return 0;
}
};
}
// 创建函数
var compareNames = createComparison("name");
// 调用函数
var result = compareNames({name:"Lillian"},{name:"Matthew"});
外部函数<code>createComparitionFunction()</code>的活动对象会添加到闭包的作用域链中,这样闭包就可以访问函数中的变量。问题是,<code>createComparitionFunction()</code>执行完毕后,闭包仍然引用着它的活动对象,所以无法销毁其活动对象。
只有闭包销毁,才能销毁函数的活动对象,释放内存:
// 解除对函数(闭包)的引用,释放内存
compareNames = null;
3 HOW (怎样使用闭包?)
闭包有很多用途,这里只列举最常用的两种。
3.1 实现私有作用域
JS没有块级作用域概念:
function outputNumbers(count){
for(var i = 0; i< count; i++){
alert(i);
}
alert(i); // 不会报错
}
由于没有块级作用域,for循环结束后,<code>i</code>并不会被销毁。所以<code>alert(i)</code>不会报错。
使用自调用函数可以模仿块级作用域:
(function(){
//这里是块级作用域
})()
其实自调用函数实现私有作用域,与闭包没有必然联系,只是自调用函数也可以用于函数内部(作为闭包):
function outputNumbers(count){
(function(){
for(var i = 0; i< count; i++){
alert(i);
}
})()
alert(i); // 报错,i没有定义
}
For循环放在自调用函数(此处是闭包)中,这样变量<code>i</code>只能在循环中被访问,在循环外部无法访问。
3.2 访问私有变量
在函数中定义的变量(参数、局部变量、内部函数),都不能在外部访问,所以是私有变量。而闭包可以访问函数中的变量,这就提供了访问私有变量的共有方法(特权方法)。
对于对象来说,有下面几种方式访问私有变量:
(1) 构造函数模式:
function MyObject() {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function() {
privateVariable++;
return privateFunction();
};
}
var obj1 = new MyObject();
console.log(obj1.publicMethod());
创建<code>MyObject</code>的实例<code>obj1</code> 后,只能用<code>publicMethod() </code>访问<code>privateVariable</code>和<code>privateFunction()</code>,没有其他方法可以直接访问私有变量和私有函数。
(2)原型模式:
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//构造函数
MyObject = function(){
};
/*函数声明只能创建局部函数,所以这里使用了函数表达式。
注意:变量MyObject没有加var,所以是全局变量。在私有作用域外部也能访问。*/
//特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
}
})();
原型模式与构造函数模式最主要的区别就是私有变量和私有函数由实例共享的。这样,变量就成了静态的、由所有实例共享的属性,即静态私有变量。
(3)模块模式:
模块模式用于为单例创建私有变量和特权方法。(单例:只有一个实例的对象。)
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//公有方法和属性
return {
publicProperty: true,
publicMethod: function(){
privateVariable++;
return privateFunction();
}
};
}();
该模式返回一个对象字面量,包含公有属性和方法。该对象是在匿名函数内部定义的,所以它的公有方法可以访问私有变量和函数。
(4)增强的模块模式:
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//创建对象,这里CustomType是一种实例类型,我们不需要理会它的具体代码
var object = new CustomType();
//公有属性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
}
//返回对象
return object;
}();
增强的模块模式,不是直接返回对象字面量,而是创建一个对象实例,增加属性和方法后返回。
这种模式,适合单例是某种类型实例的情况。上面的代码,object是CustomType的实例,匿名函数返回object对象,并赋值给singleton变量。所以,该单例singleton是CustomType的实例。
代码来源:
《JavaScript 高级程序设计》