「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
Proxy的基本介绍
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
用法:new Proxy(target, handler)
- target:表示所要拦截的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler:也是一个对象,用来定制拦截行为。(监听目标对象行为的监听器)
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b":handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target
同一个拦截器函数,可以设置拦截多个操作。
var handler = {
get: function(target, name) {//拦截对象属性的读取
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {//拦截Proxy 实例作为函数调用的操作
return args[0];
},
construct: function(target, args) {//拦截 Proxy 实例作为构造函数调用的操作
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
打印结果:
实际应用示例
get
属性读取操作的捕捉器。
get(target, property, receiver)
- target —— 是目标对象,该对象被作为第一个参数传递给 new Proxy,
- property —— 目标属性名,
- receiver —— 如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。通常,这就是 proxy 对象本身(或者,如果我们从 proxy 继承,则是从该 proxy 继承的对象)。
实例一
当尝试获取不存在的数组项时,会得到 undefined
,可以将常规数组包装到代理(proxy)中,以捕获读取操作,并在没有要读取的属性的时返回 0
。
let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
get(target, prop) {
if (prop in target) {
return target[prop];
} else {
return 0; // 默认值
}
}
});
console.log( numbers[1] ); // 1
console.log( numbers[123] ); // 0(没有这个数组项)
实例二
假如有一组词典,上面有短语以及翻译:
let dictionary = {
'Hello': '你好',
'Bye': '再见'
};
console.log( dictionary['Hello'] ); // Hola
console.log( dictionary['Welcome'] ); // undefined
如果没有我们要读取的短语,那么从 dictionary
读取它将返回 undefined
。但实际上,返回一个未翻译的短语通常比 undefined
要好。所以在这种情况下可以返回一个未翻译的短语来替代 undefined
。
为此,可以把 dictionary
包装进一个拦截读取操作的代理:
dictionary = new Proxy(dictionary, {
get(target, phrase) { // 拦截读取属性操作
if (phrase in target) { //如果词典中有该短语
return target[phrase]; // 返回其翻译
} else {
// 否则返回未翻译的短语
return phrase;
}
}
});
// 在词典中查找任意短语!
// 最坏的情况也只是它们没有被翻译。
console.log( dictionary['Hello'] ); // 你好
console.log( dictionary['Welcome']); // Welcome(没有被翻译)
实例三
实现访问 array[-1],即从尾端算起的负值索引访问数组元素。换句话说,array[-N]
与 array[array.length - N]
相同。
let array = [1, 2, 3];
array[-1] = array[2]; // 3,最后一个元素
创建一个 proxy 来实现该行为。
let array = [1, 2, 3];
array = new Proxy(array, {
get(target, prop, receiver) {
if (prop < 0) {
// 即使我们像 arr[1] 这样访问它
// prop 是一个字符串,所以我们需要将其转换成数字
prop = +prop + target.length;
}
return Reflect.get(target, prop, receiver);
}
});
console.log(array[-1]); // 3
console.log(array[-2]); // 2
set
属性设置操作的捕捉器。
set(target, property, value, receiver)
- target —— 是目标对象,该对象被作为第一个参数传递给 new Proxy,
- property —— 目标属性名称,
- value —— 目标属性的值
- receiver —— 与 get 捕捉器类似,仅与 setter 访问器属性相关。
实例一
可用于做数据验证,如Person
对象有一个age
属性,该属性应该是一个不大于 200 的整数。任何不符合要求的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;
return true;
}
};
let person = new Proxy({}, validator);
打印结果:
has
in 操作符的捕捉器。
实例一
可以使用has
捕捉器实现,利用in
操作符来检查一个数字是否在 range
范围内。(因为has
捕捉器会拦截 in
调用。)
has(target, property)
- target —— 是目标对象,被作为第一个参数传递给 new Proxy,
- property —— 属性名称。
let range = {
start: 1,
end: 10
};
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end;
}
});
console.log(5 in range); // true
console.log(50 in range); // false
apply
函数调用操作的捕捉器。
实例一
apply(target, thisArg, args)
捕捉器能使代理以函数的方式被调用:
- target 是目标对象(在 JavaScript 中,函数就是一个对象),
- thisArg 是 this 的值。
- args 是参数列表。
使用 Proxy
来替换包装函数
function delay(f, ms) {
return new Proxy(f, {
apply(target, thisArg, args) {
setTimeout(() => target.apply(thisArg, args), ms);
}
});
}
function sayHi(user) {
console.log(`Hello, ${user}!`);
}
sayHi = delay(sayHi, 3000);
console.log(sayHi.length); // 1 (*) proxy 将“获取 length”的操作转发给目标对象
sayHi("John"); // Hello, John!(3 秒后)
更多属性参考表
表格一:
属性值 | 监听器参数 | 监听内容 |
---|---|---|
get | (target, prop, reciver) | 监听目标对象的属性读取 |
set | (target, prop, value, reciver) | 监听目标对象的属性赋值 |
has | (target, prop) | 监听 in 语句的使用 |
apply | (target, thisArg, arguments) | 监听目标函数(作为目标对象)的调用行为 |
deleteProperty | (target, prop) | 监听 delete 语句对目标对象的删除属性行为 |
construct | (target, arguments, newTarget) | 监听目标构造函数(作为目标对象)利用 new 而生成实例的行为 |
表格二:
属性值 | 监听器参数 | 监听内容 |
---|---|---|
isExtensible | (target) | 监听 Objext.isExtensible() 的读取 |
preventExtensions | (target) | 监听 Objext.preventExtensions() 的读取 |
getOwnPropertyDescriptor | (target, prop) | 监听 Objext.getOwnPropertyDescriptor() 的调用 |
defineProperty | (target, property, descriptor) | 监听 Object.defineProperty() 的调用 |
ownKeys | (target) | 监听 Object.getOwnPropertyName() 的读取 |
getPrototypeOf | (target) | 监听 Objext.getPrototypeOf() 的读取 |
setPrototypeOf | (target, prototype) | 监听 Objext.setPrototypeOf() 的调用 |
参考资料:Proxy and Reflect
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐