JavaScript闭包详解:概念、应用与内存管理

一、闭包的基本概念

闭包(Closure)是指有权访问另一个函数作用域中变量的函数。 换句话说,闭包是一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起形成的组合。

闭包的形成条件:

  1. 存在内、外两层函数。
  2. 内层函数对外层函数的局部变量进行了引用。
  3. 外层函数返回内层函数。
javascript 复制代码
function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // 访问外层函数的变量
  }
  return innerFunc;
}

const closure = outerFunc();
closure(); // 输出 "I am outside!"

在这个例子中,innerFunc函数可以访问outerFunc函数的outerVar变量,并且outerFunc函数返回了innerFunc函数,因此形成了一个闭包。即使在outerFunc函数执行完毕后,closure变量仍然可以访问outerVar变量。

闭包的优缺点:

优点

  • 封装性:闭包可以帮助封装变量,防止全局污染。
  • 持久性:闭包提供了一种将变量保存在内存中的方法,即使外部函数执行完毕,闭包中的变量也不会消失。
  • 模块化:利用闭包可以模拟出私有方法和变量,增强代码的模块化和重用性。

缺点

  • 内存消耗:闭包可能会导致原本已经执行结束的外部函数的变量无法被垃圾回收,从而增加内存使用。
  • 复杂性:过度使用闭包可能会使代码难以理解和维护。

二、闭包的应用场景

模拟私有变量

javascript 复制代码
function createCounter() {
  let count = 0; // 私有变量,只能通过闭包访问
  return {
    increment: function() { 
      count++; // 内层函数可以访问外层函数的变量
    },
    getCount: function() { 
      return count; // 内层函数可以读取外层函数的变量
    }
  };
}

const counter = createCounter();
counter.increment(); // 调用increment方法,count变量加1
console.log(counter.getCount()); // 输出1,访问到了私有变量count

函数柯里化

javascript 复制代码
const add = x => y => z => x + y + z;

console.log(add(1)(2)(3)); // 输出6
// 等同于
const add1 = add(1);
const add2 = add1(2);
const result = add2(3);
console.log(result); // 输出6

防抖和节流

防抖(Debounce)

javascript 复制代码
function debounce(fn, delay) {
  let timer = null; // 使用闭包存储定时器变量
  return function () {
    if (timer) clearTimeout(timer); // 如果定时器存在,清除上一次的定时器
    timer = setTimeout(() => { // 设置新的定时器
      fn.apply(this, arguments); // 延迟执行函数
    }, delay);
  };
}

节流(Throttle)

javascript 复制代码
function throttle(fn, delay) {
  let timer = 0;
  return function() {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, arguments); // 延迟执行函数
      timer = 0; // 执行完毕后重置计时器标志
    }, delay);
  }
}

闭包在设计模式中的应用

发布-订阅模式

javascript 复制代码
function createEventHub() {
  let events = {}; // 使用闭包存储事件对象

  // 订阅事件
  function on(eventName, handler) {
    events[eventName] = events[eventName] || [];
    events[eventName].push(handler);
  }

  // 发布事件
  function emit(eventName, ...args) {
    const handlers = events[eventName];
    if (!handlers) return;
    handlers.forEach(handler => handler(...args));
  }

  return {
    on,
    emit
  };
}

// 使用事件中心
const eventHub = createEventHub();

// 订阅事件
eventHub.on('login', (username) => {
  console.log(`Welcome ${username}!`);
});

// 发布事件
eventHub.emit('login', 'John'); // 输出 "Welcome John!"

在这个例子中,createEventHub函数返回了一个事件中心对象,该对象包含onemit两个方法。通过闭包,事件中心可以在内部维护一个events对象,用于存储事件名称和对应的处理函数。当调用on方法订阅事件时,事件处理函数会被存储到events对象中;当调用emit方法发布事件时,对应的事件处理函数会被依次执行。

三、闭包的内存管理

内存泄漏问题

闭包会引用包含它的外部函数的整个活动对象,如果闭包一直存活,那么外部函数的变量也无法被垃圾回收,从而导致内存泄漏。

解决方法

  1. 及时释放:在不需要闭包时,将闭包引用的变量设置为null,断开闭包和外部变量的连接。
  2. 合理使用:避免在不必要的情况下创建闭包,尤其是在循环或事件监听器中。

通过以上内容的重新组织和代码示例的补充,相信您对JavaScript闭包的理解会更加深入。在面试时,您可以结合这些要点和示例,清晰、全面地阐述闭包的概念、优缺点、应用场景以及如何避免内存泄漏问题。

参考

juejin.cn/post/726418...

相关推荐
待磨的钝刨33 分钟前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
逐·風3 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫4 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦5 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子5 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享6 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
从兄7 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
鱼跃鹰飞7 小时前
大厂面试真题-简单说说线程池接到新任务之后的操作流程
java·jvm·面试
清灵xmf8 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询