js--代理与反射之捕获器和常见的反射 API
前言
假如你已经初步了解了代理和反射的基本知识,知道了只要在代理对象上调用其处理程序,所有捕获器都会拦截它们对应的反射 API 操作。本文来总结一下代理对象的处理程序中不同的捕获器和反射API。
正文
代理可以捕获 13 种不同的基本操作。这些操作有各自不同的反射 API 方法、参数、关联 ECMAScript操作和不变式(限制)。
1、get () 捕获器--监听劫持对象属性的访问
get() 捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get() 。
(1)拦截的操作:(proxy为代理对象),get () 返回值无限制。
a、proxy.property
b、proxy[property]
c、Object.create(proxy)[property]
d、Reflect.get(proxy, property, receiver)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
c、receiver :代理对象或继承代理对象的对象。
(3)捕获器限制:
a、如果目标对象的属性(target.property)存在不可配置且不可写的数据属性,get捕获器返回的值必须与目标对象的属性(target.property)匹配,否则报错TypeError。
b、如果目标对象的属性(target.property)存在不可配置且[[Get]]为undefined的访问器属性,则 get 捕获器的返回值必须为 undefined。
(4)get()捕获器的使用
这里通过get捕获器进行对象外形的验证,对象外形( Object Shape )指的是对象已有的属性与方法的集合, JS 引擎使用对象外形来进行代码优化,经常会创建一些类来表示对象。如果你能大胆假设某个对象总是拥有与起始时相同的属性与方法(可以通过 Object.preventExtensions() 方法、 Object.seal() 方法或Object.freeze() 方法来达到这种效果),那么在访问不存在的属性时抛出错误在这种场合就会非常有用。代理能够让对象外形验证变得轻而易举。
get捕获器会在读取对象属性时被调用,即使改属性在对象中不存在。代码如下:
var person = {} var handler = { get(trapTarget, property, receiver) { if (!(property in receiver)) { throw new TypeError("Property " + property + " doesn't exist."); } return Reflect.get(trapTarget, property, receiver); } }; var perProxy = new Proxy(person, handler); perProxy.name = "proxy";// 添加属性的功能正常 console.log(perProxy.name); // "proxy" // 读取不存在属性会抛出错误 console.log(perProxy.age); // 抛出错误Property age doesn't exist.
2、set () 捕获器--监听劫持对象属性值的设置
set() 捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set() 。
(1)拦截的操作:(proxy为代理对象)返回true表示设置成功,返回 false 表示失败,严格模式下会抛出 TypeError
a、proxy.property = value
b、proxy[property] = value
c、Object.create(proxy)[property] = value
d、Reflect.set(proxy, property, value, receiver)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
c、value :要赋给属性的值。
d、receiver :接收最初赋值的对象。
(3)捕获器限制:
a、如果目标对象的属性(target.property)存在不可配置且不可写的数据属性,get捕获器返回的值必须与目标对象的属性(target.property)匹配,否则报错TypeError。
b、如果目标对象的属性(target.property)存在不可配置且[[Get]]为undefined的访问器属性,则 get 捕获器的返回值必须为 undefined。
(4)set()捕获器的使用
这里通过 set 捕获器验证新增属性值。假设你想要创建一个对象,并要求其属性值只能是数值,这就意味着该对象的每个新增属性都要验证,并且在属性值部为数值累心更多时候抛出错误。为此你需要定义set陷阱函数来重写设置属性值时的默认行为。代码如下:
var person = { name: "Serendipity" }; var handler = { set(trapTarget, property, value, receiver) { // 忽略已有属性,避免影响它们 if (!trapTarget.hasOwnProperty(property)) { if (isNaN(value)) { throw new TypeError("Property must be a number."); } } // 添加属性 return Reflect.set(...arguments); } } let perProxy = new Proxy(person, handler); // 添加一个新属性 perProxy.age = 18; console.log(perProxy.age); // 18 console.log(person.age); // 18 // 你可以为 name 赋一个非数值类型的值,因为该属性已经存在 perProxy.name = "SerendipityProxy"; console.log(perProxy.name); // "SerendipityProxy" console.log(person.name); // "SerendipityProxy" // 抛出错误-anotherName为新增属性,会判断值类型 perProxy.anotherName = "Nicholas";// Uncaught TypeError: Property must be a number.
3、has () 捕获器--监听拦截对象被 in 操作符调用
has() 捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。
(1)拦截的操作:(proxy为代理对象)。has() 必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。
a、property in proxy
b、property in Object.create(proxy)
c、with(proxy) {(property);}
d、Reflect.has(proxy, property)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
(3)捕获器限制:
a、如果目标对象的属性 (target.property) 存在且不可配置,则处理程序必须返回 true 。
b、如果目标对象的属性 (target.property) 存在且目标对象不可扩展,则处理程序必须返回 true 。
(4)has()捕获器的使用
a、in 运算符
in 运算符用于判断指定对象中是否存在某个属性,如果对象的属性名与指定的字符串或符号值相匹配,那么 in 运算符应当返回 true ,无论该属性是对象自身的属性还是其原型的属性。例如:
var target = { value: 42 } console.log("value" in target); // true console.log("toString" in target); // true
b、隐藏对象中属性用过in操作符访问
var person = { name: "Serendipity", age: 18 }; var handler = { has(trapTarget, property) { if (property === "value") { return false; } else { return Reflect.has(trapTarget, property); } } } var perProxy = new Proxy(person, handler); console.log("value" in perProxy); // false console.log("name" in perProxy); // true console.log("toString" in perProxy); // true
4、deleteProperty() 捕获器--监听拦截对象被 delet 操作符调用
deleteProperty() 捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.deleteProperty() 。
(1)拦截的操作:(proxy为代理对象)。deleteProperty() 必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。
a、delete proxy.property
b、delete proxy[property]
c、Reflect.deleteProperty(proxy, property)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
(3)捕获器限制:
如果自有的 target.property 存在且不可配置,则处理程序不能删除这个属性。
(4)deleteProperty()捕获器的使用
a、delete 运算符
delete 运算符能够从指定对象上删除一个属性,在删除成功时返回 true ,否则返回false 。如果试图用 delete 运算符去删除一个不可配置的属性,在严格模式下将会抛出错误;而非严格模式下只是单纯返回 false 。例如:
var target = { value: 42 } console.log(delete target.value); // true console.log( target.value); // undefined console.log('value' in target) // false Object.defineProperty(target, "name", { configurable: false }); console.log(delete target.name); // false console.log('name' in target)// true
b、deleteProperty()捕获器阻止对对象特定属性的删除
var person = { name: "Serendipity", age: 18 }; var handler = { deleteProperty(trapTarget, property) { if (property === "name") { return false; } else { return Reflect.deleteProperty(trapTarget, property); } } } var perProxy = new Proxy(person, handler); console.log(delete perProxy.age);// true console.log(delete perProxy.name);// false
5、getOwnPropertyDescriptor() 捕获器--监听拦截通过 Object.getOwnPropertyDescriptor()获取属性描述符时调用
getOwnPropertyDescriptor() 捕获器会在 Object.getOwnPropertyDescriptor() 中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor() 。
(1)拦截的操作:(proxy为代理对象)。getOwnPropertyDescriptor() 必须返回对象,或者在属性不存在时返回 undefined 。
a、Object.getOwnPropertyDescriptor(proxy, property)
b、Reflect.getOwnPropertyDescriptor(proxy, property)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
(3)捕获器限制:
a、如果自有的 target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。
b、如果自有的 target.property 存在且可配置,则处理程序必须返回表示该属性可配置的对象。
c、如果自有的 target.property 存在且 target 不可扩展,则处理程序必须返回一个表示该属性存在的对象。
d、如果 target.property 不存在且 target 不可扩展,则处理程序必须返回 undefined 表示该属性不存在。
e、如果 target.property 不存在,则处理程序不能返回表示该属性可配置的对象。
(4)getOwnPropertyDescriptor()捕获器的使用
a、Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor()获取传入对象给定属性的描述符。
var person = { name: "Serendipity", isAdult: true }; Object.defineProperty(person, 'age', { get: function () { return this.age }, set: function (newValue) { this.isAdult = newValue >= 18 ? true : false } }); var descriptor = Object.getOwnPropertyDescriptor(person, 'age') console.log(descriptor); //configurable: false // enumerable: false // get: ƒ () // set: ƒ (newValue)
var person = { name: "Serendipity", age: 18 }; var handler = { getOwnPropertyDescriptor(trapTarget, property) { console.log('getOwnPropertyDescriptor()'); return Reflect.getOwnPropertyDescriptor(...arguments) } } var perProxy = new Proxy(person, handler); Object.getOwnPropertyDescriptor(perProxy, 'name'); // getOwnPropertyDescriptor() // configurable: true // enumerable: true // value: "Serendipity" // writable: true
6、defineProperty() 捕获器--监听拦截通过 Object.defineProperty() 获取属性描述符时调用
defineProperty() 捕获器会在 Object.defineProperty() 中被调用。对应的反射 API 方法为Reflect.defineProperty() 。
(1)拦截的操作:(proxy为代理对象)。defineProperty() 必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。
a、Object.defineProperty(proxy, property, descriptor)
b、Reflect.defineProperty(proxy, property, descriptor)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
c、descriptor :包含可选的 enumerable 、 configurable 、 writable 、 value 、 get 和 set定义的对象。
(3)捕获器限制:
a、如果目标对象不可扩展,则无法定义属性。
b、如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。
c、如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。
(4)defineProperty()捕获器的使用
a、Object.defineProperty()
Object.defineProperty()修改对象内部属性。
Object.defineProperty()修改对象内部属性。 var person = {}; Object.defineProperty(person, "age", { enumerable: true, configurable: false, value: 18 }); console.log(person.age);// 18 delete person.age console.log(person.age);// 18
var person = { name: "Serendipity", age: 18 }; var handler = { defineProperty(trapTarget, property, descriptor) { if (property == "isAdult") { return false } return Reflect.defineProperty(...arguments) } } var perProxy = new Proxy(person, handler); console.log(perProxy.name); Object.defineProperty(perProxy, "isAdult", { enumerable: true, configurable: false, value: false });// 报错:Uncaught TypeError: 'defineProperty' on proxy: trap returned falsish for property 'isAdult'
7、setPrototypeOf() 捕获器--监听拦截通过 Object.setPrototypeOf() 设置对象原型时调用
setPrototypeOf() 捕获器会在 Object.setPrototypeOf() 中被调用。对应的反射 API 方法为Reflect.setPrototypeOf() 。
(1)拦截的操作:(proxy为代理对象)。setPrototypeOf() 必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。
a、Object.setPrototypeOf(proxy)
b、Reflect.setPrototypeOf(proxy)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
(3)捕获器限制:
如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(target)的返回值。
8、getPrototypeOf() 捕获器--监听拦截通过 Object.getPrototypeOf() 获取对象原型时调用
getPrototypeOf() 捕获器会在 Object.getPrototypeOf() 中被调用。对应的反射 API 方法为Reflect.getPrototypeOf()
(1)拦截的操作:(proxy为代理对象)。getPrototypeOf() 必须返回对象或 null 。
a、Object.getPrototypeOf(proxy)
b、Reflect.getPrototypeOf(proxy)
c、proxy.__proto__
d、Object.prototype.isPrototypeOf(proxy)
e、proxy instanceof Object
(2)捕获器参数:
a、target :目标对象。
(3)捕获器限制:
如果 target 不可扩展,则 Object.getPrototypeOf(proxy) 唯一有效的返回值就是 Object.getPrototypeOf(target) 的返回值
9、isExtensible() 捕获器--监听拦截通过 Object.isExtensible() 判断一个对象是否是可扩展的时调用
isExtensible() 捕获器会在 Object.isExtensible() 中被调用。对应的反射 API 方法为Reflect.isExtensible() 。
(1)拦截的操作:(proxy为代理对象)。isExtensible() 必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。
a、Object.isExtensible(proxy)
b、Reflect.isExtensible(proxy)
(2)捕获器参数:
a、target :目标对象。
b、property :引用的目标对象上的字符串键属性。
(3)捕获器限制:
a、如果 target 可扩展,则处理程序必须返回 true 。
b、如果 target 不可扩展,则处理程序必须返回 false 。
(4)Object.isExtensible的使用
var empty = {}; Object.isExtensible(empty);
10、preventExtensions() 捕获器--监听拦截通过 Object.preventExtensions() 让一个对象变的不可扩展时调用
preventExtensions() 捕获器会在 Object.preventExtensions() 中被调用。对应的反射 API方法为 Reflect.preventExtensions() 。
(1)拦截的操作:(proxy为代理对象)。preventExtensions() 必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转型为布尔值。
a、Object.preventExtensions(proxy)
b、Reflect.preventExtensions(proxy)
(2)捕获器参数:
a、target :目标对象。
(3)捕获器限制:
如果 Object.isExtensible(proxy) 是 false ,则处理程序必须返回 true 。
(4)Object.preventExtensions()的使用
var empty = {}; Object.isExtensible(empty) //=== true Object.preventExtensions(empty); Object.isExtensible(empty) //=== false
11、apply() 捕获器--监听拦截通过函数被调用时调用
apply() 捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply() 。
(1)拦截的操作:(proxy为代理对象)。返回值无限制。
a、proxy(...argumentsList)
b、Function.prototype.apply(thisArg, argumentsList)
c、Function.prototype.call(thisArg, ...argumentsList)
d、Reflect.apply(target, thisArgument, argumentsList)
(2)捕获器参数:
a、target :目标对象。
b、thisArg :调用函数时的 this 参数。
c、argumentsList :调用函数时的参数列表
(3)捕获器限制:
目标对象target 必须是一个函数对象。
(4)apply()捕获器的使用
a、apply()
const myTarget = () => {}; const proxy = new Proxy(myTarget, { apply(target, thisArg, ...argumentsList) { console.log('apply()'); return Reflect.apply(...arguments) } }); proxy(); // apply()
b、apply()捕获器进行函数参数验证
function foo(...nums) { return nums.sort(); } var handler = { apply(target, thisArg, argumentsList) { for (const arg of argumentsList) { if (typeof arg !== 'number') { throw 'argement must be number'; } } return Reflect.apply(...arguments); } } const fooProxy = new Proxy(foo, handler); console.log(fooProxy(5, 1, 3)); // [1,3,5] console.log(fooProxy(5, 1, '3'));// Error: argement must be number
12、construct() 捕获器--监听拦截对象被 New 操作符调用时调用
construct() 捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct() 。
(1)拦截的操作:(proxy为代理对象)。construct() 必须返回一个对象。
a、new proxy(...argumentsList)
b、Reflect.construct(target, argumentsList, newTarget)
(2)捕获器参数:
a、target :目标构造函数。
b、argumentsList :传给目标构造函数的参数列表。
c、newTarget :最初被调用的构造函数。
(3)捕获器限制:
目标对象target 必须可以用作构造函数。
(4)construct()捕获器的使用
const myTarget = function() {}; const proxy = new Proxy(myTarget, { construct(target, argumentsList, newTarget) { console.log('construct()'); return Reflect.construct(...arguments) } }); new proxy; // construct()
b、construct() 验证构造函数参数
function Person(name) { this.name = name } var handler = { construct(target, argumentsList, newTarget) { if (argumentsList[0] === undefined) { throw "Person cannot be instantiated without name" } else { return Reflect.construct(...arguments) } } } var porPerson = new Proxy(Person, handler) var Serendipity = new porPerson("Serendipity") var s = new porPerson()//Person cannot be instantiated without name
13、ownKeys() 捕获器--监听拦截Object.keys() 及类似方法中被调用
const myTarget = {}; const proxy = new Proxy(myTarget, { ownKeys(target) { console.log('ownKeys()'); return Reflect.ownKeys(...arguments) } }); Object.keys(proxy); // ownKeys()
写在最后
以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。