JavaScript原型链终极解析:彻底搞懂prototype和__proto__的区别 - 详解

news/2025/9/24 22:41:55/文章来源:https://www.cnblogs.com/slgkaifa/p/19110144

JavaScript原型链终极解析:彻底搞懂prototype和__proto__的区别 - 详解

JavaScript原型链终极解析:彻底搞懂prototype和__proto__的区别

你被JavaScript的prototype和__proto__搞得头晕脑胀吗?这两个概念确实让很多开发者困惑不已。今天我们用最直白的方式,彻底搞清楚它们的区别和关系,让你再也不会在面试中栽跟头。

先记住一个核心区别

在深入之前,先记住这个最重要的区别:

这个区别是理解整个原型链的关键。很多人混淆这两个概念,就是因为没有搞清楚这个基本点。

JavaScript的"创世神话"

为了更好理解原型链,我们可以把JavaScript的对象体系想象成一个"神话世界"。

第一神:Object.prototype

在JavaScript的世界里,有一个万物起源,就是Object.prototype。它是所有对象的最终祖先。

// Object.prototype是万物的尽头
console.log(Object.prototype.__proto__);
// null

这个null就代表虚无,再往上就没有了。Object.prototype就是继承链的终点。

第二神:Function.prototype

接下来诞生了第二个重要角色:Function.prototype。它继承自Object.prototype:

console.log(Function.prototype.__proto__ === Object.prototype);
// true

有个容易混淆的点:Function.prototype本身也是个函数,但它是个特殊的函数。它不管你传什么参数,都返回undefined,而且不能用new调用。

函数是"一等公民"的真正含义

经常听到"函数在JavaScript中是一等公民",这到底什么意思?

其实就是说,所有的函数(包括Function构造函数本身)都继承自Function.prototype:

// 所有函数的__proto__都指向Function.prototype
console.log(Object.__proto__ === Function.prototype);
// true
console.log(Function.__proto__ === Function.prototype);
// true
console.log(String.__proto__ === Function.prototype);
// true
console.log(Number.__proto__ === Function.prototype);
// true

连Function自己都继承自Function.prototype,这看起来有点奇怪,但确实是这样设计的。

用实例来验证理解

让我们通过几个具体例子来验证我们的理解:

例子1:Object instanceof Object

这个表达式为什么是true?

console.log(Object instanceof Object);
// true

分析过程:

  1. Object是个函数,所以Object.__proto__ === Function.prototype
  2. Function.prototype.__proto__ === Object.prototype
  3. instanceof会沿着__proto__链查找,最终找到了Object.prototype
  4. 所以结果是true

例子2:Function instanceof Function

console.log(Function instanceof Function);
// true

分析过程:

  1. Function是个函数,所以Function.__proto__ === Function.prototype
  2. instanceof在第一步就找到了Function.prototype
  3. 所以结果是true

例子3:自定义函数

function MyFunction() {
}
console.log(MyFunction instanceof Function);
// true
console.log(MyFunction instanceof Object);
// true

分析过程:

  1. MyFunction是函数,MyFunction.__proto__ === Function.prototype
  2. Function.prototype.__proto__ === Object.prototype
  3. 所以MyFunction既是Function的实例,也是Object的实例

构造函数的prototype属性

当我们创建函数时,JavaScript会自动给它添加一个prototype属性:

function Person(name) {
this.name = name;
}
// Person.prototype是人为设定的
Person.prototype.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
const person1 = new Person('张三');
// person1的__proto__指向Person.prototype
console.log(person1.__proto__ === Person.prototype);
// true

这里的关键理解:

  • Person.prototype是我们人为设定的,用来给Person的实例添加共享方法
  • person1.__proto__是自动设置的,指向构造函数的prototype

原型链查找机制

当我们访问对象的属性时,JavaScript会按照这个顺序查找:

function Person(name) {
this.name = name;
}
Person.prototype.species = 'human';
Object.prototype.planet = 'earth';
const person = new Person('李四');
// 查找顺序演示
console.log(person.name);
// 直接在person对象上找到
console.log(person.species);
// 在Person.prototype上找到 
console.log(person.planet);
// 在Object.prototype上找到
console.log(person.nothing);
// 都找不到,返回undefined

查找路径:

  1. person自身属性
  2. person.proto (即Person.prototype)
  3. person.proto.proto (即Object.prototype)
  4. person.proto.proto.proto (即null)

常见误区和陷阱

误区1:混淆prototype和__proto__

function Person() {
}
const person = new Person();
// 错误理解
console.log(person.prototype);
// undefined,实例没有prototype属性
// 正确理解 
console.log(person.__proto__ === Person.prototype);
// true

误区2:直接修改__proto__

// 不推荐的做法
const obj = {
};
obj.__proto__ = Person.prototype;
// 推荐的做法
const obj = Object.create(Person.prototype);
// 或者
Object.setPrototypeOf(obj, Person.prototype);

误区3:认为所有对象都有prototype属性

const obj = {
};
console.log(obj.prototype);
// undefined,普通对象没有prototype
function func() {
}
console.log(func.prototype);
// {},只有函数才有prototype

实际应用场景

继承的实现

function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a sound');
};
function Dog(name) {
Animal.call(this, name);
}
// 实现继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + ' barks');
};
const dog = new Dog('旺财');
dog.speak();
// 旺财 makes a sound
dog.bark();
// 旺财 barks

判断对象类型

function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
function isFunction(obj) {
return typeof obj === 'function';
}
// 更现代的方法
console.log(Array.isArray([]));
// true
console.log(Array.isArray({
}));
// false

扩展内置对象(谨慎使用)

// 给所有数组添加自定义方法
Array.prototype.last = function() {
return this[this.length - 1];
};
const arr = [1, 2, 3, 4];
console.log(arr.last());
// 4
// 注意:修改内置对象原型在生产环境中要谨慎

现代JavaScript的替代方案

使用Class语法(ES6+)

class Animal
{
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name
} makes a sound`);
}
}
class Dog
extends Animal {
bark() {
console.log(`${this.name
} barks`);
}
}
const dog = new Dog('旺财');
dog.speak();
// 旺财 makes a sound
dog.bark();
// 旺财 barks

虽然class语法看起来更清爽,但底层实现还是基于原型链。

使用Object.create()

const animalMethods = {
speak() {
console.log(`${this.name
} makes a sound`);
}
};
function createAnimal(name) {
const animal = Object.create(animalMethods);
animal.name = name;
return animal;
}
const animal = createAnimal('小猫');
animal.speak();
// 小猫 makes a sound

面试常考问题

问题1:解释原型链

答案要点:

问题2:new操作符做了什么

function myNew(Constructor, ...args) {
// 1. 创建新对象
const obj = {
};
// 2. 设置原型链
Object.setPrototypeOf(obj, Constructor.prototype);
// 3. 绑定this并执行构造函数
const result = Constructor.apply(obj, args);
// 4. 返回对象
return (typeof result === 'object' && result !== null) ? result : obj;
}

问题3:instanceof的实现原理

function myInstanceof(left, right) {
let leftProto = Object.getPrototypeOf(left);
const rightPrototype = right.prototype;
while (leftProto !== null) {
if (leftProto === rightPrototype) {
return true;
}
leftProto = Object.getPrototypeOf(leftProto);
}
return false;
}

性能和最佳实践

性能考虑

  1. 避免深层原型链 - 查找属性时会影响性能
  2. 缓存属性访问 - 频繁访问的属性可以缓存到局部变量
  3. 使用hasOwnProperty - 避免查找原型链上的属性
const obj = { name: '张三'
};
// 好的做法
if (obj.hasOwnProperty('name')) {
console.log(obj.name);
}
// 更安全的做法
if (Object.prototype.hasOwnProperty.call(obj, 'name')) {
console.log(obj.name);
}

最佳实践

  1. 不要修改内置对象的原型 - 可能与其他代码冲突
  2. 使用Object.create(null)创建纯净对象 - 没有原型链的对象
  3. 优先使用组合而非继承 - 现代JavaScript推荐的模式
// 纯净对象,没有原型链
const pureObj = Object.create(null);
pureObj.name = '张三';
console.log(pureObj.toString);
// undefined
// 组合模式示例
function createLogger(config) {
return {
log(message) {
console.log(`[${config.level
}] ${message
}`);
}
};
}

调试技巧

当你需要调试原型链相关问题时,这些方法很有用:

function debugPrototypeChain(obj) {
let current = obj;
let depth = 0;
while (current !== null) {
console.log(`Level ${depth
}:`, current.constructor.name);
current = Object.getPrototypeOf(current);
depth++;
if (depth >
10) {
// 防止无限循环
console.log('Chain too deep, stopping...');
break;
}
}
}
function Person() {
}
const person = new Person();
debugPrototypeChain(person);
// Level 0: Person
// Level 1: Function 
// Level 2: Object

理解prototype和__proto__的关系是掌握JavaScript面向对象编程的关键。虽然现在有了class语法和其他现代特性,但原型链仍然是JavaScript的核心机制。掌握了这些概念,你就能更好地理解JavaScript的工作原理,写出更优雅的代码。

你在学习原型链的过程中遇到过什么困惑?或者有什么好的记忆方法?欢迎在评论区分享。

觉得这篇文章对你有帮助的话,记得点赞收藏,我会继续分享更多JavaScript深度技术内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/916363.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux中修改主机名并立即生效的完整指南

1. 查看当前主机名 在开始修改之前,先了解如何查看当前的主机名: # 查看当前主机名 hostname# 或使用hostnamectl命令(Systemd系统) hostnamectl status# 查看所有类型的主机名 hostnamectl status --all# 查看简短…

项目经理最常见的10个管理失误,你中招了吗?

做过项目的人都知道一句话:项目从来不是“顺理成章”完成的,而是一路踩坑、一路爬坑。 明明开工时信心满满,结果一到执行就乱了套:https://s.fanruan.com/rawyp延期 超支 质量差 团队闹情绪……最后搞得项目经理疲…

阿里云国际站NAS:阿里云NAS适合我的数据库备份需求吗? - 教程

阿里云国际站NAS:阿里云NAS适合我的数据库备份需求吗? - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…

沈阳 网站开发小程序开发外包报价表

3.5 损失函数 本文来自开源组织 DataWhale 🐳 CV小组创作的目标检测入门教程。 对应开源项目 《动手学CV-Pytorch》 的第3章的内容,教程中涉及的代码也可以在项目中找到,后续会持续更新更多的优质内容,欢迎⭐️。 如果使用我们…

公司新建了网站以前的就网站可以全部删除吗黄山旅游攻略及费用

在当今全球化的商业环境中,企业寻求海外市场的拓展已成为增长的重要策略。然而,海外市场的复杂性和多样性为企业带来了巨大的挑战。为了有效地定位和拓展海外客户,许多企业选择了HubSpot 出海CRM作为他们的营销和销售管理工具。今天运营坛将带…

02020407 EF Core基础07-一对多实体类关系配置插入数据查询数据、设置额外的外键字段

02020407 EF Core基础07-一对多实体类&关系配置&插入数据&查询数据、设置额外的外键字段 1. EF Core一对多关系配置(视频3-14) 1.1 实体间关系 1、所谓“关系数据库” 2、复习:数据库表之间的关系:一对…

解码数据结构基础

数据结构基本概念 数据结构的核心是计算机存储和组织数据的方式,目的是提升后续数据访问效率,存储的通常是具有特定关系的数据集合。 核心术语定义术语 定义 示例数据(Data) 可输入计算机并被处理的符号总称 学生信…

软件工程学习日志2025.9.24

📝 2025年9月24日 - Hadoop/HBase环境搭建与排错日记 🌟 今日工作概览 今天主要完成了Hadoop和HBase集群的环境搭建,解决了多个连接和配置问题,成功实现了从IDEA远程连接虚拟机HBase数据库。 🔧 技术操作记录H…

大厂代码编写习惯简谈

在当今软件开发行业,大厂的代码编写规范已经成为行业标杆。这些规范不仅仅是格式要求,更是一种工程思维的体现。简单探讨大厂代码规范的核心要点,帮助开发者提升代码质量与工程能力。 为什么需要严格的代码规范?当…

做网站什么用网页设计专业级

产业发展背景 早在2011年5月加拿大广播电视和电信委员会(CRTC)就发布了新的“国家宽带计划”,该计划显示,到2015年加拿大全体国民将享有5Mbps的宽带接入速度。CRTC表示:“来自市场的资金及有针对性的政府拨款将继续推动…

知识导航新体验:Perplexica+cpolar 24小时智能服务 - 教程

知识导航新体验:Perplexica+cpolar 24小时智能服务 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consol…

能不能写一个linux下类vim的编辑器 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

串口助手开发经验 - Luis-123

串口助手开发经验Posted on 2025-09-24 22:28 Luis-123-long 阅读(0) 评论(0) 收藏 举报1.在开发log功能的时候,发现点击发送数据后,没有反应(初始化陷阱) 经检查(很漫长的检查)后发现在最后面加了上次遗留的初…

泊头网站建设公司做网站都需要自己的服务器吗

FL Studio 21是一款强大的数字音频工作站(DAW)软件,为您提供一个完整的软件音乐制作环境。它是制作高质量的音乐、乐器、录音等的完整解决方案。该程序配备了各种工具和插件,帮助你创建专业的虚拟乐器,如贝斯、吉他、钢…

电商设计网站如何制作网站视频教程

web渗透测试漏洞复现 1. ZooKeeper未授权漏洞复现1.1 ZooKeeper简介1.2 ZooKeeper漏洞复现1.3 ZooKeeper漏洞修复建议1. ZooKeeper未授权漏洞复现 1.1 ZooKeeper简介 ZooKeeper 是一个分布式的、开源的协调服务,最初由雅虎开发,现隶属于 Apache 软件基金会,是Google的Chub…

《计算机算法设计与分析》系列--算法实现题1.1-统计数字问题

引言: 这个题在原书的配套习题解答中,描述得比较简略,我不太看得懂,于是按自己的思路做了一遍。 问题描述: 一本书有n页,页码为1,2,.. N,(注意,页码的格式,0不会在最前面) 问在这所有的页码中,0-9这10个数…

银河麒麟系统root密码重置

银河麒麟系统root密码重置 一、系统环境Kylin-Server-V10-SP3-2403-Release-20240426-x86_64二、系统密码重置 1.重启系统重启操作系统,出现如下界面后按e键,进入grub模式。2.默认grub账户密码系统需要输入grub账户密…

银河麒麟系统磁盘管理

银河麒麟系统磁盘管理 一、系统环境Kylin-Server-V10-SP3-2403-Release-20240426-x86_64.iso二、磁盘管理 1.添加物理磁盘系统识别磁盘,不重启系统重新扫描 SCSI 主机适配器识别到新连接的 SCSI 或 SATA 存储设备 ech…

浅谈傅里叶级数

我们可能都听说过傅里叶级数,但我们确切地知道它是什么吗?在这篇文章中,我将尝试逐一剖析这些概念。希望到最后,当你听到这些术语时,无论是在机器学习文献中还是与数学相关的内容中,你都能明白是怎么回事。 傅里…