babel是如何编译es6 class和extends的

1,211 阅读3分钟

前言

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的组合式继承的基础上增加了子类的原型指向父类本身这样一条继承链。