注意!面试时必考的一个js知识点:闭包。

123 阅读6分钟

hello,兄弟们,最近两天没有更新了。这两天有点小忙,但是又不知道忙什么,自己的学习状态有了点小小的问题。现在算是调整好了一点点吧,现在就来和大家分享最新学的知识点——闭包。和我一起学习的小伙伴应该知道我这进度落了不少,我正在加油赶上。

什么是闭包?

闭包(Closure)是JavaScript中的一个核心概念,它涉及到函数的作用域、生命周期以及变量的访问权限。具体来说,闭包是指一个函数可以访问并维持其外部函数作用域中的变量,即便外部函数已经执行完毕。

这样说可能兄弟们还是似懂非懂的,下面我们通过一段代码来辅助我们理解闭包。

function foo(){
    var name = '大仙'
    function bar(){
        console.log(count,age);
    }
    var count = 1
    var age = 18
    return bar
}
var age = 20
const baz = foo();
baz()

这段代码是不是一眼就能看出来应该输出的是1,18 ?那么请大家再仔细看看,到底是不是1和18。

image.png

为了快一点,我将预编译和执行结合在一起了,具体的编译和执行细节还不太了解的可以看看我之前的文章。

根据我们所学的,foo函数在执行完后就会被清除,此时bar还未被调用,而是执行完foo函数后将bar返回出来,所以此时baz=bar然后调用baz即调用了bar,此时bar是是要打印countage,可是此时foo已经结束被销毁了,已经找不到countage变量了,结果应该是报错的。

结果真的是这样嘛?答案肯定不会是这样啊!答案其实就是我们一眼看到就认为的值,但是得到这个结果并没有我们想象的那么简单,当然大家也不要有畏难情绪,其实也不是很难。这里就牵扯到了我们今天要讲的闭包了。

这段代码为什么不报错,为什么又是这个结果呢?其实当函数foo执行完毕后确实是将其从调用栈清除了,但是考虑到他里面声明了个函数还没有调用,于是我们的v8引擎在原来foo的位置上重新给他画了一小块区域用来存放子类函数需要用到的参数。就好比黑帮老大在知道自己即将被扫黑除恶扫掉时,在一个家附近某个埋下一笔钱等着他在国外留学的儿子用。不知道这个比喻咋样,哈哈哈哈,实在想不到了。

image.png

就像这样,在原来的附近打包了count和age,待到调用bar时就可以找到他需要的数据了。我们称这个被打包出来的一个整体为闭包。 稍微正式一点就是:根据js词法作用域规则,内部函数总是能访问外部函数中的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中,我们把这些变量的集合叫做闭包

这个定义有点长,但是通过刚刚的描述应该好记一点了吧!

闭包的作用

我们不仅要了解了闭包是什么,还要了解闭包的作用是什么,不然我们都会觉得闭包并没什么鸟用。闭包的作用主要有以下几个:

  1. 封装模块,防止全局变量污染:通过闭包保持函数内部的变量状态,限制这些变量的访问范围,只允许一些特定的函数访问,减少全局变量的使用。
  2. 实现共有变量(企业的模块开发) 尽管多个函数需要访问相同的变量,但这些变量不会泄露到全局作用域中,从而避免了潜在的命名冲突和数据污染。
  3. 做缓存 用闭包的特性来保存之前计算的结果,避免未来进行相同的计算。

这里我就这些作用举一个例子: 如何实现每调用一次函数实现count加一

var count = 0;
function foo(){
    count++
    return count
}
console.log(foo());
console.log(foo());
console.log(foo());

我们是不是会想到将count设置为一个全局变量,然后通过调用foo函数返回count的值。 这样做其实没有错,但是我们以后肯定是要去大厂敲代码的,到时候几个人十几个人同写一个项目时,你用你的全局变量,我用我的全局变量,那到时候就容易有变量冲突。所以我们要学会封装函数,尽量不用全局变量,这样才能很好的完成合作。这就体现了闭包的作用了!

function add(){
    var count = 0
    function foo(){
        count++
        return count
    }
    return foo
}
var bar = add()
console.log(bar());
console.log(bar());
console.log(bar());

这段代码通过将count封装到闭包里面,保存每一次的count的值,这样实现了每调用一次函数就能够count+1

闭包的缺点

所有的东西都是把双刃剑,就没有十全十美的东西,闭包也是这样。

我们前面在讲闭包时,是说他是清除掉函数的作用域后为了让其内部未被调用的函数被调用时能够正常运行而留下来的小一部分的变量集合。闭包如同作用域一般,是真实存在的,所以一定会占用一小部分内存,当闭包的数量变多时,所占调用栈的空间也会变多,最后导致调用栈的可用空间越来越小,我们称之为内存泄露

小结

闭包应用其实是在我们日常代码中还是比较常见的,如果我们运用好了闭包,那么我们将工作生涯中走的很顺畅。但是想要学好和运用好闭包光靠这一篇文章或者几篇文章是不够的,我们应该要勤加思考,可以在我们平常的代码中思考看能不能使用闭包,怎么使用闭包。相信当我们能够熟练运用闭包时,我们的编程能力将会有大大的提升。加油,我们顶峰相见!