原型与继承

张玥 2026年2月14日 阅读时间 40分钟
JavaScript 原型 继承 原型链 ES6
JavaScript原型与继承概念图

原型(prototype)是JavaScript这门语言最核心、也是最难啃的骨头之一。它不仅是实现对象继承的基础,更是理解JS内部机制的关键。本文将带你从零开始,全面剖析原型、原型链、各种继承模式以及ES6类语法,配合大量代码和示意图,让你彻底掌握“原型与继承”。无论你是刚入门还是想加深理解,这篇文章都能提供足够的深度。

原型(Prototype)基础

每个JavaScript函数都有一个特殊的prototype属性,指向一个对象。当这个函数作为构造函数(使用new)调用时,新创建的对象会内部链接到该原型对象,这个链接称为[[Prototype]](可通过__proto__Object.getPrototypeOf()访问)。

// 构造函数
function Person(name) {
  this.name = name;
}

// 向原型添加方法
Person.prototype.sayHello = function() {
  console.log(`你好,我是${this.name}`);
};

// 创建实例
const p1 = new Person('Alice');
p1.sayHello(); // 你好,我是Alice

// 查看原型关系
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
构造函数 Person
Person.prototype
{ sayHello, constructor }
实例 p1
name: "Alice"

p1.__proto__ → Person.prototype → Person.prototype.__proto__ → Object.prototype

关键点:所有实例共享原型上的方法和属性,但实例属性(如name)是独立的。原型链查找:访问对象属性时,先找自身,再沿__proto__向上查找。

原型链(Prototype Chain)

原型对象本身也是一个对象,它也有自己的原型,这样就构成了原型链。最终链顶是Object.prototype,其原型为null

// 定义两个构造函数
function Animal(type) {
  this.type = type;
}
Animal.prototype.getType = function() {
  return this.type;
};

function Dog(name) {
  this.name = name;
}

// 关键:让Dog.prototype 继承 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor

Dog.prototype.bark = function() {
  console.log('汪汪!');
};

const d1 = new Dog('Buddy');
d1.bark(); // 汪汪!
// d1本身没有type,但可以从原型链获取
console.log(d1.type); // undefined (因为Animal构造函数未执行)
d1Dog.prototypeAnimal.prototypeObject.prototypenull
✅ 方法查找: d1.bark → Dog.prototype
✅ getType → Animal.prototype
❌ type属性不存在 (因为未调用Animal构造函数)

继承模式深度剖析

JavaScript中实现继承有多种方式,每种都有其优缺点。我们逐一分析。

1. 原型链继承

// 父类
function Parent() {
  this.colors = ['red','blue'];
}
Parent.prototype.getColors = function() {
  return this.colors;
};

// 子类
function Child() {}
Child.prototype = new Parent(); // 原型指向父实例

const c1 = new Child();
c1.colors.push('green');
const c2 = new Child();
console.log(c2.colors); // ['red','blue','green'] 引用共享!
⚠️ 问题:父类实例属性变成子类原型上的共享属性,所有子实例共享引用类型值。

2. 盗用构造函数 (Constructor Stealing)

function Parent(name) {
  this.name = name;
  this.colors = ['red','blue'];
}

function Child(name) {
  Parent.call(this, name); // 借用构造函数
}

const c1 = new Child('Tom');
c1.colors.push('green');
const c2 = new Child('Jerry');
console.log(c2.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;

const c = new Child('Lisa',12);
c.sayName(); // Lisa
✅ 优点:属性独立,方法复用。
⚠️ 缺点:调用了两次父构造函数,子原型上有多余的父实例属性。

4. 寄生组合继承 (完美方案)

function inheritPrototype(Child, Parent) {
  const proto = Object.create(Parent.prototype);
  proto.constructor = Child;
  Child.prototype = proto;
}

function Parent(name) {
  this.name = name;
}
Parent.prototype.say = function() {};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inheritPrototype(Child, Parent);

// 只调用一次Parent,且原型链干净
✅ 目前最理想的继承模式,ES6 class 底层使用的就是类似逻辑。

ES6 Class 与 extends

ES6引入的class语法让原型继承更清晰,但其本质依然是基于原型的。

class Animal {
  constructor(type) {
    this.type = type;
  }
  getType() { return this.type; }
}

class Dog extends Animal {
  constructor(name, type) {
    super(type); // 调用父构造函数
    this.name = name;
  }
  bark() { console.log('汪汪'); }
}

const d = new Dog('Buddy','犬科');
console.log(d instanceof Dog); // true
console.log(d instanceof Animal); // true
实例 d
Dog.prototype
Animal.prototype
Object.prototype
null

super()必须在使用this之前调用,且只能用在constructor中。extends 会设置原型链:Object.setPrototypeOf(Dog, Animal) 以及 Dog.prototype = Object.create(Animal.prototype)

原型方法 vs 实例方法

function MyClass(value) {
  this.value = value; // 实例属性
  this.instanceMethod = function() { // 实例方法
    console.log('实例方法', this.value);
  };
}

MyClass.prototype.protoMethod = function() { // 原型方法
  console.log('原型方法', this.value);
};

const a = new MyClass(1);
const b = new MyClass(2);
console.log(a.instanceMethod === b.instanceMethod); // false
console.log(a.protoMethod === b.protoMethod); // true

原型方法 共享同一函数,节省内存。

🔧 实例方法 每个实例独立拷贝,适合需要闭包或动态生成的场景。

🔄 一般将可复用的方法定义在原型上,实例属性放在构造函数内。

内置对象原型与扩展

所有内置对象(Array, Function, Object等)都有自己的原型,我们可以修改它们,但不推荐。

// 不要这样做!
Array.prototype.first = function() { return this[0]; };
const arr = [1,2,3];
console.log(arr.first()); // 1

// 但是可能引发冲突或意外行为,尤其在不同库之间。
// 更好的做法是使用静态方法或继承。
⚠️ 修改内置原型会导致不可预测的问题,在团队项目中严禁使用。除非是polyfill。

性能与最佳实践

  • 原型链不宜过长:每层查找都有性能损耗,保持原型链扁平。
  • 使用 hasOwnProperty 过滤原型属性:遍历对象时配合Object.hasOwn()hasOwnProperty
  • 善用 Object.create(null):创建无原型的纯净对象(适合作为字典)。
  • 避免动态修改原型:性能差且不利于优化。
  • ES6 class 更清晰:推荐用于新项目,但理解原型有助于调试。
// 遍历安全模式
for (let key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key);
  }
}

// 或使用 Object.keys(obj) 只返回自有属性

调试技巧

// Chrome控制台
const obj = {a:1};
console.dir(obj); // 展开__proto__
console.log(obj.__proto__);
console.log(Object.getPrototypeOf(obj));

// 检查原型链
console.log(obj instanceof Object);
console.log(Object.prototype.isPrototypeOf(obj));
▶ Object { a: 1 }
  a: 1
  ▶ __proto__: Object
    ▶ constructor: ƒ Object()
    ▶ hasOwnProperty: ƒ hasOwnProperty()
    ...