上一章我们了解到通过webview evalJS的方法来跨页面通知事件,但是在其中还是有需要优化的地方,接下来我们慢慢的来分析。
上节回顾:
代码://页面通知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方法参数多加一个配置项
/** * 事件触发 * @param {String} eventName 事件名称 * @param {Object} data 传参参数值 * @param {Object} options 其它配置参数 */ emit(eventName, data, { self = false, // 是否通知自己,默认不通知 views = [], // 为空数组时,默认通知全部,为string数组时,认为是id,为object时,认为是webview对象 } = {}) { //code... }
然后我们针对传进来的拓展参数,进行逻辑判断,得到最终我们需要通知的webview list
/** * 事件触发 * @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 }));`) } }
如何调用
new Broadcast().emit('say',{ name: 'newsning', age: 26},{ self: true, // 通知当前页面 默认不通知 views: ['A.html','C.html'] // 默认通知所有页面,但不包括当前页面})// 如上代码就只通知到了3个页面, 当前页面, A页面, C页面
事件 - [ 订阅 | 发布 | 取消 ]
如果你遇到那种还需要移除监听事件,亦或者Once只监听一次的事件,再或是你看个代码不爽
ok!我们来撸一套简单的 守望先锋模式,哦不,是观察者模式
事件订阅
瞧瞧我们之前的代码,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方法
/** * 事件监听 * @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添加事件,而仅仅是放入所对应的事件列表中,奇怪了,看看我们之前的添加事件方法
给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 宁大大 */// 获取当前webviewconst 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封装的源码
class Man{ constructor(){ this.name = 'newsning' } say(){ console.log('天行健, 君子以自强不息. ') }}