前端基石:Stack、Heap

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

网络异常,图片无法展示
|


一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情


前言


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


GO


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

网络异常,图片无法展示
|


EC(G)


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


全局的执行环境叫做全局执行上下文,又叫做 EC(G) ,它的作用是供全局代码执行。并且提供了全局变量对象 VO(G),用来存储全局下声明的变量对象。

网络异常,图片无法展示
|


GO VS EC(G)


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


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

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

网络异常,图片无法展示
|


栈内存 VS 堆内存


栈内存的作用

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

堆内存的作用

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

举个例子

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

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


let 变量 = 值; 时,浏览器会进行三步操作:


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


var 变量 = 值; 时和 let 有一点区别:

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

var a = 1;
function b() {};
let c = 2;
console.log(window.a);
console.log(window.b);
console.log(window.c);
VM58:4 1
VM58:5 ƒ b() {}
VM58:6 undefined


所以针对上述代码,JS 代码执行会有如下操作。


  1. 首先在栈内存,VO(G)中创建值 1 并关联变量 a;
  2. 然后在堆内存,GO 中创建值 2 并关联变量 b;
  3. 再然后在堆内存,开辟一块新的内存空间,假设内存地址是 0x 001,存储 「name: 'stone', age: 13」;
  4. 接着在栈内存,声明变量 c ;
  5. 最后将变量 c 和内存 0x 001 关联起来。

网络异常,图片无法展示
|


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


「全局上下文」访问变量


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


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


  1. 首先查看赋值变量是否是全局变量,是就修改属性值。
  2. 如果没有就直接给 GO 加上一个属性值。


面试练习题


接下来看一个面试练习题,我们通过画图的方式来走代码流程

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


let a = { n:1 };


let 变量 = 值; 时,浏览器会进行三步操作:


  1. 创建值(原始类型直接存储在栈中,对象类型存储在堆中),所以在堆内存中开辟一块新的内存空间,假设内存地址是 0x001,用来存储 n: 1。
  2. 声明变量,在变量对象中声明一个变量 a 。
  3. 关联变量和值,将 a 和内存地址 0x001 关联起来。

网络异常,图片无法展示
|

let b = a;

  1. 创建值(原始类型直接存储在栈中,对象类型存储在堆中),发现值就是 a 的值,内存空间地址就是 0x001。
  2. 声明变量,在变量对象中声明一个变量 b 。
  3. 关联变量和值,将 b 和内存地址 0x001 关联起来。

网络异常,图片无法展示
|

a.x = a = { n: 2 };

  1. 创建值(原始类型直接存储在栈中,对象类型存储在堆中),所以在堆内存中开辟一块新的内存空间,假设内存地址是 0x002,用来存储 n: 2。
  2. 这里需要注意一下代码的赋值执行顺序。正常情况下当 「x = y = 10;」时,代码是从右往左执行 y = 10; x = 10(或者 x = y),但是这里需要考虑一下优先级的问题,属性访问的优先级高于赋值,所以这里的执行顺序有所不同,a.x = { n:2 },然后才是 a = { n:2 }。3. 在堆内存 0x001 中添加属性 x,并赋值内存 0x002的内存地址。
  3. 将 变量 a 赋值给新的内存地址 0x002;

网络异常,图片无法展示
|

画完堆栈内存的关系图,对于输出的结果就很明显了。

console.log(a.x); // undefined
console.log(b); // { n:1 x: { n: 2 } }

你答对了吗?其实关于堆栈内存的面试题,在开始不太熟练的时候只需要画图肯定会得到结果,当你熟练之后这张图不用画就会清晰的呈现在你的大脑里面。


参考


目录
相关文章
|
6月前
|
移动开发 前端开发 API
深入理解前端路由:构建现代 Web 应用的基石(上)
深入理解前端路由:构建现代 Web 应用的基石(上)
深入理解前端路由:构建现代 Web 应用的基石(上)
|
1月前
|
存储 监控 前端开发
掌握微前端架构:构建未来前端应用的基石
【10月更文挑战第12天】随着前端技术的发展,传统的单体应用架构已无法满足现代应用的需求。微前端架构通过将大型应用拆分为独立的小模块,提供了更高的灵活性、可维护性和快速迭代能力。本文介绍了微前端架构的概念、核心优势及实施步骤,并探讨了其在复杂应用中的应用及实战技巧。
|
1月前
|
JSON 前端开发 JavaScript
构建现代前端应用的基石
【10月更文挑战第13天】构建现代前端应用的基石
|
6月前
|
移动开发 前端开发 数据可视化
前端HTML:构建网页的基石
前端HTML:构建网页的基石
42 0
|
6月前
|
前端开发 JavaScript 开发工具
前端技术栈:构建现代Web应用的基石与实践
前端技术栈:构建现代Web应用的基石与实践
442 3
|
6月前
|
人工智能 前端开发 JavaScript
探索前端技术栈:构建现代Web应用的基石
探索前端技术栈:构建现代Web应用的基石
129 1
|
6月前
|
前端开发 JavaScript 搜索推荐
深入理解前端路由:构建现代 Web 应用的基石(下)
深入理解前端路由:构建现代 Web 应用的基石(下)
深入理解前端路由:构建现代 Web 应用的基石(下)
|
存储 前端开发 JavaScript
前端基石:构造函数和普通函数
在上面的代码中,一个 Fn 函数有两种命运,普通函数执行,new 构造函数执行。那这两种执行方法有什么区别了? 简单来理解,普通函数执行,是把 Fn 函数执行的结果返回给 f1。构造函数执行,把 Fn 当做一个类,是把创造的一个实例返回,f2 是创造出来的一个实例。本文将从执行流程中为大家分享这两类函数的区分。
157 1
|
前端开发 JavaScript
前端基石:this 的几种基本情况
本文主要讲this 的几种基本情况
158 1
|
存储 前端开发
前端基石:一段代码隐含了多少基础知识?
本文主要讲代码中隐含的基础知识
153 1
下一篇
无影云桌面