JS中什么是闭包?

882 阅读4分钟

写在前面

天空一声巨响,闭包闪亮登场。闭包在JS中是什么重要的,当然也是面试过程中必问的。在这之前我们要弄清楚什么是调用栈?

调用栈

栈,是一种数据结构,那调用栈其实是V8引擎用来管理函数调用关系的一种数据结构,看下面代码:

 var a = 2
 function add() {
    var b = 3
    return a + b
}
 add()

想要分析V8引擎怎样预编译代码,[这就需要我上篇文章讲到的内容](Javascript从作用域,作用域链到预编译 - 掘金 (juejin.cn)) 我们直接用图展示:

无标题.png

当V8引擎执行JS时,它会维护出一个内存空间,在它的运行内存里面会创建出一个栈。但是栈的空间是有限的。代码

function foo() {
    foo()
}
foo()

当V8引擎执行这段代码时,会出现爆栈的情况。但是我们写代码写了很多函数时,这不会出现爆栈的情况,这是因为当一个函数执行完毕后,该函数的执行上下文就会销毁(出栈)

闭包

进入主题,那到底什么是闭包?闭包是怎样形成的?有什么优缺点呢?

定义

当通过调用外部函数返回的内部函数后,即使外部函数已经执行结束了,但是内部函数引用了外部函数的变量依然会保存在内存中,我们把这些集合的变量,称为闭包。

有点抽象?我们直接来看代码:

function a() {
    function b() {
        var bbb = 234
        console.log(aaa);
    }
    var aaa = 123
    return b
}

var demo = a()//闭包
demo()

我们来分析一下,函数a运行结果是返回出了函数b,然后在全局下执行函数b,按道理函数a执行结束了,它的执行上下文会出栈,而函数b要打印定义在a函数里面的aaa,应该找不到会出现报错。

但是此时变量aaa形成了闭包,仍保存在内存中,会打印出结果aaa

上面代码的调用栈图示: 1.png

形成

其实我们从定义就可以得出:当函数A将内部函数B返回出来调用时就形成了闭包。简单来说两个条件:一:函数A将函数B返回出来二:调用返回出来的函数B。注意:一定要调用返回出来的函数才会形成闭包。

扩展一下:

自执行函数: (function(){})()可以立即执行,不需要调用。

自执行函数可以更好地用来形成闭包,想让函数a与某一个函数形成闭包,就可以在函数a外面套一个自执行函数形成调用。

优点

根据闭包的特点,闭包有以下几个优点:

  1. 模块化开放(实现公有变量)
  2. 做缓存
  3. 可以封装私有化属性
function add() {
    let name = 'song'
    let age = 12
    function a() {
        console.log(name);
        console.log(age);
    }
    return a
}
var res = add()
res()

add函数执行完成后出栈,形成了闭包,调用a函数时也可以访问到add中的变量,这就形成了私有化属性nameage

  1. 防止全局变量污染

因为闭包里面的变量都是定义在函数体内的,都在函数作用域里面,属于局部变量。这样不管在全局是否有相同的变量都不会污染到闭包。

缺点

一旦形成闭包,只有在页面关闭后闭包占用的内存才会被回收,所以造成了内存泄漏。

也就是说V8引擎在创建调用栈后总有闭包占有一定的运行空间,这样内存消耗很大,严重可能出现网页卡顿等问题。

所以我们可以在退出函数之前,将不使用的局部变量进行删除

总结

闭包是在开发项目过程中很重要的一种技术,巧妙地利用它的优点可以让我们在开发过程中如鱼得水,我们不仅要学会使用它,而且要用好它。“士不可以不弘毅,任重而道远”,未来的路还很长,大家一起努力。本文可能有些许错误,欢迎大家批评指正。觉得写得不错的话,别忘了点赞收藏哦。