前言

在前端来说,JS 代码可运行的环境包括浏览器环境、App 环境(基于 webview)、Node 环境等,但是无论是什么环境下执行 JS 代码,都需要开辟出相关的内存,用来存储值「Heap 堆存储」以及运行代码「Stack 栈内存 -> ECStack 执行环境栈-> Execution Context Stack 执行环境栈」

GO

我们在 JS 代码运行在浏览器中,浏览器为我们提供了很多内置的API、方法。这些内置的 API 和方法都存在堆内存空间中。当我们打开一个页面时,首先浏览器会在内存在开辟一块空间,来存放内置的 API 和方法。例如 GO(global object)全局对象:存储浏览器内置的 API,并且会为它分配一个十六进制的内存地址。
前端基石:堆栈内存 - 图1

EC(G)

当我们开始执行一段 JS 代码时,会先执行全局的代码。在代码执行时会区分全局的执行环境和私有函数执行环境。为了区分开全局执行环境和私有函数执行环境,每一次函数的执行都会创建一个属于自己的私有执行环境。

全局的执行环境叫做全局执行上下文,又叫做 EC(G) ,它的作用是供全局代码执行。并且提供了全局变量对象 VO(G),用来存储全局下声明的变量对象。
前端基石:堆栈内存 - 图2

GO VS EC(G)

这里需要区分开全局的变量对象 VO(G) 和全局对象 GO,这是两个不同的东西,但是又是有联系的。

  • VO(G):是在栈中开辟的内存,用来存储全局上下文中声明的变量。
  • GO:是在堆内存中开辟的内存,用来存储全局内置的 API、方法。

但是在浏览器环境中,会默认在 EC(G) 中声明一个变量 window (不同执行环境不一样)来执行堆内存的全局对象。
前端基石:堆栈内存 - 图3

栈内存 VS 堆内存

栈内存的作用

  1. 代码的执行环境,将不同地方的代码放置在不同的执行上下文中执行。
  2. 存储原始值类型的值。
  3. 提供的变量对象(VO/GO)存储当前上下文中声明的变量。

堆内存的作用

  1. 存储对象的值,只要是引用类型,就会在 Heap 中开辟空间(16进制地址)来存储对象的键值对(或者函数的代码字符串)。

举个例子:

当有如下代码,在浏览器环境中执行。当「声明一个变量等于一个值时」。浏览器会做哪些事情了。

  1. // 全局执行上下文
  2. let a = 1;
  3. var b = 2;
  4. let c = {
  5. name: 'stone',
  6. age: 13
  7. };

let 变量 = 值:

  1. 创建值(原始类型直接存储在栈中,对象类型存储在堆中)。
  2. 声明变量,在变量对象中声明一个变量。
  3. 关联变量和值,这个操作称之为定义(赋值)defined。

var 变量 = 值:
在「全局上下文」中,基于 let/const 声明的变量,是存储在 VO(G) 中的,但是基于 var/function 声明的变量,是直接存储在 GO 中的,所以严格意义上来讲,基于 var/function 声明的变量是不能称为全局变量的,仅仅是全局对象上的一个属性而已。
前端基石:堆栈内存 - 图4

「全局上下文」变量的访问和赋值

「全局上下文」访问变量:

  1. 首先查看 VO(G) 中是否存在变量,如果有就是全局变量。
  2. VO(G) 没有,在基于 window 查看 GO 有没有,有则是全局对象的一个属性。
  3. 如果 GO 中也没有,就会报错“xxx is not defined”

    「全局上下文」赋值变量:a = 100

  4. 首先查看赋值变量是否是全局变量,是就修改属性值。

  5. 如果没有就直接给 GO 加上一个属性值。

面试练习题

接下来看一个面试练习题

  1. let a = { n:1 };
  2. let b = a;
  3. a.x = a = { n: 2 };
  4. console.log(a.x);
  5. console.log(b);

前端基石:堆栈内存 - 图5