JavaScript Proxy 代理拦截对象操作详解
proxy
var proxy = new Proxy(target, handler);
Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
示例
let xiaohong = {
name: '小红',
age: 15,
};
xiaohong = new Proxy(xiaohong, {
get(target, key) {
let result = target[key];
// 如果是获取 年龄 属性,则添加 岁字
if (key === 'age') result += '岁';
return result;
},
set(target, key, value) {
if (key === 'age' && typeof value !== 'number') {
throw Error('age字段必须为Number类型');
}
return Reflect.set(target, key, value);
},
});
console.log(`我叫${xiaohong.name} 我今年${xiaohong.age}了`);
// xiaohong.age = "aa";
// 上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为
执行结果如下
上面代码中,作为构造函数,Proxy 接受两个参数。第一个参数是所要代理的目标对象; 第二个参数是一个配置对象, 对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
- 如果 handler 没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a; // "b"
上面代码中,handler 是一个空对象,没有任何拦截效果,访问 proxy 就等同于访问 target。
Proxy 实例方法
get
get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身 (严格地说,是操作行为所针对的对象),其中最后一个参数可选。
var person = {
name: '张三',
};
var proxy = new Proxy(person, {
get: function (target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError('Prop name "' + propKey + '" does not exist.');
}
},
});
proxy.name; // "张三"
proxy.age; // 抛出一个错误
上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回 undefined。
set()
set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
示例
在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合 get 和 set 方法,就可以做到防止这些内部属性被外部读写。
const 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`);
}
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop;
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c';
上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。
apply()
apply方法拦截函数的调用、call和apply操作。apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
示例
var target = function () {
return 'I am the target';
};
var handler = {
apply: function () {
return 'I am the proxy';
},
};
var p = new Proxy(target, handler);
p();
// "I am the proxy"
上面代码中,变量
p是Proxy的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。
var twice = {
apply(target, ctx, args) {
return Reflect.apply(...arguments) * 2;
},
};
function sum(left, right) {
return left + right;
}
var proxy = new Proxy(sum, twice);
proxy(1, 2); // 6
proxy.call(null, 5, 6); // 22
proxy.apply(null, [7, 8]); // 30
Reflect.apply(proxy, null, [9, 10]); // 38
上面代码中,每当执行
proxy函数(直接调用或call和apply调用),就会被apply方法拦截。 另外,直接调用Reflect.apply方法,也会被拦截。
has()
has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。has()方法可以接受两个参数,分别是目标对象、需查询的属性名。- 如果原对象不可配置或者禁止扩展,这时
has()拦截会报错。例:obj使用Object.preventExtensions(obj); has()方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性。for...in循环也用到了in运算符,但是has()拦截对for...in循环不生效
var handler = {
has(target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
},
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy; // false
上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has()就会返回 false,从而不会被 in 运算符发现。

还没有留言,来发表第一条吧~