从两条 “冲突铁律” 切入:彻底搞懂 JavaScript 闭包

131 阅读3分钟

两条冲突的铁律

众所周知,在js的世界中存在着两条冲突铁律:

  • 铁律1: 当一个函数执行完毕后,它的执行上下文会从调用栈中销毁。
  • 铁律2: 一个函数内部的函数,一定有权力访问该外部函数中的变量(内部作用域可以访问外部作用域变量)

铁律带来的问题

当我们在函数内部定义一个函数,并将新定义的函数作为返回值返回。假设被返回的这个函数与原函数内部的属性产生了依赖,那么这个函数的执行过程将会产生冲突。

例子

当两个函数foo与bar写成这个样子时,只按照以上两个铁律的做法将会产生冲突。

function foo() {
    let a = 0;
    function bar() {
        console.log(a);
    }
    return bar
}

let bar = foo(); //foo被执行
bar() //将返回出的bar也执行

1764917695555_21B91126-9239-4ce4-B556-75654F60B1A0.png

理解闭包前必备的知识

词法环境&变量环境

  • 一个函数执行上下文中,通常存在两种对象,变量环境与词法环境
  • 我们通常用变量环境处理函数对 var与函数的声明。
  • 使用词法环境处理let const class 等等变量的声明。
  • 并且它们都存在一个outer指针指向外部作用域的词法环境

未命名文件.png

环境记录

  • 但是实际上,这些声明并没有存储在栈中,而是引用了存储在堆中的 环境记录对象。
  • 并且在js内存管理机制下,变量环境与词法环境引用的是同一个环境记录对象,只是他们负责处理的变量类型不同,一个函数内部所有声明的变量都存在存在同一个环境对象上 环境记录&堆&栈.png

outer指针

  • outer指针存在于变量环境与词法环境中,用于用于指向该函数的外部的词法环境(作用域)
  • 由于 词法环境与变量环境 引用的环境记录对象是同一个,所以可以通过outer指针直接访问外部作用域的所有变量

outer指针.png

真正理解闭包

外层词法环境 + 内存函数 = 闭包

调用一个外部函数中返回的内部函数时,即使外部函数已经执行结束,但是内部函数依然引用了外部函数中的变量,那么外部函数的词法环境将被保留存进堆中,而剩下的外部词法环境与内部函数的组合,我们称之为闭包。

闭包.png

闭包的作用

  • 因为闭包,当外层函数执行上下文出栈后,外层词法环境被调入堆中保留,其包含的环境记录也被保留,outer指针也被保留

词法环境保留.png

  • 当内部函数执行时,内部函数执行上文入栈,也可以正常访问外部词法环境,内部包含的变量与outer指针。由此间接访问外层函数的变量(当然了,因为内存优化机制,只有被内层函数引用的变量得以保留)

依旧.png

闭包的优点

提高了编程灵活度,可以用于实现防抖技术,定义私有变量,封装模块等。

闭包的缺点

留下过多的闭包会导致,内存泄露

闭包过多png.png