关注者
1077
被浏览
58285

23 个回答

Prepack 的介绍很多回答都写了,就不重复了。我之所以回答是因为看到了一些有趣的玩法,一个老外企图组合 Prepack 和 Closure Compiler:Combining Prepack and Closure Compiler

如果我们创建这样一段代码:

(function () {
    function getWeekWords() {
        return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    }
    function getWeekNumbers() {
        return [0, 1, 2, 3, 4, 5, 6];
    }
    var words = getWeekWords();
    for (var i = 0; i < words.length; i++) {
        if (words[i] === 'Monday') {
            console.log(words[i]);
        }
    }
    var numbers = getWeekNumbers();
    for (var i = 0; i < numbers.length; i++) {
        if (numbers[i] === 1) {
            console.log(numbers[i]);
        }
    }
})();

Closure Compiler 编译结果:

for (var n = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),
         r = 0; r < n.length; r++)"Monday" === n[r] && console.log(n[r]);
for (n = [0, 1, 2, 3, 4, 5, 6], r = 0; r < n.length; r++)1 === n[r] && console.log(n[r])

Closure Compiler 优化的是代码体积,并不会考虑代码中的性能因素而进行特殊优化。可以看到中间多余变量赋值和函数调用都被优化掉了,变成了循环两个字面值数组。


Prepack 编译结果:

(function () {
    function _0() {
        return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    }
    function _1() {
        return [0, 1, 2, 3, 4, 5, 6];
    }
    getWeekWords = _0;
    getWeekNumbers = _1;
    i = undefined;
    numbers = undefined;
    words = undefined;
    words = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    i = 0;
    i = 1;
    console.log("Monday");
    i = 2;
    i = 3;
    i = 4;
    i = 5;
    i = 6;
    i = 7;
    numbers = [0, 1, 2, 3, 4, 5, 6];
    i = 0;
    i = 1;
    console.log(1);
    i = 2;
    i = 3;
    i = 4;
    i = 5;
    i = 6;
    i = 7;
})();

会发现 Prepack 在执行函数内的计算步骤时,将展开循环后的每一次结果赋值都作为代码输出了,哪些是有用的哪些是冗余没用的,Prepack 是 “不知道” 的,它所知道的是每一次赋值都是循环展开中的行为。


这样冗余的赋值步骤正好是典型的 Closure Compiler 优化场景,如果我们先将 Prepack 的编译结果输出给 Closure Compiler 编译一遍的话:

console.log("Monday"),console.log(1);

得到了一个最完美的结果!/手动斜眼

用来做编译期优化的,在一些静态语言比如 C++ 上很常见,当然 JS 本身的语言特性导致它不可能像 C++ 那样在编译期做到极端的优化。prepack 现在做到的只是把一些初始的静态计算提前放到编译时执行,包括循环展开、递归计算,比如可以把

var a = 1 + 2 * 3
var b = fib(10)

直接优化成

var a = 7
var b = 55

这应该能提升一些应用 startup 的性能。具体的实现上还是很牛逼的,看官网上说是做了一个 JS 编写的、兼容 ECMAScript 5 的解释器,优化时会用解释器真正执行一遍代码,并且追踪堆栈信息,最后生成优化的代码。

正因为做了如此多的工作,所以 prepack 未来的愿景还是很庞大的,不仅限于优化代码。它希望能变成一个 Babel VM,一个用 JS 写的 JS 引擎,这样能带来的优化是要比现在大多数初级层面的优化(如webpack2的 tree shaking)要高一个次元的。但要注意一个问题!

这样的优化不可能是百利而无一弊的,编译体积运行性能在大多数情况下是相互矛盾的,要不然 gcc 也没必要给你从 -O1 到 -O3 这几种选择了。对于 prepack 来说,仅仅简单的循环展开就会很大地增加代码的体积,比如初始化一个从 0 到 99 的整数数组:

var a = [];
for(var n = 0; n < 100; n++) a[n] = n

会被优化成:

n = undefined;
a = undefined;
a = [0, 1, 2, 3, 4, 5, /* ... */ 98, 99];
n = 0;
n = 1;
n = 2;
n = 3;
n = 4;
n = 5;
// ...
n = 99;
n = 100;

如果我们要用 for 循环初始化一个从 0 到 99999 的数组,这个体积太美不敢想 /w\。对于这个问题,我们可以避开 for 循环,用别的(更加函数式的)方式:

var a = Array.apply(null, Array(100000)).map((item, i) => i);

但即使这样也依然会增大很多体积:

var a = [0, 1, 2, 3, 4, 5, /* 此处省略90000+个整数 */ 99998, 99999];

对于 JS 这样的客户端语言来说,性能和体积都是极为重要的,别的语言上的一些经典优化(比如上面提到的循环展开),我个人认为是不适合 JS 自身的(当然跑在服务器端 Node 上就是另一回事了)。另外 prepack 还在很初期的阶段,远没有到能在生产环境里使用的级别,bug 还是挺多的:

想法是很好,就看能走多远了。