babel 究竟将代码转换成什么鸟样?

原文链接:https://github.com/lcxfs1991/blog/issues/9

媒介

将babel捧作前端一个划时代的东西肯定也不为过,它的涌现让很多程序员幸福地用上了es6新语法。但你就这么放心肠让babel跑在外网?横竖我是不放心,我就曾经由被坑过,因而萌生了研讨babel代码转换的主意。本文不是剖析babel源码,仅仅是看看babel转换的终究产品。

es6在babel中又称为es2015。因为es2015语法浩瀚,本文仅遴选了较为经常运用的一些语法点,而且主假如剖析babel-preset-2015这个插件(react开辟的时刻,常在webpack顶用到这个preset)。

babel-preset-2015

翻开babel-preset2015插件一看,一共20个插件。熟习es2015语法的同道一看,多多少少能从字面意义晓得某个插件是用于哪一种语法的转换

  • babel-plugin-transform-es2015-template-literals => es2015模板

  • babel-plugin-transform-es2015-literals

  • babel-plugin-transform-es2015-function-name => 函数name属性

  • babel-plugin-transform-es2015-arrow-functions => 箭头函数

  • babel-plugin-transform-es2015-block-scoped-functions => 函数块级作用域

  • babel-plugin-transform-es2015-classes => class类

  • babel-plugin-transform-es2015-object-super => super供应了挪用prototype的体式格局

  • babel-plugin-transform-es2015-shorthand-properties => 对象属性的快速定义,如obj = { x, y }

  • babel-plugin-transform-es2015-computed-properties => 对象中括号属性,如obj = {[‘x]: 1}

  • babel-plugin-transform-es2015-for-of => 对象for of遍历

  • babel-plugin-transform-es2015-sticky-regex

  • babel-plugin-transform-es2015-unicode-regex

  • babel-plugin-check-es2015-constants => const常量

  • babel-plugin-transform-es2015-spread => 对象扩大运算符属性,如…foobar

  • babel-plugin-transform-es2015-parameters => 函数参数默认值及扩大运算符

  • babel-plugin-transform-es2015-destructuring => 赋值解构

  • babel-plugin-transform-es2015-block-scoping => let和const块级作用域

  • babel-plugin-transform-es2015-typeof-symbol => symbol特征

  • babel-plugin-transform-es2015-modules-commonjs => commonjs模块加载

  • babel-plugin-transform-regenerator => generator特征

var, const and let

const和let如今一概转换成var。那const究竟如何保证稳定呢?假如你在源码中第二次修正const常量的值,babel编译会直接报错。
转换前

var a = 1;
let b = 2;
const c = 3;

转换后:

var a = 1;
var b = 2;
var c = 3;

那let的块级作用如何表现呢?来看看下面例子,本质就是在块级作用转变一下变量名,使之与外层差别。
转换前:

let a1 = 1;
let a2 = 6;

{
    let a1 = 2;
    let a2 = 5;

    {
        let a1 = 4;
        let a2 = 5;
    }
}
a1 = 3;

转换后:

var a1 = 1;
var a2 = 6;

{
    var _a = 2;
    var _a2 = 5;

    {
        var _a3 = 4;
        var _a4 = 5;
    }
}
a1 = 3;

赋值解构

写react的时刻,我们运用负值解构去取对象的值,用起来异常爽,像如许:

var props = {
    name: "heyli",
    getName: function() {

    },
    setName: function() {

    }
};

let { name, getName, setName } = this.props;

我们来看看转换的效果:

var props = {
    name: "heyli",
    getName: function getName() {},
    setName: function setName() {}
};

var name = props.name;
var getName = props.getName;
var setName = props.setName;

至于数组呢?假如是一个匿名数组,则babel会帮你先定义一个变量寄存这个数组,然后再对须要赋值的变量举行赋值。
转换前:

var [ a1, a2 ] = [1, 2, 3];

转换后:

var _ref = [1, 2, 3];
var a1 = _ref[0];
var a2 = _ref[1];

看到这个,觉得转换效果跟我们想的还蛮一致。哈哈,运用的恶梦还没最先。

假如运用匿名对象直接举行赋值解构会如何呢?以下。babel为了使吸收的变量唯一,直接就将匿名对象里的属性拼在一起,构成吸收这个匿名对象的变量,吓得我赶忙检查一下项目里有无这类写法。
转换前:

var { abc, bcd, cde, def } = { "abc": "abc", "bcd": "bcd", "cde": "cde", "def": "def", "efg": "efg", "fgh": "fgh" };

转换后:

var _abc$bcd$cde$def$efg$ = { "abc": "abc", "bcd": "bcd", "cde": "cde", "def": "def", "efg": "efg", "fgh": "fgh" };
var abc = _abc$bcd$cde$def$efg$.abc;
var bcd = _abc$bcd$cde$def$efg$.bcd;
var cde = _abc$bcd$cde$def$efg$.cde;
var def = _abc$bcd$cde$def$efg$.def;

另有一种对象深层次的解构赋值:
转换前:

var obj = {
    p1: [
        "Hello",
        { p2: "World" }
      ]
};

var { p1: [s1, { p2 }] } = obj;

转换后:

// 为诠释本人将代码美化了
var _slicedToArray = (function() {
    function sliceIterator(arr, i) {
        var _arr = [];
        var _n = true;
        var _d = false;
        var _e = undefined;
        try {
           // 用Symbol.iterator造了一个可遍历对象,然后进去遍历。
            for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
                _arr.push(_s.value);
                if (i && _arr.length === i) break;
            }
        } catch (err) {
            _d = true;
            _e = err;
        } finally {
            try {
                if (!_n && _i["return"]) _i["return"]();
            } finally {
                if (_d) throw _e;
            }
        }
        return _arr;
    }
    return function(arr, i) {
        if (Array.isArray(arr)) {
            return arr;
        } else if (Symbol.iterator in Object(arr)) {
            return sliceIterator(arr, i);
        } else {
            throw new TypeError("Invalid attempt to destructure non-iterable instance");
        }
    };
})();

var obj = {
   p1: ["Hello", { p2: "World" }]
};

var _obj$p = _slicedToArray(obj.p1, 2);

var s1 = _obj$p[0];
var p2 = _obj$p[1].p2;

babel在代码顶部临盆了一个大众的代码_slicedToArray。也许就是将对象内里的一些属性转换成数组,轻易解构赋值的举行。但Symbol.iterator的兼容性并不好(以下图),照样郑重运用为妙。

《babel 究竟将代码转换成什么鸟样?》

别的,下面这类对字符串举行赋值解构也一样运用到_slicedToArray要领:

const [a, b, c, d, e] = 'hello';

函数参数默认值及扩大运算符

在es5的年代,平常我们写参数的默认值都邑这么写:

function func(x, y) {
    var x = x || 1;
    var y = y || 2;
}

我们来看看babel的转换方法:

function func({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

function func1(x = 1, y = 2) {
    return [x, y];
}
function func() {
  var _ref = arguments.length <= 0 || arguments[0] === undefined ? { x: 0, y: 0 } : arguments[0];

  var x = _ref.x;
  var y = _ref.y;

  return [x, y];
}

function func1() {
  var x = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];
  var y = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1];

  return [x, y];
}

babel这里使有了arguments来做判。第一种状况触及解构赋值,因而x和y的值照样有多是undefined的。至于第二种状况,则会保证2个参数的默认值分别是1和2.

再来看一种。…y代表它吸收了剩下的参数。也就是arguments除了第一个标号的参数以外盈余的参数。

转换前:

function func(x, ...y) {
    console.log(x);
    console.log(y);
    return x * y.length;
}

转换后:

function func(x) {
    console.log(x);

    for (var _len = arguments.length, y = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        y[_key - 1] = arguments[_key];
    }

    console.log(y);
    return x * y.length;
}

箭头函数

剪头函数实在主假如省了写函数的代码,同时能够直接用使外层的this而不必忧郁context切换的题目。之前我们平常都要在外层多写一个_this/self直向this。babel的转换方法实在跟我们的处置惩罚无异。

转换前:

var obj = {
    prop: 1,
    func: function() {
        var _this = this;

        var innerFunc = () => {
            this.prop = 1;
        };

        var innerFunc1 = function() {
            this.prop = 1;
        };
    },

};

转换后:

var obj = {
    prop: 1,
    func: function func() {
        var _this2 = this;

        var _this = this;

        var innerFunc = function innerFunc() {
            _this2.prop = 1;
        };

        var innerFunc1 = function innerFunc1() {
            this.prop = 1;
        };
    }

};

对象的才能加强

对象属性的快速定义

转换前:

var a = 1,
    b = "2",
    c = function() {
        console.log('c');
    };

var obj = {a, b, c};

转换后:

var a = 1,
    b = "2",
    c = function c() {
    console.log('c');
};

var obj = { a: a, b: b, c: c };

对象中括号属性

es2015最先新增了在对象顶用中括号诠释属性的功用,这对变量、常量等当对象属性特别有效。

转换前:

const prop2 = "PROP2";
var obj = {
    ['prop']: 1,
    ['func']: function() {
        console.log('func');
    },
        [prop2]: 3
};

转换后:

var _obj;
// 已美化
function _defineProperty(obj, key, value) {
    if (key in obj) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    } else {
        obj[key] = value;
    }
    return obj;
}

var prop2 = "PROP2";
var obj = (_obj = {}, _defineProperty(_obj, 'prop', 1), _defineProperty(_obj, 'func', function func() {
    console.log('func');
}), _defineProperty(_obj, prop2, 3), _obj);

看似简朴的属性,babel却大动干戈。新增了一个_defineProperty函数,给新建的_obj = {}举行属性定义。除此以外运用小括号包住一系列从左到右的运算使全部定义更简约。

运用super去挪用prototype

之前我们平常都用obj.prototype或许尝试用this去往上寻觅prototype上面的要领。而babel则本身写了一套在prototype链上寻觅要领/属性的算法。

转换前:

var obj = {
    toString() {
     // Super calls
     return "d " + super.toString();
    },
};

转换后:

var _obj;
// 已美化
var _get = function get(object, property, receiver) {
   // 假如prototype为空,则往Function的prototype上寻觅
    if (object === null) object = Function.prototype;
    var desc = Object.getOwnPropertyDescriptor(object, property);
    if (desc === undefined) {
        var parent = Object.getPrototypeOf(object);
        // 假如在本层prototype找不到,再往更深层的prototype上找
        if (parent === null) {
            return undefined;
        } else {
            return get(parent, property, receiver);
        }
    }
    // 假如是属性,则直接返回
    else if ("value" in desc) {
        return desc.value;
    }
    // 假如是要领,则用call来挪用,receiver是挪用的对象 
    else {
        var getter = desc.get;  // getOwnPropertyDescriptor返回的getter要领
        if (getter === undefined) {
            return undefined;
        }
        return getter.call(receiver);
    }
};

var obj = _obj = {
  toString: function toString() {
    // Super calls
    return "d " + _get(Object.getPrototypeOf(_obj), "toString", this).call(this);
  }
};

Object.assign 和 Object.is

es6新增的Object.assign极大轻易了对象的克隆复制。但babel的es2015 preset并不支撑,所以没对其进入转换,这会使得一些挪动端机子碰到这类写法会报错。所以平常开辟者都邑运用object-assign这个npm的库做兼容。

Object.is用于比较对象的值与范例,es2015 preset一样不支撑编译。

es6模板

多行字符串

转换前:

console.log(`string text line 1
string text line 2`);

转换后:

console.log("string text line 1\nstring text line 2");

字符中变量运算

转换前:

var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and not ${2 * a + b}.`);

转换后:

var a = 5;
var b = 10;
console.log("Fifteen is " + (a + b) + " and not " + (2 * a + b) + ".");

标签模板

es6的这类新特征给模板处置惩罚给予更壮大的功用,一改以往对模板举行种种replace的处置惩罚方法,用一个一致的handler去处置惩罚。babel的转换主假如添加了2个属性,因而看起来也并不算比较工程浩大的编译。

转换前:

var a = 5;
var b = 10;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world "
  console.log(values[0]);  // 15
  console.log(values[1]);  // 50

  return "Bazinga!";
}

tag`Hello ${ a + b } world ${ a * b }`;

转换后:

var _templateObject = _taggedTemplateLiteral(["Hello ", " world ", ""], ["Hello ", " world ", ""]);
// 已美化
function _taggedTemplateLiteral(strings, raw) {
    return Object.freeze(Object.defineProperties(strings, {
        raw: {
            value: Object.freeze(raw)
        }
    }));
}
// 给传入的object定义strings和raw两个不可变的属性。

var a = 5;
var b = 10;

function tag(strings) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world "
  console.log(arguments.length <= 1 ? undefined : arguments[1]); // 15
  console.log(arguments.length <= 2 ? undefined : arguments[2]); // 50

  return "Bazinga!";
}

tag(_templateObject, a + b, a * b);

模块化与类

类class

javascript完成oo一直是异常热点的话题。从最原始时代须要手动保护在组织函数里挪用父类组织函数,到厥后封装好函数举行extend继承,再到babel涌现以后能够像别的面向对象的言语一样直接写class。es2015的类计划依然算是过渡计划,它所支撑的特征依然没有涵盖类的一切特征。现在重要支撑的有:

  • constructor

  • static要领

  • get 要领

  • set 要领

  • 类继承

  • super挪用父类要领。

转换前:

class Animal {

    constructor(name, type) {
        this.name = name;
        this.type = type;
    }

    walk() {
        console.log('walk');
    }

    run() {
        console.log('run')
    }

    static getType() {
        return this.type;
    }

    get getName() {
        return this.name;
    }

    set setName(name) {
        this.name = name;
    }


}

class Dog extends Animal {
    constructor(name, type) {
        super(name, type);
    }

    get getName() {
        return super.getName();
    }
}

转换后(因为代码太长,先省略辅佐的要领):

/**
......一堆辅佐要领,后文详述
**/
var Animal = (function () {
    function Animal(name, type) {
                // 此处是constructor的完成,用_classCallCheck来剖断constructor准确与否
        _classCallCheck(this, Animal);

        this.name = name;
        this.type = type;
    }
        // _creatClass用于建立类及其对应的要领
    _createClass(Animal, [{
        key: 'walk',
        value: function walk() {
            console.log('walk');
        }
    }, {
        key: 'run',
        value: function run() {
            console.log('run');
        }
    }, {
        key: 'getName',
        get: function get() {
            return this.name;
        }
    }, {
        key: 'setName',
        set: function set(name) {
            this.name = name;
        }
    }], [{
        key: 'getType',
        value: function getType() {
            return this.type;
        }
    }]);

    return Animal;
})();

var Dog = (function (_Animal) {
        // 子类继承父类
    _inherits(Dog, _Animal);

    function Dog(name, type) {
        _classCallCheck(this, Dog);
                // 子类完成constructor
                // babel会强迫子类在constructor中运用super,不然编译会报错
        return _possibleConstructorReturn(this, Object.getPrototypeOf(Dog).call(this, name, type));
    }

    _createClass(Dog, [{
        key: 'getName',
        get: function get() {
                       // 跟上文运用super挪用原型链的super编译剖析的要领一致,
                       // 也是本身写了一个回溯prototype原型链
            return _get(Object.getPrototypeOf(Dog.prototype), 'getName', this).call(this);
        }
    }]);

    return Dog;
})(Animal);
// 检测constructor准确与否
function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}
// 建立类
var _createClass = (function() {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            // es6范例请求类要领为non-enumerable
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            // 关于setter和getter要领,writable为false
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }
    return function(Constructor, protoProps, staticProps) {
        // 非静态要领定义在原型链上
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        // 静态要领直接定义在constructor函数上
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
})();
// 继承类
function _inherits(subClass, superClass) {
   // 父类肯定假如function范例
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    // 使原型链subClass.prototype.__proto__指向父类superClass,同时保证constructor是subClass本身
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    // 保证subClass.__proto__指向父类superClass
    if (superClass) 
        Object.setPrototypeOf ? 
        Object.setPrototypeOf(subClass, superClass) :    subClass.__proto__ = superClass;
}
// 子类完成constructor
function _possibleConstructorReturn(self, call) {
    if (!self) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    // 若call是函数/对象则返回
    return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

先前在用react重构项目的时刻,一切的react组件都已摒弃了es5的写法,一概采用了es6。用类的优点写继承越发轻易,但没法用mixin,须要借助更新的es7语法中的decorator才能够完成类mixin的功用(比方pureRender)。但这次剖析完babel源码以后,才发明本来babel在完成class特征的时刻,定义了很多要领,只管看起来并不太文雅。

模块化

在开辟react的时刻,我们每每用webpack搭配babel的es2015和react两个preset举行构建。之前看了一篇文章对babel此处的模块加载有些启示(《剖析 Babel 转换 ES6 module 的道理》)。

示例:

// test.js
import { Animal as Ani, catwalk } from "./t1";
import * as All from "./t2";


class Cat extends Ani {

    constructor() {
        super();
    }
}

class Dog extends Ani {
    constructor() {
        super();
    }
}
// t1.js
export class Animal {

    constructor() {

    }

}

export function catwal() {
    console.log('cat walk');
};
// t2.js
export class Person {
    constructor() {

    }

}

export class Plane {
    constructor() {

    }

}

经由过程webpack与babel编译后:

// t1.js的模块
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.catwal = catwal;

// 省略一些类继承的要领

var Animal = exports.Animal = function Animal() {
    _classCallCheck(this, Animal);
};

function catwal() {
    console.log('cat walk');
};

// t2.js的模块
Object.defineProperty(exports, "__esModule", {
    value: true
});

// 省略一些类继承的要领

var Person = exports.Person = function Person() {
    _classCallCheck(this, Person);
};

var Plane = exports.Plane = function Plane() {
    _classCallCheck(this, Plane);
};

// test.js的模块
var _t = __webpack_require__(1);

var _t2 = __webpack_require__(3); // 返回的都是exports上返回的对象属性

var All = _interopRequireWildcard(_t2);

function _interopRequireWildcard(obj) {
    // 发明是babel编译的, 直接返回
    if (obj && obj.__esModule) {
        return obj;
    }
   // 非babel编译, 猜想多是第三方模块,为了不报错,让default指向它本身
    else {
        var newObj = {};
        if (obj != null) {
            for (var key in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
            }
        }
        newObj.default = obj;
        return newObj;
    }
}

// 省略一些类继承的要领

var Cat = (function (_Ani) {
    _inherits(Cat, _Ani);

    function Cat() {
        _classCallCheck(this, Cat);

        return _possibleConstructorReturn(this, Object.getPrototypeOf(Cat).call(this));
    }

    return Cat;
})(_t.Animal);

var Dog = (function (_Ani2) {
    _inherits(Dog, _Ani2);

    function Dog() {
        _classCallCheck(this, Dog);

        return _possibleConstructorReturn(this, Object.getPrototypeOf(Dog).call(this));
    }

    return Dog;
})(_t.Animal);

es6的模块加载是属于多对象多加载,而commonjs则属于单对象单加载。babel须要做一些四肢才能将es6的模块写法写成commonjs的写法。主假如经由过程定义__esModule这个属性来推断这个模块是不是经由babel的编译。然后经由过程_interopRequireWildcard对各个模块的援用举行响应的处置惩罚。

另一个发明是,经由过程webpack打包babel编译后的代码,每个模块内里都包含了雷同的类继承协助要领,这是开辟时疏忽的。由此可看,在开辟react的时刻用es5的语法能够会比运用es6的class能使js bundle更小。

babel es2015 loose mode

开辟家校群的时刻,在android4.0下面报esModule毛病的题目,以下:
Uncaught TypeError: Cannot assign to read only property '__esModule' of #<Object>

经查证,发明是构建中babel-es2015 loader的形式题目,会致使Android4.0的用户有报错。只须要运用loose mode就能够处理题目。下面是相干的stackoverflow issue以及对应处理题目的npm包。

那末es2015和normal mode和loose mode有什么区别呢,这个着名的博客略有引见:Babel 6: loose mode

本质就是(作者总结)normal mode的转换更切近es6的写法,很多的property都是经由过程Object.defineProperty举行的。而loose mode则更切近es5的写法,机能更好一些,兼容性更好一些,但将这部分代码再转换成native es6的话会比较贫苦一些(觉得这一点并非瑕玷,有源码就能够了)。

上面esModule处理的方法,本质就是将

Object.defineProperty(exports, "__esModule", {
    value: true
});

改成 exports.__esModule = true;

再举个例子,以下面的Cat类定义:

class Cat extends Ani {

    constructor() {
        super();
    }

    miao() {
        console.log('miao');
    }
}

一般形式会编译为:

var Cat = (function (_Ani) {
    _inherits(Cat, _Ani);

    function Cat() {
        _classCallCheck(this, Cat);

        return _possibleConstructorReturn(this, Object.getPrototypeOf(Cat).call(this));
    }

    _createClass(Cat, [{
        key: "miao",
        value: function miao() {
            console.log('miao');
        }
    }]);

    return Cat;
})(_t.Animal);

loose mode形式会编译为:

var Cat = (function (_Ani) {
    _inherits(Cat, _Ani);

    function Cat() {
        _classCallCheck(this, Cat);

        return _possibleConstructorReturn(this, _Ani.call(this));
    }

    Cat.prototype.miao = function miao() {
        console.log('miao');
    };

    return Cat;
})(_t.Animal);

babel es2015中loose形式主假如针对下面几个plugin:

  • transform-es2015-template-literals

  • transform-es2015-classes

  • transform-es2015-computed-properties

  • transform-es2015-for-of

  • transform-es2015-spread

  • transform-es2015-destructuring

  • transform-es2015-modules-commonjs

每一种的转换体式格局在此就不再赘述了,人人能够回家本身试。

若有毛病,恳请指正!

参考文章

babel try out

template literals

block-scoped functions

classes

objects

commonjs and es6 module

    原文作者:lcxfs1991
    原文地址: https://segmentfault.com/a/1190000005112677
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞