【编程向导】JavaScript-组合继承与借用构造函数

组合继承

组合继承(Combination Inheritance)(也叫伪经典继承),指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

其背后的思路是使用原型链实现对原型对象的属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

🌰 示例:

function Parent(name) {
  this.name = name;
  this.attr = {
    eye: 'blue',
    hair: 'black',
    skin: 'white',
  };
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  // 第二次调用 Parent()
  // Child.prototype 又得到了 name 和 attr 两个属性
  // 并对上次得到的属性值进行了覆盖
  Parent.call(this, name);
  this.age = age;
}

// 第一次调用 Parent()
// 使得子类实例的原型对象指向父类实例对象
// Child.prototype 得到了 name 和 attr 两个属性
Child.prototype = new Parent();
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
  console.log(this.age);
};

// 第一个实例对象
let uzi = new Child('Uzi', 3);

uzi.attr.height = 80;

console.log(uzi.attr);
// { eye: 'blue', hair: 'black', skin: 'white', height: 80 }
uzi.sayName();
// 'Uzi'
uzi.sayAge();
// 3

// 第二个实例对象
let kat = new Child('Kat', 1);

console.log(kat.colors);
// { eye: 'blue', hair: 'black', skin: 'white' }
kat.sayName();
// 'Kat'
kat.sayAge();
// 1

实现步骤分解:

  • 父类构造函数定义自身属性(Parent 构造函数定义了nameattr
  • 父类原型上定义方法(Parent 的原型定义了一个方法 sayName
  • 子类构造函数调用父类构造函数,传入参数,继承父类构造函数中的属性,随后子类构造函数又自定义自身的属性(Child 构造函数在调用 Parent 构造函数时传入了 name 参数,紧接着又定义了它自己的属性 height。)
  • 子类构造函数的原型指向父类构造函数生成的实例(将 Parent 的实例赋值给 Child 的原型)
  • 在子类构造函数的原型上定义方法(在 Child 的原型上定义了方法 sayAge
  • 这样一来,就可以让两个不同的子类实例对象既分别拥有自己属性,又可以使用相同的方法

缺陷

无论什么情况下,都会调用两次父类构造函数:第一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。子类型对象最终会包含父类型对象的全部实例属性,但不得不在调用子类型构造函数时重写这些属性。

组合继承优化

组合继承优化示例一:

// Before
Child.prototype = new Parent();

// After
Child.prototype = Parent.prototype;

这种优化方式的缺点是,子类实例对象的构造函数无法区分是子类构造函数还是父类构造函数。

📌 完美写法:寄生组合式继承

组合继承优化示例二:通过中间对象,继承父类原型对象,实现子类与父类的隔离

function Parent() {
  this.name = 'Parent';
  this.num = [0, 1, 2];
}

function Child() {
  Parent.call(this);
  thi.type = 'Child';
}

Child.prototype = Object.create(Parent.prototype);

Child.prototype.constructor = Child;

借用构造函数

借用构造函数(Constructor Stealing),即在子类型构造函数的内部调用父类构造函数以实现对父类构造函数属性的继承。

🌰 示例:

function Parent() {
  this.attr = {
    eye: 'blue',
    hair: 'black',
    skin: 'white',
  };
  this.sayName = function () {
    console.log('Name');
  };
}

function Child() {
  Parent.call(this);

  this.sayHi = function () {
    console.log('Hello world!');
  };
}

let boy = new Child();
boy.attr.age = 3;
console.log(boy.attr);
// { eye: 'blue', hair: 'black', skin: 'white', age: 3}

let girl = new Child();
console.log(girl.attr);
// { eye: 'blue', hair: 'black', skin: 'white'}

在构造函数 Child 内通过 call 方法(或 apply 方法也可以),使得 Parent 的构造函数能在 Child 构造函数的环境下调用。

如此一来,子类构造函数 Child 上执行父类构造函数 Parent 中定义的所有对象初始化代码。

Child 的每个实例都会具有自己的继承与父类构造函数的属性的副本。

⚠️ 注意: 函数只不过是在特定环境中执行代码的对象,因此通过使用 applycall 方法也可以在新创建的对象上执行构造函数。

传递参数

相对于原型链而言,借用构造函数有一个很大的优势,即 可以在子类型构造函数中向父类型构造函数传递参数

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

function Child() {
  //继承了 Parent,同时还传递了参数
  Parent.call(this, 'Uzi');

  //实例属性
  this.age = 18;
}

const child = new Child();
console.log(child.name);
// 'Uzi'
console.log(child.age);
// 18
  • 通过往父类型构造函数传递参数,能自定义需要继承的属性
  • 为了确保子构造函数自身定义的属性或方法不被父构造函数生成的属性重写,可以在调用父类型构造函数后,再添加子类型构造函数中定义的属性

缺陷

  • 只能继承父类实例对象的属性和方法,不能继承原型对象的属性和方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能
  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wtrees_松阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值