"孔子是韩国人?"------JS 的原型链不是血缘,而是委托。
在前端开发中,JavaScript 的原型(prototype)机制常常让人困惑又着迷。它不像 Java、C++ 那样基于"类"的继承,而是一种基于原型的委托式面向对象模型。今天,我们就用一个轻松的例子------"如何拿到一辆小米 SU7"------来揭开原型机制的神秘面纱。
一、构造函数:造车的第一步
假设你想拥有一辆小米 SU7,首先得有个"造车蓝图"。在 JS 中,这个蓝图就是构造函数:
ini
Javascript
编辑
1function Car(color) {
2 this.color = color;
3}
当你执行 new Car('霞光紫') 时,JS 引擎会:
- 创建一个新对象;
- 将
this指向这个新对象; - 执行构造函数中的代码(如设置
color); - 返回这个新对象。
此时,你得到了一辆"独属于你"的 SU7,颜色是霞光紫。
但问题来了:每辆车都要有 drive 方法、长宽高参数吗? 如果每个实例都存一份,岂不浪费内存?
二、prototype:共享的"公共配置"
于是 JS 引入了 prototype(原型) ------所有实例共享的方法和属性存放地。
javascript
Javascript
编辑
1Car.prototype = {
2 name: 'su7',
3 height: 1.4,
4 weight: 1.5,
5 long: 4800,
6 drive() {
7 console.log('drive, 下赛道');
8 }
9};
现在,无论你创建多少辆 SU7,它们都共享同一份原型对象 。调用 car1.drive() 时,JS 会在 car1 自身找不到 drive,就沿着 __proto__ 链向上查找,最终在 Car.prototype 上找到。
✅ 关键点:
- 实例属性(如
color)存在实例自身;- 共享方法/属性(如
drive,name)存在原型上;- 查找顺序:实例 → 原型 → 原型的原型 → ... → Object.prototype → null
三、原型链的本质:委托,而非继承
很多人误以为 JS 是"类继承",其实它是对象委托。
- 传统 OOP(如 Java):子类"继承"父类,形成血缘关系。
- JS 原型式 OOP:对象"委托"给另一个对象,说:"你有这方法吗?借我用用。"
比如:
ini
Javascript
编辑
1var obj = {};
2console.log(obj.toString()); // 能调用!
obj 自己没有 toString,但它通过 __proto__ 委托给了 Object.prototype,于是成功调用。
这种设计更灵活:你可以随时修改原型,所有实例立即"感知"到变化(除非实例自己覆盖了该属性)。
四、constructor 与原型的"双向绑定"
每个函数都有 prototype 属性,而 prototype 对象默认有一个 constructor 属性,指回构造函数:
ini
Javascript
编辑
1Car.prototype.constructor === Car; // true
但注意!如果你直接重写整个 prototype (如 Car.prototype = { ... }),这个链接会被切断:
ini
Javascript
编辑
1Car.prototype = { drive() {} };
2console.log(Car.prototype.constructor === Car); // false! 现在是 Object
修复方式:
javascript
Javascript
编辑
1Car.prototype = {
2 constructor: Car, // 手动修复
3 drive() { /*...*/ }
4};
或者使用 Object.defineProperty 设置不可枚举的 constructor。
五、多层原型链:Animal → Person → su
JS 支持原型链的嵌套,模拟"多级继承":
javascript
Javascript
编辑
1function Animal() {}
2Animal.prototype.species = '动物';
3
4function Person() {}
5Person.prototype = new Animal(); // Person 委托给 Animal 实例
6
7var su = new Person();
8console.log(su.species); // '动物'
这里 su.__proto__ 是 Person.prototype(即一个 Animal 实例),而 Animal 实例的 __proto__ 又指向 Animal.prototype,最终通向 Object.prototype。
🌟 这就是为什么
su.toString()依然有效------原型链一路向上,直到Object.prototype。
六、思考:为什么 JS 选择原型?
- 动态性:运行时可修改原型,行为实时生效;
- 轻量:无需预定义"类",对象可自由扩展;
- 哲学差异:JS 认为"对象之间可以互相学习",而非"必须出自某个模板"。
正如道家思想:"上善若水,水善利万物而不争。"
JS 的原型链,也是一种"无为而治"的委托之道。
结语
下次当你写下 new Car() 时,不妨想想:
你不仅创建了一个对象,还开启了一段沿着原型链向上委托的旅程 。
而那辆小米 SU7,或许不在展厅,而在 Car.prototype 的深处,静静等待被"驱动"。