在 JavaScript 的世界里,“原型(prototype)” 是你绕不过去的核心机制。
无论你使用 ES5 的构造函数,还是 ES6 的 class,底层的面向对象系统仍然是 原型式(Prototype-based) 的。
很多同学说:
“prototype 太抽象了,好难懂!”
“proto 又是啥?和 prototype 有啥关系?”
“原型链到底是怎么查找的?”
别急,这篇文章会从 底层机制 → 实例构造 → 原型对象 → 原型链查找 → 继承机制 → constructor 修复 等角度,配合大量图解,帮你彻底搞懂 JS 原型体系。
🚗 1. 构造函数与实例:一切的起点
在 ES5 中,没有 class,所谓“类”是用构造函数模拟出来的:
function Car(color) {
this.color = color;
}
当你这样调用时:
const car1 = new Car("霞光紫");
发生了什么?
✔ new 执行四步:
- 创建一个空对象
{} - 让这个对象的
__proto__指向构造函数的prototype - 以新对象为 this,执行构造函数
- 返回新对象
🧬 2. prototype:构造函数的“实例原型对象”
构造函数 Car 有一个重要属性:
Car.prototype
它是“原型对象”,存放所有实例 共享的属性和方法:
Car.prototype = {
drive() {
console.log('drive, 下赛道');
},
name: 'su7',
height : 1.4,
weight : 1.5
};
如果实例 car1 没有某个属性,JS 就会自动去 Car.prototype 上找。
🧩 3. proto:实例对象的隐藏原型指针
__proto__ 是每个对象都有的一个 隐藏属性,指向它的原型:
car1.__proto__ === Car.prototype // true
它不是标准属性(但浏览器普遍支持),推荐用:
Object.getPrototypeOf(car1)
🔭 4. 构造函数 → 实例原型 → 实例
- 构造函数 Person 有属性
prototype - 实例 person 通过
__proto__指向Person.prototype - 于是实例就可以“访问”原型的属性与方法
🧠 5. prototype 与 proto 的关系(必须死记)
| 属性 | 归属 | 指向 |
|---|---|---|
| prototype | 构造函数 | 实例原型对象 |
| proto | 实例对象 | 指向构造函数的 prototype |
一句话总结:
prototype 是构造函数的属性
proto 是实例对象的属性
最终它们两个指向同一份原型对象
🌲 6. 深挖:原型链的真正含义
访问对象属性时,JS 采用的是 原型链查找机制:
car1.drive()
查找顺序是:
- 先看 car1 自己有没有 drive
- 没有 → 去 car1.proto(Car.prototype)上找
- 再没有 → 去 Car.prototype.proto(Object.prototype)
- 再没有 → 到达 null,查找结束
这就是 原型链(prototype chain) 。
🧱 7. 构造函数 ↔ prototype 的双向关系
- 构造函数有 prototype → 指向原型对象
- 原型对象有 constructor → 指回构造函数
这是一种天然的“双向绑定”。
🚀 8. 原型链最终都会指向 Object.prototype
来看这张图:
无论你是 Person、Car 还是其他构造函数:
Person.prototype.__proto__ === Object.prototype
而:
Object.prototype.__proto__ === null
到 null,链条结束。
🧬 9. ES5 原型继承:借助原型链实现继承
例如:
function Animal() {}
Animal.prototype.species = '动物';
function Person() {}
Person.prototype = new Animal();
const su = new Person();
此时 su 的原型链:
su → Person.prototype → Animal.prototype → Object.prototype → null
你看到 su 可以访问到:
su.species
因为通过原型链查找找到了 Animal.prototype.species。
并且你也看到了:
console.log(su.toString)
说明 su 也继承了 Object.prototype 的方法。
🔁 10. 重写 prototype 后必须手动修复 constructor
默认情况下,构造函数有一个 prototype,其 constructor 指向自身:
Person.prototype.constructor === Person
但当你这样写时:
Person.prototype = {
speci: '人类'
};
新的对象字面量没有 constructor 属性,于是它会从 Object.prototype 继承 constructor:
Person.prototype.constructor === Object // ❌ 错误
这会导致依赖 constructor 的代码出问题。
所以必须手动修复:
Person.prototype = {
speci: '人类',
constructor: Person
};
这是非常重要的“行业最佳实践”。
🎯 11. 原型式面向对象 vs 经典面向对象
传统 OOP(Java、C++)基于 class:
- 类是模板
- 实例是复制品
- 数据与结构是静态继承
而 JS:
- 没有“类继承链”,只有“原型链”
- 每个对象都可以作为另一个对象的原型
- 原型可以动态修改,所有实例立即生效
这就是 JS 的哲学:
万物皆对象,对象继承于对象,而不是类。
🏛 12. ES6 class 其实是语法糖,本质还是原型
例如:
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
}
等价于 ES5 写法:
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
本质不变:
- 依然使用 prototype 保存方法
- 依然通过 proto 链接原型链
- 依然基于 Object.prototype
🧩 13. 全流程理解图(核心总览)
下面这张图是最完整、最重要的一张(建议收藏):
它展示了:
- 构造函数 prototype
- 实例 proto
- Object.prototype
- null 作为终止点
- constructor 的指向关系
看到这张图,你就理解了整个 JS 原型体系。
🌟 14. 总结(送你一套最精华记忆口诀)
✔ 第一套:“prototype 属于函数,proto 属于对象”
prototype 是构造函数属性
__proto__ 是实例属性
最终指向同一个原型对象
✔ 第二套:“方法在原型上,属性在实例上”
实例属性:每个实例独立
原型属性:所有实例共享
✔ 第三套:“查不到就沿 proto 往上找”
对象 → 构造函数 prototype → Object.prototype → null
✔ 第四套:“重写原型别忘 constructor”
Person.prototype = { ... } → 必须补 constructor
✔ 第五套:“class 只是语法糖,本质还是原型”
class 的底层依然使用 prototype / __proto__
📌 结语
如果你能看懂所有图、理解每行代码,并且能够默画一张类似的原型链关系图,那么恭喜你:
你真的已经彻底掌握 JavaScript 原型与原型链系统了!