阅读 69

「设计模式」代理模式🥑

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

代理模式的定义

使用代理模式,在访问一个对象时,我们需要提供一个代用品(或占用符),以便控制对这个对象的访问。这个代用品我们称之为代理。

代理模式是一种非常有意义的模式,它的关键是当客户不方便直接访问一个对象时,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象会对客户的请求作出一些处理之后,再把请求转交给本体对象。

代理模式细分起来可以分为保护代理、虚拟代理、防火墙代理、远程代理、智能引用代理、写时复制代理、缓存代理。

引用自《JavaScript设计模式与开发实践》,关于各个应用种类的定义

  • 保护代理:用于对象应该有不同访问权限的情况
  • 虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候再执行
  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近
  • 远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另一个虚拟机中的对象
  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数
  • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL(操作系统中的动态链接库)是其典型运用场景
  • 缓存代理:可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数和之前一致,则可以直接返回前面存储的结果

虽然代理模式包括许多小分类,但是在 JavaScript 开发中最常用的是虚拟代理和缓存代理。本文也将着重就这两种代理的类型来做阐述。

代理模式的优缺点

优点:

  • 节省代码执行开支,如虚拟代理、保护代理,都有效的减少目标函数的调用次数
  • 代码层次分明,增删简单,方便维护

缺点:

  • 代码编写量多
  • 对编码者的能力有一定要求

代理模式的应用场景

虚拟代理

虚拟代理会把一些开销很大的对象,延迟到真正需要它的时候再执行。我们可以做一个场景假设,单身狗A想要送礼物给女神C表白,但是单身狗拿捏不住女神的心思,不知道选什么礼物在什么时机表白成功率最高,好在他们有个共同的朋友B,A把买礼物和送礼物这个操作全权委托B去操作,自己在适当的时机出现,结果完美。

var B = {
    sendGift: function() {
        C.listenGoodMood(function() { // 时刻监听女神C的好心情
            var gift = new Gift(); // 到了适当的时机再买礼物
            C.receiveGift(gift); // 女神C顺利接收礼物
        });
    }
} 
复制代码

上述是一个助于我们理解虚拟代理的场景解释。在实际业务开发中,图片预加载是最常用的场景之一。图片加载时,如果直接给某个 img 标签节点设置 src 属性,由于图片过大或者网络不佳,图片的位置往往会有段空白期。常见的做法是先用 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这就是图片预加载。

缓存代理

接下来看看缓存代理,它的作用更多的是体现在缓存两个字上,通过设置暂时存储空间,存储数据,减少重复的计算逻辑和接口请求等。

它的应用场景有用于处理计算的函数和 ajax 异步请求数据如分页请求。

代理模式的实现原理分析

图片预加载(虚拟代理)

  • 新建一个对象 myImage,用来负责往页面中创建 img 标签,提供一个对外的 setSrc 接口,外界通过这个接口给 img 标签设置 src 属性
  • 引入代理对象 proxyImage,通过代理对象,在图片加载好之前,img 先被设置成占位的 loading.gif 提示用户图片正在加载
  • 代理对象暴露出 setSrc 方法,控制用户对 MyImage 的访问,并且在此过程中加入一些额外操作
  • 用户直接调用代理对象提供的 setSrc 方法,传入目标图片

ajax 异步请求数据(缓存代理)

  • 实现核心接口请求函数 handle
  • 加入创建缓存代理的工厂函数 createProxyHandle
  • 调用工厂函数新建处理接口的代理函数 proxy
  • 调用代理函数进行请求

具体实现的代码

虚拟代理实现图片预加载

// 新建用于创建 img 的对象
var myImage = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild( imgNode );
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();
// 新建代理对象,用于控制对 myImage 的访问
var proxyImage = (function() {
    var img = new Image;
    // 图片加载完成后执行
    img.onload = function() {
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            // 默认先设置成 loading
            myImage.setSrc('loading.gif');
            // 开始对目标图片进行加载
            img.src = src;
        }
    }
})();
// 用户调用时传入目标图片 aim.png
proxyImage.setSrc('aim.png');
复制代码

缓存代理处理 ajax 异步请求数据

// 核心处理函数
var handle = function(api) {
    // 用延时模拟接口请求异步返回
    setTimeout(function(){
        console.log('接口返回:', api);
    }, 1000);
};
// 用于批量生成代理的工厂函数
var createProxyHandle = function(fn) {
    var cache = {};
    return function(api) {
        if(api in cache) {
            console.log(api+'重复,直接返回');
            return cache[api];
        }
        return cache[api] = fn.apply(this, [api]);
    }
};
// 利用工厂函数生成缓存代理
var proxy = createProxyHandle(handle);
// 尝试一下
proxy('请求1');
proxy('请求1');
proxy('请求2');
复制代码
文章分类
前端