本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士
堆栈内存
堆内存:存储对象类型的值
栈内存:代码执行;存储声明的变量及基础类型的值
堆栈内存的初始化状态
- 默认在堆内存中开辟一个堆内存空间存储浏览器为js提供的 API,即 GO 全局对象(浏览器环境下指向window)
- 代码执行都放在执行上下文中进行,默认在栈内存中创建全局执行上下文EC(G),并且全局执行上下文永远在栈底部不出栈。EC(G) 中会创建一个VO(G)全局变量对象,存储代码中定义的全局变量。
全局对象GO
与全局变量对象VO(G)
是不同的,浏览器环境下,全局对象GO
是用来存储浏览器为js提供的 API,而VO(G)
是用来存储全局作用域下声明的变量。
代码执行过程中的变量存储
- 全局上下文中,用
var
和function
声明的变量会直接存储在 GO 中。对于函数会创建 AO 活动对象,对于其他变量声明会存储在 VO 变量对象中。 - 函数创建:(1)为此开辟内存空间,并创建作用域
[[scope]]
(在哪个上下文中创建的,其作用域就是哪个);(2)存储函数作为对象的基本属性name
(函数名),length
(形参个数),prototype
,__proto__
;(3)对于函数中的代码作为字符串存储;将空间地址赋值给栈中存储的变量名 - 函数执行:函数是可执行对象。(1)创建私有的函数执行上下文,并创建自己的 AO 变量对象,用于存储函数中声明的私有变量;(2)初始化作用域链
<函数上下文,函数作用域(上级上下文)>
;(3)初始化this
;(4)初始化arguments
;(4)形参赋值,存储在AO 对象中;(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 ]
*/
let x = [12, 23];
,为[12, 23]
在堆内存中开辟空间,假设地址为A
,在栈内存中创建变量x
,并将x
指向内存地址 Aconst fn = function(y){...}
,为function(y){...}
在堆内存中开辟空间,假设地址为B
,并存储代码字符串及其属性值。fn(x);
函数执行:初始化形参y
,并将其指向实参[12, 23]
的内存地址A
y[0] = 100;
,y
通过内存地址访问到[12,23]
,并将其第一个数据改为100
y = [100];
,为[100]
开辟一个新的内存空间,假设内存地址为B
,并将y
指向内存地址B
;此时私有函数变量y
与全局作用域下的x
指向了不同的内存地址。y[1] = 200;
,通过内存地址B
访问到[100]
,并为其新增一项,此时为[100,200]
console.log(y);
输出[100,200]
- 函数执行结束,无闭包引用,直接释放内存
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 } }
let a = {n:1}
,对{n:1}
在堆内存中开辟空间进行存储,将声明变量a
放入全局变量对象AO(G)
进行存储,假设内存地址为 A,并将堆内存地址赋给a
,让a
指向改内存地址A。let b = a
,将声明变量b
放入全局变量对象AO(G)
进行存储,并将a
所指向的堆内存地址赋给b
,现在a
和b
都指向{n:1}
所在堆内存地址A。a.x = a = {n:2}
,先处理等号右边的值。为{n:2}
在堆内存中开辟空间进行存储,假设内存地址为 B。但对于a.x
,它是成员访问,成员访问的优先级比赋值优先级高
,即比a={n:2}
优先级高。 因此先执行a.x={n:2}
,即a.x
指向内存 B。此时a
和b
共同指向的内存地址 A 中存储的数据为{n:1,x:{n:2}}
。再执行a={n:2}
,将a
指向内存地址 B。console.log(a.x)
,a
指向的内存 B中存储的值为{n:2}
,没有x
属性,因为为undefined
console.log(b)
,b
指向的内存 A 中存储的数据为{n:1,x:{n:2}}
不同声明方式的区别
var
和function
定义的变量,是给全局对象 GO设置属性;let
和const
定义的变量是放在全局变量对象VO(G)
中let/const
会产生块级作用域let/const
存在暂时性死区:词法分析阶段let
声明的变量也会先做声明提升,但不可访问(与未声明的表现一致)- 基于
const
声明的变量不是真正的常量
。必须先赋初始值,const a =1
,不可以单独无值声明,const b;❌
。而且一旦进行值关联,不再允许改变指针指向;对于引用类型的值,不改变指针指向,修改引用对象的属性值是可以的。
本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士