- 本章节介绍一下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 复制代码
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 复制代码