前端面试-闭包

July 30, 2022

每次面试都碰到这个问题,就算之前在网上查过资料了,真到自己说的时候,也是稀里糊涂的,能讲清楚真的很难。

闭包是什么?

它不是变量,也不是方法。网上有一个名词叫做闭包函数,但实际上它也不是一个函数。

那它到底是什么呢?

网上搜索,会看到很多解释:

能够读取其他函数内部变量的函数

定义在一个函数内部的函数

持有外部环境变量的函数

每个人都有不同的解释,每个程序员都可以根据自己的理解给出一个定义,五花八门啊。

我也给不出一个准确的定义,因为查遍资料也没办法知道这词到底是从哪里冒出来的。

个人看来,它只是一种概念,或者是一种技巧,是为了使代码编译运行时更加省空间,有效率的一种机制。

为什么使用闭包,怎么使用闭包

这也是一直困扰我的问题,为什么要有这么抽象的概念,为什么要使用这种东西。

举个例子,比如:打开一个页面,页面上做一个计时器,从 0 开始,每一秒钟加一。

let a = 0; //全局变量 let timer = setInterval(function () { a++; console.log(a); if (a === 10) { clearInterval(timer); } }, 1000);
javascript

代码看着没问题,运行也没问题,能够达到预期的结果,但是...

首先解释几个概念:

Js 的执行环境:全局环境,函数环境

  1. 全局环境:

浏览器打开一个网页就会生成一个全局环境,在全局环境中声明的变量,作用范围是全局,即全局变量,比如代码中的变量 a

全局变量可以在任何地方使用,并且只能在浏览器关闭的时候才可以被销毁。

  1. 函数环境:

我们把上边的代码整理一下,将计时器内部的操作提出来放到一个方法中,如:

let a = 0; //全局变量 let timer = setInterval(function () { countNum(); }, 1000); function countNum() { let b = 20; a++; console.log(a); if (a === 10) { clearInterval(timer); } }
javascript

countNum 方法内部环境就是函数环境,在函数环境中声明的变量,作用范围仅限于函数环境内部,在方法外不能访问,即局部变量,比如方法中的变量 b

需要注意的是,函数执行完毕后,会立马被销毁,包括函数内部所有的东西都会被回收,节省资源。

全局变量在任何地方都可以使用,所以很容易被污染,并且除了关闭浏览器,否则不能回收。项目做大后,如果全局变量很多,容易造成负担。

所以,对于计时的操作,我们最想要的结果就是将变量放在对应的计时函数内部来管理,即:

let timer = setInterval(function () { countNum(); }, 1000); function countNum() { let a = 0; a++; console.log(a); if (a === 10) { clearInterval(timer); } }
javascript

当我们把全局变量 a 放到函数内部变成局部变量后,在 countNum 函数外的任何地方都无法访问到 a,对 a 也是一种保护,不会被函数外的操作污染。

但是,如果就是想要在函数外某个地方访问一下 a 怎么办?

这样一来,网络上关于闭包的定义就出现了:能够读取其他函数内部变量的函数

首先,我并不赞同这个定义,而且我认为这种要求很怪,若是你想要在外边访问变量,那变量的声明和定义写在外边就好了。

局部变量存在的意义就是不想要让其他的操作影响变量本身。

方法的声明定义,就是要将某个功能以及要用到的资源封装在一起,需要的时候就拿来用,不需要的时候可以立马消失不占地。

但有时候,我们封装功能时,会碰到一些问题。

就好比我们现在这个需求,改成局部变量后,问题就出现了,因为 a 不能累加了,每次执行 countNum,a 都会被重置为 0。

这时候我们需要用到闭包来解决这个问题,所以说闭包的意义就在于能够让这种封装更加完美,实现低预算高产出的结果

怎么做呢?

let count = countNum(); let timer = setInterval(function () { count(); }, 1000); function countNum() { let a = 0; function innerCount() { a++; console.log(a); if (a === 10) { clearInterval(timer); } } return innerCount; }
javascript

Js 允许函数内部声明定义另一个函数,innerCount 也形成一个函数环境,有自己的私有作用域,并且可以使用 countNum 作用域内声明的变量 a

内部环境的可以访问外部环境的变量,反之不行,就是所谓的:单向作用域链。

类似于上述嵌套作用域的情况,当内部的作用域使用了外部的变量时,那么 countNum 被执行后不能立马销毁

只有当内部函数用完了,即 innerCount 的调用结束,countNum 和 innerCount 才会同时被回收。

countNum()执行后返回 innerCount,并且赋值给 count,所以只有当 count 执行完毕之后才会销毁 countNum,也就是说并不会重复执行 let a = 0,可以实现计时的功能。

网上有人说 innerCount 就是闭包,其实并不准确

其实整个功能被实现的方法,或者说是技巧才是闭包的本质

在哪使用?

闭包并不是 Js 的专属,php, python 等等都会用到,只有真正碰到对应的需求时,才能用到闭包。

具体什么样的需求,这个不好说,很多时候你碰到一个问题,有可能你就用到闭包解决了,但你并不知道自己所使用的方法就是闭包。

怎么说呢,这就是一种解决问题的方法,就算不用闭包也可以有别的解决方法。

就比如我想访问函数内部的某个局部变量,那你就直接在函数里返回怎么了,返回后在另外一个函数内部去调用赋值给另外一个局部变量,同样可以实现调用的方法。

再者说现在计算机性能这么好,也不差那一点半点的内存,只要你自己不乱,那就按你的方式实现就行。

至于闭包这个概念,真的就只是在面试的时候有用,真正到开发的时候,能够有效的解决问题,无论什么方法都是好方法。