babel是如何編譯es6 class和extends的

鬧鬧不愛鬧發表於2019-03-11

前言

es6 class只是es5建構函式的語法糖,只是比建構函式的寫法更加具有可讀性。

// es6 class
class Person {
    name = 'jack'
    static addr = 'earth'
    getName() { return this.name; }
    get name() { return this.name }
}
// es5 function
function Person() {
    this.name = 'jack'
}
Person.prototype = {
    getName() { return this.name; },
    get name() { return this.name }
}
Person.addr = 'earth';複製程式碼

從上面可以看出:

1. 類似name的例項屬性對應建構函式的this.name;

2. 類似getName的例項方法對應建構函式的Person.prototype.getName;

3. 類似static addr等靜態屬性,對應建構函式的Person.addr。靜態方法同理;

4. 類似getter和setter鉤子函式對應建構函式的Person.prototype裡面的鉤子函式。

和es5 建構函式的區別:

類內部定義的方法都是不可列舉的(non-enumerable)

也就是說

Object.keys(Person.prototype) => []; //  es6 class
Object.keys(Person.prototype) => ['getName', 'name'] // es5 function複製程式碼

好了,既然知道這種對應關係了,那我們看下babel是如何實現這種對應關係的:

// 編譯一
class Person {
    name = 'jack'
    getName() { return this.name; }
    static getStatic() {}
}

// babel
'use strict';

var _createClass = function () { 
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);
  }
 }
    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() {
		_classCallCheck(this, Person);

		this.name = 'jack';
	}

	_createClass(Person, [{
		key: 'getName',
		value: function getName() {
			return this.name;
		}
	}], [{
		key: 'getStatic',
		value: function getStatic() {}
	}]);

	return Person;
}();
複製程式碼

babel自定義了兩個函式來做這部分編工作:_classCallCheck和_createClass

_classCallCheck通過判斷例項(this)是否是建構函式的例項,來判斷class是否是通過new來呼叫,如果不是,則丟擲錯誤。

_createClass是一個立即執行函式,返回一個匿名函式。該匿名函式通過接收三個引數。該匿名函式通過判斷傳入的引數,來決定如何呼叫Object.defineProperty函式來設定屬性。如果傳入兩個引數,則說明是給constructor.prototype物件設定屬性;如果傳入三個引數,則還需要給constructor設定靜態屬性。

其中需要注意的是:預設enumerable是false, 是為了防止通過Object.keys遍歷到prototype上的屬性和方法。

// 編譯二
class A {}
class B extends A {
	constructor(){
  		super();
      return 1;
  	};
}

// 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: { value: subClass, enumerable: false, writable: true, configurable: true } }); 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 A = function A() {
  _classCallCheck(this, A);
};

var B = function (_A) {
  _inherits(B, _A);

  function B() {
    var _ret;

    _classCallCheck(this, B);

    var _this = _possibleConstructorReturn(this, (B.__proto__ || Object.getPrototypeOf(B)).call(this));

    return _ret = 1, _possibleConstructorReturn(_this, _ret);
  }

  return B;
}(A);
複製程式碼

babel自定義了_inherits和_classCallCheck和_possibleConstructorReturn函式

_inherits作用是處理了父子類之間的兩條繼承關係:

1. 設定子類和父類之間的繼承:subClass.__proto__ = superClass;

2. 設定子類的prototype和父類的prototype的繼承:subClass.prototype.__proto__ = superClass.prototype,對應es5的原型鏈繼承方法。

3. _classCallCheck將父函式的this通過call指向子函式的this,然後執行父函式。對應es5的建構函式繼承。

_possibleConstructorReturn函式來處理子類函式的返回值:

1. 子類如果沒有顯示地return,則比較子類的this和父類的返回值,如果父類的返回值是物件型別或者function型別,則返回父類的返回值,反之,則返回子類的this;

2. 子類如果顯示地return V,則比較子類的this和V,比較邏輯同上。


總結

1. es6的class是es5建構函式的語法糖。

2. es6的extends繼承其實就是在es5的組合式繼承的基礎上增加了子類的原型指向父類本身這樣一條繼承鏈。


相關文章