阐述几个概念
1. ECStack ( Execution Context Stack)执行环境栈
浏览器会在计算机内存中分配一块内存,专门用来供代码执行的
2. Heap堆内存
存放东西(存放对象和方法即引用类型)
3. EC ( Execution Context ) 执行上下文
代码自己执行所在的环境
(1)全局的执行上下文EC(G)
(2)函数中的代码都会在一个单独的私有的执行 上下文中处理
(3)块级的执行上下文
4. GO ( Global Object )全局对象
浏览器端会让window指向GO,浏览器把内置的一些属性方法放到一个单独的内存中堆内存( Heap )
5. VO ( Varibale Object )变量对象
在当前的上下文中,用来存放创建的变量和值的地方(每一个执行 上下文中都会有一个自己的变量对象,函数私有上下文中叫做AO ( Activation Object ) 活动对象,但是也是变量对象)
AO是VO的一个分支,都是变量对象
AO是活动对象,函数中的变量对象都称为AO
JS引擎执行过程
关于JS的引擎,大家应该都知道现在最著名的V8引擎了吧,V8引擎是现在chrome浏览器的JS引擎也是NodeJs的引擎。那么这个引擎究竟是何方神圣呢?其实就是一个堆和一个栈。说详细一点就是一个内存堆和一个调用栈,那么下图就是JS引擎的一个模型图
在JS的调用栈中,说白了就是一个个函数的执行,在得到JS代码之后,首先会向栈底放入全局代码,然后遇到代码中的函数就会入栈该数并执行,基本类型的变量是直接存在栈里面的,引用类型存在堆内存中,引用类型在堆中的地址是存放在栈内的。首先将全局代码入栈,在执行它的时候,遇到了其他函数,就将函数入栈并执行,如果发现函数调用了其他的函数,再入栈执行,没有调用了,就在执行完后开始退栈。
JS引擎执行的三个阶段(敲重点):
1. 语法分析阶段
这个阶段是在JS代码加载完毕后最早开始执行,目的是为了检查JS代码的语法错误,如果检查到代码有语法错误,则抛出一个语法错误,若检查完毕且没有错误,则进入预编译阶段。
2. 预编译阶段
在全局环境和函数环境会创建一个相应的执行上下文,且在上下文中进行了三件事情
创建变量对象VO(Variable Object)、建立作用域链(Scope Chain)、确定this指向
创建变量对象要经过以下几步(变量提升)
(1)检查当前上下文中的函数声明,按照代码顺序查找当前上下文中的函数声明,并且将其提前声明,在当前上下文的VO中,以函数名称建立一个属性,该属性的值为这个函数的堆内存指针。
(2)检查当前上下文的变量声明,也是按照代码顺序进行查找,若找到,则在当前VO中,建立一个以该变量为名称的属性,并且初始化值为undefined 。
在此阶段,函数还没有执行,但是已经做好函数执行的准备工作,所以变量对象是无法访问的,在进入执行阶段后,变量对象中的变量属性就会被赋值,变量对象就会变成活动对象(ActiveObject),这些变量就可以进行访问了。
来,举个例子:
function main(a, b) {
const c = 10
function sub() {
console.log(c)
}
}
main(1, 2)
// 在调用栈的最底层有一个全局执行上下文,我们将其命名为mainEC,它的结构如下
mainEc = {
VO: {
arguments: { // arguments对象,真实情况不一定是对象,在浏览器中是类数组
a: undefined, // 形参a
b: undefined, // 形参b
length: 2 // 参数长度为2
},
sub: <sub reference>, // sub函数及其指针
c: undefined // 变量c
},
scopeChain: [], // 作用域链
this: window // this指向
}
作用域链由当前执行上下文的VO或AO和上一层上下文的AO组成,一直到全局上下文的AO
来,举例子:
const num = 10
function main() {
const a = 1
function sub() {
const b = 2
return a + b
}
sub()
}
main()
//sub函数的执行上下文
subEC = {
VO: { // 变量对象
b: undefined
},
scopeChain: [VO(subEC), AO(main), AO(global)], // 作用域链
this: window // this指向
}
3. 执行阶段即事件循环
(1)首先,会把所有代码分为同步任务、异步任务两部分,同步任务会直接进入执行栈依次执行,异步任务会进入异步队列然后再分为宏任务和微任务。
(2)宏任务进入到 宏任务队列。微任务进入微任务队列。
(3)当执行栈内的任务执行完毕,会检查微任务队列,如果有任务,就全部执行,如果没有就执行下一个宏任务。
注意,每执行一个宏任务就清空微任务队列。所以宏任务是一个个执行,微任务是全部执行。
以下为代码执行内存示意图