原理解析:JS 代码是如何被浏览器引擎编译、执行的?

2023-10-27

原理解析:JS 代码是如何被浏览器引擎编译、执行的?

  分析浏览器引擎对 JS代码的编译情况,并结合日常的 JavaScript开发经验,重新理解底层的编译解析机制。对其底层原理的理解,将有助于理解前端的跨端应用,以及一套代码生成多种小程序相关框架的底层逻辑.

在开始前请先思考:

  1. JavaScript代码被执行分为哪几个阶段?
  2. AST到底是做什么用的?

V8 引擎介绍

  当前百花齐放的编程语言,主要分为编译型语言和解释型语言。

  1. 编译型语言的特点是在代码运行前编译器直接将对应的代码转换成机器码,运行时不需要再重新翻译,直接可以使用编译后的结果。
  2. 解释型语言也是需要将代码转换成机器码,但是和编译型的区别在于运行时需要转换。比较显著的特点是,解释型语言的执行速度要慢于编译型语言,因为解释型语言每次执行都需要把源码转换一次才能执行。

  像JavaC++ 都是编译型语言;而 JavaScriptruby都是解释性语言,它们整体的执行速度都会略慢于编译型的语言。

  为了提高运行效率,很多浏览器厂商在也在不断努力。目前市面上有很多种 JS 引擎,例如 JavaScriptCorechakraV8等。而比较现代的 JS引擎,当数 V8,它引入了 Java虚拟机和 C++ 编译器的众多技术,和早期的 JS引擎工作方式已经有了很大的不同。

  V8 是众多浏览器的 JS 引擎中性能表现最好的一个,并且它是 Chrome 的内核,Node.js 也是基于 V8引擎研发的,V8引擎很具有代表性。

   V8 引擎执行 JS代码都要经过哪些阶段

  1. Parse阶段:V8 引擎负责将 JS代码转换成 AST(抽象语法树);
  2. Ignition阶段:解释器将 AST转换为字节码,解析执行字节码也会为下一个阶段优化编译提供需要的信息;
  3. TurboFan阶段:编译器利用上个阶段收集的信息,将字节码优化为可以执行的机器码;
  4. Orinoco阶段:垃圾回收阶段,将程序中不再使用的内存空间进行回收。

  其中,生成 AST、生成字节码、生成机器码是比较重要的三个阶段。

生成 AST

  身为一名前端开发,肯定在日常工作中用过 EslintBabel这两个工具,这些工具每个都和 AST脱不了干系。V8引擎就是通过编译器(Parse)将源代码解析成 AST的。

  AST 在实际工作中应用场景也比较多,我们来看看抽象语法树的应用场景,大致有下面几个:

  1. JS反编译,语法解析;
  2. Babel编译 ES6语法;
  3. 代码高亮;
  4. 关键字匹配;
  5. 代码压缩。

  这些场景的实现,都离不开通过将 JS代码解析成 AST来实现。生成 AST分为两个阶段,一是词法分析,二是语法分析,我们来分别看下这两个阶段的情况。

  1. 词法分析:这个阶段会将源代码拆成最小的、不可再分的词法单元,称为 token。比如这行代码 var a =1;通常会被分解成var 、a、=、1、; 这五个词法单元。另外刚才代码中的空格在 JavaScript中是直接忽略的。
  2. 语法分析:这个过程是将词法单元转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为抽象语法树。

  简单看一下解析成抽象语法树之后是什么样子的,代码如下。

// 第一段代码
var a = 1;

// 第二段代码
function sum (a,b) {
  return a + b;
}

  将这两段代码,分别转换成 AST抽象语法树之后返回的 JSON 格式如下。

第一段代码,编译后的结果:

{
  "type": "Program",
  "start": 0,
  "end": 10,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 10,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 9,
          "id": {
            "type": "Identifier",
            "start": 4,
            "end": 5,
            "name": "a"
          },
          "init": {
            "type": "Literal",
            "start": 8,
            "end": 9,
            "value": 1,
            "raw": "1"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

第二段代码,编译出来的结果:

{
  "type": "Program",
  "start": 0,
  "end": 38,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 38,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 12,
        "name": "sum"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 14,
          "end": 15,
          "name": "a"
        },
        {
          "type": "Identifier",
          "start": 16,
          "end": 17,
          "name": "b"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 19,
        "end": 38,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 23,
            "end": 36,
            "argument": {
              "type": "BinaryExpression",
              "start": 30,
              "end": 35,
              "left": {
                "type": "Identifier",
                "start": 30,
                "end": 31,
                "name": "a"
              },
              "operator": "+",
              "right": {
                "type": "Identifier",
                "start": 34,
                "end": 35,
                "name": "b"
              }
            }
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}

  从上面编译出的结果可以看到,AST只是源代码语法结构的一种抽象的表示形式,计算机也不会去直接去识别 JS代码,转换成抽象语法树也只是识别这一过程中的第一步。

  前面提到了前端领域经常使用一个工具 Babel,比如现在浏览器还不支持 ES6语法,需要将其转换成 ES5语法,这个过程就要借助 Babel来实现。将 ES6源码解析成 AST,再将 ES6语法的抽象语法树转成 ES5的抽象语法树,最后利用它来生成 ES5的源代码。另外 ESlint的原理也大致相同,检测流程也是将源码转换成抽象语法树,再利用它来检测代码规范。

  抽象语法树是一个很重要的概念,需要深入了解,才能更好地帮助我们理解所写代码。如果你想自己把代码翻译成 AST,我给你提供一个地址,代码帖进去就可以转换成相应的 ASTAST 在线转换

生成字节码

  将抽象语法树转换为字节码,也就是上面提到的 Ignition阶段。这个阶段就是将 AST 转换为字节码,但是之前的 V8版本不会经过这个过程,最早只是通过 AST直接转换成机器码,而后面几个版本中才对此进行了改进。如果将 AST直接转换为机器码还是会有一些问题存在的,例如:

  1. 直接转换会带来内存占用过大的问题,因为将抽象语法树全部生成了机器码,而机器码相比字节码占用的内存多了很多;
  2. 某些 JavaScript 使用场景使用解释器更为合适,解析成字节码,有些代码没必要生成机器码,进而尽可能减少了占用内存过大的问题。

  而后,官方在 V8v5.6 版本中还是将抽象语法树转换成字节码这一过程又加上了,重新加入了字节码的处理过程。再然后,V8重新引进了 Ignition解释器,将抽象语法树转换成字节码后,内存占用显著下降了,同时也可以使用 JIT编译器做进一步的优化。

  其实字节码是介于 AST和机器码之间的一种代码,需要将其转换成机器码后才能执行,字节码可以理解为是机器码的一种抽象。Ignition解释器除了可以快速生成没有优化的字节码外,还可以执行部分字节码。

生成机器码

  在 Ignition解释器处理完之后,如果发现一段代码被重复执行多次的情况,生成的字节码以及分析数据会传给 TurboFan编译器,它会根据分析数据的情况生成优化好的机器码。再执行这段代码之后,只需要直接执行编译后的机器码,这样性能就会更好。

  这里简单说一下 TurboFan编译器,它是 JIT优化的编译器,因为 V8引擎是多线程的,TurboFan 的编译线程和生成字节码不会在同一个线程上,这样可以和 Ignition解释器相互配合着使用,不受另一方的影响。

  由 Ignition解释器收集的分析数据被 TurboFan编译器使用,主要是通过一种推测优化的技术,生成已经优化的机器码来执行。这个过程我只是通过文字描述,可能你很难理解,你通过一张图来看下整个生成抽象语法树,再到转换成字节码以及机器码的一个过程。

在这里插入图片描述

总结

JavaScript代码被执行分为哪几个阶段?

  1. Parse阶段:V8 引擎负责将 JS代码转换成 AST(抽象语法树);
  2. Ignition阶段:解释器将 AST转换为字节码,解析执行字节码也会为下一个阶段优化编译提供需要的信息;
  3. TurboFan阶段:编译器利用上个阶段收集的信息,将字节码优化为可以执行的机器码;
  4. Orinoco阶段:垃圾回收阶段,将程序中不再使用的内存空间进行回收。

AST到底是做什么用的?

  1. JS反编译,语法解析;
  2. Babel编译 ES6语法;
  3. 代码高亮;
  4. 关键字匹配;
  5. 代码压缩。

生成 AST 的阶段

  1. 词法分析:这个阶段会将源代码拆成最小的、不可再分的词法单元,称为 token
  2. 语法分析:这个过程是将词法单元转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为抽象语法树。

  目前市面上比较主流的 JavaScript引擎编译过程大部分都类似,主要原因可能在于某些地方加入了特定的优化,但是其核心思路和 V8大体上是一致的

  AST 是比较重要的一个知识点,只有学习了 AST 之后,你才能在自己实现前端工具上面游刃有余。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

原理解析:JS 代码是如何被浏览器引擎编译、执行的? 的相关文章

随机推荐

  • Flink 报错:写入数据到流加载失败

    近年来 随着大数据技术的飞速发展 Apache Flink 成为了一个广泛应用的流式处理框架 然而 在使用 Flink 进行数据处理时 有时候我们可能会遇到一些错误和异常 其中一种常见的问题是 Writing records to stre
  • mfc 一些杂七杂八的东西

    cstring CString Left int nCount const 从左边1开始获取前 nCount 个字符 CString Mid int nFirst const 从左边第 nCount 1 个字符开始 获取后面所有的字符 CS
  • Qt多个ui界面,如何建立联系

    https blog csdn net qq 41399894 article details 87460230 一 最简单的方法 无非就是你建了多个ui界面 然后你只需要new它 获得它的地址信息 就可以建立联系了 如下 在MainWin
  • 酒浓码浓 - React事件阻止浏览器默认行为/冒泡

    React事件行为 React中无法用return false去阻止事件的默认响应行为 必须用 event preventDefault 阻止浏览器默认行为 例如标签不跳转 注 IE不认 在IE下需要用window event return
  • java的图片背景透明及透明度处理

    如题 以下为通过java实现的针对图片的背景透明及透明度处理 供大家需要时参考 设置源图片为背景透明 并设置透明度 param srcFile 源图片 param desFile 目标文件 param alpha 透明度 param for
  • activex控件 InvokeHelper

    当你调用关于activex控件中的相关方法时 你要导入此控件到程序中 此时就会在工程中生成一个关于此控件调用的一个伪调用类 其中的cpp中调用每 个方法都是通过InvokeHelper调用其中的dwDispID值来定位方法的地址的 因此 可
  • exports is not defined

    若是babel 6 可以看这位同仁的文章 https www cnblogs com vickya p 8645061 html 若是babel 7 设置 https www babeljs cn docs babel preset env
  • 【Python】文件操作 r+ 的问题

    问题背景 想用 python 实现文件的读取 并修改部分内容 再写回去 r 是最符合的权限 可读写 并且可以覆盖文件之前的内容 但是实际使用时 发现修改后的内容是追加的方式 而不是覆盖 with open gitignore r as f
  • CENTOS上的网络安全工具(二十四)Windows下的Hadoop+Spark编程环境构建

    前面我们搭建了hadoop集群 spark集群 也利用容器构建了spark的编程环境 但是一般来说 就并行计算程序的开发 一刚开始一般是在单机上的 比如hadoop的single node 但是老师弄个容器或虚拟机用vscode远程访问式开
  • MFC定时器SetTimer函数

    一 SetTimer表示的是定义个定时器 根据定义指定的窗口 在指定的窗口 CWnd 中实现OnTimer事件 这样 就可以相应事件了 SetTimer有两个函数 一个是全局的函数 SetTimer UINT SetTimer HWND h
  • C语言上机实验思路分享4

    实验内容 方法和步骤 1 输入 10 个整数 用选择法对这 10 个整数按从小到大的顺序排序并输出排序后的结 果 程序代码 include
  • 从现实抽象出类的步骤

    第一 找出分类 分析出类 第二 找出类的特征 分析类的相关属性 第三 找出类的行为 分析类的方法 转载于 https www cnblogs com liumeilin p 7018110 html
  • AVRCP协议介绍

    文章目录 1 AVRCP协议介绍 1 2 概念 1 2 1 1 2 2 role 用途 2 AVRCP框架 1 AVRCP协议介绍 1 2 概念 1 2 1 1 2 2 role CT controller 是一种通过向目标发送命令帧来启动
  • 静态编译和动态编译,java与javascript区别总结

    1 静态编译和动态编译 静态编译是程序在编译时就已经确定好了所有类之间的关系 要运行程序所有类 都缺一不可 若在开始运行时就把其中的某类文件丢失 就会产生 NoClassDefFoundError错误 程序会终止 在程序运行前的装载期间就把
  • flutter获取状态栏高度

    获取状态栏高度 import dart ui MediaQueryData fromWindow window padding top 系统默认的appBar等高度 位于Dart Packages flutter src material
  • 物理渲染学习笔记(三)——Cook-Torrance微表面模型

    从 Phong 到 GGX 光照模型林林总总 一直没能找机会梳理一遍 这几天依次都自己实现了一遍 也正好总结下 Microfacet 普通的着色模型假设着色的区域是一个平滑的表面 表面的方向可以用一个单一的法线向量来定义来定义 而 Micr
  • 程序员吃青春饭?程序员在35岁以后是否需要转行?你规划好了吗?

    都说程序员是一个吃青春饭的职业 都认为程序员到了35岁以后不转管理岗位就没有什么前途了 可能就要考虑换别的行业了 年龄越大可能越写不动代码了 那么程序员是不是35岁以后需要转行 我说说我自己的观点 关于程序员35岁之后是不是要转行这个问题
  • 区块链技术基础(笔记)

    一 区块链本质上是一个对等网络 peer to peer 的分布式账本数据库 二 区块链本身其实是一串链接的数据区块 其链接 指针是采用密码学哈希算法对区块头进行处理所产生的区块头哈希值 三 基本概念 1 数据区块 比特币的交易会保存在数据
  • Element ui 导航栏 刷新时高亮

    1 在组件中
  • 原理解析:JS 代码是如何被浏览器引擎编译、执行的?

    原理解析 JS 代码是如何被浏览器引擎编译 执行的 分析浏览器引擎对 JS代码的编译情况 并结合日常的 JavaScript开发经验 重新理解底层的编译解析机制 对其底层原理的理解 将有助于理解前端的跨端应用 以及一套代码生成多种小程序相关