基础 | 🔥闭包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 检查。

相关推荐
user20585561518136 小时前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州6 小时前
CSS aspect-ratio 属性完全指南
前端
Pedantic8 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘8 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆8 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师9 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆9 小时前
VSCode自动格式化三要素
前端
爱勇宝10 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen11 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程