共计 3621 个字符,预计需要花费 10 分钟才能阅读完成。
原文发表于我的博客:blog.zhangbing.site
在前端我的项目中,咱们的网页通常须要向服务器发送多个 HTTP 申请。
假如咱们的产品具备一项性能,即每当用户单击 li
标记时,客户端都会向服务器发送一个 HTTP 申请。
这是一个简略的 Demo:
<html>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
<script>
// Suppose this function is used to make HTTP requests to the server
var sendHTTPRequest = function(message) {console.log('Start sending HTTP message to the server:', message)
console.log('1000ms passed')
console.log('HTTP Request is completed')
}
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function(event) {if (event.target.nodeName === "LI") {
// Executes this function every time the <li> tag is clicked.
sendHTTPRequest(event.target.innerText)
}
}
</script>
</body>
</html>
在下面的代码中,咱们间接应用简略的 sendHTTPRequest
函数来 模仿 发送 HTTP 申请。这样做是为了更好地专一于外围指标,因而我简化了一些代码。
而后,咱们将 click 事件绑定到 ul
元素。每次用户单击诸如 <li> 5 </li>
之类的标记时,客户端将执行 sendHTTPRequest
函数以向服务器收回 HTTP 申请。
下面的程序是这样的:
为了使你们更容易尝试,我制作了一个 Codepen 演示:https://codepen.io/bitfishxyz…
当然,在实在的我的项目中,咱们可能会向服务器发送一个文件,推送告诉,或者发送一些日志。但为了演示的常规,咱们将跳过这些细节。
好了,这是一个很简略的演示,那么下面的代码有没有什么毛病呢?
如果您的我的项目非常简单,那么编写这样的代码应该没有问题。然而,如果您的我的项目很简单,并且客户端须要频繁向服务器发送 HTTP 申请,则此代码效率很低。
在下面的示例中,如果任何用户重复疾速单击 li
元素会产生什么?这时,咱们的客户端须要向服务器收回频繁的 HTTP 申请,并且每个申请都会耗费大量工夫和服务器资源。
客户端每次与服务器建设新的 HTTP 连贯时,都会耗费一些工夫和服务器资源。因而,在 HTTP 传输机制中,一次传输所有文件比屡次传输大量文件更为无效。
例如,您可能须要发送五个 HTTP 申请,每个 HTTP 申请的 HTTP 数据包大小为 1MB。当初,您一次发送一个 HTTP 申请,数据包大小为 5MB。通常预期后者的性能要比前一个更好。
网页上的大量 HTTP 申请可能会减慢网页的加载工夫,最终侵害用户体验。如果加载速度不够快,这可能会导致访问者更快地来到该页面。
因而,在这种状况下,咱们能够思考合并 HTTP 申请。
在咱们目前的我的项目中,我的思路是这样的:咱们能够在本地设置一个缓存,而后在肯定范畴内收集所有须要发送给服务器的音讯,而后一起发送。
你能够暂停一下,本人试着想方法。
提醒:您须要创立一个本地缓存对象来收集须要发送的音讯。而后,您须要应用定时器定时发送收集到的音讯。
这是一个实现。
var messages = [];
var timer;
var sendHTTPRequest = function (message) {messages.push(message);
if (timer) {return;}
timer = setTimeout(function () {console.log("Start sending messages:", messages.join(","));
console.log("1000ms passed");
console.log("HTTP Request is completed.");
clearTimeout(timer);
timer = null;
messages = [];}, 2000);
};
每当客户端须要发送音讯,也就是触发一个 onclick
事件的时候,sendHTTPRequest
并不会立刻向服务器发送音讯,而是先将音讯缓存在音讯中。而后,咱们有一个计时器,该计时器在 2 秒钟后执行,并且在 2 秒钟后,该计时器会将所有先前缓存的音讯发送到服务器。此更改达到了组合 HTTP 申请的目标。
测试后果如下:
如你所见,只管咱们屡次触发点击事件,但在两秒钟内,咱们只发送了一个 HTTP 申请。
当然,为了不便演示,我将等待时间设置为 2 秒。如果你感觉这个等待时间太长,你能够缩短这个等待时间。
对于不须要太多实时交互的我的项目,2 秒的提早并不是一个微小的副作用,但它能够加重服务器的很多压力。在适当的状况下,这是十分值得的。
下面的代码的确为我的项目提供了一些性能改良。然而就代码设计而言,下面的代码并不好。
第一,违反了繁多责任准则。sendHTTPRequest
函数不仅向服务器发送 HTTP 申请,而且还组合 HTTP 申请。该函数执行过多操作,使代码看起来非常复杂。
如果某个性能(或对象)承当了过多的责任,那么当咱们的需要发生变化时,该性能通常将不得不产生重大变动。这样的设计不能无效地应答可能的更改,这是一个蹩脚的设计。
咱们现实的代码如下所示:
咱们没有对 sendHTTPRequest
进行任何更改,而是抉择为其提供代理。这个代理函数执行合并 HTTP 申请的工作,并将合并后的消息传递给 sendHTTPRequest
发送。而后咱们当前就能够间接应用 proxySendHTTPRequest
办法了。
您能够暂停片刻,而后尝试本人解决。
这是一个实现:
var proxySendHTTPRequest = (function() {var messages = [],
timer;
return function(message) {messages.push(message);
if (timer) {return;}
timer = setTimeout(function() {sendHTTPRequest(messages.join(","));
clearTimeout(timer);
timer = null;
messages = [];}, 2000);
};
})();
其根本思维与后面的代码相似,该代码应用 messages
变量在肯定工夫内缓存所有音讯,而后通过计时器对立地发送它们。此外,这段代码应用了闭包技巧,将 messages
和 timer
变量放在部分作用域中,以防止净化全局名称空间。
这段代码与后面的代码最大的区别是它没有更改 sendHTTPRequest
函数,而是将其暗藏在 proxySendHTTPRequest
前面。咱们不再须要间接拜访 sendHTTPRequest
,而是应用代理 proxySendHTTPRequest
来拜访它。proxySendHTTPRequest
与sendHTTPRequest
具备雷同的参数列表和雷同的返回值。
这样的设计有什么益处?
- 发送 HTTP 申请和合并 HTTP 申请的工作交给了两个不同的函数,每个函数专一于一个职责。它听从繁多责任准则,并使代码更容易了解。
- 因为两个函数的参数是雷同的,咱们能够简略地用
proxySendHTTPRequest
替换sendHTTPRequest
的地位,而不须要做任何重大更改。
设想一下,如果未来网络性能有所提高,或者因为某些其余起因,咱们不再须要合并 HTTP 申请。在这一点上,如果咱们应用以前的设计,咱们将不得不再次大规模地更改代码。在以后的代码设计中,咱们能够简略地替换函数名。
事实上,这个编码技巧通常被称为设计模式中的 代理模式。
所谓的代理模式,其实在现实生活中很好了解。
- 比方说,你想拜访一个网站,但你不想泄露你的 IP 地址。那么你能够应用 VPN,先拜访你的代理服务器,而后通过代理服务器拜访指标网站。这样指标网站就无奈晓得你的 IP 地址了。
- 有时候,你会把你的实在服务器暗藏在 Nginx 服务器前面,让 Nginx 服务器为你的实在服务器解决一些琐碎的操作。
这些都是现实生活中代理模式的例子。
咱们不须要为代理模式(或任何其余设计模式)的正式定义而懊恼,咱们只须要晓得,当客户端没有间接拜访它的便当(或能力)时,咱们能够提供代理性能(或对象)来管制对指标性能(或对象)的拜访即可。客户机实际上拜访代理函数(或对象),代理函数对申请进行一些解决,而后将申请传递给指标。