深入JavaScript运行原理

274 阅读5分钟

一、深入V8引擎原理

1.1 V8引擎执行代码的流程

  • 官方V8引擎的定义
    • V8是用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于ChromeNode.js等。
    • 它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32,ARM或MIPS处理器的Linux系统上运行。
    • V8可以独立运行,也可以嵌入到任何C ++应用程序中

1.2 V8引擎其他内容补充

image.png

  • 核心3个模块
    • Parse模块 将JavaScript代码转成AST Tree
    • Ignition :解释器 将ASTTree 转换为字节码(byte Code)
      • 同时收集TurboFan 优化需要的信息
    • TurboFan :编译器 将字节码编译为CPU可以直接执行的机器码(machine code)
      • 如果某一个函数呗被多次调用 则会被标记为热点函数 会经过TurBoFan转换的优化的机器码 让CPU执行 提高代码性能
      • 如果后续执行代码过程中 改函数调用时的参数类型发生了改变 则会逆向的转成字节码 让CPU执行
  • 执行流程:
    1. 词法分析(scanner)

      • 会将对应的每一行的代码的字节流分解成有意义的代码块 代码块被称为词法单元(token 进行记号化)
    2. 语法分析(parser)

      • 将对应的tokens分析成一个元素逐级嵌套的树 这个树称之为 抽象语法树(Abstract Syntax Tree AST)
      • 这里也有对应的 pre-parser
    3. 将AST 通过Ignition解释器转换成对应的字节码(ByteCode) 交给CPU执行 同时收集信息

      • 将可优化的信息 通过TurBoFan编译器 编译成更好使用的机器码交给CPU执行
      • 如果后续代码的参数类型发生改变 则会逆优化(Deoptimization)为字节码

二、执行上下文

2.1 初始化全局对象

  • js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)

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

2.2 执行上下文

  • js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈,执行的是全局的代码块:

    • 全局的代码块为了执行会构建一个全局执行上下文Global Execution Context(GEC),GEC会 被放入到ECS中 执行
  • GEC被放入到ECS中里面包含两部分内容:

    • 在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值

      • 这个过程也称之为变量的作用域提升(hoisting)
    • 在代码执行中,对变量赋值,或者执行其他的函数

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

    • 当全局代码被执行的时候,VO就是GO对象

三、全局代码执行过程

var message = "Global Message"

function foo() {
  var message = "Foo Message"
}

var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
  • 代码执行前 image.png
  • 代码执行过程 image.png

四、函数代码执行过程

  • 执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到EC Stack中。

  • 每个执行上下文都会关联一个VO,当进入一个函数执行上下文时,会创建一个AO对象(Activation Object)

    • AO对象会使用arguments作为初始化,并且初始值是传入的参数

    • AO对象会作为执行上下文的VO来存放变量的初始化

4.1 普通函数执行

var message = "Global Message"

function foo(num) {
  var message = "Foo Message"
  var age = 18
  var height = 1.88
  console.log("foo function")
}

foo(123)

var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
  • 代码执行前

    image.png

  • 代码执行过程

    image.png

4.2 函数多次执行

var message = "Global Message"

function foo(num) {
  var message = "Foo Message"
  var age = 18
  var height = 1.88
  console.log("foo function")
}

foo(123)
foo(321)
foo(111)
foo(222)

var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
  • 执行foo(123)

    image.png

  • 执行foo(321)

    • 会重新创建AO
    • 执行完,EC弹出栈 image.png

4.3 函数相互调用

var message = "Global Message"
var obj = {
  name: "zhangsan"
}

function test() {

}

function bar() {
  console.log("bar function")
  test()
  test()
  var address = "bar"
}

function foo(num) {
  var message = "Foo Message"

  bar()

  var age = 18
  var height = 1.88
  console.log("foo function")
}

foo(123)

var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
  • 全局代码执行,foo和bar函数执行

    image.png

  • bar函数执行完毕

    image.png

  • foo函数执行完毕

    image.png

五、作用域作用域链

5.1 概念

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

    • 作用域链是一个对象列表,用于变量标识符的求值

    • 当进入一个执行上下文时,这个作用域链被创建,并且根据代码类型,添加一系列的对象

5.2 函数代码查找变量

  • 注意:函数在创建的那一刻就已经确定了自己的作用域链,跟调用位置无关

    var message = "Global Message"
    
    function foo() {
      var name = "foo"
      setTimeout(function() {
        console.log(name, message)
      }, 3000)
    }
    
    foo() // foo Global Message
    
  • 普通函数

    var message = "Global Message"
    
    function foo() {
      console.log(message)
    }
    
    foo()
    
    var obj = {
      name: "obj",
      bar: function() {
        var message = "bar message"
        foo()
      }
    }
    
    obj.bar() // Global Message
    
    • 查找过程

      image.png

  • 函数多层嵌套

    var message = "global message"
    
    function foo() {
      var name = "foo"
      function bar() {
        console.log(name)
      }
      return bar
    }
    
    var bar = foo()
    bar()
    
    • 查找过程

      image.png

5.4 面试题

// 1.面试题一:
// var n = 100
// function foo() {
//   n = 200
// }
// foo()

// console.log(n) // 200

// 2.面试题二:
// var n = 100
// function foo() {
//   console.log(n) // undefined
//   var n = 200
//   console.log(n) // 200
// }

// foo()

// 3.面试题三:
// var n = 100

// function foo1() {
//   console.log(n) // 100
// }
// function foo2() {
//   var n = 200
//   console.log(n) // 200
//   foo1()
// }
// foo2()

// 4.面试题四:
// var n = 100
// function foo() {
//   console.log(n) // undefined
//   return
//   var n = 200
// }
// foo()

// 5.在开发中可能会出现这样错误的写法
// function foo() {
//   message = "Hello World"
// }
// foo()
// console.log(message)

// 6.面试题五:
function foo() {
  var a = b = 100
  // 1. var a 定义
  // 2. a = b 赋值(=赋值从右往左)
  // 3. b = 100
}
foo()
// console.log(a) // a is not defined
console.log(b)