代理模式是为一个对象提供一个代用品或占位符,以便控制对他的访问。
代理模式是一种非常有意义的模式,在生活中可以找到很多代理的场景。比如,明星都有经纪人作为代理,明星不会主动与主办方谈论价格和演出的细节,往往是由他的经纪人出面,把商业细节谈好之后,再把合同交由给明星签名。
而我通常使用到代理模式,往往是会在操作一些开销比较大的运算结果提供暂时的存储,在下次运算的时候,如果传递进来的参数与之前一致,则直接返回前面存储的运算结果。
我在日常维护工作中,曾经遇到一个场景。在一个后台管理系统中,表格承担着非常重要的角色,而在原有的模块中,是一个分页的表格,每次点击分页的按钮都会向服务器发起数据请求,但其实在一定的程度上会造成资源的浪费。因为,我们更加希望,如果用户已经点击分页按钮的"1",“2”,“3”…的时候,下次再点击则不会向服务器发起请求。
为了让读者们能更好理解代理模式,下面有几个简单的例子:
缓存代理—计算乘积
创建一个用于求乘积的函数
let mult = function () {
let a = 1;
for (let i = 0; i < arguments.length; i++) {
a = a * arguments[i];
}
return a;
}
加入缓存的代理函数
let createCacheFun = function (fn) {
let cache = {};
return function () {
console.log(fn);
let arg = Array.prototype.join.call(arguments, ",");
if (arg in cache) {
return cache[arg];
}
return cache[arg] = fn.apply(this, arguments);
}
}
let a = createCacheFun(mult);
console.log(a(1, 2, 3, 4)); // 首次计算的结果
console.log(a(1, 2, 3, 4)); // 缓存后的计算结果
当我们第二次调用createCacheFun (1,2,3,4)的时候,mult 函数并没有被计算,而是直接返回之前缓存好的计算结果,通过增加缓存代理的方式,mult函数可以继续专注于自身的职责–计算乘积,缓存的功能是由代理对象实现的。
那么我们又回到刚刚的场景,利用缓存代理用于ajax异步请求数据。为了方便起见,使用的技术栈前台为bootstrap+jquery,后台为node+mysql。
把jquery中的Ajax转换成Promise的形式。
let baseUrl = "http://localhost:8002"; // 请求地址
let baseParams = { // 默认展示参数
currentPage: 1,
pageSize: 5
}
let cache = {} // 缓存的对象
function Ajax(url, params) {
params = Object.assign(baseParams, params); // 合并传递过来的参数
if (!cache[params.currentPage]) {
cache[params.currentPage] = {}
}
return new Promise((resolve, reject) => {
if (Object.keys(cache[params.currentPage]).length > 0) { // 判断缓存对象时候存在值
return resolve(cache[params.currentPage])
} else {
$.ajax({
url: baseUrl + url,
type: "GET",
data: Object.assign(baseParams, params),
success: function (data) {
cache[params.currentPage] = data; // 记录已经请求后的数据
resolve(data);
},
error: function (data) {
reject(data);
}
})
}
})
}
初次加载和创建节点的方法
// 开始加载
Ajax("/bookList", baseParams).then(res => {
let data = res.message
createdTr(data);
})
// 创建节点的方法
let createdTr = (function () {
return function (data) {
$(".table tbody").html("")
let str = "";
$.each(data, (i) => {
str += `
<tr>
<td>${data[i].bookName}</td>
<td>${data[i].bookPrice}</td>
<td>${data[i].author}</td>
</tr>
`
})
$(".table tbody").append(str);
}
})()
点击分页按钮,数据发生切换。
// 列表点击
$(".pagination li").click(function () {
let num = $(this).find("a").text();
if (isNaN(num)) {
return;
}
$(this).addClass("active").siblings("li").removeClass("active");
Ajax("/bookList", {
currentPage: parseInt(num)
}).then(res => {
let data = res.message
createdTr(data);
})
})
利用高阶函数动态创建代理
通过传入高阶函数这种更加灵活的方式,把ajax方法作为一个参数传入一个专门用于创建缓存代理的工厂函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
<table class="table table-striped table-bordered text-center">
<tr>
<td>书名</td>
<td>价格</td>
<td>作者</td>
</tr>
</table>
</div>
<div class="panel-footer">
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="active"><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<script>
let baseUrl = "http://localhost:8002";
let baseParams = {
currentPage: 1,
pageSize: 5
}
function Ajax(url, params) {
params = Object.assign(baseParams, params);
return new Promise((resolve, reject) => {
$.ajax({
url: baseUrl + url,
type: "GET",
data: Object.assign(baseParams, params),
success: function (data) {
resolve(data);
},
error: function (data) {
reject(data);
}
})
})
}
// 创建节点的方法
let createdTr = (function () {
return function (data) {
$(".table tbody").html("")
let str = "";
$.each(data, (i) => {
str += `
<tr>
<td>${data[i].bookName}</td>
<td>${data[i].bookPrice}</td>
<td>${data[i].author}</td>
</tr>
`
})
$(".table tbody").append(str);
}
})()
// 高阶函数实现缓存代理
let createCacheFun = function (fn) {
let cache = {};
return function (url, baseParams) {
// console.log(baseParams);
if (!cache[baseParams.currentPage]) {
cache[baseParams.currentPage] = fn.apply(this, arguments);
}
return cache[baseParams.currentPage];
}
}
// 开始加载
let cacheAjax = createCacheFun(Ajax);
cacheAjax("/bookList", baseParams).then((res) => {
console.log(res);
let data = res.message
createdTr(data);
})
// 列表点击
$(".pagination li").click(function () {
let num = $(this).find("a").text();
if (isNaN(num)) {
return;
}
$(this).addClass("active").siblings("li").removeClass("active");
cacheAjax("/bookList", {
currentPage: parseInt(num)
}).then(res => {
let data = res.message
createdTr(data);
})
})
</script>
</body>
</html>