Skip to content

JavaScript 闭包

导航目录

什么是闭包

核心定义

核心概念

  • 闭包是函数运行的一种特殊机制
  • 当函数执行时会形成一个私有上下文
  • 如果该上下文中的变量(通常是堆内存地址)被外部事物引用
  • 导致当前上下文无法被释放,就形成了闭包

闭包的双重特性

特性描述影响
保护私有变量与外界隔离避免变量污染
保存上下文中的变量值被保留可被下级上下文使用

闭包的弊端

  • 过度使用会导致内存占用过大
  • 可能引发页面渲染性能问题
  • 极端情况下导致栈溢出内存泄漏

闭包的两种基本形式

1. 返回函数(最常见形式)

核心原理:外部函数返回内部函数,内部函数引用外部函数的变量

javascript
function createCounter() {
  let count = 0; // 私有变量

  return function () {
    count++; // 访问外部变量
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2(count状态被保留)

2. 函数作为参数传递

核心原理:函数作为参数传递时,形成闭包访问外部变量

javascript
function outer(fn) {
  const secret = "confidential";
  fn(); // 内部函数访问外部变量
}

const message = "global message";

function inner() {
  console.log(message); // 访问外部变量
}

outer(inner); // 输出:"global message"

闭包的实际应用场景

1. 防抖与节流函数

应用场景:优化事件处理,减少频繁触发

javascript
// 防抖函数
function debounce(fn, delay) {
  let timer = null;

  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
}

// 使用示例
window.addEventListener(
  "resize",
  debounce(() => {
    console.log("Resize event handled");
  }, 300)
);

2. 模块模式(数据封装)

应用场景:创建私有变量,实现数据封装

javascript
const calculator = (function () {
  let memory = 0; // 私有变量

  return {
    add: function (x) {
      memory += x;
      return this;
    },
    subtract: function (x) {
      memory -= x;
      return this;
    },
    result: function () {
      return memory;
    },
  };
})();

calculator.add(5).subtract(2).result(); // 3

3. React Hooks(函数组件状态管理)

应用场景:在函数组件中管理状态

javascript
import { useState } from "react";

function Counter() {
  // useState 本身就是闭包的典型应用
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

闭包与内存管理

内存泄漏问题

注意

  • 闭包本身不是内存泄漏的原因
  • 问题在于闭包中引用的变量无法被垃圾回收
  • 常见风险场景:
    • 意外保存大型数据结构
    • 循环引用 DOM 元素
    • 未清理的定时器/事件监听器 :::

解决方案

  1. 主动释放引用
javascript
function createHeavyObject() {
  const bigData = new Array(1000000).fill("*");

  return {
    getData: () => bigData,
    // 释放方法
    cleanup: () => (bigData.length = 0),
  };
}

const obj = createHeavyObject();
// 使用后清理
obj.cleanup();
  1. 使用 WeakMap 避免强引用
javascript
const privateData = new WeakMap();

class SecureStore {
  constructor() {
    privateData.set(this, {
      secret: "123-456-789",
    });
  }

  getSecret() {
    return privateData.get(this).secret;
  }
}

// 实例销毁时,关联数据自动回收

闭包性能优化建议

1. 避免在循环中创建闭包

问题:每次循环创建新闭包,可能导致内存占用过高

javascript
// 反例(每次循环创建新闭包)
for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i); // 总是输出10
  }, 100);
}

// 正解(使用IIFE或块级作用域)
for (let i = 0; i < 10; i++) {
  setTimeout(() => console.log(i), 100);
}

2. 最小化闭包范围

优化策略:只在闭包中保留必要的变量,减少内存占用

javascript
function processData(data) {
  // 将不依赖闭包的逻辑移出
  const preprocessed = data.map((item) => item * 2);

  return function () {
    // 只保留必要的闭包逻辑
    return preprocessed.filter((x) => x > 10);
  };
}

3. 使用模块化替代大量小闭包

优化策略:将相关功能组织到一个模块中,减少闭包数量

javascript
// 模块化组织(避免多个小闭包)
const utils = (function () {
  const cache = new Map(); // 私有缓存

  function complexCalculation(x) {
    // 复杂计算逻辑
  }

  return {
    calculate: function (x) {
      if (cache.has(x)) return cache.get(x);
      const result = complexCalculation(x);
      cache.set(x, result);
      return result;
    },
  };
})();

闭包调试技巧

Chrome DevTools 中的闭包识别

  1. 打开开发者工具 → Sources 面板
  2. 在函数内设置断点
  3. 作用域面板中查看 "Closure" 部分
  4. 分析闭包变量及其引用关系

内存快照分析

  1. 使用 Memory 面板获取堆快照
  2. 搜索 "Closure" 查看所有闭包实例
  3. 分析闭包大小和引用关系

闭包的实际应用案例

1. 实现私有方法

应用场景:创建只能通过特定接口访问的私有方法

javascript
const bankAccount = (function () {
  let balance = 0;

  function validateAmount(amount) {
    return typeof amount === "number" && amount > 0;
  }

  return {
    deposit: function (amount) {
      if (validateAmount(amount)) {
        balance += amount;
        return balance;
      }
      return "Invalid amount";
    },
    withdraw: function (amount) {
      if (validateAmount(amount) && amount <= balance) {
        balance -= amount;
        return balance;
      }
      return "Invalid amount or insufficient funds";
    },
    getBalance: function () {
      return balance;
    },
  };
})();

console.log(bankAccount.deposit(100)); // 100
console.log(bankAccount.withdraw(50)); // 50
console.log(bankAccount.getBalance()); // 50

2. 实现单例模式

应用场景:确保一个类只有一个实例

javascript
const Singleton = (function () {
  let instance;

  function createInstance() {
    return {
      message: "I am a singleton",
      method: function () {
        console.log(this.message);
      },
    };
  }

  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    },
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

闭包的注意事项

1. 闭包中的 this 指向

问题:闭包中的 this 指向可能与预期不符

javascript
const obj = {
  value: 42,
  getValue: function () {
    return function () {
      console.log(this.value); // this 指向全局对象
    };
  },
};

obj.getValue()(); // undefined

// 解决方案:使用箭头函数或保存 this 引用
const obj2 = {
  value: 42,
  getValue: function () {
    const self = this;
    return function () {
      console.log(self.value); // 正确指向 obj2
    };
  },
};

obj2.getValue()(); // 42

2. 闭包与变量提升

问题:闭包中访问的变量可能受到变量提升的影响

javascript
function outer() {
  console.log(x); // undefined(变量提升)
  var x = 10;

  return function inner() {
    console.log(x); // 10(闭包访问)
  };
}

const inner = outer();
inner();

总结

核心要点

  • 闭包是 JavaScript 中强大的特性,允许函数访问其创建时的词法作用域
  • 保护和保存是闭包的两大核心特性
  • 合理使用闭包可以创建高效、安全的代码结构
  • 注意内存管理,避免因闭包导致的内存泄漏
  • 性能优化:最小化闭包范围,避免在循环中创建闭包

闭包是 JavaScript 最强大的特性之一,合理使用可以创建高效、安全的代码结构。关键是要理解其工作原理,避免滥用导致性能问题,并在不需要时主动释放资源。