0、闭包

闭包的概念困惑了我很久,记得当时我面试的时候最后一面有一个问题就是问题关于闭包的问题,然而到现在已经完全不记得当时的题目是啥了,但仍然能够回忆起当时不会的feel,虽然面试官非常友好的提醒了我应该用闭包,可是在我吭哧半天出不来的情况下,迷面试官还是耐心的给我讲了什么是闭包:有一个函数处理之后返回另一个函数,且只能执行一次。然后给我把当时的题写了一下,直到我出来都没有理解什么是闭包,那个题到底是什么题,要不是其他都答出来的话,估计都要挂。哎~一个菜鸟的心路历程。于是,闭包就成了我心里的梗。

今天凭借自己的理解,解释下什么是闭包。不免会参考网上各类大神的文章,各位看到请见谅。

闭包的理解是需要一个循序渐进的过程,下面我也会循序渐进从各个角度来阐述对闭包的不同理解,以便方便大家深度理解闭包。

多层级理解JavaScript中的闭包

多层级理解JavaScript中的闭包 - 敏捷大拇指 - 多层级理解JavaScript中的闭包





1、第一梯队理解

我个人认为闭包之所以难以理解很重要的一点在于,很多概念我们在理解的过程中都会在潜意识里和这个概念本身的名词强度关联在一起在揣摩这个概念的意思,如果自己的理解和这个名词本身的字面意思看上去不那么相关的话,就会在内心产生巨大的怀疑感,不敢相信自己的理解是否正确,哪怕是正确的。所以在立即一个概念本身的含义过程中需要一个步骤就是将自己对概念的理解和名词本身找到某种莫名的连接方法就好理解了。

而闭包这个名词换做谁听上去都不知道是在说什么,这本身就给理解这个概念造成了很大的困惑,因为一个通俗易懂的代名词就可以很好地解释一个概念的50%了。比如变量就是变化的字面量,条件语句,分支语句大家一听就很好理解其概念是什么。所以首先大家需要在概念上给闭包建立一个初级的感性认识。一下这句话是我见到的简单易懂的一种解释。

functions that return functions


意思是:闭包就是一个函数,只不过这个函数是另一个函数的返回值。

没错,最表面上看似乎就是这样的。比如写一个闭包:

[JavaScript] 纯文本查看 复制代码
function fn1() { 
      var temp = 10; 
      return function() { 
          console.log(++temp); 
      } 
 } 
 fn1()();  


上面的例子里return出来的那个function就是一个非常简单的闭包,表面上看和上面的定义语句差不多就是一个从函数里返回的函数。

第一梯队的理解到这接差不多了,虽然不正确,虽然很粗糙,但对形成一个感性认识应该是够了,总结一个第一梯队的认识,什么是闭包:

  • 一个函数
  • 被其他函数return出来的函数。


这个时候认识里面应该有这么一个概念,就是闭包和我们已经理解的一个概念应该差不多,那就是函数,没错刚开始就可以这么理解,闭包就是一个函数,是一个特殊的函数,就好像js中的方法也是函数一样。




2、第二梯队理解

有了第一梯队的认识,我们慢慢修正大脑中对闭包的认识。有的人理解闭包就是一个嵌套在函数里的函数,内部函数可以访问外部函数的数据而已。这么理解是不对的。看下面这段代码:

[JavaScript] 纯文本查看 复制代码
function fn1() { 
  var temp = 10; 
  function fn2() { 
      console.log(++temp); 
  } 
  fn2() 
} 
fn1()  


可是这时的fn1()无论执行多少次打印都是11,永远不会变,所以这还不是闭包,只有当你return出来一个内部function的时候才会形成一个闭包,闭包就是return出来的这个函数。这个内部函数可以close-over外部函数的变量直到内部的这个函数(闭包)结束掉。

这时我们再来看看第一梯队中的代码

[JavaScript] 纯文本查看 复制代码
function fn1() { 
      var temp = 10; 
      return function() { 
          console.log(++temp); 
      } 
 } 
 vat func1 = fn1(); // func1就是一个闭包(就是fn1返回的函数)。 
 func1(); // 打印11 
 func1(); // 打印12  


这个时候func1是全局变量,但是打印的时候却访问的是fn1的局部变量temp并且,当fn1()函数执行完之后,temp的变量并没有被垃圾回收到仍然存在于内存中,这就是闭包的特点。也就是刚刚我们说的内部函数close-over外部函数的变量。理解这句话就可以很好的与闭包这两个字关联起来理解闭包这个概念了。

总结第二梯队理解:
  • 闭包是一个有特定功能的函数。他是一个可以读取其他函数内部变量的一个函数。
  • 因为在JavaScript中如果你想读取一个函数内的变量(通常称为局部变量)只有函数的子函数可以访问。
  • 那么将这两个概念交叉理解,就可以简单的理解闭包就是一个定义在函数内部的函数,且可以访问函数里的局部变量的那个函数。
  • 在没有闭包,我们没法访问函数内部的局部变量,有了闭包之后,我们就可以访问函数内部的局部变量了,等同于闭包解决了一个问题,那就是在函数内部和函数外部之间建立了一座桥梁。





3、第三梯队理解

这个时候我们可以看看官方定义的闭包:闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。

再看另一个定义:那么什么是闭包呢?这里有两个定义。在计算机科学中(而不是数学中),一个闭包是一个函数或者一个函数的引用,以及他们所引用的环境信息(就像是一个表,这个表存储了这个函数中引用的每一个没有在函数内声明的变量)。

这两个定义中都有一个概念,第一个里“封闭的作用域”,第二个里“所引用的环境信息”。这里我们都可以用上面的close-over外部函数的变量暂时理解。

也就是闭包总是要有两个部分的:

  • 一部分是一个函数。
  • 另一个部分是被这个函数“包住”的(有的理解为“带走”的,或者是close-over住的)一些环境信息(可以理解环境信息就是变量),但是却不在这个函数中声明的变量表(称之为free variables或者outer variables)。


还有一个不是那么呆的定义:闭包允许你封装一些行为(函数就是行为),像其他对象一样将它传来传去(函数是first-class function),但是不论怎样,它仍然保持着对原来最初上下文的访问能力(它还能访问到 outer variables)。

这个时候的理解就比较抽象了,因为又涉及到作用域的概念,又是一个封闭的作用域。其实上面括号中有一段话(就像是一个表,这个表存储了这个函数中引用的每一个没有在函数内声明的变量),这个表就是在定义这个闭包的“闭”的范围有哪些。




4、第四梯队理解

闭包通过访问外部变量,一个闭包可以维持(keep alive)这些变量。在内部函数和外部函数的例子中,外部函数可以创建局部变量,并且最终退出;但是,如果任何一个或多个内部函数在它退出后却没有退出,那么内部函数就维持了外部函数的局部数据。

从技术上来讲,在JS中,每个function都是闭包,因为它总是能访问在它外部定义的数据。

目前我的水平也就理解到这里了,希望给大家有所帮助。