我的编程空间,编程开发者的网络收藏夹
学习永远不晚

JavaScript内存管理介绍

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

JavaScript内存管理介绍

本文已经过原作者Ahmad shaded 授权翻译。

大多数时候,我们在不了解有关内存管理的知识下也只开发,因为 JS 引擎会为我们处理这个问题。不过,有时候我们会遇到内存泄漏之类的问题,这个只有知道内存分配是怎样工作的,我们才能解决这些问题。

在本文中,主要介绍内存分配和垃圾回收的工作原理以及如何避免一些常见的内存泄漏问题。

缓存( Memory)生命周期

在 JS 中,当我们创建变量、函数或任何对象时,J S引擎会为此分配内存,并在不再需要时释放它。

分配内存是在内存中保留空间的过程,而释放内存则释放空间,准备用于其他目的。

每次我们分配一个变量或创建一个函数时,该变量的存储会经历以下相同的阶段:

 

分配内存

  • JS 会为我们处理这个问题:它分配我们创建对象所需的内存。

使用内存

  • 使用内存是我们在代码中显式地做的事情:对内存的读写其实就是对变量的读写。

释放内存

  • 此步骤也由 JS 引擎处理,释放分配的内存后,就可以将其用于新用途。

内存管理上下文中的“对象”不仅包括JS对象,还包括函数和函数作用域。

内存堆和堆

栈现在我们知道,对于我们在 JS 中定义的所有内容,引擎都会分配内存并在不再需要内存时将其释放。

我想到的下一个问题是:这些东西将被储存在哪里?

JS 引擎在两个地方可以存储数据:内存堆和堆栈。堆和堆栈是引擎是用于不同目的的两个数据结构。

堆栈:静态内存分配

 

堆栈是 JS 用于存储静态数据的数据结构。静态数据是引擎在编译时能知道大小的数据。在 JS 中,包括指向对象和函数的原始值(strings,number,boolean,undefined和null)和引用类型。

由于引擎知道大小不会改变,因此它将为每个值分配固定数量的内存。

在执行之前立即分配内存的过程称为静态内存分配。这些值和整个堆栈的限制取决于浏览器。

堆:动态内存分配

堆是另一个存储数据的空间,JS 在其中存储对象和函数。

与堆栈不同,JS 引擎不会为这些对象分配固定数量的内存,而根据需要分配空间。这种分配内存的方式也称为动态内存分配。

下面将对这两个存储的特性进行比较:

堆栈
存放基本类型和引用
大小在编译时已知
分配固定数量的内存
对象和函数
在运行时才知道大小
没怎么限制

事例

来几个事例,加强一下映像。

  1. const person = { 
  2.   name'John'
  3.   age: 24, 
  4. }; 

JS 在堆中为这个对象分配内存。实际值仍然是原始值,这就是它们存储在堆栈中的原因。

  1. const hobbies = ['hiking''reading']; 

数组也是对象,这就是为什么它们存储在堆中的原因。

  1. let name = 'John'; // 为字符串分配内存 
  2. const age = 24; // 为字分配内存 
  3.  
  4. name = 'John Doe'; // 为新字符串分配内存 
  5. const firstName = name.slice(0,4); // 为新字符串分配内存 

始值是不可变的,所以 JS 不会更改原始值,而是创建一个新值。

JavaScript 中的引用

所有变量首先指向堆栈。如果是非原始值,则堆栈包含对堆中对象的引用。

堆的内存没有按特定的方式排序,所以我们需要在堆栈中保留对其的引用。我们可以将引用视为地址,并将堆中的对象视为这些地址所属的房屋。

请记住,JS 将对象和函数存储在堆中。基本类型和引用存储在堆栈中。

 

这张照片中,我们可以观察到如何存储不同的值。注意person和newPerson都如何指向同一对象。

事例

  1. const person = { 
  2.   name'John'
  3.   age: 24, 
  4. }; 

这将在堆中创建一个新对象,并在堆栈中创建对该对象的引用。

垃圾回收

现在,我们知道 JS 如何为各种对象分配内存,但是在内存生命周期,还有最后一步:释放内存。

就像内存分配一样,JavaScript引擎也为我们处理这一步骤。更具体地说,垃圾收集器负责此工作。

一旦 JS 引擎识别变量或函数不在被需要时,它就会释放它所占用的内存。

这样做的主要问题是,是否仍然需要一些内存是一个无法确定的问题,这意味着不可能有一种算法能够在不再需要那一刻立即收集不再需要的所有内存。

一些算法可以很好地解决这个问题。我将在本节中讨论最常用的方法:引用计数和标记清除算法。

引用计数

当声明了一个变量并将一个引用类型值赋值该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另外一个变量,则该值得引用次数加1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减1。

当这个值的引用次数变成 0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。

我们看下面的例子。

 

请注意,在最后一帧中,只有hobbies留在堆中的,因为最后引用的是对象。

周期数

引用计数算法的问题在于它不考虑循环引用。当一个或多个对象互相引用但无法再通过代码访问它们时,就会发生这种情况。

  1. let son = { 
  2.   name'John'
  3. }; 
  4.  
  5. let dad = { 
  6.   name'Johnson'
  7.  
  8. son.dad = dad; 
  9. dad.son = son; 
  10.  
  11. son = null
  12. dad = null

 

由于父对象相互引用,因此该算法不会释放分配的内存,我们再也无法访问这两个对象。

它们设置为null不会使引用计数算法识别出它们不再被使用,因为它们都有传入的引用。

标记清除

标记清除算法对循环依赖性有解决方案。它检测到是否可以从root 对象访问它们,而不是简单地计算对给定对象的引用。

浏览器的root是window 对象,而NodeJS中的root是global。

 

该算法将无法访问的对象标记为垃圾,然后对其进行扫描(收集)。根对象将永远不会被收集。

这样,循环依赖关系就不再是问题了。在前面的示例中,dad对象和son对象都不能从根访问。因此,它们都将被标记为垃圾并被收集。

自2012年以来,该算法已在所有现代浏览器中实现。仅对性能和实现进行了改进,算法的核心思想还是一样的。

折衷

自动垃圾收集使我们可以专注于构建应用程序,而不用浪费时间进行内存管理。但是,我们需要权衡取舍。

内存使用

由于算法无法确切知道什么时候不再需要内存,JS 应用程序可能会使用比实际需要更多的内存。

即使将对象标记为垃圾,也要由垃圾收集器来决定何时以及是否将收集分配的内存。

如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。但是请记住,这需要权衡取舍。

性能

收集垃圾的算法通常会定期运行以清理未使用的对象。

问题是我们开发人员不知道何时会回收。收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。

内存泄漏

在全局变量中存储数据,最常见内存问题可能是内存泄漏。

在浏览器的 JS 中,如果省略var,const或let,则变量会被加到window对象中。

  1. users = getUsers(); 

在严格模式下可以避免这种情况。

除了意外地将变量添加到根目录之外,在许多情况下,我们需要这样来使用全局变量,但是一旦不需要时,要记得手动的把它释放了。

释放它很简单,把 null 给它就行了。

  1. window.users = null

被遗忘的计时器和回调

忘记计时器和回调可以使我们的应用程序的内存使用量增加。特别是在单页应用程序(SPA)中,在动态添加事件侦听器和回调时必须小心。

被遗忘的计时器

  1. const object = {}; 
  2. const intervalId = setInterval(function() { 
  3.   // 这里使用的所有东西都无法收集直到清除`setInterval` 
  4.   doSomething(object); 
  5. }, 2000); 

上面的代码每2秒运行一次该函数。如果我们的项目中有这样的代码,很有可能不需要一直运行它。

只要setInterval没有被取消,则其中的引用对象就不会被垃圾回收。

确保在不再需要时清除它。

  1. clearInterval(intervalId); 

被遗忘的回调

假设我们向按钮添加了onclick侦听器,之后该按钮将被删除。旧的浏览器无法收集侦听器,但是如今,这不再是问题。

不过,当我们不再需要事件侦听器时,删除它们仍然是一个好的做法。

  1. const element = document.getElementById('button'); 
  2. const onClick = () => alert('hi'); 
  3.  
  4. element.addEventListener('click', onClick); 
  5.  
  6. element.removeEventListener('click', onClick); 
  7. element.parentNode.removeChild(element); 

脱离DOM引用

内存泄漏与前面的内存泄漏类似:它发生在用 JS 存储DOM元素时。

  1. const elements = []; 
  2. const element = document.getElementById('button'); 
  3. elements.push(element); 
  4.  
  5. function removeAllElements() { 
  6.   elements.forEach((item) => { 
  7.     document.body.removeChild(document.getElementById(item.id)) 
  8.   }); 

删除这些元素时,我们还需要确保也从数组中删除该元素。否则,将无法收集这些DOM元素。

  1. const elements = []; 
  2. const element = document.getElementById('button'); 
  3. elements.push(element); 
  4.  
  5. function removeAllElements() { 
  6.   elements.forEach((item, index) => { 
  7.     document.body.removeChild(document.getElementById(item.id)); 
  8.     elements.splice(index, 1); 
  9.   }); 

由于每个DOM元素也保留对其父节点的引用,因此可以防止垃圾收集器收集元素的父元素和子元素。

总结在本文中,我们总结了 JS 中内存管理的核心概念。写这篇文章可以帮助我们理清一些我们不完全理解的概念。

希望这篇对你有所帮助,我们下期再见,记得三连哦!

作者:Ahmad shaded 译者:前端小智 来源:felixgerschau

 

原文:https://felixgerschau.com/javascript-memory-management/

本文转载自微信公众号「大迁世界」,可以通过以下二维码关注。转载本文请联系大迁世界公众号。

 

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

JavaScript内存管理介绍

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JavaScript内存管理介绍

大多数时候,我们在不了解有关内存管理的知识下也只开发,因为 JS 引擎会为我们处理这个问题。不过,有时候我们会遇到内存泄漏之类的问题,这个只有知道内存分配是怎样工作的,我们才能解决这些问题。

Python内存管理介绍

Python是一门动态的、一切皆对象的语言,这些内存申请可能会产生大量小的内存,为了加快内存操作和减少内存碎片化,使用Python自己的内存管理器,叫PyMalloc。

C++技术中的内存管理:内存管理工具和库的介绍

c++++ 内存管理:内存管理工具:调试器用于识别内存错误;内存分析工具提供内存使用情况见解。内存管理库:智能指针自动管理内存分配和释放,例如 c++11 的 unique_ptr 和 shared_ptr;boost 库提供更丰富的智能指
C++技术中的内存管理:内存管理工具和库的介绍
2024-05-08

Linux内存管理和寻址详细介绍

目录1.概念内存管理模式地址类型划分说明:2.页式管理x86架构32位cpux86架构 64位cpu3.地址划分4. 调试程序寄存器示例一个内核宕机的日志:查看程序寄存器段寄存器有0x23和0x2b两种情况:结语1.概念 内存管理模式 段式
2022-06-04

Linux系统基本的内存管理知识介绍

这篇文章主要介绍“Linux系统基本的内存管理知识介绍”,在日常操作中,相信很多人在Linux系统基本的内存管理知识介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux系统基本的内存管理知识介绍”的疑
2023-06-12

JavaScript 如何管理内存

最近有很多同学在面试中都被问到了 JS在浏览器中的内存管理逻辑,JS 的内存管理本质上是一个非常复杂知识点。所以咱们今天就专门花上几分钟的时间,来看看 JS在浏览器中的内存管理逻辑。

聊聊JavaScript内存管理

大多数时候,我们在不了解有关内存管理的知识下也只开发,因为 JS 引擎会为我们处理这个问题。不过,有时候我们会遇到内存泄漏之类的问题,这个只有知道内存分配是怎样工作的,我们才能解决这些问题。

Linux内存机制的介绍

这篇文章主要介绍“Linux内存机制的介绍”,在日常操作中,相信很多人在Linux内存机制的介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux内存机制的介绍”的疑惑有所帮助!接下来,请跟着小编一起来
2023-06-13

C++new与malloc和delete及free动态内存管理及区别介绍

这篇文章主要介绍了深入理解C++中的new/delete和malloc/free动态内存管理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-12-19

Java内存模型JMM的介绍

这篇文章主要讲解了“Java内存模型JMM的介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java内存模型JMM的介绍”吧!一、为什么要有内存模型在现代多核处理器中,每个处理器都有自己的
2023-06-15

网络管理员教程第五版内容介绍

摘要: 2018年网络管理员教程第五版已经上市,相信考生们已经迫不及待想了解新版教程的内容,那么网络管理员教程第五版主要有哪些内容呢?  2018年软考初级网络管理员教程第五版上市,那么新版教程有哪些变动呢?赶紧跟编程学习网小编一起来了解下网络管理员教程第五版内容简介以及目录吧!  网络管理员教程第五版内容简介 
网络管理员教程第五版内容介绍
2024-04-18

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录