学习闭包前,很有必要了解作用域链相关知识。我们先来看看作用域链相关知识。
作用域链
- [[scope]]:每个js函数都是一个对象,对象中有些属性可以访问,但有些不可以,这些不可访问的的属性仅供js引擎存取。
- [[scope]]指的是我们所说的作用域,其中存储了运行期间的上下文集合。
- 作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
function a(){
function b(){
function c(){
}
c();
}
b();
}
a();
模拟作用域链:
a defined a.[[scope]] --->0:GO
a doing a.[[scope]] --->0 :aAO
1 :GO
b defined b.[[scope]] --->0 :aAO
1 :GO
b doing b.[[scope]] --->0 : bAO
1 : aAO
c defined c.[[scope]] --->0 : bAO
1 : aAO
2 : GO
c doing c .[[scope]] --->0 : cAO
1 : bAO
2 : aAO
3 : GO
function a(){
function b(){
var b = 234;
a = 0;
}
var a = 123;
b();
console.log(a);//0说明a和b共用a的AO
}
var glob = 100;
a();
闭包
- 闭包:仅有权访问另一个函数作用域中的变量的函数。
- 常见创建方式:在一个函数内部创建另一个函数
- 外部函数活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作用域链的重点为全局执行环境。
var date = [{ name: "Mike", age: 20 }, { name: "Aate", age: 19 }];
function compare(kind) {
var number = 12;
return function (object1, object2){
value1 = object1[kind];
value2 = object2[kind];
console.log(number);//19
if (value1 > value2)
return 1;
else
return 0;
}
}
console.log(date.sort(compare("age")));{name: "Aate", age: 19}{name: "Mike", age: 20}
- 另一个函数内部定义的函数会将外部函数的活动对象添加到它的作用域中。本例中,匿名函数将compare函数的活动对象添加到自己的作用域链中。
- compare函数在执行完后,其活动对象不会被销毁,因为匿名函数的作用域链仍然在引用这个活动。当compare函数返回后,其执行环境的作用域会被销毁,但其活动对象仍留在内存,直至匿名函数被销毁,compare函数的活动对象才会被销毁。
闭包与变量
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(){
return i;
};
}
return result;
}
var a = createFunctions();
console.log(a[0]());//10
console.log(a[1]());//10
- 每个函数都返回10。每个函数的作用域链中都保存createFunctions()函数的活动对象,所以它们引用的都是同一个 i,即createFunctions()函数返回后,i为10。
- 解决方案:定义一个匿名函数,将立即执行该匿名函数的结果赋给数组。调用匿名函数时,传入i,将变量i的当前值复制给num。
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(num){
return num;
}(i);
}
return result;
}
var a = createFunctions();
console.log(a);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
关于this对象
- 使用this会造成一些问题。匿名函数的执行环境具有全局性,因此this对象常指向window。
var name = "The window";
var object = {
name: "My Object",
getNameFunc: function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()()); //"The Window"
- 把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
var name = "The window";
var object = {
name: "My Object",
getNameFunc: function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(object.getNameFunc()()); //"My Object"
- 由于我们在外部函数中声明了变量,函数返回后,that仍引用着object。
模仿块级作用域
- 使用匿名函数可以创建块级作用域。函数执行完后,作用域链即可销毁。
- 常用在全局作用域
(function(){
//这里是块级作用域
})();
举个例子:
(function(){
var now = new Date();
if(now.getMonth() == 0 &&now.getDate == 1){//到1月1日显示消息
alert("Happy New Year!");
}
})();
私有变量
- 私有变量:函数形参、局部变量和在函数内部定义的其他函数。
- 特权方法:有权访问私有变量和私有函数的公有方法。
- 可以用特权方法访问私有变量
function Person(name){
this.getName = function(){
return name;
};
this.setName = function(value){
name = value;
};
}
var person = new Person("Mike");
console.log(person.getName());//"Mike"
person.setName("Grey");
console.log(person.getName());//"Grey"
- 由于getName()和setName()是在构造函数内部定义的,作为闭包能够通过作用域链访问name。
- 私有变量name在Person的每一个实例中都不同,因为每次调用都会重新创建这两个方法
- 每个实例都会创建同样一组新方法,造成内存浪费。(静态私有变量可以避免)
静态私有变量
- 通过立即执行函数,将变量存贮在全局环境中,通过函数的闭包,构建出一个不被释放的作用域链。通过特权方法,访问私有变量
- 基本模式
(function(){
var privateVariable = 10;//私有变量和函数
function privatefunction(){
return false;
}
MyObject = function(){
};
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
举例:
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();
var person1 = new Person("Mike");
console.log(person1.getName());//"Mike"
person1.setName("Greg");
console.log(person1.setName());//"Greg"
var person2 = new Person("Ann");
console.log(person1.getName());//"Ann"
console.log(person2.getName());//"Ann"
- Person构造函数与getName()和setName()一样,都可访问私有变量name。name就变为一个静态的、有所有实例共享的属性。
模块模式
- 为单例创建私有变量和特权方法
- 方法:在匿名函数内部,首先定义私有变量和函数,然后将一个对象字面量作为函数值返回。返回的字面量中只包含可以公开的属性和方法。
- 这个对象是在匿名函数内部定义,因此有权访问私有变量和函数。
var application = function(){
var components = new Array();//私有变量和函数
components.push(new BaseComponent());//初始化 向数组添加一个BaseComponent实例来初始化数组
return {
getComponentCount:function(){//getComponentCount有权访问component
return components.length;
},
registerComponent : function(component){//registerComponent有权访问component
if(typeof component == "object"){
components.push(component);
}
}
};
}();
增强的模块模式
- 适用于那些单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况。
var application = function(){
var components = new Array();//私有变量和函数
components.push(new BaseComponent());//初始化
var app = new BaseComponent(); //创建application的一个局部副本
app.getComponentCount = function(){ //公共接口
return components.length;
};
app.registerComponent = function(component){
if(typeof component == "object"){
components.push(component);
}
};
return app;//返回这个副本
}();