Day 9
代理与反射
代理基础:
- 代理是目标对象的抽象,可以当做是目标对象的替身,但完全独立于目标对象,目标对象可以被直接操作,也可以通过代理来操作
创建空代理:
最简单的代理是空代理,就是说除了作为一个抽象的目标对象,什么也不做,在代理对象是执行的所有操作都会无障碍的传播到目标对象,因此,任何使用目标对象的地方,都可以通过同样的方式使用与之关联的代理对象
例子1:
const target = { id: 'target' };
const handler = {};
const proxy = new Proxy(target, handler);
// id属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target
// 给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo
// 给代理属性赋值会反映在两个对象上,因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar
// hasOwnProperty()方法在两个地方 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true
小结:这是最简单的一个空代理的案例,初看可能会不明所以,我当初看也很懵,但是不要着急,你现在只需要看懂,知道代理做了什么事情,在这个例子里,target是代理的目标,proxy是代理,代理的目标和代理属性共享,操作共享,包括方法也是共享的,但是代理并不等于代理目标,更倾向于操作代理时,代理把操作转发给了代理的目标,然后进行操作,目标拥有的,代理会拥有,代理拥有的,也会相应的传递给目标;
代码捕获器与反射方法:
使用代理的「目的」 :是可以定义「捕获器」(trap),捕获器就是可以直接或间接在代理对象上调用,每次在代理对象上调用这些基本操作时, 代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦 截并修改相应的行为。
- 通俗讲就是,捕获器在代理对象被调用时,先执行,从而拦截并且修改相应的行为,就是类似加了一层if判断,或者说是弹框确认
「handler」 :代理的处理对象,例子1中是一个空对象,多数情况下并不是空对象,而是定义了一个或者多个捕获器(trap)去处理代理,如果没有定义,则和上例中的空对象一样,使用默认行为。
「set trap」 :常用的trap,触发条件是在设置属性值的时候触发,
「Reflect.set」: 将值分配给属性的函数。返回一个
Boolean
,如果更新成功,则返回true
首先set trap接受4个参数,
trapTarget - 接收的属性的对象,就是**代理的目标 **
key - 要写入的属性的「键」
value- 写入属性的「值」
receiver- 操作的对象,通常是「代理」
例子2:用set trap验证一个属性的值是否为number
let target = {
name: "target"
};
let proxy = new Proxy(target, {
//target,name,target,proxy;
set(trapTarget, key, value, receiver) {
console.log(`trapTarget is ${trapTarget}, key is ${key}, value is ${value}, receiver is ${receiver}`)
// 忽视存在的属性,以免产生影响,不存在的属性才会进入判断
if (!trapTarget.hasOwnProperty(key)) {
//判断值是否为number
if (isNaN(value)) {
throw new TypeError("Property must be a number.");
}
}
// 添加到属性
return Reflect.set(trapTarget, key, value, receiver);
}
});
// 添加一个新的属性
proxy.count = 1;
console.log(proxy.count); // 1
console.log(target.count); // 1
// 赋值给存在target上的属性
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
// 新的属性值不是数字会抛出异常
proxy.anotherName = "proxy";
小结:这个例子打印出了4个参数的时刻变化,可以清晰的看到参数对应的值,可以发现,每次设置属性值的时候都会进行拦截判断,所以在获取时候,可以用get进行拦截判断,
「get trap」:
get 是读取对象属性的时候用到的trap,他接受3个参数
trapTarget: 从哪个对象读取的属性,就是target
key
- 读取的key
receiver
- 操作的对象,通常是代理(proxy)例子1:这是一个普通的get代理的按理
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
小结:p.a的时候,获取代理里面a的值经过get,返回了 a的值,p.b一样,然后c in p,因为p没有c这个属性,所以会返回false,p.c像p获取c属性,进入get,赋值为37,
再来看一个set 和get的综合例子
例子3;
var p = {
name:"chen",
work:function() {
console.log("wording...");
},
_age:18,
//监听获取name属性的事件
get name(){
return this._age;
},
//监听设置age属性的事件
set age(val) {
if (val<0 || val> 100) {//如果年龄大于100就抛出错误
throw new Error("invalid value")
}else{
this._age = val;
}
}
};
console.log(p.name);//输出18,因为这里触发了监听name属性的拦截,所以返回的age,
console.log(p._age);//这里的age没有获取拦截,所以输出原值
console.log(p._age =20);//输出20,因为这里进入设置拦截,满足条件,完成赋值
console.log(p._age =200);//报错,设置拦截进入,不满足条件,报错
console.log(p.name = 'yu')//输出 yu,因为name没有设置拦截,所以可以成功
小结:看完这个,代理的基本知识及其原理相信大家都明白了,其实捕获器还有很多,列如has等等,但是基本原理都是这样,只要把握住参数分别代表什么,捕获器的触发条件,问题都可以迎刃而解了,希望可以给大家带来帮助
本文使用 mdnice 排版