代理模式是爲一個對象提供一個代用品或佔位符,以便控制對他的訪問。
代理模式是一種非常有意義的模式,在生活中可以找到很多代理的場景。比如,明星都有經紀人作爲代理,明星不會主動與主辦方談論價格和演出的細節,往往是由他的經紀人出面,把商業細節談好之後,再把合同交由給明星簽名。
而我通常使用到代理模式,往往是會在操作一些開銷比較大的運算結果提供暫時的存儲,在下次運算的時候,如果傳遞進來的參數與之前一致,則直接返回前面存儲的運算結果。
我在日常維護工作中,曾經遇到一個場景。在一個後臺管理系統中,表格承擔着非常重要的角色,而在原有的模塊中,是一個分頁的表格,每次點擊分頁的按鈕都會向服務器發起數據請求,但其實在一定的程度上會造成資源的浪費。因爲,我們更加希望,如果用戶已經點擊分頁按鈕的"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>