🚗 JS原型链:从“如何拿到小米SU7”到理解对象的共享秘密(附超实用学习笔记)
前言: 今天我刚从小米门店出来,手里空空如也——不是没拿到SU7,而是被JS原型链“拿”得明明白白!别慌,这篇笔记就是帮你“拿到”原型链的终极攻略。就像小米SU7要“构造”才能开,JS的面向对象也要“构造”才能玩转。走,一起把原型链从“玄学”变“日常”!
一、为什么你需要“原型链”?—— 从“小米SU7”说起
想象一下:你想买一辆小米SU7,但小米官网说:“必须用‘构造函数’注册,才能拿到车”。
(注:这里纯属比喻,小米SU7是真车,但JS的“构造函数”是代码! )
- 普通用户:直接去店里问“能给我一辆SU7吗?” → 被拒(因为没注册!)
- JS开发者:用
new Car()“注册”,系统自动给你一辆带共享配置的SU7(比如默认颜色、续航)。
💡 核心思想:JS不是“类”式面向对象(像Java),而是原型式——你给对象“共享配件”,而不是“复制配件”。
就像小米SU7的“霞光紫”配色,不是每辆车都单独涂,而是所有SU7出厂自带,你只需选颜色!
二、基础:构造函数 & new 操作符 —— “注册”你的SU7
1. 构造函数:JS的“买车登记处”
function Car(color) {
this.color = color; // 实例专属属性(每辆车颜色不同)
// 其他参数:this.name = 'SU7', this.weight = 1.5...
}
- 关键:函数首字母大写(约定俗成,提醒你“这是构造函数”)。
this指向:当用new调用时,this指向新创建的空对象(即你的SU7)。
2. new 操作符:激活“注册流程”
const car1 = new Car('霞光紫'); // 生成一辆SU7
console.log(car1.color); // "霞光紫"(实例专属)
-
new做了什么?- 创建一个空对象
{}。 - 将
this绑定到这个空对象。 - 执行构造函数(给对象加属性)。
- 返回这个新对象。
- 创建一个空对象
✨ 小贴士:如果忘记
new,this会指向全局对象(浏览器是window),导致灾难性错误!(就像没注册就抢SU7,被保安拦下😂)
三、核心:prototype —— SU7的“共享配件库”
1. 为什么需要 prototype?
-
如果把所有属性都写在构造函数里:
function Car(color) { this.color = color; this.name = 'SU7'; // 每辆车都存一遍?内存爆炸! this.weight = 1.5; } -
问题:每创建一辆车,
name和weight都会重复存储(浪费内存)。
2. prototype:给所有SU7共享的“配件库”
Car.prototype = {
name: 'SU7', // 所有SU7共享这个名称
weight: 1.5, // 共享重量
drive() { // 共享驾驶方法
console.log('飙车!下赛道~');
}
};
-
关键点:
- 每个函数都有
prototype属性(指向一个对象)。 - 所有通过
new Car()创建的实例,都能访问prototype上的属性和方法。 - 实例不会直接拥有
prototype的属性,但能通过原型链访问。
- 每个函数都有
🌟 为什么叫“原型”?
JS的面向对象是原型式(不是类式)—— 你不是“从类模板复制”,而是“从原型对象借配件”。
就像小米SU7的“共享配件库”:所有车出厂自带,你只需选颜色(color),其他配置(name、weight)直接从库调用。
四、原型链:从SU7到Object的“共享之路”
1. 实例如何找到 prototype?
const car1 = new Car('霞光紫');
console.log(car1.name); // "SU7" → 从Car.prototype找到
console.log(car1.weight); // 1.5 → 从Car.prototype找到
-
秘密武器:
__proto__
每个对象都有__proto__属性(隐式原型),指向它的原型对象。console.log(car1.__proto__ === Car.prototype); // true
2. 原型链:层层向上找“配件”
// 1. car1 有 __proto__ → 指向 Car.prototype
// 2. Car.prototype 有 __proto__ → 指向 Object.prototype
// 3. Object.prototype 有 __proto__ → null(终点!)
-
验证:
console.log(car1.__proto__ === Car.prototype); // true console.log(Car.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null(原型链终点)
🔥 为什么重要?
当你调用car1.toString(),JS会:
- 先在
car1自己找 → 没有。- 通过
__proto__找到Car.prototype→ 没有。- 继续找
Object.prototype→ 找到toString()!
所以所有对象都能用toString()(因为最终都指向Object.prototype)。
五、实例属性 vs 原型属性 —— “定制” vs “共享”
1. 实例属性:专属定制
car1.species = '电动SU7'; // 实例专属属性
console.log(car1.species); // "电动SU7"(只属于car1)
console.log(car2.species); // undefined(car2没有这个属性)
- 本质:直接加在
car1对象上,不修改原型。
2. 原型属性:全局共享
Car.prototype.species = 'human'; // 所有SU7共享
console.log(car1.species); // "human"
console.log(car2.species); // "human"(car2也继承了)
⚠️ 致命陷阱:
如果直接覆盖prototype,会导致constructor丢失!Car.prototype = { ... }; // 覆盖了默认的prototype console.log(Car.prototype.constructor); // 指向新对象,不再是Car!正确做法:用
Object.assign或直接添加属性:Car.prototype.species = 'human'; // 保留constructor
六、实战:用原型链“拿”到小米SU7(代码示例)
// 步骤1:定义构造函数(注册处)
function Car(color) {
this.color = color; // 实例专属:颜色
}
// 步骤2:添加共享属性(配件库)
Car.prototype = {
name: '小米SU7',
weight: 1.5,
drive: function() {
console.log(`驾驶${this.name},颜色:${this.color}!`);
}
};
// 步骤3:注册新车(拿车)
const car1 = new Car('霞光紫');
const car2 = new Car('海湾蓝');
// 步骤4:验证
console.log(car1.name); // "小米SU7"(从原型获取)
console.log(car1.weight); // 1.5(从原型获取)
car1.drive(); // 驾驶小米SU7,颜色:霞光紫!
console.log(car1.__proto__ === Car.prototype); // true
// 实例属性覆盖原型(定制)
car1.weight = 1.6; // 只影响car1
console.log(car1.weight); // 1.6
console.log(car2.weight); // 1.5(car2仍用原型值)
💡 运行结果:
小米SU7 1.5 驾驶小米SU7,颜色:霞光紫! true 1.6 1.5
七、为什么说“原型链是JS的灵魂”?
-
内存高效:共享属性(如
name、drive)只存一份,不重复。 -
动态扩展:随时给
prototype加方法,所有实例自动生效:Car.prototype.stop = function() { console.log('停车!'); }; car1.stop(); // 所有SU7都能用 -
继承基石:通过
__proto__实现对象间继承(如Person继承Animal)。
🌈 我的感悟:
刚学JS时,我也被原型链搞崩溃过——直到某天,我用它给项目加了个“全局配置”,瞬间觉得: “这不就是JS的优雅!”
现在写代码,看到__proto__就像看到小米SU7的共享配件库,安心又高效!
八、避坑指南:新手常踩的“原型坑”
| 误区 | 错误写法 | 正确写法 | 为什么错 |
|---|---|---|---|
覆盖 prototype | Car.prototype = { ... } | Car.prototype.weight = 1.5; | 丢失 constructor,导致 instanceof 失效 |
误用 this | Car.prototype.drive = function() { this.color } | Car.prototype.drive = function() { console.log(this.color) } | this 指向实例,不是 prototype |
忽略 __proto__ | 用 Car.prototype 直接访问 | 用 car1.__proto__ 理解原型链 | __proto__ 是实例的“指路牌” |
💡 终极建议:
用Object.getPrototypeOf(obj) === Car.prototype替代obj.__proto__(更标准)。
结语:从“拿车”到“拿思路”
今天我终于“拿到”了小米SU7(虽然没提车,但学会了JS!),而你——
也已经拿到了JS原型链的“钥匙” 。
别再纠结“为什么car1.name能找到”,记住:
“原型链不是谜题,而是JS的共享图书馆——你借书,图书馆不收钱,但得还回来!”
下次写代码时,试着问自己:
“这个属性,是该写在
this里(专属),还是prototype里(共享)?”
答案会告诉你:你已从“新手”进化到“JS老司机”!
✨ 最后彩蛋:
如果你真的想“拿”小米SU7,可以去小米官网注册(别像我一样忘了new!)。
但JS的原型链,今天你已拿到,且终身有效!
共勉: “代码如车,构造要正,原型要清,才能跑得远~” 🚀