[javascript核心-18] JavaScript 堆栈内存机制🍇

58 阅读5分钟

本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士


堆栈内存

堆内存:存储对象类型的值

栈内存:代码执行;存储声明的变量及基础类型的值

堆栈内存的初始化状态

  1. 默认在堆内存中开辟一个堆内存空间存储浏览器为js提供的 API,即 GO 全局对象(浏览器环境下指向window)
  2. 代码执行都放在执行上下文中进行,默认在栈内存中创建全局执行上下文EC(G),并且全局执行上下文永远在栈底部不出栈。EC(G) 中会创建一个VO(G)全局变量对象,存储代码中定义的全局变量。

全局对象GO与全局变量对象VO(G)是不同的,浏览器环境下,全局对象GO是用来存储浏览器为js提供的 API,而VO(G)是用来存储全局作用域下声明的变量。

代码执行过程中的变量存储

  1. 全局上下文中,用varfunction声明的变量会直接存储在 GO 中。对于函数会创建 AO 活动对象,对于其他变量声明会存储在 VO 变量对象中。
  2. 函数创建:(1)为此开辟内存空间,并创建作用域[[scope]](在哪个上下文中创建的,其作用域就是哪个);(2)存储函数作为对象的基本属性name(函数名),length(形参个数),prototype__proto__;(3)对于函数中的代码作为字符串存储;将空间地址赋值给栈中存储的变量名
  3. 函数执行:函数是可执行对象。(1)创建私有的函数执行上下文,并创建自己的 AO 变量对象,用于存储函数中声明的私有变量;(2)初始化作用域链<函数上下文,函数作用域(上级上下文)>;(3)初始化this;(4)初始化arguments;(4)形参赋值,存储在AO 对象中;(5)变量提升;
  4. 代码进栈执行:从堆内存中取出代码放入函数执行上下文中执行;
  5. 上下文的回收释放:函数执行完以后所形成的私有上下文会被释放

函数执行的堆栈内存

let x = [12, 23];
const fn = function(y){
    y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
};
fn(x);
console.log(x)

/*
[ 100, 200 ]
[ 100, 23 ]
*/
  1. let x = [12, 23];,为[12, 23]在堆内存中开辟空间,假设地址为A,在栈内存中创建变量x,并将x指向内存地址 A
  2. const fn = function(y){...},为function(y){...}在堆内存中开辟空间,假设地址为B,并存储代码字符串及其属性值。
  3. fn(x);函数执行:初始化形参y,并将其指向实参[12, 23]的内存地址A
  4. y[0] = 100;y通过内存地址访问到[12,23],并将其第一个数据改为100
  5. y = [100];,为[100]开辟一个新的内存空间,假设内存地址为B,并将y指向内存地址B;此时私有函数变量y与全局作用域下的x指向了不同的内存地址。
  6. y[1] = 200;,通过内存地址B访问到[100],并为其新增一项,此时为[100,200]
  7. console.log(y);输出[100,200]
  8. 函数执行结束,无闭包引用,直接释放内存
  9. console.log(x)在全局作用域中找到x,并找到其指向的内存地址A,打印其存储的数据[100,23]

联等赋值与成员访问优先级的问题

let a = {n:1}
let b = a
a.x = a = {n:2}
console.log(a.x) // undefined
console.log(b) // { n: 1, x: { n: 2 } }
  1. let a = {n:1},对{n:1}在堆内存中开辟空间进行存储,将声明变量a放入全局变量对象AO(G)进行存储,假设内存地址为 A,并将堆内存地址赋给a,让a指向改内存地址A。
  2. let b = a,将声明变量b放入全局变量对象AO(G)进行存储,并将a所指向的堆内存地址赋给b,现在ab都指向{n:1}所在堆内存地址A。
  3. a.x = a = {n:2},先处理等号右边的值。为{n:2}在堆内存中开辟空间进行存储,假设内存地址为 B。但对于a.x,它是成员访问,成员访问的优先级比赋值优先级高,即比a={n:2}优先级高。 因此先执行a.x={n:2},即a.x指向内存 B。此时ab共同指向的内存地址 A 中存储的数据为{n:1,x:{n:2}}。再执行a={n:2},将a指向内存地址 B。
  4. console.log(a.x)a指向的内存 B中存储的值为{n:2},没有x属性,因为为undefined
  5. console.log(b)b指向的内存 A 中存储的数据为{n:1,x:{n:2}}

不同声明方式的区别

  1. varfunction定义的变量,是给全局对象 GO设置属性;letconst定义的变量是放在全局变量对象VO(G)
  2. let/const会产生块级作用域
  3. let/const存在暂时性死区:词法分析阶段let声明的变量也会先做声明提升,但不可访问(与未声明的表现一致)
  4. 基于const声明的变量不是真正的常量。必须先赋初始值,const a =1,不可以单独无值声明,const b;❌。而且一旦进行值关联,不再允许改变指针指向;对于引用类型的值,不改变指针指向,修改引用对象的属性值是可以的。

本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士