两个示例介绍JavaScript的闭包


JavaScript的闭包有两个用途:一个是访问函数内部的变量;另一个是让变量的值在作用域内保持不变。函数是JavaScript 中唯一有作用域的对象,因此JavaScript的闭包依赖于函数实现,下面结合两则示例对JavaScript的闭包做简单说明。


1. 访问函数内部的变量

JavaScript中没有严格意义上的类,所以我们常用函数来模拟类。我们可以像下面这样模拟一个Counter类,并在其中定义一个count私有变量:

function Counter() {
  var count = 2;
  return {
    increment: function() {
      count++;
    },
    get: function() {
      return count;
    }
  }
}

var foo = new Counter();
foo.increment();
foo.get();    // -> 3

JavaScript中,函数可以访问其外部定义的变量,而外部不能访问函数内部定义的变量。要访问函数内部定义的变量,需要在其内部分定义一个闭包函数,通过调用闭包函数达到访问函数内部变量的目的。

在上面示例中,count相当于一个“私有变量”,在Counter函数外部不能访问这个变量。在Counter中,还定义了incrementget两个“闭包函数”,这两个函数都保持着对Counter作用域的引用,因此可以访问到Counter作用域内定义的变量count


2. 保持变量的作用域

ECMASceript 2015(ES6)之前,JavaScript中没有块级变量(ES6中可以使用let定义块级变量),使用var定义的变量都是全局或函数级变量。

for(var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);  
  }, 1000);
}

在上面的代码中,我们期望的依次输出数字09,而实际是输出是十次10。这是因为JavaScript只有函数级作用域,没有块级作用域。要解决这个问题,就要为变量构建一个作用域。解决方案如下:

方案一:在循环中使用闭包

for(var i = 0; i < 10; i++) {
  (function(e) {
    setTimeout(function() {
      console.log(e);  
    }, 1000);
  })(i);
}

方案二:匿名函数中返回一个函数

for(var i = 0; i < 10; i++) {
  setTimeout((function(e) {
    return function() {
      console.log(e);
    }
  })(i), 1000)
}

以上两种方案都是通过闭包(匿名函数)实现的,外部函数执行后i作为参数传入,此时函数内e变量就拥有了i的一个拷贝。变量传入匿名函数后相当于有了一个闭包的作用域,然后就得到了我们想要的结果。