说起闭包,记忆最深刻的莫过于初中数学老师的解释,一个包含边界的数值范围。一条数轴,两个实心点,一条括号一样的线,表示了闭包。对应的数学记号例如[1,8],包含1、8以及大于1小于8的所有数。网上有些资深人士说,Javascript的闭包就是内部函数,或者更具体点是return的内部函数。作为数学系的毕业生,直觉告诉我应该没有那么简单:
- 中学和大学的数学课程里面都有涉及到闭包,但都是范围概念。
- 如果闭包是指内部函数,那么就完全没有必要引入闭包那么个抽象的概念害人,应该直接叫内部函数。
打开Google,输入javascript closure,选择前三个搜索结果,开始研究。
-
*
感觉流于展示javascript闭包如何工作,没有找到期待的定义和工作原理。 -
阮一峰的网络日志
阮大侠学识渊博,有很多的粉丝。我对大侠也很钦佩,但是对于javascript闭包这篇文章里面的一些内容 不大认同,用他的例子执行出的结果也与预期不一致。 -
http://jibbering.com/
详细、深入,最重要的是看到了我一直期待的范围(scope)。文章很长,但是值得精读。我对于javascript闭包的理解 主要基于该文。
最重要的概念——执行上下文(Execution Context)
我更愿意把它称为执行环境,感觉这个叫法更接地气,容易理解,下面都以执行环境来称呼它。
所有的代码都需要在某个执行环境里面才能执行,执行环境可以被理解为一个存储key-value的对象。javascript里面常用的有两类执行环境, 全局环境(Global)和函数(Function)环境。每次执行一个函数(Function),当前执行环境就会切换到一个新的执行环境,如果函数里面再调另外函数,会形成执行环境栈(Stack)。全局环境存储两类内容,全局变量和全局的函数。函数环境存储三类内容,参数(Arguments)、局部变量(Local Varibale)、所有子函数。
子函数的执行环境是父函数所形成的函数环境,也就是他能够访问父函数的参数、父函数的局部变量、同级别的其它子函数。执行环境,或者叫可访问范围,才是真正的闭包。函数的执行需要执行环境,函数还存活的时候(比如return回去),它的执行环境不能被垃圾回收器回收,否则函数无法执行。闭包的魔法正在于此。
同级别的其它子函数作为执行环境例子
function func1(){ function func2(){ console.log("Exe func2"); } return function(){ func2(); } } func1()();
func2作为return函数的执行环境,返回之后依然存在。
阮大侠的两个例子解释
阮大侠在那篇文章里面提供了两个例子,我尝试用执行环境来解释一下。我的运行环境是Node.js,只是把alert换做console.log而已。代码如下:
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; console.log(object.getNameFunc()()); var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { var that = this; return function() { return that.name; }; } }; console.log(object.getNameFunc()());
执行结果为:
undefined
My Object
我想换一下代码书写方式,但语义不变:
var name = "The Window"; function Object1() { this.name = "My Object"; this.getNameFunc = function() { return function() { return this.name; } } } var obj1 = new Object1(); console.log(obj1.getNameFunc()()); function Object2() { this.name = "My Object"; this.getNameFunc = function() { var that = this; return function() { return that.name; } } } var obj2 = new Object2(); console.log(obj2.getNameFunc()());
- 关于Object1
Object1为一个构造函数,getNameFunc为Object1的一个对象方法,getNameFunc没有参数,没有局部变量,没有除最内层返回函数之外的子函数。 所以最内层函数的执行环境为{}, 最内层函数内的this指向global,Node.js里面全局变量不会自动赋值给global,所以结果为undefined,浏览器环境 下应该为The Window。 - 关于Object2
Object2为一个构造函数,getNameFunc为Object2的一个对象方法,getNameFunc有一个局部变量that,this是obj2,所以that为obj2,最内层函数 的执行环境为{that:{name:"My Object"}}。执行结果自然就是My Object。
区别就是执行环境不同。
结语
推荐英文好的朋友好好看看这篇文章,http://jibbering.com/。长但值得拥有。