JavaScript設計模式(5)—— 結構型設計模式【1】

 

原文出自於本人個人博客網站:https://www.dzyong.com(歡迎訪問)

轉載請註明來源: 鄧佔勇的個人博客 - 《JavaScript設計模式(5)—— 結構型設計模式【1】

本文鏈接地址: https://www.dzyong.com/#/ViewArticle/90

設計模式系列博客:JavaScript設計模式系列目錄(持續更新中)

 

結構型設計模式關注於如何將類或對象組合成更大、更復雜的結構,以簡化設計。

結構型設計模式共7種,分爲兩次來進行介紹。本次介紹其中的外觀模式適配器模式代理模式裝飾者模式

 

外觀模式

爲一組複雜的子系統接口提供一個更高級的同一接口,通過這個接口使得對子系統接口的訪問更容易。在JavaScript中有時也會對底層結構兼容性做出同一封裝來簡化用戶使用。

例子:爲document綁定一個click事件來實現隱藏提升抗的交互功能。

傳統寫法: 

document.onclick = function(e){
    e.preventDefault()
    if(e.target != document.getElementById('myinput'))
        hidePageAlert()
}
function hidePageAlert(){}

這樣的寫法存在兩個問題,第一:如果其他人再次爲document綁定事件時會覆蓋掉原來的處理事件。第二:對於支持DOM2級事件的瀏覽器應該使用addEventListener方法,對於老版本的IE(9以下)應該使用attachEvent,對於不支持DOM2級的瀏覽器才使用onclick。

因此我們使用外觀模式對綁定事件進行一個包裝。

/*外觀模式*/
let addEvent = function(dom, type, fn){
    //對於支持DOM2級時間處理程序addEventListener方法的瀏覽器
    if(dom.addEventListener)
        dom.addEventListener(type, fn, false)
    //對於不支持DOM2級時間處理程序addEventListener但支持attachEvent方法的瀏覽器
    else if(dom.attachEvent)
        dom.attachEvent('on' + type, fn)
    else
        dom['on' + type] = fn
}

在使用的時候就可以放心的綁定多個時間啦

let myInput = document.getElementById('myInput')
addEvent(myInput, 'click', function(){console.log('綁定了一個點擊事件')})
addEvent(myInput, 'click', function(){console.log('綁定了兩個點擊事件')})

外觀模式可以簡化底層接口的複雜性,可以解決瀏覽器的兼容性問題,而在前面除了綁定事件外,在低版本的IE中也不兼容e.preventDefault()和e.target,這種也可以通過外觀模式來解決。

//獲取事件對象
let getEvent = function(event){
    //標準情況下返回event,IE下window.event
    return event || window.event
}
//獲取元素
let getTarget = function(event){
    var event = getEvent(event)
    return event.target || event.srcElement
}
//阻止默認行爲
let preventDefault = function(event){
    var event = getEvent(event)
    //標準瀏覽器
    if(event.preventDefault)
        event.preventDefault
    //IE瀏覽器
    else
        event.returnValue = false
}

addEvent(myInput, 'click', function(e){
    preventDefault(e)
    //獲取時間源目標對象
    if(getTarget(e) !== document.getElementById('myInput'))
        console.log('綁定了三個點擊事件')
})

上面只是外觀模式應用的一部分,很多代碼庫都通過外觀模式來進行封裝多個功能,簡化底層的操作方法。

let A = {
    //通過ID獲取元素
    g: function(id){
        return document.getElementById(id)
    },
    css: function(id, key, value){
        document.getElementById(id).style[key] = value
    },
    attr: function(id, key, value){
        document.getElementById(id)[key] = value
    },
    html: function(id, html){
        document.getElementById(id).innerHTML = html
    },
    on: function(id, type, fn){
        //對於支持DOM2級時間處理程序addEventListener方法的瀏覽器
        if(dom.addEventListener)
            dom.addEventListener(type, fn, false)
        //對於不支持DOM2級時間處理程序addEventListener但支持attachEvent方法的瀏覽器
        else if(dom.attachEvent)
            dom.attachEvent('on' + type, fn)
        else
            dom['on' + type] = fn
    }
}

 

適配器模式

 將一個類(對象)的接口(方法或者屬性)轉化成另外一個接口,以滿足用戶需求,使類(對象)之間接口的不兼容問題通過適配器得以解決。

 例子:將自己開發的A框架與jQuery框架進行融合。(A框架與jQuery相似)

適配器的主要任務就是適配兩種代碼庫中不兼容的代碼。

window.A = A = jQuery

但是對於兩個差別較大的框架適配起來就要麻煩許多了。如B框架的內容如下:

let B =  {}
B.g = function(id){
    return document.getElementById(id)
}
B.on = function(id, type, fn){
    var dom = typeof id === 'string' ? this.g(id) : id
    if(dom.addEventListener)
        dom.addEventListener(type, fn, false)
    else if(dom.attachEvent)
        dom.attachEvent('on' + type, fn)
    else
        dom['on' + type] = fn
}

B.on(window, 'load', function(){
    B.on('mybutton', 'click', function(){
        //do someing
    })
})

現在把jQuery融入到B框架中

B.g = function(id){
    return $(id).get(0)
}
B.on = function(id, type, fn){
    var dom = typeof id === 'string' ? $('#' + id) : $(id)
    dom.on(type, fn)
}

除此之外,適配器還有很多用途,比如某個方法需要傳遞很多參數,例如:

function doSomeThing(name, title, age, color, size, prize){}

我們要記住這些參數的順序是很難的,因此我們通常以參數對象的形式進行傳遞

function doSomeThing(obj){}

即使是這樣,還是存在一個弊端,我們無法判斷參數的完整性,比如有些參數沒有傳入,有些參數存在默認值,此時我們通常的做法是用適配器來適配傳入的這個參數對象。

let fun = function (obj) {
    var _adapter = {
        name: '',
        title: '',
        age: '',
        sex: ''
    }
    for (const key in _adapter) {
        _adapter[key] = obj[key] || _adapter[key]
    }
    //或者extend(_adapter, obj)

    //do things
}

適配器還可以用來對數據適配,在插件開發中會經常用到,比如這裏有一個數組。

let data = ['js', 'book', '設計模式', '2013']

 這個數組中每個成員代表不同的意義,所以這種數據結構語義不友好,我們通常將其適配成對象的形式,如下面這種結構。

/*數組轉對象的適配*/
let arrToObj = function (arr) {
    return{
        name: arr[0],
        type: arr[1],
        title: arr[2],
        data: arr[3]
    }
}

再講一種情況,對於前後端交互中,後端返回的數據接口可能會經常改變,是不可控的,爲了減少後期的麻煩,我們可以對後端返回的數據進行適配,如我們希望得到的是一個指定順序的數組。

/*適配請求得到的數據*/
let ajaxAdapter = function(){
    //處理數據,按一定順序返回數組
    return [data['key1'], data['key2'], data['key2']]
}

$.ajax({
    url: '',
    success: (res) =>{
        ajaxAdapter(res)
    }
})

 

代理模式

 由於一個對象不能直接引用另外一個對象,所以需要通過代理對象在這兩個對象之間起到中介作用。

 跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器  讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。

對於跨域我們需要找一個代理來實現相互之間的通信。代理對象其實有很多,簡單一點的如img之類的標籤通過src屬性可以向其他御下的服務器發送請求。不過這類請求是單項的,不會有響應數據

/*統計代理*/
let Count = (function () {
    //緩存圖片
    var img  = new Image()
    //返回統計數據
    return function(param){
        //統計請求字符串
        var str = 'http://.***.com/a.gif?'
        //拼接請求字符串
        for (const i in param) {
            str += i + '=' + param[i]
        }
        //發送統計請求
        img.src = str
    }
})()
//測試用例,統計Num
Count({num:10})

第二種方式就是常常提到的JSONP,通過script標籤,我們可以在script標籤的src屬性末尾加上我們的請求數據。

function jsopCallback(res){
    console.log(res)
}
//JSOP:在src後加入我們所需要傳的數據信息,後臺處理後返回
$.ajax({
    url: 'http://localhost:3000/',
    type: 'get',
    success: (res) =>{
        console.log(res)
    }
})
<script type="text/JavaScript" src="http://localhost:3000/?callback=jsopCallback"> </script>

callback參數的值爲一個函數名,即返回後會調用該函數,在該函數中我們就可以得到所返回的數據。

 

裝飾者模式

在不改變原對象的基礎上,通過對其中包裝擴展(添加屬性或者方法)使原有對象可以滿足用戶的更復雜要求。

例子:用戶信息表單需求有些變化,以前是用戶點擊輸入框時,如果輸入框輸入內容有限制,那麼其後面顯示用戶輸入格式顯示提示內容,現在需要多加一條,默認輸入框上方有一文案,點擊輸入框時,文案消失。 

傳統做法是在原來的點擊事件處理函數中加入新增內容,如果這樣的需求不止一個,我們需要挨個的去尋找對應事件的地方,並且破壞了原有的內容。對於這種情況我們可以使用裝飾者模式,代碼如下:

/*裝飾者模式*/
let decorator = function(id, fn){
    //獲取事件源
    var dom = document.getElementById(id)
    //若事件源已經綁定事件
    if(typeof dom.onclick === 'function'){
        //緩存原有的事件源原有的回調函數
        var oldClickFn  = dom.onclick
        //爲事件源定義已新的事件
        dom.onclick = function () {
            //事件源原有回調函數
            oldClickFn()
            //執行新增的事件源函數
            fn()
        }
    }else{
        dom.onclick = fn()
    }
}


var name = document.getElementById('name')
var telwarn = document.getElementById('tel_warn_text')
var teldome = document.getElementById('tel_dome_text') 
name.onclick = function () {
    telwarn.style.display = 'none'
    // teldome.style.display = 'inline-block'   //新增
}
decorator('name', function(){
    teldome.style.display = 'inline-block'   //新增
})

可以看到,我們首先判斷了是否存在原有事件,若存在,則先緩存原來的處理內容,重新綁定事件,並把之前緩存的內容放到新事件中,這樣一來,我們在不破壞原基礎上加入了新的內容,這就是裝飾者模式。

 

 

下一期介紹結構型設計模式中剩下的3種:橋接模式、組合模式和享元模式

 

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