也聊聊Javascript闭包(Closure)

说起闭包,记忆最深刻的莫过于初中数学老师的解释,一个包含边界的数值范围。一条数轴,两个实心点,一条括号一样的线,表示了闭包。对应的数学记号例如[1,8],包含1、8以及大于1小于8的所有数。网上有些资深人士说,Javascript的闭包就是内部函数,或者更具体点是return的内部函数。作为数学系的毕业生,直觉告诉我应该没有那么简单:

  1. 中学和大学的数学课程里面都有涉及到闭包,但都是范围概念。
  2. 如果闭包是指内部函数,那么就完全没有必要引入闭包那么个抽象的概念害人,应该直接叫内部函数。

打开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/。长但值得拥有。

也聊聊Javascript闭包(Closure),布布扣,bubuko.com

也聊聊Javascript闭包(Closure)

上一篇:Java类加载器加载类顺序


下一篇:初识Java——循环语句