垃圾回收机制

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. WEB服务与NGINX(23)- nginx的四层负载均衡功能

    目录 1. nginx实现四层的负载均衡 1.1 nginx四层负载配置参数 1.2 负载均衡示例-mariadb 1. nginx实现四层的负载均衡 nginx从1.9.0版本开始支持TCP模式的负 ...

  2. vue3.4中defineModel中默认值是复杂数据类型 (注意!!!)

    const drillFields = defineModel<string[]>('drillFields', { get(val) { return reactive(val || [ ...

  3. 圣诞快乐 - Splashtop 2020 年回顾及未来展望

    ​ 我们很高兴 Splashtop 的远程访问解决方案能够在这个充满挑战的时期为全球的组织和教育机构提供帮助. 实际上,2020 年 Splashtop 的每日使用量增加了400%!不仅如此,我们还增 ...

  4. 用 C 语言开发一门编程语言 — 交互式解释器

    目录 文章目录 目录 前言 环境 编译型 vs 解释型 实现交互式解释器 使用 GNU Readline 函数库 前言 通过开发一门类 Lisp 的编程语言来理解编程语言的设计思想,本实践来自著名的& ...

  5. WPF摄像头使用(WPFMediaKit)

    添加WPFMediaKit引用 使用WPFMediaKit操作摄像头需要安装WPFMediaKit相关的Nuget包.选中需要进行摄像头操作的项目,然后通过Nuget安装即可. 页面代码 引入命名空间 ...

  6. .net core 转 excel datatable list<t> 互转 xlsx

    using System; using System.Collections; using System.Collections.Generic; using System.ComponentMode ...

  7. 7款优秀的AI搜索引擎工具推荐

    AI搜索引擎不仅能够理解复杂的查询语句,还能够通过学习用户的搜索习惯和偏好,提供更加个性化的搜索结果.本篇文章将介绍7款在这一领域表现出色的AI搜索引擎工具,它们各有特色,但都致力于为用户提供更加智能 ...

  8. kubernetes configmap配置方法

    configmap解释 在k8s运行期间会有一些依赖的配置文件,他们存在宿主机中的一个目录中或者代码里,如果配置放生变化,pod在另一台服务器启动,在没有这些配置的时候,可能会导致pod无法正常运行, ...

  9. vscode开发一个luaIDE插件

    基础知识 环境准备 node.js 下载后下一步下一步即可安装成功,推荐LTS版本 yeoman 脚手架工具,也就是快速帮你新建一个插件所需的目录的工具,在工作目录下cmd,输入下列命令即可安装 np ...

  10. 记录nodejs做编辑和新增时候对数据库的操作

    server.js文件 const dao = require("../dao/user.dao"); saveDat是个对象自己处理一下 if (updataFlag) {//编 ...