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跑起来了!'

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

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

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

说完了构造函数的"共享仓库",咱们再看看实例对象身上的"寻根指针"------隐式原型(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()来操作隐式原型哦!

相关推荐
码云骑士14 小时前
104-实战论文搜索引擎-ArXiv爬取-Milvus存储-RAG问答-Gradio前端
前端·python·搜索引擎·milvus
sunly_14 小时前
TypeScript总结:15、类型速查
前端·javascript·typescript
Brown.alexis14 小时前
es6知识点3-自备使用
前端·javascript·es6
2601_9539880714 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·后端·物联网·tcp/ip·数学建模·前端框架
天空之城--14 小时前
Claude Code 高效开发 Web 2D/3D 完全指南:心法、自定义 Skill 体系与社区技能包实战
前端·3d
IT_陈寒15 小时前
SpringBoot自动配置坑了我三天,原来漏了这个注解
前端·人工智能·后端
逝水无殇15 小时前
HTML 文本格式化详解:掌握 <strong>、<em>、<mark>、<del> 等常用标签
前端·javascript·html
特立独行的猫a15 小时前
DeepSeek Harness插件和工具的区别介绍及开发入门指南
前端·ai·agent·插件·deepseek·harness
小新讲网安15 小时前
HTTP请求走私攻击实战:CL.TE与TE.CL绕过前端服务器全解析
服务器·前端·网络·web安全·http·架构·漏洞
风骏时光牛马15 小时前
AI办公系统异常事件复盘分析
前端