当我把 proto 打印出来那一刻,我懂了JS的原型链

💬 前言:我本以为我会面向对象,结果我连"对象"都没搞懂

刚开始学 JavaScript 的时候,我以为:

js 复制代码
function Person(name) {
  this.name = name;
}

const p1 = new Person('小明');
console.log(p1.name); // 小明

这不就是面向对象吗?简单!

直到有一天,我在控制台敲下:

js 复制代码
console.log(p1.__proto__);

然后------我的世界崩塌了。

满屏的 [[Prototype]]constructor__proto__......我仿佛掉进了一个无限嵌套的俄罗斯套娃里。

"我是谁?"

"我从哪里来?"

"我要到哪里去?"

------来自一个被原型链逼疯的学生的灵魂三问。

今天,就用一个真实学习者 的视角,带你从困惑到理解,一步步揭开 prototype 的神秘面纱。没有高深术语,只有大白话 + 可运行代码 + 我踩过的坑。


🚪 一、为什么需要原型?------ 因为我不想每个对象都背一份方法

假设我们要创建多个学生对象:

❌ 错误写法:每个学生都自带"技能包"

js 复制代码
function Student(name) {
  this.name = name;
  // 每个学生都独立拥有一个 sayHello 方法
  this.sayHello = function() {
    console.log(`大家好,我是${this.name}`);
  };
}

const s1 = new Student('张三');
const s2 = new Student('李四');

console.log(s1.sayHello === s2.sayHello); // false → 完全不同的两个函数!

问题来了:如果创建 1000 个学生,就会有 1000 个 sayHello 函数,内存直接爆炸 💥。

这就像学校给每个学生发一本《礼仪手册》,其实大家看的都是同一本书,但每人一本------太浪费了!

✅ 正确姿势:把公共方法放进"共享书架"(prototype)

js 复制代码
function Student(name) {
  this.name = name; // 每个学生独有的属性
}

// 所有学生共享的方法,统一挂载到 prototype 上
Student.prototype.sayHello = function() {
  console.log(`大家好,我是${this.name}`);
};

const s1 = new Student('张三');
const s2 = new Student('李四');

console.log(s1.sayHello === s2.sayHello); // true → 同一个函数,只存一份!
s1.sayHello(); // 大家好,我是张三
s2.sayHello(); // 大大家好,我是李四

📌 我的理解

  • prototype 就是构造函数的"共享书架"
  • 实例自己没有的方法,会自动去书架上找
  • 既节省内存,又方便统一管理

这就是原型存在的意义:让对象学会"蹭"!


🔗 二、四大核心概念:别再混淆 prototype 和 proto 了!

刚开始我总分不清 prototype__proto__,后来我画了张图,终于懂了。


1️⃣ 构造函数:创建实例的"模板"

js 复制代码
function Student(name) {
  this.name = name;
}

它就是一个普通函数,但通常:

  • 首字母大写
  • new 调用

new 的过程可以简化为:

  1. 创建空对象 {};
  2. this 指向它;
  3. 执行函数体;
  4. 返回这个对象。

2️⃣ prototype:构造函数的"共享仓库"

每个函数都有一个 prototype 属性,它是一个对象,用来存放所有实例共享的内容

js 复制代码
Student.prototype.species = '人类';
Student.prototype.study = function() {
  console.log(`${this.name}在努力学习`);
};

⚠️ 注意:prototype函数才有的属性


3️⃣ __proto__:实例通往原型的"梯子"

每个对象(包括实例)都有一个 __proto__ 属性(非标准但广泛支持),它指向其构造函数的 prototype

js 复制代码
const s1 = new Student('张三');

console.log(s1.__proto__ === Student.prototype); // true

👉 这就是实例能访问到 sayHello 的原因:
s1.sayHello() → 自己没有 → 顺着 __proto__ 找 → 找到 Student.prototype.sayHello

🎯 记住一句话:实例的 __proto__ 指向构造函数的 prototype


4️⃣ constructor:原型的"回老家按钮"

原型对象上有一个 constructor 属性,指向构造函数本身。

js 复制代码
console.log(Student.prototype.constructor === Student); // true
console.log(s1.constructor === Student); // true

⚠️ 重要提醒:手动重写 prototype 要修复 constructor!

js 复制代码
Student.prototype = {
  sayHello() { console.log('hi') }
};

const s1 = new Student('张三');
console.log(s1.constructor === Student); // false ❌
console.log(s1.constructor === Object); // true → 错了!

// ✅ 修复:
Student.prototype = {
  constructor: Student,
  sayHello() { console.log('hi') }
};

否则后续 instanceof 判断可能出错。


📊 核心关系图(建议收藏)

📌 再说一遍:实例的 __proto__ 指向构造函数的 prototype,原型的 constructor 指向构造函数

🔍 三、原型查找机制:JS是怎么找到方法的?

当你调用 s1.sayHello() 时,JS 引擎是这样找的:

  1. 先看 s1 自己有没有 sayHello
  2. 没有?那就通过 __proto__Student.prototype 找;
  3. 还没有?继续通过 Student.prototype.__proto__ 找上一级;
  4. 直到找到,或者查到 null

这个链条,就是原型链

🖼️JavaScript 原型链完整关系图

1. 查找示例

js 复制代码
function Student(name) {
  this.name = name;
}

Student.prototype.species = '人类';
Student.prototype.study = function() {
  console.log(`${this.name}在学习`);
};

const s1 = new Student('张三');

console.log(s1.name);        // 张三 → 自身属性
console.log(s1.species);     // 人类 → 来自 prototype
console.log(s1.toString());  // [object Object] → 来自 Object.prototype
console.log(s1.abc);         // undefined → 找不到

2. 原型链终点:null

js 复制代码
console.log(Object.prototype.__proto__); // null → 终点!

// 验证整个链:
console.log(s1.__proto__);                 // Student.prototype
console.log(s1.__proto__.__proto__);       // Object.prototype
console.log(s1.__proto__.__proto__.__proto__); // null

3. 实例属性可以"屏蔽"原型属性

js 复制代码
function Student(name) {
  this.name = name;
  this.species = '外星人'; // 覆盖原型属性
}

Student.prototype.species = '人类';

const s1 = new Student('张三');
console.log(s1.species); // 外星人

delete s1.species;
console.log(s1.species); // 人类 → 删除后重新查找原型

✅ 应用:为个别实例定制行为,不影响全局。


🧬 四、原型式继承:JS的"继承"到底是什么?

传统语言是"类继承"(血缘关系),而 JS 是"委托继承"------你不会,就去问你爸,你爸不会,就去问爷爷。

1. 经典继承实现

js 复制代码
// 父类
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  console.log(`你好,我是${this.name}`);
};

// 子类
function Student(name, grade) {
  Person.call(this, name); // 继承父类实例属性
  this.grade = grade;
}

// 继承父类原型方法
Student.prototype = new Person();
Student.prototype.constructor = Student;

// 扩展子类方法
Student.prototype.study = function() {
  console.log(`${this.name}在读${this.grade}年级`);
};

const s1 = new Student('张三', 3);
s1.greet(); // 你好,我是张三(继承)
s1.study(); // 张三在读3年级(自有)

2. ES6 class 只是语法糖

js 复制代码
class Person {
  constructor(name) { this.name = name; }
  greet() { console.log(`你好,我是${this.name}`); }
}

class Student extends Person {
  constructor(name, grade) {
    super(name);
    this.grade = grade;
  }
  study() { console.log(`${this.name}在读${this.grade}年级`); }
}

底层依然是原型链驱动。class 不是新东西,只是让你写得更爽。


💡 五、原型的实际应用

1. 工具类共享方法

js 复制代码
function Utils() {}
Utils.prototype.formatDate = function(date) { /* ... */ };

2. 扩展原生对象(谨慎!)

js 复制代码
Array.prototype.unique = function() {
  return [...new Set(this)];
};
[1,2,2,3].unique(); // [1,2,3]

⚠️ 注意:生产环境慎用,避免污染全局。

3. 单例模式

js 复制代码
function Singleton() {
  if (Singleton.prototype.instance) {
    return Singleton.prototype.instance;
  }
  this.data = '唯一实例';
  Singleton.prototype.instance = this;
}

4. 框架中的应用(如 Vue)

js 复制代码
Vue.prototype.$http = axios; // 所有组件都能用 this.$http

⚠️ 六、常见误区

❌ 误区1:混淆 prototype 和 proto

  • prototype:函数才有,是"仓库"
  • __proto__:对象都有,是"梯子"

❌ 误区2:覆盖 prototype 不修 constructor

会导致 instanceof 失效。

✅ 正确做法:永远记得修 constructor!


🏁 七、总结:原型是JS的灵魂

核心要点 说明
🔹 核心价值 共享方法,节省内存
🔹 核心关系 实例.__proto__ === 构造函数.prototype
🔹 查找机制 自身 → 原型链 → null
🔹 继承本质 委托查找,非类继承
🔹 class 本质 原型的语法糖

🌟 最后感悟

学原型的过程,就像在迷宫中找出口。

一开始觉得混乱,但当你画出那张关系图,执行第一段可运行代码,听到"啊哈!"的那一声------

你就真正理解了 JavaScript 的灵魂。

相关推荐
小离a_a1 小时前
flex垂直布局,容器间距相等
开发语言·javascript·ecmascript
Cassie燁1 小时前
element-plus源码解读1——useNamespace
前端·vue.js
一直在学习的小白~1 小时前
npm发布脚手架流程
前端·npm·node.js
ErMao1 小时前
TypeScript的泛型工具集合
前端·javascript
涔溪1 小时前
如何解决微前端架构中主应用和微应用的通信问题?
前端·架构
重铸码农荣光2 小时前
深入理解 JavaScript 原型链:从 Promise.all 到动态原型的实战探索
前端·javascript·promise
进击的野人2 小时前
深入理解 Async/Await:现代 JavaScript 异步编程的优雅解决方案
javascript·面试·ecmascript 6
我叫黑大帅2 小时前
什么叫可迭代对象?为什么要用它?
前端·后端·python
颜渊呐2 小时前
Vue3 + Less 实现动态圆角 TabBar:从代码到优化实践
前端·css