代理模式是爲對象提供一個代用品或者佔位符,以便控制對它的直接訪問。當一個對象不想被外界直接訪問,就能夠使用代理,提供一個替身對象來控制對它的訪問,替身對象對請求進行處理以後,再把請求轉交給本體對象。
javascript
通常代理有兩種類型,保護代理和虛擬代理。
保護代理 保護代理主要用來控制不一樣權限的對象對目標對象的訪問,若是目標對象想過濾掉一些請求,就可讓本身的代理去處理掉。
虛擬代理 虛擬代理主要用來延時去建立對象或者執行目標對象的方法,若是執行目標對象的方法是一個開銷很大的操做,虛擬代理就能夠先代理目標對象的方法,延遲到真正須要它的時候再去調用。
在JavaScript中實現保護代理比較困難,由於很難判斷誰訪問了對象,虛擬代理是一種比較經常使用的代理模式,因此接下來主要講解虛擬代理的應用。html
在web開發中,圖片預加載是一種經常使用的技術,當咱們在網絡環境差的狀況下加載一張大圖,圖片會有一段空白的時間,爲了提升用戶體驗,常見的作法是先使用一張loading佔位圖,而後經過異步的方式去加載圖片,等圖片加載好了再填充到img中。下面經過虛擬代理來實現這個功能:java
const myImage = (function () {
const imgNode = document.createElement('img)
document.body.appendChild(imgNode)
return {
setSrc: function (src) {
imgNode.src = src
}
}
})()
const proxyImage = (function () {
const img = new Image()
img.onload = function () {
myImage.setSrc(this.src)
}
return {
setSrc: function (src) {
myImage.setSrc('loading.gif')
this.src = src
}
}
})()
proxyImage.setSrc('http://xxx.com/01.jpg')
複製代碼
思考
爲何這麼簡單的功能須要使用代理模式了?
咱們引入面向對象中的一個原則----單一職責原則。
它指的是一個對象或者函數,應該只有一個引發它變化的緣由,通俗地將它只作一件事。在上述的圖片預加載功能中,若是咱們不引入代理,那myImage對象既要負責給img設置src,還要負責預加載圖片,這樣兩個功能耦合在一塊兒。若是有一天咱們不須要預加載功能,就只能直接修改myImage對象,把預加載圖片那部分的功能刪掉,這也違反了開閉原則。引入代理模式,完美瞭解決了上述的問題。程序員
在web開發中,網絡請求的開銷很大,假設咱們要作一個文件同步的功能,當咱們選中文件的時候,文件就會同步到服務器。首先在頁面中建立可供點擊的checkbox節點:web
<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="7"/>8
複製代碼
接下來,給checkbox綁定點擊事件,而後同步文件到服務器:ajax
const synchronousFile = function (id) {
console.log(`同步文件, id爲${id}`)
}
const checkboxList = document.querySelectorAll('input')
for (let i = 0, len = checkboxList.length; i < len; i++) {
const checkbox = checkboxList[i]
checkbox.onclick = function () {
if (this.checkbox === true) {
synchronousFile(this.id)
}
}
}
複製代碼
當咱們一次性選了3個checkbox的時候,同時也向服務器發送了3次文件同步的請求。對於手速快的程序員,可能1秒鐘能夠點擊4個checkbox,這樣頻繁的網絡請求會帶來很大的開銷。
解決方案就是經過代理synchronousFile方法,收集一段時間的請求,最後一次性發給服務器。好比等待兩秒鐘以後,而後把兩秒鐘內的須要同步的文件列表一塊兒發送給服務器。實現代碼以下:後端
const synchronousFile = function (id) {
console.log(`同步文件, id爲${id}`)
}
const proxySynchronousFile = (function () {
let cache = [], timer;
return function (id) {
if (cache.indexOf(id) > -1) {
cache.push(id)
}
if (timer) {
return
}
timers = setTimeout(function () {
synchronousFile(cache.join(','))
clearTimeout(timer)
timer = null
cache = []
}, 2000)
}
})()
const checkboxList = document.querySelectorAll('input')
for (let i = 0, len = checkboxList.length; i < len; i++) {
const checkbox = checkboxList[i]
checkbox.onclick = function () {
if (this.checkbox === true) {
proxySynchronousFile(this.id)
}
}
}
複製代碼
緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟以前一致,則能夠直接返回結果。
計算階乘
緩存
const factorial = function (n) {
if (n === 0 || n === 1) {
return 1
}
return n * factorial(n-1)
}
factorial(4) // 24
factorial(4) // 24
複製代碼
引入代理:服務器
const proxyFactorial = (function () {
const cache = {}
return function (n) {
if (cache[n]) {
return cache[n]
}
return cache[n] = factorial.call(this, n)
}
})()
proxyFactorial(4) // 24
proxyFactorial(4) // 24
複製代碼
緩存ajax請求
咱們在項目中常常會遇到分頁的請求,在一些場景下,同一頁的數據理論上只須要去服務端獲取一次,而後能夠緩存起來,下次再請求同一頁的時候就能夠直接從緩存中獲取。這種情形也能夠使用緩存代理,須要注意的是從後端獲取數據是一個異步操做,咱們沒法直接把計算結果同步的放在緩存中,而是要經過回調的方式。網絡
代理模式的變體種類不少,下面作挑一些作簡單的介紹。
經過前面的這些應用咱們能夠看到,代理和目標對象有一致的接口。好比經過代理實現的預加載圖片的功能,實現了setSrc方法,當咱們有一天不須要圖片預加載功能,也能夠直接調用本體的方法,由於它們都對外提供了setSrc方法。在客戶看來,代理和目標對象是一致的,客戶並不知道代理和目標對象的區別。這樣的作有兩個好處: