浅谈JS执行原理

132 阅读5分钟

JavaScript是一种解释型语言,它的运行原理是将JavaScript代码解释成计算机可以理解的机器码,然后由计算机执行。

JavaScript代码的执行过程分为两个阶段:编译阶段和执行阶段。

在编译阶段,JavaScript引擎会对代码进行词法分析和语法分析,生成抽象语法树(AST),然后将AST转换成字节码或者机器码。

在执行阶段,JavaScript引擎会执行生成的字节码或者机器码,将代码转换成计算机可以理解的指令,然后由计算机执行。

JavaScript引擎在执行代码时,会使用一种叫做“即时编译”(Just-In-Time Compilation,JIT)的技术,将代码动态编译成机器码,以提高代码的执行效率。

JavaScript引擎还会使用一种叫做“垃圾回收”(Garbage Collection,GC)的技术,自动回收不再使用的内存,以避免内存泄漏和内存溢出的问题。

综上所述,JavaScript的运行原理是将JavaScript代码解释成计算机可以理解的机器码,然后由计算机执行,JavaScript引擎会使用即时编译和垃圾回收等技术来提高代码的执行效率和避免内存泄漏和内存溢出的问题。

执行JS代码发生了什么

执行全局代码

初始化全局对象

js引擎会在执行代码前,会在堆内存中创建一个创建一个全局对象(global object),比如浏览器的window对象。

  • 这个对象所有的作用域都可以访问。
  • 里面会包含Date、Array、String、Number、setTimeOut、setInterval等。
  • 其中还有个window属性指向自己。

执行上下文

js引擎内部有一个执行上下文栈(Execution Context Stack),它是用于执行代码的调用栈。 那么现在它要执行谁呢?执行的是全局的代码

  • 全局的代码块为了执行会构建一个Global Execution Context(GEC)
  • GEC会被放到ESC中执行。

GEC被放入到ESC中包含两部分内容:

  1. 在代码执行前,在parse转成AST过程中,会将全局定义的变量、函数等放到GlobalOject中,但并不会赋值。(变量的提升
  2. 在代码执行过程中,对变量赋值或者执行其他函数。

VO对象(Variable Object)

每个执行上下文会关联一个VO(Variable Object,变量对象),变量和函数的声明会被添加到VO对象里。

当全局代码被执行时,VO就是GO了。

执行函数

在执行的过程中执行到一个函数时,就会根据函数体创建一个函数体执行上下文(Function Execution Context,FEC),并压入到EC中。 前面提到每个执行上下文都会关联一个VO,比如全局的执行上下文关联的VO是GO.那么函数的执行上下文关联的VO我们称之为AO:

  • 当进入一个函数的执行上下文时,会创建一个AO对象(Activation Object).
  • 这个AO对象会使用arguments作为初始化,并且初始值是传入的参数。
  • 这个AO对象会作为执行上下文的VO来存放变量的初始化。

作用域和作用域链

当进入到一个执行上下文时,执行上下文也会关联一个作用域链(Scope Chain)

  • 作用域链是一个对象列表,用于变量标识符的求值。
  • 当你进入一个执行上下文中时,这个作用域链被关联,并根据代码类型,添加一系列的对象。

变量的调用先会在本身关联的VO对象中调取,如果VO对象不存在该值则根据作用域链查找。如果在全局作用域找不到就报错。

        var message="global object"
        
        function foo(){
            console.log(message)  //undefined   
            var message="foo object"
            console.log(message) //foo object
        }
        foo()
        var message="global object"
        
        function foo(){
            console.log(message)  //global object
            message="foo object"
            console.log(message) //foo object
        }
        foo()

在执行的过程中执行到一个函数foo时,就会根据函数体创建一个函数体执行上下文(Function Execution Context,FEC),并压入到EC中。 前面提到每个执行上下文都会关联一个VO,比如全局的执行上下文关联的VO是GO.那么函数的执行上下文关联的VO我们称之为AO:

第一段代码在第一个log里AO对象message为undefined。下一行就将AO对象里的message给改成了foo object。

第二段代码里,foo函数执行上下文关联的AO对象其实是没有message的定义的。所以会根据作用域链查找,知道全局作用域中查到了message。

作用域链在函数被创建的时候就创建了。 js引擎会在执行代码前在堆内存中创建一个创建一个全局对象(global object),里面存放全局定义的变量、函数。内存中函数以foo:内存地址形式放在GO中,内存地址则指向另外一个函数对象foo。

image.png 如图可得函数作用域提升了,在函数代码执行前就在函数对象里中创建了一个作用域链,然后将这个作用域链在函数执行上下文中给关联过去。这样当函数执行上下文代码执行的时候调用message时,就会按照作用域链调用变量值。作用域链和函数定义位置有关与函数调用位置无关。

image.png