执行上下文
一段JS代码在执行之前需要被JavaScript引擎编译。 在编译阶段,变量和函数会被存放到执行上下文中(变量提升
),变量的默认值会被设置为undefined
。在代码执行阶段,JavaScript引擎会从执行上下文中去查找自定义的变量和函数。
三种创建上下文的场景
- 当JavaScript执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
- 当使用
eval
函数的时候,eval
内的代码也会被编译,并创建执行上下文。
内存结构
原始类型的数据值都是直接保存在栈中的,引用类型的值是存放在堆中的,防止栈空间内容过多影响上下文切换效率
赋值时,原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。即上图中d=c
指令只是把1003赋值给了d,让d和c指向同一对象
- 通过 Chrome开发者工具=>Memory=>take snapshot 可以查看当前内存堆栈情况
调用栈
JS引擎通过栈管理执行上下文。每当一个上下文被创建,都会被压入栈中。如下脚本中会依次入栈,然后在执行中依次出栈。
var a = 2
function add(b,c){
return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return a+result+d
}
addAll(3,6)
通过console.trace()
或Chrome开发者工具中打断点后的Call Stack
按钮可以查看函数调用关系。
栈溢出(Stack Overflow)
调用栈是有大小的,当无限递归或递归次数过大时就会溢出。可以通过循环代替递归的方式解决因递归次数过大时引起的溢出。
垃圾回收
栈回收
栈中的数据随脚本执行,无效内存会被覆盖
堆回收
堆中的数据分为新生区和老生区,两者具有不同的回收策略
- 新生区中存放生存时间短、占用空间小的对象
新生区通常只支持 1~8M 的容量
新生区中经过两次回收依然存活的对象,会被移动到老生区 - 老生区中存放生存时间久、占用空间大的对象
全停顿
触发垃圾回收时JS线程会被暂停,通常V8通过增量标记(Incremental Marking)算法
将一个完整的回收拆分成多个子任务穿插在其他JS任务中执行,避免卡顿。
块级作用域
通过let
和const
支持块级作用域,以解决变量提升存在的变量覆盖、变量污染等设计缺陷。
作用块内声明的变量不影响块外面的变量。
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
- 暂时性死区
可以认为let
/const
也存在变量提升,但提升后在赋值前不可被访问,称为暂时性死区,此时访问会导致报错。
{
console.log(myname)//Uncaught ReferenceError: Cannot access 'myname' before initialization
let myname= '极客邦'
};
网友评论