代理与反射
Proxy(代理器)
用于修改某些操作的默认行为。
在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截。
new Proxy(target,handler)
拦截操作
Proxy支持的拦截操作一共13种。
拦截操作 | 拦截行为 |
---|---|
get(target, propKey, receiver) | 拦截对象属性的读取,比如proxy.foo和 proxy['foo'] |
set(target, propKey, value, receiver) | 拦截对象属性的设置,比如 proxy.foo = v或proxy['foo'] = v,返回一个布尔值 |
has(target, propKey) | 拦截propKey in proxy的操作,返回一个布尔值 |
deleteProperty(target, propKey) | 拦截delete proxy[propKey]的操作, 返回一个布尔值 |
ownKeys(target) | 拦截Object.getOwnPropertyNames(proxy)、 Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环, 返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys()的返回结果仅包括目标对象自身的可遍历属性 |
getOwnPropertyDescriptor(target, propKey) | 拦截 Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象 |
defineProperty(target, propKey, propDesc) | 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值 |
preventExtensions(target) | 拦截Object.preventExtensions(proxy),返回一个布 尔值 |
getPrototypeOf(target) | 拦截Object.getPrototypeOf(proxy),返回一个对象 |
isExtensible(target) | 拦截Object.isExtensible(proxy),返回一个布尔值 |
setPrototypeOf(target, proto) | 拦截Object.setPrototypeOf(proxy, proto),返回 一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截 |
apply(target, object, args) | 拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args)、proxy.call(object, ...args)、proxy.apply(...) |
construct(target, args) | 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args) |
同一个拦截器对象,可以设置拦截多个操作。
Proxy构造函数
ES6原生提供Proxy构造函数,用来生成Proxy实例。
let obj = new Proxy({},{get: function (target, key, receiver) {console.log(`getting ${key}`);return Reflect.get(target, key, receiver);},set: function (target, key, value, receiver) {console.log(`setting ${key}`);return Reflect.set(target, key, value, receiver);},}
);
obj.count = 1;
++obj.count;
console.log(obj);
//输出
// setting count
// getting count
// setting count
// { count: 2 }
handler,拦截器对象,包含了一个或多个陷阱函数。
拦截读取属性行为
const person = {name: "Jhon",
};
const proxy = new Proxy(person, {get(target, property) {if (property in target) {return target[property];} else {throw new ReferenceError(`Property "${property}" does not
exist.`);}},
});
console.log(proxy.name); // Jhon
console.log(proxy.age); // Property "age" does not exist.
拦截设置属性行为
const person = {name: "Jhon",age: 30,
};
const handler = {set(target, property, value) {if (property === "age" && typeof value !== "number") {throw new TypeError("The age property must be a number.");}Reflect.set(target, property, value);},
};
const proxy = new Proxy(person, handler);
proxy.age = 31; // 成功
console.log(proxy);
console.log(person);
proxy.age = "thirty-one"; // TypeError: The age property must be a number.
如果handle没有设置任何拦截,就等于直接通向源对象。
let target = {};
let handler = {};
let p = new Proxy(target, handler);
p.a = "b";
console.log(target, p.a);//b
target.a = "bb";
console.log(target, p.a);//bb
应用
对象属性的操作:get、set、delete(js特有)。
对象的属性名只有两种数据类型:字符串string、字符symbol
保持对象结构
const handler = {get(target, property) {if (property in target) {return Reflect.get(target, property);} else {throw new ReferenceError(`Property "${property}" does not exist.`);}},set(target, property, value) {if (property in target) {Reflect.set(target, property, value);} else {throw new ReferenceError(`Property "${property}" does not
exist.`);}},
};
const person = {name: "Jhon",age: 30,showMe() {console.log(`${this.name}, ${this.age}`);},
};
const proxy = new Proxy(person, handler);
console.log(proxy.name, proxy.age); // Jhon, 30
proxy.showMe(); // Jhon, 30
proxy.gender = "male";
实现数组读取负数索引
const names = ["Jhon", "Tom", "Mary", "Lucy"];
const handler = {get(target, property) {const index = Number(property);if (isNaN(index)) {return Reflect.get(target, property);} else {return target[(index + target.length) % target.length];}},
};
const proxy = new Proxy(names, handler);
console.log(proxy[-1]); // Lucy
利用get陷阱实现DOM节点生成器
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Proxy</title></head><body></body><script>const dom = new Proxy({},{get(target, property) {return function (attrs = {}, ...children) {const el = document.createElement(property);for (let prop of Object.keys(attrs)) {el.setAttribute(prop, attrs[prop]);}for (let child of children) {if (typeof child === "string") {child = document.createTextNode(child);}el.appendChild(child);}return el;};},});const el = dom.div({},"Hello, my name is ",dom.a({ href: "http://www.example.com" }, "Mark"),". I like:",dom.ul({},dom.li({}, "The web"),dom.li({}, "Food"),dom.li({}, "...actually that's it")));document.body.appendChild(el);</script>
</html>
基于class的代理器
class Person {constructor(name, age) {this.name = name;this.age = age;return new Proxy(this, {get(target, property) {if (property in target) {return target[property];} else {throw new ReferenceError(`Property "${property}"
does not exist.`);}},});}
}
const person = new Person("Jhon", 30);
console.log(person.name); // Jhon
console.log(person.age); // 30
console.log(person.gender); // 抛出 ReferenceError
Reflect(反射)
提供了统一的反射接口,给底层操作提供了默认行为的方法合集。
每个代理陷阱都有一个对应的反射方法,每个方法都与之对应的陷阱函数同名,并且接收一致的参数。
代理陷阱
13种代理陷阱
代理陷阱 | 被重写的行为 | 默认行为 |
---|---|---|
get | 读取一个属性的值 | Reflect.get( ) |
set | 写入一个属性 | Reflect.set( ) |
has | in运算符 | Reflect.has( ) |
apply | 调用一个函数 | Reflect.apply( ) |
construct | 使用new调用一个函数 | Reflect.construct( ) |
ownKeys | Object.keys( ) Object.getOwnPropertyNames( ) Object.getOwnPropertySymbols( ) | Reflect.ownKeys( ) |
... | ... | ... |