代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問
1,例子-小明追妹子
(1) 小明喜歡一個妹子,現在小明直接給妹子送花
var Flower = function () { }
var xiaoming = {
sendFlower: function (target) {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var girl = {
receiveFlower: function (flower) {
console.log('收到備胎的鮮花:' + flower)
}
}
xiaoming.sendFlower(girl)
(2)引入一個妹子的朋友,讓她來代替小明做這件事
// 小明摸不準妹子的脈,找了個公共朋友做中間人,也就是代理人
// 當然,現在毫無用處
var Flower = function () { }
var xiaoming = {
sendFlower: function (target) {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var friend = {
receiveFlower: function (flower) {
girl.receiveFlower(flower)
}
}
var girl = {
receiveFlower: function (flower) {
console.log('收到備胎的花:' + flower)
}
}
xiaoming.sendFlower(friend)
(3) 妹子有時候會心情不好,這個時候就需要妹子朋友來監聽其心情變化,中間工具人的作用現在體現出來了
// 代理人,也就是工具人,會有自己的想法,幫小明打助攻
var Flower = function () { }
var xiaoming = {
sendFlower: function (target) {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var friend = {
receiveFlower: function (flower) {
girl.listenGoodMood(function () {
girl.receiveFlower(flower)
})
}
}
var girl = {
receiveFlower: function (flower) {
console.log('收到備胎的花:' + flower)
},
listenGoodMood: function (fn) {
setTimeout(() => {
fn()
}, 2000)
}
}
xiaoming.sendFlower(friend)
2,保護代理和虛擬代理
friend可以幫助girl過濾掉一些不合條件的對象,比如很窮的,這種情況下小明可能就直接被pass了,這叫做保護代理。
假如鮮花太貴,不好保存,小明需要在妹子心情好的時候纔去買花,這叫虛擬代理。虛擬代理把new Flower這種開銷比較大的操作,延遲到真正需要的時候才創建:
// 虛擬代理,用的時候才創建
var Flower = function () { }
var xiaoming = {
sendFlower: function (target) {
target.receiveFlower()
}
}
var friend = {
receiveFlower: function () {
girl.listenGoodMood(function () {
var flower = new Flower()
girl.receiveFlower(flower)
})
}
}
var girl = {
receiveFlower: function (flower) {
console.log('收到備胎的花:' + flower)
},
listenGoodMood: function (fn) {
setTimeout(() => {
fn()
}, 2000)
}
}
xiaoming.sendFlower(friend)
虛擬代理,也就是在真正需要使用flower的時候才創建new Flower
3,虛擬代理實現圖片預加載
3.1 圖片加載
var myImage = (function () {
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: function (src) {
imgNode.src = src
}
}
})()
//如果圖片很大或者網速很慢,這玩意就加載的很慢,需要loading
myImage.setSrc('https://pic.ibaotu.com/01/34/46/37e888piCp9c.jpg-0.jpg!ww7002')
在圖片下載完成時,會有空白,所有需要一個loading
3.2 代理解決loading問題
假如說網速很慢,只有1kb/s,那麼這個空白會持續很長時間,這個時候就需要一個代理,這個代理是一個新的圖片對象img,由代理來加載圖片,加載完成之後真正的圖片myImage修改自己的src。
var myImage = (function () {
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: function (src) {
imgNode.src = src
}
}
})()
var proxyImage = (function () {
//創建代理圖片
var img = new Image
img.onload = function () {
myImage.setSrc(this.src)
}
return {
setSrc: function (src) {
myImage.setSrc('imgs/loading.jpeg')
img.src = src
}
}
})()
proxyImage.setSrc('imgs/2.jpg')
3.3 代理的意義在哪裏?
這時候大家可能有疑問,不就是加載個圖片嗎,搞這麼複雜?
3.2代碼的意義何在呢,如果只是實現一個簡單的圖片預加載,實際上不需要這麼麻煩。
我們可以這麼寫:
var myImage = (function () {
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
var img = new Image
return {
setSrc: function (src) {
imgNode.src = 'imgs/loading.jpeg'
img.src = src
img.onload = function () {
imgNode.src = src
}
}
}
})()
myImage.setSrc('imgs/2.jpg')
但是,這段代碼的問題在於,其違反了單一職責原則。
單一職責原則指的是,就一個類(通常也包括對象和函數)而言,應該僅有一個引起它變化的原因。如果一個對象承擔了多項職責,就意味着這個對象將變得巨大,引起它變化的原因可能有多個。面向對象設計鼓勵將行爲分佈到細粒度的對象之中,如果一個對象承擔的職責過多,等於把這些職責耦合到了一起,這種耦合會導致脆弱的低內聚的設計。當變化發生時,設計可能會遭到意外地破壞。
另外,現在5g出來了,我們希望取消掉預加載功能,使用代理模式,直接刪除proxyImage就可以了,但是這段代碼我們就不得不修改myImage中的代碼了。
4,虛擬代理在惰性加載中的應用
現在我們要實現一個簡單的控制檯功能:minConsole.js,當按下f12時會調用這段代碼。
但是呢,minConsole.js這個庫有點大,我不希望在f12之前就直接加載它,於是在此之前需要先實現一個虛擬的minConsole方法,緩存調用minConsole.js之前執行的操作,這樣可以保證無論何時我們都能正常調用minConsole.js裏面的api。
未加載minConsole.js之前的代碼:
var cache = []
//模擬的miniConsole,假裝可以用
var miniConsole = {
log: function () {
var args = arguments
cache.push(function () {
miniConsole.log.apply(miniConsole, args)
})
}
}
miniConsole.log(1, 4)
miniConsole.log(2)
當用戶按下f12,開始加載真正的minConsole.js:
//虛擬代理在惰性加載中的應用:只有當用到的時候才加載
var handler = function (e) {
if (e.keyCode === 13) {
var script = document.createElement('script')
//加載完成之後執行緩存的代碼
script.onload = function () {
for (var i = 0, fn; fn = cache[i++];) {
fn()
}
}
script.src = "./miniConsole.js"
document.getElementsByTagName('head')[0].appendChild(script)
}
}
document.body.addEventListener('keydown', handler, false)
// miniConsole.js代碼
console.log('welcome miniConsole.js')
miniConsole = {
log: function () {
//真正的代碼略
console.log(Array.prototype.join.call(arguments))
}
}
上面的這段代碼中,有一個問題:如果多次按f12,script會被重複創建。
現在我們來進行優化:
var miniConsole = (function () {
var cache = []
var handler = function (e) {
if (e.keyCode === 13) {
var script = document.createElement('script')
script.onload = function () {
for (var i = 0, fn; fn = cache[i++];) {
fn()
}
}
script.src = "./miniConsole.js"
document.getElementsByTagName('head')[0].appendChild(script)
//添加這一句,取消監聽事件
document.body.removeEventListener('keydown', handler)
}
}
document.body.addEventListener('keydown', handler, false)
return {
log: function () {
var args = arguments
cache.push(function () {
miniConsole.log.apply(miniConsole, args)
})
}
}
})()
miniConsole.log(1, 4)
miniConsole.log(2)