先让我们结合生活案例理解原型原型链相关概念,想象一下一个大家庭,有很多成员。
1. 原型 (Prototype) - 家族的共同特征或技能模板
- 概念对应: 家族中代代相传的共同特征、习惯、或者家族里独有的某个手艺或知识。
- 例子: 假设你们家族的成员普遍都有高个子、善于烹饪一道祖传菜肴,或者懂得某种特定的修理技巧。这些“高个子”、“祖传菜肴烹饪方法”、“修理技巧” 就像是家族的原型属性和方法。
2. 对象 (Object) - 家族中的个体
- 概念对应: 你、你的爸爸、你的爷爷、你的曾祖父等家族里的每一个具体的人。
- 例子: 你是一个具体的“人”对象,你的爸爸是另一个具体的“人”对象,等等。
3. [[Prototype]]
(内部链接) 或 __proto__
- 子女指向父母的链接
- 概念对应: 子女天然地与父母相关联。在原型中,这个链接是单向的,从子女指向父母。
- 例子: 你出生后,你自然就知道你的爸爸是谁。你有一个内部的“链接”或者“指针”,指向你的爸爸。你的爸爸也有一个链接指向他的爸爸(你的爷爷),以此类推。
4. 原型链 (Prototype Chain) - 家族的血脉/祖谱线
- 概念对应: 这个由“子女指向父母”的链接形成的链条。从你出发,沿着你指向你爸爸的链接,再沿着你爸爸指向你爷爷的链接,一直向上追溯。
- 例子: 你的原型链就是: 你 -> 你的爸爸 -> 你的爷爷 -> 你的曾祖父 -> ... 这是一个向上追溯的祖谱线。
5. 属性/方法查找 - 寻找特征或解决问题
- 概念对应: 当你需要某种技能或想知道某个特征时,你会沿着原型链去寻找。
- 过程:
- 第一步: 你首先看看自己是否具备这个特征或技能 (查找对象自身的属性)。比如,你会不会做那道祖传菜肴?如果你自己就会,太好了,查找结束,你直接使用自己的技能。
- 第二步: 如果你自己不会 (对象本身没有该属性/方法),你就会问你的爸爸 (沿着
[[Prototype]]
链接到你的原型)。“爸爸,你会做那道祖传菜肴吗?” - 第三步: 如果你爸爸会,他就会告诉你方法或者帮你做 (在原型上找到)。查找结束。
- 第四步: 如果你爸爸也不会 (你的原型上也没有),你爸爸可能会告诉你,“我爷爷(你的曾祖父,也就是你爸爸的原型)会!” (沿着原型链继续向上)。于是你去问你的爷爷。
- 第五步: 这个过程会一直持续下去,直到找到会做这道菜的人 (在原型链上找到属性/方法)。
- 如果直到最老的祖先都找不到... 那说明这道菜肴不是你们家族的技能,或者信息失传了 (到达原型链终点仍未找到)。
6. Object.prototype
- 人类的基本共性 / 最初的祖先
- 概念对应: 这是一个所有人类都共享的最基本、最普遍的特征或能力集合。比如,所有人类都能呼吸、吃饭、睡觉(这是最基本的方法)。
- 例子: 在我们的家族链条向上追溯到足够远时,会到达一个所有人都共有的“人类祖先”。这个“人类祖先”拥有的基本能力,是所有后代都继承的。在 JavaScript 中,
Object.prototype
就好比这个提供了所有对象最基本方法的“祖先”。
7. 原型链的终点 (null
) - 追溯到第一个人类之前
- 概念对应: 当你追溯祖谱线,到了第一个“人”那里,再往上就没有“人”作为祖先了。链条就断了。
- 例子:
Object.prototype
的原型是null
。这表示Object.prototype
是这个继承体系中“人类”的开端,再往上就没有通过原型链关联的更基础的对象了。
8. 属性覆盖 (Overriding) - 青出于蓝或有自己的特长
- 概念对应: 你可能继承了爸爸妈妈的一些特征或技能,但你可能做得更好,或者发展出了自己独有的版本。
- 例子: 你的家族原型是“高个子”,你爸爸也是高个子。但你可能更高!当你询问你的身高时,是直接使用你自己的身高数据,而不是你爸爸的身高(你的个体属性覆盖了原型的属性)。或者,你学会了比祖传方法更先进的烹饪技巧,当你做菜时,用的是你自己的新方法,而不是去问爸爸或爷爷的旧方法。
原型原型链概念以及代码示例
1. 什么是原型 (Prototype)?
原型是 JavaScript 中一个普通的对象,它是另一个对象继承属性和方法的来源。
- 几乎所有 JavaScript 对象都有一个内部的
[[Prototype]]
属性,指向它的原型。 - 你可以通过
Object.getPrototypeOf(obj)
方法来获取对象的原型。 - 虽然不推荐在生产代码中使用,但
obj.__proto__
这个属性在很多环境中也暴露了[[Prototype]]
,并且在学习和调试时很直观。
代码示例 : 查看对象的原型
// 通过对象字面量创建的对象
const objLiteral = {};
console.log("--- objLiteral 原型 ---");
console.log(Object.getPrototypeOf(objLiteral)); // 输出: [Object: null prototype] {} (即 Object.prototype 对象)
console.log(objLiteral.__proto__); // 输出: [Object: null prototype] {} (与上面相同)
console.log(Object.getPrototypeOf(objLiteral) === Object.prototype); // 输出: true
console.log("\n--- 数组的原型 ---");
const arr = [];
console.log(Object.getPrototypeOf(arr)); // 输出: [] (即 Array.prototype 对象)
console.log(Object.getPrototypeOf(arr) === Array.prototype); // 输出: true
console.log("\n--- 函数的原型 ---");
function myFunc() {}
console.log(Object.getPrototypeOf(myFunc)); // 输出: [Function] (即 Function.prototype 对象)
console.log(Object.getPrototypeOf(myFunc) === Function.prototype); // 输出: true
解释:
- 通过字面量
{}
创建的对象,其原型默认指向内置的Object.prototype
对象。 - 数组的原型指向
Array.prototype
。 - 函数的原型指向
Function.prototype
(注意:这是函数对象本身的原型,不是它作为构造函数时的prototype
属性)。
2. 函数的 prototype
属性 (用于构造函数)
函数除了有自己的原型(指向 Function.prototype
),还有一个特殊的属性叫做 prototype
。这个 prototype
属性是给通过该函数作为构造函数 (new
关键字) 创建出来的对象准备的,作为这些新创建对象的原型。
代码示例 : 函数的 prototype
属性与 new
关键字
// 定义一个构造函数
function Dog(name) {
this.name = name; // name 属性会直接添加到每个实例上
}
// 在构造函数的 prototype 属性上添加方法
// 这些方法会被所有 Dog 实例共享
Dog.prototype.bark = function() {
console.log(this.name + " says Woof!");
};
Dog.prototype.species = "Canine"; // 共享属性
// 使用 new 关键字创建 Dog 的实例
const dog1 = new Dog("Buddy");
const dog2 = new Dog("Lucy");
console.log("--- Dog 实例的原型 ---");
// dog1 的原型(__proto__) 指向 Dog.prototype
console.log(Object.getPrototypeOf(dog1) === Dog.prototype); // 输出: true
// dog2 的原型(__proto__) 也指向 Dog.prototype
console.log(Object.getPrototypeOf(dog2) === Dog.prototype); // 输出: true
console.log("\n--- Dog 实例访问属性和方法 ---");
dog1.bark(); // 输出: Buddy says Woof! (从 Dog.prototype 继承)
console.log(dog2.species); // 输出: Canine (从 Dog.prototype 继承)
console.log(dog1.name); // 输出: Buddy (实例自身的属性)
console.log("\n--- 构造函数 Dog 本身的原型 ---");
// Dog 函数本身的原型指向 Function.prototype
console.log(Object.getPrototypeOf(Dog) === Function.prototype); // 输出: true
解释:
Dog
函数有一个prototype
属性,指向一个对象。- 我们在
Dog.prototype
上添加了bark
方法和species
属性。 - 使用
new Dog("...")
创建的dog1
和dog2
实例,它们的[[Prototype]]
(即__proto__
) 都被设置为了Dog.prototype
对象。 - 因此,
dog1
和dog2
可以通过原型链访问并使用Dog.prototype
上的bark
和species
。 name
属性是直接添加到this
(也就是实例本身) 上的,不是通过原型继承的。
3. 什么是原型链 (Prototype Chain)?
原型链是由对象的 [[Prototype]]
属性连接起来形成的链条。当查找一个对象的属性或方法时,如果在对象本身没有找到,就会沿着这个链向上查找,直到找到该属性或方法,或者到达链的终点。
原型链的连接方式:
- 通过对象字面量
{}
创建的对象:{}.__proto__
指向Object.prototype
。 - 通过
new Constructor()
创建的对象:instance.__proto__
指向Constructor.prototype
。 - 一个对象的原型的原型:
Object.getPrototypeOf(obj).__proto__
指向Object.getPrototypeOf(Object.getPrototypeOf(obj))
.
原型链的终点:
原型链的终点是 Object.prototype
,而 Object.prototype
的原型是 null
。null
表示链的结束,再往上就没有可以查找的原型了。
Object.prototype.__proto__ === null
代码示例 : 跟踪原型链
function Animal() {}
Animal.prototype.sound = "Generic Sound";
function Dog(name) {
this.name = name;
}
// 让 Dog 的原型指向一个 Animal 实例 (或者更常见的是 Object.create(Animal.prototype))
// 这里为了演示原型链,我们直接设置 __proto__,但在实际继承中通常不是这么做
// 更标准的继承方式是 Dog.prototype = Object.create(Animal.prototype);
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
const myDog = new Dog("Rex");
console.log("--- 原型链追踪 ---");
// 1. myDog 实例本身
console.log("myDog:", myDog); // { name: "Rex" }
// 2. myDog 的原型 (Dog.prototype)
const dogProto = Object.getPrototypeOf(myDog);
console.log("myDog的原型 (Dog.prototype):", dogProto); // Dog { sound: "Generic Sound" } (这里可能显示为 Dog {},取决于环境,但它链接到了 Animal.prototype)
// 3. Dog.prototype 的原型 (Animal.prototype)
const animalProto = Object.getPrototypeOf(dogProto);
console.log("Dog.prototype的原型 (Animal.prototype):", animalProto); // Animal { sound: "Generic Sound" }
// 4. Animal.prototype 的原型 (Object.prototype)
const objectProto = Object.getPrototypeOf(animalProto);
console.log("Animal.prototype的原型 (Object.prototype):", objectProto); // [Object: null prototype] {}
// 5. Object.prototype 的原型 (null)
const endOfChain = Object.getPrototypeOf(objectProto);
console.log("Object.prototype的原型:", endOfChain); // 输出: null
// 链条: myDog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
解释: 这个例子展示了一个稍微复杂一点的原型链:myDog
实例的原型是 Dog.prototype
,而我们将 Dog.prototype
的原型设置为了 Animal.prototype
。所以当查找属性时,会依次在 myDog
、Dog.prototype
、Animal.prototype
、Object.prototype
上查找,直到找到或到达 null
。
4. 如何通过原型链查找对象的属性或方法?
查找规则:先找自己,找不到就沿着 [[Prototype]]
向上找,直到找到或到顶 (null
)。
代码示例 : 原型链上的属性查找
// 构造函数 A
function A() {
this.propA_instance = "Instance A";
}
A.prototype.propA_proto = "Prototype A";
A.prototype.methodA = function() { console.log("Method A"); };
// 构造函数 B,继承自 函数A (通过设置 B.prototype 的原型为 A.prototype)
function B() {
this.propB_instance = "Instance B";
}
// 更实际的继承方式:用到Object.setPrototypeOf()方法,该方法会将 B.prototype 的原型设置为 A.prototype
//这意味着 B.prototype 将会继承 A.prototype 上的属性和方法。
Object.setPrototypeOf(B.prototype, A.prototype);
B.prototype.propB_proto = "Prototype B";
B.prototype.methodB = function() { console.log("Method B"); };
const instanceB = new B();
console.log("--- 属性查找过程 ---");
// 查找顺序:1 propA_instance: instanceB 没有
//2 B.prototype 没有
//3 去 B.prototype 的原型 (A.prototype) 找
//4 A.prototype 没有
// (注意: propA_instance 是添加到 A 的实例上的,不在 A.prototype 上)
// 5 继续去 A.prototype 的原型 (Object.prototype) 找,没有
// 6 到达 null,查找停止,没找到就显示undefined
//查找 propB_instance: 先从 instanceB 本身找,找到
console.log("instanceB.propB_instance:", instanceB.propB_instance); // 输出:instanceB.propB_instance: Instance B
// 查找 propB_proto: instanceB 没有,去 instanceB 的原型 (B.prototype) 找,找到
console.log("instanceB.propB_proto:", instanceB.propB_proto); // 输出:instanceB.propB_proto: Prototype B
//依次查找都没有找到,输出undefined
console.log("instanceB.propA_instance:", instanceB.propA_instance); // 输出: instanceB.propA_instance:undefined
// 查找 propA_proto: instanceB 没有,B.prototype 没有,去 B.prototype 的原型 (A.prototype) 找,找到
console.log("instanceB.propA_proto:", instanceB.propA_proto); // 输出: instanceB.propA_proto:Prototype A
//查找 toString (来自 Object.prototype): instanceB 没有,B.prototype 没有,A.prototype 没有,去 A.prototype 的原型 (Object.prototype) 找,找到
console.log("instanceB.toString:", instanceB.toString); // 输出:instanceB.toString: function toString() { [native code] }
console.log("\n--- 方法调用过程 ---");
instanceB.methodB(); // 输出: Method B (在 B.prototype 上找到)
instanceB.methodA(); // 输出: Method A (在 A.prototype 上找到)
// instanceB.nonExistentMethod(); // 会抛出 TypeError (找不到)
// 覆盖属性/方法:实例属性会“屏蔽”原型上的同名属性
instanceB.propA_proto = "Overridden in Instance B";
console.log("instanceB.propA_proto after override:", instanceB.propA_proto); // 输出:instanceB.propA_proto after override: Overridden in Instance B
// 原型上的属性未改变
console.log("A.prototype.propA_proto:", A.prototype.propA_proto); // 输出: A.prototype.propA_proto:Prototype A
// 删除实例属性后,会再次访问原型上的属性
delete instanceB.propA_proto;
console.log("instanceB.propA_proto after delete:", instanceB.propA_proto); // 输出: Prototype A (重新访问了原型)
解释:
- 当访问
instanceB.propB_instance
时,属性直接在instanceB
实例上找到,查找结束。 - 当访问
instanceB.propB_proto
时,instanceB
没有,引擎沿着__proto__
链接到B.prototype
,在那里找到了propB_proto
,查找结束。 - 当访问
instanceB.propA_proto
时,instanceB
没有,B.prototype
没有,引擎沿着__proto__
链接到B.prototype
的原型(即A.prototype
),在那里找到了propA_proto
,查找结束。 - 当访问
instanceB.toString
时,沿着链一直向上查找,最终在Object.prototype
上找到。 - 如果一个属性在实例本身和原型链上的某个位置都有,实例上的属性会“屏蔽”原型上的属性,查找会先在实例上停止。
- 删除实例上的属性后,下一次访问同名属性时,查找会继续沿着原型链进行。
概念总结:
- 原型 (Prototype): 是一个对象,用于实现继承,通过对象的
[[Prototype]]
链接。 - 函数的
prototype
属性: 是一个特殊属性,用于指定通过该函数作为构造函数创建的实例的原型。 - 原型链 (Prototype Chain): 是对象通过
[[Prototype]]
链接起来形成的链条,用于属性和方法的查找。 - 查找规则: 从对象本身开始,沿着原型链向上查找,直到找到或到达
null
。 - 原型链终点: 通常是
Object.prototype
,它的原型是null
。