JS 中的继承大法:从原型链到 ES6 类
2022-11-29 06:32:48
JS 中的继承:深入浅出
何为继承?
想象一下,你正在建造一栋房子。你有一份已经建好的蓝图,里面包含了房子的基本结构、布局和功能。现在,你想建造另一栋相似的房子,但你不想从头开始。这时候,你就可以利用“继承”的理念,将第一栋房子的蓝图作为基础,并在其基础上进行修改,建造出第二栋独一无二的房子。
在面向对象编程(OOP)中,继承正是这个道理。继承允许一个对象(称为“子对象”)从另一个对象(称为“父对象”)那里继承属性和方法。这意味着子对象可以拥有与父对象类似的功能,同时还可以添加或修改自己的独特功能。
JS 中的继承方式
在 JavaScript(JS)中,有多种实现继承的方式。每种方式都有其优缺点,开发者可以根据自己的需要选择合适的继承方式。
1. 原型链继承
原型链继承是最基本的 JS 继承方式。每个 JS 对象都有一个“原型对象”,它包含了该对象的属性和方法。当一个对象被创建时,它会将父对象的原型对象链接到自己的原型对象链中。这样,子对象就可以从父对象的原型对象中继承属性和方法。
代码示例:
function Parent() {
this.name = "Parent";
this.sayHello = function() {
console.log("Hello from Parent!");
};
}
function Child() {
this.age = 25;
}
// 将 Parent 的原型对象链接到 Child 的原型对象链
Child.prototype = new Parent();
const child = new Child();
console.log(child.name); // 输出: "Parent"
child.sayHello(); // 输出: "Hello from Parent!"
优点:
- 实现简单,易于理解。
- 可以在运行时动态添加或修改原型对象。
缺点:
- 如果父对象的原型对象发生改变,子对象也会受到影响。
- 无法继承父对象的私有属性和方法。
2. 隐式继承
隐式继承是通过构造函数来实现的。当一个构造函数被调用时,它会创建一个新的对象,并将其作为参数传递给父构造函数。这样,新对象就继承了父对象的所有属性和方法。
代码示例:
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log(`Hello from ${this.name}!`);
};
function Child(name, age) {
// 调用父构造函数,将 this 传递给父构造函数
Parent.call(this, name);
this.age = age;
}
const child = new Child("John", 25);
console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"
优点:
- 实现简单,易于理解。
- 可以继承父对象的私有属性和方法。
缺点:
- 无法继承父对象的原型对象。
- 如果父构造函数的参数发生改变,子构造函数也需要相应修改。
3. 委托继承
委托继承是通过调用父对象的属性和方法来实现的。这种方式可以继承父对象的原型对象,但需要显式地调用父对象的属性和方法。
代码示例:
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log(`Hello from ${this.name}!`);
};
function Child(name, age) {
this.name = name;
this.age = age;
}
// 委托父对象的 sayHello 方法
Child.prototype.sayHello = function() {
Parent.prototype.sayHello.call(this);
};
const child = new Child("John", 25);
console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"
优点:
- 可以继承父对象的原型对象。
- 可以选择性地继承父对象的属性和方法。
缺点:
- 需要显式地调用父对象的属性和方法。
- 如果父对象的原型对象发生改变,子对象的委托可能会失效。
4. 组合继承
组合继承是原型链继承和委托继承的结合。它可以继承父对象的原型对象,也可以显式地调用父对象的属性和方法。
代码示例:
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log(`Hello from ${this.name}!`);
};
function Child(name, age) {
// 原型链继承
Child.prototype = new Parent(name);
// 委托继承
Child.prototype.constructor = Child;
this.age = age;
}
const child = new Child("John", 25);
console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"
优点:
- 继承了原型链继承和委托继承的优点。
- 可以继承父对象的原型对象,也可以显式地调用父对象的属性和方法。
缺点:
- 实现复杂,需要同时使用原型链继承和委托继承。
- 如果父对象的原型对象或构造函数发生改变,子对象的继承可能会失效。
5. ES6 类继承
ES6 中引入了“类”的语法,为 JS 提供了更简洁、更接近其他面向对象语言的继承方式。类继承使用“extends”来指定父类,并且可以继承父类的属性、方法和原型对象。
代码示例:
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello from ${this.name}!`);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的构造函数
this.age = age;
}
}
const child = new Child("John", 25);
console.log(child.name); // 输出: "John"
child.sayHello(); // 输出: "Hello from John!"
优点:
- 语法简洁,易于理解。
- 可以继承父类的属性、方法和原型对象。
缺点:
- 仅支持单继承(一个子类只能继承一个父类)。
- 如果父类的构造函数或原型对象发生改变,子类的继承可能会失效。
常见问题解答
-
JS 中哪种继承方式最常用?
原型链继承是最基本的 JS 继承方式,也是使用最广泛的。 -
哪种继承方式最灵活?
组合继承最灵活,它可以继承父对象的原型对象,也可以显式地调用父对象的属性和方法。 -
哪种继承方式最适合单继承?
ES6 类继承最适合单继承,它使用“extends”关键字来指定父类。 -
哪种继承方式性能最好?
原型链继承性能最好,因为它不需要调用父构造函数或显式地委托父对象。 -
哪种继承方式最容易维护?
ES6 类继承最容易维护,因为它提供了简洁、易于理解的语法。