HOW - 实现原型链和构造函数组合式继承

一、介绍

1. 继承定义

继承:子类继承父类,子类拥有父类的所有特性,并且可以有自己一些更具体的特性。

2. 接口继承和实现继承

许多编程语言支持两种继承方式:接口继承(interface inheritance)和实现继承(implementation inheritance)。

  1. 接口继承(Interface Inheritance)

在接口继承中,一个可以从一个或多个接口继承方法声明,但不继承实现。接口定义了类应该具有的方法和属性,但不提供具体的实现。

实现类必须提供接口中声明的所有方法的具体实现。

接口继承的优势在于它提供了一种规范或契约,允许多个类共享相同的接口。

通常情况下,一个类可以实现多个接口,从而实现多态性和组合性。

多态:由继承产生了相关的不同类,对同一个方法可以有不同的响应,即继承同一个父类,但子类个自己实现自己的方法。

  1. 实现继承(Implementation Inheritance)

在实现继承中,一个可以从另一个继承方法和属性的实现。

子类(或派生类)继承父类(或基类)的行为和属性,并且可以通过重写方法来修改或扩展父类的行为。

实现继承通常通过类的继承关系来实现,其中子类继承父类的方法和属性,并且可以添加新的方法和属性。

一些编程语言支持接口继承和实现继承的结合使用,例如Java。在Java中,一个类可以继承另一个类(实现继承),同时实现一个或多个接口(接口继承),从而同时享受到接口的规范性和基类的行为实现。

对于 JavaScript 来说,JavaScript高级程序设计(第4版)中提过一句话,“在 ECMAScript 中,无法实现接口继承,因为函数没有签名”。在JavaScript中函数的参数数量、类型和返回值类型都不是函数类型的一部分。因此,无法通过函数的签名(参数和返回值类型)来定义接口,也就无法通过函数的签名来实现接口继承。

举个例子,假设我们想要定义一个接口Drawable,要求实现类必须具有一个draw方法,以及一个接口Resizable,要求实现类必须具有一个resize方法。在其他语言中,我们可以像下面这样定义:

interface Drawable {
    void draw();
}
interface Resizable {
    void resize(int width, int height);
}
class Rectangle implements Drawable, Resizable {
    // 实现 draw 方法
    public void draw() {
        // 绘制矩形
    }
    // 实现 resize 方法
    public void resize(int width, int height) {
        // 调整矩形大小
    }
}

但在JavaScript中,我们无法直接定义接口,并要求类去实现它们。JavaScript是一种动态类型语言,函数的签名(参数和返回值类型)在定义函数时并不是必需的,因此无法利用函数签名来实现接口继承。相反,在JavaScript中,我们通常通过文档或约定来定义对象应该具有的方法或属性,然后在实现类中按照这些约定来编写代码

当然,现在流行使用 Typescript,我们可以使用接口继承来实现类似于其他语言中的接口继承的概念。TypeScript 的接口(interfaces)可以被一个类实现,从而约束该类必须具有接口中定义的属性和方法。这种方式类似于其他语言中的接口继承。

// 定义一个接口
interface Animal {
  name: string;
  makeSound(): string;
}
// 定义一个实现了 Animal 接口的类
class Dog implements Animal {
  constructor(public name: string) {}
  makeSound() {
    return "Dog barks";
  }
}
// 定义一个实现了 Animal 接口的类
class Cat implements Animal {
  constructor(public name: string) {}
  makeSound() {
    return "Cat meows";
  }
}
// 使用实现了 Animal 接口的类
const dog = new Dog("Buddy");
console.log(dog.name); // 输出: Buddy
console.log(dog.makeSound()); // 输出: Dog barks
// 使用实现了 Animal 接口的类
const cat = new Cat("Whiskers");
console.log(cat.name); // 输出: Whiskers
console.log(cat.makeSound()); // 输出: Cat meows

只不过,Typescript 本质上也属于类型约定。

3. 继承实现的方式

关于 JavaScript 继承实现一般有四种主要的继承方法:

  • 基于原型链的继承
  • 基于构造函数的继承
  • 组合式继承
  • class extends 继承

关于第 1、2 种的具体实现,可以自行学习。这里阐述一下他们存在的问题。

基于原型链的继承(Prototype-based Inheritance)和基于构造函数的继承(Constructor-based Inheritance)都是 JavaScript 中常见的继承方式,但它们各自存在一些问题:

基于原型链的继承:

  1. 共享属性和方法: 在原型链继承中,子类实例共享父类原型上的属性和方法。如果一个实例修改了原型上的属性或方法,其他实例也会受到影响,这可能会导致意外的副作用。

  2. 无法向父类传递参数: 在原型链继承中,子类无法向父类构造函数传递参数,因为子类实例是通过原型链链接到父类构造函数的,无法直接访问父类构造函数。

基于构造函数的继承:

  1. 无法继承父类原型上的属性和方法: 在构造函数继承中,子类无法继承父类原型上的属性和方法,因为它们之间没有原型链的关系。

  2. 方法重复定义: 如果父类的方法在构造函数内定义,那么每个子类实例都会创建一个新的方法实例,这会导致内存浪费。

因此,通常在实际开发中,会结合使用两种继承方式来克服各自的局限性,或者使用其他模式(如组合继承、工厂模式等)来实现继承,包括现代 Class 继承。

二、组合式继承实现

1. 介绍

原型链和构造函数组合式继承是JavaScript中常用且灵活的继承方式之一,也称为经典继承模式(Classical Inheritance Pattern)或伪经典继承模式(Pseudo-classical Inheritance Pattern)。

其允许子类继承父类的属性和方法,这种方式结合了原型链继承和构造函数继承的优点,避免了它们各自的缺点。

2. 实现

首先,我们定义一个父类 Animal,其中包含一个构造函数和一些方法:

function Animal(name) {
	this.name = name;
}
Animal.prototype.getName = function() {
	return this.name;
}
Animal.prototype.makeSound = function() {
	return "aooo~";
}

然后,我们定义一个子类 Dog,使用构造函数继承父类的属性,并通过设置原型链来继承父类的方法:

function Dog(name, breed) {
	Animal.call(this, name); // 调用父类构造函数,继承父类的属性
	this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 设置子类的原型为父类的实例,继承父类的方法
    // Object.create
    // function create(prototypeObj) {
    //   return { '__proto__': prototypeObj };
    // }

Dog.prototype.constructor = Dog; // 修正子类的构造函数指向自身

// 子类可以覆盖父类的方法
Dog.prototype.makeSound = function() {
  return "Dog barks";
};

3. 缺点

  1. 重复调用父类构造函数: 在组合继承中,子类在调用父类构造函数时会创建多余的属性副本,导致内存浪费。
  2. 构造函数中重复定义方法: 如果父类的方法在构造函数中定义,那么每个子类实例都会创建一个新的方法实例,这会导致内存浪费。

可以发现,主要还是在 Parent.call(this) 时会存在一些问题。

虽然组合继承不是完美的继承方式,但它在大多数情况下能够很好地满足需求,尤其是在需要兼顾灵活性和性能的场景下。然而,在一些对性能要求较高的场景下,可能需要考虑其他更优化的继承方式,比如 ES6 中的 class 语法。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值