垃圾回收机制

这篇博客涉及到很多的知识面以及JS的底层知识,我相信我们大家一个个逐个攻破,能够对V8引擎如何进行垃圾内存的回收的知识有所掌握,接下来我会先带大家一起来学习前提知识

JS中的栈内存堆内存

JS的内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。

其中栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池。

栈数据结构

栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。栈被称为是一种后入先出(LIFO,last-in-first-out)的数据结构。由于栈具有后入先出的特点,所以任何不在栈顶的元素都无法访问。为了得到栈底的元素,必须先拿掉上面的元素

堆数据结构

堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,我们只需要关心书的名字。

基本数据类型与内存的关系

基本数据类型保存在栈内存中,因为基本数据类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。(1.boolean 2.null 3.undefined 4.number 5.string 6.symbol)

为了更好的搞懂基本数据类型变量与栈内存,我们结合以下例子与图解进行理解:

1
2
let num1 = 1;
let num2 = 1;

栈内存

看看变量复制

1
2
3
4
let a = 20;
let b = a;
b = 30;
console.log(a); // 此时a的值是多少,是30?还是20?

答案是:20

在这个例子中,a、b 都是基本类型,它们的值是存储在栈内存中的,a、b 分别有各自独立的栈空间, 所以修改了 b 的值以后,a 的值并不会发生变化。

从下图可以清晰的看到变量是如何复制并修改的。

变量复制

PS: 需要注意的是闭包中的基本数据类型变量不保存在栈内存中,而是保存在堆内存中。闭包详情已经在我的另一篇博客中讲解过了

请移步至闭包

引用数据类型与内存的关系

Array,Function,Object…可以认为除了上文提到的基本数据类型以外,所有类型都是引用数据类型。引用数据类型存储在堆内存中,因为引用数据类型占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

直接看变量复制

1
2
3
4
let m = { a: 10, b: 20 };
let n = m;
n.a = 15;
console.log(m.a) //此时m.a的值是多少,是10?还是15?

答案是:15

在这个例子中,m、n都是引用类型,栈内存中存放地址指向堆内存中的对象,引用类型的复制会为新的变量自动分配一个新的值保存在变量中,但只是引用类型的一个地址指针而已,实际指向的是同一个对象,所以修改 n.a 的值后,相应的 m.a 也就发生了改变。

从下图可以清晰的看到变量是如何复制并修改的。

变量复制

执行环境(上下文)

JavaScript 中有三种执行上下文类型

  • 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
  • Eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

。。。未完

0%