当前位置: 首页 > news >正文

【5+】跨webview多页面 触发事件(二)

上一章我们了解到通过webview evalJS的方法来跨页面通知事件,但是在其中还是有需要优化的地方,接下来我们慢慢的来分析。

上节回顾:【5+】跨webview多页面 触发事件(一)
代码:

//页面通知

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只监听一次的事件,再或是你看个代码不爽
clipboard.png

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添加事件,而仅仅是放入所对应的事件列表中,奇怪了,看看我们之前的添加事件方法

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

相关文章:

  • C#设计模式(6)-Abstract Factory Pattern
  • Linux网络状态工具ss命令使用详解【转】
  • 随笔--独立软件开发
  • 小技巧:处理ASP提交的参数是经过GB2312 URL编码的
  • Weex 和 React Native的比较
  • 640-802 新版CCNA考试题库下载
  • Kafka部署与代码实例(转)
  • 0-1岁宝宝的游戏和活动指南
  • Oracle性能优化之表压缩及并行提高效率的测试
  • Excel数组排序+图片统一大小
  • composer
  • 不求完美但求易用 报价软件适时出笼(温州传奇4)
  • 微信开源mars源码分析1—上层samples分析
  • 如何让普通域用户可以登录域控
  • jQuery实现AJAX定时局部页面刷新
  • C++入门教程(10):for 语句
  • CSS 提示工具(Tooltip)
  • JavaScript设计模式系列一:工厂模式
  • Kibana配置logstash,报表一体化
  • Python_网络编程
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • Ruby 2.x 源代码分析:扩展 概述
  • spring cloud gateway 源码解析(4)跨域问题处理
  • ubuntu 下nginx安装 并支持https协议
  • 彻底搞懂浏览器Event-loop
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 聚类分析——Kmeans
  • 前端存储 - localStorage
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 如何进阶一名有竞争力的程序员?
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 跳前端坑前,先看看这个!!
  • 微信小程序--------语音识别(前端自己也能玩)
  • 写代码的正确姿势
  • AI算硅基生命吗,为什么?
  • 阿里云移动端播放器高级功能介绍
  • ​LeetCode解法汇总518. 零钱兑换 II
  • #考研#计算机文化知识1(局域网及网络互联)
  • $.ajax中的eval及dataType
  • (C语言)fread与fwrite详解
  • (离散数学)逻辑连接词
  • (转)Oracle存储过程编写经验和优化措施
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .net core 6 redis操作类
  • .project文件
  • @Controller和@RestController的区别?
  • @EnableConfigurationProperties注解使用
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429
  • [Android]如何调试Native memory crash issue
  • [BZOJ1040][P2607][ZJOI2008]骑士[树形DP+基环树]
  • [codeforces]Checkpoints
  • [codevs] 1029 遍历问题
  • [Docker]三.Docker 部署nginx,以及映射端口,挂载数据卷
  • [IE编程] 多页面基于IE内核浏览器的代码示例
  • [ios] IOS文件操作的两种方式:NSFileManager操作和流操作【转】