主题
原型
原型是 JavaScript 实现“对象共享属性和方法”的底层机制。很多看起来分散的概念,比如构造函数、instanceof、class 继承,本质上都和原型有关。
先分清两个词
最容易混淆的是:
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 会按这条链往上找:
- 先找对象自身
- 自身没有,再找原型
- 原型也没有,就继续往上找
- 一直找到
Object.prototype - 最后还没有,就返回
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 没有绕开原型,而是在继续使用它。
哪些操作会受原型影响
比较常见的有:
- 方法共享
instanceoffor...in- 属性遮蔽
例如对象自身属性会遮蔽原型上的同名属性:
js
const base = { name: 'base' }
const obj = Object.create(base)
obj.name = 'self'
console.log(obj.name) // 'self'使用建议
- 先把原型理解成“对象复用属性和方法的底层机制”。
- 共享方法优先放在原型上,不要在每个实例里重复创建。
- 理解原型时优先使用
Object.getPrototypeOf(),少依赖__proto__。 - 看到
class、继承、instanceof时,优先想到背后仍然是原型链。
