
代码奉上
var a = 'hello'
function test() {
console.log(a)
}
test()
解析过程
全局代码
创建阶段
创建全局执行上下文
全局执行上下文 = {
}
预处理阶段
创建全局的变量对象,我们给变量对象取个名字叫做VO(G)
变量对象里面有未赋值的变量,函数,this对象
全局执行上下文 = {
VO(G):{
a: undefined,
test: function,
this: window
}
}
并且会创建函数:
1.会创建一个堆内存,里面存储函数代码字符串
2.会初始化当前函数的作用域scope
= 所在上下文的变量对象
比如说test函数是处于全局上下文里面的,所以test函数的scope就指向了全局上下文的变量对象VO(G)
test函数.[scope] = VO(G)
所以,此刻全局执行上下文如下:
全局执行上下文 = {
VO(G):{
a: undefined,
this: window,
test: function,
test[[scope]]: VO(G)
}
}
创建完毕了全局的变量对象之后,我们的全局代码预处理完毕。
此刻将全局的执行上下文放入执行栈里面,激活该全局上下文,然后就可以开始执行全局代码了!😁
执行阶段
开始执行我们的全局代码:
走到了这一行代码:var a = 'hello'
给变量a赋值hello
值
此时的全局执行上下文的变量对象是:
VO(G):{
a: 'hello',
test: function,
this: window
}
继续走代码
然后走到了这一行代码:test()
,调用我们的函数。
注意!注意!调用函数要搞事情了!🐑
test函数
创建阶段
在调用了函数之后,执行我们的函数代码之前
我们像上面全局代码一样,会先创建一个函数执行上下文
函数执行上下文 = {
}
预处理阶段
然后初始化函数的变量对象,取了名VO(test)
:
1.arguments(就是函数传入的参数,此函数没有参数传入!)
2.变量(此函数里面没有自己的变量!)
3.this对象(因为是window调用的这个函数,所以这个函数的this指向的是window对象!)
以及会初始化作用域链[scopeChain]
,当前作用域(当前变量对象VO(test)
) -> 当前函数的[scope]属性
还记得上面说的,当前函数的[scope]属性是全局执行上下文的变量对象VO(G)
嘛!
函数执行上下文 = {
VO(test):{
arguments:[],
this: window
},
[scope]:VO(G),
[scopeChain]: VO(test) => VO(G)
}
函数的变量对象创建完毕,将函数执行上下文推入执行栈中
此时,该函数执行上下文在执行栈的顶部,激活该函数执行上下文。
执行阶段
开始执行函数内部的代码:console.log(a)
遇到变量a,开始在函数的作用域链里面查找a的值
首先在内部的作用域里面也就是变量对象里面查找a,找不到
再去[[scope]]属性里面查找,也就是全局执行上下文的变量对象里面查找,找到了,a='hello'
所以打印:hello
回收阶段
此时,函数已经执行完毕,函数执行上下文推出执行栈
函数维护的栈内存被回收,栈内存里面存储的值也被回收了,所以如果栈内存里面存储了一些堆内存地址,那么对应的堆内存也会被回收(前提是堆内存地址没有其他的引用了)
此时,全局执行上下文又处于执行栈栈顶,激活全局执行上下文,全局代码继续执行
回收全局代码
全局代码执行完毕,关闭浏览器/tab页,全局执行上下文出栈,回收。
HAPPY ENDING! 🎉
知识点合集
关于回收机制
典型的堆内存的回收机制有如下:
标记法
每隔一段时间就对所有的内存空间地址进行一次检测,
检测到该内存空间地址如果没有被引用,那么浏览器就回收。
计数法
如果该内存空间地址被引用了一次,那么就数字 + 1,
如果没有引用(比如设置了null),那么数字 - 1。
当该数字为0的时候,浏览器就立即将该内存回收!
栈内存的作用是什么?
1.存储基本的数据类型的值,比如说var a = 100
2.存储引用数据类型的指针,也就是堆内存地址,比如说const obj = { name:"rose" }
,obj对象的内存地址就是存在栈内存里面的!
3.给代码提供运行环境,比如说,函数每执行一次,就会生成一个新的栈内存!比如:
function test(){
...
}
test() // 生成一个新的栈内存
test() // 生成一个新的栈内存
test() // 生成一个新的栈内存
资料
