深入理解闭包

73 阅读5分钟

概念

  • 闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript 高级程序设计》
  • 简洁定义: 闭包是指在一个函数内部定义的函数能够访问其外部函数的变量,即使外部函数已经执行完毕并从调用栈中被移除。
  • 技术定义: 闭包是由函数以及其对外部作用域变量的引用共同构成的一个包裹体。在 JS 中,当一个函数能够记住并访问它的词法作用域,即使函数在当前词法作用域之外执行,这个函数与其词法作用域的组合就被称为闭包。
  • 理解:函数执行时形成的私有上下文 EC(FN),正常情况下,代码执行完会出栈后释放;但是特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用了,则上下文不会出栈释放,从而形成不销毁的上下文。 函数执行过程中,会形成一个全新的私有上下文,可能会被释放,可能不会被释放,不论释放与否,他的作用是:
    • 保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,保护自己的私有变量不受外界干扰(操作自己的私有变量和外界没有关系);
    • 保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;
    我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包
  • 稍全面的回答: 在 js 中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

闭包的特性

  1. 内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。

    • 闭包是密闭的容器,,类似于 set、map 容器,存储数据的
    • 闭包是一个对象,存放数据的格式为 key-value 形式
  2. 函数嵌套函数

  3. 本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除

闭包的形成条件

  • 函数的嵌套
  • 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期

闭包的作用

  1. 模仿块级作用域
  2. 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
  3. 封装私有化变量
  4. 创建模块

闭包的常见应用场景

闭包的两个场景,闭包的两大作用:保存/保护。 在开发中, 其实我们随处可见闭包的身影, 大部分前端 JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送 ajax 请求成功|失败的回调;setTimeout 的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。

闭包的注意事项

  • 内存泄漏: 闭包会使得一些不再需要的变量无法被垃圾回收,可能导致内存泄漏。因此,在使用闭包时需要小心管理内存。
  • 性能: 由于闭包保留了对外部变量的引用,过多使用闭包可能会影响性能。
function Foo() {
    var i = 0;
    return function() {
        console.log(i++);
    }
}
var f1 = Foo(),
var f2 = Foo();
f1();
f1();
f2();

输出:0-1-0

  • 创建 f1f2:

    • var f1 = Foo(), var f2 = Foo(); 这一行创建了两个独立的函数 f1f2,每个函数都有自己独立的 i 变量,因为 Foo 每次调用时都会创建一个新的作用域。
    • f1f2 的调用是独立的,并且它们的 i 变量不会互相影响。
  • 执行 f1f2:

    • 第一次调用 f1() : f1 中的 i0 开始,自增后输出 0
    • 第二次调用 f1() : f1 中的 i 已经是 1,自增后输出 1
    • 第一次调用 f2() : 因为 f2 是一个新的闭包,f2 中的 i0 开始,自增后输出 0
  • 每次调用 Foo(),都会创建一个新的执行上下文(或作用域),因此 i 变量在每次调用时都是重新创建的,初始值都是 0

    var f1 = Foo(); // 创建一个新的闭包,f1中的i初始化为0
    var f2 = Foo(); // 创建另一个新的闭包,f2中的i也初始化为0
    
  • f1f2 是两个独立的闭包,它们各自有自己独立的 i 变量。

理解闭包中的变量状态

  • 重要的是理解 f1f2 是两个独立的闭包,它们各自“记住”了 Foo 函数中独立的 i 变量,因此它们不会相互干扰。
  • f1if1() 的调用中自增,而 f2i 是独立的,并且在 f2() 第一次调用时,它的初始值仍然是 0