在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 语法,体现了语言的成熟过程。理解每个阶段的优缺点有助于:

  1. 维护老代码 - 理解各种继承模式的原理
  2. 做出正确选择 - 根据项目需求选择合适的继承方式
  3. 避免常见陷阱 - 特别是引用类型共享问题
  4. 深入理解 JavaScript - 看到语法糖背后的原型本质

在现代开发中,推荐优先使用 ES6 Class,既保证了代码的可读性,又具备了良好的性能。

posted @ 2025-11-03 09:30  悠哉大斌  阅读(8)  评论(0)    收藏  举报