Skip to content
On This Page

JavaScript实现继承

JavaScript 是基于原型链的语言,继承主要通过原型(prototype)来实现

1. 原型链继承

核心:将父类的实例作为子类的原型。

javascript
function Parent() {
  this.name = 'parent';
  this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child() {
  this.age = 10;
}
Child.prototype = new Parent();        // 核心
Child.prototype.constructor = Child;   // 修正构造函数指向

const child1 = new Child();
const child2 = new Child();
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue', 'green']  → 引用类型被共享!

优点:简单,继承了父类原型上的方法和父类实例属性。

缺点

  • 所有子类实例共享父类实例的引用类型属性(如数组、对象)。
  • 创建子类实例时无法向父类构造函数传参。

2. 构造函数继承(伪造对象/经典继承)

核心:在子类构造函数中调用父类构造函数,并绑定 this

javascript
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;
}

const child1 = new Child('Tom', 10);
const child2 = new Child('Jerry', 12);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue']  → 不共享,每个实例独立
child1.sayName(); // 报错!没有继承父类原型上的方法

优点

  • 解决了引用类型共享问题。
  • 可以向父类传递参数。

缺点

  • 方法定义在父类构造函数中(如果写在 Parent 内部,每个实例会重复创建方法,浪费内存)。
  • 无法继承父类原型上的方法,子类实例只是“拷贝”了父类实例的属性。

3. 组合继承(原型链 + 构造函数)

核心:用构造函数继承属性,用原型链继承方法。这是最常用的经典继承模式。

javascript
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 child1 = new Child('Tom', 10);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
child1.sayName(); // 'Tom'

优点:融合了原型链和构造函数的优点,实例属性独立,方法共享。

缺点调用了两次父类构造函数,造成一定的性能浪费(子类原型上多了一份多余的父类实例属性,会被实例属性覆盖)。

4. 原型式继承(Object.create)

核心:基于已有的对象创建新对象,本质上是对传入对象的一次浅复制。

javascript
const parent = {
  name: 'parent',
  colors: ['red', 'blue'],
  sayName() { console.log(this.name); }
};

const child1 = Object.create(parent);
const child2 = Object.create(parent);
child1.name = 'Tom';
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue', 'green']  → 引用类型依然共享

优点:简单,无需定义构造函数,适合对象直接继承。

缺点:和原型链继承一样,引用类型属性会被所有实例共享。

Object.create 的第二个参数可以定义额外属性(与 Object.defineProperties 相同)。

5. 寄生式继承

核心:在原型式继承的基础上,增强对象(添加方法)。

javascript
function createAnother(original) {
  const clone = Object.create(original);
  clone.sayHi = function() {   // 增强对象
    console.log('hi');
  };
  return clone;
}

const parent = { name: 'parent', colors: ['red'] };
const child = createAnother(parent);
child.sayHi(); // 'hi'

优点:适合主要关注对象,而不是类型和构造函数的场景。

缺点:方法在每个实例上都会创建一遍,不能复用;引用类型依然共享。

6. 寄生组合式继承(最理想的继承模式)

核心:通过寄生方式,砍掉父类实例属性,只继承原型。只调用一次父类构造函数

javascript
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 child1 = new Child('Tom', 10);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
child1.sayName();           // 'Tom'

优点

  • 只调用一次父类构造函数,效率高。
  • 原型链干净(子类原型上没有多余的父类实例属性)。
  • 实例属性独立,方法共享。
  • 被认为是最理想的继承方式

7. ES6 Class 的 extends 继承

ES6 引入了 classextends 关键字,底层依然是寄生组合式继承,但语法更清晰。

javascript
class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
  }
  sayName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);   // 相当于 Parent.call(this, name)
    this.age = age;
  }
  sayAge() {
    console.log(this.age);
  }
}

const child = new Child('Tom', 10);
child.colors.push('green');
console.log(child.colors); // ['red', 'blue', 'green']
child.sayName();           // 'Tom'

优点:语法简洁、易读,内部实现最优(寄生组合式)。

注意

  • 子类必须在 constructor 中调用 super(),否则 this 不可用。
  • 可以重写父类方法,并通过 super.method() 调用父类方法。

8. 继承方式对比总结

8.1 对比表格

方式实例属性独立方法共享可传参调用父类次数推荐程度
原型链继承1
构造函数继承❌(方法无法共享)1(每次实例化)
组合继承2⚠️(有缺陷但可用)
原型式继承✅(通过原型)0⚠️(适合简单对象)
寄生式继承❌(方法不共享)0⚠️
寄生组合式1✅(最佳)
ES6 extends1✅✅(最推荐)

8.2 面试回答建议

  • JavaScript 实现继承有七种常见方式,从最初的原型链继承寄生组合式继承,再到 ES6 的 class extends。最核心的问题是引用类型共享方法复用寄生组合式继承被认为是最理想的传统实现,而现代开发中直接使用 ES6 class 即可,它底层就是寄生组合式继承,语法更友好。
  • 如果面试官追问底层,可以补充:extends 内部通过 Object.setPrototypeOfObject.create 建立原型链,子类的 super 相当于 Parent.call(this)