闭包是 JavaScript 的一个重要知识点,对于学前端的人来说是必须要理解的一个知识点,它其实已经时不时地被我们使用着,只不过我们“日用而不知”,本文带你一起来学习下闭包。

全局作用域与局部作用域

在认识闭包之前,首先要了解下 JavaScript 的作用域概念。看看下面这段代码:

1
2
3
4
5
var x = 2;
function test() {
return x * x;
}
test(); // 4

可以看到,在 JavaScript 的语法里,函数内部可以读取函数外部的变量,此时变量 x 就是 window 下的全局作用域。然而,函数内部定义的变量,外部是无法访问的:

1
2
3
4
function test() {
var x = 2;
}
alert(x); // error

这里的 x 就是局部作用域,只能在函数内部使用它,需要注意的是,在函数内部定义变量时,要使用 var 关键字,否则它实际上也是一个全局变量!

计数器问题

也许你已经发现了,全局变量可以在任意地方调用它,可是我们却不能从外部读取函数内部的变量。如果你还未发觉,那在看看下面这个例子。假设现在你想写一个计数器函数,你可能会这么写:

1
2
3
4
5
6
7
8
function count() {
var num = 0;
return num += 1;
}
var result = count();
console.log(result); // 1
console.log(result); // 1
console.log(result); // 1

你看,当你写完之后兴致勃勃地执行它,原本以为会输出 1 2 3 ,结果全都是 1,你在纳闷的时候突然发现这里的 count 是一个局部作用域,每次调用函数的话它又被重新定义了,于是你想着这样改:

1
2
3
4
5
6
7
8
var num = 0;
function count() {
return num += 1;
}
count();
count();
count();
console.log(num); // 3

这样计数器就可以了,结果是 3 了,但是你想,现在的 num 是全局变量了,这就意味着谁都可以修改 num 的值,那这个计数器现在还有意义么?所以,我们的需要对其进行优化。

闭包

我们试着这样来实现,就是在函数里面在内嵌一个函数,这样,内层函数就能访问外层函数的变量,而变量也是被封装在外层函数里面,本身它也是属于局部变量,作用域也是局部的。

1
2
3
4
5
6
7
var count = (function () {
var num = 0;
return function () { return num += 1; }
})();
count();
count();
count();

上面这个就是闭包(closure),关于它的官方概念有很多文献可以参考,我的简单理解是函数里面在嵌套函数,内部函数可以访问外部函数的变量,外部函数的变量是私有属性,只能在函数作用域内使用,这就是形成了闭包。它的好处是解决了开头提到的函数内部定义的变量,外部无法访问的问题。另外,闭包还有一个特色就是,局部变量定义后会一直保存在内存中,并没有在函数被调用后就被垃圾回收。

额外思考题

在查阅了相关资料后,从 W3C 看到了关于闭包的两道思考题,可以说很经典了,就留给大家思考吧!
代码片段1(来自 W3Cschool )

1
2
3
4
5
6
7
8
9
10
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());

代码片段2(来自 W3Cschool )

1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());