js常用設計模式3-代理模式 1,例子-小明追妹子 2,保護代理和虛擬代理 3,虛擬代理實現圖片預加載 4,虛擬代理在惰性加載中的應用

代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問

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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章