前端设计模式之代理模式(九)
- 针对对象设置代理访问,不得直接访问
- 直接访问对象,在远程面向对象系统中,可能对象创建开销很大,或者某些操作需要安全控制,我们添加一个访问层访问该对象
- 例如,你通过房产中介买房子,中介就是一个代理,而非真正的房主
- 再例如,明星都有经纪人,经纪人就是一个代理
实现代理模式
class RealImg {
fileName: string;
constructor(fileName: string) {
this.fileName = fileName;
this.loadFromDist();
}
display() {
console.log("display...", this.fileName);
}
private loadFromDist() {
console.log("loading...", this.fileName);
}
}
class ProxyImg {
readImg: RealImg;
constructor(fileName: string) {
this.readImg = new RealImg(fileName);
}
display() {
this.readImg.display();
}
}
const proxImg = new ProxyImg("xxx.png"); // 使用代理
proxImg.display();
是否符合设计原则?
- 代理和目标分离,解耦
- 代理可自行扩展逻辑
- 目标也可自行扩展逻辑
符合开放封闭原则
场景
DOM 事件代理
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<script>
document.getElementById("div1").addEventListener("click", function (e) {
const target = e.target;
if (target.nodeName === "A") {
alert(target.innerHTML);
}
});
</script>
虚拟代理
- virtualImage 代替真实 DOM 像图片发送了请求,却未曾渲染
class PreLoadImage {
constructor(imgNode) {
this.imgNode = imgNode;
}
setSrc(imgUrl) {
this.imgNode.src = imgUrl;
}
}
class ProxyImage {
// 占位图的url地址
static LOADING_URL = "xxxxxx";
constructor(targetImage) {
this.targetImage = targetImage;
}
// 该方法主要操作虚拟Image,完成加载
setSrc(targetUrl) {
// 真实img节点初始化时展示的是一个占位图
this.targetImage.setSrc(ProxyImage.LOADING_URL);
// 创建一个帮我们加载图片的虚拟Image实例
const virtualImage = new Image();
// 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
virtualImage.onload = () => {
this.targetImage.setSrc(targetUrl);
};
// 设置src属性,虚拟Image实例开始加载图片
virtualImage.src = targetUrl;
}
}
缓存代理
- 空间换时间
- 通过代理计算的同时对计算结果进行缓存
// addAll方法会对你传入的所有参数做求和操作
const addAll = function () {
console.log("进行了一次新计算");
let result = 0;
const len = arguments.length;
for (let i = 0; i < len; i++) {
result += arguments[i];
}
return result;
};
// 为求和方法创建代理
const proxyAddAll = (function () {
// 求和结果的缓存池
const resultCache = {};
return function () {
// 将入参转化为一个唯一的入参字符串
const args = Array.prototype.join.call(arguments, ",");
// 检查本次入参是否有对应的计算结果
if (args in resultCache) {
// 如果有,则返回缓存池里现成的结果
return resultCache[args];
}
return (resultCache[args] = addAll(...arguments));
};
})();
function cacheProxy(fn) {
const cache = Object.create(null);
return (...args) => {
const key = args.map(arg => JSON.stringify(arg)).join("__");
if (cache[key]) {
console.log("命中缓存");
return cache[key];
}
const res = fn.apply(this, args);
cache[key] = res;
return res;
};
}
function sum(n1, n2) {
return n1 + n2;
}
const proxySum = cacheProxy(sum);
console.log(proxySum(3, 5)); // 8
console.log(proxySum(3, 5)); // 命中缓存 8
webpack devServer 正向(客户端)代理
参考 webpack.docschina.org/configurati…
// webpack.config.js
module.exports = {
// 其他配置...
devServer: {
proxy: {
"/api": "http://localhost:8081",
},
},
};
nginx 反向(服务端)代理
nginx 配置文件可参考 www.runoob.com/w3cnote/ngi…
server {
listen 8000;
location / {
proxy_pass http://localhost:8001;
}
location /api/ {
proxy_pass http://localhost:8002;
proxy_set_header Host $host;
}
}
Proxy
跟踪属性访问
const user = {
name: "张三",
};
const proxy = new Proxy(user, {
get(target, key) {
console.log("get...");
return Reflect.get(target, key);
},
set(target, key, val) {
console.log("set", val);
return Reflect.set(target, key, val);
},
});
proxy.name = "李四"; // set 李四
console.log(proxy.name); // get... 李四
隐藏属性
const user = {
name: "张三",
age: 25,
girlfriend: "小红",
};
const hiddenProps = ["girlfriend"]; // 要隐藏的属性 key
const proxy = new Proxy(user, {
get(target, key) {
if (hiddenProps.includes(key as string)) return undefined;
return Reflect.get(target, key);
},
has(target, key) {
if (hiddenProps.includes(key as string)) return false;
return Reflect.has(target, key);
},
set(target, key, val) {
if (hiddenProps.includes(key as string)) return false;
console.log("set...", val);
return Reflect.set(target, key, val);
},
});
console.log(proxy.girlfriend); // undefined
console.log(proxy.girlfriend); // undefined
console.log("girlfriend" in proxy); // false
验证属性
const user = {
name: "张三",
age: 25,
};
const proxy = new Proxy(user, {
get(target, key) {
return Reflect.get(target, key);
},
set(target, key, val) {
if (key === "age") {
if (typeof val !== "number") return false; // 验证 age 类型
}
return Reflect.set(target, key, val);
},
});
proxy.age = "a";
console.log(proxy.age); // 25
function sum(n1, n2) {
return n1 + n2;
}
const proxySum = new Proxy(sum, {
apply(target, thisArg, argumentsList) {
const [num1, num2] = argumentsList;
const isNumber = n => typeof n === "number";
if (!isNumber(num1) || !isNumber(num2)) {
throw new TypeError("args is must be number");
}
return Reflect.apply(target, thisArg, argumentsList);
}
});
proxySum(3, 6); // 必须传入数字
记录实例
const userList = new WeakSet(); // 每次初始化 user ,都记录到这里
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const ProxyUser = new Proxy(User, {
construct(...args) {
const user = Reflect.construct(...args);
userList.add(user); // 记录 user 对象
return user;
},
});
const user1 = new ProxyUser("张三");
const user2 = new ProxyUser("李四");
console.log("userList", userList);
注意事项
捕获器不变式
- 捕获器即 get ,不变式即不能因为 Proxy 而改变对象本身的描述符特性
const obj = { x: 100, y: 0 };
Object.defineProperty(obj, "y", {
value: 200,
writable: false,
configurable: false,
});
const proxy = new Proxy(obj, {
get() {
return "abc";
},
});
console.log(proxy.x);
console.log(proxy.y); // y 属性描述符被修改,proxy 不能修改它的值
this
- 函数里的 this 是由执行时确认的,而非定义时
const user = {
name: "张三",
getName() {
console.log("this...", this);
return this.name;
},
};
const proxy = new Proxy(user, {});
user.getName(); // 执行时 this 是 user
proxy.getName(); // 执行时 this 是 proxy
装饰器模式 对比 代理模式
- 装饰器模式:层层装饰,增强其能力
- 代理模式:控制其访问能力,隐藏底层具体的操作
转载自:https://juejin.cn/post/7197753883422163002