在JavaScript中“对象继承”语法的历史演化
在 JavaScript 中,对象继承语法经历了多个重要的演化阶段。详细介绍一下每个阶段的语法、优缺点和实际应用:
1. 原型链继承(早期)
语法
function Parent() {
this.name = 'parent';
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
return this.name;
};
function Child() {
this.childProp = 'child';
}
// 核心继承代码
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.sayName()); // 'parent'
优点
- 简单直观,是最基础的继承方式
- 实现了基本的原型方法继承
缺点
- 引用类型共享问题:
var child1 = new Child(); var child2 = new Child(); child1.colors.push('green'); console.log(child2.colors); // ['red', 'blue', 'green'] - 问题! - 无法向父类构造函数传参
- 无法实现多继承
2. 构造函数继承(经典继承)
语法
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Child(name, age) {
// 核心继承代码
Parent.call(this, name); // 在子类上下文中执行父类构造函数
this.age = age;
}
var child1 = new Child('Tom', 10);
var child2 = new Child('Jerry', 8);
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue'] - 解决了共享问题
优点
- 解决了引用类型共享问题
- 可以向父类传递参数
- 可以实现多继承(多个 call)
缺点
- 方法都在构造函数中定义,每次实例化都要创建方法,无法复用
- 只能继承实例属性,无法继承原型方法
3. 组合继承(最常用)
语法
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent
this.age = age;
}
// 核心继承代码
Child.prototype = new Parent(); // 第一次调用 Parent
Child.prototype.constructor = Child;
var child = new Child('Tom', 10);
优点
- 结合了原型链和构造函数的优点
- 实例属性独立,原型方法共享
- 是 ES5 时代最完善的继承方式
缺点
- 父类构造函数被调用两次
- 子类原型上有不必要的父类实例属性
4. 原型式继承
语法
// Object.create 的 polyfill
function createObject(o) {
function F() {}
F.prototype = o;
return new F();
}
var parent = {
name: 'parent',
colors: ['red', 'blue'],
sayName: function() {
return this.name;
}
};
var child = createObject(parent);
child.name = 'child';
ES5 的 Object.create
var parent = {
name: 'parent',
colors: ['red', 'blue']
};
var child = Object.create(parent, {
name: {
value: 'child',
writable: true
}
});
优点
- 适用于不需要构造函数的简单对象继承
- 非常灵活
缺点
- 引用类型共享问题依然存在
- 缺乏结构化的类定义
5. 寄生式继承
语法
function createChild(parent) {
var clone = Object.create(parent);
clone.sayHello = function() {
return 'Hello ' + this.name;
};
return clone;
}
var parent = { name: 'parent' };
var child = createChild(parent);
优点
- 可以在不修改原对象的情况下增强对象
- 适合主要关注对象而不是类型的场景
缺点
- 方法不能复用
- 和原型式继承有相同的引用类型问题
6. 寄生组合式继承(最理想)
语法
function inheritPrototype(child, parent) {
// 创建父类原型的副本
var 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() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 核心继承代码
inheritPrototype(Child, Parent);
// 或者简化版本
// Child.prototype = Object.create(Parent.prototype);
// Child.prototype.constructor = Child;
优点
- 只调用一次父类构造函数
- 原型链保持正确
- 是最有效率的继承方式
- 避免了不必要的属性
缺点
- 语法相对复杂
- 需要理解原型机制
7. ES6 Class 继承
语法
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
sayName() {
return this.name;
}
static staticMethod() {
return 'static';
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 必须调用 super
this.age = age;
}
sayAge() {
return this.age;
}
}
const child = new Child('Tom', 10);
优点
- 语法简洁,更接近传统面向对象语言
- 内置支持静态方法继承
- 更好的工具支持(类型检查、自动补全等)
- 支持 super 关键字调用父类方法
缺点
- 仍然是基于原型的语法糖
- 需要理解底层原理避免误用
- 某些高级模式不如 ES5 灵活
实际应用建议
现代开发
// 推荐:使用 ES6 Class
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
super.speak();
console.log(`${this.name} barks.`);
}
}
兼容性考虑
// 如果需要支持老浏览器,使用寄生组合式继承
function extend(Child, Parent) {
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
}
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
extend(Dog, Animal);
总结
JavaScript 的继承语法从简单的原型链发展到今天优雅的 class 语法,体现了语言的成熟过程。理解每个阶段的优缺点有助于:
- 维护老代码 - 理解各种继承模式的原理
- 做出正确选择 - 根据项目需求选择合适的继承方式
- 避免常见陷阱 - 特别是引用类型共享问题
- 深入理解 JavaScript - 看到语法糖背后的原型本质
在现代开发中,推荐优先使用 ES6 Class,既保证了代码的可读性,又具备了良好的性能。
浙公网安备 33010602011771号