《深入理解 JavaScript 原型:从“造车”说起》

49 阅读3分钟

深入理解 JavaScript 原型:从“造车”说起

在 JavaScript 的学习旅程中,“原型(Prototype)”往往是一个让人既熟悉又陌生的概念。不同于 Java 或 C++ 等基于类(Class-based)的面向对象语言,JavaScript 是基于原型(Prototype-based) 的。

今天,我们就结合代码实例,从“如何拿到一辆小米 SU7”这个需求出发,深入剖析 JavaScript 的原型机制。


1. 构造函数:工厂的诞生

在 ES6 之前,JavaScript 并没有 class 关键字(虽然现在有了,但本质依然是语法糖)。如果我们想要批量创建对象,比如生产多辆汽车,就需要使用构造函数

我们可以定义一个 Car 构造函数:


// 构造函数:首字母大写,用于生产对象
function Car(color) {
    // 1. 构造函数内部的 this 指向新创建的实例对象
    this.color = color;
    // this.name = 'su7';
    // this.height = 1.4;
    // this.long = 4800;
}

// 使用 new 关键字创建实例
const car1 = new Car('霞光紫');
const car2 = new Car('海湾蓝');

关键点:

  • new 的作用:当我们使用 new Car() 时,JS 引擎在后台做了一系列操作:

    1. 创建一个新对象;
    2. 将 this 绑定到这个新对象;
    3. 执行构造函数代码(设置属性);
    4. 返回这个对象。
  • 私有属性:在构造函数内部通过 this.xxx 定义的属性(如 color),是每个实例独有的。每辆车的颜色可能不同,互不影响。


2. Prototype:共享的智慧

如果每辆车都需要一个 drive(驾驶)方法,或者都有相同的 name(型号),我们直接写在构造函数里会有什么问题?


function Car(color) {
    this.color = color;
    this.drive = function() { 
        console.log('drive, 下赛道'); 
    } // 问题所在
}

如果在构造函数里定义方法,那么每创建一个实例,都会在内存中重新创建一遍这个方法。这显然是对内存的极大浪费。

这时,prototype(原型) 就登场了。

核心概念:每个函数都有一个 prototype 属性,它指向一个对象。这个对象上的属性和方法,会被该构造函数创建的所有实例共享

我们可以这样优化代码(参考 1.jsreadme.md):


// 将公共的方法和属性定义在 prototype 上
Car.prototype = {
    drive() {
        console.log('drive, 下赛道');
    },
    name: 'su7',
    height: 1.4,
    weight: 1.5,
    long: 4800,
};

const car1 = new Car('霞光紫');
car1.drive();       // 输出: drive, 下赛道
console.log(car1.name); // 输出: su7

理解:

  • Car.prototype 就像是一个公共的“配件仓库”。
  • 实例 car1 本身并没有 drive 方法,但它可以从“仓库”里借用。
  • 这就是 JS 原型式面向对象的精髓:通过委托(Delegation)而非复制来实现共享。

3. __proto__ 与原型链:寻宝之路

既然实例 car1 上没有 drive 方法,它是怎么找到 Car.prototype 上的 drive 的呢?

这就涉及到了对象的隐式原型 —— __proto__(规范中称为 [[Prototype]])。

规则:任何对象(除了 null)都有一个 __proto__ 属性,它指向创建该对象的构造函数的 prototype

当我们访问 car1.drive 时,查找过程如下:

  1. 自身查找:先看 car1 自己有没有 drive 属性? → 没有。
  2. 原型查找:顺着 car1.__proto__ 找到 Car.prototype,看这里有没有? → 有!于是调用它。

原型链的终点

如果 Car.prototype 上也没有呢?
Car.prototype 也是一个对象,它也有自己的 __proto__,指向 Object.prototype
如果 Object.prototype 上还没有,再往上找就是 null

car1 → Car.prototype → Object.prototype → null

这就是所谓的原型链
null 表示“没有原型对象”,是查找的终点。


4. 总结:JS 面向对象的独特之处

传统的类(Class)面向对象是基于血缘关系的模板复制,而 JavaScript 的原型面向对象是基于关联关系的。

  • 构造函数 (Constructor) :负责构建对象的“肉体”(私有属性)。
  • 原型 (Prototype) :负责赋予对象的“灵魂”(共享方法)。
  • 实例 (Instance) :通过 __proto__ 链接到原型,实现了属性和方法的继承。

理解了这三者的关系,你就真正拿到了驾驭 JavaScript 这辆“跑车”的钥匙 🚗💨。