美文网首页
JavaScript设计模式(三) 代理模式

JavaScript设计模式(三) 代理模式

作者: optimistic_bfbe | 来源:发表于2021-06-16 21:46 被阅读0次

    我们来回顾上一章策略模式留下的问题:



    假设公司的绩效等级分为 A,B,C,D,E 年终奖对应的绩点为 2,1.8,1.5,1.1,0.8。假设年终基数为 base,年终奖为:年终基数 * 绩点。已知绩效等级,求员工的年终奖。

    // 绩效等级和绩点的映射关系
    const levelToCredit = {
        A: 2,
        B: 1.8,
        C: 1.5,
        D: 1.1,
        E: 0.8
    }
    
    // 年终奖计算逻辑
    function getsalary(base, level) {
      return levelToCredit[level] * base
    }
    

    代理模式,有点像经纪人的角色,可以完成非核心事务。假设我们要使用一个函数在页面加载一张图片,代码应该是这样的:

    var imgNode = (function () {
      const imgNode = document.createElement('img')
      document.body.appendChild(imgNode)
      return {
        setSrc: function(src) {
          imgNode.src = src
        }
      }
    })()
    
    imgNode.setSrc('https://img.com/banner.png') // 加载图片
    

    事实上,我们调用 setSrc 的时候,会发起一个网络请求,在图片资源返回前,图片是空白的,为了避免图片返回的时候闪烁,我们为图片增加 loading 的情况。假设一开始直接修改上述代码:

    var imgNode = (function () {
      // 真正展示图片的节点
      const imgNode = document.createElement('img')
      document.body.appendChild(imgNode)
      // 代理节点
      var proxyImg = document.createElement('img')
      // 当代理图片加载完成的时候,把图片的链接赋值给展示节点
      proxyImg.onload = function() {
        imgNode.setSrc(this.src)
      }
      return {
        setSrc: function(src) {
          // 预先loading
          imgNode.src = 'https://img.com/loading.gif'
          // 代理节点开始请求图片数据
          proxyImg.src = src
        }
      }
    })()
    
    imgNode.setSrc('https://img.com/banner.png') // 加载图片
    

    这样的代码相信是很多人的第一选择,包括我开始想到的方案也是这样的,那这样的代码有什么缺点呢?
    在未来假设网速快到极致,这样 loading 占位的图片反而导致页面闪烁,这时候如果想把代理的方式去掉,就要修改函数的逻辑,那这样就违反了开放封闭的原则。
    并且代理和创建节点,这两个职责耦合到了一个函数,也违反单一职责原则。不过大家现在可能仅仅知道我们要遵守这些原则,至于为什么,比较少人考究,也很少文章详细解释为什么。
    以我个人的开发经验来说,可以这么理解:

    假设某个需求里面有 A 和 B 两个功能,B 功能是帮 A 做一些事;小王同学把这两个功能都写在了函数 F 里,某一天,产品要砍掉 B 这个功能,然后这时小王离职了,小李接替他的工作来砍掉B,小李看完代码发现砍掉 B 得修改整个 F 函数,小李不仅要看懂函数 F,还要把 B 功能从 F 里面剔除,并且最后还要保证不影响 A 功能。这么一顿操作,小李感觉整个人都不好了,因为他只是想砍掉 B。
    不幸的是他还要同时了解 A 功能,F 函数的代码接口以及修改。这样的开发模式在大型应用里面时非常致命的,容易导致整个项目稳定性过差,并且开发的代码容易成为相互的心智负担。

    假设我们使用代理模式把 B 分离出去,回到上面的例子,首先我们是没有代理的代码,imgNode 函数负责生成一张图片:

    
    
    var imgNode = (function () {
      // 创建展示的图片节点
      const imgNode = document.createElement('img')
      // 挂载到body
      document.body.appendChild(imgNode)
      // 暴露setSrc
      return {
        setSrc: function(src) {
          imgNode.src = src
        }
      }
    })()
    
    
    var proxyImg = (function(){
      // 图片节点
      let node = null
      // 创建代理节点
      const proxyImg = document.createElement('img')
      // 图片加载完成 把node设置为代理图片的src
      proxyImg.onload = function() {
        node.setSrc(this.src)
      }
      return (imgNode) => {
        return {
          setSrc: function(src) {
            // 使用代理图片节点请求
            proxyImg.src = src
            // 缓存 node
            node = imgNode
            // 把展示图片节点的图片设置为 loading
            img.setSrc('https://img.com/loading.png')
          }
      }
      }
    })()
    
    const img = proxyImg(imgNode)
    img.setSrc('https://img.com/banner.png')
    

    这里 proxyImg 这个函数完全是一个锦上添花的功能,假设未来网速快到一定程度,这种 loading 可以淘汰,那我们不需要代理也能正常使用,把 img.setSrc 换成 imgNode.setSrc 实现功能。那么代理模式有什么特点呢,首先实现了和原功能一样的 api 这样在取消使用代理模式的时候也不会因为 api 的差异性而进行大改。

    相关文章

      网友评论

          本文标题:JavaScript设计模式(三) 代理模式

          本文链接:https://www.haomeiwen.com/subject/sxetyltx.html