JS高级 - JS基本运行原理

109 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

V8是用C ++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js

V8可以独立运行,也可以嵌入到任何C ++应用程序中,同时V8也是可以跨平台

image.png

  1. 字节码是一种是一种中间码与特定软件运行和软件环境、与硬件环境无关

    需要经过编译器(比如虚拟机)转译后才能转换为计算机可以直接识别的机器码

  2. 在编译执行JS的时候,浏览器会收集相关信息,如类型信息

function sum(num1, num2) {
  console.log(num1 + num2)
}

// 以下所执行的操作,都是对两个数进行求和操作
// 所以JS代码在运行过程中,可能会出现重复性的执行指令,如函数的多次调用,循环体中的语句等
// 如果每次都先将他们转换为对应的字节码后在转换成机器码,这明显是没有必要的,因为他们对应的机器码都是一致的
// 所以V8会将这部分代码转换为经过优化的机器码,并将对应的机器码保存下来,以便于可以直接执行对应的CPU指令
sum(10, 20)
sum(40, 60)
sum(20, 50)

// 但JS是弱数据类型语言, 所以对于同一个语句,对于不同数据类型的变量,其所对应的CPU指令是不一样的
// 例如下行代码就不是两数之和  而是进行字符串的拼接
// 所以此时之前编译出来的那个经过优化的机器码就不再适用
// 所以此时V8会这这类机器码进行反优化操作
// 也就是将这些机器码反向编译为对应的字节码
// 在重新编译形成我们所需要的新的机器码,这个过程被称之为Deoptimization
sum('hello ', 'world')

// 反优化 是一种比较消耗性能的操作,所以在实际开发中,应该避免这种行为
// 这也就是为什么,TypeScript会通过类型注解的方式尽可能在编译阶段就对我们所使用的变量的数据类型进行限制

完整流程

image.png

  1. 浏览器通过网络请求,缓存等形式拿到对应的JS代码后 会将对应的JS代码以字节流的形式进行读取

  2. JS通过Parse模块对JS代码进行词法分析和语法分析

    Parse模块会将JavaScript代码转换成AST(抽象语法树)

    这是因为Ignition解释器并不直接识别JS代码

    ps: 如果函数没有被调用,那么是不会被转换成AST的

    • 词法分析(英文lexical analysis)

      • 通过词法分析器(lexical analyzer,简称lexer)可以将字符序列转换成token序列

        这个过程被称之为记号化(tokenization)

      • lexer也可以被称之为扫描器(scanner)

    let num = 20
    

    转换后

    [
        {
            "type": "Keyword",
            "value": "let"
        },
        {
            "type": "Identifier",
            "value": "num"
        },
        {
            "type": "Punctuator",
            "value": "="
        },
        {
            "type": "Numeric",
            "value": "20"
        }
    ]
    
    • 语法分析(syntactic analysis,也叫 parsing)

    Parser模块会将上述的token序列转换为对应的语法描述对象

    {
      "type": "Program",
      "body": [
        {
          "type": "VariableDeclaration",
          "declarations": [
            {
              "type": "VariableDeclarator",
              "id": {
                "type": "Identifier",
                "name": "num"
              },
              "init": {
                "type": "Literal",
                "value": 20,
                "raw": "20"
              }
            }
          ],
          "kind": "let"
        }
      ],
      "sourceType": "script"
    }
    

    最后 Parser模块会将对于的语法表示对象抽象为一颗语法树(AST abstract syntax tree)

    1. Ignition是一个解释器,会将AST转换成ByteCode(字节码)

      • 同时会收集TurboFan优化所需要的信息
      • 如果函数只调用一次,Ignition会解释执行ByteCode
    2. TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码

      • 如果一个函数被多次调用,那么就会被标记为热点函数

        那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能

      • 但是,机器码实际上也会被还原为ByteCode

        • 这是因为如果后续执行函数的过程中,类型发生了变化

        • (比如sum函数原来执 行的是number类型,后来执行变成了string类型)

        • 之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码