所谓自定义事件,就是有别于浏览器的特定行为事件(如click, mousemove, keyup
等)
自定义事件可以让我们自主命名事件名,并且可以通过特定的方法进行添加,触发以及删除
为了和浏览器内置事件进行区分,浏览器自定义事件又被称之为合成事件
在实际开发中,我们通常使用自定义事件来在组件和组件之间进行数据通信
我们可以使用Event
或 CustomEvent
构造函数来创建我们自己的自定义事件
区别在于 Event
是所有JS内置事件的顶层父元素,所以一般使用Event
来以脚本的方式触发一些浏览器内置事件
而CustomEvent
是专门用于创建一些用户自定义事件
基本使用
const btnEl = document.getElementById('btn')
btnEl.addEventListener('click', e => console.log('按钮被点击了'))
// 创建对应的事件对象
// 参数一: type - 事件名称 -- 自定义名称?
// 参数二: 配置对象
// 1. bubbles 布尔类型值 默认值为false
// - 当值为true时表示该事件可以冒泡
// - 当值为false时表示该事件不会冒泡
//
// 2. cancelable 布尔类型值 默认值为false
// - 当值为true时,如果事件内部调用event.preventDefault方法
// - 事件对应的dispatchEvent方法会返回false, 可以使用这点来模拟取消事件的默认行为
// - 当值为false时,会忽略内部调用的event.preventDefault方法 ?
const event = new Event('click')
// 使用脚本方式触发事件
// elem.dispatchEvent(事件对象)
btnEl.dispatchEvent(event)
const btnEl = document.getElementById('btn')
btnEl.addEventListener('customEvent', e => console.log('按钮被点击了'))
// 虽然使用event可以绑定和触发自定义事件
// 但是从语义化角度,还是推荐使用CustomEvent来绑定和触发自定义事件
const event = new Event('customEvent')
btnEl.dispatchEvent(event)
配置对象
const btnEl = document.getElementById('btn')
const dvEl = document.getElementById('dv')
btnEl.addEventListener('customEvent', e => console.log('按钮被点击了'))
dvEl.addEventListener('customEvent', e => console.log('按钮点击事件发生了冒泡'))
const event = new Event('customEvent', {
bubbles: true // 让自定义事件和内置事件一样,拥有相同的冒泡和捕获机制
})
btnEl.dispatchEvent(event)
/*
=>
按钮被点击了
按钮点击事件发生了冒泡
*/
const btnEl = document.getElementById('btn')
const dvEl = document.getElementById('dv')
// 在JS中, 内置事件是可能拥有自己的默认行为的
// 但是用户自己创建的自定义事件是不可能存在对应的默认行为的
// 但是可以使用cancelable配置项来控制对应的自定义事件能否被取消
const event = new Event('customEvent', {
cancelable: true // 允许事件被取消
})
btnEl.addEventListener('click', checkHide)
dvEl.addEventListener('customEvent', e => {
if (confirm('是否取消事件')) {
// 如果event开启了cancelable且调用了e.preventDefault()
// 那么对应的dispatchEvent方法会返回false
e.preventDefault()
}
})
function checkHide() {
// customEvent事件可能调用e.preventDefault()
// 所以该方法的返回值可能为false也可能为true
if (dvEl.dispatchEvent(event)) {
dvEl.hidden = true
} else {
console.log('自定义事件被取消了')
}
}
isTrusted
浏览器在触发事件的时候,会传入对应的事件对象,在事件对象中有一个boolean类型的属性isTrusted
当这个属性为true时,表示该事件是内置事件,反之就说明该事件是用户自定义事件
const btnEl = document.getElementById('btn')
btnEl.addEventListener('customEvent', e => console.log(e.isTrusted)) // => false
btnEl.addEventListener('click', e => console.log(e.isTrusted)) // => true
const event = new Event('customEvent')
btnEl.dispatchEvent(event)
具体事件类型
Event是所有内置事件的父类,我们在使用脚本触发对应内置事件的时候,可以使用具体的事件类来创建事件对象
例如: UIEvent MouseEvent PointEvent KeyboardEvent
等
当我们使用正确的构造器时候,就允许为该类型的事件指定标准属性
const mouseEvent = new MouseEvent("click", {
bubbles: true,
cancelable: true,
// 因为该事件是MouseEvent类型,所以可以指定对应事件的标准属性
clientX: 100,
clientY: 100
})
const event = new Event("click", {
bubbles: true,
cancelable: true,
// 如果指定了非对应事件类型的属性,对应属性会静默失效
clientX: 100,
clientY: 100
})
console.log(event.clientX) // => undefined
console.log(mouseEvent.clientX) // => 100
CustomEvent
对于我们自己定义的事件,应该使用CustomEvent构造器
CustomEvent构造器的使用和Event构造器的使用方式基本一致
唯一的区别是使用CustomEvent的时候,我们可以detail属性传递自定义属性
const btnEl = document.getElementById('btn')
const event = new CustomEvent('customEvent', {
// CustomEvent除了有cancelable属性和bubbles属性外
// 还存在一个属性detail 用于传递自定义数据
// detail 属性可以是任何类型数据, 推荐使用对象类型,以便于传递多个自定义数据
detail: {
foo: '这是自定义数据'
}
})
// 在按钮被点击的时候,同时触发按钮上的自定义事件
btnEl.addEventListener('click', () => btnEl.dispatchEvent(event))
// 通过detail传递的自定义数据,可以在事件对象的detail属性中获取
btnEl.addEventListener('customEvent', e => console.log(e.detail)) // => {foo: '这是自定义数据'}
事件中的事件是同步的
通常事件是在队列中处理的。也就是说:如果浏览器正在处理 onclick
,这时发生了一个新的事件,例如鼠标移动了,那么它的处理程序会被排入队列,相应的 mousemove
处理程序将在 onclick
事件处理完成后被调用。
但是当一个事件是在另一个事件中发起的。例如使用 dispatchEvent
。这类事件将会被立即处理,即在新的事件处理程序被调用之后,恢复到当前的事件处理程序
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// 输出顺序为:1 → nested → 2
document.addEventListener('menu-open', () => alert('nested'));
</script>
如果我们希望事件中的事件以异步的方式进行执行,可以将事件中的事件包装到零延迟的 setTimeout
中。
即让事件中的事件转变为宏任务后单独加入事件队列中异步执行
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
// 输出顺序为:1 → 2 → nested
document.addEventListener('menu-open', () => alert('nested'));
</script>