首页 > 系统相关 >JavaScript内存管理

JavaScript内存管理

时间:2023-11-04 11:44:54浏览次数:42  
标签:name 管理 对象 JavaScript 回收 let 内存 垃圾

在使用垃圾回收的编程环境中,开发者通常无须关心内存管理。不过,JavaScript 运行在一个内存

管理与垃圾回收都很特殊的环境。分配给浏览器的内存通常比分配给桌面软件的要少很多,分配给移动

浏览器的就更少了。这更多出于安全考虑而不是别的,就是为了避免运行大量 JavaScript 的网页耗尽系

统内存而导致操作系统崩溃。这个内存限制不仅影响变量分配,也影响调用栈以及能够同时在一个线程

中执行的语句数量。

将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行

代码时只保存必要的数据。如果数据不再必要,那么把它设置为 null,从而释放其引用。这也可以叫

作解除引用。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用,

如下面的例子所示:

function createPerson(name){

let localPerson = new Object();

localPerson.name = name;

return localPerson;

}

let globalPerson = createPerson("Nicholas");

// 解除 globalPerson 对值的引用

globalPerson = null;

在上面的代码中,变量 globalPerson 保存着 createPerson()函数调用返回的值。在 createPerson()

内部,localPerson 创建了一个对象并给它添加了一个 name 属性。然后,localPerson 作为函数值

被返回,并被赋值给 globalPerson。localPerson 在 createPerson()执行完成超出上下文后会自

动被解除引用,不需要显式处理。但 globalPerson 是一个全局变量,应该在不再需要时手动解除其

引用,最后一行就是这么做的。

不过要注意,解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关

的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。

  1. 通过 const let 声明提升性能

ES6 增加这两个关键字不仅有助于改善代码风格,而且同样有助于改进垃圾回收的过程。因为 const

和 let 都以块(而非函数)为作用域,所以相比于使用 var,使用这两个新关键字可能会更早地让垃圾回

收程序介入,尽早回收应该回收的内存。在块作用域比函数作用域更早终止的情况下,这就有可能发生。

  1. 隐藏类和删除操作

根据 JavaScript 所在的运行环境,有时候需要根据浏览器使用的 JavaScript 引擎来采取不同的性能优

化策略。截至 2017 年,Chrome 是最流行的浏览器,使用 V8 JavaScript 引擎。V8 在将解释后的 JavaScript

代码编译为实际的机器码时会利用“隐藏类”。如果你的代码非常注重性能,那么这一点可能对你很

重要。

运行期间,V8 会将创建的对象与隐藏类关联起来,以跟踪它们的属性特征。能够共享相同隐藏类

的对象性能会更好,V8 会针对这种情况进行优化,但不一定总能够做到。比如下面的代码:

function Article() {

this.title = 'Inauguration Ceremony Features Kazoo Band';

}

let a1 = new Article();

let a2 = new Article();

V8 会在后台配置,让这两个类实例共享相同的隐藏类,因为这两个实例共享同一个构造函数和原

型。假设之后又添加了下面这行代码:

a2.author = 'Jake';

此时两个 Article 实例就会对应两个不同的隐藏类。根据这种操作的频率和隐藏类的大小,这有

可能对性能产生明显影响。

当然,解决方案就是避免 JavaScript 的“先创建再补充”(ready-fire-aim)式的动态属性赋值,并在

构造函数中一次性声明所有属性,如下所示:

function Article(opt_author) {

this.title = 'Inauguration Ceremony Features Kazoo Band';

this.author = opt_author;

}

let a1 = new Article();

let a2 = new Article('Jake');

这样,两个实例基本上就一样了(不考虑 hasOwnProperty 的返回值),因此可以共享一个隐藏类,

从而带来潜在的性能提升。不过要记住,使用 delete 关键字会导致生成相同的隐藏类片段。看一下这

个例子:

function Article() {

this.title = 'Inauguration Ceremony Features Kazoo Band';

this.author = 'Jake';

}

let a1 = new Article();

let a2 = new Article();

delete a1.author;

在代码结束后,即使两个实例使用了同一个构造函数,它们也不再共享一个隐藏类。动态删除属性

与动态添加属性导致的后果一样。最佳实践是把不想要的属性设置为 null。这样可以保持隐藏类不变

和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。比如:

function Article() {

this.title = 'Inauguration Ceremony Features Kazoo Band';

this.author = 'Jake';

}

let a1 = new Article();

let a2 = new Article();

a1.author = null;

  1. 内存泄漏

写得不好的 JavaScript 可能出现难以察觉且有害的内存泄漏问题。在内存有限的设备上,或者在函

数会被调用很多次的情况下,内存泄漏可能是个大问题。JavaScript 中的内存泄漏大部分是由不合理的

引用导致的。

意外声明全局变量是最常见但也最容易修复的内存泄漏问题。下面的代码没有使用任何关键字声明

变量:

function setName() {

name = 'Jake';

}

此时,解释器会把变量 name 当作 window 的属性来创建(相当于 window.name = 'Jake')。

可想而知,在 window 对象上创建的属性,只要 window 本身不被清理就不会消失。这个问题很容易

解决,只要在变量声明前头加上 var、let 或 const 关键字即可,这样变量就会在函数执行完毕后离

开作用域。

定时器也可能会悄悄地导致内存泄漏。下面的代码中,定时器的回调通过闭包引用了外部变量:

let name = 'Jake';

setInterval(() => {

console.log(name);

}, 100);

只要定时器一直运行,回调函数中引用的 name 就会一直占用内存。垃圾回收程序当然知道这一点,

因而就不会清理外部变量。

使用 JavaScript 闭包很容易在不知不觉间造成内存泄漏。请看下面的例子:

let outer = function() {

let name = 'Jake';

return function() {

return name;

};

};

调用 outer()会导致分配给 name 的内存被泄漏。以上代码执行后创建了一个内部闭包,只要返回

的函数存在就不能清理 name,因为闭包一直在引用着它。假如 name 的内容很大(不止是一个小字符

串),那可能就是个大问题了。

  1. 静态分配与对象池

为了提升 JavaScript 性能,最后要考虑的一点往往就是压榨浏览器了。此时,一个关键问题就是如

何减少浏览器执行垃圾回收的次数。开发者无法直接控制什么时候开始收集垃圾,但可以间接控制触发

垃圾回收的条件。理论上,如果能够合理使用分配的内存,同时避免多余的垃圾回收,那就可以保住因

释放内存而损失的性能。

浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。如果有很多对象被初始化,然

后一下子又都超出了作用域,那么浏览器就会采用更激进的方式调度垃圾回收程序运行,这样当然会影

响性能。看一看下面的例子,这是一个计算二维矢量加法的函数:

function addVector(a, b) {

let resultant = new Vector();

resultant.x = a.x + b.x;

resultant.y = a.y + b.y;

return resultant;

}

调用这个函数时,会在堆上创建一个新对象,然后修改它,最后再把它返回给调用者。如果这个

矢量对象的生命周期很短,那么它会很快失去所有对它的引用,成为可以被回收的值。假如这个矢量

加法函数频繁被调用,那么垃圾回收调度程序会发现这里对象更替的速度很快,从而会更频繁地安排

垃圾回收。

该问题的解决方案是不要动态创建矢量对象,比如可以修改上面的函数,让它使用一个已有的矢量

对象:

function addVector(a, b, resultant) {

resultant.x = a.x + b.x;

resultant.y = a.y + b.y;

return resultant;

}

当然,这需要在其他地方实例化矢量参数 resultant,但这个函数的行为没有变。那么在哪里创

建矢量可以不让垃圾回收调度程序盯上呢?

一个策略是使用对象池。在初始化的某一时刻,可以创建一个对象池,用来管理一组可回收的对象。

应用程序可以向这个对象池请求一个对象、设置其属性、使用它,然后在操作完成后再把它还给对象池。

由于没发生对象初始化,垃圾回收探测就不会发现有对象更替,因此垃圾回收程序就不会那么频繁地运

行。下面是一个对象池的伪实现:

// vectorPool 是已有的对象池

let v1 = vectorPool.allocate();

let v2 = vectorPool.allocate();

let v3 = vectorPool.allocate();

v1.x = 10;

v1.y = 5;

v2.x = -3;

v2.y = -6;

addVector(v1, v2, v3);

console.log([v3.x, v3.y]); // [7, -1]

vectorPool.free(v1);

vectorPool.free(v2);

vectorPool.free(v3);

// 如果对象有属性引用了其他对象

// 则这里也需要把这些属性设置为 null

v1 = null;

v2 = null;

v3 = null;

如果对象池只按需分配矢量(在对象不存在时创建新的,在对象存在时则复用存在的),那么这个

实现本质上是一种贪婪算法,有单调增长但为静态的内存。这个对象池必须使用某种结构维护所有对

象,数组是比较好的选择。不过,使用数组来实现,必须留意不要招致额外的垃圾回收。比如下面这

个例子:

let vectorList = new Array(100);

let vector = new Vector();

vectorList.push(vector);

由于 JavaScript 数组的大小是动态可变的,引擎会删除大小为 100 的数组,再创建一个新的大小为

200 的数组。垃圾回收程序会看到这个删除操作,说不定因此很快就会跑来收一次垃圾。要避免这种动

态分配操作,可以在初始化时就创建一个大小够用的数组,从而避免上述先删除再创建的操作。不过,

必须事先想好这个数组有多大。

注意 静态分配是优化的一种极端形式。如果你的应用程序被垃圾回收严重地拖了后腿,

可以利用它提升性能。但这种情况并不多见。大多数情况下,这都属于过早优化,因此不

用考虑。

标签:name,管理,对象,JavaScript,回收,let,内存,垃圾
From: https://www.cnblogs.com/dlx609/p/17809115.html

相关文章

  • JavaScript如何定义类与函数如何实现继承自Object类实现方法------前端
    HTML页面用于展示<!DOCTYPEhtml><!--这是HTML的注释--><htmllang="en"id="myHtml"> <head> <!--这里不是设置了编码,而是告诉浏览器,用什么编码方式打开文件避免乱码--> <metacharset="UTF-8"> <metaname="viewport"......
  • JavaScript Array对象(属性、方法) 留言板案例
    一、创建数组对象的方式vararrOb=newArray(值,........)vararrOb=Array(值,.......)vararrOb=[值,.........]vararrOb=newArray(n);arrOb[0]=值1;arrOb[1]=值2;二、数组的属性length   //数组中元素的数目vararr=['云南','九寨沟','拉萨','西双版纳','......
  • Java智慧工地管理平台系统源码
    施工现场涉及面广,多种元素交叉,状况较为复杂,如人员出入、机械运行、物料运输等工程项目管理在一定程度上存在着决策层看不清、管理层管不住、执行层做不好的问题一、什么是智慧工地呢?简单地说,智慧工地是将“互联网+”引入建筑行业领域,从施工现场源头抓起,收集施工现场人员、环境、......
  • 开源物流管理系统——【3】在线站点
    好消息,在线站点已经搭建完成。在线站点的搭建是为了让更多的朋友有一个直观的在线预览体验,以便提供更多的意见来完善业务流程和模块功能。接下来对在线站点做下基本的介绍:1.站点地址及访问账号:地址:http://wuliu.gardenengineer.club/账号密码:[email protected]......
  • 云服务器的CPU利用率,外网出带宽使用率,内存利用率,磁盘利用率
    云服务器的CPU利用率、外网出带宽使用率、内存利用率和磁盘利用率是用于监测服务器性能和资源使用情况的关键指标,它们各自代表不同方面的服务器运行状态:CPU利用率:CPU(中央处理单元)利用率表示服务器的处理器单元的使用情况。它表示服务器上正在运行的进程或任务对CPU资源的占用程度......
  • JavaScript 函数、函数构造、函数调用、参数、函数返回值、变量的作用域、预解析
    一、函数及函数的构造函数是一个可重用的代码块,用来完成某个特定功能。每当需要反复执行一段代码时,可以利用函数来避免重复书写相同代码。函数包含着的代码只能在函数被调用时才会执行,就可以避免页面载入时执行该脚本简单来说就是一个封装,封装的是一个特定的功能,重复使用函......
  • 全网首发 Python3 实现快读(按字符读入(省内存专用
    全网首发Python3实现快读(按字符读入(省内存专用来源:https://www.luogu.com.cn/discuss/724761此题卡内存,如果按照Python常用的input().split()方法会MLE。因为input()一次读入大量字符串,占用内存极大。于是打算按照C++的快读逻辑写一个Python3的快读。然而并没有......
  • Python 包管理器入门指南
    什么是PIP?PIP是Python包管理器,用于管理Python包或模块。注意:如果您的Python版本是3.4或更高,PIP已经默认安装了。什么是包?一个包包含了一个模块所需的所有文件。模块是您可以包含在项目中的Python代码库。检查是否安装了PIP在命令行中导航到Python脚本目录的位......
  • linux使用top命令java进程占用65%内存和160%CPU,是因为什么咋解决?
    Java进程占用大量内存和CPU的原因可能有多种,以下是一些可能的原因和解决方法:内存泄漏:Java应用程序可能存在内存泄漏,即未正确释放不再使用的内存。您可以使用Java内存分析工具(如VisualVM、MAT等)来检测和分析应用程序的内存使用情况,并查找潜在的内存泄漏问题。一旦发现内存泄漏,您可以......
  • Python 包管理器入门指南
    什么是PIP?PIP是Python包管理器,用于管理Python包或模块。注意:如果您的Python版本是3.4或更高,PIP已经默认安装了。什么是包?一个包包含了一个模块所需的所有文件。模块是您可以包含在项目中的Python代码库。检查是否安装了PIP在命令行中导航到Python脚本目录的......