PSJay Blog

#FIXME, seriously

Javascript 闭包

| Comments

先说些有的没的。博客几个月没更新了,本想写篇 11 年的总结,写了几百字后竟也无从继续下笔,干脆删掉。不更新博客的原因是因为我写博客越来越谨慎,原来越严肃,生怕写出来的东西误导了别人或者太过没有深度,没有意义,而不是像高中时代那样在博客中畅所欲言了。但我始终觉得博客不应被荒废,于是决定开始动手写写我“怕”写的东西,也是种沉淀。

之前看过一些 Javascript 闭包的资料,一直没有机会用到,不过前两天正好用了用,做个总结。废话完毕。

先谈谈 Javascript 变量的作用域,看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
var a = 1;

function hi() {
  var b = 2;
  alert(a); // 1
  alert(b); // 2
}

hi();

alert(a); // 1
alert(b);// b is not defined

如果将这段代码放在浏览器中执行,浏览器会依次弹出 1, 2, 1,执行到最后一句话时,浏览器会报错,说变量 b 未被定义。从这里可以看到一个显而易见的事实:函数内部可以访问函数外部的变量,反之则不行。这很简单并且容易理解,但有个地方需要注意:如果你不小心没有写『b = 2』之前的『var』关键字,这段程序不会出错,并且最后一个语句将会使浏览器弹出 2,因为此时 b 被声明成了全局变量

接下来谈谈 Javascript 对象。Javascript 是基于对象(Object-Based)的,而不是面向对象(Object-Oriented)的,因为它没有那么明显的封装、继承、多态等特性,并且它没有类的概念,它使用的是 prototype 模型。不过这毫不影响一个事实:除了少有的几种类型,其他数据类型大多数都是对象。看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function isObject(foo){
  alert(foo instanceof Object);
}

var a = 1;
var b = true;
var c = [];
var d = {};

isObject(a)// false
isObject(b)// false
isObject(c)// true
isObject(d)// true
isObject(function(){})// true

你可以把『Object』当成所有对象的基对象(和 Java 相似吧?),你会发现一点东西:数组也是对象,还有,是的,函数也是对象!记住这一点,函数是对象。

然后就可以看一个稍显奇怪的例子了 :

1
2
3
4
5
6
7
8
9
10
11
12
function outer() {
  var a = 0;
  function inner() {
      a++;
      alert(a);
  }
  return inner;
}

var foo = outer();
foo();// 1
foo();// 2

简单的描述这段程序的前半部分就是这样一句话:内部函数访问了外部的变量,外部函数返回了内部函数这个对象。然后看后半部分,倒数第三句话执行了 outer 函数,并把其返回值,inner 函数赋值给了 foo。所以 foo 是个函数的引用,并且可被调用。最后两句话调用了 foo 两次,分别弹出 1 和 2,弹出的值就是 outer 的内部变量 a 的值。奇怪的地方来了:outer 执行完毕之后, 居然还能对 a 进行操作。更重要的是:在 outer 执行完之后,除了通过调用 foo,你根本没有其他办法对 a 进行操作。

这个例子就是个闭包。产生这种结果的原因如下:

内部函数总是可以访问其外部变量,即便外部函数已经执行完毕。

也就是说,如果形成了闭包,垃圾收集器不会回收内部函数引用的外部变量,以便让内部函数继续对其进行操作。

通常,如果你需要保护一个变量,你就可以使用闭包。例如,我们经常这样编写 Java 代码:

1
2
3
4
5
6
7
8
9
10
11
public class User {
  private String name;

  public void setName(String name) {
      this.name = name;
  }

  public String getName() {
      return this.name;
  }
}

User 的 name 属性在外部是不可见的,只能通过 getter 和 setter 来对其进行读取。写成 Javascript 闭包版本就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function User() {
  var name;
  return {
      setName: function(n) {
          name = n;
      },
      getName: function() {
          return name;
      }
  };
}

// Test Closure
var user = User();
user.setName("tom");
alert(user.getName());

这就是 Javascript 的闭包!

参考资料: [1]http://www.zhihu.com/question/19554716 [2]http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

Comments