JS 原型与原型链详解

JavaScript 原型与原型链详解

文章目录

  • JavaScript 原型与原型链详解
    • 一、基础概念类
      • 1.1 什么是原型?JavaScript 中如何访问一个对象的原型?
      • 1.2 构造函数、实例对象和原型对象之间的关系是什么?
      • 1.3 prototype 和 **proto** 的区别是什么?
    • 二、原型链机制类
      • 2.1 什么是原型链?描述原型链的查找机制
      • 2.2 代码示例分析
    • 三、构造函数与实例类
      • 3.1 new 操作符执行时发生了什么?
      • 3.2 手动实现 new 操作符
        • 3.2.1apply逻辑
        • 3.2.2`result instanceof Object ? result : obj;`解决的问题
    • 四、继承与原型链类
      • 4.1 JS如何实现继承?
    • 原型链继承
    • 五、高级应用类
      • 5.1 如何修改内置对象(如 Array)的原型?这样做有什么风险?
        • 5.1.1修改内置原型
        • 5.1.2创建子类继承内置对象(ES6 Class方式)
        • 5.1.3使用Object.create创建原型链继承
      • 5.2 如何判断一个属性是对象自身的还是继承自原型链的?
    • 六、总结

一、基础概念类

1.1 什么是原型?JavaScript 中如何访问一个对象的原型?

**原型(Prototype)**是 JavaScript 实现继承的基础机制。每个 JavaScript 对象都有一个内部属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf() 访问),它指向该对象的原型对象。原型对象本身也是一个普通对象,同样拥有自己的原型,这样就形成了原型链。

访问对象原型的方式

  • 对于构造函数:通过 Constructor.prototype 访问
  • 对于实例对象:
    • obj.__proto__(非标准但广泛支持)
    • Object.getPrototypeOf(obj)(标准方法)
function Person() {}
const p = new Person();// 访问构造函数的原型
console.log(Person.prototype); // 访问实例的原型
console.log(p.__proto__); 
console.log(Object.getPrototypeOf(p)); 

1.2 构造函数、实例对象和原型对象之间的关系是什么?

三者关系可概括为:

  1. 构造函数:用于创建对象的函数,拥有 prototype 属性
  2. 原型对象:通过 Constructor.prototype 访问,包含共享属性和方法
  3. 实例对象:通过 new Constructor() 创建,其 __proto__ 指向构造函数的原型

关系图示:

构造函数 (Person)├── prototype (原型对象)│     ├── constructor (指回构造函数)│     └── 共享属性和方法└── 实例化└── 实例对象 (p)└── __proto__ (指向原型对象)

关键点:

  • 构造函数的 prototype 属性指向原型对象
  • 原型对象的 constructor 属性指回构造函数
  • 实例对象的 __proto__ 指向构造函数的原型对象

1.3 prototype 和 proto 的区别是什么?

特性prototype__proto__
所属对象函数对象(包括函数)
作用为构造函数定义共享属性和方法指向对象的原型,形成原型链
是否标准否(非标准但广泛支持)
访问方式Constructor.prototypeobj.__proto__Object.getPrototypeOf(obj)
用途场景实现继承和共享方法属性查找机制

关键区别

  • prototype 是函数特有的属性,用于实现基于原型的继承
  • __proto__ 是对象实例的属性,指向其构造函数的原型对象

二、原型链机制类

2.1 什么是原型链?描述原型链的查找机制

原型链是由对象的 __proto__ 链接形成的链条结构,它允许对象访问其原型上的属性和方法,直到 Object.prototype.__proto__(值为 null)为止。

原型链查找机制

  1. 访问对象属性时,首先在对象自身查找
  2. 如果自身不存在该属性,则通过 __proto__ 查找其原型对象
  3. 继续沿原型链向上查找,直到找到属性或到达原型链顶端(null
  4. 如果最终未找到,则返回 undefined

这种机制实现了 JavaScript 的继承和属性共享。

2.2 代码示例分析

function Person() {}
Person.prototype.name = "Alice";let p = new Person();
console.log(p.name); // "Alice"
console.log(p.hasOwnProperty("name")); // false

执行过程解析

  1. p.name 访问时:
    • 首先在 p 对象自身查找 name 属性 → 未找到
    • 通过 p.__proto__ 查找 Person.prototype → 找到 name: "Alice"
    • 返回 "Alice"
  2. p.hasOwnProperty("name")
    • hasOwnProperty 是检查属性是否为对象自身的属性
    • name 实际存在于 Person.prototype 上,而非 p 对象自身
    • 返回 false

三、构造函数与实例类

3.1 new 操作符执行时发生了什么?

new 操作符创建实例时,背后执行了以下步骤:

  1. 创建一个新的空对象 {}
  2. 将新对象的 __proto__ 指向构造函数的 prototype 属性
  3. 将构造函数的 this 绑定到新对象,并执行构造函数
  4. 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象

完整过程示例

function Person(name) {this.name = name;
}const p = new Person("Alice");

等价于:

function myNew(Constructor, ...args) {// 1. 创建空对象let obj = {};// 2. 设置原型链obj.__proto__ = Constructor.prototype;// 3. 绑定this并执行构造函数let result = Constructor.apply(obj, args);// 4. 返回结果(优先返回对象,否则返回新对象)return result instanceof Object ? result : obj;
}const p = myNew(Person, "Alice");

3.2 手动实现 new 操作符

function myNew(Constructor, ...args) {// 1. 创建一个新对象,并将其原型指向构造函数的prototypelet obj = Object.create(Constructor.prototype);// 2. 调用构造函数,将this绑定到新对象,args是一个数组let result = Constructor.apply(obj, args);// 3. 如果构造函数返回了一个对象,则返回该对象;否则返回新对象return result instanceof Object ? result : obj;
}
3.2.1apply逻辑

apply(thisArg,argsArray) 接收 两个参数

  1. thisArg(必需):函数运行时绑定的 this 值。
  2. argsArray(可选):一个数组或类数组对象,包含传递给函数的参数列表。如果省略或为 null/undefined,则相当于传递空数组。

argsArray 的核心作用是解耦 myNew 和构造函数 Constructor 之间的参数关系

  1. myNew 不需要关心具体有多少参数,只需将所有额外参数打包到 args 数组中。
  2. Constructor 可以自由定义自己需要的参数,通过 this 接收并处理。
3.2.2result instanceof Object ? result : obj;解决的问题

作用:处理构造函数 Constructor 的返回值,确保最终返回的对象符合预期

在 JavaScript 中,构造函数(通过 new 调用的函数)可以显式返回一个值。这个返回值可以是:

  1. 一个对象(包括数组、函数、普通对象等)。
  2. 一个原始值(如 numberstringbooleannullundefined)。

如果构造函数返回一个对象,new 操作符会忽略默认创建的实例对象,直接返回这个指定的对象;如果返回原始值,则忽略返回值,仍然返回默认创建的实例对象。

示例:构造函数返回对象 vs 原始值
// 情况1:构造函数返回对象
function Person1() {return { name: "Bob" }; // 返回一个新对象
}
const p1 = new Person1();
console.log(p1); // { name: "Bob" }(不是 Person1 的实例)// 情况2:构造函数返回原始值
function Person2() {return 123; // 返回原始值
}
const p2 = new Person2();
console.log(p2); // Person2 的实例(不是 123)

四、继承与原型链类

4.1 JS如何实现继承?

原型链继承

核心:将子类的原型指向父类的实例

JavaScript 中通过原型链继承实现继承的基本模式:

function Parent() {this.parentProperty = "Parent Value";
}Parent.prototype.parentMethod = function() {console.log("Parent Method");
};function Child() {this.childProperty = "Child Value";
}// 关键步骤:将Child的原型指向Parent的实例
Child.prototype = new Parent();const c = new Child();
console.log(c.parentProperty); // "Parent Value"
c.parentMethod(); // "Parent Method"

继承关系图示

Child实例 (c)├── __proto__ → Child.prototype (Parent的实例)├── __proto__ → Parent.prototype├── constructor → Parent└── parentMethod└── childProperty (来自Child构造函数)

注意:这种继承方式存在一些问题(如引用类型共享、无法向父类构造函数传参等),现代开发更推荐使用 ES6 的 classextends 语法。

五、高级应用类

5.1 如何修改内置对象(如 Array)的原型?这样做有什么风险?

5.1.1修改内置原型

修改方式示例

// 添加自定义方法到Array原型
Array.prototype.customMethod = function() {console.log("This is a custom method");
};const arr = [1, 2, 3];
arr.customMethod(); // "This is a custom method"

潜在风险

  1. 全局污染:修改内置原型会影响所有使用该内置对象的代码
  2. 命名冲突:可能与未来 JavaScript 版本新增的方法名冲突
  3. 兼容性问题:可能导致与其他库或框架的不可预期交互
  4. 维护困难:使代码行为变得不可预测,增加调试难度

最佳实践

  • 避免直接修改内置原型
  • 如需扩展功能,考虑使用工具函数或创建子类
  • 如果必须修改,添加前缀以减少冲突风险(如 myCustomMethod
5.1.2创建子类继承内置对象(ES6 Class方式)

这是最推荐的方式,既扩展了功能,又不会影响原生对象。

1. 基础继承示例

// 创建继承自Array的自定义类
class CustomArray extends Array {// 添加自定义方法customSum() {return this.reduce((acc, val) => acc + val, 0);}// 可以添加更多自定义方法customMax() {return Math.max(...this);}
}// 使用示例
const arr = new CustomArray(1, 2, 3, 4);
console.log(arr.customSum()); // 输出: 10
console.log(arr.customMax()); // 输出: 4
console.log(arr instanceof Array); // true (仍然属于Array类型)
5.1.3使用Object.create创建原型链继承

更底层的实现方式,不使用ES6 class语法。

// 创建基于Array的新对象
const EnhancedArray = function(...items) {const arr = [...items];return Object.setPrototypeOf(arr, EnhancedArray.prototype);
};// 设置原型链
EnhancedArray.prototype = Object.create(Array.prototype);
EnhancedArray.prototype.constructor = EnhancedArray;// 添加自定义方法
EnhancedArray.prototype.customSum = function() {return this.reduce((acc, val) => acc + val, 0);
};// 使用示例
const arr = EnhancedArray(1, 2, 3);
console.log(arr.customSum()); // 输出: 6
console.log(arr instanceof Array); // true

5.2 如何判断一个属性是对象自身的还是继承自原型链的?

判断方法

  1. hasOwnProperty() 方法:

    • 只检查对象自身的属性,不检查原型链
    const obj = { a: 1 };
    console.log(obj.hasOwnProperty('a')); // true
    console.log(obj.hasOwnProperty('toString')); // false
    
  2. Object.getOwnPropertyNames()

    • 返回对象自身的所有属性名(不包括原型链)
    const obj = { a: 1, b: 2 };
    console.log(Object.getOwnPropertyNames(obj)); // ["a", "b"]
    
  3. in 操作符:

    • 检查属性是否存在于对象或其原型链中
    const obj = { a: 1 };
    console.log('a' in obj); // true
    console.log('toString' in obj); // true
    

综合示例

function checkProperty(obj, prop) {if (obj.hasOwnProperty(prop)) {console.log(`${prop} 是对象自身的属性`);} else if (prop in obj) {console.log(`${prop} 是继承自原型链的属性`);} else {console.log(`${prop} 不是对象的属性`);}
}const obj = { a: 1 };
checkProperty(obj, 'a'); // "a 是对象自身的属性"
checkProperty(obj, 'toString'); // "toString 是继承自原型链的属性"
checkProperty(obj, 'b'); // "b 不是对象的属性"

六、总结

JavaScript 的原型和原型链机制是其面向对象编程的核心,理解这些概念对于掌握 JavaScript 至关重要:

  1. 原型是对象间共享属性和方法的机制
  2. 原型链实现了属性的查找和继承
  3. 构造函数、实例和原型三者构成了 JavaScript 对象系统的基础
  4. new 操作符通过特定步骤创建实例并建立原型链
  5. 原型链继承是 JavaScript 实现继承的主要方式
  6. 谨慎操作内置原型,避免潜在风险
  7. 准确判断属性来源对调试和维护非常重要

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

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

相关文章

DEVICENET转MODBUS TCP网关连接DeviceNet数字远程IO模块配置案例

设备与网络架构,主控设备:支持Modbus TCP协议的PLC(如西门子S7-1200)。网关设备:开疆智能Modbus TCP转DeviceNet网关KJ-DVCZ-MTCPS(需支持DeviceNet从站功能)。目标设备:DeviceNet数…

Ubuntu下使用PyTurboJPEG加速图像编解码

目录 一、概述 二、安装PyTurboJPEG 三、测试 一、概述 在计算机视觉领域,图像编解码是绕不开的基础环节。虽然 OpenCV 能解决大部分图像处理问题,但在性能要求严苛的场景下存在短板。本文将介绍基于 libjpeg-turbo 的高效 JPEG 编解码库 PyTurboJPE…

MCU、MPU、GPU、Soc、DSP、FPGA、CPLD……它们到底是什么?

MCU、MPU、GPU、Soc、DSP、FPGA、CPLD…… 这些简称在各大论坛、会议、发布会中屡见不鲜,看到简称,虽然也能说出大概; 但要问具体是什么?用在什么场景?又有什么区别……好像还是差点意思;本篇文章就记录一…

Django RBAC项目后端实战 - 03 DRF权限控制实现

项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…

[网页五子棋][用户模块]数据库设计和配置(MyBatis)、约定前后端交互接口、服务器开发

文章目录 数据库数据库设计配置 MyBatis1. Spring 配置2. 创建实体类3. 创建 Mapper 接口4. 使用 MyBatis 约定前后端交互接口登录接口注册接口获取用户信息 服务器开发loginregistergetUserInfo完整代码 数据库 数据库设计 完成注册登录以及用户分数管理 使用数据库来保存上…

Qt/C++学习系列之列表使用记录

Qt/C学习系列之列表使用记录 前言列表的初始化界面初始化设置名称获取简单设置 单元格存储总结 前言 列表的使用主要基于QTableWidget控件,同步使用QTableWidgetItem进行单元格的设置,最后可以使用QAxObject进行单元格的数据读出将数据进行存储。接下来…

防火墙通常可以分为哪些类型?

防火墙是目前保护网络安全的重要设备,能够通过监控、过滤和控制进出网络的数据流量,来保护内部网络不会受到未经授权的IP地址进行访问和恶意的网络威胁,设置防火墙能够帮助企业确保网络的安全性,同时防火墙也会根据不同的功能来划…

基于GeoTools的道路相交多个点容差冗余计算实战

目录 前言 一、关于道路相交 1、相交四个点 2、点更多的情况 二、基于距离的相交点去重 1、冗余距离计算 2、调用过程 3、去重后的结果 三、总结 前言 在地理信息系统(GIS)领域,道路网络数据的处理与分析一直是关键课题。随着城市化进…

android:foregroundServiceType详解

在 Android 中,foregroundServiceType 是用于声明前台服务类型的属性,主要从 Android 10(API 29)开始引入,并在 Android 11(API 30)及更高版本 中进一步细化。以下是所有可用的 foregroundServi…

React+Taro 微信小程序做一个页面,背景图需贴手机屏幕最上边覆盖展示

话不多说 直接上图 第一步 import { getSystemInfoSync } from tarojs/taro;第二步 render() {const cardBanner getImageUrlByGlobal(member-merge-bg.png);const { safeArea, statusBarHeight } getSystemInfoSync();const NAV_BAR_HEIGHT 44;const navBarHeight NAV…

从零开始的云计算生活——番外,实战脚本。

目录 题目一:系统信息收集脚本 题目二:用户管理配置脚本 题目三:磁盘空间管理脚本 题目四:网络配置检查脚本 题目五:系统日志分析脚本 题目一:系统信息收集脚本 编写一个脚本名为 collect_system_info…

MySQL基础知识(DDL、DML)

什么是数据库? 数据库:英文为 DataBase,简称DB,它是存储和管理数据的仓库。 注释: 单行注释:-- 注释内容 或 # 注释内容(MySQL特有)多行注释: /* 注释内容 */ 分类 SQL语句根据其功能被分为…

用volatile修饰数组代表什么意思,Java

文章目录 volatile 修饰数组引用的含义volatile 对数组元素无效总结 如何让数组元素也具有 volatile 特性? 当用 volatile 关键字修饰一个数组时,它只保证数组引用的可见性和部分原子性,而不保证数组元素的可见性和原子性。 换句话说&#x…

Ubuntu 24.04 LTS 长期支持版发布:对服务器用户意味着什么?新特性、升级建议与性能影响初探

更多云服务器知识,尽在hostol.com 在服务器运维的广阔世界里,每一次主流操作系统长期支持(LTS)版本的发布,都无异于一次重要的“时代交替”。它不仅带来了一系列令人瞩目的技术革新,更重要的是&#xff0c…

题目 3241: 蓝桥杯2024年第十五届省赛真题-挖矿

题目 3241: 蓝桥杯2024年第十五届省赛真题-挖矿 时间限制: 3s 内存限制: 512MB 提交: 1267 解决: 224 题目描述 小蓝正在数轴上挖矿,数轴上一共有 n 个矿洞,第 i 个矿洞的坐标为 ai 。小蓝从 0 出发,每次可以向左或向右移动 1 的距离&#xf…

vue3+ts+vite创建的后台管理系统笔记

Vue3+ Vite + Element-Plus + TypeScript 从0到1搭建企业级后台管理系统(前后端开源):参考有来科技学习搭建项目 创建项目bug汇总,知识点src 路径别名配置和tsconfig.json文件报错【这个不配置好,会引起其他页面引用时报错:见--整合 Pinia】:整合 Pinia 【参考-- src 路径…

指针01 day13

十三:指针变量 一:数据类型 ​ 指针类型---------对应处理的数据是指针 (地址)这种数据 ​ 整型类型---------对应处理的数据是整数这种类型 二:定义指针类型的变量 ​ 语法: 基类型(1) *(…

基于深度学习的智能文本生成:从模型到应用

前言 随着人工智能技术的飞速发展,自然语言处理(NLP)领域取得了显著的进展。其中,智能文本生成技术尤其引人注目。从聊天机器人到内容创作,智能文本生成不仅能够提高效率,还能创造出令人惊叹的内容。本文将…

Oracle业务用户的存储过程个数及行数统计

Oracle业务用户的存储过程个数及行数统计 统计所有业务用户存储过程的个数独立定义的存储过程定义在包里的存储过程统计所有业务用户存储过程的总行数独立定义的存储过程定义在包里的存储过程通过DBA_SOURCE统计类型个数和代码行数📖 对存储过程进行统计主要用到以下三个系统…

多线程安全:核心解决方案全解析

在多线程环境下保证共享变量的线程安全,需解决原子性、可见性、有序性三大问题。以下是核心解决方案及适用场景: 一、同步锁机制(互斥访问) synchronized 关键字 原理:通过 JVM 监视器锁(Monitor)确保同一时间仅一个线程访问临界区。示例:public class Counter {privat…