Skip to content

原型链与继承

JavaScript 的继承基于原型链,而非传统的类继承。ES6 的 class 只是语法糖,底层仍是原型。

原型链结构

每个对象都有一个内部属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf 访问),指向其原型对象。

js
const obj = { name: 'Alice' }

// 原型链:obj → Object.prototype → null
console.log(Object.getPrototypeOf(obj) === Object.prototype)  // true
console.log(Object.getPrototypeOf(Object.prototype))          // null(链的终点)

函数与 prototype

js
function Person(name) {
  this.name = name
}

Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}`
}

const alice = new Person('Alice')
const bob   = new Person('Bob')

// alice 和 bob 共享同一个 greet 方法(节省内存)
console.log(alice.greet === bob.greet)  // true

// 原型链:alice → Person.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(alice) === Person.prototype)  // true

new 操作符做了什么:

js
function myNew(Constructor, ...args) {
  // 1. 创建空对象,原型指向 Constructor.prototype
  const obj = Object.create(Constructor.prototype)
  // 2. 执行构造函数,this 指向新对象
  const result = Constructor.apply(obj, args)
  // 3. 如果构造函数返回对象则用它,否则返回 obj
  return result instanceof Object ? result : obj
}

const alice = myNew(Person, 'Alice')
console.log(alice.greet())  // Hi, I'm Alice

属性查找机制

js
function Animal(name) {
  this.name = name
}
Animal.prototype.type = 'animal'
Animal.prototype.speak = function() {
  return `${this.name} makes a sound`
}

const dog = new Animal('Rex')
dog.breed = 'Labrador'  // 实例自有属性

// 查找顺序:实例自有 → 原型 → 原型的原型 → ...
console.log(dog.breed)   // 'Labrador'(自有属性)
console.log(dog.type)    // 'animal'(原型属性)
console.log(dog.speak()) // 'Rex makes a sound'(原型方法)
console.log(dog.toString()) // '[object Object]'(Object.prototype 方法)

// hasOwnProperty 只检查自有属性
console.log(dog.hasOwnProperty('breed'))  // true
console.log(dog.hasOwnProperty('type'))   // false

ES6 class 语法糖

js
class Animal {
  #sound  // 私有字段(ES2022)

  constructor(name, sound) {
    this.name = name
    this.#sound = sound
  }

  speak() {
    return `${this.name} says ${this.#sound}`
  }

  // 静态方法
  static create(name, sound) {
    return new Animal(name, sound)
  }

  // getter/setter
  get info() {
    return `[Animal: ${this.name}]`
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name, 'Woof')  // 必须先调用 super
    this.tricks = []
  }

  learn(trick) {
    this.tricks.push(trick)
    return this
  }

  // 重写父类方法
  speak() {
    return `${super.speak()} 🐕`
  }
}

const rex = new Dog('Rex')
rex.learn('sit').learn('shake')
console.log(rex.speak())   // Rex says Woof 🐕
console.log(rex.info)      // [Animal: Rex]
console.log(rex.tricks)    // ['sit', 'shake']

// class 底层仍是原型
console.log(Object.getPrototypeOf(Dog) === Animal)                  // true(类继承)
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype) // true(实例继承)

原型式继承模式对比

js
// 方式一:Object.create(最纯粹的原型继承)
const animalProto = {
  speak() { return `${this.name} speaks` }
}

const cat = Object.create(animalProto)
cat.name = 'Whiskers'
console.log(cat.speak())  // Whiskers speaks

// 方式二:混入(Mixin)— 多继承的替代方案
const Serializable = {
  serialize() { return JSON.stringify(this) },
  deserialize(json) { return Object.assign(this, JSON.parse(json)) }
}

const Validatable = {
  validate() { return Object.keys(this).every(k => this[k] !== null) }
}

class User {
  constructor(name, email) {
    this.name = name
    this.email = email
  }
}

// 混入多个能力
Object.assign(User.prototype, Serializable, Validatable)

const user = new User('Alice', 'alice@example.com')
console.log(user.serialize())  // {"name":"Alice","email":"alice@example.com"}
console.log(user.validate())   // true

instanceof 原理

js
// instanceof 沿原型链查找
function myInstanceof(obj, Constructor) {
  let proto = Object.getPrototypeOf(obj)
  while (proto !== null) {
    if (proto === Constructor.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

const rex = new Dog('Rex')
console.log(myInstanceof(rex, Dog))    // true
console.log(myInstanceof(rex, Animal)) // true(原型链上有 Animal.prototype)
console.log(myInstanceof(rex, Array))  // false

常见陷阱

js
// ❌ 直接修改 prototype 会影响所有实例
Array.prototype.sum = function() {
  return this.reduce((a, b) => a + b, 0)
}
// 污染全局,与其他库冲突,for...in 会枚举到

// ✅ 用工具函数代替
function sum(arr) {
  return arr.reduce((a, b) => a + b, 0)
}

// ❌ 原型属性共享引用类型
function Team() {}
Team.prototype.members = []  // 所有实例共享同一个数组!

const t1 = new Team()
const t2 = new Team()
t1.members.push('Alice')
console.log(t2.members)  // ['Alice'] ← 被污染了

// ✅ 引用类型属性放在构造函数中
function Team() {
  this.members = []  // 每个实例独立的数组
}

总结

  • 原型链是 JS 继承的底层机制,class 是语法糖
  • new 创建对象时,实例的 [[Prototype]] 指向构造函数的 prototype
  • 属性查找沿原型链向上,直到 null
  • 引用类型属性应定义在构造函数中,方法定义在 prototype
  • 优先使用 class 语法,更清晰且支持私有字段

系统学习 Web 前端生态,深入底层架构