JavaScript代理模式

1 什么是代理模式

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

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象,替身对象对请求做出一些处理之后,再把请求转交给本体对象。

2 实现一个简单的代理模式

举个例子,A与B是好朋友,但是有一天两人吵架了,于是A决定向B发消息道歉,这段行为用代码简单描述一下,如下所示:

var Message = function () {};

var A = {
  // 发送消息方法
  sendMessage: function (target) {
    var message = new Message();
    target.receiveMessage(message);
  },
};

var B = {
  // 接收消息方法
  receiveMessage: function (message) {
    console.log("收到消息:" + message);
  },
};

A.sendMessage(B);

但是,如果B拉黑了A,导致A无法通过发消息给B,只能通过两人的共同好友C来表达,这时A通过C来向B道歉,如下所示:

var Message = function () {};

var A = {
  // 发送消息方法
  sendMessage: function (target) {
    var message = new Message();
    target.receiveMessage(message);
  },
};

var C = {
  // C接收消息,并发送给B
  receiveMessage: function (message) {
    B.receiveMessage(message);
  },
};

var B = {
  // 接收消息方法
  receiveMessage: function (message) {
    console.log("收到消息:" + message);
  },
};

A.sendMessage(C);

如果C很了解B,B心情好的时候会与A和解,B心情不好的时候调解会失败,那么C可以通过监听B的心情变化来决定什么时候发消息给B,代码如下:

var Message = function () {};

var A = {
  // 发送消息方法
  sendMessage: function (target) {
    var message = new Message();
    target.receiveMessage(message);
  },
};

var C = {
  // 接收消息,并发送给B
  receiveMessage: function (message) {
    // 监听B的好心情,在心情好时发消息
    B.listenGoodMood(function () {
      B.receiveMessage(message);
    });
  },
};

var B = {
  // 接收消息方法
  receiveMessage: function (message) {
    console.log("收到消息:" + message);
  },
  // 监听心情变化方法
  listenGoodMood: function (fun) {
    // 假设1s之后心情变好
    setTimeout(() => {
      fun();
    }, 1000);
  },
};

A.sendMessage(C);

3 保护代理和虚拟代理

在上述例子中,代理C可以帮助B过滤掉一些消息,比如B不认识的人,或者B讨厌的人,这些消息在代理C中被过滤掉,这叫做保护代理

保护代理用于控制不同权限的对象对目标对象的访问,但在JavaScript并不容易实现保护代理,因为我们无法判断谁访问了某个对象。

C可以选择在B心情好的时候发送消息,使成功概率增加,这叫做虚拟代理。虚拟代理可以把一些开销很大的对象,延迟到真正需要它的时候才去创建。例如:

var C = {
  // 接收消息,并发送给B
  receiveMessage: function () {
    // 监听B的好心情,在心情好时发消息
    B.listenGoodMood(function () {
      var message = new Message();
      B.receiveMessage(message);
    });
  },
};

4 虚拟代理实现图片预加载

Web开发中,图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性,如果图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。

常见的做法是先用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种场景就很适合使用虚拟代理。

下面我们来实现这个虚拟代理,首先创建一个普通的本体对象,这个对象负责往页面中创建一个img标签,并且提供一个对外的setSrc接口,外界调用这个接口,便可以给该img标签设置src属性:

var myImage = (function () {
  // 创建img标签
  var imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return {
    setSrc: function (src) {
      // 设置图片src方法
      imgNode.src = src;
    },
  };
})();

myImage.setSrc("XXXXXXX");

如果我们把网速调慢,然后通过MyImage.setSrc给该img节点设置src,可以看到,在图片被加载好之前,页面中有一段长长的空白时间。
在这里插入图片描述
现在开始引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位的loading.gif, 来提示用户图片正在加载。代码如下:

var myImage = (function () {
  // 创建img标签
  var imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return {
    setSrc: function (src) {
      // 设置图片src方法
      imgNode.src = src;
    },
  };
})();

// 代理对象,在图片加载好之前,放一张loading图片
var proxyImage = (function () {
  var img = new Image();
  img.onload = function () {
    myImage.setSrc(this.src);
  };
  return {
    setSrc: function (src) {
      myImage.setSrc("XXXXXXXXXX");
      img.src = src;
    },
  };
})();

proxyImage.setSrc("XXXXXXXXXX");

在这里插入图片描述

5 虚拟代理合并HTTP请求

例如以下场景,上学时每天都需要写作业,作业最后交给老师批阅,但是老师可能带了许多个班级,每个班级又有很多学生,如果老师亲自收作业,会浪费大量的时间,如果我们先把作业交给小组长,小组长再交给班长,由班长负责交给老师,这样可以节省很多工夫。

Web开发中,网络请求是很大的开销。假设我们在做一个文件同步的功能,当我们选中一个checkbox的时候,它对应的文件就会被同步到另外一台备用服务器上面:

<input type="checkbox" id="1" />1 <input type="checkbox" id="2" />2
<input type="checkbox" id="3" />3 <input type="checkbox" id="4" />4
<input type="checkbox" id="5" />5 <input type="checkbox" id="6" />6
<input type="checkbox" id="7" />7 <input type="checkbox" id="8" />8
<input type="checkbox" id="9" />9
var synchronousFile = function (id) {
  console.log("开始同步文件,id 为: " + id);
};

var checkbox = document.getElementsByTagName("input"); // 获取所有的checkbox
for (var i = 0, c; (c = checkbox[i++]); ) {
  // 每点击checkbox一次,就发送一次文件
  c.onclick = function () {
    if (this.checked === true) {
      synchronousFile(this.id);
    }
  };
}

在这里插入图片描述
当我们选中4个checkbox的时候,依次往服务器发送了4次同步文件的请求。而点击一个checkbox并不是很复杂的操作,用户可以再很短的时间内快速进行checkbox的点击工作,由此可见,如此频繁的网络请求将会带来相当大的开销。

这时,我们可以通过一个代理函数proxySynchronousFile来收集一段时间之内的请求,最后一次性发送给服务器。比如等待2秒之后才把这2秒之内需要同步的文件ID打包发给服务器,如果不是对实时性要求非常高的系统,2 秒的延迟不会带来太大副作用,却能大大减轻服务器的压力。

var synchronousFile = function (id) {
  console.log("开始同步文件,id 为: " + id);
};

var proxySynchronousFile = (function () {
  var cache = [], // 保存2s之内需要同步的文件id
    timer; // 定时器
  return function (id) {
    cache.push(id); // 推入id
    if (timer) return; // 如果定时器已经启动,不再执行下面的操作

    timer = setTimeout(() => {
      synchronousFile(cache.join(",")); // 向服务器发送文件
      clearTimeout(timer); // 清空定时器
      timer = null;
      cache.length = 0; // 清空id集合
    }, 2000);
  };
})();

var checkbox = document.getElementsByTagName("input"); // 获取所有的checkbox
for (var i = 0, c; (c = checkbox[i++]); ) {
  // 每点击checkbox一次,就发送一次文件
  c.onclick = function () {
    if (this.checked === true) {
      proxySynchronousFile(this.id);
    }
  };
}

在这里插入图片描述

6 缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。


例如:计算乘积,假设计算乘积是一个很复杂的运算

先创建一个计算乘积的函数:

var mult = function () {
  let result = 1;
  for (let i = 0, l = arguments.length; i < l; i++) {
    result = result * arguments[i];
  }
  return result;
};

mult( 2, 3 ); // 输出:6 
mult( 2, 3, 4 ); // 输出:24

现在加入缓存代理函数:

var mult = function () {
  let result = 1;
  for (let i = 0, l = arguments.length; i < l; i++) {
    result = result * arguments[i];
  }
  return result;
};

var proxyMult = (function () {
  let cache = {};
  return function () {
    let 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

当我们第二次调用proxyMult( 1, 2, 3, 4 )的时候,mult函数并没有被计算,直接返回了之前缓存好的计算结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值