在 JavaScript 的世界中,原型(Prototype)和原型链(Prototype Chain) 是每个前端开发者都必须掌握的核心概念。它们不仅是 JavaScript 实现面向对象编程的基础,更是理解函数、对象、继承机制的关键所在。
今天,我们就来系统地梳理一下原型与原型链的底层原理,帮助你彻底搞懂这个"看似复杂、实则清晰"的机制。
一、什么是原型?构造函数与 prototype 的关系
在 JavaScript 中,我们通常使用构造函数来创建对象实例。例如:
javascript
function Person(name) {
this.name = name;
}
const person1 = new Person("Alice");
const person2 = new Person("Bob");
每个构造函数都有一个内置属性:prototype
。这个属性指向一个对象,该对象包含了所有实例可以共享的属性和方法。
javascript
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
person1.sayHello(); // 输出: Hello, I'm Alice
person2.sayHello(); // 输出: Hello, I'm Bob
✅ 关键点:
Person.prototype
是一个对象。- 所有通过
new Person()
创建的实例,都可以访问prototype
上定义的方法和属性。 - 这种方式避免了在每个实例中重复定义相同的方法,节省内存,提升性能。
二、对象的原型:__proto__
与 Object.getPrototypeOf()
当我们使用构造函数创建一个对象时,这个对象内部会自动包含一个指向其构造函数 prototype 的指针 ,这个指针就是对象的原型。
在 ES5 之前,虽然这个指针是内部机制,但现代浏览器普遍实现了 __proto__
属性来访问它:
javascript
console.log(person1.__proto__ === Person.prototype); // true
⚠️ 但请注意:
__proto__
不是标准推荐使用的属性,尽管大多数环境支持它。- ES5 引入了标准方法
Object.getPrototypeOf()
来安全获取对象的原型:
javascript
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true ✅ 推荐使用
📌 小贴士:始终优先使用
Object.getPrototypeOf(obj)
而不是obj.__proto__
,以保证代码的规范性和可移植性。
三、原型链:属性查找的"向上追溯"机制
当你访问一个对象的属性时,JavaScript 引擎会按以下顺序查找:
- 先在对象自身查找;
- 如果找不到,就去它的原型对象中查找;
- 如果原型中也没有,就继续查找原型的原型;
- 一直向上追溯,直到找到属性或原型链结束。
这个逐层向上查找的过程,就是 原型链(Prototype Chain)。
🔗 原型链的终点:Object.prototype
所有对象的原型链最终都会指向 Object.prototype
,而 Object.prototype.__proto__
为 null
,表示链的终点。
javascript
console.log(Object.getPrototypeOf(Object.prototype)); // null
这也是为什么我们创建的任意对象都能调用 toString()
、hasOwnProperty()
等方法的原因:
javascript
person1.toString(); // 继承自 Object.prototype
🧩 原型链示意图
javascript
person1
↓ __proto__
Person.prototype
↓ __proto__
Object.prototype
↓ __proto__
null
四、原型的引用特性:共享与动态继承
JavaScript 中的对象是通过引用传递的。这意味着:
- 所有实例共享同一个原型对象;
- 原型对象不会被每个实例复制一份;
- 当你修改原型时,所有实例都会"动态"继承这些变化。
✅ 动态修改原型的示例
javascript
// 先创建实例
const person = new Person("Charlie");
// 后添加方法到原型
Person.prototype.greet = function() {
console.log("Hi there!");
};
// 实例仍然可以访问新方法
person.greet(); // 输出: Hi there!
💡 这种"动态性"是 JavaScript 灵活性的体现,但也需谨慎使用,避免在生产环境中随意修改原生对象的原型。
五、常见误区与最佳实践
❌ 误区1:混淆 prototype
和 __proto__
prototype
是构造函数的属性;__proto__
是实例对象的原型指针;- 两者指向的是同一个对象(对于实例而言)。
❌ 误区2:认为原型是"副本"
原型是引用共享,不是复制。修改原型会影响所有实例。
✅ 最佳实践建议
- 使用
Object.create(null)
创建无原型链的对象(用于纯字典场景); - 优先使用
Object.getPrototypeOf()
和Object.setPrototypeOf()
; - 避免修改原生对象(如
Array.prototype
)的原型; - 理解
class
语法糖背后的原型机制(ES6 的class
本质仍是基于原型);
六、总结:原型与原型链的核心要点
概念 | 说明 |
---|---|
prototype |
构造函数的属性,存储共享方法和属性 |
__proto__ |
实例的内部原型指针(不推荐直接使用) |
Object.getPrototypeOf() |
标准方法,获取对象的原型 ✅ |
原型链 | 属性查找时逐层向上追溯的机制 |
终点 | Object.prototype ,其 __proto__ 为 null |
特性 | 引用共享、动态继承、内存高效 |
结语
原型与原型链是 JavaScript 的"灵魂"之一。理解它,不仅能帮助你写出更高效的代码,还能在面试、调试、框架源码阅读中游刃有余。
🔥 记住一句话:
"每个对象都有一个原型,查找属性时会沿着原型链一路向上,直到
null
。"