深入理解 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 引擎在后台做了一系列操作:- 创建一个新对象;
- 将
this绑定到这个新对象; - 执行构造函数代码(设置属性);
- 返回这个对象。
-
私有属性:在构造函数内部通过
this.xxx定义的属性(如color),是每个实例独有的。每辆车的颜色可能不同,互不影响。
2. Prototype:共享的智慧
如果每辆车都需要一个 drive(驾驶)方法,或者都有相同的 name(型号),我们直接写在构造函数里会有什么问题?
function Car(color) {
this.color = color;
this.drive = function() {
console.log('drive, 下赛道');
} // 问题所在
}
如果在构造函数里定义方法,那么每创建一个实例,都会在内存中重新创建一遍这个方法。这显然是对内存的极大浪费。
这时,prototype(原型) 就登场了。
核心概念:每个函数都有一个
prototype属性,它指向一个对象。这个对象上的属性和方法,会被该构造函数创建的所有实例共享。
我们可以这样优化代码(参考 1.js 和 readme.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 时,查找过程如下:
- 自身查找:先看
car1自己有没有drive属性? → 没有。 - 原型查找:顺着
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 这辆“跑车”的钥匙 🚗💨。