JavaScript中的内存回收机制

2023-11-17

JS的内存回收

在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间。

以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器会识别并释放它们。

而对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以尽量少使用全局变量。

垃圾回收的两种方式

1. 引用计数

引用计数的判断原理很简单,语言引擎有一张"引用表",保存了内存里面所有资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。举个例子:

// 创建一个对象,由变量o指向这个对象的两个属性
var o = {
    name: 'hello',
    handsome: true
};
// name虽然设置为了null,但o依旧有name属性的引用
o.name = null;
var s = o;
// 我们修改并释放了o对于对象的引用,但变量s依旧存在引用
o = null;
// 变量s也不再引用,对象很快会被垃圾回收器释放
s = null;

引用计数存在一个很大的问题,就是对象间的循环引用,比如如下代码中,对象o1与o2相互引用,即便函数执行完毕,垃圾回收器通过引用计数也无法释放它们。

function f() {
    var o1 = {};
    var o2 = {};
    o1.a = o2; // o1 引用 o2
    o2.a = o1; // o2 引用 o1
    return;
};
f();

2. 标记清除

标记清除的概念也好理解,从根部出发看是否能达到某个对象,如果能达到则认定这个对象还被需要,如果无法达到,则释放它,这个过程大致分为三步:

  • 垃圾回收器创建roots列表,roots通常是代码中保留引用的全局变量,在js中,我们一般认定全局对象window作为root,也就是所谓的根部。
  • 从根部出发检查所有 的roots,所有的children也会被递归检查,能从root到达的都会被标记为active。
  • 未被标记为active的数据被认定为不再需要,垃圾回收器开始释放它们。

当一个对象零引用时,我们从根部一定无法到达;但反过来,从根部无法到达的不一定是严格意义上的零引用,比如循环引用,所以标记清除要更优于引用计数。

(自2012年起,所有现代浏览器都使用了标记清除垃圾回收算法,但老版本的IE6除外。)

如何避免内存泄漏

既然已经知道了垃圾回收的原理,那么如何避免创建无法回收的对象,以至造成内存泄漏的尴尬呢?下面说说常见的四种js内存泄漏。

1. 全局变量

尽可能少的去创建全局变量是js开发者的常识,但如下两种方式还是会意外的创建全局变量

第一种是在函数中声明变量未使用var:

function fn() {
    a = 1;
};
fn();
window.a //1

在js中未使用任何修饰符声明变量,该变量默认提升为全局变量 。

第二种是在非严格模式下在函数体内通过this来创建变量:

function fn() {
    this.a = 1;
};
fn();
window.a //1

当直接调用函数fn时,等同于window.fn(),在非严格模式下函数体内的this会指向window,所以本质上还是创建了一个全局变量。 

有时候我们无法避免使用全局变量,那么记得在使用完毕后手动释放它们,例如让变量指向null。

2. 被遗忘的定时器或回调函数

var serverData = loadData();
setInterval(function () {
    var renderer = document.getElementById('renderer');
    if (renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 3000);

在上述代码中,当dom元素renderer被移除时,由于是周期定时器的缘故,定时器回调函数始终无法被回收,这也导致了定时器会一直对数据serverData保持引用,好的做法是在不需要时停止定时器。

在例如我们在使用事件监听时,如果不再需要监听记得移除监听事件。

var element = document.getElementById('button');

function onclick(event) {
    element.innerHTML = 'text';
};

element.addEventListener('click', onclick);
// 移除监听
element.removeEventListener('click', onclick);

3. 闭包

闭包在js开发中是极其常见的,我们来看个例子:

var theThing = null;
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        //unused未执行,但一直保持对theThing的引用
        if (originalThing)
            console.log("hi");
    };
    //创建一个新对象
    theThing = {
        longStr: new Array(1000000).join('*'),
        someMethod: function () {
            console.log("message");
        }
    };
};

setInterval(replaceThing, 1000);

定时器每次调用replaceThing,theThing都会获得一个包含数组longStr与闭包someMethod的新对象。

闭包unused保持着对象originalThing的引用,因为theThing赋值的缘故,也保持了对theThing的引用。

虽然unused没执行,但引用关系会导致originalThing一直无法被回收,那么theThing也一样。

正确做法是在replaceThing 最后添加originalThing  = null;

所以我们常说,对于闭包中的变量,在不需要时一定记得手动释放。

4. DOM的引用

操作dom总是被认为是不好的,但一定得操作,我们的习惯是通过一个变量来存储它,这样就可以反复使用了,但这也会造成一个问题,dom会被引用2次。

var elements = document.getElementById('button')

function doStuff() {
    elements.innerHTML = 'hello aaa';
};
// 清除引用
elements = null;
document.body.removeChild(document.getElementById('button'));

在上述代码中,一次引用是基于dom树的引用,第二是变量elements的引用,当我们不需要这个dom时,都做两次清除操作。 

补充

不管是什么程序语言,内存的声明周期都满足以下三个阶段:

  • 分配你需要的内存空间
  • 使用分配到的内存(读、写)
  • 不需要时将其释放或归还

JS的内存空间 

JavaScript内存空间分为:队列

存放变量,基本类型数据与指向复杂类型数据的引用指针;

存放复杂类型数据;

池(一般也会归类为栈中)又称为常量池,用于存放常量;

队列在任务队列也会使用。

1. 栈空间

具备FILO(first in last out)先进后出的特性,较为经典的就是乒乓球盒结构,先放进去的乒乓球只能最后取出来。

在js中数据类型一般分类基本数据类型(Number Boolean Null Undefined String Symbol)与引用数据类型(Object Array Function),其中栈一般用于存放基本类型数据,例如以下代码在栈内存中分布:

var a = 1;
var b = a;
a = 2;

可以看到基本类型数据的变量名与值都存放在栈内存中,当我们将变量a复制给b时,栈会新开内存用于存放变量b,且当我们修改变量a时对变量b不会造成任何影响,因为a与b是互不相关的两份数据。 

2. 堆空间 

是一种无序的树状结构,同时它还满足key-value键值对的存储方式;我们只用知道key名,就能通过key查找到对应的value。比较经典的就是书架存书的例子,我们知道书名,就可以找到对应的书籍。

在js中堆内存一般用于存储引用类型的数据,需要注意的是由于引用类型的数据一般可以拓展,数据大小可变,所以存放在堆内存中;

但对引用类型数据的引用地址是固定的,所以地址指向还是会存放在栈内存中。

我们通过内存图来模拟以下代码:

var a = [1,2,3];
var b = a;
a.push(4);

 

当我们创建数组a时,栈内存中只保存了变量a与指向堆内存中数组的地址指针,而当我们将a复制给变量b时,其实只是复制了一份地址指针,两者还是指向同一数组,无论谁修改,都会影响彼此。

这便是我们熟知的浅拷贝,若想对浅拷贝与深拷贝有更深了解,可以参考:让你彻底理解浅拷贝和深拷贝的区别_小小1001的博客-CSDN博客_深拷贝和浅拷贝的区别

 3. 队列

队列具有FIFO(First In First Out)先进先出的特性,与栈内存不同的是,栈内存只存在一个出口用于数据进栈出栈;而队列有一个入口与一个出口,理解队列一个较为实际的例子就像我们排队取餐,先排队的永远能先取到餐。

在js中使用队列较为突出的就是js执行机制中的event loop事件循环,如果大家对于js事件执行机制有兴趣,可以参考:JavaScript 执行机制浅谈_火星飞鸟的博客-CSDN博客

本篇文章参考:javascript的内存(垃圾)回收机制

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript中的内存回收机制 的相关文章

  • 声明为对象文字与函数的剔除视图模型之间的区别

    在knockout js中 我看到视图模型声明为 var viewModel firstname ko observable Bob ko applyBindings viewModel or var viewModel function
  • 从 HTML 字符串中解析和选择

    使用 ajax 调用 我返回了页面的partialView HTML 但在显示它之前 我希望从主 div 中提取信息 如果我创建一个浮动窗口 该数据只是大小信息 Code div class window details more data
  • 检测单选按钮/复选框状态的变化

    我需要可靠地检测页面上单选按钮 复选框的状态变化 以便查看表单是否被修改 现在 这是一个完全独立的脚本 我无法修改任何控制表单的内容 目前 我只能看到两种方法 onchange事件处理程序 有助于处理文本框 文本区域和选择 但不会针对复选框
  • 将 Regex 对象分配给 html 输入模式

    我需要以编程方式将正则表达式对象分配给输入元素模式属性 以下是我当前的实现 var regex d 5 element attr pattern regex toString slice 1 1 有没有更好的方法来做到这一点而不需要字符串操
  • JavaScript 字符串中的脚本标签[重复]

    这个问题在这里已经有答案了 我遇到一个问题 即 JavaScript 中带引号的字符串内有结束脚本标记 并且它正在杀死脚本 我认为这不是预期的行为 可以在这里看到这样的示例 http jsbin com oqepe edit http js
  • jQuery:查找具有特定自定义属性的元素

    我只想找到具有特定自定义属性值的元素 例如 我想找一个div其具有属性data divNumber 6 var number 6 var myDiv data divNumber number 我尝试使用http api jquery co
  • 有没有一种方法可以在没有输入的情况下发送表单值

    假设我有以下表格
  • Javascript 将对象推送为克隆

    我将 d3 用于交互式网络应用程序 我需要绑定的数据在交互过程中发生变化 并且由 JSON 变量中的一些选定对象组成 为此 我在 JSON 变量上使用了映射 并进行了一些查询来选择适当的对象 对象被推送到列表中 并且该列表被绑定为新数据 我
  • 从选择 onChange 调用 javascript 函数 [重复]

    这个问题在这里已经有答案了 所以我有一个简单的 HTML 选择框和一个 javascript 警报功能 我希望选择框有一个 onchange 事件来调用 javascript 警报函数 这是我到目前为止所拥有的 HTML div Type
  • 将时间戳转换为一个数组

    在应用程序脚本 谷歌表中运行 我从 API 获取时间戳并返回此结果 1 6370611672429312E18 1 63706107263277082E18 我执行此代码并且工作正常 但问题不在数组中 我每次都需要它在数组中 const t
  • 如何将一个数组中的所有项目复制到另一个数组中?

    如何将数组的每个元素 其中元素是对象 复制到另一个数组中 以便它们完全独立 我不想更改一个数组中的元素来影响另一个数组 这里的关键是 数组中的条目是对象 并且 您不希望对一个数组中的对象的修改显示在另一个数组中 这意味着我们不仅需要将对象复
  • 通过 JavaScript 单击按钮/页面提交

    我想了解 asp net 框架如何知道何时单击了按钮 因此一旦收到请求 就会在服务器上触发其单击事件 我需要了解它是如何工作的 因为我想从 JavaScript 触发按钮的服务器单击事件 我能够从 JavaScript 执行页面提交 doc
  • Firefox Addon 中的 JQuery 导致多个警告

    我在 Firefox 插件中使用 jquery 但我不断收到大量警告消息 如下所示 anonymous function does not always return a value System JS WARNING resource g
  • AngularJS + jQuery 移动

    是否还有其他可能性来设计AngularJS以移动友好的方式应用程序CSS 我正在计划一个移动应用程序 并希望使用 AngularJS 进行逻辑和数据绑定 但我不想自己设计所有内容CSS The AngularJSFAQ说它使用jQuery
  • 播放没有音频标签的音频

    是否可以在没有音频标签的情况下仅使用 javascript 播放音频 我通过 tinyMce 编辑器注入脚本 因为我无权访问网站的后端 并且它不支持客户端的音频标签 她只想要当您将鼠标悬停在图像上时发出简单的声音 我已经完成了所有设置 但是
  • 从链接打开本地文件夹

    如何通过单击任何链接打开本地文件夹视图 我尝试了很多选择 例如 a href Open folder a or a Open folder a or a Open folder a 解决方案 启动可下载链接 以下内容适用于所有浏览器 但一如
  • 如何将MathJax公式转换为img

    Mathjax 现在在我的项目中运行良好 但有一个问题 有没有办法将MathJax的公式 纯html和css 转换成img文件 我可以保存 MathJax 可以配置为生成 SVG 看http docs mathjax org en late
  • 如何在 TypeScript 中使用 navigation.replace ?

    我试图在我的代码中使用它 const navigation useNavigation navigation replace AllFriends 但我不断收到错误消息 Property replace does not exist on
  • 为什么 JavaScript 中是 [1,2] + [3,4] = "1,23,4" ?

    我想将一个数组的元素添加到另一个数组中 所以我尝试了以下方法 1 2 3 4 它的回应是 1 23 4 到底是怎么回事 The 操作员没有为数组定义 发生的事情是 JavaScript将数组转换为字符串并将它们连接起来 Update 由于这
  • 如果 POST 响应仅包含 ID,如何将数据保存到我的 Ember 存储?

    Ember 数据期望我的服务器在每次成功后返回完整的对象POST 但是 我的 API 只返回一种元对象 其中包含id 当 Ember 收到此对象时 记录中的现有数据将被删除 除了id 例如 当我这样做时 var asset App Asse

随机推荐

  • YOLOv5小目标检测(方法与评价)

    问题 当我们在对小目标数据集进行检测时 发现无论如何都有一些漏检的 其中我们也添加一些模块 以及其他的一些改进方法 如注意力 激活函数等等 结果始终不会令人满意 map也没有丝毫的提升 目的 增加对小目标的检测能力 不能产生漏检 自述 许多
  • ARM芯片学习(S5PV210开发)——GPIO控制LED

    1 GPIO介绍 GPIO general purpose input output 通用输入输出 GPIO就是芯片的引脚 是比较特殊的引脚 可以通过代码来操作 控制引脚的高低电平以及工作模式 与GPIO相对的就是固定功能的引脚 我们不能通
  • FPGA笔记8——串口通信(回环实验)

    目录 串口通信原理 串行通信基础知识 处理器与外部设备通信的两种方式 串行通信的通信方式 串行通信的传输方向 常见的串行通信接口 异步串口通信UART基础知识 数据格式 传输速率 接口标准 RS232接口 串口通信实验RS 232 实验任务
  • LeetCode-斐波那契数列

    class Solution public int Fibonacci int n if n 0 return 0 if n 1 return 1 return Fibonacci n 1 Fibonacci n 2 int a 0 b 1
  • 1 RocketMQ简介

    简介 RocketMQ是由阿里捐赠给Apache的一款低延迟 高并发 高可用 高可靠的分布式消息中间件 经历了淘宝双十一的洗礼 RocketMQ既可为分布式应用系统提供异步解耦和削峰填谷的能力 同时也具备互联网应用所需的海量消息堆积 高吞吐
  • 深入解决Linux内存管理之page fault处理

    说明 Kernel版本 4 14 ARM64处理器 Contex A53 双核 使用工具 Source Insight 3 5 Visio 1 概述 内核实现只是在进程的地址空间建立好了vma区域 并没有实际的虚拟地址到物理地址的映射操作
  • 《学会提问》-批判性思维

    这本书名为学会提问 但实际内容是讲解如何训练批判性思维能力 如何通过提问 来质疑别人的观点 选择正确的论证 来形成自己的理性决策 批判性思维的最终结果就是要求一个人虚怀若谷地接纳各种观点 理性评判这些观点 然后在理性判断的基础上决定采取哪些
  • 如何使用python调用电脑麦克录音

    import wave import pyaudio 定义数据流块 CHUNK 1024 FORMAT pyaudio paInt16 CHANNELS 2 RATE 44100 录音时间 RECORD SECONDS 5 要写入的文件名
  • 华为OD机试 C++ 回文字符串

    题目 什么是 回文串 就是一个字符串正着读和反着读都一样 而且要注意大小写的区别 例如 leVel 是一个回文串 因为正着反着都一样 art 就不是 反过来就变成 tra 了 Level 也不是 因为大小写不同 现在 你要做的就是用给定的一
  • NOIP中的数学--第6课 计数原理 (上)

    加法原理与乘法原理 1 加法原理 完成一个工程可以有n类办法 ai代表第i类方法的数目 那么完成这件事共有 S a 1 a 2 a n 种不同的方法 2 乘法原理 完成一个工程需要分n个步骤 ai 代表第i个步骤的不同方法数目 那么完成这件
  • python3 通过 pybind11 使用Eigen加速

    python是很容易上手的编程语言 但是有些时候使用python编写的程序并不能保证其运行速度 例如 while 和 for 这个时候我们就需要借助c 等为我们的代码提速 下面是我使用pybind11调用c 的Eigen库的简单介绍 第一步
  • 实现页面失去焦点十秒后强制执行js

    页面失去焦点后开始倒计时 时间到了执行指定js 适用于 检测页面无操作退出登录 在线答题防止切换页面搜索答案 浏览页面多久后获取奖励等场景
  • 概率论与数理统计--排列组合(一)

    排列 从n个不同元素中 任取m m n m与n均为自然数 下同 个元素按照一定的顺序排成一列 叫做从n个不同元素中取出m个元素的一个排列 从n个不同元素中取出m m n 个元素的所有排列的个数 叫做从n个不同元素中取出m个元素的排列数 用符
  • 118.杨辉三角

    一 题目 118 杨辉三角 力扣 LeetCode 二 代码 class Solution public vector
  • 开关电源环路稳定性分析(2)-从开环到闭环

    大家好 这里是大话硬件 在上一节中 基于欧姆定律 基尔霍夫定律 伏秒平衡这些已知的知识点 可以推导出Buck变换器的输入输出关系 今天这一节 我们还是从全局的概念来解析开关电源 1 运放和开关电源 如果一上来就分析开关电源的环路稳定性 我估
  • Spring Boot中集成Redis

    14 1 简介 redis是一款高性能的NOSQL系列的非关系型数据库 14 1 1 非关系型数据库的优势 1 性能NOSQL是基于键值对的 可以想象成表中的主键和值的对应关系 而且不需要经过SQL层的解析 所以性能非常高 2 可扩展性同样
  • 华为三层交换机STP配置

    学习目的 掌握启用和关闭STP的方法 了解不同的STP模式的差异 掌握修改网桥优先级影响根网桥选举的方法 掌握修改端口优先级影响根端口与指定端口选举的方法 掌握配置边缘的方法 拓扑图 场景 你是公司的网络管理员 公司的网络使用了两层网络结构
  • Redis基础--认识redis和对比同类型产品

    一 redis定义与应用 Nosql定义 NoSQL是不同于传统的关系数据库的数据库管理系统的统称 其两者最重要的区别是NoSQL不使用SQL作为查询语言 MySQL定义 MySQL是一种关系型数据库 关系型数据库的一个常见用法是存储长期的
  • 如何大批量压缩图片

    一 ImageMagick ImageMagick 是一个功能强大的命令行图像处理工具 可以用于批量处理图片 它支持各种图像格式和操作 包括压缩和优化 二 使用 ImageMagick 进行大批量压缩指定路径的图片 你可以通过以下步骤实现
  • JavaScript中的内存回收机制

    JS的内存回收 在js中 垃圾回收器每隔一段时间就会找出那些不再使用的数据 并释放其所占用的内存空间 以全局变量和局部变量来说 函数中的局部变量在函数执行结束后这些变量已经不再被需要 所以垃圾回收器会识别并释放它们 而对于全局变量 垃圾回收