共计 3237 个字符,预计需要花费 9 分钟才能阅读完成。
我们知道,在 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 中的 $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>