JavaScript 原型链深度解析

JavaScript 原型链深度解析:从概念到实践

前言

JavaScript 原型链是前端开发中最重要也是最容易混淆的概念之一。理解原型链不仅有助于我们掌握 JavaScript 的面向对象编程,更是深入理解继承、方法查找等核心机制的关键。本文将从基础概念开始,逐步深入到原型链的实际应用。

1. 核心概念理解

什么是原型?

在 JavaScript 中,每个对象都有一个内部属性指向另一个对象,这个被指向的对象就是原型。原型本身也是一个对象,它可以包含属性和方法,这些属性和方法可以被其他对象共享。

 1const person = {
 2  name: 'John',
 3  age: 30,
 4}
 5
 6
 7console.log(person.__proto__) 

三个关键属性

根据原型链的核心概念,我们需要理解三个核心概念:

  1. __proto__: 每个对象都有这个属性,指向自己的原型对象
  2. prototype: 每个构造函数都有这个属性,指向实例对象的原型对象
  3. constructor: 原型对象里的属性,指向构造函数本身

原型链关系图解

 1构造函数 Person              实例对象 john
 2┌─────────────────┐         ┌─────────────────┐
 3│                 │         │                 │
 4Person()      │         │     john
 5│                 │         │   name: 'John'
 6prototype ────┼────────▶│   age: 30
 7│                 │         │                 │
 8└─────────────────┘         │   __proto__ ────┼─┐
 9                            │                 │ │
10                            └─────────────────┘ │
11
12
13
14                            ┌─────────────────────────────┐
15Person.prototype
16                            │                             │
17constructor ──────────────┐│
18                            │                            ││
19sayHello: function()      ││
20                            │                            ││
21__proto__ ────────────────┼┼─┐
22                            │                            ││ │
23                            └────────────────────────────┘│ │
24                                     ▲                    │ │
25                                     │                    │ │
26                                     └────────────────────┘ │
27
28
29                            ┌─────────────────────────────────┐
30Object.prototype
31                            │                                 │
32constructor: Object
33toString: function()           │
34valueOf: function()            │
35hasOwnProperty: function()     │
36                            │                                 │
37__proto__: null
38                            └─────────────────────────────────┘

关系说明图

 1┌─────────────────────────────────────────────────────────────┐
 2原型链关系总览
 3└─────────────────────────────────────────────────────────────┘
 4
 51. 构造函数的 prototype 属性
 6   Person.prototype ──────────▶ Person 的原型对象
 7
 82. 实例对象的 __proto__ 属性
 9   john.__proto__ ────────────▶ Person.prototype
10
113. 原型对象的 constructor 属性
12   Person.prototype.constructor ──▶ Person 构造函数
13
144. 原型链向上查找
15   john.__proto__.__proto__ ──▶ Object.prototype ──▶ null

三个关键属性的代码验证

 1// 构造函数
 2function Person(name, age) {
 3  this.name = name
 4  this.age = age
 5}
 6
 7// 在原型上添加方法
 8Person.prototype.sayHello = function () {
 9  return `Hello, I'm ${this.name}`
10}
11
12// 创建实例
13const john = new Person('John', 30)
14
15// 验证三个关键属性的关系
16console.log('=== 三个关键属性关系验证 ===')
17
18// 1. __proto__ 验证
19console.log(
20  'john.__proto__ === Person.prototype:',
21  john.__proto__ === Person.prototype
22) // true
23
24// 2. prototype 验证
25console.log(
26  'Person.prototype 指向原型对象:',
27  typeof Person.prototype === 'object'
28) // true
29
30// 3. constructor 验证
31console.log(
32  'Person.prototype.constructor === Person:',
33  Person.prototype.constructor === Person
34) // true
35console.log('john.constructor === Person:', john.constructor === Person) // true
36
37// 完整的原型链关系
38console.log('\n=== 完整原型链关系 ===')
39console.log('john.__proto__:', john.__proto__)
40console.log('john.__proto__.__proto__:', john.__proto__.__proto__)
41console.log(
42  'john.__proto__.__proto__.__proto__:',
43  john.__proto__.__proto__.__proto__
44)

原型链层次结构图

 1实例层级:     john (实例对象)
 2
 3__proto__
 4
 5原型层级:     Person.prototype (构造函数原型)
 6
 7__proto__
 8
 9基础层级:     Object.prototype (根原型)
10
11__proto__
12
13终点:        null

属性查找流程图

 1访问 john.sayHello() 时的查找过程:
 2
 3john 对象中查找 sayHello
 4
 5        ▼ (未找到)
 6john.__proto__ (Person.prototype) 中查找
 7
 8        ▼ (找到!)
 9返回 Person.prototype.sayHello
10
11访问 john.toString() 时的查找过程:
12
13john 对象中查找 toString
14
15        ▼ (未找到)
16john.__proto__ (Person.prototype) 中查找
17
18        ▼ (未找到)
19john.__proto__.__proto__ (Object.prototype) 中查找
20
21        ▼ (找到!)

2. 原型链的工作机制

属性查找过程

当我们访问一个对象的属性时,JavaScript 引擎会按照以下顺序查找:

  1. 首先在对象本身查找
  2. 如果没找到,沿着 __proto__ 链向上查找
  3. 一直查找到 Object.prototype
  4. 如果还没找到,返回 undefined
 1function Animal(species) {
 2  this.species = species
 3}
 4
 5Animal.prototype.eat = function () {
 6  return `${this.species} is eating`
 7}
 8
 9function Dog(name, breed) {
10  Animal.call(this, 'Dog') // 继承 Animal 的属性
11  this.name = name
12  this.breed = breed
13}
14
15// 设置原型链:Dog.prototype -> Animal.prototype -> Object.prototype
16Dog.prototype = Object.create(Animal.prototype)
17Dog.prototype.constructor = Dog
18
19Dog.prototype.bark = function () {
20  return `${this.name} is barking`
21}
22
23const myDog = new Dog('Buddy', 'Golden Retriever')
24
25// 属性查找演示
26console.log(myDog.name) // "Buddy" - 在实例上找到
27console.log(myDog.bark()) // "Buddy is barking" - 在 Dog.prototype 上找到
28console.log(myDog.eat()) // "Dog is eating" - 在 Animal.prototype 上找到
29console.log(myDog.toString()) // "[object Object]" - 在 Object.prototype 上找到

原型链图解

 1// 原型链结构演示
 2function createPrototypeChain() {
 3  function Animal(type) {
 4    this.type = type
 5  }
 6
 7  Animal.prototype.move = function () {
 8    return `${this.type} is moving`
 9  }
10
11  function Dog(name) {
12    Animal.call(this, 'Dog')
13    this.name = name
14  }
15
16  Dog.prototype = Object.create(Animal.prototype)
17  Dog.prototype.constructor = Dog
18  Dog.prototype.bark = function () {
19    return 'Woof!'
20  }
21
22  const dog = new Dog('Rex')
23
24  // 打印原型链
25  console.log(
26    'dog.__proto__ === Dog.prototype:',
27    dog.__proto__ === Dog.prototype
28  )
29  console.log(
30    'Dog.prototype.__proto__ === Animal.prototype:',
31    Dog.prototype.__proto__ === Animal.prototype
32  )
33  console.log(
34    'Animal.prototype.__proto__ === Object.prototype:',
35    Animal.prototype.__proto__ === Object.prototype
36  )
37  console.log(
38    'Object.prototype.__proto__ === null:',
39    Object.prototype.__proto__ === null
40  )
41
42  return dog
43}
44
45createPrototypeChain()

3. 构造函数与原型的关系

构造函数的工作原理

当使用 new 操作符调用构造函数时,发生以下步骤:

 1function Person(name, age) {
 2  // 1. 创建一个新对象
 3  // 2. 将新对象的 __proto__ 指向构造函数的 prototype
 4  // 3. 将 this 绑定到新对象
 5  this.name = name
 6  this.age = age
 7  // 4. 返回新对象(除非显式返回其他对象)
 8}
 9
10Person.prototype.greet = function () {
11  return `Hello, I'm ${this.name}, ${this.age} years old.`
12}
13
14// 模拟 new 操作符的工作过程
15function myNew(constructor, ...args) {
16  // 1. 创建新对象,并设置原型链
17  const obj = Object.create(constructor.prototype)
18
19  // 2. 调用构造函数,绑定 this
20  const result = constructor.apply(obj, args)
21
22  // 3. 返回对象
23  return result instanceof Object ? result : obj
24}
25
26const person1 = new Person('Alice', 25)
27const person2 = myNew(Person, 'Bob', 30)
28
29console.log(person1.greet()) // "Hello, I'm Alice, 25 years old."
30console.log(person2.greet()) // "Hello, I'm Bob, 30 years old."

原型方法的共享

 1function User(username) {
 2  this.username = username
 3  this.loginCount = 0
 4}
 5
 6// 在原型上定义方法,所有实例共享
 7User.prototype.login = function () {
 8  this.loginCount++
 9  console.log(`${this.username} logged in. Total logins: ${this.loginCount}`)
10}
11
12User.prototype.logout = function () {
13  console.log(`${this.username} logged out.`)
14}
15
16const user1 = new User('john_doe')
17const user2 = new User('jane_smith')
18
19// 验证方法共享
20console.log(user1.login === user2.login) // true - 同一个函数对象
21
22user1.login() // "john_doe logged in. Total logins: 1"
23user2.login() // "jane_smith logged in. Total logins: 1"
24
25// 动态添加原型方法
26User.prototype.changePassword = function () {
27  console.log(`${this.username} changed password.`)
28}
29
30user1.changePassword() // "john_doe changed password." - 已存在实例也能使用新方法

4. 原型继承的实现方式

经典继承模式

 1// 父类
 2function Animal(name, species) {
 3  this.name = name
 4  this.species = species
 5  this.energy = 100
 6}
 7
 8Animal.prototype.eat = function (food) {
 9  this.energy += 10
10  return `${this.name} is eating ${food}. Energy: ${this.energy}`
11}
12
13Animal.prototype.sleep = function () {
14  this.energy += 20
15  return `${this.name} is sleeping. Energy: ${this.energy}`
16}
17
18// 子类
19function Cat(name, breed) {
20  // 调用父类构造函数
21  Animal.call(this, name, 'Cat')
22  this.breed = breed
23}
24
25// 设置原型继承
26Cat.prototype = Object.create(Animal.prototype)
27Cat.prototype.constructor = Cat
28
29// 添加子类特有方法
30Cat.prototype.meow = function () {
31  return `${this.name} says: Meow!`
32}
33
34// 重写父类方法
35Cat.prototype.sleep = function () {
36  this.energy += 25 // 猫睡觉恢复更多能量
37  return `${this.name} is purring while sleeping. Energy: ${this.energy}`
38}
39
40const myCat = new Cat('Whiskers', 'Persian')
41console.log(myCat.eat('fish')) // "Whiskers is eating fish. Energy: 110"
42console.log(myCat.meow()) // "Whiskers says: Meow!"
43console.log(myCat.sleep()) // "Whiskers is purring while sleeping. Energy: 135"

ES6 Class 语法糖

 1// 使用 ES6 class 语法实现同样的继承
 2class Animal {
 3  constructor(name, species) {
 4    this.name = name
 5    this.species = species
 6    this.energy = 100
 7  }
 8
 9  eat(food) {
10    this.energy += 10
11    return `${this.name} is eating ${food}. Energy: ${this.energy}`
12  }
13
14  sleep() {
15    this.energy += 20
16    return `${this.name} is sleeping. Energy: ${this.energy}`
17  }
18}
19
20class Cat extends Animal {
21  constructor(name, breed) {
22    super(name, 'Cat')
23    this.breed = breed
24  }
25
26  meow() {
27    return `${this.name} says: Meow!`
28  }
29
30  sleep() {
31    this.energy += 25
32    return `${this.name} is purring while sleeping. Energy: ${this.energy}`
33  }
34}
35
36const cat = new Cat('Luna', 'Siamese')
37console.log(cat instanceof Cat) // true
38console.log(cat instanceof Animal) // true
39
40// ES6 class 本质上还是基于原型的
41console.log(Cat.prototype.__proto__ === Animal.prototype) // true

5. 原型链的实际应用

扩展内置对象原型

 1// 为 Array 原型添加自定义方法
 2Array.prototype.last = function () {
 3  return this[this.length - 1]
 4}
 5
 6Array.prototype.first = function () {
 7  return this[0]
 8}
 9
10const numbers = [1, 2, 3, 4, 5]
11console.log(numbers.first()) // 1
12console.log(numbers.last()) // 5
13
14// 为 String 原型添加方法
15String.prototype.capitalizeWords = function () {
16  return this.split(' ')
17    .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
18    .join(' ')
19}
20
21console.log('hello world javascript'.capitalizeWords()) // "Hello World Javascript"
22
23// 注意:在生产环境中修改内置对象原型需要谨慎

创建工厂函数

 1function createLogger(prefix) {
 2  function Logger(name) {
 3    this.name = name
 4    this.prefix = prefix
 5  }
 6
 7  Logger.prototype.log = function (message) {
 8    console.log(`[${this.prefix}] ${this.name}: ${message}`)
 9  }
10
11  Logger.prototype.error = function (message) {
12    console.error(`[${this.prefix}] ${this.name} ERROR: ${message}`)
13  }
14
15  Logger.prototype.warn = function (message) {
16    console.warn(`[${this.prefix}] ${this.name} WARN: ${message}`)
17  }
18
19  return Logger
20}
21
22const AppLogger = createLogger('APP')
23const APILogger = createLogger('API')
24
25const userLogger = new AppLogger('UserService')
26const authLogger = new APILogger('AuthService')
27
28userLogger.log('User logged in') // [APP] UserService: User logged in
29authLogger.error('Authentication failed') // [API] AuthService ERROR: Authentication failed

6. 常见问题与最佳实践

原型污染问题

 1// 错误示例:修改 Object.prototype
 2Object.prototype.customMethod = function () {
 3  return 'This is dangerous!'
 4}
 5
 6// 这会影响所有对象
 7const obj = {}
 8console.log(obj.customMethod()) // "This is dangerous!"
 9
10// 正确做法:使用 Object.defineProperty 并设置为不可枚举
11Object.defineProperty(Object.prototype, 'safeMethod', {
12  value: function () {
13    return 'This is safer!'
14  },
15  writable: true,
16  configurable: true,
17  enumerable: false, // 不会出现在 for...in 循环中
18})

检查原型链关系

 1function Vehicle(type) {
 2  this.type = type
 3}
 4
 5function Car(brand, model) {
 6  Vehicle.call(this, 'Car')
 7  this.brand = brand
 8  this.model = model
 9}
10
11Car.prototype = Object.create(Vehicle.prototype)
12Car.prototype.constructor = Car
13
14const myCar = new Car('Toyota', 'Camry')
15
16// 多种方式检查原型链关系
17console.log(myCar instanceof Car) // true
18console.log(myCar instanceof Vehicle) // true
19console.log(myCar instanceof Object) // true
20
21console.log(Car.prototype.isPrototypeOf(myCar)) // true
22console.log(Vehicle.prototype.isPrototypeOf(myCar)) // true
23console.log(Object.prototype.isPrototypeOf(myCar)) // true
24
25console.log(myCar.constructor === Car) // true
26
27// 使用 Object.getPrototypeOf 获取原型
28console.log(Object.getPrototypeOf(myCar) === Car.prototype) // true

安全地扩展原型

 1// 创建一个安全的原型扩展函数
 2function extendPrototype(Constructor, methods) {
 3  Object.keys(methods).forEach((methodName) => {
 4    if (Constructor.prototype[methodName]) {
 5      console.warn(
 6        `Method ${methodName} already exists on ${Constructor.name}.prototype`
 7      )
 8      return
 9    }
10
11    Constructor.prototype[methodName] = methods[methodName]
12  })
13}
14
15function User(name) {
16  this.name = name
17  this.friends = []
18}
19
20// 安全地扩展 User 原型
21extendPrototype(User, {
22  addFriend: function (friend) {
23    if (!this.friends.includes(friend)) {
24      this.friends.push(friend)
25    }
26    return this // 支持链式调用
27  },
28
29  removeFriend: function (friend) {
30    this.friends = this.friends.filter((f) => f !== friend)
31    return this
32  },
33
34  getFriends: function () {
35    return this.friends.slice() // 返回副本
36  },
37})
38
39const user = new User('Alice')
40user.addFriend('Bob').addFriend('Charlie')
41console.log(user.getFriends()) // ['Bob', 'Charlie']

7. 性能考虑

原型方法 vs 实例方法

 1// 性能测试:原型方法 vs 实例方法
 2function UserWithInstanceMethods(name) {
 3  this.name = name
 4
 5  // 实例方法 - 每个实例都会创建新的函数对象
 6  this.greet = function () {
 7    return `Hello, ${this.name}`
 8  }
 9}
10
11function UserWithPrototypeMethods(name) {
12  this.name = name
13}
14
15// 原型方法 - 所有实例共享同一个函数对象
16UserWithPrototypeMethods.prototype.greet = function () {
17  return `Hello, ${this.name}`
18}
19
20// 创建大量实例进行性能对比
21console.time('Instance Methods')
22const instanceUsers = []
23for (let i = 0; i < 10000; i++) {
24  instanceUsers.push(new UserWithInstanceMethods(`User${i}`))
25}
26console.timeEnd('Instance Methods')
27
28console.time('Prototype Methods')
29const prototypeUsers = []
30for (let i = 0; i < 10000; i++) {
31  prototypeUsers.push(new UserWithPrototypeMethods(`User${i}`))
32}
33console.timeEnd('Prototype Methods')
34
35// 原型方法通常更节省内存

8. 现代 JavaScript 中的原型链

使用 Object.create 创建干净的继承

 1// 创建没有原型的对象
 2const pureObject = Object.create(null)
 3pureObject.name = 'Pure'
 4console.log(pureObject.toString) // undefined - 没有继承 Object.prototype
 5
 6// 使用 Object.create 实现继承
 7const animal = {
 8  type: 'Animal',
 9  eat() {
10    console.log(`${this.name} is eating`)
11  },
12}
13
14const dog = Object.create(animal)
15dog.name = 'Buddy'
16dog.breed = 'Labrador'
17dog.bark = function () {
18  console.log(`${this.name} is barking`)
19}
20
21console.log(dog.type) // "Animal" - 从原型继承
22dog.eat() // "Buddy is eating"
23dog.bark() // "Buddy is barking"

使用 Proxy 拦截原型链访问

 1function createSmartObject(data) {
 2  return new Proxy(data, {
 3    get(target, property) {
 4      if (property in target) {
 5        return target[property]
 6      }
 7
 8      // 自定义原型链查找逻辑
 9      console.log(
10        `Property '${property}' not found, searching in prototype chain...`
11      )
12
13      // 可以实现自定义的属性查找逻辑
14      if (property === 'dynamicProperty') {
15        return 'This is dynamically generated!'
16      }
17
18      return undefined
19    },
20  })
21}
22
23const smartObj = createSmartObject({ name: 'Smart Object' })
24console.log(smartObj.name) // "Smart Object"
25console.log(smartObj.dynamicProperty) // "This is dynamically generated!"

9. 调试原型链

实用的调试技巧

 1// 创建一个用于调试原型链的工具函数
 2function debugPrototypeChain(obj, maxDepth = 10) {
 3  const chain = []
 4  let current = obj
 5  let depth = 0
 6
 7  while (current && depth < maxDepth) {
 8    chain.push({
 9      depth,
10      constructor: current.constructor?.name || 'Unknown',
11      properties: Object.getOwnPropertyNames(current),
12      prototype: current,
13    })
14
15    current = Object.getPrototypeOf(current)
16    depth++
17  }
18
19  return chain
20}
21
22function Animal(name) {
23  this.name = name
24}
25Animal.prototype.species = 'Unknown'
26
27function Dog(name, breed) {
28  Animal.call(this, name)
29  this.breed = breed
30}
31Dog.prototype = Object.create(Animal.prototype)
32Dog.prototype.constructor = Dog
33
34const myDog = new Dog('Rex', 'German Shepherd')
35
36// 调试原型链
37const chain = debugPrototypeChain(myDog)
38console.table(chain)

10. 总结与最佳实践

核心要点回顾

  1. 理解三个关键概念

    • __proto__: 对象的原型指针
    • prototype: 构造函数的原型属性
    • constructor: 原型对象的构造函数引用
  2. 原型链查找机制:从对象本身开始,沿着 __proto__ 链向上查找属性

  3. 继承实现:通过 Object.create() 或 ES6 extends 建立原型链关系

最佳实践建议

 1// 1. 使用 Object.create 而不是直接赋值原型
 2// ❌ 错误做法
 3Child.prototype = Parent.prototype // 会直接修改父类原型
 4
 5// ✅ 正确做法
 6Child.prototype = Object.create(Parent.prototype)
 7Child.prototype.constructor = Child
 8
 9// 2. 谨慎修改内置对象原型
10// ❌ 避免这样做
11Array.prototype.myMethod = function () {
12  /*...*/
13}
14
15// ✅ 更好的做法
16const ArrayWithExtensions = class extends Array {
17  myMethod() {
18    /*...*/
19  }
20}
21
22// 3. 使用现代语法
23// ✅ 推荐使用 ES6 class
24class Animal {
25  constructor(name) {
26    this.name = name
27  }
28
29  speak() {
30    console.log(`${this.name} makes a sound`)
31  }
32}
33
34class Dog extends Animal {
35  speak() {
36    console.log(`${this.name} barks`)
37  }
38}
39
40// 4. 性能优化:原型方法优于实例方法
41// ✅ 原型方法 - 内存效率更高
42User.prototype.greet = function () {
43  return `Hello, ${this.name}`
44}
45
46// ❌ 实例方法 - 每个实例都创建新函数
47function User(name) {
48  this.name = name
49  this.greet = function () {
50    return `Hello, ${this.name}`
51  }
52}

现代开发中的应用

在现代 JavaScript 开发中,虽然 ES6+ 提供了更简洁的类语法,但理解原型链仍然至关重要:

  1. 框架理解:许多 JavaScript 框架和库仍然基于原型链
  2. 调试能力:理解原型链有助于调试复杂的继承问题
  3. 性能优化:了解原型链可以帮助写出更高效的代码
  4. 面试准备:原型链是前端面试的常考点
个人笔记记录 2021 ~ 2025