上节回顾:【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('天行健, 君子以自强不息. ') } }