目录
- 概述
- proxy实例的方法
- 应用例子
概述
Proxy在目标对象前设一个拦截层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制可以对外界的访问进行过滤和改写。
对空对象进行拦截
/**
* 对空对象进行拦截,重定义了
*/
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target,key,value,receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1;
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面的代码说明,Proxy实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
get方法拦截读取操作
/**
* get方法用于拦截某个属性的读取操作
*/
var person = {
name: "张三"
};
var proxy = new Proxy (person, {
get: function (target, property) {
if (property in target) {
console.log(target[property]);
}
else{
throw new ReferenceError("Property \"" + property + "\" does not exist. ");
}
}
})
proxy.name
// proxy.age
get拦截实现数组读取负数索引
/**
* 使用get拦截实现数组读取负数索引
*/
function createArray(...elements) {
let handler = {
get(target, propsKey, receiver){
let index = Number(propKey);
if (index < 0){
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
利用get拦截实现一个生成各种DOM节点的通用函数dom
/**
* 利用get拦截实现一个生成各种DOM节点的通用函数dom
*/
const dom = new Proxy({},{
get(target,property){
return function (attrs{}, ...children) {
// 创建元素节点
const el = document.createElement(property);
for(let prop of Object.keys(attrs)){
el.setAttribute(prop,attrs[prop]);
}
// 创建文本节点
for(let child of children){
child = document.createTextNode(child);
}
el.appendChild(child);
}
return el;
}
});
const el = dom.div({},'hello,my name is',
dom.a({href:'//example.com'},'Mark'),',I like:',
dom.ul({},
dom.li({},'the web')),
dom.li({},'Food'),
dom.li({},'actually')
)
set方法用于拦截某个属性的赋值操作
假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求
/**
* 假定Person对象有一个age属性,该属性应该是一个不大于200的整数,
* 那么可以使用Proxy对象保证age的属性值符合要求
*/
let validator = {
set: function (obj,prop,value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
//对于age以外的属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
结合get和set方法,防止内部属性被外部读/写
/**
* 结合get和set方法,防止内部属性被外部读/写
*/
var handler = {
get (target, key){
invariant(key, 'get');
return target[key];
},
set (target,key,value){
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant(key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action}
private "${key}" property`);
}
}
var target = {};
var proxy = new Proxy(target, handler);
proxy._prop
//Error :Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
上面的代码中,只要读/写的属性名的第一个字符是下划线,一律抛出错误,从而达到禁止读/写内部属性的目的。
Proxy.revocable()
该方法返回一个可取消的Proxy实例。
//proxy.revocable()
let target = {};
let handler = {};
// 返回一个对象,proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo //TypeError: Revoked
该方法的一个使用场景是:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
this问题
虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下也无法保证与目标对象的行为一致。主要原因是在Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理。