深色模式
代理模式
背景
代理,很好理解,比如我们想找某人办事,有时我们的关系不到,就需要通过中间人来代替我们办事。在程序中,代理使用的地方特别多,比如我们向后端请求数据时对 XMLHttpRequest
或 axios
进行封装,比如添加 token, 参数校验等操作,对 HTTP 请求进行代理操作。 代理是一种思想,没有具体模式。使用代理对于用户来说是透明的,代理对外接口和本体对外接口常常是一致的。
在 JS 中 可以使用 Proxy
和 Reflect
对对象代理操作, 这个在 Vue3 中的响应式中有使用。
使用代理可以将开销大的对象,延迟到真正需要它的时候创建。比如,缓存代理。
图片预加载
图片预加载可以用在网络不好,图片非常大的情况下。在图片下载完成之前,我们可以在图片占位一个替代图片,图片加载完成后替换成正确的图片。其实现方式很简单,如下
- 图片不使用预加载
可以看到,imgNode 直接下载图片,在网速很慢情况下,加载大图片会有卡顿,空图片占位情况。
js
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
},
};
})();
myImage.setSrc('http://file.liuzunkun.com/bigimg.png');
- 图片使用预加载
可以看到图片预加载处理结果,本例中未使用代理操作
js
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
var img = new Image();
// 图片预加载完成后处理
img.onload = function () {
imgNode.setSrc(img.src);
};
return {
setSrc: function (src) {
// 1. 图片节点使用本地占位图片,该图片小
imgNode.src = './small.png';
// 图片预加载后,通过浏览器缓存将目标图片节点 imgNode 地址设置为目标地址
img.src = src;
},
};
})();
myImage.setSrc('http://file.liuzunkun.com/bigimg.png');
- 使用代理加载图片
代理操作和原始加载图片有相同对外 API,其实现原理对用户透明。
可以看到 proxyImage 代理了 myImage 操作,这样做的好处是, myImage 专职加载工作,代理负责加载时机,结构清晰。
js
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
},
};
})();
var proxyImage = (function () {
var img = new Image();
img.onload = function () {
myImage.setSrc(img.src);
};
return {
setSrc: function (src) {
myImage.setSrc('./small.png');
img.src = src;
},
};
proxyImage.setSrc('http://file.liuzunkun.com/bigimg.png');
HTTP 请求缓存
我们这里使用的是虚拟代理,对用户来说是无感的。在 《JavaScript 设计模式与实践》中有这样一个例子:用户选择文件列表同步文件到系统,每选择一个文件发起一次同步请求。使用代理可以先缓存一段时间内的同步文件请求,触发定时任务批量同步操作。
由于 HTTP 请求频繁,会消耗大量资源。使用代理缓存请求数据,批量请求会减少请求频率。
假设有一个文件列表删除的功能,有如下按钮,每点击一个按钮我们做删除操作
html
<body>
<button class="delfile" id="1">文件 1</button>
<button class="delfile" id="2">文件 2</button>
<button class="delfile" id="3">文件 3</button>
<button class="delfile" id="4">文件 4</button>
<button class="delfile" id="5">文件 5</button>
<button class="delfile" id="6">文件 6</button>
<script>
var buttons = document.querySelectorAll("button[class='delfile']");
console.log({ buttons });
buttons.forEach(item => {
item.onclick = function () {
syncDeleteFile(this.id);
};
});
function syncDeleteFile(fileid) {
console.log(`sync delete file ${fileid}`);
}
</script>
</body>
上面的代码不太好,请求频率太高,可以使用代理缓存请求数据,定时统一操作
js
buttons.forEach(item => {
item.onclick = function () {
proxySyncDeleteFile(this.id);
};
});
var proxySyncDeleteFile = (function () {
const fileIds = [];
let timer;
return function (fileId) {
if (!fileIds.includes(fileId)) {
fileIds.push(fileId);
}
if (timer) return;
timer = setTimeout(() => {
syncDeleteFile(fileIds.join(';'));
fileIds.length = 0;
clearTimeout(timer);
timer = null;
}, 2000);
};
})();
注意,将请求数据缓存起来批量操作是一个很好的操作,我们还可以使用全局数据缓存起来,统一提交操作。
参考
- JavaScript 设计模式与开发实践