说说我对Javascript闭包的理解

·  阅读 220

我不是软件科班出身,表述会有不严谨的地方,希望大家能够帮忙指正。

说闭包之前,这几个概念需要了解:公有变量,私有变量,作用域

公有变量:大家都能用的变量,最经典的代表就是浏览器中的全局对象window

私有变量:只有某段程序能用的变量,有以下场景

function fn() {
  var word = 'hello'
  console.log(word)
}
复制代码

以上场景中的word变量,只能在fn函数中使用,在fn函数外部是无法访问到的,认为是fn函数的私有变量

作用域:一个变量的有效的区域(空间)

比如window全局变量,到处都能用,它的作用域就是“全局作用域”;

fn中的word变量,有效区域只在fn函数内,它的作用域就是“函数作用域”。

Javascript中,es5版本之下最常用的作用域只有这两种:全局作用域和函数作用域

(eval作用域等不被推荐的用法,就不探讨了)

这里先说说简短的个人理解:利用函数作用域来保护私有变量,并且返回了对被保护变量的引用,这样的行为就是闭包。

闭包的典型例子如下

var greet = (function() {
   var word = 'hello'  //word变量被保护在这个立即执行函数(IIFE)内部

   return function greet() {  //返回一个函数
      console.log(word)  //保持对word的引用
   }
})()

greet() // 'hello'
复制代码

为什么要有闭包?

假设有以下场景,你写了一段程序,这段程序实现了某个功能,它的代码量不小,为了实现这个功能,你写了很多工具函数,引用了许多相关的变量。

这些函数和变量是相互组合使用的,它们是一个功能块,也就是“模块”,这个功能模块,总不能把它写在全局环境下吧?

不对,还是可以写在全局环境下的,反正代码跑起来也没什么问题。

假设你定义了一个变量a,一个函数b,直接写在了全局环境下,假设这个模块实现了功能A

那么,将来的程序员(包括你自己),想要写另外一个功能B

为了实现这个功能B,你也想写一个变量a,函数b,这个时候你就尴尬了,你想起来实现功能A的时候已经用掉了这些命名,不能再使用a、b了

你想到c、d这两个名字,你用了c和d这两个名字,开始调试,结果发现,功能A罢工了!

你检查后发现,原来写功能A的时候也用了c d两个名字,功能B里的命名对功能A产生了干扰。

命名冲突的原因几乎不用解释,但要描述出来的话,就是“当前作用域下已存在同名变量”,而功能相互冲突的原因,也是因为两个功能写在同样的作用域下,要解决以上问题,就要从作用域入手。

这个时候我们应该意识到:一个模块应该有自己的作用域,来保证模块的正常运行

JS除了全局作用域外,能用的只有函数作用域了,所以我们可以用函数作用域来保护这个模块,那么接下来就要用到闭包了,因为闭包本质上就是一个函数作用域

闭包的应用

JS除了全局作用域外,能用的只有函数作用域了,所以你想到用函数作用域来保护这个模块,

假设你使用功能A的时候,用的其实也就是一个函数fnA,

那么你可以在函数作用域中写好了所有流程后,把这个fnA 通过 return语句暴露到外界

有以下代码

function getA() {
   var a = 'xxx'
   var c, d

   function b() {
     // 做了某些不为人知的事情   
   }
   //...
   function fnA() {
     b()
     console.log(a)
     // 剩余的程序...
   }

   return fnA
}

var fnA = getA()
复制代码

这么一来,你的功能A就有了属于自己的独立作用域,里面的a b c d 啊什么东西,都被保护在了getA函数作用域下

不需要担心getA函数执行之后,作用域会销毁的问题,js引擎在检测到私有变量的引用保持之后,会自动保留这个作用域,并且加上一个特别的名字Closure(闭包),也就是说,这样的写法,就是闭包

结论

以上就是闭包的运用,我自己对闭包的看法:利用函数作用域来保护私有变量,并且返回了对被保护变量的引用,这样的行为就是闭包

等开发者在想写一个新功能的时候,不再需要考虑与旧功能命名冲突的问题,也不再需要担心新功能是否会影响旧功能。

关于闭包的成因,深入了解的话,建议搜索 JS的垃圾回收机制JS作用域链

分类:
前端
标签: