加载中...
JavaScript设计模式 -- 3.代理模式
发表于:2021-06-03 | 分类: 前端

定义

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式的关键是当客户不方便直接访问对象或者不满足需要时。提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象

st=>start: 客户
op=>operation: 代理对象
e=>end: 本体对象

st->op->e

应用场景

  • 在前后端分离的架构中,收到浏览器端 同源策略 的影响,常见的问题就是 CORS(跨域资源共享),这个可以通过后端设置响应头Access-Control-Allow-Origin*,来允许浏览器跨域访问资源。
    当然,前端也可以通过 node 以及其中间件,在本地搭建一个服务,然后将页面中的请求转发到真正的 web server(同源策略只针对浏览器,服务端向服务端发送请求是没有同源限制的)。在后者的方式中。node 承担的就是一个代理的作用。
  • 当需要中间人来辅助完成工作,或锦上添花时使用。

小明追女神

代理需要在一些额外的场景下才能发挥它的作用,不然只会把原来简单的事情搞复杂。比如小明去追女神。如果没有其他影响因素,单纯的通过小红去转送花,那是毫无意义的。

但是,我们假设,当女神心情好的时候收到花,小明表白的成功率会提升很多,而当女神心情差的时候收到花,成功率近乎于 0。

那么小明在没有和女神过多的接触下。无法分辨女神的情绪。所以可以通过和女神比较熟悉的小红,小红来监听女神的心情。当女神心情好的时候再送出花。这样成功率会更高。

var Flower = function () {};

var xiaoming = {
  sendFlower: function (target) {
    var flower = new Flower();
    target.receiveFlower(flower);
  },
};

// 代理
var xiaohong = {
  receiveFlower: function (flower) {
    // 监听女神的心情
    goddess.listenGoodMood(function () {
      goddess.receiveFlower(flower);
    });
  },
};

var goddess = {
  receiveFlower: function (flower) {
    console.log("收到花:" + flower);
  },
  listenGoodMood: function (fn) {
    // 假设女神大概10秒后心情变好。
    setTimeout(function () {
      fn();
    }, 10000);
  },
};

xiaoming.sendFlower(xiaohong);

保护代理

一束花的开销可能很大,所以我们需要当真正需要它的时候才去创建。

var xiaohong = {
  receiveFlower: function () {
    // 监听女神的心情
    goddess.listenGoodMood(function () {
      // 延迟创建flower对象
      var flower = new Flower();
      goddess.receiveFlower(flower);
    });
  },
};

缓存代理

在项目中我们经常遇到一些分页请求,或者一些历史数据、历史图表数据请求等等。理论上,我们可以将已经看过的数据缓存下来。

下面我们就按照这两个,使用高阶函数动态创建代理。

分页数据

// 假设项目中已有一个请求库,且只返回具体数据
const request = (method, url, data) => {
  return fetch(url, {
    method: method,
    body: JSON.stringify(data),
    headers: {
      "content-type": "application/json",
    },
  })
    .then((response) => response.json())
    .then((data) => data.data)
    .cacth(() => {});
};

const getData = (() => {
  // 缓存
  const cache = new Map();
  return async function (pageNum, pageSize) {
    if (cache.has(`${pageSize}-${pageNum}`)) {
      return new Pormise((resolve) => {
        resolve(cache.get(`${pageSize}-${pageNum}`));
      });
    }
    return request("POST", "some api", { pageNum, pageSize }).then((data) => {
      // 设置缓存
      cache.set(`${pageSize}-${pageNum}`, data);
      return data;
    });
  };
})();
// 当用户翻页时调用
const data = await getData(1, 10);

也可以使用 Proxy 构造函数来创建代理,该代理可以优化一些耗时操作

function cacheProxy(fn) {
  const cache = new Map();
  return new Proxy(fn, {
    // 函数调用trap
    apply(target, context, args) {
      const argsProp = args.join(" ");
      // Reflect提供的静态方法和Object上的对应方法相同
      if (Reflect.has(cache, argsProp)) {
        console.log("using cache data...");
        return Reflect.get(cache, argsProp);
      }

      console.log("cal new data...");
      const result = target(...args);
      Reflect.set(cache, argsProp, result);

      return result;
    },
  });
}

const expensiveHandler = (a, b) => {
  return a + b;
};

const expensiveProxy = cacheProxy(expensiveHandler);

expensiveProxy(1, 2); // cal new data...
expensiveProxy(1, 2); // using cache data...

上面获取历史数据的例子我们也可以用 Proxy 来改写一下

const createCacheProxy = (fn) => {
  const cache = new Map();
  return new Proxy(fn, {
    async apply(target, context, args) {
      const argsProp = args.join(" ");
      if (Reflect.has(cache, argsProp)) {
        return Reflect.get(cache, argsProp);
      }

      const result = await target(...args);
      Reflect.set(cache, argsProp, result);

      return result;
    },
  });
};

const getData = async (pageNum, pageSize) => {
  const data = await request("POST", "some api", { pageNum, pageSize });
  return data;
};

const getDataProxy = createCacheProxy(getData);
const data = await getDataProxy(1, 10);
上一篇:
JavaScript设计模式 -- 2.策略模式
下一篇:
数据结构和算法
本文目录
本文目录