Proxy基础知识

  • 本章节介绍一下Proxy部分,在此以前须要了解一下元编程

一. 元编程

  • 元编程是指某类计算机程序的编写,这类计算机程序编写或者操纵其余程序(或者自身)做为他们的数据,或者在运行时完成部分本应在编译时完成的工做。不少状况下与手工编写所有代码相比工做效率更高。
  • 编写元程序的语言称为元语言,被操纵的语言被称为目标语言,一门语言是自身的元语言的能力被称为反射!
  • 从ES5开始,js得到对Proxy,Reflect对象的支持,容许拦截并定义基本语言操做的自定义行为,例如(属性查找,赋值,枚举,函数调用等),借助 Proxy,Reflect这两个对象,能够在js进行元编程

二. 概述

1. 基本含义

  • Proxy用于修改某些操做的默认行为,等同于在语言层面作出修改,因此属于元编程(meta programming),即对编程语言进行编程
  • Proxy能够理解为在目标对象以前架设一层拦截,外界对该对象的访问,都必须先经过这层拦截。
  • 所以能够对外界的访问进行过滤和改写,Proxy的本意就是代理,在js中就是表示用Proxy来代理某些操做,做为代理器
  • 一个简单的get读取操做拦截的例子
// 声明一个拦截器对象,属性就是要监听的对象属性
 var handler={  get:function(target,name){  if(target.hasOwnProperty(name)){  return `对象包含有${name}属性`  }else{  return `对象没有${name}属性`  }  }  }  // 给obj对象绑定拦截器,而后建立拦截器实例  var obj={name:'yiye',f:()=>{}}  var p=new Proxy(obj,handler)  // 1. 直接使用Proxy实例的属性  console.log(p.name);//对象包含有name属性  console.log(p.age);//对象没有age属性  // 1.2 给Proxy实例赋值(可是被拦截了,因此修改不生效,除非Proxy去修改)  p.name="改变"  console.log(p.name);//对象包含有name属性  // 2.此时没有拦截函数,因此会提示不是函数  console.log(p.f());//p.f is not a function  // 3. 若是调用对象不存在的函数则会提示变量不是函数  console.log(p.foo());//p.foo is not a function 复制代码
  • 能够把proxy做为对象属性绑定到对象中 var obj={proxy:new Proxy(target,handler)}
  • proxy有也能够做为其余对象的原型对象,Object.create(obj)
var handler={
 get:function(target,proxyKey){  return 35  }  }  var a={name:'hh'}  // 经过obj.proxy.xxx的形式使用代理器  // new Proxy(obj,handler)的形式建立代理器  var obj={proxy:new Proxy(Object.create(a),handler)}  console.log(obj.proxy.name);//35  console.log(obj.proxy.age);//35 复制代码

2. 参数

  • new Proxy(target,handler)接收两个参数,表示目标对象和内部监听对象,handler为{}空对象表示不设置拦截,至关于直接访问原对象
  • handler内部监听的方法为set的时候有三个参数,(target,name,value),target表示目标对象,name表示属性名,value表示值
  • handler监听器监听的方法不同的时候,参数个数和含义也都不同
  • 下面是一个监听set操做的例子
// 声明一个拦截器
 var handler={  set:function(target,name,value){  target[name]="set+"+value;  }  }  // 给obj对象绑定拦截器,而后建立拦截器实例  var obj={name:'yiye',f:()=>{}}  var p=new Proxy(obj,handler)  // 1. 直接使用Proxy实例的属性  console.log(p.name);//yiye  // 1.2 给Proxy实例赋值  p.name="改变"  console.log(p.name);//set+改变 复制代码

3.proxy做为一种设计模式

  • 程序设计中存在一种设计模式为代理模式,Proxy Pattern
  • 所谓的代理者是指一个类别能够做为其余东西的接口,代理者能够做为任何东西的接口:网络链接,内存中的大对象,文件或其余昂贵或没法复制的资源。
  • 垃圾回收机制中的引用计数方法就是使用了代理模式
  • 当一个对象被引用时,建立代理者,每一个代理者都会引用到对象。而做用在代理者的运算会传递到对象中。一旦全部的代理者都不存在,那么对象就会被清除,也就是当成垃圾回收了

三.Proxy实例的方法

  • proxy实例的方法共有13种,在此只介绍使用较多的几种。

1. get()

  • get方法用于拦截对象属性的读取操做,接受三个参数,依次为目标对象、属性名和 proxy 实例自己(严格地说,是操做行为所针对的对象),其中最后一个参数可选。
  • 访问目标对象不存在的属性,也会执行到proxy监听的get读取操做若是不存在拦截器,那么返回的是undefined
  • 监听的get方法能够继承!
var a={name:'a'}
 var proxy=new Proxy(a,{  get(target,name,value){  if(target.hasOwnProperty(name)) return 'get+'+target[name]  return '不存在该属性'  }  })  var child=Object.create(proxy)  console.log(child);//{name: "eee",__proto__:Proxy},此处的Proxy是监听器对象Proxy,而不是函数  console.log(child.__proto__);//不存在该属性  console.log(child.prototype);//不存在该属性  // 当对象child不存在属性name的时候,指向原型对象的属性  console.log(child.name);//get+a  // 此时给对象添加属性name,那么就指向对象自身的属性  child.name='eee'  console.log(child.name);//eee 复制代码
  • 使用proxy完成链式调用
var pipe = function (value) {
 var funcStack = []; // 存储函数  var oproxy = new Proxy({} , {  get : function (pipeObject, fnName) {  if (fnName === 'get') {  return funcStack.reduce(function (val, fn) {  return fn(val);  },value);  }  funcStack.push(window[fnName]);  return oproxy; // 返回proxy代理器  }  });  return oproxy; // 返回proxy代理器  }   var double = n => n * 2;  var pow = n => n * n;  var reverseInt = n => n.toString().split("").reverse().join("") | 0; // 数字反转  // 至关于 reverseInt(pow(double(3)));//3*2=6,6*6=36,36的倒序为63  console.log(pipe(3).double.pow.reverseInt.get) // 63 复制代码

2. set()

  • set方法有四个参数,分别是目标对象,属性名,属性值,proxy实例自己,最后一个参数可选
  • 一个实例:age属性的值大于200就提示错误
var obj={age:10,name:'yiye'}
 var person=new Proxy(obj,{  set:function(newobj,name,val,pro){  if(name==='age'){  if(val>200){  throw new Error("this age is too max")  }  }  newobj[name]=val;// 更新  }  })  console.log(person);//Proxy {age: 10, name: "yiye"}  console.log(obj);//{age: 10, name: "yiye"}   // 直接修改目标对象的属性  obj.age=210  console.log(obj);//{age: 210, name: "yiye"}  console.log(person);//Proxy {age: 210, name: "yiye"}   // 修改proxy代理对象的属性(也生效!)  person.age=20;// 此时都不会报错,而且数值一直  console.log(person);//Proxy {age: 20, name: "yiye"}  console.log(obj);//{age: 20, name: "yiye"}  // person.age=220;// 此时超过限制  console.log(obj); //Uncaught Error: this age is too max  console.log(person); //Uncaught Error: this age is too max 复制代码

限制

  • 当对象的某个属性不可写,那么set方法的监听将失效
var obj={age:10,name:'yi'}
 Object.defineProperty(obj,'foo',{  writable:false,  value:'foo'  })  var person=new Proxy(obj,{  set:function(target,name,value,receiver){  target[name]="set+"+value;  }  })  console.log(person);//Proxy {age: 10, name: "yi", foo: "foo"}  // 修改foo属性  person.foo="f"  // 可是是修改不生效,依旧为foo!  console.log(person);//Proxy {age: 10, name: "yi", foo: "foo"}   // 此时修改其余属性,会生效的!  person.age=10000  person.name='good'  console.log(person);//Proxy {age: "set+10000", name: "set+good", foo: "foo"} 复制代码

3. apply()

  • apply方法拦截的有三种,函数的调用!!!,call绑定,apply绑定!
  • 接受三个参数,分别是目标对象,目标对象的上下文,目标对象的参数数组
// 1. 建立一个监听apply操做的代理器
 var sum=function(a,b){  return a+b  }  var proxy=new Proxy(sum,{  apply:function(target,ctx,args){  console.log(target,ctx,args);  /*  ƒ (a,b){  return a+b  } undefined (2) [1, 2]  */  return sum(...args);  }  })  // 2.函数调用  console.log(proxy(1,2));//3  // 3. call调用  console.log(proxy.call(null,2,3));//5  // 4. apply调用  console.log(proxy.apply(null,[4,5]));//9 复制代码

三.Proxy.revocable()

  • Proxy.revocable()返回一个可取消的Proxy实例
  • 返回的是一个对象,对象的proxy属性是proxy实例,revoke属性是一个函数,能够取消proxy实例,执行revoke函数以后,再访问proxy实例,就会抛出一个错误
var obj={age:22,name:'ww'}
 var handler={}  var {proxy,revoke}=Proxy.revocable(obj,handler);  console.log(proxy);//Proxy {age: 22, name: "ww"}  // 执行revoke函数,取消代理  revoke();  console.log(proxy);//Proxy {}  // 此时再访问proxy实例的属性会报错提示代理已被取消  console.log(proxy.age);// Cannot perform 'get' on a proxy that has been revoked 复制代码

四. this指向问题

  • proxy会进行代理,可是这种代理不会使得this指向改变
  • 若是在监听的方法中不对this指向作绑定,那么使用的是this指向的规则
var obj={
 m:function(){  console.log(this===proxy)  }  }  var handler={}  var proxy=new Proxy(obj,handler)  // obj对象调用m属性方法,因此内部this指的是obj  obj.m();//false  // proxy代理器实例对象调用m属性方法,因此内部this指向的是proxy  proxy.m();//true 复制代码
  • 若是须要绑定this指向target目标对象,那么就
const target = new Date('2015-01-01');
 const handler = {  get(target, prop) {  if (prop === 'getDate') {  return target.getDate.bind(target);  }  return Reflect.get(target, prop);  }  };  const proxy = new Proxy(target, handler);  console.log(target.getDate());//1  // 此时即便是proxy对象调用方法,属性内部的this依旧指向目标对象target  console.log(proxy.getDate()) // 1 复制代码
相关文章
相关标签/搜索