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与反射(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程