babel 是如何编译 class 的

617 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

最简单的类

class Person {
  constructor(name) {
    this.name = name
  }
}

编译后

// 使用babel编译后的代码会自动开启严格模式
"use strict";

// 对Person函数的调用进行检测,阻止Perosn被像一个普通函数那样被调用
function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

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

  this.name = name;
};

添加属性

class Person {
    // 实例属性
    foo = 'foo';
    // 静态属性
    static bar = 'bar';

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

编译后

"use strict";

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

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

  // 挂载实例属性
  _defineProperty(this, "foo", "foo");

  this.name = name;
};

// 挂载静态顺序
_defineProperty(Person, "bar", "bar");

使用访问器方法

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

    sayHello() {
        return 'hello, I am ' + this.name;
    }

    static onlySayHello() {
        return 'hello'
    }

    get name() {
        return 'Klaus';
    }

    set name(newName) {
        console.log('new name 为:' + newName)
    }
}

编译后

"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a 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);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var Person = /*#__PURE__*/ (function () {
  function Person(name) {
    _classCallCheck(this, Person);

    this.name = name;
  }

  // 往构造函数上添加方法,并将挂载完方法的构造函数返回
  // 参数1: 构造函数
  // 参数2: 实例方法组成的数组
  // 参数3; 静态方法组成的数组
  _createClass(
    Person,
    [
      {
        key: "sayHello",
        value: function sayHello() {
          return "hello, I am " + this.name;
        }
      },
      {
        key: "name",
        get: function get() {
          return "Klaus";
        },
        set: function set(newName) {
          console.log("new name 为:" + newName);
        }
      }
    ],
    [
      {
        key: "onlySayHello",
        value: function onlySayHello() {
          return "hello";
        }
      }
    ]
  );

  return Person;
})();

实现继承

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

  static staticMethod() {
    console.log('staticMethod')
  }
}

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

const child = new Child('Klaus', '18')

Child.staticMethod()

类Parent是构造函数Parent形式的语法糖,所以函数Parent同时有 prototype 属性和 __proto__ 属性

所以Babel在实现继承的时候,存在两条继承链

  1. Child.prototype === Parent ---> 静态方法的继承
  2. Child.prototype.prototype === Parent.prototype ---> 实例方法的继承

IoB33k.png

也就是说,ES6中实现的继承 = 寄生组合式继承 + Object.setPrototypeOf(Child, Parent)

上述代码经过编译后的代码中,babel使用了_inherits _possibleConstructorReturn这两个辅助函数来实现继承,

具体转换可以使用babel的try it out来进行测试

_inherits

function _inherits(subClass, superClass) {
  // 类只能继承自函数或null
  // 如果继承自函数,则该函数可以使用构造函数,也可以是普通函数
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function")
  }

  // 子类继承父类的实例方法
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  })

  if (superClass) _setPrototypeOf(subClass, superClass)
}

// Object.setPrototypeOf(Child, Parent)
// 子类继承父类静态方法
function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf ||
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

_possibleConstructorReturn

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  
  // 返回的是一个函数
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived),
      result;
    if (hasNativeReflectConstruct) {
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this, arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}

// self ==> 子类实例  call ==> 子类调用父类构造方法后的返回值
// 该方法的作用是
//  + 如果父类的构造函数有返回值, 则返回构造函数中的返回值
//  + 如果父类的构造函数返回undefined, 那么就返回self 
function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  } else if (call !== void 0) {
    // void 0 === undefined ---> true
    throw new TypeError(
      "Derived constructors may only return object or undefined"
    );
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  // 子组件实际返回的实例对象是通过父组件来进行创建的
  // 所以如果没有在使用this之前调用super方法
  // 子组件内部的self的值是undefined
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}

// 实际调用
var _super = _createSuper(Child);

var _this; 

// 执行super方法,返回的_this才是实际的子类实例
// 没有执行super方法的时候,_this的值是undefined
// 所以在子类中,在使用this之前,需要先手动调用super
_this = _super.call(this, name);
_this.age = age;
return _this;

_possibleConstructorReturn实际的功能如下:

  1. 在子类中,使用this之前,必须调用super方法
  2. 如果父类的构造函数返回了对象或函数,就之前使用父类的返回值
class Parent {
  constructor(name) {
    this.name = name
  }
}

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

const child = new Child('Klaus', 23)

console.log(child) // => {name: 'Klaus', age: 23}
class Parent {
  constructor(name) {
    this.name = name
    return {}
  }
}

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

const child = new Child('Klaus', 23)

console.log(child) // => { age: 23 }