第九章 代理与反射 (下) 代理捕获器与反射方法、代理模式

news/2025/2/13 2:24:28/

9.2 代理捕获器与反射方法

        代理可以捕获13种不同的基本操作。这些操作有各自不同的反射API方法、参数、关联ECMAScript操作和不变式。

        正如前面示例所展示的,有几种不同的JavaScript操作会调用同一个捕获器处理程序。不过,对于在代理对象上执行的任何一种操作,只会有一个捕获处理程序被调用。不会存在重复捕获的情况。

        只要在代理上调用,所有捕获器都会拦截它们对应的反射API操作。

9.2.1 get()

        get()捕获器会在获取属性值的操作中被调用。对应的反射API方法为Reflect.get()。

const myTarget = {};const proxy = new Proxy(myTarget, {get(target, property, receiver) {console.log('get()');return Reflect.get(...arguments)}
});proxy.foo;
// get()
1、返回值

        返回值无限制。

2、拦截的操作
  • proxy.property
  • proxy[property]
  • Object.create(proxy)[property]
  • Reflect.get(proxy, property, receiver)

3、捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • receiver:代理对象或继承代理对象的对象。

4、捕获器不变式

        如果target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配。

        如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined。

9.2.2 set()

        set()捕获器会在设置属性值的操作中被调用。对应的反射API方法为Reflect.set()。

const myTarget = {};const proxy = new Proxy(myTarget, {set(target, property, value, receiver) {console.log('set()');return Reflect.set(...arguments)}
});proxy.foo = 'bar';
// set()
1、返回值

        返回true表示成功;返回false表示失败,严格模式下会抛出TypeError。

2、拦截的操作
  • proxy.property = value
  • proxy[property] = value
  • Object.create(proxy)[property] = value
  • Reflect.set(proxy, property, value, receiver)

3、捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • value:要赋给属性的值。
  • receiver:接收最初赋值的对象。

4、捕获器不变式

        如果target.property不可写且不可配置,则不能修改目标属性的值。

        如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值。

        在严格模式下,处理程序中返回false会抛出TypeError。

9.2.3 has()

        has()捕获器会在in操作符中被调用。对应的反射API方法为Reflect.has()。

const myTarget = {};const proxy = new Proxy(myTarget, {has(target, property) {console.log('has()');return Reflect.has(...arguments)}
});'foo' in proxy;
// has()
1、返回值

        has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

2、拦截的操作
  • property in proxy
  • property in Object.create(proxy)
  • with(proxy) {(property);}
  • Reflect.has(proxy, property)

3、捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。

4、捕获器不变式

        如果target.property存在且不可配置,则处理程序必须返回true。

        如果target.property存在且目标对象不可扩展,则处理程序必须返回true。

9.2.4 defineProperty()

        defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API方法为Reflect.defineProperty()。

const myTarget = {};const proxy = new Proxy(myTarget, {defineProperty(target, property, descriptor) {console.log('defineProperty()');return Reflect.defineProperty(...arguments)}
});Object.defineProperty(proxy, 'foo', { value: 'bar' });
// defineProperty()
1、返回值

        defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。

2、拦截的操作
  • Object.defineProperty(proxy, property, descriptor)
  • Reflect.defineProperty(proxy, property, descriptor)

3、捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • descriptor:包含可选的
  • enumerable、configurable、writable、value、get和set定义的对象。

4、捕获器不变式

        如果目标对象不可扩展,则无法定义属性。

        如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。

        如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。

9.2.5 getOwnProperyDescriptor()

        getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用。对应的反射API方法为Reflect.getOwnPropertyDescriptor()。

const myTarget = {};const proxy = new Proxy(myTarget, {getOwnPropertyDescriptor(target, property) {console.log('getOwnPropertyDescriptor()');return Reflect.getOwnPropertyDescriptor(...arguments)}
});Object.getOwnPropertyDescriptor(proxy, 'foo');
// getOwnPropertyDescriptor()
 1、返回值

        getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined。

2、拦截的操作
  • Object.getOwnPropertyDescriptor(proxy, property)
  • Reflect.getOwnPropertyDescriptor(proxy, property)

3、捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。

4、捕获器不变式

        如果自有的target.property存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。

        如果自有的target.property存在且可配置,则处理程序必须返回表示该属性可配置的对象。

        如果自有的target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象。

        如果target.property不存在且target不可扩展,则处理程序必须返回undefined表示该属性不存在。

        如果target.property不存在,则处理程序不能返回表示该属性可配置的对象。

9.2.6 deleteProperty()

        deleteProperty()捕获器会在delete操作符中被调用。对应的反射API方法为Reflect.deleteProperty()。

const myTarget = {};const proxy = new Proxy(myTarget, {deleteProperty(target, property) {console.log('deleteProperty()');return Reflect.deleteProperty(...arguments)}
});delete proxy.foo
// deleteProperty()
1、返回值

        deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。

2、拦截的操作
  • delete proxy.property
  • delete proxy[property]
  • Reflect.deleteProperty(proxy, property)

3、捕获器处理程序对象
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。

4、捕获器不变式

        如果自有的target.property存在且不可配置,则处理程序不能删除这个属性。

9.2.7 ownKeys()

        ownKeys()捕获器会在Object.keys()及类似方法中被调用。对应的反射API方法为Reflect.ownKeys()。

const myTarget = {};const proxy = new Proxy(myTarget, {ownKeys(target) {console.log('ownKeys()');return Reflect.ownKeys(...arguments)}
});Object.keys(proxy);
// ownKeys()

1、返回值

        ownKeys()必须返回包含字符串或符号的可枚举对象。

2、拦截的操作
  • Object.getOwnPropertyNames(proxy)
  • Object.getOwnPropertySymbols(proxy)
  • Object.keys(proxy)
  • Reflect.ownKeys(proxy)

3、捕获器处理程序参数
  • target:目标对象。

4、捕获器不变式

        返回的可枚举对象必须包含target的所有不可配置的自有属性。

        如果target不可扩展,则返回可枚举对象必须准确地包含自有属性键。

9.2.9 setPrototyOf()

        setPrototypeOf()捕获器会在Object.setPrototypeOf()中被调用。对应的反射API方法为Reflect.setPrototypeOf()。

const myTarget = {};const proxy = new Proxy(myTarget, {setPrototypeOf(target, prototype) {console.log('setPrototypeOf()');return Reflect.setPrototypeOf(...arguments)}
});Object.setPrototypeOf(proxy, Object);
// setPrototypeOf()
1、返回值

        setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。

2、拦截操作
  • Object.setPrototypeOf(proxy)
  • Reflect.setPrototypeOf(proxy)

3、捕获器处理程序参数
  • target:目标对象。
  • prototype:target的替代原型,如果是顶级原型则为null。

4、捕获器不变式

        如果target不可扩展,则唯一有效的prototype参数就是Object.getPrototypeOf(target)的返回值。

9.2.10 isExtensible()

        isExtensible()捕获器会在Object.isExtensible()中被调用。对应的反射API方法为Reflect.isExtensible()。

const myTarget = {};const proxy = new Proxy(myTarget, {isExtensible(target) {console.log('isExtensible()');return Reflect.isExtensible(...arguments)}
});Object.isExtensible(proxy);
// isExtensible()
1、返回值

        isExtensible()必须返回布尔值,表示target是否可扩展。返回非布尔值会被转型为布尔值。

2、拦截的操作
  • Object.isExtensible(proxy)
  • Reflect.isExtensible(proxy)

3、捕获器处理程序参数
  • target:目标对象。

4、捕获器不变式

        如果target可扩展,则处理程序必须返回true。

        如果target不可扩展,则处理程序必须返回false。

9.2.11 preventExtensions()

        preventExtensions()捕获器会在Object.preventExtensions()中被调用。对应的反射API方法为Reflect.preventExtensions()。

const myTarget = {};const proxy = new Proxy(myTarget, {preventExtensions(target) {console.log('preventExtensions()');return Reflect.preventExtensions(...arguments)}
});Object.preventExtensions(proxy);
// preventExtensions()
1、返回值

        preventExtensions()必须返回布尔值,表示target是否已经不可扩展。返回非布尔值会被转型为布尔值。

2、拦截的操作
  • Object.preventExtensions(proxy)
  • Reflect.preventExtensions(proxy)

3、捕获器处理程序参数
  • target:目标对象。

4、捕获器不变式

        如果Object.isExtensible(proxy)是false,则处理程序必须返回true。

9.2.12 apply()

        apply()捕获器会在调用函数时中被调用。对应的反射API方法为Reflect.apply()。

const myTarget = () => {};const proxy = new Proxy(myTarget, {apply(target, thisArg, ...argumentsList) {console.log('apply()');return Reflect.apply(...arguments)}
});proxy();
// apply()
1、返回值

        返回值无限制

2、拦截的操作
  • proxy(...argumentsList)
  • Function.prototype.apply(thisArg, argumentsList)
  • Function.prototype.call(thisArg, ...argumentsList)
  • Reflect.apply(target, thisArgument, argumentsList)

3、捕获器处理程序参数
  • target:目标对象。
  • thisArg:调用函数时的this参数。
  • argumentsList:调用函数时的参数列表

4、捕获器不变式

        target必须是一个函数对象。

9.2.13 construct()

        construct()捕获器会在new操作符中被调用。对应的反射API方法为Reflect.construct()

const myTarget = function() {};const proxy = new Proxy(myTarget, {construct(target, argumentsList, newTarget) {console.log('construct()');return Reflect.construct(...arguments)}
});new proxy;
// construct()
1、返回值

        construct()必须返回一个对象。

2、拦截的操作
  • new proxy(...argumentsList)
  • Reflect.construct(target, argumentsList, newTarget)

3、捕获器处理程序参数
  • target:目标构造函数。
  • argumentsList:传给目标构造函数的参数列表。
  • newTarget:最初被调用的构造函数。

4、捕获器不变式

        target必须可以用作构造函数。

 9.3 代理模式

        使用代理可以在代码中实现一些有用的编程模式。

9.3.1 跟踪属性访问

        通过捕获get、set和has等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:

const user = {name: 'Jake'
};const proxy = new Proxy(user, {get(target, property, receiver) {console.log(`Getting ${property}`);return Reflect.get(...arguments);},set(target, property, value, receiver) {console.log(`Setting ${property}=${value}`);return Reflect.set(...arguments);}
});proxy.name;     // Getting name
proxy.age = 27; // Setting age=27

9.3.2 隐藏属性

        代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:

const hiddenProperties = ['foo', 'bar'];
const targetObject = {foo: 1,bar: 2,baz: 3
};
const proxy = new Proxy(targetObject, {get(target, property) {if (hiddenProperties.includes(property)) {return undefined;} else {return Reflect.get(...arguments);}},has(target, property) {if (hiddenProperties.includes(property)) {return false;} else {return Reflect.has(...arguments);}}
});// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true

9.3.3 属性验证

        因为所有赋值操作都会触发set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:

const target = {onlyNumbersGoHere: 0
};const proxy = new Proxy(target, {set(target, property, value) {if (typeof value !== 'number') {return false;} else {return Reflect.set(...arguments);}}
});proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1

9.3.4 函数与构造函数参数验证

          跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值:

function median(...nums) {return nums.sort()[Math.floor(nums.length / 2)];
}const proxy = new Proxy(median, {apply(target, thisArg, argumentsList) {for (const arg of argumentsList) {if (typeof arg !== 'number') {throw 'Non-number argument provided';}}return Reflect.apply(...arguments);}
});console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided

        类似地,可以要求实例化时必须给构造函数传参:

class User {constructor(id) {this.id_ = id;}
}const proxy = new Proxy(User, {construct(target, argumentsList, newTarget) {if (argumentsList[0] === undefined) {throw 'User cannot be instantiated without id';} else {return Reflect.construct(...arguments);}}
});new proxy(1);new proxy();
// Error: User cannot be instantiated without id

9.3.5 数据绑定与可观察对象

        通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。

        比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:

const userList = [];class User {constructor(name) {this.name_ = name;}
}const proxy = new Proxy(User, {construct() {const newUser = Reflect.construct(...arguments);userList.push(newUser);return newUser;}
});new proxy('John');
new proxy('Jacob');
new proxy('Jingleheimerschmidt');console.log(userList); // [User {}, User {}, User{}]

        另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:

const userList = [];function emit(newValue) {console.log(newValue);
}const proxy = new Proxy(userList, {set(target, property, value, receiver) {const result = Reflect.set(...arguments);if (result) {emit(Reflect.get(target, property, receiver));}return result;}
});proxy.push('John');
// John
proxy.push('Jacob');
// Jacob

9.4 小结

        代理是ECMAScript 6新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟出了一片前所未有的JavaScript元编程及抽象的新天地。

        从宏观上看,代理是真实JavaScript对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分JavaScript的基本操作和方法。在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式。

        与代理如影随形的反射API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射API看作一套基本操作,这些操作是绝大部分JavaScript对象API的基础。

        代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。


http://www.ppmy.cn/news/1339009.html

相关文章

Trie树数据结构——(字符串统计,最大异或对)

Trie树&#xff1a;是一种能够高效存储和查找字符串集合的数据结构 Trie字符串统计 思路&#xff1a; &#xff08;笔记来自AcWing 835. Trie字符串统计 - AcWing&#xff09; 代码如下&#xff1a; #include<iostream> #include<cstdio> #include<string>…

TypeScript(十) Map对象、元组、联合类型、接口

1. Map对象 1.1. 简述 Map对象保存键值对&#xff0c;并且能够记住键的原始插入顺序。   任何值都可以作为一个键或一个值。 1.2. 创建 Map 使用Map类型和new 关键字来创建Map&#xff1a; 如&#xff1a; let myMap new Map([["key1", "value1"],[&…

【乳腺肿瘤诊断分类及预测】基于PNN概率神经网络

课题名称&#xff1a;基于PNN的乳腺肿瘤诊断分类及预测 版本日期&#xff1a;2023-06-15 运行方式: 直接运行PNN0501.m 文件即可 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 威斯康辛大学医学院经过多年的收集和整理&#xff0c;建…

【技能---如何正确导出onnx】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言注意事项案例说明1. 使用 int 转换来避免直接使用 tensor.size 的返回值&#xff1a;2. 使用 scale_factor 替代 size 参数&#xff1a;3. 将 -1 放在 view 操作…

Uni-app 如何上传文件, 使用的API是什么

在uni-app中上传文件的方法有很多&#xff0c;其中一种常用的方法是使用wx.uploadFile() API。该API可以上传本地文件或网络文件&#xff0c;并支持设置请求头、请求参数等选项。 一.引入API import { uploadFile } from /util/request.js;二.使用API 上传文件 uploadFile({…

Django模型(七)

一、聚合与分组查询 1.1、准备数据 class Cook(models.Model):"""厨师"""name = models.CharField(max_length=32,verbose_name=厨师名)level = models.IntegerField(verbose_name=厨艺等级)age = models.IntegerField(verbose_name=年龄)sect …

山东省七五商贸有限公司缝纫设备采购项目(第一批次)

山东省七五商贸有限公司缝纫设备采购项目(第一批次) (招标编号:JCJS-2024-002) 项目所在地区:山东省 一、招标条件 本山东省七五商贸有限公司缝纫设备采购项目(第一批次)已由项目审批/核准/备案机关批准&#xff0c;项目资金来源为其他资金/&#xff0c;招标人为山东省七五商贸…

【JavaSE篇】——内部类

目录 &#x1f393;内部类 &#x1f388;内部类的分类 &#x1f6a9;实例内部类 一.如何实例内部类对象 二.实例内部类中为什么不能有静态成员变量 &#xff08;用final解决&#xff09; 三.在实例内部类对象时&#xff0c;如何访问外部类当中相同的成员变量&#xff1f;…