Skip to content

原型

原型是 JavaScript 实现“对象共享属性和方法”的底层机制。很多看起来分散的概念,比如构造函数、instanceofclass 继承,本质上都和原型有关。

先分清两个词

最容易混淆的是:

  • prototype:函数拥有的属性
  • 对象的原型:对象内部关联的上一级对象

可以先记成:

  • 构造函数通过 prototype 决定“实例共享什么”
  • 实例对象通过原型链去查找属性

prototype 是什么

普通对象没有 prototype 属性,函数才有:

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

User.prototype.sayHi = function () {
  console.log(`hi, ${this.name}`)
}

这里的 User.prototype 就是未来实例共享方法的地方。

实例是怎么连到原型上的

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

const user = new User('Lee')

通过 new User() 创建实例时,实例会关联到 User.prototype

js
console.log(Object.getPrototypeOf(user) === User.prototype) // true

这也是为什么 user 可以调用定义在 User.prototype 上的方法。

属性查找是怎么发生的

读取一个属性时,JavaScript 会按这条链往上找:

  1. 先找对象自身
  2. 自身没有,再找原型
  3. 原型也没有,就继续往上找
  4. 一直找到 Object.prototype
  5. 最后还没有,就返回 undefined
js
function User(name) {
  this.name = name
}

User.prototype.sayHi = function () {
  console.log(`hi, ${this.name}`)
}

const user = new User('Lee')

console.log(user.name) // 自身属性
console.log(user.sayHi) // 来自 User.prototype
console.log(user.toString) // 来自 Object.prototype

原型链

对象沿着原型不断向上连接,形成的查找链路就是原型链。

js
function User() {}

const user = new User()

console.log(Object.getPrototypeOf(user) === User.prototype) // true
console.log(Object.getPrototypeOf(User.prototype) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object.prototype) === null) // true

可以把它理解成一条“查不到就继续往上问”的链。

不建议依赖 __proto__

很多资料会直接讲 obj.__proto__,但更推荐用标准 API:

js
Object.getPrototypeOf(obj)
Object.setPrototypeOf(obj, proto)

__proto__ 更适合理解历史资料,不适合作为日常主写法。

constructor 是怎么来的

函数的 prototype 默认会带一个 constructor,指回函数本身:

js
function User() {}

console.log(User.prototype.constructor === User) // true

这个关系在很多情况下有用,但也不是完全不能被改掉。

instanceof 判断的是什么

A instanceof B 的本质是:

  • B.prototype 是否出现在 A 的原型链上
js
function User() {}

const user = new User()

console.log(user instanceof User) // true
console.log(user instanceof Object) // true

所以 instanceof 不是在判断“长得像不像”,而是在判断原型链关系。

原型和 class 的关系

class 只是把原型机制写得更清晰。

js
class User {
  sayHi() {
    console.log('hi')
  }
}

const user = new User()
console.log(Object.getPrototypeOf(user) === User.prototype) // true

也就是说,class 没有绕开原型,而是在继续使用它。

哪些操作会受原型影响

比较常见的有:

  • 方法共享
  • instanceof
  • for...in
  • 属性遮蔽

例如对象自身属性会遮蔽原型上的同名属性:

js
const base = { name: 'base' }
const obj = Object.create(base)

obj.name = 'self'
console.log(obj.name) // 'self'

使用建议

  • 先把原型理解成“对象复用属性和方法的底层机制”。
  • 共享方法优先放在原型上,不要在每个实例里重复创建。
  • 理解原型时优先使用 Object.getPrototypeOf(),少依赖 __proto__
  • 看到 class、继承、instanceof 时,优先想到背后仍然是原型链。

基于 MIT 许可发布