zoukankan      html  css  js  c++  java
  • 利用JavaScript自定义事件完成组件间的数据通信

    我们知道,在JavaScript中,原生DOM事件在开发中是很有用的(与用户交互的重要方式),但是操作原生DOM事件其实有两大缺点:性能低、依赖于浏览器(NodeJs、小程序等不可用)。那么这个时候,就需要我们进行自定义事件去处理某些特定的业务。

    认识Event对象及元素的dispatchEvent方法

    在JavaScript中,所有事件的父对象就是Event对象,也就是说像我们平时所有的点击(click)、触摸(touch)、鼠标等事件对象都继承自Event。理解这一点是很重要的。先来简单看一个事件的场景。

    场景一、页面上有两个按钮a、b,当点击按钮b的时候,调用按钮a的点击事件。简单布局代码如下:

    <button id="btn1">a</button>
    <button id="btn2">b</button>
    

    程序员A的做法,分别获取这两个按钮,然后给b按钮添加点击事件后,调用按钮a的click方法。代码如下:

    <button id="btn1" onclick="alert('I am a button named a')">a</button>
    <button id="btn2">b</button>
    <script>
        let btn1 = document.querySelector('#btn1');
        let btn2 = document.querySelector('#btn2');
        btn2.onclick = function(){
          btn1.onclick();
        }
    </script>
    

    程序员B的做法,分别获取这两个按钮,然后给b按钮添加点击事件后,在回调函数中在添加按钮a的点击事件。代码如下:

    <button id="btn1">a</button>
    <button id="btn2">b</button>
    <script>
        let btn1 = document.querySelector('#btn1');
        let btn2 = document.querySelector('#btn2');
        btn2.onclick = function(){
          btn1.addEventListener('click',function(){
            alert('I am a button named a')
          },false)
        }
    </script>
    

    看到这里,你认为谁的做法是正确的?显然程序员A的做法是对的(就目前的要求来看),但有缺陷,如果按钮a的事件是通过addEventListener方法去注册监听的,就不起作用了。那么该怎样做才会比较好?这就需要我们的Event对象和元素的dispatchEvent方法了。改进代码如下:

    <button id="btn1">a</button>
    <button id="btn2">b</button>
    <script>
        let btn1 = document.querySelector('#btn1');
        let btn2 = document.querySelector('#btn2');
        btn1.addEventListener('click',function(){
            alert('I am a button named a')
          },false)
        btn2.onclick = function(){
          let ev = new Event('click');
          btn1.dispatchEvent(ev);
        }
    </script>
    

    其中:

    • Event对象的构造函数需要一个参数,事件类型
    • dispatchEvent方法是将某个事件分发给某个元素

    认识自定义事件

    前面说过,在浏览器端javascript中,所有的事件都继承自Event,那么其实要想实现一个自定义事件,也是需要继承自Event。

    还是类似上面说过的场景,场景二:有两个按钮a,b,当调用b按钮的点击事件,怎么去触发a按钮上的自定义事件?

    <button id="btn1">a</button>
    <button id="btn2">b</button>
    <script>
        let a = document.querySelector('#btn1');
        let b = document.querySelector('#btn2');
        a.addEventListener('myClick',function(){
            alert('I am a button named a')
        },false)
        class MyEvent extends Event{
            constructor(...args){
                super(...args);
                this.a = 12;
            }
        }
        b.onclick = function(){
            const ev = new MyEvent('myClick');
            a.dispatchEvent(ev);
        }
    </script>
    

    这就是自定义事件的思想体现

    • 根据事件类型进行事件的注册
    • 根据事件的类型分发对应的事件给需要者

    可以看出,对事件进行管理是很有必要,如Java中的EventBus、VueJs中的、emit等,将事件的监听者及分发者抽象成一个独立的模块,来进行事件的管理(添加、移除等)有利用解耦。

    事件队列完成组件间的通信

    这里可以把事件队列想象成一根管道,类似前端gulp实现的核心思想(基于管道)、当使用者需要使用某个事件的时候,就在管道中注册一个事件,然后通过该事件的类型,从管道中分发一个该类型的事件,在不需要使用后,还可以对相应的事件进行移除操作。代码如下:

    class Pipe{
        constructor(){
            this.pipes = {};
        }
        /**
         * 注册事件
         * @param {*} type 
         * @param {*} fn 
         */
        on(type,fn){
            this.pipes[type] = this.pipes[type] || [];
            if(this.pipes[type].findIndex(func => func==fn)==-1){
                this.pipes[type].push(fn);
            }
        }
        /**
         * 移除事件
         * @param {*} type 
         * @param {*} fn 
         */
        off(type,fn){
            if(this.pipes[type]){
                this.pipes[type] = this.pipes[type].filter((func) => func!==fn);
    
                if(this.pipes[type].length===0){
                    delete this.pipes[type];
                }
            }
        }
        /**
         * 分发事件
         * @param {*} type 
         * @param  {...any} args 
         */
        emit(type,...args){
            if(this.pipes[type]){
                this.pipes[type].forEach((fn) => {
                    fn(...args);
                })
            }
        }
    }
    

    场景:通过事件队列模拟vuejs中组件间通信的实现。Component1负责对数据进行渲染,Component2中点击按钮,来改变Component1实例属性a的值。代码如下:

     
    <div id="box">
            {{a}}
    </div>
    <button id="btn1">++</button>
    <script>
        const $ = document.querySelectorAll.bind(document);
        let pipe = new Pipe();
        class Component1{
            constructor(){
                this.a = 12;
                this.el = $("#box")[0];
                this.render();
                pipe.on('add',(arg) => {
                    this.a+=arg;
                    this.render();
                })
            }
    
            render(){
                this.el.innerHTML = this.a;
            }
        }
    
        class Component2{
            constructor(){
                this.el = $("#btn1")[0];
                this.el.onclick = function(){
                    pipe.emit('add',12);
                }
            }
        }
    
        new Component1();
        new Component2();
    </script>
    
    

    转发至:https://segmentfault.com/a/1190000020550517?utm_source=tag-newes
  • 相关阅读:
    C++ 学习笔记
    面向对象
    多线程
    Spring-扫描注解原理,注解自动扫描原理分析
    Eclipse 中报错的阅读顺序
    Eclipse 常用技巧及常见问题解决
    JAVA高级复习-自定义泛型类、泛型接口的注意点
    JAVA高级复习-泛型的使用
    IntelliJ IDEA学习笔记连载一IntelliJ IDEA中创建Maven工程
    JAVA高级复习-多线程的创建方式二
  • 原文地址:https://www.cnblogs.com/nianzhilian/p/12973221.html
Copyright © 2011-2022 走看看