JavaScript 中实现继承有多种方法,但是归根到底就是通过原型链机制实现。
原型链继承(Prototype Chain Inheritance)
原型链继承是最基本的继承方式,通过将子类的原型指向父类的实例来实现继承。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
};
function Child() {
this.age = 10;
}
// 原型链继承
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child();
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent
缺点:父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享。其中一个子类实例进行修改,会导致所有其他子类实例的这个值都会改变。
借用构造函数继承(Constructor Stealing Inheritance)
借用构造函数继承通过在子类构造函数中调用父类构造函数,实现属性的继承。
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name); // 借用构造函数
this.age = age;
}
const child = new Child('Child', 10);
console.log(child.name); // 输出: Child
console.log(child.age); // 输出: 10
优点:解决了原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过call传参)。
缺点:所有方法都定义在构造函数中,每次都需要重新创建(对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法)。
组合继承(Combination Inheritance)
结合原型链继承和借用构造函数继承,将子类的原型指向父类的实例,在子类构造函数中调用父类构造函数。
既继承了父类的属性,又继承了父类的原型方法。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
};
function Child(name, age) {
Parent.call(this, name); // 借用构造函数
this.age = age;
}
// 原型链继承
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child('Child', 10);
console.log(child.name); // 输出: Child
console.log(child.age); // 输出: 10
child.sayHello(); // 输出: Hello from Parent
优点:同时解决了构造函数引用类型的问题,同时避免了方法会被创建多次的问题。
缺点:父类构造函数被调用了两次。同时子类实例以及子类原型对象上都会存在 name 属性。虽然根据原型链机制,并不会访问到原型对象上的同名属性,但总归是不美。
原型式继承(Prototypal Inheritance)
通过创建一个临时构造函数,将其原型指向父类的实例,然后返回这个临时构造函数的实例。
function createObject(o) {
function F() {}
F.prototype = o;
return new F();
}
const parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello from Parent');
}
};
const child = createObject(parent);
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent
或者使用 Object.create 方法来创建一个子类对象。
let animal = {
type: "animal",
speak: function () {
console.log(this.name + " makes a noise.");
},
};
let dog = Object.create(animal);
dog.name = "Rex";
寄生式继承(Parasitic Inheritance)
在原型式继承的基础上,增强对象的功能。
function createObject(o) {
function F() {}
F.prototype = o;
return new F();
}
function createEnhancedObject(o) {
const clone = createObject(o);
clone.sayGoodbye = function() {
console.log('Goodbye from Child');
};
return clone;
}
const parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello from Parent');
}
};
const child = createEnhancedObject(parent);
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent
child.sayGoodbye(); // 输出: Goodbye from Child
或者
function createDog(name) {
let dog = Object.create(animal);
dog.name = name;
return dog;
}
寄生组合式继承(Parasitic Combination Inheritance)
寄生组合式继承是一种结合了寄生式继承和组合式继承的方式,旨在减少组合式继承中多余的调用父类构造函数的开销。通过创建一个中间对象来继承父类的原型,避免了调用父类构造函数两次。
// 父类构造函数
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
// 父类原型方法
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 子类构造函数
function Child(name, age) {
// 借用构造函数继承父类属性
Parent.call(this, name);
this.age = age;
}
// 创建父类原型的副本并赋值给子类原型
Child.prototype = Object.create(Parent.prototype);
// 修正子类原型的 constructor 属性
Child.prototype.constructor = Child;
// 子类原型方法
Child.prototype.sayAge = function() {
console.log(this.age);
};
// 示例使用
const child1 = new Child('Alice', 18);
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
child1.sayName(); // 'Alice'
child1.sayAge(); // 18
const child2 = new Child('Bob', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
child2.sayName(); // 'Bob'
child2.sayAge(); // 20
ES6 类继承(Class Inheritance)
ES6 引入了 class 关键字和 extends 关键字,使得继承变得更加简洁和直观。
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello from Parent');
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
}
const child = new Child('Child', 10);
console.log(child.name); // 输出: Child
console.log(child.age); // 输出: 10
child.sayHello(); // 输出: Hello from Parent
参考
https://www.nonhana.xyz/2024/05/06/some-research/js中如何实现类的继承/