Proxy 的基本定义
Proxy 这个词本身就是代理的意思,在这里可以理解它为一个代理器。在 JavaScript 中,Proxy 对象用于在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Proxy 的参数解析
new Proxy(target, handler)
- target:参数表示所要拦截的目标对象。
- handler:参数也是一个配置对象,用来定制拦截行为。如果 handler 没有设置任何拦截,那就等同于直接通向原对象。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Proxy 的常见拦截操作
1. get 方法
get(target, propKey, receiver)
- target:参数表示目标对象。
- propKey:参数表示属性名。
- receiver:参数表示 proxy 实例本身(严格地说,是操作行为所针对的对象,也就是所谓的接收器),其中最后一个参数可选。
2. set 方法
set(target, property, value, receiver)
- target:参数表示目标对象。
- property:参数表示要设置的属性名称。
- value:参数表示要赋予属性的值。
- receiver:参数表示最初被调用的对象,通常是代理对象。
3. has 方法
has() 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效典型操作就是 in 运算符,一般用于隐藏某些属性,不被 in 运算符发现。
根据提供的内容,我为您完成了重构并保持了段落结构。请查看以下内容:
```javascript
// 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) 可以接受三个参数,依次为:this、object、args。
```
以下是重构后的代码:
```javascript
// 目标函数
function target() {
console.log("目标函数被调用");
return "res";
}
// 使用 Proxy 实现私有变量的 getset
const handler = {
get(target, key) {
if (key.charAt(0) !== "_") {
console.log("参数列表:", arguments);
}
return Reflect.get(target, key);
},
set(target, key, value) {
if (key.charAt(0) !== "_") {
console.log("设置值:", value);
}
return Reflect.set(target, key, value);
},
};
const targetProxy = new Proxy(target, handler);
console.log(targetProxy()); // res
targetProxy.name = "saucxs"; // 设置值:saucxs
console.log(targetProxy.name); // "saucxs"
```
在上述代码中,我们声明了两个私有变量:`_prop` 和 `_userListId`,以便在 `userList` 对象的内部方法中进行调用,但不希望从外部访问或修改 `userList._prop` 和 `userList._userListId`。为了实现这个目标,我们使用了代理(Proxy)对象来拦截属性操作。以下是重构后的代码:
```javascript
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); // 这里会抛出错误,因为 _userListId 是私有的
userList._userListId = "123abcad"; // 这里也会抛出错误,因为 _userListId 是私有的
```
通过这种方式,我们确保了 `userList._prop` 和 `userList._userListId` 只能够在对象的内部方法中访问和修改。如果尝试从外部获取或设置这些私有属性,将会触发错误。
链式调用
在 JavaScript 中,我们可以使用 `Proxy` 对象来实现链式调用。以下是一个简单的示例:
```javascript
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` 的函数,它接受一个对象作为参数,并返回一个代理对象。当我们访问代理对象的属性时,它会拦截属性访问操作,并在调用属性方法后返回代理对象,以便可以继续在代理对象上进行属性访问或方法调用。这样我们就可以实现链式调用。
```javascriptlet 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; // 抛出错误:username in userInfo can only be string
```
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; // 在真正操作时只要关系数据就行
};
以下是内容重构的代码:
```javascript
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...
console.log(proxyFunction(5)); // data from cache...
```