babel 是如何編譯 class 的?

經常使用ES6中的class,但卻一直不知道 babel 是如何編譯的,所以就抽空研究了一下,下面是相關的代碼,關鍵地方都已經添加了註釋。

1. 編譯類

ES5 定義類

function Person(name) {
  this.name = name;
}

Person.prototype = {
  sayHello: function () {
    return 'hello, I am ' + this.name;
  },
  get name() {
    return 'kevin';
  },
  set name(newName) {
    console.log('new name 爲:' + newName)
  }
}

Person.onlySayHello = function () {
  return 'hello'
};

ES6 定義類

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return 'hello, I am ' + this.name;
  }
  static onlySayHello() {
    return 'hello'
  }
  get name() {
    return 'kevin';
  }
  set name(newName) {
    console.log('new name 爲:' + newName)
  }
}

babel 轉義後代碼:

'use strict';

var _createClass = function () {
  // 遍歷傳入的屬性數組並添加到Person上
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }
  // Person,Person原型上方法和Person自身的方法
  return function (Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function () {
  function Person(name) {
    _classCallCheck(this, Person); // 判斷是否new調用

    this.name = name;
  }

  _createClass(Person, [{
    key: 'sayHello',
    value: function sayHello() {
      return 'hello, I am ' + this.name;
    }
  }, {
    key: 'name',
    get: function get() {
      return 'kevin';
    },
    set: function set(newName) {
      console.log('new name 爲:' + newName);
    }
  }], [{
    key: 'onlySayHello',
    value: function onlySayHello() {
      return 'hello';
    }
  }]);

  return Person;
}();

babel 將Person分解在了兩個數組中,一個數組存儲原型上的方法,一個數組儲存靜態方法,然後傳入到_createClass中處理,在 _createClass中通過定義的defineProperties方法將傳入的方法添加到構造函數Person上,可以看出,ES6中類和ES5上面最大的不同就是增加了靜態方法的添加,所以通過ES6生成的實例可以直接調用父類的靜態方法,而使用ES5方式生成的實例調用時則會報錯。

2. 編譯繼承

那麼babel又是如何編譯繼承的呢?

ES5 寄生組合式繼承

function Parent (name) {
    this.name = name;
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype = Object.create(Parent.prototype);

var child1 = new Child('kevin', '18');

console.log(child1);

ES6 extend

class Parent {
  constructor(name) {
    this.name = name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); 
    this.age = age;
  }
}

var child1 = new Child('kevin', '18');

console.log(child1);

babel 轉義後代碼:

'use strict';

function _possibleConstructorReturn(self, call) {
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }
  // 繼承父類原型方法
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { // 添加一個constructor屬性指向子類原型
      value: subClass,
      enumerable: false, // 不可枚舉
      writable: true,
      configurable: true
    }
  });
  // 繼承父類靜態方法,可簡化爲: Child.__proto__ = Parent。
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Parent = function Parent(name) {
  _classCallCheck(this, Parent);

  this.name = name;
};

var Child = function (_Parent) {
  _inherits(Child, _Parent);

  function Child(name, age) {
    _classCallCheck(this, Child);

    // 將子類中this指向父類,可簡化爲 Parent.call(Child)
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));

    _this.age = age;
    return _this;
  }

  return Child;
}(Parent);

var child1 = new Child('kevin', '18');

console.log(child1);

關鍵步驟已經在代碼中加了註釋,總結一下,extend做了三件事情,

  1. 繼承父類的原型方法和靜態方法
  2. 改變子類原型的constructor指向
  3. 將子類中this指向父類,從而獲取父類中的屬性

完。

參考:
https://github.com/mqyqingfeng/Blog/issues/105

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章