• js 设计模式——代理模式


    代理模式

    代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

    生活中有很多的代理模式的场景。例如,明星有经纪人作为代理,老板有秘书作为代理等等,当有事情的时候,会找到经纪人或秘书,再由他们转达给明星或者老板。

    首先通过一个例子来简单的了解了解,故事是这样的...

    以下故事纯属虚构,不要当真

    大家都知道三顾茅庐(不知道的百度一下)吧,诸葛亮何许人也,厉害的不要不要的,名声在外啊。好巧不巧刘备知道了,刘备心想:“这么厉害的人跟着我,岂不美哉,统一三国起步指日可待”,于是,刘备带着礼物就去请人家了。正常流程应该这样:

    // 刘备
    let bei = {
      // 邀请
      invite(){
        liang.reception('草鞋')
      }
    }
    // 亮
    let liang = {
       // 收到礼物
       reception(gift){
         console.log('亮收到礼物:' + gift)
       }
    }
    // 调用方法
    bei.invite()
    

    但是呢,事实不是这样的,诸葛亮心想:“刘备一卖草鞋的就想见我?”,于是呢,刘备只见到了门童:

    // 刘备
    let bei = {
      // 邀请
      invite(){
        mentong.reception('草鞋')
      }
    }
    // 门童
    let mentong = {
      // 接收礼物
      reception(gift){
        console.log('门童收到礼物:' + gift)
        // 给诸葛亮
        liang.reception('草鞋')
      }
    }
    // 亮
    let liang = {
      // 接收礼物
      reception(gift){
        console.log('亮收到礼物:' + gift)
      }
    }
    // 调用方法
    bei.invite()
    

    所以,刘备就只能把礼物给了门童,门童在交给了诸葛亮,然后诸葛亮一看,好家伙,草鞋。。。

    到此可以看成一个简单的代理了

    保护代理和虚拟代理

    保护代理

    诸葛亮收到草鞋后也是无语,然后叫来门童告诉他:“以后呢,送草鞋的,你就不用给我了,自己看着处理就好了”,门童心领神会,表示ok

    // 门童
    let mentong = {
      // 接收礼物
      reception(gift){
        console.log('门童收到礼物:' + gift)
        if(gift !== '草鞋'){
          // 给诸葛亮
          liang.reception(gift)
        }
      }
    }
    

    通过代理(门童)来处理一些不必要的东西,过滤掉无用信息,这可以理解为 保护代理

    但在 JavaScript 并不容易实现保护代理,因为我们无法判断谁访问了某个对象。

    虚拟代理

    话题又回到刘备这,刘备这连着送礼都两天了,也见不到人。有人就给他出了个方法。于是啊就去找到门童说:“小兄弟这钱你拿着,你帮我买点礼物给诸葛先生”,门童也是诧异什么时候变聪明了

    // 门童
    let mentong = {
      // 接收礼物
      reception(){
        // 拿钱去买礼物
        let book = new Book()
        // 给诸葛亮
        liang.reception(book)
      }
    }
    

    诸葛亮这回挺开心,于是就答应见刘备了(~ ̄▽ ̄)~

    这可以理解为 虚拟代理

    虚拟代理实现图片预加载

    注:I know 这个例子大家可能都看过了,因为没有想到更好的例子(想到了更改补上),但是这个例子我会一行一行讲解清楚的o( ̄▽ ̄)ブ

    平时由于网络的不佳,导致图片出来前会有一片空白。所以我们限用一张 loading 图片占位,在异步方式加载图片

    没用代理

    // 创建一个本体对象
    var myImage = (function(){
      // 创建标签
      var imgNode = document.createElement( 'img' );
      // 添加到页面
      document.body.appendChild( imgNode );
      return {
        // 设置图片的src
        setSrc: function( src ){
          // 更改src
          imgNode.src = src;
        }
      }
    })();
    
    myImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
    

    引入代理对象

    // 创建一个本体对象
    var myImage = (function(){
      // 创建标签
      var imgNode = document.createElement( 'img' );
      // 添加到页面
      document.body.appendChild( imgNode );
      return {
        // 设置图片的src
        setSrc: function( src ){
          // 更改src
          imgNode.src = src;
        }
      }
    })();
    
    // 创建代理对象
    var proxyImage = (function(){
      // 创建一个新的img标签
      var img = new Image;
      // img 加载完成事件
      img.onload = function(){
        // 调用 myImage 替换src方法
        myImage.setSrc( this.src );
      }
      return {
        // 代理设置地址
        setSrc: function( src ){
          // 预加载 loading
          myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
          // 赋值正常图片地址
          img.src = src;
        }
      }
    })();
    
    proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
    

    现在我们可以通过 proxyImage 间接地访问 MyImageproxyImage 控制了客户对 MyImage 的访问,并
    且在此过程中提前把 img 节点的 src 设置为了一张本地的 loading 图片


    现在再来看看不用代理来实现预加载

    // 创建一个本体对象
    var MyImage = (function(){
      // 创建标签
      var imgNode = document.createElement( 'img' );
      // 添加到页面
      document.body.appendChild( imgNode );
      //  创建一个新的img标签
      var img = new Image;
      // img 加载完成
      img.onload = function(){
        // 替换地址
        imgNode.src = img.src;
      };
      return {
        // 设置地址
        setSrc: function( src ){
          // 本地 loading 图片地址
          imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif';
          // 赋值正常图片地址
          img.src = src;
        }
      }
    })();
    
    MyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
    

    现在来看看没有用代理的代码

    • 违反了单一原则。MyImage除了要负责img节点的设置,还要负责预加载图片。这导致在处理其中一个职责时会因其强耦合性影响另一个职责。
    • 违反了开闭原则。倘若以后要去掉预加载,只能去更改MyImage对象,这不符合开闭原则。

    代理和本体接口的一致性

    如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请求本体。其中关键是代理对象和本体都对外提供了 setSrc 方法,在客户看来,代理对象和本体是一致的, 代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别这样做有两个好处:

    • 用户可以放心地请求代理,他只关心是否能得到想要的结果。
    • 在任何使用本体的地方都可以替换成使用代理。(是不是有点里氏替换的味道)
    // 预加载
    proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
    
    // 不用预加载
    myImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
    

    缓存代理

    再举一个经典的例子啊(没想到别的,以后想到了再换(~ ̄▽ ̄)~)

    乘积函数

    var mult = function(){
      console.log( '开始计算乘积' );
      var a = 1;
      for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a * arguments[i];
      }
      return a;
    };
    

    缓存代理函数

    var proxyMult = (function(){
      // 缓存结果
      var cache = {};
      return function(){
        // 将参数转化为字符串
        var args = Array.prototype.join.call( arguments, ',' );
        // 遍历缓存结果如果存在直接返回结果
        if ( args in cache ){
          return cache[ args ];
        }
        // 不存在进行计算并保存结果
        return cache[ args ] = mult.apply( this, arguments );
      }
    })();
    
    proxyMult( 1, 2, 3, 4 ); // 输出:24 
    proxyMult( 1, 2, 3, 4 ); // 输出:24
    

    我们也可以动态创建代理

    /**************** 计算乘积 *****************/
    var mult = function(){
      var a = 1;
      for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a * arguments[i];
      }
      return a;
    }
    /**************** 计算加和 *****************/
    var plus = function(){
      var a = 0;
      for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a + arguments[i];
      }
      return a;
    }
    /**************** 创建缓存代理的工厂 *****************/
    var createProxyFactory = function( fn ){
      // 缓存结果
      var cache = {};
      return function(){
        // 将参数转换成字符串
        var args = Array.prototype.join.call( arguments, ',' );
        // 遍历缓存结果如果存在直接返回结果
        if ( args in cache ){
          return cache[ args ];
        }
        // 不存在进行相应的计算并保存结果
        return cache[ args ] = fn.apply( this, arguments );
      }
    };
    
    // 创建乘法和加法
    var proxyMult = createProxyFactory( mult ),proxyPlus = createProxyFactory( plus )
    
    alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24 
    alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24 
    alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10 
    alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
    

    代理模式包括许多小分类,在JavaScript开发中最常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但我们在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。

    目前对于代理模式的理解就这么多,以后有了新的理解会继续更新的,溜了溜了(~ ̄▽ ̄)~

  • 相关阅读:
    Java的特性和优势
    MyBatis
    SpringBoot简介
    Liunx
    MySql简介与入门
    Volatile
    MySQL简介
    Redis
    Spring IoC
    什么是springboot
  • 原文地址:https://www.cnblogs.com/loveyt/p/11410593.html
Copyright © 2020-2023  润新知