proxy代理及其7种应用场景!!这下弄懂了
什么是 proxy?
proxy 的基本定义
Proxy
是 JavaScript 中的元编程特性,它允许开发者拦截并自定义对象的基本操作,从而实现对语言层面的编程。你可以将Proxy
看作是一种"代理器",用来代理对象的访问,以便可以拦截和改变这些访问。通过创建代理对象,你可以在对象上设置拦截器,以捕获对对象属性、方法等的操作,然后执行自定义的逻辑。
proxy 的参数解析
proxy 这个词本身就是代理的意思,在这里可以理解它为一个代理器
new Proxy(target, handle)
:
- target 参数表示所要拦截的目标对象,
- handler 参数也是一个配置对象,用来定制拦截行为。如果 handler 没有设置任何拦截,那就等同于直接通向原对象。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Proxy的常见拦截操作
1. get方法
get(target, propKey, receiver)
:实现拦截操作,用于拦截某个属性的读取操作。
可以接受三个参数,依次为
- target 参数表示目标对象。
- propKey 参数表示属性名。
- receiver 参数表示 proxy 实例本身(严格地说,是操作行为所针对的对象,也就是所谓的接收器),其中最后一个参数可选。
2. set方法
get(target, property, value, receiver)
:用来拦截某个属性的赋值操作。一般用于对需要赋值的数进行过滤,加工,设置权限等等。
可以接受四个参数,依次为:
- target 参数表示目标对象。
- property 参数表示要设置的属性名称。
- value参数表示要赋予属性的值。
- receiver参数表示最初被调用的对象,通常是代理对象。
3. has方法
has() 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效典型操作就是 in运算符 ,一般用于隐藏某些属性,不被 in 运算符发现。
const handler = {
has(target, key) {
if (key[0] === "_") {
return false;
}
return key in target;
},
};
let target = { _prop: "foo", prop: "foo" };
let proxy = new Proxy(target, handler);
if ('_prop' in proxy) {
console.log(111);
} else {
console.log(222);
} // 222
4. deleteProperty方法
deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者 false,当前属性就无法被 delete 命令删除。
const handler = {
deleteProperty(target, key) {
invariant(key, "delete");
delete target[key];
return true;
},
};
const invariant = (key, action) => {
if (key[0] === "_") {
throw new Error(`can't ${action} to private '${key}' property`);
}
};
let target = { _prop: "hah", name: "zs" };
let proxy = new Proxy(target, handler);
delete proxy._prop; // can't delete to private '_prop' property
5. apply方法
apply(target, object, args)
:用于拦截Proxy实例作为函数调用操作的操作符,例如 proxy(...args)、proxy.call(object, ...args) 和 proxy.apply(...)。这个拦截器在代理函数被调用时执行。
可以接受三个参数,依次为:
- target :被代理的目标函数。
- object:被调用时绑定的
this
值。 - arg:一个类数组对象,包含函数调用时传入的实参列表。
let target = function () {
console.log("目标函数被调用");
return "res"
};
target = new Proxy(target, {
apply: function (target, thisArg, argumentsList) {
console.log("参数列表:", argumentsList);
const result = Reflect.apply(target, thisArg, argumentsList);
console.log("调用结果:", result);
return result;
},
});
target(1, 2, 3);
6. ownKeys方法
ownKeys(target)
:拦截自身属性的读取操作(例如object.getownPropertyNames(proxy)、
object.getownPropertysymbols(proxy)、object.keys(proxy)、for...in 循环等方法),并返回目标对象所有自身属性的属性名数组。
let target = {
_foo: 'foo',
_bar: 'bar',
name: 'saucxs'
};
let handler = {
ownKeys (target) {
return Reflect.ownKeys(target).filter(key => key.charAt(0) != '_');
}
};
target = new Proxy(target, handler);
for (let key of Object.keys(target)) {
console.log(target[key]);
}
// "saucxs"
proxy 的 7 种应用场景
1. 实现私有变量
可以通过拦截属性操作实现私有变量,即使用 get
和 set
拦截器来拦截属性的读取和赋值操作实现私有化。
let userList = {
_prop: {},
_userListId: "123abc",
getUsers: function () {},
getUser: function (userId) {},
setUser: function (userId, config) {},
};
const RESTRICTED = ["_prop", "_userListId"];
userList = new Proxy(userList, {
get(target, key, proxy) {
if (RESTRICTED.indexOf(key) > -1) {
throw Error(
`${key} is restricted. Can't get to private '${key}' property.`
);
}
return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if (RESTRICTED.indexOf(key) > -1) {
throw Error(
`${key} is restricted. Can't set to private '${key}' property.`
);
}
return Reflect.get(target, key, value, proxy);
},
});
// 以下操作都会抛出错误
console.log(userList._userListId);
userList._userListId = "123abcad";
在上面面的代码中,我们声明了两个私有变量:_prop, _userListId,便于 userList 这个对象内部的方法调用,但不希望从外部也能够访问以及修改userList._prop, userList._userListId,所以利用 proxy 拦截属性的操作实现变量的私有化。
2. 性能监测
对于那些调用频繁、运行缓慢或占用执行环境资源较多的属性或接口,如果开发者想要记录它们的使用情况或性能表现,此时可以借助proxy代理使用 apply
处理程序来拦截函数的调用实现性能检测。
// 创建一个普通的函数
function myFunction() {
// 模拟一个耗时的操作
for (let i = 0; i < 1000000000; i++) {
// do something
}
console.log("函数调用完成");
}
// 创建一个代理对象来拦截对函数的调用
const proxyFunction = new Proxy(myFunction, {
apply: function (target, thisArg, argumentsList) {
// 记录函数调用开始时间
const startTime = performance.now();
// 调用原始函数
const result = Reflect.apply(target, thisArg, argumentsList);
// 计算执行时间
const executionTime = performance.now() - startTime;
// 打印执行时间
console.log(`函数执行时间:${executionTime} 毫秒`);
// 返回函数调用的结果
return result;
},
});
// 通过代理调用函数并检测性能
proxyFunction();
3. 链式调用
拦截对象的属性访问操作,并在调用属性方法后返回代理对象,以便可以继续在代理对象上进行属性访问或方法调用。
const Chainable = function(obj) {
return new Proxy(obj, {
get(target, prop) {
if (prop in target) {
if (typeof target[prop] === 'function') {
return (...args) => {
target[prop](...args);
return Chainable(target);
};
}
return Chainable(target[prop]);
}
throw new Error(`Property ${prop} not found`);
}
});
};
const api = {
add(x) {
this.value += x;
},
subtract(x) {
this.value -= x;
},
getValue() {
console.log(this.value);
}
};
const chain = Chainable(api);
chain.add(5).subtract(3).getValue(); // Output: 2
在上面的示例中,我们定义了一个
Chainable
函数,它接受一个对象作为参数,并返回一个代理对象。代理对象的get
操作用于拦截属性的读取,所以利用get
操作:如果属性是函数,它会返回一个函数,该函数在调用后返回代理对象,从而实现链式调用的效果。
4. 只读模式
使用 set
拦截器,防止对象的属性被修改即可实现只读模式
let target = { readOnlyProp: 10 };
target = new Proxy(target, {
set(target, prop, value) {
throw new Error('Property is read-only');
}
});
target.readOnlyProp = 20; // Error: Property is read-only
5. 数据校验
在属性赋值前使用 set
拦截器进行数据校验,确保只有符合规定的数据被赋值。
let userInfo = {
id: 0,
username: "abc",
password: 14,
};
userInfo = new Proxy(userInfo, {
set(target, key, value, proxy) {
if (key === "username" && typeof value !== "string") {
throw Error(`${key} in userInfo can only be string`);
}
return Reflect.set(target, key, value, proxy);
},
});
// 抛出错误:username in userInfo can only be string
userInfo.username = 123;
// 赋值成功
userInfo.username = 333;
6. 数据双向绑定
调用 get
和 set
方法来截获对目标对象属性的读取和写入操作。通过在这些方法中添加自定义逻辑,可以实现数据的双向绑定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button>Click Me</button>
<script>
const btn = document.querySelector('button');
let obj = { 'value': 233 };
// 监听数据的变化同时更新视图
const handler = {
get: function (target, property, receiver) {
return target[property];
},
set: function (target, property, value, receiver) {
target[property] = value;
btn.innerText = `value is ${target[property]}.`; // 值在改变的同时更新视图
return true;
}
// 注意target属性操作使用[]
};
obj = new Proxy(obj, handler);
btn.onclick = () => {
obj.value = obj.value + 1; // 在真正操作时只要关系数据就行
};
</script>
</body>
</html>
7. 缓存机制
通过使用 Proxy
代理对象,我们能够在函数调用的各个阶段进行拦截和自定义操作,从而实现了缓存机制。利用代理函数的 apply
方法拦截了原始函数的调用,通过在这个方法中添加缓存逻辑,可以在每次调用函数前检查缓存并返回缓存数据,避免重复计算从而提高性能。
const cacheFun = (n) => {
const cacheData = new Map();
return (n) => {
if (cacheData.has(n)) {
console.log("data from cache");
return cacheData.get(n);
}
const result = n * n;
cacheData.set(n, result);
return result;
};
};
const proxyFunction = new Proxy(cacheFun(), {
apply(target, thisArg, args) {
console.log("Using cache...");
return target(...args);
},
});
console.log(proxyFunction(5)); // Using cache... 25
console.log(proxyFunction(5)); // Using cache... data from cache... 25
~0v0~ 以上是proxy代理有关内容如有错,还请指正!
转载自:https://juejin.cn/post/7266374711823581236