JavaScript之闭包

203 阅读2分钟

闭包的定义

定义一:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。 --《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 );
}

闭包引起的其他问题

  1. this指向问题
var name = "The Window";
var object = {
    name : "My Object", 
    getNameFunc : function () { 
        return function () { 
            return this.name;
        };
    } 
}; 
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
  1. 内存泄漏问题