JavaScript 原型链深度解析:从概念到实践
前言
JavaScript 原型链是前端开发中最重要也是最容易混淆的概念之一。理解原型链不仅有助于我们掌握 JavaScript 的面向对象编程,更是深入理解继承、方法查找等核心机制的关键。本文将从基础概念开始,逐步深入到原型链的实际应用。
1. 核心概念理解
什么是原型?
在 JavaScript 中,每个对象都有一个内部属性指向另一个对象,这个被指向的对象就是原型。原型本身也是一个对象,它可以包含属性和方法,这些属性和方法可以被其他对象共享。
1const person = {
2 name: 'John',
3 age: 30,
4}
5
6
7console.log(person.__proto__)
三个关键属性
根据原型链的核心概念,我们需要理解三个核心概念:
__proto__
: 每个对象都有这个属性,指向自己的原型对象prototype
: 每个构造函数都有这个属性,指向实例对象的原型对象constructor
: 原型对象里的属性,指向构造函数本身
原型链关系图解
1构造函数 Person 实例对象 john
2┌─────────────────┐ ┌─────────────────┐
3│ │ │ │
4│ Person() │ │ john │
5│ │ │ name: 'John' │
6│ prototype ────┼────────▶│ age: 30 │
7│ │ │ │
8└─────────────────┘ │ __proto__ ────┼─┐
9 │ │ │
10 └─────────────────┘ │
11 │
12 │
13 ▼
14 ┌─────────────────────────────┐
15 │ Person.prototype │
16 │ │
17 │ constructor ──────────────┐│
18 │ ││
19 │ sayHello: function() ││
20 │ ││
21 │ __proto__ ────────────────┼┼─┐
22 │ ││ │
23 └────────────────────────────┘│ │
24 ▲ │ │
25 │ │ │
26 └────────────────────┘ │
27 │
28 ▼
29 ┌─────────────────────────────────┐
30 │ Object.prototype │
31 │ │
32 │ constructor: Object │
33 │ toString: function() │
34 │ valueOf: function() │
35 │ hasOwnProperty: 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 引擎会按照以下顺序查找:
- 首先在对象本身查找
- 如果没找到,沿着
__proto__
链向上查找 - 一直查找到
Object.prototype
- 如果还没找到,返回
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. 总结与最佳实践
核心要点回顾
-
理解三个关键概念:
__proto__
: 对象的原型指针prototype
: 构造函数的原型属性constructor
: 原型对象的构造函数引用
-
原型链查找机制:从对象本身开始,沿着
__proto__
链向上查找属性 -
继承实现:通过
Object.create()
或 ES6extends
建立原型链关系
最佳实践建议
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+ 提供了更简洁的类语法,但理解原型链仍然至关重要:
- 框架理解:许多 JavaScript 框架和库仍然基于原型链
- 调试能力:理解原型链有助于调试复杂的继承问题
- 性能优化:了解原型链可以帮助写出更高效的代码
- 面试准备:原型链是前端面试的常考点
个人笔记记录 2021 ~ 2025