JS中的堆(Heap)栈(Stack)内存
都是在计算机内存中开辟的空间
- 栈内存 Stack:ECStack(Execution [ˌeksɪˈkjuːʃn] Context Stack)
1.存储原始值类型
2.代码执行的环境 - 堆内存 Heap:
1.存储对象值类型
EC(Execution [ˌeksɪˈkjuːʃn] Context )执行上下文:区分代码执行的环境
- 常见上下文分类:
- 全局上下文EC(G)
2.函数私有上下文EC(?)
3.块级私有上下文EC(Bolck)
- 产生私有上下文->进栈执行->出栈释放(可能释放)
- 变量对象:当前上下文中,用来存储声明的变量的地方
1.VO(Varibale Object):VO(G) 或者 VO(BLOCK)
2.AO(Active Object):AO(?)
GO(Global Object)全局对象
- window指向GO对象
- 全局上下文中,基于var/function声明的变量是直接存储到GO
对象上的,而基于let/const生命的变量才是存放在VO(G)中的
let 变量 = 值 的操作步骤
- 第一步:创建值
1.原始值类型:直接存储在栈内存中,按值操作
number
string
boolean
null
undefined
Symbol
bigInt
- 对象类型值:按照堆内存地址来操作
@1、对象:开辟一个堆内存空间(16进制地址)、一次存储对此昂的键值对、把空间地址赋值给变量
@2、函数:内存空间存储三部分信息
作用域[[scope]]:当前所处上下文
函数体中的代码字符串
当作普通对象存储的静态属性和方法[name&length]
- 第二步:声明变量 declare
- 第三步:变量和值关联在一起(赋值)defined
以代码为例来详细解读函数的底层处理机制
1 、
var x = 12
let y = 12
z = 14
console.log(x)
console.log(window.x)
console.log(y)
console.log(window.y)
console.log(z)
console.log(window.z)
全局代码执行,形成EC(G)全局执行上下文
- EC(G)全局执行上下文
VO(G):全局变量对象(基于let/const声明的全局变量存储在这里)
y:13
window -- > GO全局对象(基于var/function声明的全局变量存在这里)
x:12
z:14 // window.z = 14 直接设置在GO中,相当于省略了‘.window’ - 代码执行:
@1、console.log(x) :结果是 12 ;
首先看VO(G)中是否存在,如果不存在 再去GO中看看是否存在,如果都不存在则报错:x is not defined
@2、console.log(window.x):结果是12;
直接到GO中找这个属性,如果不存在,则是undefined(因为是访问当前对象的某个成员,成员不存在的话结果是undefined,所以不是报错)
@3、console.log(y):结果是13;
基于let声明的变量存在VO(G)中
@4、console.log(window.y):结果是undefined;
@5、console.log(z):结果是14;直接去GO中找
@6、console.log(window.z) :结果是14;直接去GO中找
2、
let x = [12,23]
function fn(y) {
y[0] = 100
y = [100]
y[1] = 200
console.log(y)
}
fn(x)
console.log(x)
文字版:
再重复一边
计算机会开辟两两个空间:栈内存、堆内存
@1、栈内存:存储原始值(值类型)、提供代码执行的环境
@2、堆内存:存储对象值类型上边已经提到:凡是变量=xxx 都是:
@1、先创建值
@2、定义变量
@3、赋值
代码执行步骤:
1、浏览器开辟一块空间-->栈内存-->ECStack代码执行环境栈
2、浏览器开辟一块空间-->堆内存-->-GO全局对象:16进制地址定为 0x000
@1、存放内置属性:
setTimeout,
setInterval,
requestAnimationFrame
.......
3、最开始肯定是全局代码执行,形成EC(G):全局执行上下文-->进栈执行
@1、全局执行上下文中,基于let/const声明的变量要存放到全部变量对象VO(G)中
4、代码自上而下执行
5、[12,23]是个数组,所以开辟一个堆内存来存储,16进制地址暂定为:0x001
@1、存储的内容有:
0:12,
1: 23,
length:2
.....
6、定义变量x
基于let声明,存储到VO(G)中
7、x和0x001关脸 x --> 0x001
8.VO(G)中存储的变量:
@1、x -------> 0x001
9、function xxx 也是定义一个变量,基于function/let声明的而变量存在GO中
@1、函数也是一个对象,所以开一个堆内存,16进制地址为:0x002
@2、存储的内容:
- 作用域[[scope]]:函数在哪个上下文中创建,那么它的作用域就是哪个上下文(为作用域链做铺垫)
- 函数代码字符串
- 当作普通对象来存放键值对值(静态私有属性方法)
(a)、name:fn
(b)、length:1(形参个数)
所以函数如果不执行,一点意义都没有
[[重复:函数的作用域是在它创建的时候声明的]]
@3、把10进制地址0x002赋值给fn
10、此时GO中存储的属性:
setTimeout,
setInterval,
requestAnimationFrame,
fn:0x002
..........
11、函数执行并传值:fn(x) ----> fn(0x001)
@1、产生全新的私有执行上下文EC(FUN),然后进栈执行[私有上下文中有个私有变量对象AO(FUN),用来存储私有上下文中声明的变量]
@2、代码正式执行前:
- 初始化作用域链<EC(FUN)当前自己的私有上下文,EC(G)函数的作用域(上级执行上下文)>,明确变量的归属
- 初始化THIS指向:window
- 初始化aguments:{0:’0x001‘}
(aguments是类数组集合,存的是实参,不管有没有形参,只要传了实参,arguments中都会有值) - 形参赋值:y = 0x001
- 变量提升:无
- 形参和上下文中声明的变量都是私有的
- 此时AO(G)中存的值是:
y ---> 0x001
@3、代码执行
y[0] = 100
y = [100]
y[1] = 200
console.log(y)
第一行:是自己的私有变量,地址指向0x001,所以把堆内存0x001中索引为0的值改成100
此时0x001中存储的内容变成:
0:100,
1:23,
length:2,
......
y虽然是私有变量,但是和全局变量x指向的同一个地址,全局x的值也受影响,此时x的值为:[100,23]第二行 y = [100] :[100]是个数组,所以再重新开一个堆内存0x003,用来存储这个数组
0:100,
length:1
.....
此时jy指向新的堆内存地址0x003第三行:根据y执行的新地址,在0x003这个堆内存中增加了一个索引为1的值,此时0x003的存储内容是:
0:100,
1:200,
length:2
.....第四行:输出y,y是私有的,地址指向0x003
所以输出值为:*[100,200]
@4、fn执行完后,出栈释放
12、函数执行完,执行全局代码:console.log(x)
刚才已经提到 x指向0x001
0x001中的值在fn执行的时候已给修改成:[100,23]
全局代码在浏览器关掉的时候才会释放
以上就是整个代码的运行机制
总结:
1、创建函数的过程:
@1、开辟堆内存[16进制地址]
@2、存储的内容
- 作用域[[scope]]:在哪个上下文中创建的,那么它的作用于就是哪个上下文(为作用域链做铺垫)
- 函数代码字符串
- 作为普通对象有的属性:
name:函数名
length:形参的个数
@3、把16进制空间地址赋值给变量(函数名)即可
2、普通函数执行要做的事情:
@1、产生全新的私有执行上下文EC(?),然后进栈执行
私有上下文中有私有变量对象AO(G),用来存放私有变量
@2、代码执行前还要做的事情:
- 初始化作用域链:<EC(?)自己私有的执行上下文,EC(G)函数的作用域(上级执行上下文)>
- 初始化this
- 初始化arguments:类数组集合,存储实参
- 形参赋值
- 变量提升
[[形参和在私有上下文中声明的变量都是私有变量,存在AO中]]
@3、代码正式执行
@4.执行完出栈(如果被外部占用,不出栈,类如闭包)
3、作用域链
<自己私有上下文,作用域(上级上下文)>
@1、在私有上下文中遇到个变量,首先看是否为私有变量(看AO中是否存在),如果是私有变量,则接下来操作的都是私有变量(和外界都没有关系)
@2、如果不是自己私有的,则去上级上下文中查找,如果是上级的,则操作的都是上级的
@3、如果也不是上级的,则继续找上级的上级的上下文....直到找到EC(G)为止
@4 如果EC(G)中也没有,则:
- 如果是获取变量值,则报错
- 如果是设置变量值,则相当于给window设置对应的属性
function fn() {
/* fn执行,形成私有上下文EC(FN)
* AO(FN):
* 作用域链:<EC(FN),EC(G)>
* 形参赋值:--
* 变量提升:--
* 代码执行
*/
console.log(n) // 既不是形参,也没有在这个上下文中声明过,所以AO(FN)中没有,沿着作用域链往上找,全局GO也没有,所以会报错:n is not defined
}
fn()
如果是:
function fn() {
n = 100 // 相当于给GO中设置一个属性n,值是100 --> window.n = 100
}
fn()
console.log(n, window.n) // 100 100