Proxy详解
Posted lyralee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Proxy详解相关的知识,希望对你有一定的参考价值。
一.Proxy基础
1. 什么是Proxy?
Proxy是一个构造函数,可以通过它生成一个Proxy实例。
const proxy = new Proxy(target, handler); // target: 目标对象,待操作对象(可以是普通对象,数组,函数,proxy实例对象) // handler: 一个对象,最多可包含13个操作方法,也可以是空对象
2. Proxy的作用
1. Proxy是一个ES6语法,它的作用主要是通过handler对象中的拦截方法拦截目标对象target的
某些操作(如读取,赋值,函数调用,new等),然后过滤或者改写这些操作的默认行为,
或者只是在完成这些操作的默认行为的基础上,增加一些副作用(如打印前置log等)。
2. 生成的实例对象是针对target对象的“拦截器”。也可以叫做“代理器”。
3. 然后必须通过操作proxy,即“拦截器”(拦截器对象本身性质和目标对象target一样,比如:target是函数,
那么proxy也是函数)才能触发拦截方法,来完成对目标对象的操作和访问。
4. 如果handler是个空对象({}),那么操作拦截器相当于直接操作目标对象target。
3. Proxy构造函数的特征
1. Proxy函数没有prototype属性,所以也就不能使用instanceof判断是否是Proxy实例
2. Proxy实例的数据类型和target数据类型一致。
var proxy1 = new Proxy([], {}); proxy1 instanceof Array; // true var proxy2 = new Proxy(function(){}, {}); typeof proxy2; //"function"
二. Proxy拦截器handler方法
一共有13个拦截方法(对应Reflect的13个方法),可以大体分为两个部分。
1. 新的方法名
返回值是布尔值的方法有:
1. has(target, propKey)
作用: 拦截判断target对象是否含有属性propKey的操作
拦截操作: propKey in proxy; 不包含for...in循环
对应Reflect: Reflect.has(target, propKey)
语法:
const handler = { has(target, propKey){ // ... //默认操作 return propKey in target; } } const proxy = new Proxy(target, handler)
示例: 过滤某些私有属性
const handler = { has(target, propKey) { if (propKey[0] === ‘_‘) { return false; } return propKey in target; } } const target = {_myprop: 1, a: 2, c:3}; const proxy = new Proxy(target, handler); ‘_myprop‘ in proxy; // false
特殊情况: 目标target是不可扩展或者某个属性不可配置,只能返回默认行为结果;否则报错
var obj = { a: 10 }; Object.preventExtensions(obj); var p = new Proxy(obj, { has: function(target, prop) { return false; //只能是return prop in target; } }); ‘a‘ in p; //Uncaught TypeError: ‘has‘ on proxy: trap returned falsish for property ‘a‘ but the proxy target is not extensible
2. deleteProperty(target, propKey)
作用: 拦截删除target对象的propKey属性的操作
拦截操作: delete proxy[propKey]
语法:
const handler = { deleteProperty(target, propKey){ // ... //默认操作 delete target[propKey]; return true; //!!!严格模式下必须有,否则报错; } } const proxy = new Proxy(target, handler)
示例:
var obj = { a: 10 }; var p = new Proxy(obj, { deleteProperty(target, prop) { console.log(‘delete propName ‘,prop);
if delete target[prop]; return true; } }); delete p.a; console.log(obj); // 运行结果如下: ‘delete propName a‘ {}
特殊情况: 属性是不可配置属性时,不能删除; 但是对象不可扩展的时候,可以删除属性。
var obj = { a: 10 }; Object.defineProperty(obj, ‘b‘, { value: 2, configurable: false }); var p = new Proxy(obj, { deleteProperty(target, prop) { delete target[prop]; return true; } }); delete p.b; // Uncaught TypeError: Cannot delete property ‘b‘ of #<Object> console.log(obj);
3. ownKeys(target)
返回: 数组(数组元素必须是字符或者Symbol,其他类型报错)
作用: 拦截获取键值的操作
拦截操作: Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
for...in...循环
语法:
最后取到的结果不是return的值,而是会自动过滤
const handler = { ownKeys(target) {
// 所有的keys;也可以是其他的数组 return Reflect.ownKeys(target); } }
示例:
var obj = { a: 10, [Symbol.for(‘foo‘)]: 2 }; Object.defineProperty(obj, ‘c‘, { value: 3, enumerable: false }) var p = new Proxy(obj, { ownKeys(target) { return [...Reflect.ownKeys(target), ‘b‘, Symbol.for(‘bar‘)]; } }); const keys = Object.keys(p); // [‘a‘] // 自动过滤掉Symbol/非自身/不可遍历的属性 for(let prop in p) { // 和Object.keys()过滤性质一样,只返回target本身的可遍历属性 console.log(‘prop-‘,prop); // prop-a } const ownNames = Object.getOwnPropertyNames(p);// [‘a‘, ‘c‘, ‘b‘] // 只返回拦截器返回的非Symbol的属性,不管是不是target上的属性 const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)] // 只返回拦截器返回的Symbol的属性,不管是不是target上的属性 const ownKeys = Reflect.ownKeys(p);// [‘a‘,‘c‘,Symbol(foo),‘b‘,Symbol(bar)] // 返回拦截器返回的所有值
特殊情况:
1)如果某个属性是不可配置的,那么该属性在拦截器中必须被返回,否则报错
2)如果target对象是不可扩展的,那么拦截器返回的数组必须是操作的默认返回结果。
即必须是被拦截的操作的默认行为,如getOwnPropertyNames()
4. apply(target, thisArg, args)--target是函数
返回:函数执行结果
作用: 拦截proxy作为函数调用时的操作
拦截操作:proxy()
proxy.call(obj, ...args)
proxy.apply(obj, [...args])
Reflect.apply(proxy, thisArg, args)
语法:
const handler = { apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) { return Reflect.apply(...arguments); } }
示例:
const handler = { apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) { console.log(‘---apply拦截器---‘,contextThis,‘-‘,args); return Reflect.apply(...arguments)+‘end‘; } } let target = function(a,b) { return a+b; } const proxy = new Proxy(target, handler); console.log(‘proxy-->‘,proxy(5,6)); console.log(‘proxy.call-->‘,proxy.call(null, 5, 6)); console.log(‘proxy.apply-->‘,proxy.apply(null, [5,6])); console.log(‘Reflect-->‘,Reflect.apply(proxy, null, [5,6])); // 运行结果如下: /* ---apply拦截器---undefined-[5,6] proxy-->11end ---apply拦截器---null-[5,6] proxy.call-->11end ---apply拦截器---null-[5,6] proxy.apply-->11end ---apply拦截器---null-[5,6] Reflect-->11end */
5. construct(target, args, newTarget)--target是构造函数
返回: 实例对象, 不是对象会报错
作用: 拦截new命令
拦截操作: new proxy(...args)
语法:
const handler = { construct(target, args, newTarget){// args是参数数组, newTarget是生成的proxy实例 console.log(‘----拦截new----‘,args,‘-‘, newTarget === proxy); return Reflect.construct(target, args);// 默认行为,也可以return {a: b},只要是对象就可以 } } const target = function(a,b) {}; var proxy = new Proxy(target, handler); console.log(‘proxy type-->‘, typeof proxy); const result = new proxy(1,2); // 触发拦截器 console.log(‘result type-->‘,typeof result)
// 运行结果如下: proxy type-->function ----拦截new----[1,2]-true result type-->object
2. 方法名和对象原有方法名一样
6. get(target, propKey, receiver)
返回: 返回读取的属性
作用:拦截对象属性的读取
拦截操作:proxy[propKey]或者点运算符
语法:
const handler = { get(target, propKey, receiver) {// receiver是proxy实例 return Reflect.get(target, propKey); } }
示例: 实现函数的链式操作
const funsObj = { double(n) {return n*2}, square(n) {return n**2} } var pipe = (value) => { const callStack=[]; return new Proxy({}, { get(target, propKey, receiver) { console.log(propKey,callStack); if (propKey === ‘get‘) { return callStack.reduce((val, fn) => { return fn(val); }, value) } else { callStack.push(funsObj[propKey]); } return receiver; //返回proxy实例才能实现链式调用 } }) } pipe(3).double.square.get; //36
get方法可以继承,但是receiver的值会是直接触发的那个对象。
const proxy= new Proxy({}, { get(target, propKey, receiver) { return receiver; } }) const p = Object.create(proxy); console.log(p.a === p); // true p.a返回receiver
特殊情况:如果对象的属性writable和configurable同时为false, 则拦截器只能返回默认行为
const target = {}; Object.defineProperty(target, ‘a‘, { value: 1, writable: false, configurable: false }) const proxy = new Proxy(target, { get(target,propKey) { return 2; //应该return 1;不能返回其他值,否则报错 } }) proxy.a; // Uncaught TypeError
7.set(target,propKey, value,receiver)
作用: 拦截对象的属性赋值操作
拦截操作: proxy[propkey] = value
语法:
const proxy = new Proxy({}, { set(target, propKey, value,receiver) {// receiver时proxy实例 Reflect.set(...arguments); //当Reflect.set传入的参数有receiver且属性writable=true时,会在receiver在定义属性,会触发defineProperty拦截 return true; //!!!严格模式下必须有,否则报错;非严格模式下可以省略 }, defineProperty(target, propKey, propDesc) { console.log(‘defineProperty‘) } })
set方法也可以继承,receiver也是最终调用的那个实例,和get方法一样。参照get的方法。
设置 target[propKey] = receiver;
当对象的属性writable为false时,该属性不能在拦截器中被修改;
const obj = {}; Object.defineProperty(obj, ‘foo‘, { value: ‘bar‘, writable: false, configurable: true, }); const handler = { set: function(obj, prop, value, receiver) { Reflect.set(...arguments); return true; }, }; const proxy = new Proxy(obj, handler); proxy.foo = ‘baz‘; console.log(obj); // {foo: bar} 说明修改失败
8. defineProperty(target, propKey,PropDesc)
返回: 严格模式下必须返回true
作用:拦截定义属性或者重新定义属性操作
拦截操作: Object.defineProperty(proxy, propKey,propDesc)
Reflect.defineProperty(proxy, propKey,propDesc)
语法兼示例:
const proxy = new Proxy({}, { defineProperty(target, propKey, propDescriptor) {// 最后一个参数是属性描述对象 Reflect.defineProperty(...arguments); return true;// !!严格模式下必须返回true,否则报错 } }) Object.defineProperty(proxy, ‘a‘, {value:5})
特殊情况:
如果对象是不可扩展的(preventExtensible(obj)),则不能添加属性;
如果对象的某属性writable和configurable同时为false, 则不能重新定义该属性的值;
如果上面的属性有其中一个是true,可以重新定义该属性的值。
9. getPrototypeOf(target)
返回: 返回对象或者null,否则报错
作用:拦截获取原型对象的操作
拦截操作:Object.getPrototypeOf(proxy)
proxy.__proto__
Object.isPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
instanceof
语法:
var p = new Proxy({}, { getPrototypeOf(target) { return Reflect.getPrototypeOf(target); } });
示例:
var proxy = new Proxy({}, { getPrototypeOf(target) { return {a:1} } }); Object.getPrototypeOf(proxy); // {a:1}
特殊情况:
如果目标对象是不可扩展的,那么只能返回默认的原型对象。
10.setPrototypeOf(target, proto)
返回:严格模式下返回true,否则报错;只能是布尔值
作用:拦截设置原型对象的操作
拦截操作:Object.setPrototypeOf(proxy, proto)
proxy.__proto__
Reflect.setPropertyOf(proxy,proto)
语法:
var proxy = new Proxy({}, { setPrototypeOf(target, proto) { Reflect.setPrototypeOf(...arguments); return true; // !!!严格模式下必须是true;否则报错 } })
特殊情况:
如果对象不可扩展,只能进行默认行为。不能修改。
11. isExtensible(target)---只能返回默认行为结果
返回:布尔值
作用: 拦截是否可扩展方法的调用
拦截操作: Object.isExtensible(proxy)
Reflect.isExtensible(proxy)
语法:
const proxy = new Proxy({}, { isExtensible(target) { return Reflect.isExtensible(target); //!!!返回结果只能是这个;即proxy和target的可扩展性必须是一致的 } })
12. preventExtensible(target)--遵循默认行为
返回:严格模式下返回true,否则报错;
拦截操作:Object.preventExtensible(proxy)
Reflect.preventExtentsible(proxy)
语法:
const proxy = new Proxy({}, { preventExtensions(target) { Reflect.preventExtensions(target); //严格模式下,必须存在 return true; // 严格模式下必须存在 } })
13. getOwnPropertyDescriptor(target, propKey)
返回: 对象或者undefined
拦截操作: Object.getOwnPropertyDescriptor(proxy)
Reflect.getOwnPropertyDescriptor(proxy)
语法:
const proxy = new Proxy({}, { getOwnPropertyDescriptor(target, propKey) { return Reflect.getOwnPropertyDescriptor(target,propKey); } })
三. 静态方法-Proxy.revocable()
作用: 返回一个可取消的proxy实例
语法:
const {proxy, revoke} = Proxy.revocable(target,handler); // proxy是实例 // revoke是个方法;直接调用后取消proxy实例
示例:
const {proxy, revoke} = Proxy.revocable({},{}); proxy.a=5; revoke(); proxy.a // Uncaught TypeError: Cannot perform ‘get‘ on a proxy that has been revoked
应用:
用于一个只能访问一次的代理器
四. this指向
拦截器方法中的this指向proxy实例
五.Proxy应用
观察者模式:通过拦截对象的赋值操作,实时调用观察函数。
以上是关于Proxy详解的主要内容,如果未能解决你的问题,请参考以下文章