代理就是一种由你创建的特殊对象,它“封装”另一个普通对象,或者说挡在这个普通对象前面,你可以在代理对象上注册特殊的处理函数(trap),代理上执行各种操作的时候会调用这个程序
1 2 3 4 5 6 7 8 9 10 11 12 var obj={a :1 }, handles={ get (target,key,context ) { console .log("accessing" ,key); return Reflect .get(target,key,context) } } pobj=new Proxy (obj,handlers); obj.a; pobj.a;
这里的映射是有意对称的,每个代理处理函数在对应的元编程任务执行的时候进行拦截,而每个Reflefct工具在一个对象上执行相应的元编程任务,每个代理函数都有一个自动调用相应的Reflect工具的默认定义。
一、介绍 定义: 用于定义基本操作的自定义行为
本质: 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)
元编程(Metaprogramming,又译超编程 ,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
一段代码来理解
1 2 3 4 5 6 7 1 #!/bin/ bash2 # metaprogram3echo '#!/bin/bash' >program 4for ((I=1 ; I<=1024 ; I++)) do 5 echo "echo $I" >>program6done 7chmod +x program
这段程序每执行一次能帮我们生成一个名为program
的文件,文件内容为1024行echo
,如果我们手动来写1024行代码,效率显然低效
元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率 ,或者给与程序更大的灵活度去处理新的情形而无需重新编译
Proxy
亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
二、用法 Proxy
为 构造函数,用来生成 Proxy
实例
1 1var proxy = new Proxy(target, handler)
参数 target
表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
handler
通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为
handler解析 关于handler
拦截属性,有如下:
get(target,propKey,receiver):拦截对象属性的读取
set(target,propKey,value,receiver):拦截对象属性的设置
has(target,propKey):拦截propKey in proxy
的操作,返回一个布尔值
deleteProperty(target,propKey):拦截delete proxy[propKey]
的操作,返回一个布尔值
ownKeys(target):拦截Object.keys(proxy)
、for...in
等循环,返回一个数组
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)
,返回一个布尔值
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 实例作为函数调用的操作
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
Reflect 若需要在Proxy
内部调用对象的默认行为,建议使用Reflect
,其是ES6
中操作对象而提供的新 API
基本特点:
只要Proxy
对象具有的代理方法,Reflect
对象全部具有,以静态方法的形式存在
修改某些Object
方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
)
让Object
操作都变成函数行为
下面我们介绍proxy
几种用法:
get() get
接受三个参数,依次为目标对象、属性名和 proxy
实例本身,最后一个参数可选
1 2 3 4 5 6 7 8 9 10 11 1var person = { 2 name: "张三" 3 };4 5var proxy = new Proxy (person, { 6 get: function (target, propKey ) {7 return Reflect .get(target,propKey)8 }9 });10 11proxy.name
get
能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1function createArray (...elements ) { 2 let handler = {3 get (target, propKey, receiver ) {4 let index = Number (propKey);5 if (index < 0 ) {6 propKey = String (target.length + index);7 }8 return Reflect .get(target, propKey, receiver);9 }10 };11 12 let target = [];13 target.push(...elements);14 return new Proxy (target, handler);15 }16 17let arr = createArray('a' , 'b' , 'c' ); 18arr[-1 ]
注意:如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1const target = Object .defineProperties({}, { 2 foo: {3 value: 123 ,4 writable: false ,5 configurable: false 6 },7 });8 9const handler = { 10 get (target, propKey ) {11 return 'abc' ;12 }13 };14 15const proxy = new Proxy (target, handler); 16 17proxy.foo 18
set() set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy
实例本身
假定Person
对象有一个age
属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy
保证age
的属性值符合要求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1let validator = { 2 set: function (obj, prop, value ) {3 if (prop === 'age' ) {4 if (!Number .isInteger(value)) {5 throw new TypeError ('The age is not an integer' );6 }7 if (value > 200 ) {8 throw new RangeError ('The age seems invalid' );9 }10 }11 12 13 obj[prop] = value;14 }15 };16 17let person = new Proxy ({}, validator); 18 19person.age = 100 ; 20 21person.age 22person.age = 'young' 23person.age = 300
如果目标对象自身的某个属性,不可写且不可配置,那么set
方法将不起作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1const obj = {}; 2Object .defineProperty(obj, 'foo' , { 3 value: 'bar' ,4 writable: false ,5 });6 7const handler = { 8 set: function (obj, prop, value, receiver ) {9 obj[prop] = 'baz' ;10 }11 };12 13const proxy = new Proxy (obj, handler); 14proxy.foo = 'baz' ; 15proxy.foo
注意,严格模式下,set
代理如果没有返回true
,就会报错
1 2 3 4 5 6 7 8 9 10 11 1 'use strict' ;2const handler = { 3 set: function (obj, prop, value, receiver ) {4 obj[prop] = receiver;5 6 return false ;7 }8 };9const proxy = new Proxy ({}, handler); 10proxy.foo = 'bar' ; 11
deleteProperty() deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1var handler = { 2 deleteProperty (target, key) {3 invariant(key, 'delete' );4 Reflect .deleteProperty(target,key)5 return true ;6 }7 };8function invariant (key, action) { 9 if (key[0 ] === '_' ) {10 throw new Error (`无法删除私有属性` );11 }12 }13 14var target = { _prop : 'foo' }; 15var proxy = new Proxy (target, handler); 16delete proxy._prop 17
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty
方法删除,否则报错
Reflect.apply() 静态方法 **Reflect**
.apply()
通过指定的参数列表发起对目标(target)函数的调用。
1 Reflect.apply(target, thisArgument, argumentsList)
参数
返回值 返回值是调用完带着指定参数和 this
值的给定的函数后返回的结果。
可取消代理 普通代理总是陷入到目标对象,并且在创建之后不能修改——只要还保持着对这个代理的引用,代理的机制就将维持下去,如果你想要的创建一个在你想要停止它作为代理时便可以被停止的代理,可以创建可取消代理。(revocable proxy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var obj={a :1 },handlers={ get (target,key,context ) { console .log('accessing:' ,key); return target[key]; } }; var p=Proxy .revocable(obj,handlers);const pobj=p.proxy;const prevoke=p.revoke;console .log(pobj.a);prevoke(); pobj.a()
可取消代理可能的应用场景:在应用中把代理分发到第三方,其中管理你的模型数据,而不是给出真实模型本身的引用。如果你的模型对象改变或被替换,就可以使分发出去的代理失效,这样第三方就可以知晓变化并请求更新到这个模型的引用。
使用代理: 代理在前: 代理与目标交流,首先与代理交互,通过与代理交互来增加某些特殊的规则,这些是message本身没有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var messages=[],handlers={ get (target,key ) { if (typeof target[key]=='string' ){ return target[key].replace(/[^w]/g ,"" ); } return target[key]; }, set (target,key,val ) { if (typeof val=='string' ){ if (target.indexOf(val)==-1 ){ target.push(val.toLowerCase()); } } return true ; } } var messages_proxy=new Proxy (messages,handlers);messages_proxy.push('heLLo...' ,42 ,"wOrlD!!" ,"WoRld!!" ); messages_proxy.forEach(val => { console .log(val); }) messages.forEach(v =>console .log(v));
代理在后: 让目标与代理交流,而不是代理与目标交流。代理只能与主对象交互,实现方式就是把proxy对象放到主对象的[[Prototype]]链中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var handlers={ get (target,key,context ) { return function ( ) { context.speak(key+"!" ) } } }, catchall=new Proxy ({},handlers), greeter={ speak (who="someone" ) { console .log("hello" ,who) } } Object .setPrototypeOf(greeter,catchall);greeter.speak(); greeter.speak("world" ); greeter.everyone();
访问不存在的属性名抛出错误 代理在前 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var obj={ a :1 , foo ( ) { console .log("a:" ,this .a) } }, handlers={ get (target,key,context ) { if (Reflect .has(target,key)){ return Reflect .get(target,key,context) } else { throw Error ("no such property/method" ) } }, set (target,key,val,context ) { if (Reflect .has(target,key)){ return Reflect .set(target,key,val,context) }else { throw Error ("no such property/method" ) } } }, pobj=new Proxy (obj,handlers) pobj.a=3 ; pobj.foo(); pobj.b=4 ;
代理在后: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var handlers={ get ( ) { throw "no such property/method" }, set ( ) { throw "no such property/method" } } var pobj=new Proxy ({},handlers);var obj={ a :1 , foo ( ) { console .log("a:" ,this .a) } } Object .setPrototypeOf(obj,pobj);obj.a=3 ; obj.foo(); obj.b=4 ;
代理实现多个[[Prototype]]链接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 var obj1={ name :"obj-1" , foo ( ) { console .log("obj1.foo" ,this .name) } },obj2={ name :"obj-2" , foo ( ) { console .log("obj2.foo" ,this .name) }, bar ( ) { console .log("obj2.bar" ,this .name) } }, handlers={ get (target,key,context ) { if (Reflect .has(target,key)){ return Reflect .get(target,key,context) }else { for (let p of target[Symbol .for("[[Prototype]]" )]){ if (Reflect .has(p,key)){ return Reflect .get(p,key,context) } } } } }, obj3=new Proxy ({ name :"obj3" , baz ( ) { this .foo(); this .bar() } },handlers) obj3[Symbol .for("[[Prototype]]" )]=[obj1,obj2]; obj3.baz();