咱们知道,在JavaScript中,原生DOM事件在开发中是颇有用的(与用户交互的重要方式),可是操做原生DOM事件其实有两大缺点:性能低、依赖于浏览器(NodeJs、小程序等不可用)。那么这个时候,就须要咱们进行自定义事件去处理某些特定的业务。
在JavaScript中,全部事件的父对象就是Event对象,也就是说像咱们平时全部的点击(click)、触摸(touch)、鼠标等事件对象都继承自Event。理解这一点是很重要的。先来简单看一个事件的场景。javascript
场景1、页面上有两个按钮a、b,当点击按钮b的时候,调用按钮a的点击事件。简单布局代码以下:前端
<button id="btn1">a</button> <button id="btn2">b</button>
程序员A的作法,分别获取这两个按钮,而后给b按钮添加点击事件后,调用按钮a的click方法。代码以下:vue
<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的点击事件。代码以下:java
<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>
其中:gulp
前面说过,在浏览器端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中的$on、$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>