上節回顧:【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只監聽一次的事件,再或是你看個代碼不爽segmentfault
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
給document監聽一個事件
觸發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 在線轉換地址
最後您還能夠在github上看到一些其它5+ Api封裝的源碼 5+ api整合
class Man{ constructor(){ this.name = 'newsning' } say(){ console.log('天行健, 君子以自強不息. ') } }