JS继承|使用组合继承模式、寄生式组合继承来实现new

文章详细介绍了JavaScript中的继承实现方式,包括原型链、盗用构造函数、组合继承以及寄生式组合继承。讨论了各种继承方式的优缺点,如组合继承的效率问题和寄生式组合继承作为更优的继承模式。还提供了实现这些继承方式的代码示例。
摘要由CSDN通过智能技术生成

这篇文章是为了做什么?

这篇文章可以来捋清实现继承的专业名词hha

组成继承是什么?

组合继承(也叫伪经典继承)综合了原型链和盗用构造函数。

基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方 法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

原型链大家都知道,盗用构造函数是什么鬼?

  • 盗用构造函数”(constructor stealing)(也称作“对象伪装”或“经典继承”)。

基本思路很简单:在子类构造函数中调用父类构造函数

  • 它的出现是为了解决:原型包含引用值导致的继承问题。

举个栗子

function Father(name){
    this.name = name;
}

function Son() {  
    // 继承 Father 并传参 
    Father.call(this, "Nicholas");
    // 实例属性
    this.age = 29;  
}

Son 里面的 Father.call(this, “Nicholas”); 就是盗用构造函数

它的优缺点

  1. 相对于原型链,盗用构造函数可以在子构造函数中给父构造函数传参
  2. 子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。
  3. 必须在构造函数中定义方法,因此函数不能重用

如何实现组合继承?

代码描述:

  • 创建父子构造函数、父子构造函数的原型方法。
  • 子构造函数中盗用父构造函数,同时绑定父构造函数的实例到子的原型上
function SuperType(name){
      this.name = name;
      this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};

function SubType(name, age){ 
    // 继承属性 
    SuperType.call(this, name);
    this.age = age;
}
// 继承方法  
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
    console.log(this.age);
};

let instance1 = new SubType("Nicholas", 29); 
instance1.colors.push("black"); 
console.log(instance1.colors); // "red,blue,green,black" 
instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29

let instance2 = new SubType("Greg", 27);
console.log(instance2.colors);  // "red,blue,green"
instance2.sayName();            // "Greg";
instance2.sayAge();             // 27
  • SubType 的原型链上会存在一个 SuperType 的实例,从而可以继承 SuperType 的属性和方法。但是,这种方式也会导致一些问题,因为 SubType.prototype 直接引用了一个 SuperType 的实例,所以对于 SubType.prototype 的修改可能会影响到其他使用 SuperType 的子类。
  • 组合继承其实也存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次:一次在是 创建子类原型时调用,另一次是在子类构造函数中调用。

ok接下来实现组合继承的 new

/**
 *
 * @param {*} 这种方式为组合继承,解决了原型继承的问题:原型构造函数中的所有属性变为原型属性,并且被所有实例共享
 * @param {*} 同时解决了盗用构造函数(对象伪装、经典继承)的问题: 子类构造函数可以向父类构造函数传递参数,,并且每个
 * @param {*} 实例都会拥有单独的父类构造函数的属性,不再全部共享父类构造函数的属性,但是同时因为改变this指向,子类也无法访问父类的原型
 */
function myNew(fn, ...args) {
  // 创建一个空对象,该对象的 __proto__ 属性指向构造函数的 prototype对象
  // const obj = Object.create(fn.prototype);
  const obj = {}
  obj.__proto__ = fn.prototype                              // 原型继承
  // 将构造函数中的 this 绑定到新创建的对象上
  const result = fn.apply(obj, args);                       // 盗用构造函数
  // const result = fn.call(obj, ...args);
  /**
   * @why 为什么要判断 object
   * 判断构造函数的返回值是否为对象,如果是则直接返回该对象,否则返回新对象
   * 防止构造函数 Person 中返回对象把 new 创建的对象覆盖
   */
  if (result && typeof (result) === 'object') {
    return result;
  }
  return obj;
}
function Person(name, age) {
  this.name = name;
  this.age = age;
  /**
   * @why 为什么要判断 object 的情况
   */
  // return {aaa:'aaa'}
}
const p = myNew(Person, 'Tom', 16);

console.log(p); // Person { name: 'Tom', age: 16 }

寄生式组合继承是什么?

寄生式组合继承上原型式继承和寄生式继承的组合

原型式继承是啥?

是即使不自定义 类型也可以通过原型实现对象之间的信息共享。它的实现是以下函数:

function object(o) {
    function F() { }
    F.prototype = o;
    return new F();
}
  • 这个 object()函数会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。本质上,object()是对传入的对象执行了一次浅复制。
let person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

let a_person = object(person);
a_person.name = "Greg";
a_person.friends.push("Rob");

let b_person = object(person);
b_person.name = "Linda";
b_person.friends.push("Barbie");

console.log(person.friends);    // "Shelby,Court,Van,Rob,Barbie"
console.log(person.name);       // Nicholas
console.log(a_person);          // "Shelby,Court,Van,Rob,Barbie"
console.log(b_person);          // "Shelby,Court,Van,Rob,Barbie"
  • 适用于这种情况:你有一个对象,想在它的基础上再创建一个新对象。 8 你需要把这个对象先传给 object(),然后再对返回的对象进行适当修改。
  • 在这个例子中,person 对象定义了另一个对象也应该共享的信息,把它传给 object()之后会返回一个新对象。这个新对象的原型 是 person,意味着它的原型上既有原始值属性又有引用值属性。这也意味着 person.friends 不仅是 person 的属性,也会跟 a_person 和 b_person 共享。

寄生式继承是啥?

寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种 方式增强对象,然后返回这个对象。

function createAnother(original){  
    let clone = object(original); // 通过调用函数创建一个新对象 
    clone.sayHi = function() { // 以某种方式增强这个对象
        console.log("hi");
    };
    return clone; // 返回这个对象 }
}
  • 寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式 继承所必需的,任何返回新对象的函数都可以在这里使用
  • 通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。

如何实现寄生式组合继承?

  • 它的基本模式如下:
// 寄生式组合继承:
function inheritPropertype(Sub, SuperType) {
  let prototype = object(SuperType);  // 创建对象:原型式继承
  prototype.constructor = Sub;        // 增强对象:修改父类构造函数的实例,属于寄生式继承,指定constructor是为了解决重写子类原型导致的constructor丢失
  Sub.prototype = prototype;          // 赋值对象:赋值修改prototype
}

相对于组合继承,这里只调用了一次 SuperType 构造函数,避免了 SubType.prototype 上不必要也用不到的属性,因此可以说这个例子的效率更高。

而且,原型链仍然保持不变,因此 instanceof 操作符和isPrototypeOf()方法正常有效。寄生式组合继承可以算是引用类型继承的最佳模式

接下来实现寄生式组合继承的 new

/**
 * @param {*} 寄生式组合继承:对组合继承的扩展
 * @why 使用这个函数来拷贝 SuperType.prototype 来防止赋值是地址的引用
 */
function object(obj) {
  // 原型式继承 Object.create()是使用的这种方式,原型式继承的规范化,第一个参数为原型对象,第二个参数是新增属性
  function F() {} // 一个临时的构造函数
  F.prototype = obj.prototype; // 修改临时构造函数的原型指向
  return new F(); // 返回临时构造函数的实例,实际上是对 obj 的一次浅复制
  // 若在return 之前,对F的实例进行修改属性,则为寄生式继承
}

// 寄生式组合继承:
function inheritPropertype(Sub, SuperType) {
  let prototype = object(SuperType);  // 原型式继承
  prototype.constructor = Sub;        // 修改父类构造函数的实例,属于寄生式继承,指定constructor是为了解决重写子类原型导致的constructor丢失
  Sub.prototype = prototype;          // 赋值修改prototype
}
// 定义父构造函数
function SuperType(type) {
  this.type = type;
}

// 定义一个父上下文的原型方法
SuperType.prototype.sayType = function (){
  console.log(`SuperType's prototype function :${this.type}`);
}

// 定义子构造函数
function Sub(type, age) {
  SuperType.call(this, type);
  this.age = age;
}
/**
 * @usePlace 继承
 */
inheritPropertype(Sub, SuperType);

// 修改原型后定义一个Sub上的原型方法
Sub.prototype.sayAge = function () {
  console.log(`Sub's prototype function :${this.age}`);
}
const SubT = new Sub('a', 12);
console.log(SubT);  // Sub { type: 'a', age: 12 }
SubT.sayAge();      // Sub's prototype function :12
SubT.sayType();     // SuperType's prototype function :a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值