闭包的定义
定义一:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。 --《You Don't Know JS》
定义二:闭包是指有权访问另一个函数作用域中的变量的函数。 -- 《JavaScript高级程序设计》 关键词: 1. 是一个函数;2.能够访问另外一个函数作用域中的变量。
创建闭包的常见方式就是在一个函数内部再创建一个函数。
var a = 1;
function foo () {
var b = 2;
function bar () {
console.log(a + b);
}
return bar;
}
var baz = foo();
baz(); // 3
分析: 变量b位于函数foo的执行环境中,在全局执行环境中是不能访问的。我们在foo函数内部定义一个bar函数,bar函数既可以访问变量b,也可以访问全局变量a,此时的这个bar函数就是闭包。bar函数记住了当前的词法作用域,在全局执行环境中访问时,仍然可以访问到定义时的词法作用域。
特性: 1.可以访问当前函数以外的变量; 2.即使外部函数已经返回,闭包仍然可以访问外部函数定义的变量; 3.闭包可以更新外部变量值
注意:闭包可以使执行环境得以保留而不被垃圾回收清理掉。
留问题:看看自己平时写过的代码,闭包用在哪些地方了?
循环闭包
请看下面这段代码,并说出其打印的内容
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
预想情况:1 2 3 4 5 实际输出:6 6 6 6 6 首先解释 6 是从哪里来的。这个循环的终止条件是 i 不再 <=5。条件首次成立时 i 的值是6。因此,输出显示的是循环结束时 i 的最终值。 我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。
如何解决这个问题: 1、使用立即执行函数IIFE + 新增变量j(IIFE 会通过声明并立即执行一个函数来创建作用域):
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}
2、使用块作用域:
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
闭包引起的其他问题
- this指向问题
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
- 内存泄漏问题