JS原型链:从“如何拿到小米SU7”到理解对象的共享秘密

23 阅读6分钟

🚗 JS原型链:从“如何拿到小米SU7”到理解对象的共享秘密(附超实用学习笔记)

前言: 今天我刚从小米门店出来,手里空空如也——不是没拿到SU7,而是被JS原型链“拿”得明明白白!别慌,这篇笔记就是帮你“拿到”原型链的终极攻略。就像小米SU7要“构造”才能开,JS的面向对象也要“构造”才能玩转。走,一起把原型链从“玄学”变“日常”!


一、为什么你需要“原型链”?—— 从“小米SU7”说起

想象一下:你想买一辆小米SU7,但小米官网说:“必须用‘构造函数’注册,才能拿到车”。
注:这里纯属比喻,小米SU7是真车,但JS的“构造函数”是代码!

  • 普通用户:直接去店里问“能给我一辆SU7吗?” → 被拒(因为没注册!)
  • JS开发者:用 new Car() “注册”,系统自动给你一辆带共享配置的SU7(比如默认颜色、续航)。

💡 核心思想:JS不是“类”式面向对象(像Java),而是原型式——你给对象“共享配件”,而不是“复制配件”。
就像小米SU7的“霞光紫”配色,不是每辆车都单独涂,而是所有SU7出厂自带,你只需选颜色!

1F511784-A8CD-4CA1-A988-AEF9823B416E.png


二、基础:构造函数 & 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 做了什么?

    1. 创建一个空对象 {}
    2. this 绑定到这个空对象。
    3. 执行构造函数(给对象加属性)。
    4. 返回这个新对象。

小贴士:如果忘记 newthis 会指向全局对象(浏览器是 window),导致灾难性错误!(就像没注册就抢SU7,被保安拦下😂)


三、核心:prototype —— SU7的“共享配件库”

1. 为什么需要 prototype

  • 如果把所有属性都写在构造函数里:

    function Car(color) {
      this.color = color;
      this.name = 'SU7'; // 每辆车都存一遍?内存爆炸!
      this.weight = 1.5;
    }
    
  • 问题:每创建一辆车,nameweight 都会重复存储(浪费内存)。

2. prototype:给所有SU7共享的“配件库”

Car.prototype = {
  name: 'SU7',      // 所有SU7共享这个名称
  weight: 1.5,      // 共享重量
  drive() {         // 共享驾驶方法
    console.log('飙车!下赛道~');
  }
};
  • 关键点

    • 每个函数都有 prototype 属性(指向一个对象)。
    • 所有通过 new Car() 创建的实例,都能访问 prototype 上的属性和方法
    • 实例不会直接拥有 prototype 的属性,但能通过原型链访问

🌟 为什么叫“原型”?
JS的面向对象是原型式(不是类式)—— 你不是“从类模板复制”,而是“从原型对象借配件”。
就像小米SU7的“共享配件库”:所有车出厂自带,你只需选颜色(color),其他配置(nameweight)直接从库调用。


四、原型链:从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会:

  1. 先在 car1 自己找 → 没有。
  2. 通过 __proto__ 找到 Car.prototype → 没有。
  3. 继续找 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的灵魂”?

  1. 内存高效:共享属性(如 namedrive只存一份,不重复。

  2. 动态扩展:随时给 prototype 加方法,所有实例自动生效:

    Car.prototype.stop = function() { console.log('停车!'); };
    car1.stop(); // 所有SU7都能用
    
  3. 继承基石:通过 __proto__ 实现对象间继承(如 Person 继承 Animal)。

🌈 我的感悟
刚学JS时,我也被原型链搞崩溃过——直到某天,我用它给项目加了个“全局配置”,瞬间觉得: “这不就是JS的优雅!”
现在写代码,看到 __proto__ 就像看到小米SU7的共享配件库,安心又高效


八、避坑指南:新手常踩的“原型坑”

误区错误写法正确写法为什么错
覆盖 prototypeCar.prototype = { ... }Car.prototype.weight = 1.5;丢失 constructor,导致 instanceof 失效
误用 thisCar.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的原型链,今天你已拿到,且终身有效
共勉: “代码如车,构造要正,原型要清,才能跑得远~” 🚀