一种使 JavaScript 代码运行得更快的工具

165 阅读3分钟

image.png

它有什么作用?

Prepack是一个优化JavaScript源代码的工具: 可以在编译时而不是运行时完成的计算被消除。 预打包将 JavaScript 包的全局代码替换为等效代码,这些代码是简单的赋值序列。这摆脱了大多数中间计算和对象分配。

例子

世界您好

(function () {
  function hello() { return 'hello'; }
  function world() { return 'world'; }
  global.s = hello() + ' ' + world();
})();
s = "hello world";

取消抽象税

(function () {
  var self = this;
  ['A', 'B', 42].forEach(function(x) {
    var name = '_' + x.toString()[0].toLowerCase();
    var y = parseInt(x);
    self[name] = y ? y : x;
  });
})();
_a = "A";
_b = "B";
_4 = 42;

斐波那契

(function () {
  function fibonacci(x) {
    return x <= 1 ? x : fibonacci(x - 1) + fibonacci(x - 2);
  }
  global.x = fibonacci(15);
})();
x = 610;

模块初始化

(function () {
  let moduleTable = {};
  function define(id, f) { moduleTable[id] = f; }
  function require(id) {
    let x = moduleTable[id];
    return x instanceof Function ? (moduleTable[id] = x()) : x;
  }
  global.require = require;
  define("one", function() { return 1; });
  define("two", function() { return require("one") + require("one"); });
  define("three", function() { return require("two") + require("one"); });
  define("four", function() { return require("three") + require("one"); });
})();
three = require("three");
(function () {
  var _0 = function (id) {
    let x = _1[id];
    return x instanceof Function ? _1[id] = x() : x;
  };

  var _5 = function () {
    return _0("three") + _0("one");
  };

  var _1 = {
    one: 1,
    two: 2,
    three: 3,
    four: _5
  };
  require = _0;
  three = 3;
})();

请注意大多数计算是如何预先初始化的。但是,计算四个 (_5) 的函数保留在残差程序中,因为它在初始化时未被调用。

环境交互和分支

(function(){
  function fib(x) { return x <= 1 ? x : fib(x - 1) + fib(x - 2); }
  let x = Date.now();
  if (x === 0) x = fib(10);
  global.result = x;
})();
(function () {
  var _$1 = this;
  var _$0 = _$1.Date.now();
  var _1 = 0 === _$0;
  var _0 = _1 ? 55 : _$0;
  result = _0;
})();

它是如何工作的?

要实现预包装,必须将几件事结合在一起:

  • 抽象语法树 (AST)

    Prepack在AST级别运行,使用Babel来解析和生成JavaScript源代码。

  • 具体执行

    Prepack 的核心是一个几乎兼容 ECMAScript 5 的解释器——用 JavaScript 实现! 解释器严格遵循ECMAScript 2016 语言规范,重点关注正确性和规范一致性。 你可以将 Prepack 中的解释器视为 JavaScript 的干净参考实现。

    解释器能够跟踪和撤消所有效果,包括所有对象突变。这将启用推测优化。

  • 符号执行

    除了计算具体值外,Prepack的解释器还能够对通常由环境交互产生的抽象值进行操作。例如,可以返回一个抽象值。您还可以通过辅助帮助程序函数手动注入抽象值,例如。预打包跟踪对抽象值执行的所有操作。当分支抽象值时,Prepack 将分叉执行并探索所有可能性。因此,Prepack 为 JavaScript 实现了一个符号执行引擎。Date.now``__abstract()

  • 抽象解释

    符号执行在遇到抽象值上的分支时将分叉。在控制流合并点,预打包将加入发散的执行,实现一种抽象解释的形式。联接变量和堆属性可能会产生条件抽象值。预打包跟踪有关抽象值的值和类型域的信息。

  • 堆序列化

    在初始化阶段结束时,当全局代码返回时,预打包将捕获最终堆。 Prepack 按顺序遍历堆,生成新鲜直接的 JavaScript 代码,用于创建并链接初始化堆中可访问的所有对象。堆中的某些值可能是对抽象值进行计算的结果。对于这些值,