原文地址:github.com/ruizhengyun…javascript
咱们都知道 Javascript 具备自动垃圾回收机制。一听到自动这个词,多好啊,能帮咱们作点,咱们就能够少作点事了。或许也部分是由于这个自动回收垃圾的机制,不少的前端小伙伴就减小了和内存空间打交道的场合或机会,就容易忽视这些。还有就是,前端开发人员许多都不是计算机专业毕业的,对内存空间的认知就比较模糊了,有的干脆就是一无所知。刚入行(前端)几年的我(一个文科生),就是一无所知的菜鸟,如今好多了,用我对象的话说抢了她(计算机专业毕业的)的饭碗。其实嘛,不知道不要紧,能够学,那些计算机专业的小伙伴不也是从上大学开始学的嘛,咱只不过晚了点,如此而已。前端
基础知识很重要,别嫌我啰嗦,再强调一次。固然了,我确定不是第一个,也不是最后一个这么啰嗦的。接下来的场景面试,你确定深有体悟:日常在公司开发应用玩的很转,一面试,仿佛就在裸奔,许多知识讲的不完全,本身都以为虚。这个现象的本质仍是在于基础知识的不扎实,因此在面试的时候,你想讲可是又不是很懂,或许你耍个聪明的去规避这些,可面试官也不傻(要是碰到傻得告诉我)。因此,请不要忽略基础知识。java
好了,接下来好好说说基础知识之内存空间究竟是怎么个玩法。这一次,必定要整个明白(给本身立个 Flag)。node
它们分别是堆(heap)、栈(stack)、队列(queue),一图知全部。git
Javascript 没有像 C/C++ 严格意义上区分堆内存和栈内存。咱们能够简单粗暴一刀切(就这个意思了)的理解 Javascript 数据存在堆内存中。可是,重点来了,有些场景仍是须要栈数据结构的思惟来理解的,好比执行上下文,存放变量。github
从上图一眼可看出其特色 后进先出(LIFO)(走后门啊这是),这就是栈的存储原理,一个有后台的数据结构。面试
这里有个池的概念,即存放常量,因此也叫常量池。算法
一种树状结构。比如 JSON 格式中的数据,你有 key
,我有对应的 value
, 就立马返给你。一个绝对公平的数据结构。编程
只要你要,只要我有。数组
Javascript 中,队列数据结构的应用主要体如今事件循环机制(eventLoop) 上。其特色很直白就是先进先出(FIFO),和栈结构正好相反,一个相对公平的数据结构。
先到先得
Javascript 执行上下文后,会建立一个叫变量对象的特殊对象,其实这个对象也存在堆内存中,但因为特殊,要与堆内存区分。
基础数据类型都是一些简单的数据段,包括 Undefined、Null、Boolean、Number、String 和 Symbol
。保存在栈内存中,由于这些类型在内存中分别占有固定大小的空间,经过按值访问,因此可直接操做变量中的值,即寻值。
引用数据类型的值保存在堆内存的对象中。由于这类值的大小不固定,所以不能把它们保存到栈内存中,但内存地址大小的固定的,所以保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 而后再经过地址找到堆中的值,即寻址。
注闭包中的变量并不保存中栈内存中,而是保存在堆内存中,这也就解释了外层函数弹出调用栈后为何闭包还能引用到函数内的变量。如今的 JS 引擎能够经过逃逸分析辨别出哪些变量须要存储在堆上,哪些须要存储在栈上。
var name = 'pr'; // 变量对象
var age = 30; // 变量对象
var info = { name: 'pr' }; // 变量 info 存在变量对象中,{ name: 'pr' } 做为对象存在堆内存中
var relation = [1, 2, 3]; // 变量 relation 存在变量对象中,[1, 2, 3] 做为对象存在堆内存中
复制代码
所以当要访问
info
、relation
这些对内存的引用数据类型时,其实是从变量对象中获取其地址引用,而后才从堆内存得到数据。
问题1
var name = 'pr';
var nickName = name;
nickName = '一如既往如我';
console.log(name); // pr
复制代码
问题2
var info = { name: 'pr' };
var nicInfo = info;
nicInfo.name = '一如既往如我';
console.log(info.name); // 一如既往如我
复制代码
问题3
var info = { name: 'pr' };
var nicInfo = info;
nicInfo = null
console.log(info.name); // pr
复制代码
上面3个例子,相信此次你应该明白了。在变量数据中发生了数据复制。基本数据类型和引用数据类型的复制过程图可看下面
对于问题1,基础类型复制过程
对于问题2,引用类型复制过程
对于问题3,null
是基本类型,因此同问题1,并不会影响堆内存中的对象,因此 info
不受影响,你学会了么?
在计算机的数据结构中,栈比堆的运算速度快,Object 是一个复杂的结构且能够扩展:数组可扩充,对象可添加属性,均可以增删改查。将他们放在堆中是为了避免影响栈的效率。而是经过引用的方式查找到堆中的实际对象再进行操做。因此查找引用类型值的时候先去栈查找再去堆查找,这点要熟记于心。
本文一开始就提到 Javascript 具备自动垃圾回收机制,因此前端开发就没关注过内存的使用问题。可是,了解内存机制有助于知道编写的代码在执行过程当中发生了什么,从而提升编程质量。
1.局部变量和全局变量的销毁
2.以 Google 的 V8 引擎为例,V8 引擎中全部的 JS 对象都是经过堆来进行内存分配的
3.V8引擎对堆内存中的 JS 对象进行分代管理
对垃圾回收算法来讲,核心思想就是如何判断内存已经再也不使用,经常使用垃圾回收算法有下面两种。
引用计数算法定义“内存再也不使用”的标准很简单,就是看一个对象是否有指向它的引用。若是没有其余对象指向它了,说明该对象已经再也不须要了。
let info = {
name: 'pr',
age: 30
};
let nickInfo = info;
info = '一如既往如你';
nickInfo = null;
复制代码
引用计数有一个致命的问题,那就是循环引用
function cycleFn() {
let o1 = {};
let o2 = {};
o1.obj = o2;
o2.obj = o1;
}
cycleFn();
复制代码
cycle
函数执行完成以后,对象 o1
和 o2
实际上已经再也不须要了,但根据引用计数的原则,他们之间的相互引用依然存在,所以这部份内存不会被回收。因此现代浏览器再也不使用这个算法。可是 IE 依旧使用
let createElementDiv = document.createElement("div");
createElementDiv.onclick = function() {
console.log("点击建立的元素 div", createElementDiv);
};
复制代码
上面的例子就是一个循环引用。变量 createElementDiv
有事件处理函数的引用,同时事件处理函数也有 createElementDiv
的引用,由于 createElementDiv
变量可在函数内被访问,因此循环引用就出现了。
标记清除算法将“再也不使用的对象”定义为“没法到达的对象”。即从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留。那些从根部出发没法触及到的对象被标记为再也不使用,稍后进行回收。因此上面的例子就能够正确被垃圾回收处理了。
var name = 'pr';
console.log(name);
name = null;
复制代码
上面 name = null
作了释放引用,脱离执行环境,这个值在下次垃圾收集器执行时释放。因此,适当时候解除引用,是页面性能提高的一个好的方式。
对于持续运行的服务进程(daemon),必须及时释放再也不用到的内存。不然,内存占用愈来愈高,轻则影响系统性能,重则致使进程崩溃。 对于再也不用到的内存,没有及时释放,就叫作内存泄漏(memory leak)
一、浏览器方法
二、命令行方法 使用 Node 提供的 process.memoryUsage 方法。
node
> process.memoryUsage()
// 输出
{ rss: 23904256,
heapTotal: 7331840,
heapUsed: 5042520, // 用到的堆的部分,判断内存泄漏,以这个字段为准。
external: 20601
}
复制代码