// 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程 ; // 即对编程语言进行编程 ; // proxy可以理解为对目标对象之前设置一层拦截,外界对该对象的访问,都必须先通过 // 这层拦截,因此提供了一种机制,对外界的访问进行过滤和改写 ; var 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 ) ; } } ) ; // 下面也是一个proxy的例子 ; var proxy = new Proxy( {} , { get: function ( target , property ) { return 35 ; } , } ) ; proxy.time ; // 35 proxy.count ; // 35 proxy.number ; //35 let obj = Object.create( Proxy ) ; obj.time; // 35 // 设置目标函数和拦截函数 var target = {} ; var handler = {} ; // 新建一个拦截函数 var myProxy = new Proxy( target , handler ) ; proxy.a = 'a' ; target.a ; // a ; // 同一个拦截函数,可以设置多个拦截操作; var handler = { get: function () { if( name == 'protyper' ) { return obj.protyper ; } return 'Hello,' + name ; } , apply: function ( target , thisBinding , args ) { return args[0] ; } , constructor: function ( target , args ) { return args[1] ; } } ; var fproxy = new Proxy( function ( x , y ) { return x + y ; } , handler ) ; fproxy( 1 , 2 ) ;// 1 new fproxy( 1 , 2 ) ;// { value: 2 } fproxy.prototype === Object.prototype ;//true fproxy.foo ; //"Hello,foo" // 对于可以设置,但没有设置拦截的操作,则直接落在目标对象上,按照原先的生产方 // 式产生结果 ; 1.get( target , propKey , receiver ) ; // 拦截对象属性的读取,比如proxy.foo和proxy[foo] // 最后一个参数receive是一个对象,可选,参见下面Reflect.get的部分 ; 2.set( target , propKey , value , receiver ) ; // 拦截对象属性的设置 ,比如proxy.foo = v 或 proxy['foo'] = v , 返回一个布尔值 ; 3.has( target , propKey ) ; // 拦截propKey in proxy的操作,并且返回一个布尔值 ; 4.deleteProperty // 拦截一个delete proxy[propKey]的操作,返回一个布尔值 ; 5.ownKeys ; // 拦截Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy) // 返回一个数组,该方法返回目标对象所有属性的属性名,而Object.keys仅返回目标对象所有自身可遍历的属性; 6.getOwnPropertyDescriptor( target , propKey ) ; // 该方法返回一个目标对象的描述对象; 7.definedProperty( target , propKey , propDscr ) ; // 拦截Object.definedProperty( proxy , propKey , propDesc ),Object.definedProperties( propKeys, // propDescs ) , 返回一个布尔值 ; 8.perventExtensions( target ) ; // 拦截preventExtensions( proxy ) , 并返回一个布尔值 ; 9.getPrototypeOf( target ) ; // 拦截getPrototypeOf( proxy ) , 并返回一个对象 ; 10.isExtensible( target ) ; // 拦截isExtensible( proxy ) , 返回一个布尔值 ; 11.setPrototypeOf( target ) ; // 拦截setPrototypeOf( proxy ) , 返回一个布尔值 ; 12.apply( target , object , ...args ) ; // 拦截Proxy作为实例的作为函数调用的操作,比如Proxy( ...args ) , Proxy.call( object , ...args ) , // Proxy.apply( ... ) ; 13.canstruct( target , args ) ; // 拦截Proxy实例作为构造函数调用的操作,比如new Proxy( ...args ) ; 二、Proxy实例的方法 1.get方法用于拦截某个属性的读取操作 ; var person = { name: '张三' , } var proxy = new Proxy( person , { get: function ( target , property ) { if ( property in target ) { return target[ property ] ; } else { throw new ReferenceError( 'property\'' + property + '\'does not Exist.' ) ; } } } ) ; Proxy.name ; //张三 proxy.age ; // ReferenceError ; // 上面的代码表示,如果访问的目标对象的属性不存在,则抛出一个错误 ; 2.get方法可以继承 ; let proto = new Proxy( {} , { get( target , propertyKey , receiver ) { console.log( 'GET' + propertyKey ) ; return target[ propertyKey ] ; } , } ) ; let obj = Object.create( proto ) ; obj.xxx ; // 'GET xxx' ; // 上面的代码中,拦截操作定义在prototype对象上面,所以如果读取Obj对象继承的属性时,拦截会生效; // 下面的例子使用get拦截,实现数组读取负数的索引; function createArray( ...elements ) { let handler = { get( target , propKey , receiver ) { let index = Number( propKey ) ; if ( index < 0 ) { propKey = String( target.length + index ) ; } return Reflect.get( target , propKey , receiver ) ; } } ; let target = [] ; target.push( ...elements ) ; return new Proxy( target , handler ) ; } let arr = createArray( 'a' , 'b' , 'c' ) ; arr[-1] ; // c // 上面的代码中,数组的输出参数是-1就会输出数组的最后一个成员 ; // 利用proxy , 可以将读取属性的操作( get ) ,转变为执行某个函数 , 从而实现属性的链式操作 ; var pipe = ( function () { return function ( value ) { var funStack = [] ; var oproxy = new Proxy( {} , { get: function ( pipeObject , fnName ) { if ( fnName == 'get' ) { return funStack.reduce( function ( val , fn ) { return fn( val ) ; } , value ) ; } funStack.push( window[ fnName ] ) ; retuen oproxy ; } , } ) ; return oproxy ; } } ()) ; 3.拦截某个属性的赋值操作 ; 假定Person对象有一个age属性,该属性是一个不大于200的整数,那么可以使用Proxy保证age属性值符合要求; let validator = { set: function ( obj , prop , value ) { if ( prop === 'age' ) { if ( !Number.isInteger( value ) ) { throw new TypeError( 'The age is not integer' ) ; } if ( value > 200 ) { throw new RangeError( 'The age seems invalid' ) ; } } obj[prop] = value ; } } ; let person = new Proxy( {} , validator ) ; person.age = 100 ; person.age ; // 100 person.age = 'young' ; // 报错 person.age = 300 ; // 报错
//上面代码中,由于设置了存值函数set ,任何不符合要求的age属性赋值 ,都会抛出一个错误 ,这是数据验证的一种 //实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM ; //有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合 //set和get方法,就可以防止这些内部属性被外部读写 ; var handler = { get ( target , key ) { invariant( key , 'get' ) ; return target[key] ; } , set ( target , key , value ) { invariant( key , 'set' ) ; target[key] = value ; return true ; } } ; function invariant ( key , action ) { if ( key[0] === '_' ) { throw new Error( `Invalid attempt to ${action} private "${key}" property` ) ; } } var target = {} ; var proxy = new Proxy( target , handler ) ; proxy._prop ; // Error : Invalid attempt to get private '_prop' property ; proxy._prop = 'c' ; // Error : Invalid attempt to set private '_prop' property ; 上面代码中,只要读写的属性名的第一个字符是下划线,一律报错,从而达到禁止读写内部属性的目的。 注意,如果目标对象的某个属性,不可写也不可配置,那么set不得改变这个属性的值,只能返回同样的值, 否则报错 ; apply ; apply方法拦截函数的调用、call和apply操作 ; apply方法可以接收三个参数,分别是目标对象,目标对象的上下文对象( this )和目标对象的参数数组; var handler = { apply ( target , ctx , args ) { return Reflect.apply( ...arguments ) ; } } ; 下面是一个例子 ; var target = function () { return 'I`m the target' ; } ; var handler = { apply: function () { return 'I`m the proxy' ; } }; var p = new Proxy( target , handler ) ; p() ; // im the proxy ; 上面代码中,变量p是Proxy的实例,当它作为函数调用时( p() ) , 就会被apply方法拦截,返回一个字符串 ; 下面是一个例子 ; var twice = { apply ( target , ctx , args ) { return Reflect.apply( ...arguments ) * 2 ; } } ; function sum ( left , right ) { return left + right ; } ; var proxy = new Proxy( sum , twice ) ; proxy( 1 , 2 ) ; // proxy.call( null , 5 , 6 ) ; // 22 proxy.apply( null , [ 7 , 8 ] ) ; // 30 另外直接调用Reflect.apply( proxy , null , [9,10] ) ; // 38 has ; has方法拦截HasProperty操作,即判断对象是否具有某个属性的时候,这个方法会生效 ;典型操作就是in运算符 ; var handler = { has ( target , key ) { if ( key[0] === '_' ) { return false ; } return key in target ; } } ; var target = { _prop: 'foo' , prop: foo } ; var proxy = new Proxy( target , handler ) ; proxy._prop ; //false ; 如果对象内部属性的第一个字符是_ , proxy则会返回false ,从而不会被in运算符发现 ; 如果对象的内部属性是不可配置或者不可扩展,in运算符则会发现,报错 ; var obj = { a: 10 } ; Object.perventExtensions( obj ) ; var p = new Proxy( obj , { has: function ( target , prop ) { return fasle ; } ; } ) ; a in p ; // TypeError is thrown ; 虽然for..in循环也用到了in运算符,但是has拦截对for..in不生效 ; let stu1 = { name: '张三' , score: 59 } ; let stu2 = { name: '李四' , score: 99 } ; let handler = { has ( target , prop ) { if ( prop === 'score' && target[prop] < 60 ) { console.log( `${target.name} 不及格` ) ; return false ; } return prop in target ; } } let oproxy1 = new Proxy( stu1 , handler ) ; let oproxy2 = new Proxy( stu2 , handler ) ; 'score' in oproxy1 ; // 张三 不及格 // false 'score' in oproxy2 ; // true for ( let a in oproxy1 ) { console.log( oproxy1[a] ) ; } for ( let b in oproxy2 ) { console.log( oproxy2[a] ) ; } 上面的代码中,因为has拦截对for..in循环不生效,导致没有拦截到不符合要求的属性 ; constructor ; constructor 方法用以拦截new命令,下面是拦截对象的写法 ; var handler = { constructor ( target , args , newTarget ) { return new target( ...args ) ; } } ; constructor方法可以接受两个参数 ; -target:目标对象 ; -args:构建函数的参数对象 ; var p = new Proxy( function () {} , { constructor: function ( target , args ) { console.log( 'called' + args.join( ',' ) ) ; return { value: args[0] * 10 } ; } , } ) ;