如果感觉文章不错,欢送关注、点赞和分享!

继续分享技术博文,关注微信公众号  前端LeBron

好久不见,怎么这么久没更新了呢?

Emm...最近绩效评估季,绩效总结、360 评估,要写的货色比拟多嚯,耽误了一段时间

废话不多说,迎来 JavaScript 设计模式第三篇:代理模式 ~

代理模式概念

代理模式给某一个对象提供一个代理对象或者占位符,并由代理对象管制原对象的援用,也能够了解为对外裸露的接口并不是原对象。艰深地讲,生存中也有比拟常见的代理模式:中介、寄卖、经纪人等等。而这种模式存在的意义在于当访问者与被访问者不不便间接拜访/接触的状况下,提供一个替身来处理事务流程,理论拜访的是替身,替身将事务做了一些解决/过滤之后,再转交给本体对象以加重本体对象的累赘。

最简代理模式实现

由简入繁

下面理解了代理模式的相干概念,接下来咱们用一个最简代理模式的例子实现一下代理模式,从代码中感触代理模式的流程

Talk is Cheap. Show me the code!

  • client向服务端发送一个申请
  • proxy代理申请转发给服务端
  • 服务端解决申请
const Request = function () {};const client = {  requestTo: (server) => {    const req = new Request();    server.receiveRequest(req);  },};const server = {  handleRequest: (request) => {    console.log('receive request: ', request);  },};const proxy = {  receiveRequest: (request) => {    console.log('proxy request: ', request);    server.handleRequest(request);  },};client.requestTo(proxy);/** * proxy request:  Request {} * receive request:  Request {} */

爱护代理

爱护代理,顾名思义是为了爱护本体

基于权限管制对资源的拜访

上面用一个场景和例子来理论感受一下,基于下面最简代理模式进行扩大,咱们能够应用爱护代理实现,过滤未通过身份校验的申请、监听服务端 ready 才发送申请等操作,爱护实体服务端不被非法申请攻打和升高服务端累赘。

const proxy = {  receiveRequest: (request) => {    // 校验身份    const pass = validatePassport(request);    if (pass) {      // 监听服务端 ready 后代理申请      server.listenReady(() => {        console.log('proxy request: ', request);        server.handleRequest(request);      });    }  },};

虚构代理

虚构代理作为创立开销大的对象的代表,帮助管制创立开销大的资源,直到真正须要一个对象的时候再去创立它,由虚构代理来表演对象的替身,对象创立后,再将资源间接委托给实体对象

上面将会实现一个虚构代理实现图片预加载的例子,从代码和理论场景中感触虚构代理的作用。

  • 实体图片对象挂载在body中
  • 因为加载图片耗时较高,开销较大,加载图片资源时

    • 将实体图片对象设置为loading状态
    • 应用替身对象执行图片资源加载
    • 监听替身对象资源加载实现,将资源替换给实体对象
const img = (() => {  const imgNode = document.createElement('img');  document.body.appendChild(img);  return {    setSrc: (src) => {      imgNode.src = src;    },    setLoading: () => {        imgNode.src = 'loading.gif'    }  };})();const proxyImg = (() => {  // 替身图片对象  const tempImg = new Image();  // 监听资源加载实现,将资源替换给实体图片对象  tempImg.onload = function () {    img.setSrc(this.src);  };  return {    // 代理开始将实体对象设置为loading状态,应用替身对象开始加载图片资源    setSrc:(src)=>{        img.setLoading()        tempImg.src = src;    }  }})();proxyImg.setSrc('file.jpg')

代理模式的利用

看完爱护代理和虚构代理之后,上面来看看代理模式在前端中的一些具体利用

申请优化(埋点、谬误的数据聚合上报)

前段时间有幸受邀加入了 ByteTech 字节青训营的评委,次要加入评审的是前端监控零碎主题我的项目。前端监控就会波及一些谬误等信息的上报,局部我的项目只实现了最简的 HTTP 申请上报。

而有局部我的项目对这块内容做了以下优化,是一个比拟贴切的代理模式实际场景:

  • Navigator.sendBeacon

    • 使用户代理在有机会时异步地向服务器发送数据( HTTP POST ),不影响交互性能
    • https://developer.mozilla.org...
  • 数据聚合上报(未应用代理模式优化版本为,每次 report 都应用申请上报)

    • 升高申请次数,聚合多事件/信息进行上报

      • 定时
      • 定量分组
上面简略实现一下两种上报的示意代码
  • 定时
const events = [];const TIMER = 10000;let timer = null;const init = () => {  // 初始化时启动定时器  timer = setInterval(() => {    // 定时应用 sendBeacon 上报    const evts = events.splice(0, events.length);    navigator.sendBeacon('/path', { events: evts });  }, TIMER);};const destroyed = () => {  // 销毁时革除定时器  clearInterval(timer);};const report = (eventName, data) => {  // sdk 上报工具函数,聚合事件  events.push({    eventName,    data,  });};
  • 定量分组
const events = [];const LIMIT = 10;const reportRequest = () => {  // 定量分组应用 sendBeacon 上报  const evts = events.splice(0, LIMIT);  navigator.sendBeacon('/path', { events: evts });};const report = (eventName, data) => {  // sdk 上报工具函数,聚合事件  events.push({    eventName,    data,  });  if (events.length >= LIMIT) {    reportRequest();  }};

数据缓存代理

  • 之前看到过一道面试题:前端怎么实现缓存到期主动删除缓存?

    • 如果正向地去思考,缓存到期了,程序都敞开了,怎么删?
    • 换个角度:不在过期时立刻 set ,get 时才须要判断缓存是否过期

      在 get 时判断下是否过期,过期了再删除不就得了 ~

  • 通过 ProxyStorage 代理缓存中间件,实现反对设置缓存过期工夫
  • 代理一层,便于切换缓存中间件,减少可维护性
const Storage = {  set(key, value, maxAge) {    localStorage.setItem(      key,      JSON.stringify({        maxAge,        value,        time: Date.now(),      })    );  },  get(key) {    const v = localStorage.getItem(key);    if (!v) {      return null;    }    const { maxAge, value, time } = JSON.parse(v);    const now = Date.now();    if (now < time + maxAge * 1000) {      return value;    } else {      localStorage.removeItem(key);      return null;    }  },  has(key) {    const v = localStorage.getItem(key);    if (!v) {      return false;    }    const { maxAge, time } = JSON.parse(v);    const now = Date.now();    if (now < time + maxAge * 1000) {      return true;    } else {      localStorage.removeItem(key);      return false;    }  },};

申请函数的封装

通过代理模式封装申请函数,能够实现以下性能:

  • 植入通用参数、通用申请头
  • 全局申请埋点上报
  • 全局异样状态码处理器
  • 全局申请谬误、异样上报和解决

const SUCCESS_STATUS_CODE = 200,  FAIL_STATUS_CODE = 400;const isValidHttpStatus = (statusCode) =>  statusCode >= SUCCESS_STATUS_CODE && statusCode < FAIL_STATUS_CODE;const ErrorCode = {  NotLogin: 2022,};const ErrorHandler = {  [ErrorCode.NotLogin]: redirectToLoginPage,};const request = async (reqParams) => {  const { headers, method, data, params, url } = reqParams;  // 封装申请参数,植入通用参数、通用申请头  const requestObj = {    url: url + `?${qs.stringify({ ...commonParams, ...params })}`,    headers: { ...commonHeaders, ...headers },    data,    method,    start: Date.now(),  };  try {    // 上报申请开始埋点    reportEvent(AJAX_START, requestObj);    const res = await ajax(requestObj);    requestObj.end = Date.now();    requestObj.response = res;    // 上报申请完结埋点    reportEvent(AJAX_END, requestObj);    const { statusCode, data: resData } = res;    const { errorCode } = resData;    if (!isValidHttpStatus(statusCode)) {      // 异样状态码埋点上报      reportEvent(AJAX_ERROR, requestObj);      throw resData;    } else if (errorCode) {      // 错误码全局处理器定义,未定义则把谬误抛出给下层业务解决      reportEvent(AJAX_WARNING, requestObj);      if (ErrorHandler(errorCode)) {        ErrorHandler(errorCode)();      } else {        throw resData;      }    } else {      // 失常返回申请数据      return resData;    }  } catch (error) {    // 捕捉谬误并进行埋点上报,抛给下层业务解决    requestObj.error = error;    reportEvent(AJAX_ERROR, requestObj);    throw error;  }};

Vue中的代理模式

将数据、办法、计算属性等代理到组件实例上
let vm = new Vue({  data: {    msg: 'hello',    vue: 'vue'  },  computed:{    helloVue(){      return this.msg + ' ' + this.vue    }  },  mounted(){    console.log(this.helloVue)  }})

Koa 中的代理模式

context 上下文代理封装在 request 和 response 里的属性
app.use((context) => {  console.log(context.request.url)  console.log(context.url)  console.log(context.response.body)  console.log(context.body)})

其余代理模式

除了本文提到的代理模式利用,还有其余十分多的变体和利用

这里简要列举和介绍一下,就不一一具体开展阐明了

  • 防火墙代理:管制网络资源拜访,爱护主体不让”好人“靠近
  • 近程代理:为一个对象在不同的地址空间提供部分代表,比方大家的”迷信上网“
  • 爱护代理:用于对象应该有不同的拜访权限的状况
  • 智能援用代理:取代了简略的指针,它在拜访对象时执行一些附加的操作,比方计算一个对象被援用的次数(可能用于 GC 的援用计数

小结

代理模式有着许多的小分类,前端开发工作中罕用的有虚构代理、爱护代理和缓存代理等。其实读到这里,大家也能感触到,日常开发工作中常做的一个动作 —— ”封装“ ,其实就是代理模式的使用 ~

设计模式系列文章举荐

  • JavaScript 设计模式 —— 单例模式
  • JavaScript 设计模式 —— 策略模式
继续分享技术博文,欢送关注!
  • 微信公众号:前端LeBron
  • 掘金:前端LeBron
  • 知乎:前端LeBron