JS继承看着一篇就够了(图文解析)

131 阅读6分钟

原型链继承

原型链继承就是修改函数的prototype实现继承,将父类的实例方法和原型方法都继承了过来,作为子类型的原型对象

function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
}

function SubType() {
    this.subproperty = false;
}

SubType.prototype = new SuperType(); 

SubType.prototype.getSubValue = function() {
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue()); // true
WeChate6328b2384fcfd63e1365ef44a7760d3

原型链方案存在的缺点:因为多个实例共享同一个原型对象,多个实例对引用类型的操作会被篡改,另外一个问题就是在创建子类实例时候无法向超类的构造函数传递参数

function SuperType(){
  this.colors = ["red", "blue", "green"];
}
function SubType(){}

SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
console.info(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType(); 
console.info(instance2.colors); //"red,blue,green,black"

借用构造函数继承

借用构造函数继承基本思想就是在子类型构造函数内部调用超类型构造函数,这样每次创建一个SubType

function  SuperType(){
    this.color=["red","green","blue"];
}
function  SubType(){
    //继承自SuperType 可以传递参数
    SuperType.call(this, "aa");
}
var instance1 = new SubType();
instance1.color.push("black");
console.info(instance1.color);//"red,green,blue,black"

var instance2 = new SubType();
console.info(instance2.color);//"red,green,blue"

借用构造函数继承只能继承Super的实例属性不能继承原型属性

16091667269696_ pic

组合继承

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
  console.info(this.name);
};
function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  console.info(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.info(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
instance1.sayName(); // Nicholas
instance1.sayAge(); // 29
var instance2 = new SubType("Greg", 27);
console.info(instance2.colors); // [ 'red', 'blue', 'green' ]
instance2.sayName(); // Greg
instance2.sayAge(); // 27

image

组合继承是最常用的继承方式,他的缺点在于子类型(instance1)上和原型上(subType.prototype)上有相同的属性, 原因是SuperType会被调用两次

原型式继承

原型式继承类似于原形链继承,子类共享一个fn的实例


function object(o) {
    function fn() {};
    fn.prototype = o;
    return new fn();
}

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg"; // 赋值的时候是给自身加属性
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.info(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

image

原型式继承多个实例的引用类型属性指向相同,存在篡改的可能,Object.create()就是上述object方法,同时必须给订一个已知的对象

NodeJS中我们常常使用inherits实现继承,

util.inherits(SubType,SuperType);
exports.inherits = function(ctor, superCtor) {
    ctor.super_ = superCtor;
    ctor.prototype = Object.create(superCtor.prototype, {
        constructor: {
        value: ctor,
        enumerable: false,
        writable: true,
        configurable: true
        }
    });
};

寄生式继承

寄生式继承与工厂模式类似,通过创建一个增强式函数来实现继承

function createAnother(original){
  var clone = object(original); 
  clone.sayHi = function(){  
    console.info("hi");
  };
  return clone; 
}

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

原型链继承多个实例的引用类型属性指向相同,存在篡改的可能

寄生组合式继承

组合继承因为调用2次父类的构造函数,导致存在两个2个相同的属性(name,colors),我们完全可以只使用其原型对象就够了而不必new SuperType,所以这就是寄生组合式继承,此时我想到了在组合继承的基础上直接将SuperType的原型赋值给SubType的原型不就可以了


// 直接赋值仍然可以实现 但是他们使用的是同一个引用
SubType.prototype = SuperType.prototype;
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  console.info(this.age);
};

虽然直接赋值可以实现继承 但是SubType.prototype和SuperType.prototype成了同一个对象 这也就是为啥必须使用object增强函数

function inheritPrototype(subType, superType){
    var prototype = Object.create(superType.prototype); // 创建对象
    prototype.constructor = subType;                    // 增强对
    subType.prototype = prototype;                      // 指定对象
  }
  
  function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
  }

  SuperType.prototype.sayName = function(){
    console.info(this.name);
  };

  function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
  }
  
  inheritPrototype(SubType, SuperType);
  
  SubType.prototype.sayAge = function(){
    console.info(this.age);
  }
  
  var instance1 = new SubType("Nicholas", 23);
  var instance2 = new SubType("lGregxy", 23);
  
  instance1.colors.push("black"); // ["red", "blue", "green", "black"]
  instance1.colors.push("yellow"); // ["red", "blue", "green", "yellow"]
  

1654035278451

增强对象方式 Object.create VS Object.setPrototypeOf VS Object.assign

  • Obejct.create是直接创建一个新的对象,其实现方式就是我们上面提到的object

      function SuperType(name){
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
    
      SuperType.prototype.sayName = function(){
          console.info(this.name);
      };
    
      function SubType(name, age){
          SuperType.call(this, name);
          this.age = age;
      }
      
      SubType.prototype.sayAge = function(){
          console.info(this.age);
      }
    
      SubType.prototype = Object.create(SuperType.prototype);
      console.info(SubType.prototype.sayAge); // undefined
    
  • Object.setPrototypeOf 实现的是将A的原型指向B,但是A的原型方法依然保留

      function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
      }
    
      SuperType.prototype.sayName = function(){
          console.info(this.name);
      };
    
      function SubType(name, age){
          SuperType.call(this, name);
          this.age = age;
      }
    
      SubType.prototype.sayAge = function(){
          console.info(this.age);
      }
    
      Object.setPrototypeOf(SubType.prototype ,SuperType.prototype);
      console.info(SubType.prototype.sayAge); // sayAge函数
      console.info(SubType.prototype.__proto__.sayName); // sayName函数
    

    实现方式如下

    Object.prototype.setPrototypeOf = function(obj, proto) {
      if(obj.__proto__) {
          obj.__proto__ = proto;
          return obj;
      } else {
          // 如果你想返回 prototype of Object.create(null):
          var Fn = function() {
              for (var key in obj) {
                  Object.defineProperty(this, key, {
                      value: obj[key],
                  });
              }
          };
          Fn.prototype = proto;
          return new Fn();
      }
    }
    
  • Object.assign 其实就是函数的浅复制 将SuperType的方法复制到SubType原型中

    function SuperType(name){
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    
    SuperType.prototype.sayName = function(){
        console.info(this.name);
    };
    
    function SubType(name, age){
        SuperType.call(this, name);
        this.age = age;
    }
    
    SubType.prototype.sayAge = function(){
        console.info(this.age);
    }
    
     Object.assign(SubType.prototype ,SuperType.prototype);
    console.info(SubType.prototype.sayAge);
    console.info(SubType.prototype.sayName === SuperType.prototype.sayName); // true
    console.info(SubType.prototype.__proto__.sayName); // undefined
    

    实现方式如下

    Object.assign = function (target, ...source) {
        if (target == null) {
            throw new TypeError("Cannot convert undefined or null to object");
        }
        let ret = Object(target);
        source.forEach(function (obj) {
            if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                ret[key] = obj[key];
                }
            }
            }
        });
        return ret;
    };
    
    

ES6 extends继承

extends实现的继承本质其实就是寄生组合式继承

ES6 Class 通过 Babel转译为ES5

定义Point类如下

class Point {
  // 构造函数
  constructor(x,y){
    this.x = x;
    this.y = y;
  }
  // 普通方法
  getPoint() {
    return [this.x, this.y];
  }
  
  // 箭头函数
  getAge = () => {
    return 'age'
  }
    
  // 静态方法
  static testPoint() {
    return 'testPoint'
  }
}

babel转换为ES5的代码如下

// 判断letf是否为right的实例
function _instanceof(left, right) {
    if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
        return !!right[Symbol.hasInstance](left);
    } else {
        return left instanceof right;
    }
}
// 检查class是否通过new的方式创建实例
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 _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; 
}

function _createClass(Constructor, protoProps, staticProps) {
  // 普通的实例方法写到prototype中
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    // 静态方法 写到构造函数中
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
}

var Point = /*#__PURE__*/function () {
  // 构造函数
  function Point(x, y) {
    _classCallCheck(this, Point);

    _defineProperty(this, "getAge", function () {
      return 'age';
    });

    this.x = x;
    this.y = y;
  } // 普通方法


  _createClass(Point, [{
    key: "getPoint",
    value: function getPoint() {
      return [this.x, this.y];
    } // 箭头函数

  }], [{
    key: "testPoint",
    value: // 静态方法
    function testPoint() {
      return 'testPoint';
    }
  }]);

  return Point;
}();

分析此时的原型链如下,可以发现静态方法放到了Point对象本身的属性,实例方法放到了原型链上

image

ES6 Class 如何实现继承

定义Child类如下

class Point {
  // 构造函数
  constructor(x,y){
    this.x = x;
    this.y = y;
  }
  // 普通方法
  getPoint() {
    return [this.x, this.y];
  }
  
  // 箭头函数
  getAge = () => {
    return 'age'
  }
    
  // 静态方法
  static testPoint() {
    return 'testPoint'
  }
}


class Child extends Point {
  // 子类继承父类的属性
  constructor(x, y) {
    super(x, y);
  }
}

babel转换为ES5代码如下


function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
    };
    return _setPrototypeOf(o, p);
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function");
    }
    // 寄生组合继承 实现了Child的实例可以访问Point的原型属性(也就可以访问Point的实例函数)
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            writable: true,
            configurable: true
        }
    });
    // Child.__proto__ === Point 通过这种方式实现对静态方法的继承 此时Child.testPoint可以访问
    if (superClass) _setPrototypeOf(subClass, superClass);
}

function _createSuper(Derived) {
// 查看是否支持Reflex
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    // _getPrototypeOf方法用来获取Derived的原型,也就是Derived.__proto__ 就是Point
    var Super = _getPrototypeOf(Derived),
      result;
    // 看下是否支持Reflect Reflex创建的实例相当于 创建一个新的实例赋值child实例,否则就是直接修改child实例子
    if (hasNativeReflectConstruct) {
      // 这里的this事child的实例 NewTarget就是child方法
      var NewTarget = _getPrototypeOf(this).constructor;
      /* Reflect.construct的操作可以简单理解为:
        result = new Super(...arguments),第三个参数如果传了则作为新创建对象的构造函数
        也就是result.__proto__ === NewTarget.prototype,
       否则默认为Super.prototype */
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this, arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}


var Point = /*#__PURE__*/function () {
  // 构造函数
  function Point(x, y) {
    _classCallCheck(this, Point);

     _defineProperty(this, "getAge", function () {
      return 'age';
    });

    this.x = x;
    this.y = y;
  } // 普通方法


  _createClass(Point, [{
    key: "getPoint",
    value: function getPoint() {
      return [this.x, this.y];
    }
  }], [{
    key: "testPoint",
    value: function testPoint() {
      return 'testPoint';
    }
  }]);

  return Point;
}();

var Child = /*#__PURE__*/function (_Point) {
    // 寄生组合继承 主要继承原型属性(getPoint)和静态方法(testPoint)
  _inherits(Child, _Point);

  var _super = _createSuper(Child);

  // 子类继承父类的属性
  function Child(x, y) {
    _classCallCheck(this, Child);
    // 执行super 主要继承实例属性(x, y , getAge)
    return _super.call(this, x, y);
  }

  return Child;
}(Point);

定义Child class 没实现继承之前的原型链如下,注意红色部分继承之后会改变指向

image

实现继承之后的原型链如下, 绿色部分实现静态方法继承,蓝色部分实现实例方法继承,红色部分实现了实例继承

image

参考文章

JavaScript常用八种继承方案

babel在线代码转换