Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 编译执行过程 #26

Open
Yuanfang-fe opened this issue Apr 26, 2021 · 0 comments
Open

JavaScript 编译执行过程 #26

Yuanfang-fe opened this issue Apr 26, 2021 · 0 comments

Comments

@Yuanfang-fe
Copy link
Owner

Yuanfang-fe commented Apr 26, 2021

v8 工作原理

因为 JS 是运行在引擎中的,我们以 V8 为例,来理解下JS的运行过程。
首先来看下 v8 引擎的工作原理

image

v8 有许多子模块,但有4个重要模块:Parser(解析器)、Ignition(解释器)、TurboFan(编译器)、Orinoco(垃圾回收)

Parser(解析器):
负责将JavaScript源码转换为Abstract Syntax Tree (AST);
如果函数没有被调用,那么是不会被转换成AST的

Ignition(解释器):
负责将 AST 转换为 Bytecode(字节码),有一个监视器会监控函数的调用次数,
如果函数只调用一次,Ignition 会执行 Bytecode;
如果调用多次,JIT(Just-in-time 即时编译) 会把 Bytecode 送到 TurboFan 中编译成机器码,并把编译结果存储起来。
image

Node.js是基于V8引擎实现的,因此node命令提供了很多V8引擎的选项,使用node的 --print-bytecode 选项,可以打印出Ignition生成的Bytecode。

由于V8不会编译没有被调用的函数,因此需要在最后一行调用foo函数。

function foo(a) {
  var b = 2;
  function c() { }
  var d = function () { };

  b = 3;
}

foo(1);
node --print-bytecode test.js
[generated bytecode for function: foo]
Parameter count 2
Register count 2
Frame size 16
   12 E> 0x75eaa296006 @    0 : a5                StackCheck 
   28 S> 0x75eaa296007 @    1 : 0c 02             LdaSmi [2]
         0x75eaa296009 @    3 : 26 fb             Star r0
   60 S> 0x75eaa29600b @    5 : 81 00 00 02       CreateClosure [0], [0], #2
         0x75eaa29600f @    9 : 26 fa             Star r1
   80 S> 0x75eaa296011 @   11 : 0c 03             LdaSmi [3]
         0x75eaa296013 @   13 : 26 fb             Star r0
         0x75eaa296015 @   15 : 0d                LdaUndefined 
   87 S> 0x75eaa296016 @   16 : a9                Return 
Constant pool (size = 1)
Handler Table (size = 0)

TurboFan(编译器):
将多次用到的函数,标记为热点函数,并编译成机器码,但是如果再后续执行过程中,类型发生变化,之前的机器码不再能正确处理,则会将机器码逆向转成字节码运算。

Orinoco(垃圾回收):
负责将程序不再需要的内存空间回收;

JavaScript 执行过程

JavaScript 执行过程分为两个阶段,编译阶段、执行阶段。
编译阶段:词法分析 => 语法分析
执行阶段:执行上下文

JavaScript执行过程 (4)

image

编译阶段

词法分析或分词(tokenize)

JS 引擎(chrome 和 node 为 v8)会将代码当成字符串分解成词法单元(token)。所谓 token,指的是语法上不可能再分的、最小的单个字符或字符串。
例如:var answer = 6 * 7; 会被分解成

[
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "answer"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "Numeric",
        "value": "6"
    },
    {
        "type": "Punctuator",
        "value": "*"
    },
    {
        "type": "Numeric",
        "value": "7"
    },
    {
        "type": "Punctuator",
        "value": ";"
    }
]

自动转换网站 https://esprima.org/demo/parse.html

语法分析或解析(parse),生成 AST

将上一步生成的 token 数据,根据语法规则转为 AST。如果源码符合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个 “语法错误”。

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "answer"
          },
          "init": {
            "type": "BinaryExpression",
            "operator": "*",
            "left": {
              "type": "Literal",
              "value": 6,
              "raw": "6"
            },
            "right": {
              "type": "Literal",
              "value": 7,
              "raw": "7"
            }
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "script"
}

执行阶段

执行上下文

执行上下文:指当前执行环境中的变量、函数声明,参数(arguments),作用域链,this等信息。分为全局执行上下文、函数执行上下文、eval 执行上下文,其区别在于全局执行上下文只有一个,函数执行上下文在每次调用函数时候会创建一个新的函数执行上下文。eval不常用,不做解释。

执行上下文的组成代码示例:

const ExecutionContextObj = {
    VO: window, // 变量对象
    ScopeChain: {}, // 作用域链
    this: window
};

执行上下文的组成图例示例:

image

执行上下文生命周期

创建阶段
  • 生成变量对象

    • 创建arguments
    • 扫描函数声明
    • 扫描变量声明
  • 建立作用域链

  • 确定this的指向

执行阶段
  • 变量赋值
  • 函数的引用
  • 执行其他代码

image

变量对象

变量对象是执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
主要分为全局上下文的变量对象和函数上下文的变量对象。

全局上下文的变量对象
  1. 全局执行上下文中,变量对象就是全局对象。
  2. 在顶层js代码中,this指向全局对象,全局变量会作为该对象的属性来被查询。在浏览器中,window就是全局对象。
函数上下文的变量对象
  1. 函数上下文中,用活动对象(activation object, AO)来表示变量对象。
    活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。
  2. 进入执行上下文时,活动对象是被创建,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

举个例子:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;
}

foo(1);

在进入执行上下文后,这时候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

代码执行

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值

还是上面的例子,当代码执行完后,这时候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}
  1. 全局上下文的变量对象初始化是全局对象
  2. 函数上下文的变量对象初始化只包括 Arguments 对象
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  4. 在代码执行阶段,会再次修改变量对象的属性值

建立作用域链

作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

举个例子:

var num = 30;

function test() {
    var a = 10;

    function innerTest() {
        var b = 20;

        return a + b
    }

    innerTest()
}

test()

当执行到调用innerTest函数,进入innerTest函数环境。全局执行上下文和test函数执行上下文已进入执行阶段,innerTest函数执行上下文在预编译阶段创建变量对象,所以他们的活动对象和变量分别是AO(global),AO(test)和 VO(innerTest),而innerTest的作用域由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,如下:

innerTestEC = {
  // 变量对象
  VO = { b: undefined},
  // 作用域链
  scopeChain = [VO(innerTest), AO(test), AO(global)],
  // this 指向
  this: window
}

总结一下作用域链的特点:

  1. 作用域链的第一项永远是当前作用域(当前上下文的变量对象或活动对象);
  2. 最后一项永远是全局作用域(全局执行上下文的活动对象);
  3. 作用域链保证了变量和函数的有序访问,查找方式是沿着作用域链从左至右查找变量或函数,找到则会停止查找,找不到则一直查找到全局作用域,再找不到则会抛出引用错误。

关于v8的详细课程,建议学习深入V8引擎

参考:
https://finget.github.io/views/frontEnd/base/20210310.html#%E6%8F%90%E4%B8%80%E5%98%B4
https://www.cnblogs.com/bala/p/12205485.html
https://zhuanlan.zhihu.com/p/72959191

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant