Appearance
原型与原型链
难度等级:🟡 中级(2-4年)
分类:JavaScript
归档日期:2026-05-14
📚 理论知识
核心概念
每个 JavaScript 对象都有一个内部链接指向另一个对象,这个对象就是它的原型。原型本身也是一个对象,也有自己的原型,如此形成链条。
- 每个函数都有
prototype属性(显式原型) - 每个对象都有
__proto__属性(隐式原型),指向其构造函数的prototype
三者关系
js
function Person(name) {
this.name = name
}
const p = new Person('Alice')
p.__proto__ === Person.prototype // true
Person.prototype.constructor === Person // true关系链:
p (实例)
└── __proto__ ──► Person.prototype
├── constructor ──► Person(函数)
└── __proto__ ──► Object.prototype
└── __proto__ ──► null原型链(Prototype Chain)
当访问一个对象的属性时,JS 引擎会:
- 先在对象本身查找
- 找不到,沿
__proto__向上查找原型对象 - 继续向上,直到
Object.prototype - 还找不到,返回
undefined(到达null终止)
js
function Animal(name) {
this.name = name
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound.`
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
Dog.prototype.bark = function () {
return `${this.name} barks!`
}
const dog = new Dog('Rex')
dog.bark() // Rex barks! ← 在 Dog.prototype 找到
dog.speak() // Rex makes a sound. ← 在 Animal.prototype 找到
dog.toString() // ← 在 Object.prototype 找到new 操作符做了什么
js
function myNew(Constructor, ...args) {
// 1. 创建空对象,__proto__ 指向构造函数的 prototype
const obj = Object.create(Constructor.prototype)
// 2. 执行构造函数,绑定 this
const result = Constructor.apply(obj, args)
// 3. 若构造函数返回对象则用它,否则返回 obj
return result instanceof Object ? result : obj
}常见误区
| 问题 | 要点 |
|---|---|
Object.create(null) | 创建无原型的纯净对象 |
instanceof 原理 | 沿原型链查找是否存在目标 prototype |
| 继承方式对比 | 原型链继承 / 借用构造函数 / 组合继承 / class extends |
hasOwnProperty | 只检查自身属性,不走原型链 |
❌ 错误答法示范
错误示例 1:混淆 prototype 和 __proto__
js
// ❌ 错误理解
const obj = {}
obj.prototype // undefined,普通对象没有 prototype!❌ 问题:prototype 是函数专有属性,普通对象没有。对象的隐式原型是 __proto__,不是 prototype。
错误示例 2:直接赋值覆盖 prototype 导致 constructor 丢失
js
// ❌ 错误写法
Dog.prototype = Animal.prototype
// ✅ 正确写法
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog❌ 问题:直接赋值会让两个类共享同一个 prototype 对象,修改 Dog.prototype 会污染 Animal.prototype。
🎤 模拟面试作答
嗯,这道题我来说一下我的理解。
原型这个概念,本质上是 JavaScript 实现继承和属性共享的底层机制。每个对象在创建的时候,都会有一个内部指针指向另一个对象,这个对象就是它的原型。
具体来说有两个属性要区分清楚:一个是函数才有的 prototype,是显式原型;另一个是每个对象都有的 __proto__,是隐式原型,它指向构造函数的 prototype。
原型链就是这些指向关系串起来的链条。当我们访问一个属性的时候,JS 引擎会先在对象自身找,找不到就沿着 __proto__ 往上找,一直找到 Object.prototype,还没有就返回 undefined,这条链就叫原型链。
另外 new 操作符背后其实做了三件事:创建一个新对象、把它的 __proto__ 指向构造函数的 prototype、执行构造函数绑定 this。
在实际项目里,现在大家都用 class extends 语法,但底层还是原型链那套机制,理解原型链能帮助我们更好地排查继承相关的 bug。
……如果面试官感兴趣,我还可以聊聊 class 和 ES5 组合继承在实现上的差异,或者 instanceof 的底层判断逻辑。
❓ 面试官追问预测
追问 1:instanceof 是怎么判断的?能手写一个吗?
应对思路:沿左边对象的
__proto__链向上查找,看是否能找到右边构造函数的prototype,找到返回 true,到 null 还没找到返回 false。
js
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left)
while (proto) {
if (proto === right.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}追问 2:class 继承和 ES5 原型继承有什么区别?
应对思路:
class是语法糖,但有几个关键差异:class必须用new调用;class内部默认严格模式;super()必须在this之前调用;class的方法不可枚举。
追问 3:如何实现没有原型链的纯净对象?有什么用?
应对思路:
Object.create(null),创建出来的对象没有__proto__,不会继承toString、hasOwnProperty等,适合用作纯粹的 Map/字典,避免 key 冲突,性能也稍好。
追问 4:修改 prototype 上的属性,已经创建的实例会受影响吗?
应对思路:会。实例的
__proto__指向的是同一个 prototype 对象的引用,动态修改 prototype 上的属性,所有实例都能感知到。但如果是直接替换整个prototype对象,已创建的实例不受影响,因为__proto__还指向旧的 prototype。