面试官:讲一下闭包?内存泄露场景?循环引用为什么导致内存泄露?怎么判断是否存在循环引用?

开发 前端
这篇文章就跟大家详细的说一下关于闭包的问题,争取可以做到让大家看完这篇文章之后,对比闭包的问题可以顺畅回答!

Hello,大家好,我是 Sunday。

在最近的中小厂面试中,【闭包】的问题被很多公司提到。如果单纯说闭包是比较简单的,一句话就可以说清楚:“可以访问其他函数作用域中变量的函数,就是闭包函数”。

但是,随后延伸的问题,如:闭包造成内存泄漏的场景、循环引用为什么导致内存泄露?怎么判断是否存在循环引用? 等问题,很多同学回答的并不好。

因此,这篇文章就跟大家详细的说一下关于闭包的问题,争取可以做到让大家看完这篇文章之后,对比闭包的问题可以顺畅回答!

1. 什么是闭包

闭包是指 函数在创建时保留了对其定义作用域的引用,即使函数执行在其词法作用域之外,也能访问该作用域中的变量。

闭包在 JavaScript 中的常见表现形式是:函数嵌套函数,内部函数访问外部函数的变量。

由于 JavaScript 的函数是“第一类公民”,可以作为值返回、传递或保存,因此在外部函数返回后,闭包依然保留对外部变量的访问权限。

function outerFunction() {
  let counter = 0;
  
  return function innerFunction() {
    counter++;
    console.log(counter);
  };
}

const increment = outerFunction();
increment(); // 输出: 1
increment(); // 输出: 2

在上述代码中,innerFunction 是一个闭包,它可以访问 outerFunction 中的变量 counter,即使 outerFunction 已经执行完毕。

2. 闭包导致的内存泄露场景

在 JS 中,闭包有时会导致内存泄露,这是因为:闭包在访问外部作用域的变量时会让这些变量无法被垃圾回收,从而导致不必要的内存占用。

2.1. 常见的内存泄露场景

  • 未清理的事件监听:如果事件监听器引用了外部作用域中的变量,且在不需要时未移除,则会导致闭包一直存在,无法释放内存。
function addEvent() {
  const element = document.getElementById('button');
  const someData = "Important data";
  element.addEventListener('click', function() {
    console.log(someData); // 闭包引用了外部变量 someData
  });
}

addEvent();
// 这里如果不手动移除事件监听器,则 someData 永远不会被释放,造成内存泄露
  • 定时器未清理:在定时器的回调函数中使用了闭包,但在不再需要时未清除定时器,导致回调函数及其引用的外部变量无法被回收。
function createTimer() {
  const largeData = new Array(10000).fill('*');
  setInterval(function() {
    console.log(largeData); // 定时器闭包持有 largeData 的引用
  }, 1000);
}

createTimer();
// 这里如果不清除定时器,largeData 将永远无法释放

3. 循环引用导致内存泄露

循环引用是指:两个或多个对象相互引用,从而形成一个循环结构,导致垃圾回收器无法回收这些对象。

3.1. 为什么循环引用会导致内存泄露?

JS 的垃圾回收机制使用 标记清除(mark-and-sweep) 算法。即:垃圾回收器会从根对象(如全局对象)出发,查找所有可达对象。

若对象形成了循环引用,且不再被根对象访问,则垃圾回收器无法将其清除,这会导致这些对象长期保留在内存中,形成内存泄露。

function createCircularReference() {
  const objectA = {};
  const objectB = {};
  objectA.ref = objectB; // objectA 引用 objectB
  objectB.ref = objectA; // objectB 引用 objectA,形成循环引用
}

createCircularReference();
// 这里 objectA 和 objectB 都无法被回收

在这个示例中,objectA 和 objectB 互相引用,形成了循环引用。如果没有外部引用它们,按理说可以被垃圾回收,但由于相互持有的引用,导致它们无法被清除,形成内存泄露。

4. 如何检测循环引用

在项目中,如果出现 内存泄漏 的问题,那么可以通过以下方式进行检查:

  • 手动检测:在代码中通过逻辑分析或使用 console.log 输出检查对象的相互引用关系。
  • 使用开发者工具检测:现代浏览器的开发者工具提供了内存快照和堆分析,可以捕获内存快照来分析内存的使用情况,帮助发现循环引用和内存泄露。在 Chrome 开发者工具中,可以通过 Memory(内存) 面板,使用 Heap Snapshot(堆快照)来查看对象的引用关系,并检查是否有意外的循环引用。

图片图片

  • JSON.stringify 检测:尝试使用 JSON.stringify 序列化对象,如果对象中存在循环引用,JSON.stringify 会抛出 TypeError 异常,可以用这种方式简单检测循环引用(注意这种方法只能用于检测较简单的循环引用,复杂场景需结合其他方法)。
function hasCircularReference(obj) {
  try {
    JSON.stringify(obj);
    return false; // 无循环引用
  } catch (error) {
    return true; // 有循环引用
  }
}

const objectA = {};
const objectB = { ref: objectA };
objectA.ref = objectB;

console.log(hasCircularReference(objectA)); // 输出: true
  1. WeakMap 弱引用:使用 WeakMap 结构管理对象引用。由于 WeakMap 的键是弱引用,不会影响对象的垃圾回收,可以通过 WeakMap 追踪对象引用关系,并避免循环引用导致的内存泄露。

5. 如何避免循环引用导致的内存泄露

如果检测出现内存泄漏的问题,那么可以通过以下方式尝试解决:

  • 避免对象互相引用:在设计数据结构时,尽量避免互相引用,尤其是大的复杂对象。
  • 使用 WeakMap 或 WeakSet:在 JavaScript 中,WeakMap 和 WeakSet 是弱引用结构,存储在 WeakMap 或 WeakSet 中的对象不会被阻止垃圾回收。可以使用 WeakMap 和 WeakSet 来存储对象之间的引用关系,避免循环引用导致的内存泄露。
const weakMap = new WeakMap();
const objectA = {};
const objectB = {};

weakMap.set(objectA, objectB);
  • 在不需要时手动断开引用:当对象不再使用时,可以手动将引用设为 null 或 undefined,确保垃圾回收器能够正常回收它们。
let objectA = {};
let objectB = {};
objectA.ref = objectB;
objectB.ref = objectA;

// 当不再需要时,断开引用关系
objectA.ref = null;
objectB.ref = null;


责任编辑:武晓燕 来源: 程序员Sunday
相关推荐

2017-11-15 19:30:08

Python内存泄露循环引用

2017-12-11 11:00:27

内存泄露判断

2016-09-08 16:16:26

iOS移动应用内存泄漏

2022-06-07 12:03:33

Java内存模型

2021-12-20 10:30:33

forforEach前端

2021-10-27 07:15:36

Go 循环引用

2024-03-13 07:53:57

弱引用线程工具

2022-10-18 08:38:16

内存泄漏线程

2020-08-07 15:15:01

Java内存泄漏面试

2024-02-22 15:36:23

Java内存模型线程

2021-05-27 21:47:12

Python垃圾回收

2023-09-12 14:56:13

MyBatis缓存机制

2021-04-19 18:56:58

大数字符串运算

2017-05-04 16:07:11

Tomcat内存泄露

2021-07-28 10:08:19

类加载代码块面试

2023-11-15 09:14:27

Java值传递

2010-08-10 10:00:57

Flex内存

2013-04-09 14:49:18

Linux内存统计内存泄露

2013-08-07 10:07:07

Handler内存泄露

2022-10-10 11:37:14

Gomap内存
点赞
收藏

51CTO技术栈公众号