闭包是指有权访问另一个 函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数。
闭包的特点:
1.函数嵌套函数,并以函数作为返回值。
2.内部函数可以访问外部函数的变量
3.参数和变量不会被回收。
例如:
function test(){ var x = 10; return function(y){ return y + x; } } var t1 = test(); console.log(t1(9));
在该例子中,在匿名函数从 test()中被返回后,它的作用域链被初始化为包含 test()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在 test()中定义的所有变量。更为重要的是,test() 函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换 句话说,当 test() 函数返回后,其执行环境的作用域链会被销毁,但它的活 动对象仍然会留在内存中;直到匿名函数被销毁后,test()的活动对象才会 被销毁。如下:
function test(){ var x = 10; return function(y){ return y + x; } } //创建函数 var t1 = test(); //调用函数 var num = t1(9); //解除对匿名函数的引用(以便释放内存) t1 = null;
随着匿名函数的作用域链被销毁,其他作用域 (除了全局作用域)也都可以安全地销毁了。
闭包与变量
作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最 后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说 明这个问题。
function test(){ var arr = []; for(var i = 0; i < 10; i++){ arr[i] = function(){ return i; } } return arr; } var arr = test(); console.log(arr[1]());//10
这个函数会返回一个函数数组,每个函数返回的都是 最大值 10。因为 当test() 执行完成时,i的值是10,闭包只能取得包含函数中任何变量的最 后一个值,所以每次返回都是10。
可以通过创建另一个匿名函数强制让闭包的行为 符合预期,如下所示。
function test(){ var arr = []; for(var i = 0; i < 10; i++){ arr[i] = function(num){ return function(){ return num; } }(i) } return arr; } var arr = test(); console.log(arr[1]());//1
让匿名函数自执行,参数传进去, 然后返回另一个匿名函数形成闭包,而这个匿名函数就会取得自执行函数的参数,所以就会返回不同的值
关于this对象
虽然闭包 可以访问 外部函数的 所有变量,但是对于this 和 arguments 却存在问题,如下
var name = 'window'; var obj = { name: 'obj', test: function(){ return function(){ return this.name; } } } console.log(obj.test()()); // window
这里访问的this 确实全局window 对象,而不是外部函数的this ,每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函 数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
也可以让它访问到外部函数的this 如下:
var name = 'window'; var obj = { name: 'obj', test: function(){ var that = this; return function(){ return that.name; } } } console.log(obj.test()()); // obj
这样就可以访问到 外部函数的this了,如果想访问作用域中的 arguments 对 象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。
内存泄漏
由于 IE9 之前的版本对 JScript 对象和 COM 对象使用不同的垃圾收集例程。
因此闭包在 IE 的这些版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁。如下:
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); }; }
以上代码创建了一个作为 element 元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用,由于匿名函数保存了一个对 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 对象的引 用,顺利地减少其引用数,确保正常回收其占用的内存。