js闭包

156 阅读3分钟

定义

闭包是指有权访问另一个函数作用域中的变量的函数。即使该函数执行完毕,其作用域内的变量也不会被销户,闭包可以继续访问这些变量。

形成条件

  • 函数嵌套函数:在一个函数内部定义另一个函数。
  • 内部函数应用外部函数的变量:内部函数使用了外部函数作用域的变量。

示例代码

function outerFunction() {
    let outerVariable = 'I am from outer function';

    function innerFunction() {
        console.log(outerVariable);
    }

    return innerFunction;
}

const closure = outerFunction();
closure(); // 输出: I am from outer function
  • outerFunction 内部定义了 innerFunction,构成了嵌套函数结构。
  • innerFunction 引用了 outerFunction 中的 outerVariable
  • 当 outerFunction 执行完毕后,它返回了 innerFunction,并将其赋值给 closure。此时,尽管 outerFunction 已经执行结束,但由于 innerFunction 形成了闭包,它依旧可以访问 outerVariable

闭包的用途

  • 读取函数内部的变量:借助闭包,能够在函数外部访问函数内部的变量。
  • 使变量的值始终保持在内存中:闭包会持有外部函数的变量,这些变量不会随着外部函数执行结束而被销毁。
  • 封装私有变量和方法:在JavaScript中没有真正意义上的私有变量,可以通过闭包模拟私有变量和方法。

闭包的注意事项

  • 内存泄露风险:由于闭包会持有外部函数变量,这些变量不会随垃圾回收机制回收,若使用不当,可能会导致内存占用过高。
  • 变量引用问题:闭包变量是引用类型,要注意变量的值可能会发生变化。

避免闭包内存泄漏的方法

  1. 及时释放引用:在闭包不使用时,手动将其引用设置为null。
function outer() {
    let largeData = new Array(1000000).fill(1);
    return function inner() {
        console.log(largeData.length);
    };
}

let closure = outer();
closure(); 

// 当闭包不再使用时,释放引用
closure = null;
  1. 避免在循环中使用闭包:如果在循环中使用闭包,闭包会捕获循环变量的引用,可能导致意外的结果或内存问题。可以使用let关键字来创建块级作用域,或者使用立即执行函数表达式(IIFE)。
// 使用 let 关键字
for (let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}

// 使用立即执行函数表达式
for (var i = 0; i < 5; i++) {
    (function(index) {
        setTimeout(() => {
            console.log(index);
        }, 1000);
    })(i);
}
//每次循环都会创建一个新的 IIFE,并且将当前 `i` 的值作为参数 `index` 传递给这个 IIFE。
//在 IIFE 内部,`index` 有自己独立的作用域,它保存了传递进来的 `i` 的值。
//这样,每个 `setTimeout` 的回调函数都会引用自己独立的 `index` 值,从而实现了在 1 秒后依次输出 0 到 4 的预期结果。
//综上所述,IIFE 在这里的作用就是为每个 `setTimeout` 回调函数创建独立的作用域,
//确保它们能够正确地访问不同的变量值,避免了 `var` 作用域带来的问题。
  1. 合理使用闭包:避免在不必要的情况下创建闭包,确保闭包只引用必要的变量。如果闭包引用了大型对象或数组,并且在闭包外部不再使用这些对象,那么这些对象将无法被垃圾回收。
// 不良示例:闭包引用了不必要的大型对象
function badExample() {
    let largeObject = { data: new Array(1000000).fill(1) };
    return function() {
        console.log('This is a closure.');
    };
}

// 改进示例:避免闭包引用不必要的对象
function goodExample() {
    return function() {
        console.log('This is a closure.');
    };
}
  1. 监控内存使用情况:使用浏览器的开发者工具(如 Chrome 的任务管理器和内存分析工具)来监控内存使用情况,及时发现和解决内存泄漏问题。可以定期检查内存快照,比较不同时间点的内存使用情况,找出可能导致内存泄漏的代码。