Hypocrite Nush
  1. 1 Hypocrite Nush
  2. 2 Life Will Change Lyn
  3. 3 Last Surprise Lyn
  4. 4 かかってこいよ NakamuraEmi
  5. 5 Flower Of Life 发热巫女
  6. 6 Time Bomb Veela
  7. 7 BREAK IN TO BREAK OUT Lyn
  8. 8 One Last You Jen Bird
  9. 9 Warcry mpi
  10. 10 Libertus Chen-U
2017-04-16 18:31:06

设计模式——代理模式

代理模式是业务场景中很常用的模式。关键在于当不方便直接访问一个对象时,提供一个替身对象来接受访问,然后通过替身对象的内部处理将请求转交给本体对象。


图片预加载示例

在前端开发中,图片预加载是一个很常见的需求。如果直接给img标签设置src属性,当图片过多或者网络不通畅时,网页渲染时图片可能还未完全加载,则图片位置就会显示空白。为了增强用户体验,常见做法是用一张loading图片占位然后异步加载填充。下面我们先实现一个常见的预加载函数。

let myImage = (function() {
  let imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  let img = new Image;
  img.onload = function() {
    imgNode.src = img.src
  };
  return {
    setSrc: function(src) {
      imgNode.src = '...' // loading图片路径
      img.src = src;
    }
  }
})();

虽然实现了功能,但这段代码存在一些问题。MyImage对象不仅负责给img节点设置值,还负责预加载功能。由于两个功能产生了强耦合导致拓展性不足。如果以后可能某些图片极小或者网速变得很快,不需要预加载功能了就必须深入内部修改逻辑。接下来我们用代理模式来优化这段代码。

let myImage = (function() {
  let imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  return {
    setSrc: function(src) {
      imgNode.src = src;
    }
  }
})();
let proxy = (function() {
  let img = new Image;
  img.onload = function() {
    myImage.setSrc(this.src);
  };
  return {
    setSrc: function(src) {
      myImage.setSrc(...); // loading图片路径
      img.src = src;
    }
  }
}) ();

现在我们可以通过proxy代理对象来控制对本体对象的图片加载功能的访问了,并且将本体的预加载图片功能设置到了代理对象上。我们可以看到,虽然增加了代理对象但是并没有改变本体对象的接口。也就意味着如果今后不需要预加载,我们只需要更改用户请求接口到本体对象即可,整个过程对用户是不可见的。至此,我们就实现了代理模式的封装与隔离。

虚拟代理

代理模式根据场景不同也分为很多种。在前端开发中常用的代理有虚拟代理和缓存代理。以上图片预加载实现的代理模式就是虚拟代理。虚拟代理的原则就是节省请求的开支,这一点和函数节流的思想很像,通过代理先对请求进行预处理,当达到某个可以接受的程度时,再转给本体对象处理。再举一个在线编辑器同步的例子。它不会你的内容一改变就立即给服务器发送同步请求,如果这样实现,当遇到一个手速惊人的用户时保证弄死服务器。所以我们一般用虚拟代理来收集变化,用节流思想发送请求。

let send = function(article) {
  return $.ajax({
    url: ...,
    type: 'post',
    contentType: 'text/plain',
    data: article
  })
};
let proxy = (function() {
  let content = document.querySelector('#article'),
      timer;
  return function() {
    let article = content.value;
    if (timer) return;
    timer = setTimeout(function() {
      send(article).then(function() {
        clearTimeout(timer);
        timer = null;
      })
    }, 2000);
  }
})();

如果不是对实时性要求非常高的系统,2秒延迟的副作用基本可以忽略,但是却大大减轻了服务器的压力。

缓存代理

缓存代理主要用来为一些重复性高开销又大的运算结果提供存储,在下次运算时如果参数一致则直接返回存储结果。在项目中比较常见的就是分页需求了。同一页的数据只需要在后台获取一次,下次再请求同一页时即可直接用缓存数据。例子如下:

let proxy = (function() {
  let cache = {};
  return (page, fn) {
    let pageData = cache[page];
    if (pageData) {
      return fn(pageData);
    }
    http.getPage(page) // 后台获取数据
    .then((data) => {
      cache[page] = data; // 缓存新数据
      fn(data);
    }
  } // fn处理页码
})();

继续优化的话可以用ES6的weak map来存储缓存数据保证集合结构的纯洁性。

小结

代理模式的主要特点就是代理对象并不影响本体对象的功能接口,主要目的在于信息的优化处理。所以我们在编写业务代码时,不必优先考虑实现代理模式。当发现本体对象数据处理不方便时,再编写代理模式即可。总的来说属于一个锦上添花的模式。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「设计模式」 标签。

文章目录

 - [图片预加载示例](#图片预加载示例)
 - [虚拟代理](#虚拟代理)
 - [缓存代理](#缓存代理)
 - [小结](#小结)
回到首页