定义
定义一:有权访问另一个函数作用域中的变量的函数。
定义二:可以访问其被创建时所处的上下文环境的函数。
创建闭包常见方式
function createComparationFunction(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;
}
}
}
内部函数访问了外部函数的propertyName;
当函数被调用时,会创建一个执行环境及相应的作用域链。
作用域链本质上是一个指向变量对象的指针列表,只引用但不包含变量对象。
(js高程P179 图7-1)
在createComparationFunction函数内部定义的函数会将createComparationFunction的活动对象添加到其作用域链。
var compare = createComparationFunction("name");
var result = compare({name:"a"},{name:"b"});
因为其内部的匿名函数的作用域链仍然在引用这个活动对象,因此在createComparationFunction函数执行完毕后,其活动对象也不会被销毁,这一过程直至匿名函数被销毁,内存中的活动对象才会被销毁:
compare = null;
由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多内存。
闭包与变量
闭包的副作用:闭包只能取得包含函数中任何变量的最后一个值。
因为闭包保存的是整个变量对象。
经典问题:
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result [i] = function(){
return i;
}
}
return result;
}
alert(createFunctions()[3]()) //结果为10
这个函数会返回一个返回值都是10的函数数组,而不是返回值依次为1,2,3……9,10的数组。
因为每个函数的作用域链中都保存着createFunctions函数的活动对象,所以他们引用的都是同一个变量i。
createFunctions函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以每一个函数内部i的值都为10。
改进:
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result [i] = function(num){
return function(){
return num;
}
}(i)
}
return result;
}
alert(createFunctions()[3]()) //结果为3
这次在第一个匿名函数的内部又包含了另一个匿名函数。每次循环中,第一个匿名函数会被立即执行并将结果赋给数组。执行时传入了变量i,由于函数参数是按值传递的,因此就会将i当前的值赋值给num,而此时第二个匿名函数内部又创建了一个访问num的闭包,最终会将num返回给数组。
有关this对象
var name = "window";
var object = {
name:"my object",
getName:function(){
return function(){
return this.name;
}
}
}
alert(object.getName()()); //window
闭包在搜素this与arguments时,只会搜索到其自身的活动对象为止,因此永远不可能直接访问外部函数中的this与arguments。此时的this为window。
在定义闭包之前,可以把外部作用域的this对象保存在一个闭包能访问到的变量that中:
var name = "window";
var object = {
name:"my object",
getName:function(){
var that = this;
return function(){
return that.name;
}
}
}
alert(object.getName()()); //my object
内存泄漏
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
由于匿名函数保存了一个对外部函数的活动对象的引用,因此只要匿名函数存在,element的引用数至少为1,其占用的内存永远不会被回收。
改进:
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
通过把element.id的值给变量id并不能解决内存泄漏。即使匿名函数没有直接引用element,但其引用了外部函数的整个活动对象,而其中包含了element,因此element仍然占用内存。
因此有必要把element设为null,解除对DOM对象的引用,从而可以正常回收其占用的内存。
块级作用域
js没有块级作用域的概念,这说明在块语句中定义的变量实际上是包含在函数中的。
可以使用匿名函数来模仿块级作用域:
(function(){
//这里面为块级作用域
})();
将函数声明包含在括号中表示他其实是一个函数表达式;紧随其后的另一对圆括号会立即调用此函数。
用法:
function outputNumbers(count){
(function(){
for(var i = 0;i
由于定义的匿名函数是闭包,因此它可以访问count,但是i却会在循环结束后被立即销毁。
这种技术经常在全局作用域中被用在变量和函数外部,从而限制向全局作用域中添加过多的变量和函数。通过创建块级作用域(私有作用域),每个开发人员可以使用自己的变量又不用担心污染全局作用域。
(function(){
var a = new Date();
var b = a;
alert(b);
})()
私有变量
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数,局部变量和在函数内部定义的其它函数。
如果在函数内部创建一个闭包,那么闭包通过自己的作用域链可以访问函数内的私有变量,基于此可以创建用于访问私有变量的公有方法。
特权方法:有权访问私有变量和私有函数的公有方法。
- 构造函数模式
function Myobject(){
//私有变量
var privateVar = 10;
//私有函数
function privateFun(){
return false;
}
//特权方法:
this.publicMethod = function(){
privateVar ++;
return privateFun();
}
}
能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问构造函数中定义的所有变量和函数。privateVar和privateFun都只能通过特权方法访问。
在构造函数中定义特权方法的缺点:每次创建实例调用构造函数都会重新创建同样一组新方法。
- 静态私有变量
通过在上面学习过的块级作用域中定义变量和函数:
(function(){
//私有变量
var privateVar = 10;
//私有函数
function privateFun(){
return false;
}
//构造函数
MyObject = function(){
};
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVar ++;
return privateFun();
};
})()
在声明MyObject时并没有使用var关键字,由于初始化未经声明的变量总会创建一个全局变量,因此MyObject成为了一个能够在私有作用域外访问到的全局变量。
而由于此处的特权方法是在原型上定义的,因此所有实例使用同一个函数。
此时变量privateVar就变成了一个静态的,由所有实例共享的属性。
- 模块
为单例(只有一个实例的对象)创建私有变量和特权方法。
js利用对象字面量的方式来创建单例:
var singleton = {
name:value,
method:function(){
//方法代码
}
}
模块为单例添加私有变量和特权方法:
var singleton = function(){
//私有变量
var privateVar = 10;
//私有函数
function privateFun(){
return false;
}
//特权方法和属性
return{
publicProperty:true,
publicMethod:function(){
privateVar ++;
return privateFun();
}
}
}();
此模块定义了一个返回对象的匿名函数,在匿名函数内部定义了私有变量和私有函数,然后将一个对象字面量作为函数的值返回。此对象字面量中只包含公开的属性和方法,且可以访问私有变量和私有函数。
如果要创建一个对象并以某些数据对其初始化,同时还要公开一下能访问这些私有数据的方法,那么就使用模块。