探索JavaScript原型链的神秘世界
嘿,前端小伙伴们!今天咱们要一起踏上一段超有趣的旅程,深入探索JavaScript中那个神秘又超级重要的原型链世界😎。
一、从对象说起:开启原型之门
在JavaScript这个神奇的编程世界里,对象就像是一个个装满宝藏的魔法盒子🧙♂️。它们是复合数据类型,就像一个无序的“键值对”百宝箱,能把各种数据类型(数字、字符串、布尔值,甚至其他对象)都收纳其中。
(一)对象的创建魔法
对象字面量方式:这就像是我们用魔法咒语直接召唤出一个装满宝贝的盒子,简单又直接。比如:
let obj1 = { name: "Jack", age: 26 };Object构造函数模式:稍微复杂一点的召唤术,先创建一个空盒子,再逐个把宝贝放进去。
let obj2 = new Object(); obj2.name = "Jack"; obj2.age = 26;构造函数模式:这可是批量制造魔法盒子的强大法术哦!定义一个构造函数,就像是打造一个盒子的模具,然后用
new关键字就能创造出无数个相似但又独特的盒子。function Test(name, age) { this.name = name; this.age = age; this.say = function() { console.log('我能说话'); } } let obj3 = new Test('Jack', 26); let obj4 = new Test('Rose', 25);不过要小心哦,用这种方式创建的对象,每个盒子里的方法(比如
say函数)都是独一无二的,就像每个魔法盒子都有自己的专属小魔法,虽然看起来一样,但其实占用了不少内存空间呢🧙♀️。
(二)构造函数与实例的神秘纽带
构造函数和它创造出来的实例对象之间,有着一种神秘的联系。每个实例对象都知道自己是哪个构造函数生出来的,就像孩子知道自己的妈妈是谁一样。通过 constructor 属性,实例就能找到它的构造函数。
function Test(name, age) {
this.name = name;
this.age = age;
}
let obj3 = new Test('Jack', 26);
console.log(obj3.constructor === Test); 这里的 obj3 就是 Test 构造函数的一个小宝贝(实例),它通过 constructor 属性紧紧抓住了自己的创造者 Test。
二、原型对象登场:共享魔法的源泉
当我们发现很多魔法盒子(实例对象)都需要一些共同的魔法(属性和方法)时,原型对象就闪亮登场啦✨!
(一)原型对象的奇妙特性
每个对象(包括函数哦,因为函数在JavaScript里也是一种对象)都有一个神秘的 prototype 属性,它就像一个隐藏在幕后的超级魔法库,里面存放着可以被所有实例对象共享的魔法(属性和方法)。
function Test(name, age) {
this.name = name;
this.age = age;
}
Test.prototype.say = function() {
console.log('我能说话');
};
let obj3 = new Test('Jack', 26);
let obj4 = new Test('Rose', 25);
obj3.say();
obj4.say();
console.log(obj3.say === obj4.say); 看,我们把 say 魔法放到了 Test 的原型对象这个超级魔法库里,所有 Test 构造函数创造出来的小盒子(实例)都能使用这个魔法,而且它们调用的是同一个魔法哦,这样就大大节省了内存空间,是不是超级厉害😃?
(二)原型对象的自我修养
原型对象有一个
constructor属性,就像一个魔法标签,清楚地标明了自己是属于哪个构造函数的。function Test(name, age) { this.name = name; this.age = age; } console.log(Test.prototype.constructor === Test);- 它本身就是一个普通的对象,但却拥有改变其他对象的强大力量,因为它的属性和方法可以被继承到其他对象身上。
三、隐式原型__proto__:幕后的导航精灵
(一)__proto__的神秘力量
每个JavaScript对象都有一个隐藏的小助手,叫做 __proto__(记住哦,两边是两条小短下划线),它就像一个幕后的导航精灵🧚♂️,默默地为原型链查找指明方向。实例对象的 __proto__ 会指向创建它的构造函数的原型对象。
function Test(name, age) {
this.name = name;
this.age = age;
}
let obj3 = new Test('Jack', 26);
console.log(obj3.__proto__ === Test.prototype); (二)考考你哦🧐
- 构造函数是
Test,实例是obj3。 obj3.constructor === Test是true,因为实例能通过constructor属性找到自己的构造函数。obj3.__proto__ === Test是false,obj3.__proto__指向的是Test.prototype,而不是Test构造函数本身哦。Test.prototype === obj3.__proto__是true,这就是实例和构造函数原型对象之间的神秘联系。obj3.__proto__.constructor === Test是true,再次验证了原型对象的constructor属性指向它所属的构造函数。
四、原型链:魔法世界的传承链条
(一)追寻Object.prototype的踪迹
我们发现 Test.prototype 也有自己的 __proto__,那它指向谁呢🧐?答案是 Object.prototype!就像所有的魔法传承最终都能追溯到一个古老的魔法源头一样,在JavaScript中,所有的对象(包括构造函数的原型对象)最终都继承自 Object.prototype,它就是原型链的顶点👑。
function Test(name, age) {
this.name = name;
this.age = age;
}
console.log(Test.prototype.__proto__ === Object.prototype); (二)到达终点:null
那 Object.prototype 的 __proto__ 又是什么呢🤔?它就像魔法世界的尽头,指向 null,表示原型链在这里画上了句号。
console.log(Object.prototype.__proto__ === null); (三)原型链的工作原理
当我们在一个对象上寻找某个魔法(属性或方法)时,就像在魔法盒子里找宝贝一样,如果在这个对象自身没找到,它的 __proto__ 导航精灵就会带着我们顺着原型链向上查找,一直找到 Object.prototype,如果还没找到,那就只能失望地返回 undefined(如果是找方法没找到就会直接报错说不是一个函数哦)😅。
(四)练习时间到啦💪
Test.prototype === (obj3.__proto__ 或 obj4.__proto__),因为实例的__proto__指向构造函数的原型对象。obj3.__proto__.__proto__ === Object.prototype,沿着原型链向上查找。obj3.__proto__ === obj4.__proto__是true,因为它们都是由Test构造函数创建的,共享同一个原型对象。Test.prototype.__proto__ === Object.prototype,再次强调原型链的指向关系。obj4.__proto__.constructor === Test,原型对象的constructor属性指向构造函数。Object.prototype.__proto__ === null,原型链的终点。obj3.say === obj4.say是true,因为它们共享原型对象上的say方法。
五、进阶探秘:深入原型链的核心
(一)普通对象与函数对象的区别
在JavaScript的魔法世界里,有两种主要类型的对象:普通对象和函数对象。普通对象就像我们常见的那些普通魔法盒子,用 { } 创建;而函数对象则像是拥有特殊魔法技能的魔法盒子,通过 new Function 或者函数声明、函数表达式创建。我们可以用 typeof 这个魔法探测器来区分它们🧐。
function f1() {}
let f2 = function() {}
let f3 = new Function('name');
let b1 = new f1();
let b2 = { name: 'Rose' };
let b3 = new Object();
console.log(typeof f1);
console.log(typeof f2);
console.log(typeof f3);
console.log(typeof b1);
console.log(typeof b2);
console.log(typeof b3);
console.log(typeof Object); (二)原型链机制的秘密
还记得我们之前说的 __proto__ 是原型链查找的导航精灵吗🧚♂️?但是这里有个小谜题,为什么在构造函数 Test 的原型对象上定义属性,通过构造函数本身访问不到,而在 Object.prototype 上定义就能访问到呢🤔?
function Test(name, age) {
this.name = name;
this.age = age;
}
Test.prototype.sex = '男';
console.log(Test.sex);
Object.prototype.sex = '男';
console.log(Test.sex); 原来,原型链的查找是从实例对象开始的,而不是从构造函数本身哦!构造函数虽然也是函数对象,也有 __proto__ 属性,但是它的查找机制和实例对象不太一样。
(三)Function的原型之谜
构造函数 Test 的 __proto__ 指向 Function.prototype,这就像是函数对象都有一个共同的魔法祖先🧙♂️。而且,Function.prototype 和 Function.__proto__ 竟然相等,这看起来有点像魔法世界里的一个神奇循环😮!不过,其实 Function 作为一个特殊的内置对象,它在代码运行前就存在了,它的原型指向 Function.prototype 是为了保持和其他函数的一致性,表明一种特殊的关系。
function Test(name, age) {
this.name = name;
this.age = age;
}
console.log(Test.__proto__ === Function.prototype);
console.log(Function.prototype === Function.__proto__); 同时,Function.prototype.__proto__ 指向 Object.prototype,这也确保了原型链的完整性,所有的函数对象最终也都继承自 Object.prototype。
(四)再来一轮练习巩固一下😎
console.log(obj1.__proto__ === Test.prototype)是true,实例的__proto__指向构造函数的原型对象。obj1.__proto__.__proto__ === Object.prototype,沿着原型链向上查找。Object.prototype.__proto__ === null,原型链的终点。console.log(Test.prototype === obj1.__proto__)是true,再次验证实例和构造函数原型对象的关系。Test.__proto__ === Function.prototype,函数对象的__proto__指向Function.prototype。Function.prototype.__proto__ === Object.prototype,保持原型链的完整性。Function.prototype === Function.__proto__是true,函数对象原型的特殊关系。
六、总结时刻:掌握原型链的魔法钥匙
- 每个对象都有一个隐式原型
__proto__,但只有函数对象才有prototype属性哦。 __proto__是我们在原型链世界里的导航精灵,原型链查找全靠它,而不是prototype属性。- 函数对象的
__proto__都指向Function.prototype,这是它们的魔法根源。 - 多个对象通过
__proto__连接起来,就形成了神奇的原型链,它让JavaScript的对象世界充满了魔法传承和共享的魅力✨。
希望这次的原型链之旅能让你对JavaScript的这个重要概念有更清晰、更有趣的理解😘。如果在旅途中你发现了任何错误或者有更好的想法,欢迎评论私信告诉我哦,让我们一起在JavaScript的魔法世界里不断探索进步💖!

1 条评论
每个标点都承载着思考的重量。