代理模式的定义:其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

简单来说代理模式就是访问一个对象的时候,我们不直接去访问,而是访问一个代理对象(中间人),这个代理对象再去对访问实际的对象,在这个代理对象中我们可以做一些事情,如:拓展功能、控制访问、提高性能等。

业务开发中常见的代理类型有:虚拟代理、缓存代理。

虚拟代理

图片预加载

在开发中我们常常会用到图片预加载的功能,当图片没有加载完成的时候显示一张 loading 图片,当图片加载完毕后将 loading 替换成真实图片,这样当用户网络慢的时候就不会看到一片空白提高了用户体验,这个场景就非常适合使用虚拟代理。

  1. class PreLoadImage {
  2. static loadingImageSrc = '/assets/xxx.jpg';
  3. constructor(imageNode) {
  4. this.imageNode = imageNode;
  5. }
  6. setSrc(url) {
  7. // 先指向 loading 图
  8. this.imageNode.src = PreLoadImage.loadingImageSrc;
  9. // 利用 Image onload 来监测是否加载完毕
  10. const image = new Image();
  11. image.onload = () => {
  12. // 加载完毕后再赋值
  13. this.imageNode.src = url;
  14. };
  15. image.src = url;
  16. }
  17. }

上面的代码开启来是没问题的,但实际上违反了单一职责原则,这个类它做了两件事,1. 图片加载、2. 设置图片地址,现在我们用代理模式来实现一下,PreLoadImage类只做 DOM 层面的事情,也就是图片设置,然后再创建一个 ProxyLoadImage类来实现图片的加载判断。

  1. class PreLoadImage {
  2. constructor(imageNode) {
  3. this.imageNode = imageNode;
  4. }
  5. setSrc(url) {
  6. this.imageNode.src = url;
  7. }
  8. }
  9. // 代理类
  10. class ProxyLoadImage {
  11. static loadingImageSrc = '/assets/xxx.jpg';
  12. constructor(preLoadImage) {
  13. this.preLoadImage = preLoadImage;
  14. }
  15. setSrc(url) {
  16. // 先指向 loading 图
  17. preLoadImage.setSrc(ProxyLoadImage.loadingImageSrc);
  18. // 利用 Image onload 来监测是否加载完毕
  19. const image = new Image();
  20. image.onload = () => {
  21. preLoadImage.setSrc(url);
  22. };
  23. image.src = url;
  24. }
  25. }

防抖函数、节流函数

在实际的开发中,节流、防抖函数常常用于性能优化,它们就属于虚拟代理的实现,我们不直接发起网络请求,而是用节流/防抖函数来去发起,它们会控制网络数量来达到性能优化的目的。

缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的储存,如果下一次运算的参数和之前的一致,就直接使用缓存,而无需再次运算。

计算乘积

  1. const mult = (...args) => {
  2. let result = 1;
  3. for (let i = 0, len = args.length; i < len; i++) {
  4. result = result * args[i];
  5. }
  6. return result;
  7. };
  8. mult(2, 3); // 6
  9. mult(2, 3); // 重新计算:6

上面 mult函数中每次调用都会重新进行计算,即使我们传递的参数是一样的,目前这个函数比较简单还是无所谓的,但如果是一个复杂计算的话,重复计算就有点浪费资源了,现在我们加载缓存代理看看:

  1. const proxyMult = (() => {
  2. const cache = {};
  3. return (...args) => {
  4. const key = args.join(',');
  5. if (cache[key]) return cache[key];
  6. return cache[key] = mult(...args);
  7. };
  8. })();

通过 proxyMult函数来实现缓存功能,mult函数来实现计算功能,每个函数都专注于自己的职责,当我们调用 proxyMult 传递相同的参数时,就直接返回函数的数据,而非重新计算了。

缓存代理用于网络请求

在开发中常常会遇到分页的需求,我们可以利用缓存代理来将数据缓存起来,这样下次再请求同一页的数据时便可以直接使用直接缓存的数据,而不用发起网络请求了。
这里使用 Vue3 的 Composition API 来实现:

  1. // usePage
  2. import { ref, toRefs, unref, reactive, watchPostEffect, type Ref } from 'vue';
  3. export type PageRequest<T> = (
  4. current: number,
  5. size: number,
  6. params?: Record<string, string>
  7. ) => Promise<{
  8. total: number;
  9. data: T[];
  10. }>;
  11. export type Option = {
  12. isRequest?: Ref<boolean> | boolean;
  13. };
  14. function usePage<T>(request: PageRequest<T>, option?: Option) {
  15. const data = ref([]) as Ref<T[]>;
  16. const exception = ref();
  17. const page = reactive({
  18. current: 1,
  19. size: 10,
  20. total: 0,
  21. });
  22. const loading = ref(false);
  23. const { isRequest } = option ?? {};
  24. watchPostEffect(() => {
  25. if (isRequest !== undefined && !unref(isRequest)) {
  26. return;
  27. }
  28. loading.value = true;
  29. const { current, size } = page;
  30. request(current, size)
  31. .then((res) => {
  32. page.total = res.total;
  33. data.value = res.data;
  34. loading.value = false;
  35. })
  36. .catch((e) => {
  37. exception.value = e;
  38. });
  39. });
  40. return {
  41. data,
  42. loading,
  43. ...toRefs(page),
  44. };
  45. }
  46. export default usePage;
  1. // usePageCacheProxy
  2. import { ref, computed, watch, watchEffect } from 'vue';
  3. import usePage, { type PageRequest } from './usePage';
  4. function usePageCacheProxy<T>(request: PageRequest<T>) {
  5. const isRequest = ref(false);
  6. const cache: Record<string, T[]> = {};
  7. const result = usePage<T>(request, {
  8. isRequest,
  9. });
  10. const cacheKey = computed(() => {
  11. const { current, size } = result;
  12. return `${current.value}:${size.value}`;
  13. });
  14. watch(result.data, (newData) => {
  15. const key = cacheKey.value;
  16. if (!cache[key]) {
  17. // 缓存数据
  18. cache[key] = newData;
  19. }
  20. });
  21. watchEffect(() => {
  22. const key = cacheKey.value;
  23. if (cache[key]) {
  24. // 有缓存, 不发起网络请求
  25. isRequest.value = false;
  26. return (result.data.value = cache[key]);
  27. }
  28. // 没有缓存, 发起网络请求
  29. isRequest.value = true;
  30. });
  31. return result;
  32. }
  33. export default usePageCacheProxy;

整理与 JavaScript 设计模式与开发实践。