Skip to content

JavaScript 原型与原型链

大白话解释: 原型链就像"家族传承"。每个对象都有一个"爸爸"(原型对象),爸爸也有自己的"爸爸",一直往上追溯,最终到 Object.prototype(老祖宗),再往上就是 null(不存在)。

为什么要理解原型链?

  • 理解继承机制:JavaScript 没有传统的"类继承",而是通过原型链实现
  • 理解 new 操作符new Person() 做了什么?创建对象、绑定原型、执行构造函数
  • 理解 class 本质:ES6 的 class 只是语法糖,底层还是原型链
  • 理解 instanceof:检查构造函数的 prototype 是否在对象的原型链上

原型链的查找规则:

  1. 先在对象自己身上找
  2. 没找到就去原型对象上找
  3. 还没找到就去原型的原型上找
  4. 一直找到 null 为止,找到就返回,找不到就 undefined

原型链是 JavaScript 实现继承的核心机制。理解原型链,才能真正理解 newclassinstanceof 背后发生了什么。


原型三角关系

JavaScript 中每个对象都有一个关联的原型对象,形成三角关系:

构造函数 (Constructor)

    │  .prototype(指向原型对象)

原型对象 (Prototype)

    │  Object.getPrototypeOf()(推荐)
    │  .__proto__(已废弃,仅用于演示)

实例对象 (Instance)
js
function Person(name) {
  this.name = name
}

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

const person = new Person('张三')

// 三角关系验证
console.log(Person.prototype.constructor === Person)            // true
console.log(Object.getPrototypeOf(person) === Person.prototype) // true(推荐用法)
console.log(person.__proto__ === Person.prototype)              // true(__proto__ 已废弃,仅作演示)
console.log(person.constructor === Person)                      // true(沿原型链找到的)

三者的区别

属性属于指向说明
prototype函数原型对象只有函数才有,实例没有
__proto__所有对象原型对象⚠️ 已废弃,推荐用 Object.getPrototypeOf()
constructor原型对象构造函数默认指向创建它的函数
js
// prototype 只有函数才有
console.log(typeof Person.prototype)           // 'object'
console.log(typeof person.prototype)           // 'undefined'

// Object.getPrototypeOf() 所有对象都有(推荐)
console.log(typeof Object.getPrototypeOf(person))  // 'object'
console.log(typeof Object.getPrototypeOf(Person))  // 'object'(函数也是对象)

// constructor 在原型对象上
console.log(Person.prototype.constructor)      // [Function: Person]
console.log(person.constructor)                // [Function: Person](沿原型链找到)

new 操作符的完整过程

js
const p = new Person('张三')

new 内部做了 4 件事:

js
function myNew(Constructor, ...args) {
  // 1. 创建一个空对象,原型指向构造函数的 prototype
  const obj = Object.create(Constructor.prototype)

  // 2. 执行构造函数,this 指向新对象
  const result = Constructor.apply(obj, args)

  // 3. 如果构造函数返回了对象,则用该对象;否则用新创建的对象
  // ⚠️ result instanceof Object 对 null 返回 false,所以 null 不会被误当作对象返回
  return result instanceof Object ? result : obj
}
js
// 验证
function Person(name) {
  this.name = name
}

const p1 = new Person('张三')
const p2 = myNew(Person, '张三')

console.log(p1.name)                // '张三'
console.log(p2.name)                // '张三'
console.log(p1.__proto__ === Person.prototype) // true
console.log(p2.__proto__ === Person.prototype) // true

构造函数返回对象的情况

js
function Special(name) {
  this.name = name
  return { custom: true } // 返回了一个对象
}

const obj = new Special('test')
console.log(obj.name)    // undefined(被丢弃了)
console.log(obj.custom)  // true(用了返回的对象)

原型链查找机制

当访问对象的属性时,JS 引擎会沿着原型链向上查找:

实例对象 → 构造函数.prototype → Object.prototype → null
js
function Animal(name) {
  this.name = name
}

Animal.prototype.eat = function () {
  return `${this.name} is eating`
}

function Dog(name) {
  Animal.call(this, name)
}

Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

Dog.prototype.bark = function () {
  return `${this.name} says woof!`
}

const dog = new Dog('旺财')

// 查找过程:
dog.bark()
// 1. dog 自身 → 没有 bark
// 2. Dog.prototype → 找到 bark ✅

dog.eat()
// 1. dog 自身 → 没有 eat
// 2. Dog.prototype → 没有 eat
// 3. Animal.prototype → 找到 eat ✅

dog.toString()
// 1. dog 自身 → 没有 toString
// 2. Dog.prototype → 没有 toString
// 3. Animal.prototype → 没有 toString
// 4. Object.prototype → 找到 toString ✅

dog.xyz
// 1. dog 自身 → 没有
// 2. Dog.prototype → 没有
// 3. Animal.prototype → 没有
// 4. Object.prototype → 没有
// 5. null → 返回 undefined

完整原型链图

dog (实例)
  └─ Object.getPrototypeOf() → Dog.prototype
                                  └─ Object.getPrototypeOf() → Animal.prototype
                                                                   └─ Object.getPrototypeOf() → Object.prototype
                                                                                                   └─ Object.getPrototypeOf() → null
js
// 验证原型链(推荐用 Object.getPrototypeOf)
console.log(Object.getPrototypeOf(dog) === Dog.prototype)                      // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype)         // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype)      // true
console.log(Object.getPrototypeOf(Object.prototype) === null)                  // true

hasOwnProperty vs in

js
function Person(name) {
  this.name = name
}
Person.prototype.type = 'human'

const p = new Person('张三')

// hasOwnProperty —— 只检查自身属性
console.log(p.hasOwnProperty('name'))  // true(自身)
console.log(p.hasOwnProperty('type'))  // false(原型上的)

// in —— 检查整个原型链
console.log('name' in p)  // true
console.log('type' in p)  // true(原型上也有)
console.log('xyz' in p)   // false

遍历属性的区别

js
// for...in —— 遍历自身 + 原型链上可枚举的属性
for (const key in p) {
  console.log(key) // 'name', 'type'
}

// Object.keys —— 只遍历自身可枚举属性
Object.keys(p) // ['name']

// Object.getOwnPropertyNames —— 只遍历自身所有属性(含不可枚举)
Object.getOwnPropertyNames(p) // ['name']

继承的 5 种方式

大白话解释: 继承就像"儿子继承父亲的财产"。在 JavaScript 中,子构造函数可以拥有父构造函数的属性和方法,不用重复写。5 种方式就像 5 种不同的"继承方案",从最简单的(但有缺陷的)到最完善的(class 语法糖)。

1. 原型链继承

js
function Animal(name) {
  this.name = name
  this.colors = ['black']
}
Animal.prototype.eat = function () {
  return `${this.name} is eating`
}

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

Dog.prototype = new Animal()

const dog1 = new Dog('旺财')
const dog2 = new Dog('小黑')

// ❌ 问题:引用类型的属性被所有实例共享
dog1.colors.push('white')
console.log(dog2.colors) // ['black', 'white'](被影响了!)

2. 构造函数继承(经典继承)

js
function Animal(name) {
  this.name = name
  this.colors = ['black']
}

function Dog(name, breed) {
  Animal.call(this, name) // 调用父构造函数
  this.breed = breed
}

const dog1 = new Dog('旺财', '金毛')
const dog2 = new Dog('小黑', '拉布拉多')

dog1.colors.push('white')
console.log(dog2.colors) // ['black'](不受影响 ✅)

// ❌ 问题:方法不能复用,每个实例都创建一份
// ❌ 问题:无法继承原型上的方法

3. 组合继承

js
function Animal(name) {
  this.name = name
  this.colors = ['black']
}
Animal.prototype.eat = function () {
  return `${this.name} is eating`
}

function Dog(name, breed) {
  Animal.call(this, name) // 第二次调用 Animal
  this.breed = breed
}

Dog.prototype = new Animal() // 第一次调用 Animal
Dog.prototype.constructor = Dog

// ✅ 解决了引用类型共享和方法复用问题
// ❌ 问题:Animal 被调用了两次

4. 寄生组合继承(最佳方案)

js
function Animal(name) {
  this.name = name
  this.colors = ['black']
}
Animal.prototype.eat = function () {
  return `${this.name} is eating`
}

function Dog(name, breed) {
  Animal.call(this, name)
  this.breed = breed
}

// 核心:用 Object.create 创建中间对象,避免调用 Animal
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

Dog.prototype.bark = function () {
  return `${this.name} says woof!`
}

const dog = new Dog('旺财', '金毛')
console.log(dog.eat())   // '旺财 is eating'
console.log(dog.bark())  // '旺财 says woof!'
console.log(dog instanceof Dog)    // true
console.log(dog instanceof Animal) // true

5. class 继承(ES6 推荐)

js
class Animal {
  constructor(name) {
    this.name = name
    this.colors = ['black']
  }

  eat() {
    return `${this.name} is eating`
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name) // 必须先调用 super,才能使用 this
    this.breed = breed
  }

  bark() {
    return `${this.name} says woof!`
  }
}

const dog = new Dog('旺财', '金毛')
console.log(dog.eat())  // '旺财 is eating'

继承方式对比

方式引用类型共享方法复用调用次数推荐度
原型链继承❌ 共享✅ 复用1
构造函数继承✅ 独立❌ 不复用1
组合继承✅ 独立✅ 复用2⚠️
寄生组合继承✅ 独立✅ 复用1
class 继承✅ 独立✅ 复用1✅✅

class 语法的原型本质

class 只是原型链的语法糖,底层仍然是基于原型的。

class 定义方法的等价形式

js
class Person {
  // 构造函数
  constructor(name) {
    this.name = name // 实例属性
  }

  // 原型方法(Person.prototype 上)
  sayHello() {
    return `Hello, I'm ${this.name}`
  }

  // 静态方法(Person 自身上,不在 prototype 上)
  static create(name) {
    return new Person(name)
  }

  // getter(Person.prototype 上)
  get info() {
    return { name: this.name }
  }
}

// 等价的 ES5 写法
function Person(name) {
  this.name = name
}

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

Person.create = function (name) {
  return new Person(name)
}

Object.defineProperty(Person.prototype, 'info', {
  get() {
    return { name: this.name }
  },
})

extends 的原型链等价

js
class Animal {}
class Dog extends Animal {}

// 等价于:
// 1. 实例的原型链
Dog.prototype.__proto__ === Animal.prototype // true

// 2. 构造函数本身的继承
Dog.__proto__ === Animal // true(静态方法也能继承)
js
// 验证
class Animal {
  static breathe() {
    return 'breathing'
  }
}
class Dog extends Animal {}

console.log(Dog.breathe())            // 'breathing'(静态方法继承了)
console.log(Dog.__proto__ === Animal) // true

instanceof 原理

instanceof 沿着原型链查找,检查构造函数的 prototype 是否在实例的原型链上。

js
function myInstanceof(obj, Constructor) {
  let proto = Object.getPrototypeOf(obj)
  while (proto !== null) {
    if (proto === Constructor.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}
js
class Animal {}
class Dog extends Animal {}

const dog = new Dog()

console.log(dog instanceof Dog)    // true
console.log(dog instanceof Animal) // true
console.log(dog instanceof Object) // true

自定义 instanceof 行为

js
class CustomType {
  static [Symbol.hasInstance](obj) {
    return typeof obj === 'string' && obj.length > 5
  }
}

console.log('hello' instanceof CustomType)     // false(长度 <= 5)
console.log('hello world' instanceof CustomType) // true(长度 > 5)

Object.create 与原型

js
// Object.create 创建指定原型的对象
const proto = {
  greet() {
    return `Hello, I'm ${this.name}`
  },
}

const person = Object.create(proto)
person.name = '张三'
console.log(person.greet()) // 'Hello, I'm 张三'

// 创建纯净对象(没有原型,没有继承任何方法)
const bare = Object.create(null)
console.log(bare.__proto__)        // undefined
console.log(bare.toString)         // undefined(没有 Object.prototype 的方法)
console.log(bare instanceof Object) // false

为什么需要 Object.create(null)

js
// 用普通对象做 Map 时,可能有 key 冲突
const map = {}
map['toString'] = 'oops' // 覆盖了 Object.prototype.toString!

// 用 Object.create(null) 就没有这个问题
const safeMap = Object.create(null)
safeMap['toString'] = 'safe' // 不会影响任何原型方法

常见面试题

⚠️ 面试题中常用 __proto__ 来演示原型链关系,便于理解。实际开发中请使用 Object.getPrototypeOf() 替代。

题目 1:原型链的终点是什么

js
const obj = {}
console.log(obj.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__ === null) // true
// 终点是 null

题目 2:Function.proto 是什么

js
console.log(Function.__proto__ === Function.prototype) // true
// Function 既是构造函数,也是自己的实例(特殊存在)
// Function.__proto__ === Function.prototype
// Object.__proto__ === Function.prototype
// Array.__proto__ === Function.prototype

题目 3:输出什么

js
function Foo() {}
const f1 = new Foo()
const f2 = new Foo()

console.log(f1.__proto__ === Foo.prototype)   // true
console.log(f1.__proto__ === f2.__proto__)    // true(共享原型)
console.log(f1.constructor === Foo)           // true
console.log(f1.constructor === f2.constructor) // true

题目 4:修改 prototype 后的关系

js
function Foo() {}
const f1 = new Foo()

// 此时
console.log(f1.__proto__ === Foo.prototype) // true

// 重新赋值 prototype
Foo.prototype = {}
const f2 = new Foo()

console.log(f1.__proto__ === Foo.prototype) // false(f1 的原型还是旧的)
console.log(f2.__proto__ === Foo.prototype) // true(f2 的原型是新的)
console.log(f1.constructor === Foo)         // false(旧原型的 constructor)
console.log(f2.constructor === Foo)         // false(新原型没有 constructor)
console.log(f2.constructor === Object)      // true(沿原型链找到 Object)

题目 5:class 继承的原型链

js
class A {}
class B extends A {}
class C extends B {}

const c = new C()

console.log(c.__proto__ === C.prototype)                // true
console.log(C.prototype.__proto__ === B.prototype)      // true
console.log(B.prototype.__proto__ === A.prototype)      // true
console.log(A.prototype.__proto__ === Object.prototype) // true

console.log(C.__proto__ === B)  // true(构造函数继承)
console.log(B.__proto__ === A)  // true
console.log(A.__proto__ === Function.prototype) // true

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准