JavaScript原型:对象世界的"族谱"与"共享仓库"

你是否曾好奇过,为什么[].push()能直接调用?为什么你能随意给String添加自定义方法?这一切的背后,都藏着JavaScript中一个既神秘又强大的机制------原型(prototype)。今天,咱们就来扒一扒这个对象世界里的"族谱"和"共享仓库"。

一、显示原型:构造函数的"共享仓库"

首先,咱们得认识一位重要角色------显示原型(prototype)。你可以把它理解成构造函数的一个"共享仓库"。

javascript 复制代码
// 定义一个汽车构造函数
function Car(color) {
  this.color = color; // 每个实例都有自己的颜色
}

// 在原型上"存放"共享的属性和方法
Car.prototype.name = 'su7-Ultra';
Car.prototype.run = function() {
  console.log(`${this.color}的${this.name}跑起来了!`);
};

const car1 = new Car('橙色');
const car2 = new Car('绿色');

console.log(car1.name); // 'su7-Ultra'
car2.run(); // '绿色的su7-Ultra跑起来了!'

你发现了吗?虽然car1和car2是不同的实例,但它们都能访问到Car.prototype上的name和run。这就是原型的神奇之处------把公共的属性和方法放在原型上,避免每个实例都重复创建一份,既省内存又方便维护。

值得注意的是,实例对象无法修改或删除原型上的属性和方法,只能"借用"或者"覆盖"(在自己身上重新定义同名属性)。

二、隐式原型:实例对象的"寻根指针"

说完了构造函数的"共享仓库",咱们再看看实例对象身上的"寻根指针"------隐式原型(proto)。

每个JavaScript对象都天生带着一个__proto__属性,它指向创建这个对象的构造函数的prototype。用代码表示就是:

javascript 复制代码
car1.__proto__ === Car.prototype; // true

这就解释了为什么实例能访问到原型上的属性------当V8引擎查找对象的属性时,会先在对象自身查找,如果找不到,就会顺着__proto__去原型上找。

三、new的原理:对象诞生的"五步曲"

既然说到实例和原型的关系,咱们就不得不聊聊new关键字这个"接生婆"是怎么工作的。创建一个实例其实分五步:

  1. 搭空屋子 :创建一个空对象{}
  2. 认干爹 :让构造函数的this指向这个空对象
  3. 装修屋子:执行构造函数中的代码(给空对象添加属性)
  4. 认祖归宗 :把空对象的__proto__指向构造函数的prototype
  5. 抱回家:返回这个"装修好"的对象

咱们可以手动模拟这个过程:

javascript 复制代码
function Person() {
  this.name = '张三';
}

// 手动实现new的效果
function myNew(constructor) {
  const obj = {}; // 1. 创建空对象
  constructor.call(obj); // 2. 绑定this并执行构造函数
  obj.__proto__ = constructor.prototype; // 4. 设置原型链
  return obj; // 5. 返回对象
}

const p1 = myNew(Person);
const p2 = new Person();
// p1和p2的结构基本一样

四、原型链:对象世界的"族谱图"

了解了原型的基本概念,咱们再来看看更宏大的"原型链"。

当V8引擎查找一个对象的属性时,如果在对象自身找不到,就会顺着__proto__去原型上找;如果原型上也找不到,就会继续顺着原型的__proto__往上找,直到找到null为止。这个层层查找的链路,就是原型链。

咱们通过一个经典的例子来理解:

javascript 复制代码
// 爷爷构造函数
function GrandParent() {
  this.name = '爷爷';
  this.card = 'visa';
}

// 爸爸构造函数
function Parent() {
  this.lastName = '张';
}
Parent.prototype = new GrandParent(); // 爸爸的原型指向爷爷的实例

// 儿子构造函数
function Child() {
  this.name = '张三';
  this.age = 18;
}
Child.prototype = new Parent(); // 儿子的原型指向爸爸的实例

const child = new Child();
console.log(child.lastName); // '张' (从爸爸那里继承)
console.log(child.card); // 'visa' (从爷爷那里继承)
console.log(child.toString()); // '[object Object]' (从Object那里继承)

在这个例子中,child的原型链是这样的: child -> Child.prototype(Parent实例) -> Parent.prototype(GrandParent实例) -> GrandParent.prototype -> Object.prototype -> null

这就是为什么几乎所有JavaScript对象都能调用toString()、hasOwnProperty()等方法------因为它们都在Object.prototype这个"老祖宗"身上!

五、没有原型的对象:"跳出三界外"

不过,JavaScript里也有一些"特立独行"的对象,它们没有原型。通过Object.create(null)可以创建这样一个"跳出三界外"的对象:

javascript 复制代码
const obj1 = {}; // 有原型,__proto__指向Object.prototype
const obj2 = Object.create(null); // 没有原型,__proto__是undefined

console.log(obj1.toString); // 有这个方法
console.log(obj2.toString); // undefined,找不到这个方法

这种对象适合用作纯粹的"哈希表",因为它不会受到原型链上属性的干扰。

写在最后

原型系统是JavaScript的核心机制之一,它既实现了对象间的属性共享,又构建了一套灵活的继承体系。理解原型,就像拿到了一把钥匙,能帮你打开JavaScript对象世界的大门。

下次再看到Array.prototype.forEach这样的代码时,不妨想想:哦,原来这就是在给数组的"共享仓库"添加工具呢!

希望这篇文章能让你对JavaScript原型有更深刻的理解。如果你有任何疑问,欢迎在评论区留言讨论!

小贴士:虽然__proto__在浏览器中被广泛支持,但在实际开发中,更推荐使用Object.getPrototypeOf()和Object.setPrototypeOf()来操作隐式原型哦!

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端