【5+】跨webview多頁面 觸發事件(二)

上一章咱們瞭解到經過webview evalJS的方法來跨頁面通知事件,可是在其中仍是有須要優化的地方,接下來咱們慢慢的來分析。

上節回顧:【5+】跨webview多頁面 觸發事件(一)
代碼:html

//頁面通知

class Broadcast{
    /**
     * 構造器函數
     */
    constructor(){
        
    }
    
    /**
     * 事件監聽
     * @param {String} eventName 事件名稱
     * @param {Function} callback 事件觸發後執行的回調函數
     * @return {Broadcast} this
     */
    on(eventName, callback){
        document.addEventListener(eventName, e => {
            callback.call(e, e.detail)
        })
        return this
    }
    
    /**
     * 事件觸發
     * @param {String} eventName 事件名稱
     * @param {Object} data 參數
     * @return {Broadcast} this
     */
    emit(eventName, data){
        // 獲取全部的webview
        var all = plus.webview.all()
        // 遍歷所有頁面
        for(var w in all){
            // 挨個來evalJS
            all[w].evalJS(`document.dispatchEvent(new CustomEvent('${eventName}', {
                detail:JSON.parse('${JSON.stringify(data)}'),
                bubbles: true,
                cancelable: true
            }));`)
        }
        return this
    }
    
    
}

自定義須要通知頁面

能夠看到,以前咱們emit發送通知時,是對全部的webview進行獲取通知,可是有時候咱們並不想通知全部的頁面,並且通知別人的時候也不想通知本身啊,怎麼辦,在這裏咱們在emit方法參數多加一個配置項git

/**
     * 事件觸發
     * @param {String} eventName 事件名稱
     * @param {Object} data 傳參參數值
     * @param {Object} options 其它配置參數
     */
    emit(eventName, data, {
        self = false, // 是否通知本身,默認不通知
        views = [], // 爲空數組時,默認通知所有,爲string數組時,認爲是id,爲object時,認爲是webview對象
    } = {}) {
        //code...
    }

而後咱們針對傳進來的拓展參數,進行邏輯判斷,獲得最終咱們須要通知的webview listgithub

/**
     * 事件觸發
     * @param {String} eventName 事件名稱
     * @param {Object} data 傳參參數值
     * @param {Object} options 其它配置參數
     */
    emit(eventName, data, {
        self = false, // 是否通知本身,默認不通知
        views = [], // 爲空數組時,默認通知所有,爲string數組時,認爲是id,爲object時,認爲是webview對象
    } = {}) {
        let all = []
        // 獲取 特定 webview 數組
        if(views.length > 0) {
            // 若是是string 類型,則統一處理獲取爲 webview對象
            all.map(item => typeof item === 'string' ? plus.webview.getWebviewById(item) : item)
        } else {
            // 不特定通知的webview數組時,直接獲取所有已存在的webview
            all = plus.webview.all()
        }
        // 若是不須要通知到當前webview 則過濾
        if(!self) {
            let v =  plus.webview.currentWebview()
            all = all.filter(item => item.id !== v.id)
        }
        // 遍歷全部須要通知的頁面
        for(let v of all) {
            v.evalJS(`document.dispatchEvent(new CustomEvent('${eventName}', {
                detail:JSON.parse('${JSON.stringify(data)}'),
                bubbles: true,
                cancelable: true
            }));`)
        }
    }

如何調用web

new Broadcast().emit('say',{
    name: 'newsning',
    age: 26
},{
    self: true, // 通知當前頁面 默認不通知
    views: ['A.html','C.html'] // 默認通知全部頁面,但不包括當前頁面
})
// 如上代碼就只通知到了3個頁面, 當前頁面, A頁面, C頁面

事件 - [ 訂閱 | 發佈 | 取消 ]

若是你遇到那種還須要移除監聽事件,亦或者Once只監聽一次的事件,再或是你看個代碼不爽
clipboard.pngsegmentfault

ok!咱們來擼一套簡單的 守望先鋒模式,哦不,是觀察者模式api

事件訂閱

瞧瞧咱們以前的代碼,on方法是直接把傳進來的函數做爲調用,這樣子在外部調用時移除事件就沒路子了,包括Once也非常蛋疼數組

/**
     * 事件監聽
     * @param {String} eventName 事件名稱
     * @param {Function} callback 事件觸發後執行的回調函數
     * @return {Broadcast} this
     */
    on(eventName, callback){
        document.addEventListener(eventName, e => {
            callback.call(e, e.detail)
        })
        return this
    }

咱們先來定義好2個專門放置事件的存儲對象,碧如 :緩存

// 事件列表
    const events = {
        // 事件名稱 : 事件方法數組    
    },
    // 單次事件列表
    events_one = {

    }

以後咱們修改一下on方法,並新增一個once方法babel

/**
     * 事件監聽
     * @param {String} eventName 事件名稱
     * @param {Function} callback 事件觸發後執行的回調函數
     */
    on(eventName, callback) {
        // 獲取已存在的事件列表
        if(!events[eventName]) {
            events[eventName] = []
        }
        // 添加至數組
        events[eventName].push(callback)
    }

    /**
     * 事件監聽 (單次)
     * @param {String} eventName 事件名稱
     * @param {Function} callback 事件觸發後執行的回調函數
     */
    once(eventName, callback) {
        // 獲取已存在的單次事件列表
        if(!events_one[eventName]) {
            events_one[eventName] = []
        }
        // 添加至數組
        events_one[eventName].push(callback)
    }

醬紫,每次添加事件時,都會放入咱們的事件列表中,可是!咱們並無給任何dom添加事件,而僅僅是放入所對應的事件列表中,奇怪了,看看咱們以前的添加事件方法dom

clipboard.png

給document監聽一個事件

clipboard.png

觸發document事件

nonono , 咱們不這麼藉助document亦或者其它dom的事件監聽,還記得上一章的 evalJS('faqme()')麼?咱們就用親切的函數來觸發事件

事件發佈

在事件訂閱當中,咱們僅僅只是把事件放入了事件列表中,咱們該如何觸發?

編寫一個靜態方法,用來觸發當前頁面的事件, 而後經過

static _emitSelf(eventName, data) {
        if(typeof data === 'string') {
            data = JSON.parse(data)
        }
        // 獲取所有事件列表 和 單次事件列表,而且合併
        let es = [...(events[eventName] || []), ...(events_one[eventName] || [])]
        // 遍歷觸發
        for(let f of es) {
            f && f.call(f, data)
        }
        // 單次事件清空
        events_one[eventName] = []
    }

再配合修改一下 emit 裏面的 evalJS

/**
     * 事件觸發
     * @param {String} eventName 事件名稱
     * @param {Object} data 傳參參數值
     * @param {Object} options 其它配置參數
     */
    emit(eventName, data, {
        self = false, // 是否通知本身,默認不通知
        views = [], // 爲空數組時,默認通知所有,爲string數組時,認爲是id,爲object時,認爲是webview對象
    } = {}) {
        let all = []
        // 獲取 特定 webview 數組
        if(views.length > 0) {
            // 若是是string 類型,則統一處理獲取爲 webview對象
            all.map(item => typeof item === 'string' ? plus.webview.getWebviewById(item) : item)
        } else {
            // 不特定通知的webview數組時,直接獲取所有已存在的webview
            all = plus.webview.all()
        }
        // 若是不須要通知到當前webview 則過濾
        if(!self) {
            let v =  plus.webview.currentWebview()
            all = all.filter(item => item.id !== v.id)
        }
        // 遍歷全部須要通知的頁面
        for(let v of all) {
            /////////////////////////
            ////////////////這裏是重點, 調用Broadcast的靜態方法
            /////////////////////////
            v.evalJS(`Broadcast && Broadcast._emitSelf && Broadcast._emitSelf('${eventName}', '${JSON.stringify(data)}')`)
        }
    }

這樣子,就巧妙的觸發了每一個webview頁面 相對應的事件,而且單次事件也獲得了清除

事件移除

咱們知道前面的事件訂閱只是將事件存起來了,事件移除相應的就是把事件列表清空

static _offSelf(eventName) {
        //清空事件列表
        events[eventName] = []
        events_one[eventName] = []
    }

最後收尾

所定義的2個靜態方法,觸發 和 移除 事件,咱們在內部代理2個相應的方法

/**
     * 當前頁面事件觸發 
     * @param {String} eventName 事件名稱
     * @param {Object} data 傳參參數值
     */
    emitSelf(eventName) {
        Broadcast._emitSelf(eventName, data)
    }
    
    /**
     * 清空當前頁面事件 
     * @param {String} eventName 事件名稱
     */
    offSelf(eventName) {
        Broadcast._offSelf(eventName)
    }

最後,成果已經出現

A.html

var b = new Broadcast()
            
            b.on('say', function(data){
                alert(JSON.stringify(data))
                
                // 刪除本頁面say事件
                //b.offSelf('say')
            })
            
            b.once('say', function(data){
                //單次
                alert('單次:'+JSON.stringify(data))
            })

B.html

new Broadcast().emit('say', {
                from: '我是B啊',
                id: 666
            })

最後附上源碼:

/**
 * 5+ Broadcast.js by NewsNing 寧大大 
 */

// 獲取當前webview
const getIndexView = (() => {
        // 緩存
        let indexView = null
        return(update = false) => {
            if(update || indexView === null) {
                indexView = plus.webview.currentWebview()
            }
            return indexView
        }
    })(),
    // 獲取所有webview 
    getAllWebview = (() => {
        // 緩存
        let allView = null
        return(update = false) => {
            if(update || allView === null) {
                allView = plus.webview.all()
            }
            return allView
        }
    })()

// 事件列表
const events = {

    },
    // 單次事件列表
    events_one = {

    }

//頁面通知類
class Broadcast {
    /**
     * 構造器函數
     */
    constructor() {

    }

    /**
     * 事件監聽
     * @param {String} eventName 事件名稱
     * @param {Function} callback 事件觸發後執行的回調函數
     */
    on(eventName, callback) {
        // 獲取已存在的事件列表
        if(!events[eventName]) {
            events[eventName] = []
        }
        // 添加至數組
        events[eventName].push(callback)
    }

    /**
     * 事件監聽 (單次)
     * @param {String} eventName 事件名稱
     * @param {Function} callback 事件觸發後執行的回調函數
     */
    once(eventName, callback) {
        // 獲取已存在的單次事件列表
        if(!events_one[eventName]) {
            events_one[eventName] = []
        }
        // 添加至數組
        events_one[eventName].push(callback)
    }

    /**
     * 事件觸發
     * @param {String} eventName 事件名稱
     * @param {Object} data 傳參參數值
     * @param {Object} options 其它配置參數
     */
    emit(eventName, data, {
        self = false, // 是否通知本身,默認不通知
        views = [], // 爲空數組時,默認通知所有,爲string數組時,認爲是id,爲object時,認爲是webview對象
    } = {}) {
        let jsstr = `Broadcast && Broadcast._emitSelf && Broadcast._emitSelf('${eventName}', '${JSON.stringify(data)}')`
        this._sendMessage(jsstr, self, views)
    }

    /**
     * 當前頁面事件觸發 
     * @param {String} eventName 事件名稱
     * @param {Object} data 傳參參數值
     */
    emitSelf(eventName) {
        Broadcast._emitSelf(eventName, data)
    }

    /**
     * 事件關閉移除
     * @param {String} eventName 事件名稱
     * @param {Object} options 其它配置參數
     */
    off(eventName, {
        self = false, // 是否通知本身,默認不通知
        views = [] // 爲空數組時,默認通知所有,爲string數組時,認爲是id,爲object時,認爲是webview對象
    } = {}) {
        let jsstr = `Broadcast && Broadcast._offSelf && Broadcast._offSelf('${eventName}')`
        this._sendMessage(jsstr, self, views)
    }

    /**
     * 清空當前頁面事件  
     * @param {String} eventName 事件名稱
     */
    offSelf(eventName) {
        Broadcast._offSelf(eventName)
    }

    /**
     * 頁面通知
     * @param {String} jsstr 須要運行的js代碼
     * @param {Boolean} self 是否通知本身,默認不通知
     * @param {Array} views 爲空數組時,默認通知所有,爲string數組時,認爲是id,爲object時,認爲是webview對象
     */
    _sendMessage(
        jsstr = '',
        self = false,
        views = []
    ) {
        let all = []
        // 獲取 特定 webview 數組
        if(views.length > 0) {
            // 若是是string 類型,則統一處理獲取爲 webview對象
            all.map(item => typeof item === 'string' ? plus.webview.getWebviewById(item) : item)
        } else {
            // 不特定通知的webview數組時,直接獲取所有已存在的webview
            all = getAllWebview(true)
        }
        // 若是不須要通知到當前webview 則過濾
        if(!self) {
            let v = getIndexView()
            all = all.filter(item => item.id !== v.id)
        }
        // 遍歷所有頁面
        for(let v of all) {
            v.evalJS(jsstr)
        }
    }

    static _emitSelf(eventName, data) {
        if(typeof data === 'string') {
            data = JSON.parse(data)
        }
        // 獲取所有事件列表 和 單次事件列表,而且合併
        let es = [...(events[eventName] || []), ...(events_one[eventName] || [])]
        // 遍歷觸發
        for(let f of es) {
            f && f.call(f, data)
        }
        // 單次事件清空
        events_one[eventName] = []
    }

    static _offSelf(eventName) {
        //清空事件列表
        events[eventName] = []
        events_one[eventName] = []
    }

}

您也能夠經過babel在線轉化成es5 在線轉換地址

clipboard.png

最後您還能夠在github上看到一些其它5+ Api封裝的源碼 5+ api整合

class Man{
    constructor(){
        this.name = 'newsning'
    }
    say(){
        console.log('天行健, 君子以自強不息. ')
    }
}
相關文章
相關標籤/搜索