ES6代理和反射新特性,详细讲解
代理与反射
es6新增了代理和反射特性,这两个特性为开发者提供了拦截并向基本操作嵌入额外行为的能力。
代理基础
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Title</title></head><body><h2>代理与反射</h2></body><script>const target = {id: 'target'}const handle = {}const proxy = new Proxy(target,handle)console.log(proxy.id)console.log(target.id)</script>
</html>
代理是目标对象的抽象。 所以直接操作代理和直接操作对象,所操作的值都会映射到代理对象上。
代理对象每次执行某个操作(读属性、写属性、定义新属性、查询原型、把它作为函数调用)时,它只会把相应操作发送给处理器对象或目标对象。
Proxy构造器接收两个参数,目标对象和处理器对象。如果处理器对象上存在对应方法,代理就调用该方法执行相应操作。如果处理器对象上不存在对应方法,则代理就在目标对象上执行基础操作。

定义捕获器
使用代理的主要目的是可以定义捕获器。当定义捕获器后,在代理对象上调用基本操作时(比如新增、删除、修改),代理可以在运行这些操作之前调用捕获器,从而拦截并修改相应的行为。
下面看一个例子
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Title</title>
</head>
<body><h2>代理与反射</h2>
</body>
<script>const target = {id: 'target'}const handle = {get(){console.log('捕获器运行')return '这是一个捕获器'}}const proxy = new Proxy(target,handle)console.log(proxy.id)console.log(target.id)
</script>
</html>

在访问代理对象的属性时,会出发get操作(捕获器函数),但是在原始对象上操作是不会触发的。否则就应该打印两次了。
捕获器函数可以接收三个参数,分别是目标对象,要查询的属性以及代理对象。
那如果我们想在捕获函数内部调用原始对象上的行为呢?Reflect对象为我们提供了这个能力。
下面看一个例子
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Title</title>
</head>
<body><h2>代理与反射</h2>
</body>
<script>const target = {foo: 'bar'}const handle = {get(){return Reflect.get(...arguments)}}const proxy = new Proxy(target,handle)console.log(proxy.foo)console.log(target.foo)
</script>
</html>

可以看到值是一样的,那么我们利用这个属性就可以在操作对象时给对象的属性添加一些额外的东西,比如
const target = {foo: 'bar'
}
const handle = {get(){return Reflect.get(...arguments) + '帅'}
}
const proxy = new Proxy(target,handle)
console.log(proxy.foo)
console.log(target.foo)

捕获器的一些限制
在使用捕获器时,需要遵守"捕获器不变式"。原因是因为:如果一个捕获器不遵守捕获器不变式,可能会出现过于反常的行为! 下面来看一个例子:
如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出TypeError
const target = {}
Object.defineProperty(target,'foo',{// 不可配置configurable: false,// 不可写writable: false,value: 'bar'
})
const handle = {get(){return Reflect.get(...arguments) + '帅'}
}
const proxy = new Proxy(target,handle)
console.log(proxy.foo)
console.log(target.foo)

那捕获器函数返回一个一样的值呢?

实测返回一样的值不会报错。
撤销代理
很简单,调用一个revocable方法。有想要了解具体的可以看看MDN的介绍。
Proxy.revocable() - JavaScript | MDN
看一段代码就知道怎么使用了
const target = {}
Object.defineProperty(target,'foo',{// 不可配置// configurable: false,// // 不可写// writable: false,value: 'bar'
})
const handle = {get(){console.log('运行')return Reflect.get(...arguments)}
}
const proxy = new Proxy(target,handle)
console.log(proxy.foo)
console.log(target.foo)
const {proxy:proxy1,revoke} = Proxy.revocable(target,handle)
proxy1.foo
revoke()
proxy1.foo

代理的不足
-
this指向问题
-
代理与内部槽位
-
- 有些js内置类型可能会依赖内部槽位进行一些方法的调用,而代理对象是无法访问到这些方法的,就会导致出错。
const target = new Date()
const proxy = new Proxy(target,{})
console.log(proxy instanceof Date)
proxy.getDate()
输出结果:

反射
Reflect对象是一个内置的对象,不可以使用new运算符进行实例化,它可以用自己的方法代替原生的操作对象。就是有一个人和你一模一样,你可以不用亲自做一些事情,这个人就帮你做了。
来看一下例子:
const target = {foo: 'bar'
}
delete target.foo
console.log(target)
这是我们如果想要删除对象上的某个属性,采取的一种做法。
来看下反射如何做
const target = {foo: 'bar'
}
Reflect.deleteProperty(target,'foo')
console.log(target)

在学习过程中我是有一个疑惑的,为什么JS会出现反射呢? 其实它是为了跟Proxy进行配合。
Proxy可以代理对象的一些操作,比如增加、删除、修改。想一下这个场景:如果Proxy代理了新增操作,然后最后想调用原生的赋值方法,该怎么做?
- 自己手动实现一个。
const target = {foo: 'bar'
}
let proxy1 = new Proxy(target,{get(target, p, receiver) {console.log('当前值为::',target[p])return target[p]}
})
console.log(proxy1.foo)

- 使用反射
const target = {foo: 'bar'
}
let proxy1 = new Proxy(target,{get(target, p, receiver) {console.log('当前值为::',target[p])return Reflect.get(target,p,receiver)}
})
console.log(proxy1.foo)

我们无需关心原来操作的实现逻辑,只需要关心我们要为某个操作添加的逻辑即可,原来的逻辑反射会替我们做到。
所以反射的Api是和Proxy捕获器的api一一对应的,当我们想要调用原生操作时,直接使用反射提供的api即可。
总结
从宏观来看,代理是真实JS对象的透明抽象层。在遵循捕获器不变式的前提下,代理可以定义捕获器,可以拦截大部分JS的基本操作和方法。从而增强这些操作。 例如实现装饰器。
反射则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射API看作一套基本操作,包含绝大部分JS对象的API基础。
相关文章:
ES6代理和反射新特性,详细讲解
代理与反射 es6新增了代理和反射特性,这两个特性为开发者提供了拦截并向基本操作嵌入额外行为的能力。 代理基础 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta charset"UTF-8"&g…...
vue计算属性 初步使用案例
<template><div><h1>购物车</h1><div v-for"item in filteredItems" :key"item.id"><p>{{ item.name }} - {{ item.price }} 元</p><input type"number" v-model.number"item.quantity"…...
大模型时代,呼叫中心部门如何建设一套呼出机器人系统?
大模型时代,呼叫中心部门如何建设一套呼出机器人系统? 作者:开源呼叫中心系统 FreeIPCC,Github地址:https://github.com/lihaiya/freeipcc 在大模型时代,呼叫中心部门建设一套呼出机器人系统需要综合考虑技…...
使用Java绘制图片边框,解决微信小程序map组件中marker与label层级关系问题,label增加外边框后显示不能置与marker上面
今天上线的时候发现系统不同显示好像不一样,苹果手机打开的时候是正常的,但是一旦用安卓手机打开就会出现label不置顶的情况。尝试了很多种办法,也在官方查看了map相关的文档,发现并没有给label设置zIndex的属性,只看到…...
力扣 LeetCode 142. 环形链表II(Day2:链表)
解题思路: 使用set判断是否重复添加,如果set加入不进去证明之前到达过该节点,有环 public class Solution {public ListNode detectCycle(ListNode head) {Set<ListNode> set new HashSet<>();ListNode cur head;while (cur …...
用MVVM设计模式提升WPF开发体验:分层架构与绑定实例解析
MVVM(Model-View-ViewModel)是一种架构模式,广泛应用于现代前端开发,尤其是在微软的WPF(Windows Presentation Foundation)应用程序中。它旨在通过将视图(UI)与业务逻辑(…...
C++中的动态断言和静态断言
C中包含动态断言(assert)和静态断言(static_assert),下面分别分析各自的用法。 1.动态断言(assert) assert 是一个宏,在预处理阶段不生效,在运行阶段才起作用࿰…...
运算放大器的学习(一)输入阻抗
输入阻抗 最近需要对运算放大器进行学习,我们后面逐一对其参数进行了解。 首先了解下输入阻抗。 放大电路技术指标测试示意图: 输入电阻: 从放大电路的输入端看进去的等效电阻称为放大电路的输入电阻,如上图,此处考虑…...
Rust枚举之卧龙凤雏(Rust Option枚举、Rust Result枚举)(Rust Enum、Some(T)、Ok(T)、Err(E))链式操作
文章目录 Rust 枚举之卧龙凤雏枚举的基本概念枚举定义示例 Result 枚举:凤雏Result 枚举的定义Result 的使用场景示例 1:文件读取示例 2:链式操作与错误处理 Option 枚举:卧龙Option 枚举的定义Option 的使用场景示例 1࿱…...
TCP/IP协议,TCP和UDP区别
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/互联网协议)是一组用于计算机网络中的通信协议,它为数据传输提供了标准框架,广泛用于互联网和局域网中。TCP/IP协议包括多个层次,每…...
【go从零单排】Timer、Epoch 时间函数
🌈Don’t worry , just coding! 内耗与overthinking只会削弱你的精力,虚度你的光阴,每天迈出一小步,回头时发现已经走了很远。 📗概念 在 Go 语言中,time.Timer 是一个用于在指定时间后执行操作的计时器。…...
壁仞科技上市前最后一波 校招 社招 内推
随着美国大选结束,国内GPU 产业得到空前的的发展空间,国内芯片相关股票一片飘红。 国内大型 GPU厂商壁仞科技,摩尔线程等正紧锣密鼓地加紧上市。 GPGPU 芯片赛道来到了史无前例的红利点,抓住机会💪 壁仞科技正在火热…...
【微软报告:多模态基础模型】(2)视觉理解
欢迎关注【youcans的AGI学习笔记】原创作品 【微软报告:多模态基础模型】(1)从专家到通用助手 【微软报告:多模态基础模型】(2)视觉理解 【微软报告:多模态基础模型】(3)…...
Linux 驱动
四十三、Linux设备树 43.1 DTS、DTB 和 DTC DTS 是设备树源码文件 DTB 是将DTS 编译以后得到的二进制文件。 DTC 工具将.dts 编译为.dtb 43.2 DTS语法 43.2.1 .dtsi 头文件 在.dts 设备树文件中,可以通过“#include”来引用.h、.dtsi 和.dts 文件。 …...
【数学二】线性代数-线性方程组-齐次线性方程组、非齐次线性方程组
考试要求 1、会用克拉默法则. 2、理解齐次线性方程组有非零解的充分必要条件及非齐次线性方程组有解的充分必要条件. 3、理解齐次线性方程组的基础解系及通解的概念,掌握齐次线性方程组基础解系和通解的求法. 4、理解非齐次线性方程组的解的结构及通解的概念. 5、会用初等行变…...
Git别名设置
在 Git 中设置命令别名可以让你更高效地使用常见的 Git 命令。通过为常用命令创建简短的别名,可以减少输入的字符数并加速工作流程。 参考链接 设置 Git 命令别名的方法: 使用 Git 配置命令: Git 允许通过 git config 命令来设置命令别名。这…...
算法基础 -- 红黑树原理与插入伪代码
红黑树原理与插入伪代码 红黑树的原理 红黑树是一种自平衡的二叉搜索树,通过对节点的颜色(红色或黑色)以及结构的约束条件来保持树的平衡。红黑树的原理可以通过以下五个特性描述: 节点是红色或黑色。根节点必须是黑色。所有叶…...
力扣 LeetCode 27. 移除元素(Day1:数组)
解题思路: 注意:数组只能覆盖,不能删除 erase方法的复杂度为O( n )而不是O( 1 ),因为需要把删除后后面的数组向前移动 方法一:双层for循环暴力 方法二:快慢指针 fast表示新数组的元素 slow表示新数组元…...
微服务链路追踪skywalking安装
SkyWalking是一个开源的分布式追踪系统,主要用于监控和分析微服务架构下的应用性能。 它提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案,特别适用于微服务、云原生架构和基于容器的环境(如Docker、K8s、Mesos&…...
mqtt学习笔记(一)
以解决问题方式逐步学习探索 mqtt使用场景mqtt可能缺点mqtt学习疑问探索1、mqtt主题发布过的历史消息,全新连接的client能消费到吗?2、mqtt的client掉线如何重连,重连后订阅的topic配置还在不?3、mqtt的client掉线重连后ÿ…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
