栈空间和堆空间
JS 是什么类型的语言
- 弱类型:支持隐式类型转换
- 动态:可以使用同一个变量保存不同类型的数据,在运行过程中会检查数据类型
function foo(){
var a = "极客时间"
var b = a
var c = {name:"极客时间"}
var d = c
}
foo()
JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率。
闭包
产生闭包的核心:
- 预扫描内部函数
- 把内部函数引用的外部变量保存到堆中
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
setName:function(newName){
myName = newName
},
getName:function(){
console.log(test1)
return myName
}
}
return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
当 foo 函数的执行上下文被销毁时,闭包引用的变量仍存在在堆中,所以存在内存问题垃圾回收
由垃圾回收器管理
调用栈中的数据回收
当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。 记录当前执行状态的指针(称为 ESP)
堆中的数据回收
由垃圾回收器控制
代际假说和分代收集
代际假说(the generational hypothesis):很多对象在内存中存在的时间很短(die young)
V8 中把堆分为新生代和老生代两个区域:
- 标记是否活动对象
- 回收非活动对象内存
- 内存整理
副垃圾回收器
Scavenge 算法:在对象区域进行垃圾回收流程后,把存活的对象复制到空闲区域,然后角色翻转,原本的对象区域成为空闲区域,空闲区域成为对象区域。
主垃圾回收器
全停顿
增量标记算法(Incremental Marking)
V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成
编译器和解释器
V8 如何执行 JS 代码
- 初始化基础环境
- 解析源码生成 AST 和作用域
- 依据 AST 和作用域生成字节码
- 解释执行字节码
- 监听热点代码
- 优化热点代码为二进制的机器代码
- 反优化生成的二进制机器代码
生成字节码
字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。 为了减少系统的内存使用
执行代码
在解释器( Ignition )执行字节码的过程中,如果发现有热点代码(HotSpot),比如一段代码被重复执行多次,这种就称为热点代码,那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。
即时编译(JIT)技术
混合使用编译器和解释器的技术称为 JIT(Just In Time)技术