js之数据代理和反射

40 阅读5分钟

ES6新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象种对这些操作加以控制。

代理

创建代理

代理使用proxy构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。

const target = {
    id:'target'
}
const proxy = new Proxy(target,{
    
});
console.log(target.id); // target
console.log(proxy.id); // target
console.log(target.id === proxy.id); // target

定义捕获器

使用代理的主要目的是可以定义捕获器。捕获器就是在处理程序对象种定义的“基本操作的拦截器”。

const target = {
    id:'target'
}
const proxy = new Proxy(target,{
    get(){
    	return "proxy";
    }
});
console.log(target.id); // target
console.log(proxy.id); // proxy

捕获器参数

所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。比如get()捕获器会接收到目标对象、要查询的属性和代理对象三个参数。

const target = {
    id:'target'
}
const proxy = new Proxy(target,{
    get(p_target,property,p_proxy){
        console.log(p_target == target)
        console.log(property)
        console.log(p_proxy == proxy)
    	return p_target[property];
    }
});
proxy.id
//true
//id
// true

反射

反射API

所有捕获器都可以基于自己的参数重建原始操作,但并非所有捕获器行为都像get()那么简单。因此可以通过调用全局Reflect对象上(封装了原型行为)的同名方法来重建。

处理程序对象种所有可以捕获的方法都有对应的反射API的方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有被拦截方法相同的行为。

const target = {
    id:'target',
    name:'target'
}
const proxy = new Proxy(target,{
    get(p_target,property,p_proxy){
        let name = '';
        if(property == 'name'){
            name = "atuotuo";
        }
    	return Reflect.get(...arguments) + name;
    }
});
console.log(proxy.id) //target
console.log(proxy.name) //targetatuotuo

实用反射API

  1. 反射API与对象API:Object上的方法适用于通用程序,而反射方法适用于细粒度的对象控制与操作。

  2. 状态标记:很多反射方法返回标记,表示意图执行的操作是个否成功。

  3. 用一等函数代替操作符:

    Reflect.get():代替对象属性访问操作符

    Reflect.set():代替赋值=

    Reflect.has():代替in

    Reflect.deleteproperty():代替delete

    Reflect.construct():代替new

  4. 安全地应用函数:在通过apply方法调用函数时,被调用的函数可能也定义了自己的apply属性。为了绕过这个问题,可以使用定义Fcuntion原型上的apply方法。

代理捕获器与反射方法

get()

get()捕获器会在获取属性值的操作中被调用。对用的反射API方法为Reflect.get();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    get(p_target,property,p_proxy){
    	return Reflect.get(...arguments); 
    }
});
proxy.id // target

set()

set()捕获器会在设置属性值的操作中被调用。对用的反射API方法为Reflect.set();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    set(p_target,property,value,p_proxy){
        console.log("set()")
    	return Reflect.set(...arguments); 
    }
});
proxy.id = 'tar';//set()

set()

get()捕获器会在in操作符中被调用。对用的反射API方法为Reflect.has();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    set(p_target,property){
        console.log("has()")
    	return Reflect.has(...arguments); 
    }
});
'id' in proxy //has()

defineProperty()

defineProperty() 捕获器会在Object.defineProperty()中被调用。对用的反射API方法为Reflect.defineProperty();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    defineProperty(p_target,property,descriptor){
        console.log("defineProperty()")
    	return Reflect.defineProperty(...arguments); 
    }
});
Object.defineProperty(proxy,'id',{
    value:'tar'
});// defineProperty()

getOwnPropertyDescriptor()

getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用。对用的反射API方法为Reflect.getOwnPropertyDescriptor();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    getOwnPropertyDescriptor(p_target,property){
        console.log("getOwnPropertyDescriptor()")
    	return Reflect.getOwnPropertyDescriptor(...arguments); 
    }
});
Object.getOwnPropertyDescriptor(proxy,'id');// getOwnPropertyDescriptor()

deleteProperty()

deleteProperty()捕获器会在Object.deleteProperty()中被调用。对用的反射API方法为Reflect.deleteProperty();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    deleteProperty(p_target,property){
        console.log("deleteProperty()")
    	return Reflect.deleteProperty(...arguments); 
    }
});
delete proxy.id// deleteProperty()

ownKeys()

ownKeys()捕获器会在 Object.keys()、Object.getOwnPropertyName、Object.getOwnPropertySymbols、Reflect.ownKeys中被调用。对用的反射API方法为Reflect.ownKeys();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    ownKeys(p_target){
        console.log("ownKeys()")
    	return Reflect.ownKeys(...arguments); 
    }
});
Object.keys(proxy)// ownKeys()

getPrototypeOf()

getPrototypeOf()捕获器会在 Object.getPrototypeOf()中被调用。对用的反射API方法为Reflect.getPrototypeOf();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    getPrototypeOf(p_target){
        console.log("getPrototypeOf()")
    	return Reflect.getPrototypeOf(...arguments); 
    }
});
Object.getPrototypeOf(proxy)// getPrototypeOf()

setPrototypeOf()

setPrototypeOf()捕获器会在 Object.setPrototypeOf()中被调用。对用的反射API方法为Reflect.setPrototypeOf();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    setPrototypeOf(p_target,prototype){
        console.log("setPrototypeOf()")
    	return Reflect.setPrototypeOf(...arguments); 
    }
});
Object.setPrototypeOf(proxy,Object)// setPrototypeOf()

isExtensible()

isExtensible()捕获器会在 Object.isExtensible()中被调用。对用的反射API方法为Reflect.isExtensible();判断对象是否可扩展

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    isExtensible(p_target){
        console.log("isExtensible()")
    	return Reflect.isExtensible(...arguments); 
    }
});
Object.isExtensible(proxy)// isExtensible()

preventExtensions()

preventExtensions()捕获器会在 Object.preventExtensions()中被调用。对用的反射API方法为Reflect.preventExtensions();让对象变得不可扩展

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    preventExtensions(p_target){
        console.log("preventExtensions()")
    	return Reflect.preventExtensions(...arguments); 
    }
});
Object.preventExtensions(proxy)// preventExtensions()

apply()

apply()捕获器会在调用函数时被调用。对用的反射API方法为Reflect.apply();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    apply(p_target,thisArg,...argumentList){
        console.log("apply()");
        return Reflect.apply(...arguments);
    }
});
proxy();// apply

construct()

construct()捕获器会在new操作符时被调用。对用的反射API方法为Reflect.construct();

const target = {
    id:'target',
}
const proxy = new Proxy(target,{
    construct(p_target,argumentList,newTarget){
        console.log("construct()");
        return Reflect.apply(...arguments);
    }
});
new proxy();// construct

代理模式

  1. 跟踪属性访问

    通过捕获get、set、has等操作,可以直到对象属性声明时候被访问修改以及查询过。

    const obj = {
        id:'obj',
    }
    const p = new Proxy(obj,{
        get(target,property,receiver)
        {
            console.log(`get ${property}`);
            return Reflect.get(...arguments);
        },
        set(target,property,value,receiver)
        {
            console.log(`set ${property} = ${value}`);
            return Reflect.set(...arguments);
        }
    });
    obj.id // get id
    obj.name = 'atuotuo' // set name = atuotuo
    
  2. 隐藏属性

    代理的内部实现对外部代码时不可见的。隐藏要隐藏目标对象的上属性。

    const obj = {
        id:'123',
        name:'atuotuo'
    }
    const p = new Proxy(obj,{
        get(target,property,receiver)
        {
            if(property == 'id') return undefined;
            return Reflect.get(...arguments);
        },
        set(target,property,value,receiver)
        {
            console.log(`set ${property} = ${value}`);
            return Reflect.set(...arguments);
        }
    });
    obj.id // undefined
    obj.name// atuotuo
    
  3. 属性验证

    可以根据所赋的值决定是否允许还是拒绝赋值

    const obj = {
        id:'123',
        name:'atuotuo',
        age:13
    }
    const p = new Proxy(obj,{
        get(target,property,receiver)
        {
            if(property == 'id') return undefined;
            return Reflect.get(...arguments);
        },
        set(target,property,value,receiver)
        {
            if(property == 'age' && typeof value !== 'number'){
                return false;
            }
            return Reflect.set(...arguments);
        }
    });
    
  4. 函数与构造函数参数验证

    对函数和构造函数参数进行审查

    function medium(...num){
        return num.sort()[Math.floor(num.length / 2)]
    }
    const p = new Proxy(medium,{
        apply(target,thisArg,argumentList){
            for(const arg of argumentList){
                if(typeof arg  != 'number'){
                    throw "error";
                }
            }
            return Reflect.apply(...arguments);
        }
    });
    
  5. 数据绑定与可观测对象

    通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。

    比如:还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发生信息。

    const list = [];
    function emit(newValue){
        console.log(newValue);
    }
    const p = new Proxy(list,{
        set(target,property,value,receiver)
        {
            console.log(property);
            const res = Reflect.set(...arguments);
            if(res){
                emit(Reflect.get(target,property,receiver));
            }
            return res;
        }
    });
    p.push('123'); // 123
    p.push('456'); // 456