闭包
- 闭包是指有权访问另一个函数作用域中的变量的函数。
- 创建闭包的常见方式,就是在一个函数内部创建另一个函数。
function createComparisonFunction(propertyName) {
return function(object1, object2){
//内部函数的作用域链中包含createComparisonFunction()的作用域
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
-
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments 和其他命名参数的值来初始化函数的活动对象但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。
-
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除对匿名函数的引用(以便释放内存)
compareNames = null;
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(PublicMethod),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包与变量
- 作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
//这个函数会返回一个函数数组,每个函数都返回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){
//创建并返回了一个访问num 的闭包
return function(){
return num;
};
}(i);//将立即执行改匿名函数的结果赋给数组
}
return result;
}
关于this对象
- this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this 等于那个对象
- 匿名函数的执行环境具有全局性,因此其this对象通常指向window(在通过call()或apply()改变函数执行环境的情况下,this 就会指向其他对象。)
- 但有时候由于编写闭包的方式不同,这一点可能不会那么明显.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
- 为什么匿名函数没有取得其包含作用域(或外部作用域)的this 对象呢?
- 每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
- 把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
/**在定义匿名函数之前,我们把this对象赋值给了一个名叫that的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。即使在函数返回之后,that 也仍然引用着object,所以调用object.getNameFunc()()就返回了"My Object"。**/
内存泄露
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
- 如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁.
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
/**由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会导致无法减少element 的引用数。只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收**/
- 解决方法是,在退出函数之前,将不使用的局部变量全部删除。
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
模仿块级作用域
- javaScript 没有块级作用域的概念,意味着在块语句中定义的变量,实际上是在包含
函数中而非语句中创建的
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //计数
}
//即使像下面这样错误地重新声明同一个变量,也不会改变它的值。
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
var i; //重新声明变量
alert(i); //计数
}
- 匿名函数可以用来模仿块级作用域并避免这个问题。
//用作块级作用域(通常称为私有作用域)的匿名函数的语法
(function(){
//这里是块级作用域
})();
- 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
(function(){
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1){
alert("Happy new year!");
}
})();
/**这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函
数执行完毕,就可以立即销毁其作用域链了。**/
好好学习
网友评论