JS 闭包
Posted 你今天学习了吗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS 闭包相关的知识,希望对你有一定的参考价值。
1.先来回顾一下作用域链和执行环境吧,
当某个函数被调用,会创建一个执行环境和相应的作用域链。如:
function compare (value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return1;
} else {
return 0;
}
}
var result = compare(5, 10);
以上代码先定义了compare()函数,然后又在全局作用域中调用了它,当调用compare()时,会创建一个包含arguments,value1,value2的活动对象。全局执行环境的变量对象(包含result,compare)在compare()执行环境的作用域链中则位于第二位,如图:
后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部变量的变量对象,则只在函数的执行过程存在。
在创建compare()函数时,会先创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[ [Scope] ] 属性中。
当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[ [Scope] ] 属性中的对象构建起执行环境的作用域链。
此后,又会有一个活动对象(在此作为变量对象使用)被创建并推入执行环境作用域链的前端。
对于上述例子,compare()的执行环境而言,其作用域链包含了两个变量对象:本地活动对象和全局变量对象,显然,作用域链本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
一般来讲,当函数执行完毕时,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),闭包中有所不同
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 compare = createComparisonFunction(\'name\');
var result = compare({name: \'alice\'}, {name: \'bob\'});
下列代码执行时:
匿名函数返回到compare中时,其作用域链初始化为包含createComparisonFunction函数的活动对象和全局变量对象。这样,匿名函数就可以访问createComparisonFunction()中定义的所有变量
更为重要的是,createComparisonFunction()函数执行完毕后,其执行环境的作用域链被销毁,但是其活动对象不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象
直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁
例如:
var compareNames = createComparisonFunction(\'names\'); // 创建函数
var result = compareNames( {name: \'alice\' }, { name: \'bob\'} ); // 调用函数
compareNames = null; // 解除对匿名函数的引用(以便释放内存)
由于闭包会携带包含它的函数的作用域,会占用更多的内存,过度使用闭包会导致内存占用过多,慎用闭包
IE 会造成内存泄漏,这是IE 的一个bug
(1)闭包与变量
闭包只能取得包含函数中任何变量的最后一个值
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的同一个变量对象,所以都是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;
}
定义一个匿名函数,并立即执行该匿名函数的结果赋给数组。
(2)关于this对象
我们知道,this是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,
this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window
var name = \'The Window\';
var object = {
name: \'My Object\';
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()); // \'The Window\'
这里输出为什么不是My Object 呢,不是说闭包可以访问外部函数的变量吗
前面提到过,每个函数在被调用时都会自动取得两个特殊变量,arguments和this
内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能访问直接访问外部函数中的这两个变量
我们可以把外部作用域的this保存在一个闭包能够访问的变量里。
var name = \'The Window\';
var object = {
name: \'My Object\';
getNameFunc: function () {
var that = this; // 保存当前上下文this
return function () {
return that.name;
};
}
};
alert(object.getNameFunc()()); // \'My Object\'
在几种情况下,this的值可能会特殊改变,
var name = \'The Window\';
var object = {
name: \'My Object\',
getName: function () {
return this.name;
}
}
object.getName(); // \'My Object\'
(object.getName) (); // \'MyObject\'
(object.getName = object.getName) (); // \'My Window\' 相当于赋值,把函数拿到全局中调用
(3)内存泄漏
IE的内存泄漏,我们知道IE的垃圾回收机制在IE8之前是引用计数,如下例子
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;
}
通过把element.id的副本保存在变量中,消除了循环引用,但是记住,闭包会引用包含函数的整个活动对象,而其中包含着element。
即使闭包不使用element,包含函数的活动对象中也仍然会保存着一个引用。因此,有必要把element设置为null,解除对dom对象引用
以上是关于JS 闭包的主要内容,如果未能解决你的问题,请参考以下文章