JavaScript简餐——代理Proxy与反射(一)
2021/11/7 1:10:53
本文主要是介绍JavaScript简餐——代理Proxy与反射(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 前言
- 一、代理Proxy基础
- 二、定义捕获器
- 三、捕获器参数和反射API
- 四、捕获器不变式
- 五、总结
前言
写本《JavaScript简餐》系列文章的目的是记录在阅读学习《JavaScript高级程序设计(第4版)》一书时出现的各个知识点。虽是对读书的笔记和总结,但是希望它轻量、简洁、犀利,不会引起阅读疲劳,可以在碎片化时间和闲暇之余轻巧地沐浴一下知识点。每篇文章只针对一个小部分进行讲解式的梳理,来达到个人复习总结和分享知识的目的。
ECMAScript6中新增了代理与反射,让我们可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
一、代理Proxy基础
代理是目标对象的抽象。从很多方面看,代理类似C++中的指针,因为它可以用作目标对象的替身,但又完全独立于目标对象。目标对象既可以直接被操作,也可以通过代理来操作。说句人话:代理就是对象的分身,在分身上的所有操作都会影响到“本体”。要创建代理,最简单的就是创建空代理,即除了作为一个对象的抽象什么也不做。代理是使用Proxy构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象,二者缺一不可。话不多说直接上代码:const person = { name: 'Lucy' }; const handler = {}; const proxy = new Proxy(person, handler); // (目标对象, 处理程序对象) console.log(person.name); // Lucy console.log(proxy.name); // Lucy person.name = 'Jack'; // 改变person对象(目标对象)上的name属性那么在proxy上也会改变 console.log(person.name); // Jack console.log(proxy.name); // Jack console.log(person === proxy) // false
二、定义捕获器
使用代理的主要目的是可以定义捕获器。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。还是直接上代码来直观理解一下。这里我们在刚刚例子中的handler对象中定义一个get()捕获器。const person = { name: 'Lucy' }; const handler = { get() { return '捕获器触发!' } }; const proxy = new Proxy(person, handler); // (目标对象, 处理程序对象)这样,当通过代理对象执行get()操作时,就会触发定义的get()捕获器。所有这些操作只要发生在代理对象上,就会触发get()捕获器。注意,只有在代理对象上执行这些操作才会触发捕获器。在目标对象执行这些操作仍然会产生正常的行为。我们做一下试验:
console.log(person.name); // Lucy console.log(proxy.name); // 捕获器触发!果不其然,在代理对象上执行get操作时捕获器触发了!
三、捕获器参数和反射API
所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。比如,get()捕获器会接收到目标对象、要查询的属性和代理对象这三个参数:const person = { name: 'Lucy' }; const handler = { get(trapTarget, property, receiver) { console.log(trapTarget === person); console.log(property); console.log(receiver === proxy); } }; const proxy = new Proxy(person, handler); // (目标对象, 处理程序对象) proxy.name; // true // name // true有了这些参数,就可以重建被捕获方法的原始行为:
const person = { name: 'Lucy' }; const handler = { get(trapTarget, property, receiver) { return trapTarget[property]; } }; const proxy = new Proxy(person, handler); // (目标对象, 处理程序对象) console.log(person.name); // Lucy console.log(proxy.name); // Lucy虽然我们可以自己手动重建原始行为,但是完全可以没这个必要,因为比get复杂的操作还有很多。为此,我们可以通过调用全局Reflect对象上的同名方法来轻松重建。处理程序对象中所有可以捕获的方法都有对应的反射API方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。因此,使用反射API也可以像下面这样定义出空代理对象:
const person = { name: 'Lucy' }; const handler = { get() { return Reflect.get(...arguments); } }; const proxy = new Proxy(person, handler); // (目标对象, 处理程序对象) console.log(person.name); // Lucy console.log(proxy.name); // Lucy如果真的想创建一个可以捕获所有方法,然后将每个方法转发给对应反射API的空代理,那么甚至不需要定义处理程序对象:
const person = { name: 'Lucy' }; const proxy = new Proxy(person, Reflect); // (目标对象, 处理程序对象) console.log(person.name); // Lucy console.log(proxy.name); // Lucy
四、捕获器不变式
使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制的。根据ECMAScript规范,每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵守“捕获器不变式”,这个规则通常会防止捕获器定义中出现过于反常的行为。比如,如果目标对象有一个不可配置且不可写的属性,那么在捕获器返回一个与该属性不同的值时会抛出TypeError。代码如下:const person = {}; Object.defineProperty(person, 'name', { configurable: false, writable: false, value: 'Lucy' }); const handler = { get() { return 'Jack'; } }; const proxy = new Proxy(person, handler); console.log(proxy.name); // TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'Lucy' but got 'Jack')
五、总结
以上就是今天要讲的内容,今天简单地介绍了一下proxy基础、代理的创建方式、捕获器的定义、捕获器的作用及其用法、反射API和捕获器不变式。下一篇我们来继续深入一下代理Proxy与反射。撒花~这篇关于JavaScript简餐——代理Proxy与反射(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-28MQ底层原理资料详解:新手入门教程
- 2024-11-28MQ项目开发资料详解:新手入门教程
- 2024-11-28MQ项目开发资料详解:入门与初级用户指南
- 2024-11-28MQ消息队列资料入门教程
- 2024-11-28MQ消息队列资料:新手入门详解
- 2024-11-28MQ消息中间件资料详解与应用教程
- 2024-11-28MQ消息中间件资料入门教程
- 2024-11-28MQ源码资料详解与入门教程
- 2024-11-28MQ源码资料入门教程
- 2024-11-28RocketMQ底层原理资料详解