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掉线重连后ÿ…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...
