深入理解JavaScript面向对象继承机制——以frontend-hard-mode-interview项目为例
前言
面向对象编程(OOP)是JavaScript中非常重要的编程范式,而继承则是OOP的核心概念之一。本文将以frontend-hard-mode-interview项目中的内容为基础,全面解析JavaScript中实现继承的六种方式,帮助开发者深入理解JavaScript的继承机制。
JavaScript继承的本质
在JavaScript中,继承主要通过原型链(prototype chain)来实现。每个对象都有一个内部属性[[Prototype]]
(可通过__proto__
访问),当访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript引擎会沿着原型链向上查找。
六种继承方式详解
1. 类式继承(原型链继承)
类式继承是最基本的继承方式,通过将子类的原型指向父类的实例来实现。
function Parent() {
this.parentProperty = true;
}
Parent.prototype.getParentValue = function() {
return this.parentProperty;
};
function Child() {
this.childProperty = false;
}
// 关键步骤:继承实现
Child.prototype = new Parent();
const instance = new Child();
console.log(instance.getParentValue()); // true
优点:
- 实现简单直观
- 子类可以访问父类原型上的方法
缺点:
- 父类实例属性成为子类原型属性,所有子类实例共享这些属性
- 创建子类实例时无法向父类构造函数传参
适用场景:适合方法继承但不适合属性继承的场景
2. 构造函数继承
通过在子类构造函数中调用父类构造函数实现继承。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Child(name) {
Parent.call(this, name); // 关键调用
}
const child1 = new Child('child1');
child1.colors.push('green');
const child2 = new Child('child2');
console.log(child2.colors); // ['red', 'blue']
优点:
- 解决了引用类型共享问题
- 可以向父类传递参数
缺点:
- 无法继承父类原型上的方法
- 方法都在构造函数中定义,无法复用
适用场景:适合属性继承但不适合方法继承的场景
3. 组合继承
结合类式继承和构造函数继承的优点。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
const child = new Child('Tom', 10);
child.sayName(); // Tom
child.sayAge(); // 10
优点:
- 融合了两种继承方式的优点
- 实例属性私有,共享方法
缺点:
- 父类构造函数被调用两次
- 子类原型上会有多余的父类实例属性
适用场景:通用场景下的继承实现
4. 原型式继承
基于已有对象创建新对象,不自定义类型。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court']
};
const anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
优点:
- 不需要创建构造函数
- 实现简单
缺点:
- 引用类型属性会被共享
- 无法实现代码复用
适用场景:简单对象之间的继承
5. 寄生式继承
在原型式继承基础上增强对象。
function createAnother(original) {
const clone = Object.create(original);
clone.sayHi = function() {
console.log('hi');
};
return clone;
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court']
};
const anotherPerson = createAnother(person);
anotherPerson.sayHi(); // hi
优点:
- 可以为对象添加额外方法
缺点:
- 方法不能复用
- 引用类型属性共享
适用场景:需要为对象添加额外方法的场景
6. 寄生组合式继承
最理想的继承方式,解决了组合继承的问题。
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {
console.log(this.age);
};
const child = new Child('Tom', 10);
child.sayName(); // Tom
child.sayAge(); // 10
优点:
- 只调用一次父类构造函数
- 原型链保持不变
- 最理想的继承方式
适用场景:所有需要继承的场景
ES6中的class继承
虽然本文主要讨论ES5的继承方式,但了解ES6的class继承也很重要:
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
const child = new Child('Tom', 10);
child.sayName(); // Tom
child.sayAge(); // 10
ES6的class语法实际上是寄生组合式继承的语法糖,更加简洁易读。
总结
- 原型链继承:通过改变原型链实现继承,但引用类型属性会被共享
- 构造函数继承:通过调用父类构造函数实现继承,但无法继承原型方法
- 组合继承:结合前两种方式,但父类构造函数会被调用两次
- 原型式继承:基于已有对象创建新对象,适合简单继承
- 寄生式继承:在原型式继承基础上增强对象
- 寄生组合式继承:最理想的继承方式,解决了所有问题
在实际开发中,推荐使用ES6的class语法或寄生组合式继承,它们是最完善、最高效的继承方式。理解这些继承方式的原理和区别,有助于我们在不同场景下选择最合适的实现方式。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考