引言
在 JavaScript 的世界里,没有传统意义上的"类"作为蓝图来构建对象(至少在 ES6 之前是这样)。取而代之的,是一套独特而优雅的机制------原型 (Prototype)与原型链(Prototype Chain)。这套机制不仅是 JavaScript 面向对象编程的基石,更是其灵活性与动态性的源泉。
本文将结合具体的代码实例,带你彻底揭开原型与原型链的神秘面纱,理解它们如何协同工作,让对象之间实现高效的属性共享与继承。
一、从"造车"说起:为什么需要原型?
想象一下,你是一家汽车工厂的工程师。如果每生产一辆车,你都要重新编写一遍"这辆车有四个轮子、一个引擎、能跑"的代码,那将是多么低效且浪费资源!
在 JavaScript 中,构造函数(Constructor)就像是一个模具。我们来看一个经典的例子,定义一个 Car 构造函数:
javascript
function Car(color) {
// 每辆车独特的属性,放在构造函数内部
this.color = color;
// 如果把所有属性都放这里:
// this.name = 'su7';
// this.height = 1.4;
// this.drive = function() { console.log('driving...'); };
}
如果我们把 name、height 或者 drive 方法直接写在构造函数里,意味着每 new 一辆车,内存中就会复制一份完全相同的数据和方法。对于成千上万辆车来说,这是巨大的浪费。
原型的出现,就是为了解决"共享"的问题。
我们可以将那些所有车辆共有的属性和方法,挂载到构造函数的 prototype 对象上:
javascript
// 共享的属性和方法,只存一份!
Car.prototype = {
name: 'su7',
height: 1.4,
weight: 1.5,
drive() {
console.log('drive, 下赛道');
}
};
const car1 = new Car('霞光紫');
const car2 = new Car('海湾蓝');
console.log(car1.name); // 输出: su7
console.log(car2.name); // 输出: su7
car1.drive(); // 输出: drive, 下赛道
在这个例子中,car1 和 car2 虽然颜色不同(实例自有属性),但它们共享了 name、height 以及 drive 方法。这些共享内容并没有存储在 car1 或 car2 自身内部,而是存在于 Car.prototype 中。
核心概念 1 :
prototype是函数(构造函数)的一个属性,它是一个对象。这个对象上的属性和方法,会被该构造函数创建的所有实例共享。
二、探秘内部机制:__proto__ 与寻找之旅
既然属性不在实例自己身上,那当我们执行 car1.drive() 时,JavaScript 引擎是如何找到 drive 方法的呢?这就引出了另一个关键角色:__proto__。
1. 隐式原型链接
在 JavaScript 中,几乎每个对象 (除了 null)都有一个内部的私有属性,通常表示为 __proto__(在标准中称为 [[Prototype]])。
- 当你使用
new Car()创建一个实例时,这个实例的__proto__会自动指向构造函数的prototype对象。 - 也就是说:
car1.__proto__ === Car.prototype成立。
我们可以用代码验证这一点:
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speci = '人类';
const p1 = new Person('张三', 18);
console.log(p1.__proto__); // 指向 Person.prototype
console.log(p1.__proto__ === Person.prototype); // true
2. 属性查找的"接力赛"
当你访问一个对象的属性(例如 p1.speci)时,JavaScript 引擎会启动一场查找接力赛:
- 第一棒 :先在对象自身(实例)上查找。如果有,直接返回;如果没有,进入下一棒。
- 第二棒 :沿着
__proto__指针,去它的原型对象 (Person.prototype)上查找。 - 第三棒 :如果原型对象上也没有,就继续沿着原型对象的
__proto__往上找。默认情况下,它指向Object.prototype。 - 终点 :
Object.prototype是所有普通对象的终极原型。它的__proto__是null。如果连这里都找不到,引擎就会返回undefined。
这条由 __proto__ 串联起来的链条,就是著名的原型链(Prototype Chain)。
核心概念 2 :原型链是对象通过
__proto__属性向上追溯,直到null的一条链路。它是 JavaScript 实现属性继承和共享的根本机制。
三、🗺️ 全景图解:一张图看懂复杂关系
文字描述虽然逻辑清晰,但原型系统中错综复杂的引用关系往往让人在脑海中难以构建完整的模型。为了让你彻底"看见"原型链,我们引入下面这张JavaScript 原型关系全景图。
这张图完美地串联了我们前面提到的所有概念:构造函数、实例、prototype、__proto__ 以及 constructor。

深度读图指南
请跟随图中的箭头,我们将这张图拆解为三个关键视角:
1. 横向视角:构造函数与原型的"双向奔赴"
请看图的左上部分:
- **
Person**(构造函数) 通过黑色的prototype箭头指向Person.prototype。- 这意味着:构造函数拥有一个"仓库",用来存放共享给实例的方法。
Person.prototype通过黑色的constructor箭头指回Person。- 这意味着:原型对象记得是谁创造了它。这是一个闭环,确保了
Person.prototype.constructor === Person成立。
- 这意味着:原型对象记得是谁创造了它。这是一个闭环,确保了
2. 纵向视角:实例与原型的"隐形脐带"
请看图中那条醒目的蓝色曲线:
- **
person**(实例) 通过__proto__箭头指向Person.prototype。- 这是原型链的起点 。当你访问
person的属性时,如果自身没有,JS 引擎就会顺着这条蓝色箭头,去Person.prototype里找。 - 口诀 :实例的
__proto__永远等于构造函数的prototype。
- 这是原型链的起点 。当你访问
3. 链条视角:通往顶端的"天梯"
请看图右侧垂直向下的蓝色直线:
Person.prototype也有自己的__proto__,它指向了Object.prototype。- 这说明
Person.prototype本身也是一个对象,它也受Object管辖。
- 这说明
Object.prototype的__proto__指向了null。- 这是原型链的终点 。
null意味着"无路可走",查找至此结束。
- 这是原型链的终点 。
结合代码的读图体验 : 当你执行 person.toString() 时:
- 引擎看
person自身?没有toString。 - 顺着蓝色曲线去
Person.prototype找?没有。 - 顺着蓝色直线去
Object.prototype找?找到了 ! (toString是 Object 内置方法)。 - 任务完成。
这张图告诉我们:原型链本质上就是一串由 __proto__ 连接起来的对象链表,而 prototype 和 constructor 则是维护这个系统结构完整性的关键纽带。
四、实战演练:彻底搞懂继承
理解了原型和原型链,继承就变得顺理成章。假设我们要创建一个 SportsCar(跑车),它应该拥有普通 Car 的所有特性,还要有自己的特技。
javascript
// 父构造函数
function Car(color) {
this.color = color;
}
Car.prototype.drive = function() {
console.log('普通驾驶');
};
// 子构造函数
function SportsCar(color, speed) {
// 借用父构造函数,继承实例属性
Car.call(this, color);
this.speed = speed;
}
// 关键步骤:建立原型链继承
// 让 SportsCar 的原型指向一个由 Car 创建的实例
SportsCar.prototype = new Car();
// 修正 constructor 指向(最佳实践,对应图中 constructor 箭头的修复)
SportsCar.prototype.constructor = SportsCar;
// 添加子类特有的方法
SportsCar.prototype.race = function() {
console.log('赛道狂飙,速度:' + this.speed);
};
const myCar = new SportsCar('红色', 300);
myCar.drive(); // 来自父级原型链:普通驾驶
myCar.race(); // 来自子类原型:赛道狂飙,速度:300
console.log(myCar.color); // 来自实例自身:红色
在这个过程中发生了什么?(对照全景图想象)
SportsCar.prototype = new Car():这行代码创建了一个临时的Car实例。- 这个临时实例的
__proto__指向Car.prototype。 - 我们将这个临时实例赋值给
SportsCar.prototype。 - 此时,
SportsCar.prototype的__proto__就自然地指向了Car.prototype。 - 当
myCar访问drive方法时,查找路径变成了:myCar->SportsCar.prototype->Car.prototype(找到!) ->Object.prototype->null。
这就是原型链继承的精髓:通过修改原型链的指向,让子类的实例能够访问到父类原型上的方法。
五、总结与启示
回顾全文,结合那张清晰的全景图,我们可以提炼出以下核心要点:
- 构造函数与 Prototype :每个函数都有一个
prototype属性,用于存放供实例共享的属性和方法。 - 实例与
__proto__:每个实例都有一个__proto__属性,它在实例化时自动指向构造函数的prototype(图中蓝色曲线的含义)。 - 原型链查找机制 :访问属性时,JS 引擎会沿
__proto__链条逐级向上查找,直到Object.prototype或null(图中蓝色直线的含义)。 - 闭环的重要性 :
constructor属性确保了原型对象能找回构造函数,维持系统的完整性(图中黑色反向箭头的含义)。 - 继承本质:JS 的继承不是拷贝,而是原型链的委托查找。
理解原型和原型链,是掌握 JavaScript 高级特性的必经之路。无论是后续的 class 语法糖,还是框架源码中的巧妙运用,其底层逻辑都离不开这套精妙的原型机制。
下次当你写下 new 关键字时,不妨在脑海中浮现出那张全景图:描绘出那条连接着实例、原型、再通向 Object 的隐形链条。正是这条链条,赋予了 JavaScript 无限的可能。