JS 解析:闭包 | 豆包MarsCode AI刷题

2 阅读5分钟

前言

JavaScript 作为一种高效的动态脚本语言,广泛应用于前端开发,同时在 Node.js 环境中也成为了后端开发的重要选择。然而,很多开发者在日常使用中,往往只停留在基本的语法和功能层面,而忽略了它背后的一些关键机制。深入理解 JavaScript 的底层运行原理,不仅有助于解决复杂的调试问题,还能够编写出性能更高、行为更可预测的代码。 在这篇文章中,我们将深入解析 JavaScript 的核心概念——闭包。闭包作为 JavaScript 中非常强大的特性,它为我们提供了创建函数内部私有变量和延迟执行的能力,在接下来的内容中,我们将详细介绍闭包的概念、用法及其垃圾回收机制、内存泄漏、以及解决方案,帮助你更深入地理解和掌握这一特性。

闭包是什么?

简单来说:闭包就是一个函数定义在另一个函数内部,形成嵌套函数。

例如:

function fn1() {
    var a = 1
    function fn2() {
        return a++
    }
    return fn2
}
var b = fn1()

在这个例子中,fn2是一个闭包,它可以访问 fn1 内部的变量 a,既然 fn1 已经执行完并返回了结果。

为什么需要闭包?

先来看一个例子:

var a = 1
function addOne() {
    return a++
}
console.log(addOne()); // 1
console.log(addOne()); // 2
console.log(addOne()); // 3

我们现在需要每次调用 addOne(),全局变量 a 就会递增,假设我们现在做一个团队开发的项目,难免会遇到多人分工合作导致使用同名变量,就会造成变量污染。

为了避免这样的情况,我们使用作用域来局限这个变量,这样每个作用域都有自己的变量 a,他们之间不会造成任何影响。

function fn1() {
    var a = 1
    return a++
}
console.log(fn1()); 
console.log(fn1()); 
console.log(fn1()); 

这样一来,a 是函数作用域中的变量,也不会受其他变量的影响。

但同时,问题接踵而至:我们在上面代码中,打印了 3 次,我们预想的结果是 1,2,3。但输出的结果却是:1,1,1,这是为什么呢?

在搞明白原因之前,我们先来搞清楚一个机制:垃圾回收机制

垃圾回收机制:

垃圾回收机制是一种自动管理内存的过程,负责回收不再使用的内存,以避免内存泄漏。开发者不需要手动释放内存,JavaScript 引擎会自动判断哪些内存不再需要并回收它们。

function example() {
    let obj = { name: 'object' };
    console.log(obj.name);  // 'object'
    // 函数执行完成后,obj不再引用,垃圾回收器会释放其占用的内存。
}
example();

也就是说,当我们再次调用该函数时,obj会被重新赋值,然后打印出重新赋值的属性值。

所以这就是为什么a每次输出的都是 1,因为 a 在函数作用域中,是一个局部变量,每次执行完函数后都会触发垃圾回收机制,导致局部变量 a 被清除,而每次重新调用时,a 只能重新赋值,所以每次输出的都是同样的值。

在明白该方案的不足后,我们现在需要一种既不能造成全局污染,又必须让数据长期驻留在内存中的实现方案

解决方案:闭包

function fn1() {
    var a = 1
    function fn2() {
        return a++
    }
    return fn2
}
var b = fn1()
console.log(b()); // 1
console.log(b()); // 2
console.log(b()); // 3

执行结束后,我们发现,打印出来的正是我们想要的结果,我们声明了一个变量 b 作为 fn1() 的引用,fn1()会返回一个函数,也就是闭包函数 fn2(),当我们每次调用 b()时,也就是执行闭包函数 fn2(),fn1() 并不会执行。

同时,外部无法修改内部变量,保证了变量的私有性。

闭包的缺陷

前置知识:内存泄漏

内存泄漏: 是指在程序运行过程中,程序未能及时释放不再使用的内存,导致内存持续占用。在 JavaSript中,有垃圾回收机制的存在,内存管理大多数为自动处理。

但是往往有一些不会导致垃圾回收机制的代码,无法释放内存。

  • 声明的全局变量
  • 没有清除的定时器
  • 闭包导致的内存泄漏

闭包导致的内存泄漏

闭包在不导致变量污染以及触发垃圾回收机制的前提下,使数据长期驻留在内存中,那么,闭包的缺陷也会显现出来,数据长期驻留在内存中,无法释放,就会导致内存泄漏。

解决方案:手动释放内存

function fn1() {
    var a = 1
    function fn2() {
        return a++
    }
    return fn2
}
var b = fn1()
console.log(b()); // 1
console.log(b()); // 2
console.log(b()); // 3

b=null

闭包函数使用完后,将闭包函数的引用赋值为null,自动清除内存。

总结

在本文中,我们深入解析了 JavaScript 中的闭包和垃圾回收机制,并探讨了闭包带来的优势及其潜在的内存泄漏问题。

  • 闭包的概念: 闭包是一个函数可以访问其外部函数作用域中的变量,即使外部函数已经执行完毕。它能够实现私有变量和延迟执行,是 JavaScript 中非常强大的特性。
  • 闭包的优势: 闭包能够避免全局变量的污染,通过函数作用域来保护数据,同时能够保持数据在内存中,允许多个调用之间共享状态。
  • 闭包的缺陷: 由于闭包会让数据长期驻留在内存中,如果不及时释放,会造成内存泄漏。常见的内存泄漏场景包括未清除的全局变量、定时器以及闭包中长期存在的变量。
  • 内存泄漏的解决方案: 在使用闭包时,可以通过手动释放内存(如将闭包的引用设置为 null)来避免内存泄漏。此外,定期清理全局变量和未使用的定时器也是必要的措施。

理解闭包的工作原理及其与垃圾回收机制的交互,能够帮助开发者编写出更高效、行为可预测的 JavaScript 代码,避免不必要的内存占用和性能问题。