关于ES6中子类继承的实现原理逐行解析

JS中万物皆对象

所以到了es6对一个对象生成器(构造函数)也定义了新的写法——类(class)使之写法和面向对象的写法更像

现如今我们定义一个一个构造函数就是定义一个类,写法如下:

class Parent {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    saySomething() {
        console.log('Hi, I am Parent');
    }
}

这是一个最简单的类,定义了实例属性name和age和一个原型方法saySomething

 

我们知道es6的语法需要编译成es5的语法后才能在浏览器端运行,babel这个工具就可以把我们的es6的代码编译成es5,

我们将这段代码复制到babel编译器看能得到什么:

"use strict";

function _instanceof(left, right) {
  if (                              // 判断传进来的构造函数right不为null并且当前Symbol类型有定义(即可用),并且构造函数的[Symbol.hasInstance]方法能访问
    right != null &&    
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left); // 能则把实例传进构造函数的[Symbol.hasInstance]去看是不是由该构造函数生成的
  } else {
    return left instanceof right; // 若没有则直接调用instanceof来判断
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) { // 判断调用当前是否是用new操作符调用了函数
    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; // 有value值则是可写的
    Object.defineProperty(target, descriptor.key, descriptor); //再调用内部方法defineProperty进行定义
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps); // 添加原型方法
  if (staticProps) _defineProperties(Constructor, staticProps); // 添加静态方法
  return Constructor;
}

// 在es5写法中就是用变量声明的方式定义了一个Parent的自执行函数,得到一个Parent的构造函数
var Parent = /*#__PURE__*/ (function() {
  function Parent(name, age) {
    _classCallCheck(this, Parent); // 在这里为了确认Parent是通过new方法调用的

    this.name = name; // 添加name和age两个实例方法
    this.age = age;
  }

// 这一步主要就是为这个构造函数添加原型方法和静态方法(如果有就作为第三个参数传递进去,与第二个参数一样是一个数组)
  _createClass(Parent, [
    {
      key: "saySomething",
      value: function saySomething() {
        console.log("Hi, I am Parent");
      }
    }
  ]);

  return Parent;
})();

这里定义Parent构造函数时主要就是调用了两个方法_classCallCheck和_createClass.

总结:_classCallCheck是用来确认Parent这个构造函数是不是由new调用的。_createClass.是用来挂载原型方法/静态方法的

 

现在我们理解了在es6中定义一个类实质是发生了什么。

下一步就是看子类继承父类(即使用extends关键字时)es5中又是怎么实现的呢

// 现在我们再来定义一个子类,继承与上面的父类
class Child extends Parent {
    constructor(name,age){
        super(name,age);  // es6中规定子类继承父类必须在constructor函数里实现super方法,因为子类没有this,必须继承自父类的this,所以也就是说只能在super后面使用this关键字
    }
    speak(){
        console.log("I am Child");
    }
}

我们把class Child和class Parent都复制到babel编译器中,得到如下es5代码:

"use strict";

function _typeof(obj) {
  "@babel/helpers - typeof";
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj &&
        typeof Symbol === "function" &&
        obj.constructor === Symbol &&
        obj !== Symbol.prototype
        ? "symbol"
        : typeof obj;
    };
  }
  return _typeof(obj);
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function"); // 判断传进来的父类是不是null或函数,如果不是则抛出错误
  }
// 这一步就是重点,用Object.create方法,使子类的prototype的__proto__指向父类的prototype,即共享其原型上的方法/属性
// superClass && superClass.prototype短路操作符保证存在superClass存在的情况下取superClass.prototype
// 第二个参数为了正确设置subClass.prototype的constructor指向
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  });
   // 最后如果superClass存在,则调用_setPrototypeOf
  if (superClass) _setPrototypeOf(subClass, superClass);
}

// 这个方法主要就是使用Object.setPrototypeOf方法使子类subClass.__proto__指向父类,实现静态方法/属性的继承
function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf || // 如果有Object.setPrototypeOf就调这个方法,没有就自己实现o.__proto__ = p
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function() {
    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);
  };
}

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}

function _isNativeReflectConstruct() {
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;
  try {
    Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
    return true;
  } catch (e) {
    return false;
  }
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, 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;
}


// 构造函数定义在这里,变量声明式定义一个自执行函数,返回Parent构造函数,与上面无异
var Parent = /*#__PURE__*/ (function() {
  function Parent(name, age) {
    _classCallCheck(this, Parent);

    this.name = name;
    this.age = age;
  }

  _createClass(Parent, [
    {
      key: "saySomething",
      value: function saySomething() {
        console.log("Hi, I am Parent");
      }
    }
  ]);

  return Parent;
})();


// 主要就是定义子类的时候,我们在使用关键字extends来继承一个类的时候,实际上是把这个类当作参数传入了这个自执行函数中,现在传入的是Parent,当然你也可以class Child extends null{}来继承null,此时就会把null传入这个自执行函数中
var Child = /*#__PURE__*/ (function(_Parent) {  //_Parent就是自执行函数拿到的父类的形参
  _inherits(Child, _Parent);  // 第一步就是调用_inherits方法,把子构造函数(也就是下面的function Child()函数声明会有变量提升所以可以先调用再定义)和父构造函数(也就是上面的var Parent这个变量)

  var _super = _createSuper(Child); // 这个方法以后再研究。。。hhhhhh(尴尬又不失礼貌的微笑)

  function Child(name, age) {
    _classCallCheck(this, Child); // _classCallCheck方法同上不在赘述

    return _super.call(this, name, age);
  }

  _createClass(Child, [// _createClass方法同上不在赘述
    {
      key: "speak",
      value: function speak() {
        console.log("I am Child");
      }
    }
  ]);

  return Child;
})(Parent);

通过理解上面的代码和旁边的注释。

总结:我们知道了子类继承父类使调用了一个最主要的方法_inherits(Child, _Parent);

在其内部实现了两个继承:

  1. 实现了Child.prototype.__proto__ = Parent.prototype的继承

  2. 实现了Child.__proto__ = Parent的继承

前者作为构造函数调用时,子类继承父类的原型属性/方法。

即:

let b = new Child();
b.saySomething(); // Hi, I am Parent

后者作为对象调用时,使子类继承父类的静态属性/方法。

即:

Child.index; // 1

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值