JavaScript的运行机制

157 阅读5分钟

阐述几个概念

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)当执行栈内的任务执行完毕,会检查微任务队列,如果有任务,就全部执行,如果没有就执行下一个宏任务。

注意,每执行一个宏任务就清空微任务队列。所以宏任务是一个个执行,微任务是全部执行。


以下为代码执行内存示意图 在这里插入图片描述