浅浅认识一下js中的闭包

1,212 阅读4分钟

闭包

  • 根据js词法作用域的规则,内部函数总是能访问外部函数中的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也就需要被保存在内存中,我们把这些变量的集合叫做闭包。

词法作用域

  • 在词法作用域中,变量的作用域由它们在源代码中出现的位置决定,而不是由它们在运行时被引用的位置决定。即一个域所处的环境,是由函数声明的位置来决定的。

闭包的原理

作用域链

  • js引擎在查找变量的时候回先在函数中查找,找不到就会根据outer的指向去到外层作用域中查找,层层往上,这种查找的关系链就称为作用域链 。
  1. 函数在执行前预编译,会创建执行上下文对象。
  2. 变量环境中有一个内定的outer属性用于指明该函数的外层作用域是谁。
  3. outer的指向是根据词法作用域来定的。

变量对象

  • 函数的执行上下文(Execution Context)中包含一个变量对象,它存储了函数的作用域内的所有变量和函数声明。当一个函数形成闭包时,它会保留对其外部执行上下文中的变量对象的引用。

垃圾回收

  • 通常,当一个函数执行完毕,其执行上下文会被销毁(出栈),变量对象也随之消失。但是,如果一个内部函数形成了闭包,那么外部函数的执行上下文将不会被垃圾回收,因为内部函数仍然引用着它的变量对象。

闭包的形成

  • 闭包通常在函数内部定义函数时形成。当内部函数被返回或以某种方式保存下来供以后使用时,它就形成了一个闭包,因为它保持了对其外部作用域中变量的引用,即使外部函数已经完成了执行。

例如:

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

bar函数是一个闭包,此时闭包内的变量分别为count=1age=18。即使foo函数已经执行完毕,bar依然能访问到foo函数作用域中的变量。

bar函数内部试图访问countage时,它首先在其直接的作用域中查找,由于bar是在foo的作用域中定义的,所以它能找到countage,并且它们的值分别是1和18。尽管全局作用域中也有一个age变量,但bar函数不会访问到它,因为它首先找到了foo作用域中的age

因此,当执行baz() 时,控制台上将输出118,这是因为bar函数内部的console.log语句打印出了foo作用域中countage的值。

闭包的作用

优点

  1. 封装私有变量:闭包可以用来创建模块化的代码,保护变量不被外部代码访问,同时提供公共接口来与这些变量交互,放置全局变量污染

  2. 数据持久化:闭包可以保持函数执行之间的状态,即使函数在不同时间点被调用,它仍然可以访问上次调用时的变量值。

  3. 延迟执行:闭包可以用于实现异步编程,如定时器和事件处理器,这些场景下闭包可以捕获调用时刻的变量状态。

  4. 回调函数:在异步操作中,闭包经常用作回调函数,以便在异步操作完成后可以访问调用时的作用域中的变量。

缺点

  1. 内存泄漏:闭包可以长时间保留变量,闭包写得越多,闭包占据的调用栈越多,导致调用栈的可用空间就会越小,因为JavaScript的垃圾回收机制不会回收仍然被引用的变量。

  2. 性能考虑:频繁使用闭包可能会影响性能,因为它们增加了内存的负担,并可能使垃圾回收更为复杂。