垃圾回收机制

1.js中的内存回收

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

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

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

2.垃圾回收的两种模式

引用计数

引用计数的判断原理很简单,就是看一份数据是否还有指向它的引用,如果没有任何对象再指向它,那么垃圾回收器就会回收,举个例子:

// 创建一个对象,由变量o指向这个对象的两个属性
var o = {
name: '听风是风',
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();

标记清除

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

a.垃圾回收器创建roots列表,roots通常是代码中保留引用的全局变量,在js中,我们一般认定全局对象window作为root,也就是所谓的根部。

b.从根部出发检查所有 的roots,所有的children也会被递归检查,能从root到达的都会被标记为active。

c.未被标记为active的数据被认定为不再需要,垃圾回收器开始释放它们。

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

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

如何避免内存泄漏

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

1.全局变量

尽可能少的去创建全局变量是js开发者的常识,但如下两种方式还是会意外的创建全局变量,第一是在函数中声明变量未使用var:

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

上述代码中我们在函数体内声明了一个变量a,由于未使用var声明,即便在函数体内,但它依旧是一个全局变量。我们知道全局变量等同于在window上添加属性,所以在函数执行完毕,我们依旧可以访问到它。

第二种是在函数体内通过this来创建变量:

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

我们知道,当直接调用函数fn时,等同于window.fn(),所以函数体内的this会指向window,所以本质上还是创建了一个全局变量。

当然上述问题也不是无法解决,我们可以使用严格模式来避免这个问题,试着在代码头部添加‘use strict’,你会发现a就无法访问了,因为严格模式下,全局对象指向undefined。

有时候我们无法避免使用全局变量,那么记得在使用完毕后手动释放它们,例如让变量指向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 = '听风是风';
};
// 清除引用
elements = null;
document.body.removeChild(document.getElementById('button'));

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

补充:

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

a.分配你需要的内存空间

b.使用分配到的内存(读、写)

c.不需要时将其释放或归还

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时,其实只是复制了一份地址指针,两者还是指向同一数组,无论谁修改,都会影响彼此。

这便是我们熟知的浅拷贝,若想对浅拷贝与深拷贝有更深了解。

3.队列

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

在js中使用队列较为突出的就是js执行机制中的event loop事件循环,如果大家对于js事件执行机制有兴趣。

javascript的内存(垃圾)回收机制?的更多相关文章

  1. JavaScript具有自动垃圾回收机制

    JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存.   正常的生命周期:     局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...

  2. JavaScript中的垃圾回收机制与内存泄露

    什么是内存泄露? 任何编程语言,在运行时都需要使用到内存,比如在一个函数中, var arr = [1, 2, 3, 4, 5]; 这么一个数组,就需要内存. 但是,在使用了这些内存之后, 如果后面他 ...

  3. Javascript中的垃圾回收机制

    Javascript 中的内存管理 译自MDN,Memory Management 简介 在底层语言中,比如C,有专门的内存管理机制,比如malloc() 和 free().而Javascript是有 ...

  4. Java内存垃圾回收机制(转贴)

    Java的堆是一个运行时数据区,类的实例(对象)从中分配空间.Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,这些对象通 过new.newarray.anewarray和mul ...

  5. 三、python对字符串和集合的内存垃圾回收机制

    变量声明: name1 = "andy" name2 = name1   这个时候我把name1的值给改成了“tom”,问现在name2的值是什么?为什么? 答:andy,因为你把 ...

  6. 160930、Javascript的垃圾回收机制与内存管理

    一.垃圾回收机制-GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...

  7. javascript的垃圾回收机制与内存管理

    一.垃圾回收机制—GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...

  8. JavaScript 垃圾回收机制分析

    同C# .Java一样可以手工调用垃圾回收程序,但是由于其消耗大量资源,而且手工调用的不会比浏览器判断的准确,所以不推荐手工调用垃圾回收.   最近精力主要用在了Web 开发上,读了一下<Jav ...

  9. JavaScript作用域链和垃圾回收机制

    作用域链 基本概念: 在了解作用域链和内存之前,我们先了解两个概念,分别是执行环境和变量对象. 执行环境:定义变量或者函数有权访问的其他数据,决定了它们各自的行为.每个对象都有自己的执行环境. 变量对 ...

  10. 你不知道的JavaScript--Item28 垃圾回收机制与内存管理

    1.垃圾回收机制-GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...

随机推荐

  1. DNS(4) -- dns功能实现-配置正向解析和反向解析以及DNS递归查询示例

    目录 1 DNS配置示例 1.1 DNS解析类型 1.2 配置正向解析 1.3 配置反向解析 1.4 DNS递归查询 1.4.1 开启递归查询 1.4.2 关闭递归查询 1 DNS配置示例 1.1 D ...

  2. leaflet 实现地图上标记的发散闪烁动画

    先看效果 js文件:blinkmarker.js L.blinkMarker = function (point, property) { // 使用js标签,便于操作,这个temDivEle的作用是 ...

  3. Idefics2 简介: 为社区而生的强大 8B 视觉语言模型

    我们很高兴在此发布 Idefics2,这是一个通用的多模态模型,接受任意文本序列和图像序列作为输入,并据此生成文本.它可用于回答图像相关的问题.描述视觉内容.基于多幅图像创作故事.从文档中提取信息以及 ...

  4. 基于Ubuntu-22.04安装K8s-v1.28.2实验(四)使用域名访问网站应用

    安装负载均衡metalb 安装metalb kubectl create namespace metallb-system 配置metalb #kubectl create secret generi ...

  5. C# Datagridview combox列 初始化颜色

    DataGridView 初始化完成后,在combox里显示颜色,如这样: DataGridView 注册 cellPainting事件: private void m_dataGridView_Ce ...

  6. iOS使用SignalR客户端代码典范-桥接web SignalR 客户端库

    一.SignalR介绍 SignalR是微软基于.Net提供的一个开源实时Web RPC库,可以用在web实时通信的需求上面,比如聊天,web数据更新 SignalR的接口使用十分简单 由于最近的一个 ...

  7. RTOS入门

    RTOS是什么? RTOS是一款操作系统,相当于Windows\Linux 分为ucos FreeRTOS RT-Thread LiteOS 比裸机开发的优势在于,多任务系统,不必串行 临界区 临界区 ...

  8. WPS相关技巧

    1 标题自动编号 首先,新建一个空白word,打开.点击默认的标题样式,可以看到并不会自动编号. 接下来,就设置如何自动编号.首先选择"编号". 然后,选择"多级编号&q ...

  9. 关于java的一些吧啦吧啦

    今天凌晨在催眠时刻听了一些了java相关,顺便睡觉了 学习了关于电脑中的一些知识,类似cmd之类的快捷指令,比如切换盘符,显示文件夹等等: 还有jdk的版本下载,第一个程序helloworld怎么编写 ...

  10. 小米 红米 Redmi MIUI 5G开关

    小米 红米 Redmi MIUI 5G开关 1.打开手机拨号界面输入 *#*#54638#*#* 之后拨号界面底部会显示display 5G network menu (显示5G网络菜单) 注意:先是 ...