JavaScript 原型与原型链
大白话解释: 原型链就像"家族传承"。每个对象都有一个"爸爸"(原型对象),爸爸也有自己的"爸爸",一直往上追溯,最终到 Object.prototype(老祖宗),再往上就是 null(不存在)。
为什么要理解原型链?
- 理解继承机制:JavaScript 没有传统的"类继承",而是通过原型链实现
- 理解
new操作符:new Person()做了什么?创建对象、绑定原型、执行构造函数 - 理解
class本质:ES6 的 class 只是语法糖,底层还是原型链 - 理解
instanceof:检查构造函数的 prototype 是否在对象的原型链上
原型链的查找规则:
- 先在对象自己身上找
- 没找到就去原型对象上找
- 还没找到就去原型的原型上找
- 一直找到
null为止,找到就返回,找不到就undefined
原型链是 JavaScript 实现继承的核心机制。理解原型链,才能真正理解 new、class、instanceof 背后发生了什么。
原型三角关系
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 → nulljs
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() → nulljs
// 验证原型链(推荐用 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) // truehasOwnProperty 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) // true5. 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) // trueinstanceof 原理
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