深入理解 JavaScript 原型机制:从“如何拿到小米 SU7”说起

126 阅读3分钟

“孔子是韩国人?”——JS 的原型链不是血缘,而是委托。

在前端开发中,JavaScript 的原型(prototype)机制常常让人困惑又着迷。它不像 Java、C++ 那样基于“类”的继承,而是一种基于原型的委托式面向对象模型。今天,我们就用一个轻松的例子——“如何拿到一辆小米 SU7”——来揭开原型机制的神秘面纱。


一、构造函数:造车的第一步

假设你想拥有一辆小米 SU7,首先得有个“造车蓝图”。在 JS 中,这个蓝图就是构造函数

Javascript
编辑
1function Car(color) {
2    this.color = color;
3}

当你执行 new Car('霞光紫') 时,JS 引擎会:

  1. 创建一个新对象;
  2. 将 this 指向这个新对象;
  3. 执行构造函数中的代码(如设置 color);
  4. 返回这个新对象。

此时,你得到了一辆“独属于你”的 SU7,颜色是霞光紫。

但问题来了:每辆车都要有 drive 方法、长宽高参数吗? 如果每个实例都存一份,岂不浪费内存?


二、prototype:共享的“公共配置”

于是 JS 引入了 prototype(原型) ——所有实例共享的方法和属性存放地。

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)存在实例自身;
  • 共享方法/属性(如 drivename)存在原型上;
  • 查找顺序:实例 → 原型 → 原型的原型 → … → Object.prototype → null

三、原型链的本质:委托,而非继承

很多人误以为 JS 是“类继承”,其实它是对象委托

  • 传统 OOP(如 Java):子类“继承”父类,形成血缘关系。
  • JS 原型式 OOP:对象“委托”给另一个对象,说:“你有这方法吗?借我用用。”

比如:

Javascript
编辑
1var obj = {};
2console.log(obj.toString()); // 能调用!

obj 自己没有 toString,但它通过 __proto__ 委托给了 Object.prototype,于是成功调用。

这种设计更灵活:你可以随时修改原型,所有实例立即“感知”到变化(除非实例自己覆盖了该属性)。


四、constructor 与原型的“双向绑定”

每个函数都有 prototype 属性,而 prototype 对象默认有一个 constructor 属性,指回构造函数:

Javascript
编辑
1Car.prototype.constructor === Car; // true

但注意!如果你直接重写整个 prototype(如 Car.prototype = { ... }),这个链接会被切断:

Javascript
编辑
1Car.prototype = { drive() {} };
2console.log(Car.prototype.constructor === Car); // false! 现在是 Object

修复方式:

Javascript
编辑
1Car.prototype = {
2    constructor: Car, // 手动修复
3    drive() { /*...*/ }
4};

或者使用 Object.defineProperty 设置不可枚举的 constructor


五、多层原型链:Animal → Person → su

JS 支持原型链的嵌套,模拟“多级继承”:

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 的深处,静静等待被“驱动”。