相同点
- 基于原型链实现:ES6 类继承和传统构造函数继承本质上都是基于原型链来实现属性查找。无论是
class
语法还是构造函数方式,当访问一个对象的属性时,如果对象自身没有该属性,都会沿着原型链向上查找,直到找到该属性或到达原型链顶端(null
)。
- 属性查找优先级:都遵循先在实例自身查找属性,若找不到则在原型对象上查找的原则。例如:
// 传统构造函数方式
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name +'makes a sound.');
};
const dog = new Animal('Buddy');
dog.speak(); // 先在 dog 实例找 speak,找不到去 Animal.prototype 找
// ES6 类方式
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name +'makes a sound.');
}
}
const dog = new Animal('Buddy');
dog.speak(); // 同样先在 dog 实例找 speak,找不到去 Animal.prototype 找
不同点
- 语法糖下实际的原型链构建:
- 传统构造函数继承:通过
call
或 apply
方法在子构造函数中调用父构造函数来实现属性继承,然后通过修改子构造函数的 prototype
为父构造函数的实例来实现原型链继承。例如:
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('Parent name:'+ this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
- **ES6 类继承**:使用 `extends` 关键字简洁地实现继承。内部自动处理了原型链的构建,包括设置子构造函数的 `prototype` 为父构造函数 `prototype` 的一个副本,并修正 `constructor` 指向子构造函数。例如:
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log('Parent name:'+ this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
}
- 多重继承场景下的表现:
- 传统构造函数继承:实现多重继承比较复杂,通常需要借助一些库(如
mixin
模式),手动将多个对象的方法和属性混合到一个构造函数的原型上。例如:
function mixin(target, source) {
Object.keys(source.prototype).forEach(key => {
if (!target.prototype[key]) {
target.prototype[key] = source.prototype[key];
}
});
return target;
}
function A() {}
A.prototype.aMethod = function() {
console.log('a method');
};
function B() {}
B.prototype.bMethod = function() {
console.log('b method');
};
function C() {}
mixin(C, A);
mixin(C, B);
const c = new C();
c.aMethod();
c.bMethod();
- **ES6 类继承**:原生不支持多重继承,只能继承自一个父类。如果需要类似多重继承的效果,同样可以使用 `mixin` 模式,但相比传统构造函数方式,由于 `class` 语法的限制,实现起来可能需要更多的技巧。例如:
function mixin(...mixins) {
return function(Base) {
class Mixin extends Base {
constructor(...args) {
super(...args);
mixins.forEach(mixin => {
Object.assign(this, new mixin());
});
}
}
return Mixin;
};
}
class A {
aMethod() {
console.log('a method');
}
}
class B {
bMethod() {
console.log('b method');
}
}
const C = mixin(A, B)(class {});
const c = new C();
c.aMethod();
c.bMethod();
在复杂业务场景中利用这些特性进行优雅代码设计
- 代码复用:在一个电商系统中,有
Product
类作为基础类,包含产品的基本属性和方法,如 name
、price
和 getDetails
方法。Book
和 Electronics
类继承自 Product
类,复用 Product
类的属性和方法,同时添加各自特有的属性和方法。
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
getDetails() {
return `Name: ${this.name}, Price: ${this.price}`;
}
}
class Book extends Product {
constructor(name, price, author) {
super(name, price);
this.author = author;
}
getDetails() {
return `${super.getDetails()}, Author: ${this.author}`;
}
}
class Electronics extends Product {
constructor(name, price, brand) {
super(name, price);
this.brand = brand;
}
getDetails() {
return `${super.getDetails()}, Brand: ${this.brand}`;
}
}
- 多态性:在图形绘制系统中,有
Shape
类作为基类,包含 draw
方法。Circle
和 Rectangle
类继承自 Shape
类,各自重写 draw
方法以实现不同的绘制逻辑。
class Shape {
draw() {
console.log('Drawing a shape');
}
}
class Circle extends Shape {
draw() {
console.log('Drawing a circle');
}
}
class Rectangle extends Shape {
draw() {
console.log('Drawing a rectangle');
}
}
const shapes = [new Circle(), new Rectangle()];
shapes.forEach(shape => shape.draw());
- 利用原型链属性查找优化内存:如果有大量相似对象,它们共享原型上的方法,通过原型链查找属性,可以减少内存占用。例如在游戏开发中,大量的游戏角色对象继承自一个基类,共享一些通用的行为方法(如移动、攻击等),这些方法定义在原型上,每个角色实例只保存自身特有的属性(如角色名称、等级等)。