内存空间与内存机制
这一节,我们会对JS的内存空间和内存机制做详细的介绍,深入地了解变量是如何存放在内存中的,最后我们还会解释闭包这一概念,帮助大家更好的理解JS的内存机制。
首先,我们来看一个问题:
var a = 20;
var b = a;
b =30;
console.log(a);
相信大家可以立刻得到输出20这个结论,即变量a没有受到变量b的改变所造成的影响。我们再来看一个问题:
var a = { name: '前端开发' }
var b = a;
b.name = '进阶';
a.name的值是多少?
这个问题的输出就变成了进阶,我们会发现:变量b的改变影响到了变量a。上述问题是怎么出现的呢?我们首先需要了解一下堆内存和栈内存。
堆内存和栈内存
堆数据结构
堆是一种经过排序的树形数据结构,每个结点都有一个值。堆的存取是随意,这就如同我们在图书馆的书架上取书, 虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书, 我们只需要关心书的名字。
栈数据结构
栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。 栈被称为是一种后入先出(LIFO,last-in-first-out)的数据结构。 由于栈具有后入先出的特点,所以任何不在栈顶的元素都无法访问。 为了得到栈底的元素,必须先拿掉上面的元素。
在JS中的应用
JS的数据类型分为基本类型:(Undefined、Null、Boolean、Number、String、Symbol)和引用类型(Object、Array、function等除了基本类型外的数据类型),本文只讨论数据在内存中的存储,不对数据类型进行赘述。
基于堆数据结构和栈数据结构的特性,我们把占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据(即基本类型)保存在栈内存中,把占据空间大、大小不固定,按引用来访问的数据(即引用类型)保存在堆内存中。
备注: 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
基于上述知识点,我们来分析一下前面的两个问题:
// 示例1
var a = 20;
var b = a;
b =30;
console.log(a);
- 在第一步,我们声明了变量a(
Number类型,属于基本类型),所以我们将它存放在栈内存中。 - 接着我们声明了变量b,把变量a赋值给变量b,因为a的值是基本类型,所以我们把变量a的值(即2)复制后存放在一个新的栈内存中,然后赋值给变量b,变量a和变量b存放在两个完全无关的栈内存中。
- 我们修改变量b的值,这时我们新开辟了一块栈内存,存放基本类型值30,让变量b指向这个新的栈内存,所以原来的存放a的栈内存完全不会受到影响,输出结果为20。
内存回收
javascript有自动垃圾收集机制,垃圾收集器每隔一段时间就会执行一次释放操作,找出那些不再使用的值,然后释放其占用的内存。
垃圾回收算法
核心就是如何判断内存已经不再使用,可以回收。JS使用标记清除方法来进行垃圾回收。
标记清除:将”不再使用的对象“定义为“无法到达的对象”,即从根部(js对应全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留,到达不了的标记为不再使用,稍后回收。
内存泄漏(memory leak)
对于不再使用的内存,没有及时释放,就是内存泄漏。 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
常见的内存泄漏有:
- 意外的全局变量
// 函数foo内部忘记使用var,实际上JS会把bar挂载到全局对象上,意外创建一个全局变量
function foo(arg) {
bar = "this is a hidden global variable";
}
foo();
window.bar; // 是"this is a hidden global variable",而不是undefined
- 可能由 this 创建
// foo 调用自己,this 指向了全局对象(window),而不是 undefined
function foo() {
this.variable = "potential accidental global";
}
foo();
window.variable; //是"potential accidental global",而不是undefined
解决方法: 在JavaScript文件头部加上'use strict',使用严格模式避免意外的全局变量。此时上例中this指向undefined。
-
遗忘的计时器或回调函数
-
脱离dom的引用
-
闭包
私货课堂
来看几个小问题:
-
从内存看,null和undefined的区别是什么?
给一个全局变量赋值为null,相当于把这个变量的指针对象及值清空,js会回收这些全局变量。 给一个全局变量赋值为undefined,相当于把这个对象的值清空,但对象依旧存在。
给一个对象的属性或局部变量赋值为null,相当于给这个属性分配了一块空的内存,值为null。 给一个对象的属性或局部变量赋值为undefined,说明这个值为空值。
-
从内存看,闭包是什么?
从内存的角度看,闭包中的变量并不保存在栈内存中,而是保存在堆内存中。当函数执行完成后,因为闭包中所引用的变量还存放在堆内存中,且在闭包中有被引用。所以垃圾回收算法不会清除闭包中的变量,容易造成内存问题。
总结
在这一节,我们详细介绍了JS中变量在内存中的存储,还补充解释了闭包这一概念。接下来我们会继续介绍其他JS核心知识。
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。