前端面试-闭包
每次面试都碰到这个问题,就算之前在网上查过资料了,真到自己说的时候,也是稀里糊涂的,能讲清楚真的很难。
闭包是什么?
它不是变量,也不是方法。网上有一个名词叫做闭包函数,但实际上它也不是一个函数。
那它到底是什么呢?
网上搜索,会看到很多解释:
能够读取其他函数内部变量的函数
定义在一个函数内部的函数
持有外部环境变量的函数
每个人都有不同的解释,每个程序员都可以根据自己的理解给出一个定义,五花八门啊。
我也给不出一个准确的定义,因为查遍资料也没办法知道这词到底是从哪里冒出来的。
个人看来,它只是一种概念,或者是一种技巧,是为了使代码编译运行时更加省空间,有效率的一种机制。
为什么使用闭包,怎么使用闭包
这也是一直困扰我的问题,为什么要有这么抽象的概念,为什么要使用这种东西。
举个例子,比如:打开一个页面,页面上做一个计时器,从 0 开始,每一秒钟加一。
let a = 0; //全局变量
let timer = setInterval(function () {
a++;
console.log(a);
if (a === 10) {
clearInterval(timer);
}
}, 1000);
javascript
代码看着没问题,运行也没问题,能够达到预期的结果,但是...
首先解释几个概念:
Js 的执行环境:全局环境,函数环境
- 全局环境:
浏览器打开一个网页就会生成一个全局环境,在全局环境中声明的变量,作用范围是全局,即全局变量,比如代码中的变量 a
全局变量可以在任何地方使用,并且只能在浏览器关闭的时候才可以被销毁。
- 函数环境:
我们把上边的代码整理一下,将计时器内部的操作提出来放到一个方法中,如:
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 等等都会用到,只有真正碰到对应的需求时,才能用到闭包。
具体什么样的需求,这个不好说,很多时候你碰到一个问题,有可能你就用到闭包解决了,但你并不知道自己所使用的方法就是闭包。
怎么说呢,这就是一种解决问题的方法,就算不用闭包也可以有别的解决方法。
就比如我想访问函数内部的某个局部变量,那你就直接在函数里返回怎么了,返回后在另外一个函数内部去调用赋值给另外一个局部变量,同样可以实现调用的方法。
再者说现在计算机性能这么好,也不差那一点半点的内存,只要你自己不乱,那就按你的方式实现就行。
至于闭包这个概念,真的就只是在面试的时候有用,真正到开发的时候,能够有效的解决问题,无论什么方法都是好方法。