1. ES5类的三种继承方式
一:原型链继承: 通过让子类的原型对象指向父类的实例,实现继承父类原型上的方法。会导致子类共享父类原型。后续父类原型修改时可能会影响到子类。
function Parent() {
this.name = "Parent";
}
Parent.prototype.sayHello = function() {
console.log("Hello, " + this.name);
}
function Child() {
this.age = 5;
}
Child.prototype = new Parent();
二:构造函数继承:通过在子类的构造函数中调用父类的构造函数,从而继承父类的属性(this的指向改变)。缺点是无法继承父类原型上的方法,只能继承属性。
function Parent() {
this.name = "Parent";
}
Parent.prototype.sayHello = function() {
console.log("Hello, " + this.name);
}
function Child() {
Parent.call(this);
this.age = 5;
}
let c1 = new Child()
c1.sayHello(); // c1.sayHello is not a function
// 错误解释:Parent 构造函数仅设置了实例的属性name。当我们在 Child 构造函数中调用 Parent.call(this, name) 时,只是将 Parent 构造函数中的属性初始化到 Child 实例上,并没有涉及到 Parent.prototype 上的方法。
// 明确:Parent.call(this) 只会执行 Parent 构造函数的代码。
三:混合继承:结合构造函数继承和原型链继承的方式,通过调用父类构造函数继承属性,同时将子类原型对象指向一个新的对象,来继承父类原型的方法。
通过使用 构造函数继承 解决了 属性继承的问题;通过使用 原型链继承,解决了方法继承的问题;
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function () {
console.log('Hello, ' + this.name);
};
function oneChild(name, age) {
Parent.call(this, name);
this.age = age;
}
oneChild.prototype = Object.create(Parent.prototype);
let c2 = new oneChild('tom', 21);
c2.sayHello();
// 混合继承:通过使用 构造函数继承解决了 属性继承的问题;通过使用原型链继承,解决了方法继承的问题;
2. ES6 中 class 的继承
extends 关键字, 用于创建一个类的子类,从而继承父类的所有属性和方法。
super 关键字有两种主要用途:
1、在子类的构造函数中调用父类的构造函数 2、在子类的方法中,调用父类的方法。super.parentMethod()
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello, ' + this.name);
}
}
class Child extends Parent {
constructor(name, age) {
// 通过 super 调用父类的构造函数
super(name);
this.age = age;
}
introduce() {
// 通过 super 调用父类方法
super.sayHello()
console.log(`I am ${this.name} and I am ${this.age} `)
}
}
3. 自定义实现类的多继承
3.1 使用 mixins 混入
混入是一种将一个对象的属性和方法复制到另一个对象上的技术
// 基础类
class Parent1 {
sayHello() {
console.log("Hello from Parent1");
}
}
// 基础类
class Parent2 {
sayHi() {
console.log("Hello from Parent2");
}
}
// 目标类
class Child {
constructor(name) {
this.name = name
}
}
const mixins = (target, ...sources) => {
sources.map(source => {
Object.getOwnPropertyNames(source).map(name => {
if(name !== 'constructor') {
target.prototype[name] = source[name]
}
})
})
}
mixins(Child, Parent1.prototype, Parent2.prototype)
const tom = new Child('Tom')
tom.sayHello()
tom.sayHi()
3.2 使用 ES6类的混入
通过函数来实现混入,并使用ES6类语法。
/**
* Object.getOwnPropertyDescriptor(obj, prop) 返回一个对象,该对象描述给定对象上特定属性的配置(不包含原型对象上的属性)
* - obj: 要查找其属性的对象。
* - prop:要检索其描述的属性的名称
* - return: 如果指定的属性存在于对象上,则返回其属性描述符
*
*
* Object.defineProperty(obj, prop, descriptor) 在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
* - obj: 要定义属性的对象。
* - prop: 一个字符串或 Symbol,指定了要定义或修改的属性键。
* - descriptpr: 要定义或修改的属性的描述符。
* - return: 传入函数的对象,其指定的属性已被添加或修改。
*/
const mixins = (Base, ...Mixins) => {
class Mix extends Base {
constructor(...args) {
super(...args);
Mixins.forEach(Mixin => {
copyProperties(this, new Mixin());
});
}
}
Mixins.forEach(Mixin => {
copyProperties(Mix.prototype, Mixin.prototype);
copyProperties(Mix, Mixin);
});
return Mix;
};
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== 'constructor' && key !== 'prototype' && key !== 'name') {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
// 基础类
class Parent1 {
sayHello() {
console.log('Hello from Parent1');
}
}
// 基础类
class Parent2 {
sayGoodbye() {
console.log('Goodbye from Parent2');
}
}
class Base {}
class Child extends mixins(Base, Parent1, Parent2) {
constructor(name) {
super();
this.name = name;
}
}
const child = new Child('Tom');
child.sayHello();
child.sayGoodbye();