javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > js垃圾回收机制

JavaScript垃圾回收机制实现方法

作者:超级无敌大蟑王

垃圾回收的基本思路是确定哪些变量不会再次被使用,然后回收这些变量占用的内存,本文给大家介绍JavaScript垃圾回收机制实现方法,感兴趣的朋友跟随小编一起看看吧

1.垃圾回收的概念

JavaScript使用垃圾自动回收机制进行内存管理,无需程序员手动分配和释放内存。垃圾回收的基本思路是确定哪些变量不会再次被使用,然后回收这些变量占用的内存。垃圾回收过程是周期性的,垃圾回收程序每隔一段时间会运行一次,垃圾回收也会影响到应用程序的性能。常用的垃圾回收机制主要包括标记清除和引用计数。

1.1 什么是垃圾回收机制:

GCGarbage Collection ,程序工作过程中会产生很多"垃圾",这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而 GC 就是负责回收垃圾的,因为他工作在引擎内部,所以对于我们前端来说,GC 过程是相对比较无感的,这一套引擎执行而对我们又相对无感的操作也就是常说的 垃圾回收机制
不是所有语言都有 GC,一般的高级语言里面会自带 GC,比如 Java、Python、JavaScript 等,也有无 GC 的语言,比如 C、C++ 等,那这种就需要我们程序员手动管理内存了,相对比较麻烦
在像 C/C++ 这样的语言中,开发者需要手动分配(malloc)和释放(free)内存。这种方式非常灵活,性能也高,但有两个致命缺点:

JavaScript中存在两种变量:局部变量全局变量。全局变量的生命周期会持续到页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。(不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。)

2.垃圾回收机制是如何实现的

2.1核心理念:

GC 机制的核心思想是可达性。简单来说,就是判断一个对象是否“可达”,如果不可达,它就是“垃圾”。

GC 会有一系列的对象,它们是可达性的起点。在 JavaScript 中,主要的根包括:

全局对象:比如浏览器环境下的 window 对象,Node.js 环境下的 global 对象。
因为全局对象在应用程序的整个生命周期内都存在。只要你的网页开着,window 对象就永远不会消失。它是所有全局变量和内置API(如 setTimeout, localStorage)的宿主。如果它被回收了,整个JavaScript环境就都将崩塌。因此,全局对象是GC最重要、最基础的一个“根”。

函数调用栈:当前正在执行的函数中的局部变量和参数。
因为当前正在执行的代码和它所依赖的数据,理所当然是“存活”的。调用栈代表了程序执行的“此时此刻”。如果这些正在使用的变量被回收了,程序将立即出错。因此,调用栈中所有栈帧里的变量和参数,都被视为临时的“根”。

活跃的 DOM 树:页面上存在的 DOM 元素。
因为DOM节点是构成用户界面的实体。用户能看到、能与之交互的元素,必须始终存在于内存中,浏览器需要依据它来进行绘制和响应事件。因此,所有在DOM树上的节点都被认为是“可达的”。

垃圾回收器会从这些“根”出发,沿着引用链进行遍历。所有能从“根”访问到的对象,都会被认为是“活”的(可达的);反之,所有无法从“根”访问到的对象,就会被认为是“死”的(不可达的),并成为垃圾回收的目标。

// 创建一个根对象,并被变量 user 引用
let user = {
  name: "Alice"
};
// 原来的 { name: "Alice" } 对象失去了引用,因此它变成了不可达对象,等待被回收。
user = null;

2.2主流垃圾回收算法

浏览器通常使用的垃圾回收方法有两种:标记清除引用计数

2.2.1标记清除

这是现代浏览器中最常用的垃圾回收算法。它完美地解决了循环引用的问题。

function createCircularReference() {
  let objA = {};
  let objB = {};
  objA.b = objB; 
  objB.a = objA; 
}
createCircularReference();

2.2.2引用计数

这是早期的一种 GC 算法,思想非常简单。

这种情况下,就要手动释放变量占用的内存:

obj1.a =  null
obj2.a =  null

2.3 V8 引擎的优化:分代回收

为了解决标记-清除算法的效率问题,Google 的 V8 引擎(用于 Chrome 和 Node.js)采用了一种更先进的策略:分代回收
这个策略基于一个重要的观察:“大部分对象都是朝生夕死的”。也就是说,很多对象在创建后很快就不再被使用,而少数对象会存活很长时间。

V8 将堆内存分为两个主要区域:
新生代:Scavenge 算法

3.减少垃圾回收

3.1 手动处理:
虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。

避免意外的全局变量
始终使用 constlet 声明变量,开启严格模式('use strict';)。

function leakyFunction() {
  // 如果没有 'let' 或 'const', a 会被创建为全局变量
  // 它将永远不会被回收,除非手动设为 null
  a = new BigObject();
}

警惕闭包
闭包是 JavaScript 的强大特性,但也很容易造成内存泄漏。闭包可以使其父函数中的变量在函数执行结束后仍然存活。

function createClosure() {
  let largeData = new Array(1e6).fill('*'); 
  // 这个返回的函数持有了对 largeData 的引用
  return function() {
  	...
    return largeData.length;
  };
}
let myClosure = createClosure();
// 即使 createClosure 执行完毕,largeData 也不会被回收,因为它被 myClosure 引用。
// 如果不再需要它,应手动解除引用。
myClosure = null;

定时器和事件监听器
setInterval, setTimeoutaddEventListener 如果不被正确清理,它们的回调函数和其引用的外部变量都不会被回收。

let element = document.getElementById('my-button');
let largeData = new BigObject();
function onClick() {
  // do something with largeData
}
element.addEventListener('click', onClick);
// 正确做法:
// element.removeEventListener('click', onClick);
// element = null;

在组件销毁或元素移除时,务必使用 clearInterval, clearTimeoutremoveEventListener 清理掉相关的定时器和监听器。

面试标准答案(背下来!)
Q:请简述 JavaScript 的垃圾回收机制。

A:
JavaScript 的垃圾回收(GC)是自动内存管理机制,核心是​​标记清除​​和​​引用计数​​。

  • ​​标记清除​​:从根对象(如全局变量)出发,标记所有可达对象,清除未标记的内存。​​优点​​是解决循环引用问题,​​缺点​​是执行时可能短暂卡顿。
  • 引用计数​​:记录每个对象的引用次数,归零时立即回收。​​缺点​​是无法处理循环引用,可能导致内存泄漏。

V8 引擎的优化:

  • 分代回收:新生代(存活时间短)用Scavenge算法,老生代用标记清除/整理。
  • 增量回收:将GC任务拆分成小任务,减少卡顿。。

避免内存泄漏:

  • 避免意外全局变量。
  • 及时清除定时器、事件监听。
  • 谨慎使用闭包引用大对象。
  • 清理无用的 DOM 引用。

到此这篇关于JavaScript垃圾回收机制的文章就介绍到这了,更多相关js垃圾回收机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文