ES6 proxy

// 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程 ;
// 即对编程语言进行编程 ;
// 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 } ;
  } , 
 } ) ;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值