JavaScript 中的原型(Prototype)和原型链(Prototype Chain)
在 JavaScript 中,原型(Prototype)和原型链(Prototype Chain)是理解 JavaScript 面向对象编程(OOP)和继承机制的绝对核心。
这两个概念听起来抽象,但如果用大白话来解释,其实就是一个“共享资源库”和“顺藤摸瓜找资源的过程”。
下面为你详细拆解这两个概念:
一、 什么是原型(Prototype)?
在 JavaScript 中,几乎所有的对象都有一个与之关联的另一个对象,这个关联的对象就叫做“原型”。对象会从原型继承属性和方法。
我们可以把原型理解为一个“模板”或“共享仓库”。
为了搞懂原型,必须先区分两个极易混淆的属性:prototype 和 __proto__。
1. prototype(显式原型)
- 谁拥有它? 只有函数(Function)才拥有
prototype属性(箭头函数除外)。 - 它是干嘛的? 当你把一个函数当作构造函数(Constructor)来创建实例时,这个
prototype属性就是即将创建出来的实例的原型。 - 比喻:
prototype就像是汽车工厂里的设计图纸。
2. __proto__(隐式原型 / [[Prototype]])
- 谁拥有它? 所有对象(包括函数、数组、普通对象等)都拥有
__proto__属性(现在官方推荐使用Object.getPrototypeOf()来获取)。 - 它是干嘛的? 它指向创建这个对象的构造函数的
prototype(即它的原型)。 - 比喻:
__proto__就像是出厂的汽车上自带的一本说明书,上面写着:“我是根据哪张图纸(prototype)造出来的”。
代码验证:
// 1. 创建一个构造函数
function Person(name) {
this.name = name;
}
// 2. 在函数的 prototype 上添加方法(放入共享仓库)
Person.prototype.sayHello = function() {
console.log("Hello, my name is " + this.name);
};
// 3. 实例化对象
let p1 = new Person("Alice");
// 4. 验证他们的关系!
console.log(p1.__proto__ === Person.prototype); // true (p1的说明书指向了Person的图纸)
二、 什么是原型链(Prototype Chain)?
当试图访问一个对象的属性或方法时,JavaScript 引擎会执行一个查找过程。
查找规则(原型链机制):
- 首先在对象自身查找该属性。
- 如果自身没有,就顺着
__proto__去它的原型对象上找。 - 如果原型对象上也没有,就继续顺着原型对象的
__proto__去上一级原型对象上找。 - 这样一层一层向上找,就像一条链子,这就是原型链。
- 直到找到顶端
Object.prototype,如果它的__proto__也就是null也没有,就返回undefined。
原型链的完整示例:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {}
let p1 = new Person("Bob");
// 1. 访问 p1.name
// p1 自身有 name 属性,直接返回 "Bob"
// 2. 访问 p1.sayHello()
// p1 自身没有 sayHello,顺着 p1.__proto__ 找到 Person.prototype,找到了,执行!
// 3. 访问 p1.toString()
// p1 自身没有 -> 去 Person.prototype 找,也没有
// -> 顺着 Person.prototype.__proto__ 找,找到了 Object.prototype
// Object.prototype 上有 toString() 方法,执行!
// 4. 访问 p1.fly()
// p1 -> Person.prototype -> Object.prototype -> null
// 全部找完都没有,抛出错误或返回 undefined。
原型链的终点:
所有普通对象的原型链最终都会指向 Object.prototype,而 Object.prototype.__proto__ === null。null 就是原型链的尽头。
三、 牢记“核心铁三角”
如果你能看懂下面这三层关系,你就彻底掌握了原型:
实例.__proto__ === 构造函数.prototype
(实例的隐式原型指向构造函数的显式原型)构造函数.prototype.constructor === 构造函数
(原型对象里面有一个constructor属性,指回构造函数本身)Object.getPrototypeOf(实例) === 构造函数.prototype
(这是 ES6 推荐的获取原型的方法,等同于__proto__)
四、 常见数据类型的原型链图解
了解原生对象是如何通过原型链连接的,对调试非常有帮助:
普通对象:
let obj = {}obj->Object.prototype->null数组:
let arr = []arr->Array.prototype->Object.prototype->null
(这就是为什么数组能用push、map等方法,因为它们在Array.prototype上)函数:
function fn() {}fn->Function.prototype->Object.prototype->null
(这就是为什么函数能用call、apply、bind)
五、 相关重要方法
instanceof运算符:
用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。javascriptp1 instanceof Person; // true p1 instanceof Object; // true (因为 Object.prototype 在 p1 的原型链上)hasOwnProperty():
判断一个属性是存在于对象自身上,还是存在于原型链上。javascriptp1.hasOwnProperty('name'); // true (自身属性) p1.hasOwnProperty('sayHello'); // false (来自原型链)Object.create():
创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。javascriptlet parent = { house: "别墅" }; let child = Object.create(parent); // child.__proto__ === parent console.log(child.house); // "别墅"
总结
- 原型是为了实现属性和方法的共享,节省内存。
- 原型链是 JavaScript 实现继承的底层机制。当你调用对象的方法时,JS 引擎就是靠着原型链“顺藤摸瓜”找到该方法的。
- ES6 的
class语法(class和extends)本质上只是原型继承的语法糖,底层依然是基于原型链在运作。