Javascript 闭包

Javascript闭包

说到javascript的闭包,就要先说一下传统语言的一些特征,如C,Java,C#等等,void Test() {int i = 0;} ,当Test方法调用结束时,执行栈弹出,局部变量被回收,而js的闭包,则是提供了一种在test这个执行域(函数)消亡时,仍能够访问i的方式。

1
2
3
4
5
6
function greeting(name) {
var text = 'Hello, ' + name; // local variable  
return function() { alert(text); } // 每次调用时,产生闭包,并返回内部函数对象给调用者  
}
var sayHello=greeting("Closure");
sayHello() // 通过闭包访问到了局部变量text  

1.闭包原理

闭包的原理涉及到ECMAScript语言的一些特征,下面进行详述

1.1 Execution Context

控制权转移到一段可执行代码时候,就会产生一个Execution Context(简称EC)。

一段可执行代码在ECMAScript里的定义可视为一个function(eval暂不提,strict mode也将其排除在外),即函数调用的时候,就会通过压栈的方式将一个个function的ECStack压入栈中,该function return的时候就从栈中pop出来,用个简单的例子来形容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function foo(flag) {
  if (flag) {
    return;
  }
  foo(true);
})(false);

// 压栈情况,globalContext视环境而定,如果是浏览器,就是window
ECStack = [
  <foo> functionContext
  globalContext
];
// 压入递归方法foo
ECStack = [
  <foo> functionContext  recursively
  <foo> functionContext
  globalContext
];
//再弹出至只剩globalContext
ECStack = [
  globalContext
];

而每个context又是由什么组成的呢, context = { VO = {}, //variable object,函数调用时的内部变量的集合,包括arguments,内部方法等. this , //不赘述了,简而言之就是caller(a.b()的a),当然new是另一情况了 scope chain = [] //见下文,用于变量查找 } 附上 VO, this, 闭包的产生主要是Scope Chain原因,但是强烈推荐以上几篇文章,对于理解ECMAScript的机制有很大帮助,能让人顿悟很多东西。

1.2 Scope

Scope上下文,即是在执行语句时候能够访问到的所有变量的集合,Javascript是允许inner function存在的,因而其Scope就是一个链表形式的存在,其最顶端就是window(浏览器环境),然后每定义一个function,就定义一个Scope Object,保存一个outer reference,以访问上一级变量集合,形成一个层级式的链,处于最底部的function会一层一层向上找变量,这也是为什么不要使用t = 'without var is global var',会大大降低执行效率。附上ECMA-262

而每个Scope保存的out reference是由function定义时决定的,而不是调用执行时决定的,如

1
2
3
4
5
6
7
8
9
10
(function() {var right = 5;
var s = function() {
   alert(right);
}
var o = function() {
var right = 4;
s();
}
o();
})();

alert结果是5而非4,如果把最上级的right定义注释掉,则right is not defined。所以这个时候是不是能理解闭包的原理了,虽然greeting方法已经消亡,但是返回的匿名函数的Scope保存有其上一级的outer reference,也就是text变量等的集合。

下一篇文章会介绍一下使用闭包的场景和相应的危害,欢迎拍砖。