- 本章节介绍一下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}属性`
}
}
}
var obj={name:'yiye',f:()=>{}}
var p=new Proxy(obj,handler)
console.log(p.name);
console.log(p.age);
p.name="改变"
console.log(p.name);
console.log(p.f());
console.log(p.foo());
- 可以把proxy作为对象属性绑定到对象中
var obj={proxy:new Proxy(target,handler)} proxy有也可以作为其他对象的原型对象,Object.create(obj)
var handler={
get:function(target,proxyKey){
return 35
}
}
var a={name:'hh'}
var obj={proxy:new Proxy(Object.create(a),handler)}
console.log(obj.proxy.name);
console.log(obj.proxy.age);
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;
}
}
var obj={name:'yiye',f:()=>{}}
var p=new Proxy(obj,handler)
console.log(p.name);
p.name="改变"
console.log(p.name);
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);
console.log(child.__proto__);
console.log(child.prototype);
console.log(child.name);
child.name='eee'
console.log(child.name);
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;
}
});
return oproxy;
}
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
console.log(pipe(3).double.pow.reverseInt.get)
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);
console.log(obj);
obj.age=210
console.log(obj);
console.log(person);
person.age=20;
console.log(person);
console.log(obj);
console.log(obj);
console.log(person);
限制
- 当对象的某个属性
不可写,那么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);
person.foo="f"
console.log(person);
person.age=10000
person.name='good'
console.log(person);
3. apply()
apply方法拦截的有三种,函数的调用!!!,call绑定,apply绑定!- 接受三个参数,分别是目标对象,目标对象的上下文,目标对象的参数数组
var sum=function(a,b){
return a+b
}
var proxy=new Proxy(sum,{
apply:function(target,ctx,args){
console.log(target,ctx,args);
三.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);
revoke();
console.log(proxy);
console.log(proxy.age);
四. this指向问题
- proxy会进行代理,但是这种代理
不会使得this指向改变 - 如果在监听的方法中不对this指向做绑定,那么使用的是this指向的规则
var obj={
m:function(){
console.log(this===proxy)
}
}
var handler={}
var proxy=new Proxy(obj,handler)
obj.m();
proxy.m();
如果需要绑定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());
console.log(proxy.getDate())