过不少谈如何理解闭包的方法,但大多数文章,都是照抄或者解释《Javascript高级程序设计(第三版)》对于闭包的讲解,甚至例程都不约而同的引用高程三181页‘闭包与变量’一节的那个“返回数组各个项,结果各个项的值都相同”的例程,还有些文章的讲解过程上一步与下一步之间的跨度简直就是一步登天,让人反复看半天都没法理解。javascript
闭包的理解须要不少概念作铺垫,包括变量做用域链、执行环境、变量活动对象、引用式垃圾内存收集机制等,若是对本文涉及的这些概念不理解,能够去找本《Javascript高级程序设计(第三版)》好好看看并理解这些概念。java
我本身也是在反复编写一些例子并仔细认真的阅读和理解高程三与闭包相关的概念和知识后,终于对闭包有了必定的理解,为了检验学习效果,特别写篇文章以便检验本身是否真正理解和掌握。数组
在理解闭包以前,咱们先来看些理解闭包容易忽略的小东西。闭包
//例子1:
function foo(){ var a = 1; return a; }; console.log(foo()); //1
上面的这个例子很好理解,一个函数若是return一个值,那么这个函数就能够看成return的值直接使用,不管它return的是一个引用类型(函数、数组)仍是基本数据类型。函数
//例子2:
function foo(){ function foos(){ var b = 2; return b; } }; console.log(foos()); //Uncaught ReferenceError: foos is not defined
这个例子说明,若是一个函数B被声明在一个函数A的内部,那么在函数A外部,经过函数B的函数名直接调用函数B,是会出未定义错误的。你能够理解为:变量做用域规则中的局部做用域规则对于函数声明一样成立。学习
//例子3:
function foo(){ var a = 1; function foos(){ return a;//注意:返回的a是foo中定义的变量,而不是foos中定义的变量。 } console.log(foos()); }; foo();//1
例子3说明foos只能在foo内部执行,这符合咱们对于“变量做用域规则中的局部做用域规则,对于函数声明一样成立”的理解。spa
//例子4:
function foo(){ var arr = new Array();
for(var i=0;i<10;i++){ arr[i] = function(){ return i; }; } return function(){ for(var k = 0;k<arr.length;k++){ console.log(arr[k]()); } }; }; console.log(foo()());//输出10个10;
例子4中中执行foo()之后获得的是foo本身返回的一个匿名函数(以下所示):设计
return function(){ for(var k = 0;k<arr.length;k++){ console.log(arr[k]()); } };
那么,此时咱们还须要执行一遍这个获得的匿名函数,才能得出最后的值(也就是:10个10),而执行这个获得的匿名函数(咱们不妨叫它函数C)时,foo已经执行了一遍了,这个时候foo的变量i的值已是10了,因为变量对象是经过引用赋值的,因此这个时候foo内的闭包函数(也就是引用了foo的变量i的匿名函数)再来引用foo的变量i,那么获得的值天然就是10了。这里的关键在与foo内的闭包函数执行的时机,若是闭包函数当即执行后再赋值给arr[i],那么就能够获得咱们指望的值0到9了;code
//例子5:
function foo(){ var arr = new Array(); for(var i=0;i<10;i++){ arr[i] = function(){ return i; }(); } return function(){ for(var k = 0;k<arr.length;k++){ console.log(arr[k]); } }; }; console.log(foo()());//0到9
关于例子4更详细的解释:
在例子4中,根据javascript的引用垃圾收集机制(这个机制的规则是:存在大于0个引用的变量不该该被销毁),虽然此时函数foo已经执行完了,可是foo的这个变量i由于被匿名函数(咱们不妨叫它函数B)引用,因此这个i仍是没有被垃圾收集程序销毁,那么这个i此时存在哪里呢?确定不是存在于函数foo的执行环境里面,由于执行环境建立和销毁的规则是:当一个函数建立时,则建立这个函数的执行环境,一旦这个函数执行完毕则立刻销毁这个执行环境。所以这个i只可能存在于foo内部的匿名函数(函数B)的执行环境中,这个函数B的执行环境中不但有本身的做用域和本身的变量对象,并且包含了其外部函数foo的做用域和foo的变量对象,变量i就存在于函数B包含的foo的做用域中和变量对象中。因为javascript不能直接操做内存空间,因此javascript只能以引用的方式访问变量对象,当foo的变量i的值已经变成10了的时候,再去执行foo的闭包函数(函数B),那么此时对i的引用天然会获得10。对象
使用以上实验得出的原理,咱们还能够很容易的理解《Javascript高级程序设计(第三版)》181页最下面的那个例子:
//《Javascript高级程序设计(第三版)》181页最下面的那个例子: function createFunctions(){ var result = new Array(); for(var i=0;i<10;i++){ result[i] = function(num){ return function(){ return num; } }(i);//这里是将参数传给该匿名函数,而且当即执行该匿名函数的写法; } return result; } for(var i=0;i<10;i++){ console.log(createFunctions()[i]()); }//0,1,2,3,4,5,6,7,8,9
同时,咱们能够很容易的改写这个例子:
function createFunctions(){ var result = new Array(); for(var i=0;i<10;i++){ result[i] = function(){ return function(){ return i; }()//这里添加了当即执行 }();//这里是将参数传给该匿名函数,而且当即执行该匿名函数的写法; } return result; } for(var i=0;i<10;i++){ console.log(createFunctions()[i]);//这里去掉了一对括号 }//0,1,2,3,4,5,6,7,8,9
这个改写的例子同时证实,最内层的匿名函数仍是能够引用最外面全局函数的变量。
下面这张图片中的例子,使用闭包就能够很好的理解了:
要是实在不理解,还能够试着这样来验证一下帮助理解:
function foo(){ var diss = "mem_count"; return function(){ return diss; } } var fo = foo(); var as = fo(); //undefined as //"mem_count" var as2 = fo(); //undefined as2 //"mem_count" fo() //"mem_count" fo() //"mem_count" fo() //"mem_count"
若是这样仍是没办法理解,那真是应该先去看看变量的做用域、垃圾回收机制,函数表达式等基础知识了。