JavaScript预编译和堆栈空间
一、JS代码执行前做了一些事情
- 预编译前奏:确定函数环境(它是一个抽象的概念)
- 预编译: 确定当前执行上下文对象(执行环境),声明提前并在内存开辟空间,存储变量和函数
JS是静态作用域检测,代码一旦写好,作用域就确定了
二、栈空间 和 函数环境
栈是一种数据结果,先进后出。大家都知道函数是可以嵌套调用执行的,假设一个函数A调用函数B,当函数B执行完,应该要回到函数代码A继续执行,为了知道函数B执行完成之后应该回到哪个位置,那么在执行函数B之前应该先记录断点(汇编的东西)
当函数B执行完成,代码要回到断点继续执行,那我们应该如果保存断点呢?要知道函数是可以嵌套调用的!
用栈记录断点位置,当最内层的函数执行完毕,栈的最上层保存的位置信息弹出,并转换代码执行的位置(可以看作一个指针,指针指向的代码开始继续执行)
但是不光要记录执行位置,还要记录该函数的变量,当恢复到上层函数时要保存当初的环境
于是出现了函数环境这个概念,每一个函数调用的时候都应该有一个函数环境以保存其执行时分配的内存变量,如果函数内部发送函数调用需要先将当前函数环境保存,确保内部发生函数调用结束后,能够恢复现场
这个存储环境的过程和断点几乎一致,因此我们可以这么理解:
假设一段伪代码:
声明变量 var0
函数 A() {
声明变量 var1
函数 B()
声明变量 var2
}
函数 B() {
声明变量 var3
函数 C()
声明变量 var4
}
函数 C() {
声明变量 var5
}
//执行函数 A
函数A()
我们写一下步骤:
- 全局代码执行前,确定全局环境将环境压入环境栈中(其实只是默认从下到上的使用栈空间),在栈中开辟环境空间为了后面保存变量
- 执行全局环境代码,声明变量保存,调用函数A,同理:函数A代码执行前,确定A函数环境并压入环境栈中,…调用其它函数也一样
- 一直到函数C执行完成,这个时候要销毁环境C,因此环境C的内存要全部销毁(置0,或者不在监视,意味着可以不管),环境C从栈顶弹出,变量也无法再次访问
- 其它函数调用完成也是如此,知道全局代码执行完成,环境栈为空,停止执行代码
闭包有一些特殊性,后面再讲,闭包并不和栈的执行冲突
三、堆空间
根据上面的栈,你应该了解到,语言如果没有堆只有栈的话:函数执行完成无法保留数据,只能根据返回值复制一份。
堆空间特性:
- 存储拥有复杂数据结构的数据
- 创建在堆内,和执行栈的执行环境的入栈出栈不相关,垃圾回收机制决定是否回收无用的内存
JavaScript的对象类型的数据都保存在堆中
执行上下文(执行环境)是存在的对象,是规范中的存在和实现,JS无法访问,无法操作,只不过大家都这么叫,我们这篇讲的是函数环境,是内存上的实现,要注意区分
每个函数都有对应的执行环境,它定义了变量或者函数有权访问的数据,决定他们各自的行为