核心概念
在开始之前,理解几个关键概念很重要:
- 构造函数 (Constructor): 用
new
关键字调用的函数,用于创建对象实例。 - 原型对象 (Prototype Object): 每个构造函数都有一个
prototype
属性,指向一个对象。这个对象包含了由该构造函数创建的所有实例共享的属性和方法。 - 实例 (Instance): 通过
new
构造函数创建的对象。 __proto__
/[[Prototype]]
: 每个对象(实例)都有一个内部链接(在旧浏览器中是__proto__
,标准中是[[Prototype]]
,可通过Object.getPrototypeOf()
访问),指向其构造函数的原型对象。这就是原型链的基础。- 原型链 (Prototype Chain): 当试图访问一个对象的属性时,如果在该对象本身找不到,JavaScript 会沿着
__proto__
链向上查找,直到找到该属性或到达链的末端 (null
)。
1. 原型链继承
-
核心思想: 让子类的原型对象等于父类的一个实例。
-
实现: Child.prototype = new Parent();
-
代码示例:
javascriptfunction Parent() { this.parentProp = 'Parent Property'; this.colors = ['red', 'blue']; // 引用类型属性 } Parent.prototype.getParentProp = function() { console.log(this.parentProp); } function Child() { this.childProp = 'Child Property'; } // 关键步骤:子类的原型指向父类的实例 Child.prototype = new Parent(); // 可选:修复构造函数指向(否则 Child 实例的 constructor 会指向 Parent) // Child.prototype.constructor = Child; // 如果需要的话 let child1 = new Child(); let child2 = new Child(); console.log(child1.parentProp); // "Parent Property" (来自父类实例) child1.getParentProp(); // "Parent Property" (来自父类原型) child1.colors.push('green'); console.log(child2.colors); // ["red", "blue", "green"] - 问题:共享了引用类型
-
图解:
-
优点:
- 简单直观,易于实现。
- 父类原型上的方法能被子类实例共享。
- 实例既是
instanceof Child
也是instanceof Parent
。
-
缺点:
- 共享引用类型: 父类实例上的引用类型属性(如
colors
数组)会被所有子类实例共享,一个实例修改会影响其他实例。 - 无法传递参数: 创建子类实例时,无法向父类构造函数传递参数。
- 创建父类实例有多余属性:
Child.prototype
上包含了parentProp
和colors
这些本应属于实例的属性。
- 共享引用类型: 父类实例上的引用类型属性(如
2. 构造函数继承
-
核心思想: 在子类构造函数内部,使用
call()
或apply()
调用父类构造函数,将父类的实例属性复制到子类实例上。 -
代码示例:
javascriptfunction Parent(name) { this.name = name; this.colors = ['red', 'blue']; this.sayName = function() { // 方法在构造函数中,非原型 console.log(this.name); } } Parent.prototype.parentProtoProp = 'Parent Proto Prop'; function Child(name, age) { // 关键步骤:借用父类构造函数,并传递参数 Parent.call(this, name); // this 指向 Child 实例 this.age = age; } let child1 = new Child('Alice', 10); let child2 = new Child('Bob', 12); child1.colors.push('green'); console.log(child1.name); // "Alice" console.log(child1.age); // 10 console.log(child1.colors); // ["red", "blue", "green"] console.log(child2.name); // "Bob" console.log(child2.colors); // ["red", "blue"] - 解决了共享引用类型问题 child1.sayName(); // "Alice" // console.log(child1.parentProtoProp); // undefined - 问题:无法继承父类原型属性/方法 // console.log(child1 instanceof Parent); // false - 问题:实例并非 Parent 的实例

- 优点:
- 解决引用类型共享问题: 每个子类实例都有自己独立的父类实例属性副本。
- 可以传递参数: 可以在子类构造函数中向父类构造函数传递参数。
- 缺点:
- 无法继承原型: 只能继承父类构造函数中的属性/方法,无法继承父类原型上的属性/方法。
- 方法冗余: 父类构造函数中的方法(如
sayName
)会被复制到每个子类实例上,无法复用,浪费内存。 instanceof
问题:子类实例不是父类构造函数的实例 (child1 instanceof Parent
为false
)。
3. 组合继承 (Combination Inheritance)
-
核心思想: 结合原型链继承和构造函数继承。使用构造函数继承来继承实例属性(解决引用共享和传参问题),使用原型链继承来继承原型方法(实现方法复用)。
-
代码示例:
javascriptfunction Parent(name) { this.name = name; this.colors = ['red', 'blue']; } Parent.prototype.sayHello = function() { console.log('Hello from Parent Proto'); } function Child(name, age) { // 步骤 1: 借用构造函数继承实例属性 Parent.call(this, name); this.age = age; } // 步骤 2: 原型链继承原型方法 Child.prototype = new Parent(); // 调用了一次 Parent 构造函数 Child.prototype.constructor = Child; // 修复 constructor 指向 Child.prototype.sayAge = function() { console.log(this.age); } let child1 = new Child('Alice', 10); let child2 = new Child('Bob', 12); child1.colors.push('green'); console.log(child1.name); // "Alice" console.log(child1.colors); // ["red", "blue", "green"] console.log(child2.name); // "Bob" console.log(child2.colors); // ["red", "blue"] child1.sayHello(); // "Hello from Parent Proto" child1.sayAge(); // 10 console.log(child1 instanceof Child); // true console.log(child1 instanceof Parent); // true
-
图解:

- 优点:
- 结合了前两种方式的优点:既能继承实例属性(独立副本、可传参),又能继承原型方法(共享)。
instanceof
正常工作。
- 缺点:
- 调用两次父类构造函数:
- 在子类构造函数中
Parent.call(this)
调用一次。 - 在设置子类原型时
Child.prototype = new Parent()
又调用一次。
- 在子类构造函数中
- 这导致子类原型对象上有一份多余的、从未被使用的父类实例属性(如图中
Child.prototype
也就是那个Parent Instance
也会有name
,colors
属性,虽然child1
实例上的同名属性会覆盖它们)。
- 调用两次父类构造函数:
4. 原型式继承
-
核心思想: 基于一个现有对象创建一个新对象,新对象的
[[Prototype]]
指向现有对象。ES5 提供了Object.create()
方法来实现。 -
代码示例:
javascriptlet person = { name: "Base Person", friends: ["Shelby", "Court"] }; let anotherPerson = Object.create(person); anotherPerson.name = "Greg"; // 覆盖属性 anotherPerson.friends.push("Rob"); let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(anotherPerson.name); // "Greg" console.log(yetAnotherPerson.name); // "Linda" console.log(person.friends); // ["Shelby", "Court", "Rob", "Barbie"] - 问题:共享引用类型
-
图解:

- 优点:
- 不需要显式创建构造函数即可实现继承。
Object.create()
可以传入第二个参数来定义新对象的自有属性。
- 缺点:
- 共享引用类型: 与原型链继承类似,原型对象上的引用类型属性会被所有实例共享。
- 无法直接实现构造函数模式(如传递初始化参数)。
5. 寄生式继承
-
核心思想: 创建一个仅用于封装继承过程的函数。该函数在内部以某种方式(如原型式继承)创建对象,然后增强该对象(添加属性/方法),最后返回这个对象。
-
代码示例:
javascriptfunction createAnother(original) { // 步骤 1: 创建一个继承自 original 的新对象 let clone = Object.create(original); // 或者使用 object(original) // 步骤 2: 增强这个新对象 clone.sayHi = function() { console.log("hi, I am " + this.name); }; // 步骤 3: 返回增强后的对象 return clone; } let person = { name: "Base Person", friends: ["Shelby"] }; let anotherPerson = createAnother(person); anotherPerson.name = "Greg"; anotherPerson.sayHi(); // "hi, I am Greg" let yetAnotherPerson = createAnother(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.sayHi(); // "hi, I am Linda" // 问题:sayHi 方法在每个实例上都重新创建了 console.log(anotherPerson.sayHi === yetAnotherPerson.sayHi); // false
-
图解: (类似原型式继承,但
createAnother
函数是关键)

- 优点:
- 可以为对象添加方法而无需修改原始对象或其原型。
- 代码封装性较好。
- 缺点:
- 方法冗余: 与构造函数继承类似,增强的方法(如
sayHi
)是在每个新对象上单独创建的,没有实现函数复用。 - 本质上还是基于原型式或其他继承方式,并带有其缺点(如引用类型共享)。
- 方法冗余: 与构造函数继承类似,增强的方法(如
6. 寄生组合式继承 - 推荐 (ES6 Class 之前)
-
核心思想: 结合构造函数继承(获取实例属性)和寄生式继承的思路来继承原型。避免了组合继承中调用两次父类构造函数的问题。
-
步骤:
- 使用
Parent.call(this, ...)
继承父类实例属性。 - 使用
Object.create(Parent.prototype)
创建一个空对象,其[[Prototype]]
指向父类原型。 - 将这个空对象赋值给
Child.prototype
。 - 修复
Child.prototype.constructor
指向Child
。
- 使用
-
代码示例:
javascriptfunction inheritPrototype(childConstructor, parentConstructor) { // 步骤 2 & 3: 创建父类原型的副本,并赋值给子类原型 let prototype = Object.create(parentConstructor.prototype); // 步骤 4: 修复 constructor 指向 prototype.constructor = childConstructor; childConstructor.prototype = prototype; } function Parent(name) { this.name = name; this.colors = ['red', 'blue']; } Parent.prototype.sayHello = function() { console.log('Hello from Parent Proto'); } function Child(name, age) { // 步骤 1: 借用构造函数 Parent.call(this, name); this.age = age; } // 关键步骤:调用辅助函数实现原型继承 inheritPrototype(Child, Parent); Child.prototype.sayAge = function() { console.log(this.age); } let child1 = new Child('Alice', 10); let child2 = new Child('Bob', 12); child1.colors.push('green'); console.log(child1.name); // "Alice" console.log(child1.colors); // ["red", "blue", "green"] console.log(child2.name); // "Bob" console.log(child2.colors); // ["red", "blue"] child1.sayHello(); // "Hello from Parent Proto" child1.sayAge(); // 10 console.log(child1 instanceof Child); // true console.log(child1 instanceof Parent); // true
-
图解:

- 优点:
- 只调用一次父类构造函数: 避免了组合继承的性能问题。
- 完美继承: 同时继承实例属性(独立、可传参)和原型方法(共享)。
instanceof
关系正确。- 被认为是 ES6
class extends
出现之前最理想的继承范式。
- 缺点:
- 实现相对复杂一些,需要封装辅助函数。
7. ES6 Class 继承
-
核心思想: 使用
class
和extends
关键字实现继承,是寄生组合式继承的语法糖 。super
关键字用于调用父类的构造函数和方法。 -
代码示例:
javascriptclass Parent { constructor(name) { this.name = name; this.colors = ['red', 'blue']; } sayHello() { console.log('Hello from Parent class'); } } class Child extends Parent { constructor(name, age) { // 关键:调用父类构造函数,相当于 Parent.call(this, name) super(name); this.age = age; } sayAge() { console.log(this.age); } } let child1 = new Child('Alice', 10); let child2 = new Child('Bob', 12); child1.colors.push('green'); console.log(child1.name); // "Alice" console.log(child1.colors); // ["red", "blue", "green"] console.log(child2.name); // "Bob" console.log(child2.colors); // ["red", "blue"] child1.sayHello(); // "Hello from Parent class" child1.sayAge(); // 10 console.log(child1 instanceof Child); // true console.log(child1 instanceof Parent); // true // class 内部的方法定义在原型上 console.log(child1.sayAge === Child.prototype.sayAge); // true
-
优点:
- 语法简洁清晰: 使用
class
,extends
,super
关键字,更符合传统面向对象语言的习惯。 - 内置实现: 本质上是寄生组合继承的最佳实践,解决了之前各种方式的缺点。
- 是现代 JavaScript 中推荐的继承方式。
- 语法简洁清晰: 使用
-
缺点:
- 需要 ES6 环境或构建工具(如 Babel)转译。
class
语法可能掩盖其基于原型的本质,对于初学者可能需要额外理解其工作原理。
总结:
JavaScript 的继承经历了从简单但有缺陷(原型链、构造函数)到组合(解决部分问题但有冗余)再到优化(原型式、寄生式、寄生组合式)最终到标准化、语法糖化(ES6 Class)的过程。目前,ES6 Class 继承 是最推荐和最常用的方式。