基础 | 🔥闭包99%盲区?内存泄漏炸弹💣已埋!

⚡闭包引发内存泄漏的原理图

graph TD A[外部函数执行] --> B[创建闭包] B --> C[闭包引用外部变量] C --> D[变量无法被垃圾回收] D --> E[内存泄漏]

⚠️闭包不释放 = 内存不回收?

闭包与内存泄漏的关系

闭包(Closure)是 JavaScript 中一个强大但容易引发内存问题的特性。当一个内部函数引用了外部函数的变量时,即使外部函数执行完毕,这些变量也不会被销毁,因为闭包保持着对它们的引用。

🧨原生事件不移除导致内存泄漏

javascript 复制代码
function attachEvent() {
  const element = document.getElementById('button');
  const handler = function() {
    console.log('Button clicked!');
  };
  element.addEventListener('click', handler);
  // ❌ 忘记移除事件监听器
}

问题分析

  • handler 函数通过闭包引用了 attachEvent 的作用域。
  • 即使 attachEvent 执行完毕,elementhandler 之间的引用关系依然存在。
  • 如果 attachEvent 被多次调用(例如在组件重复渲染时),会不断创建新的事件监听器,但旧的监听器未被移除,导致内存中累积了大量无法回收的 DOM 元素和事件处理函数。

🧨其他常见的内存泄漏场景

  1. 定时器未清除

    javascript 复制代码
    let name = 'Jake';
    setInterval(() => {
      console.log(name); // 闭包引用了外部变量 name
    }, 100);
    // ❌ 定时器一直运行,name 永远不会被释放
  2. 意外的全局变量

    javascript 复制代码
    function setName() {
      name = 'Jake'; // 没有使用 var/let/const 声明
    }
    // name 成为 window 的属性,除非页面关闭,否则不会被回收
  3. 被遗忘的 DOM 引用

    javascript 复制代码
    function assignHandler() {
      let element = document.getElementById('someElement');
      element.onclick = () => console.log(element.id); // 闭包引用 element
      // ❌ element 无法被垃圾回收
    }

闭包的定义与使用场景

闭包是指一个函数能够访问并操作其外部作用域中的变量,即使在其外部函数返回之后。

🎯使用场景

  1. 数据封装与私有变量

    javascript 复制代码
    function createCounter() {
      let count = 0; // 私有变量
      return {
        increment() { count++; },
        getCount() { return count; }
      };
    }
    const counter = createCounter();
    counter.increment();
    console.log(counter.getCount()); // 1
  2. 模块模式

    javascript 复制代码
    const myModule = (function() {
      let privateVar = 'secret';
      return {
        getPrivateVar() { return privateVar; }
      };
    })();
    console.log(myModule.getPrivateVar()); // 'secret'
  3. 回调函数与事件处理

    javascript 复制代码
    function setupButton() {
      const button = document.getElementById('myButton');
      let clickCount = 0;
      button.addEventListener('click', function() { // 闭包
        clickCount++;
        console.log(`Clicked ${clickCount} times`);
      });
    }

setInterval 需要注意的点

  1. 及时清除 :使用 clearInterval 清除不再需要的定时器。
  2. 避免内存泄漏:确保定时器回调函数中引用的外部变量能够被正确释放。
  3. this 绑定问题 :在类方法中使用 setInterval 时,注意 this 的指向。

⏱️setTimeout(1)setTimeout(2) 的区别

  • setTimeout(fn, 1)setTimeout(fn, 2) 的区别在于延迟时间。
  • 浏览器有最小延迟限制(通常为 4ms),因此 setTimeout(fn, 1) 实际延迟可能接近 setTimeout(fn, 2)
  • 在现代浏览器中,两者几乎没有实际性能差异。

🧱宏任务与微任务

  • 宏任务(Macro Task) :包括 setTimeoutsetIntervalsetImmediate(Node.js)、I/OUI 渲染 等。
  • 微任务(Micro Task) :包括 Promise.thenMutationObserverqueueMicrotask 等。
  • 执行顺序:事件循环中,先执行宏任务队列中的一个任务,然后执行所有微任务队列中的任务,再进行下一次循环。

🧼PureComponent 与 Function Component

  • PureComponent :是 React 类组件的一个优化版本,它会浅比较 propsstate,如果相同则跳过更新。
  • Function Component :是使用函数定义的组件,配合 React Hooks(如 useState, useEffect)可以实现类组件的大部分功能。

🏹箭头函数与普通函数的区别

  1. this 绑定 :箭头函数没有自己的 this,它会捕获其所在上下文的 this 值。
  2. arguments 对象 :箭头函数没有 arguments 对象。
  3. new.target:箭头函数不能作为构造函数使用。
  4. 原型 :箭头函数没有 prototype 属性。

🔧defineProperty 方法

  • Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
  • 使用场景
    • 精确控制属性的读写行为(getter/setter)。
    • 实现响应式数据(Vue 2.x 的核心原理)。

🔍for..inObject.keys 的区别

  • for..in:遍历对象自身及其原型链上所有可枚举的属性。
  • Object.keys():返回一个数组,包含对象自身所有可枚举的属性名(不包括原型链)。

🛡️闭包特权函数的使用场景

  • 模块化开发:通过闭包创建私有作用域,暴露公共 API。
  • 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
  • 防抖与节流:利用闭包保存状态,控制函数执行频率。

📮GET 与 POST 的区别

特性 GET POST
数据位置 URL 参数 请求体
数据长度限制 有(URL 长度限制)
安全性 较低(数据在 URL 中可见) 较高
幂等性 幂等 非幂等
缓存 可被缓存 不会被缓存
用途 获取数据 提交数据

Q1: 闭包一定会导致内存泄漏吗?

A1: 不一定。只有当闭包持有对不需要的对象的引用时,才会导致内存泄漏。合理使用闭包并及时解除引用可以避免问题。

Q2: 如何避免闭包引起的内存泄漏?

A2: 及时解除对 DOM 元素或大对象的引用,使用 removeEventListener 移除事件监听器,清除定时器等。

Q3: 箭头函数可以解决闭包中的 this 问题吗?

A3: 可以。箭头函数没有自己的 this,它会继承外层作用域的 this,从而避免 this 指向错误。

Q4: Object.defineProperty 在 Vue 2.x 中是如何实现数据劫持的?

A4: Vue 2.x 通过 Object.defineProperty 为每个对象属性设置 getter 和 setter,从而在数据读取和修改时触发视图更新。

Q5: 为什么 for..in 会遍历原型链上的属性?

A5: for..in 的设计就是如此,它会枚举对象自身及原型链上所有可枚举的属性。如果只想遍历自身属性,应使用 Object.keys()hasOwnProperty 检查。

相关推荐
苏格拉没有底了7 分钟前
由频繁创建3D火焰造成的内存泄漏问题
前端
阿彬爱学习8 分钟前
大模型在垂直场景的创新应用:搜索、推荐、营销与客服新玩法
前端·javascript·easyui
我是哪吒12 分钟前
分布式微服务系统架构第164集:架构懂了就来了解数据库存储扩展千亿读写
后端·面试·github
UrbanJazzerati27 分钟前
PowerShell 自动化实战:自动化为 Git Staged 内容添加 Issue 注释标记
后端·面试·shell
橙序员小站34 分钟前
通过trae开发你的第一个Chrome扩展插件
前端·javascript·后端
Lazy_zheng34 分钟前
一文掌握:JavaScript 数组常用方法的手写实现
前端·javascript·面试
是晓晓吖36 分钟前
关于Chrome Extension option的一些小事
前端·chrome
wave77738 分钟前
feign的bean创建过程-底层请求过程-源码走读
后端·面试
MrSkye38 分钟前
🔥从菜鸟到高手:彻底搞懂 JavaScript 事件循环只需这一篇(下)
前端·javascript·面试
方佑38 分钟前
✨ Nuxt 混合渲染实践: MemOS前端体验深度优化指南
前端