事件event
浏览器事件
先捕获,再冒泡。想像成东西扔进水里再浮起来的过程。
特殊情况:如果给一个 body 中的子节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。
官方eventListener api学习(注意几个参数):
https://zhuanlan.zhihu.com/p/24555031
https://juejin.im/post/5cc941436fb9a03236394027#heading-3
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/removeEventListener
stopPropagation / stopImmediatePropagation
e.target
指向触发事件监听的对象。e.currentTarget
指向添加监听事件的对象。
事件代理
事件代理的方式相较于直接给目标注册事件来说,有以下优点:
- 节省内存
- 不需要给子节点注销事件
React中的事件
事件代理。
【必读】React 事件代理与 stopImmediatePropagation: https://github.com/youngwind/blog/issues/107
并非 #child 和 #parent 的事件分别代理到 document 上,而是 React 在 document 上绑定了一个 dispatchEvent 函数(事件),在执行 dispatchEvent 的过程中,其内部会依次执行 #child 和 #parent 上绑定的事件。
通过 React 绑定的事件,其回调函数中的 event 对象,是经过 React 合成的 SyntheticEvent,与原生的 DOM 事件的 event 不是一回事。准确地说,在 React 中,e.nativeEvent 才是原生 DOM 事件的那个 event,虽然 React 的合成事件对象也同样实现了 stopPropagation 接口。(注意:React合成事件里有stopPropagation,但没有stopImmediatePropagation。)
Q:点击 #child ,只触发 #child 上的事件,不要触发任何其他元素的事件,包括绑在 document上的原生事件“,我应该怎么做呢?
A:”调用e.nativeEvent.stopImmediatePropagation
“。React 合成事件对象的e.stopPropagation
,只能阻止 React 模拟的事件冒泡,并不能阻止真实的 DOM 事件冒泡,更加不能阻止已经触发元素的多个事件的依次执行。在这种情况下,只有原生事件对象的 stopImmediatePropagation
能做到。
第三方库
1. wolfy87-eventemitter
介绍:Evented JavaScript for the browser(在浏览器里使用类似node.js的事件机制)
https://github.com/Olical/EventEmitter npm package: wolfy87-eventemitter API: https://github.com/Olical/EventEmitter/blob/master/docs/guide.md
实践经验:特别注意的地方是,必须先on监听,再emit发消息才会收到消息。
关于使用第三方库vs 原生DOM event的疑问(提问很详细)
https://github.com/Olical/EventEmitter/issues/74
作者回答:第三方库no-DOM更快。
eastbaby个人补充观点:相比DOM event,这个可以跨页面。
node.js 官方event
const events = require('events');
const emitter = new events.EventEmitter();
// 绑定sayHi事件,可以绑定多个同名事件,触发时会顺序触发
emitter.on('sayHi', function(someone){
console.log("我是", someone)
})
emitter.on('sayHi', function(someone){
console.log("我就是", someone)
})
// 触发sayHi事件
emitter.emit('sayHi', 'pfan');
// 我是pfan
// 我就是pfan
Node.js中大部分的模块,都继承自Event模块,来异步操作处理如request、stream。
模拟实现
function Emitter(){
this.events = {}
}
Emitter.prototype.on = function(type, listener){
// 在事件对象中加入新的属性
// 确保新的属性以数组的形式保存
this.events[type] = this.events[type] || [];
// 在数组中加入事件处理函数
this.events[type].push(listener);
}
Emitter.prototype.off = function(type, listener){
if(this.events && this.events[type]){
var i = this.events[type].length;
while (i--) {
if (this.events[type][i] === listener) {
list.splice(i, 1);
}
}
}
}
Emitter.prototype.once = function(type, listener){
let self = this
this.on(type, function(){
self.off(type)
listener(...arguments)
}) // 给他监听上,执行emit的时候才会执行listner
}
Emitter.prototype.emit = function(type, arg){
if(this.events[type]) {// 如果事件对象中含有该属性
this.events[type].forEach(function(listener){
listener(arg)
})
}
}
module.exports = Emitter;
参考:https://github.com/pfan123/Articles/issues/16
MDN 自定义事件
属于DOM事件。这些事件通常称为合成事件,而不是浏览器本身触发的事件。
CustomEvent
dispatchEvent
可以挂在document上用。
<form>
<textarea></textarea>
</form>
----
// Create a new event, allow bubbling, and provide any data you want to pass to the "details" property
const eventAwesome = new CustomEvent('awesome', {
bubbles: true,
detail: { text: () => textarea.value }
});
// The form element listens for the custom "awesome" event and then consoles the output of the passed text() method
form.addEventListener('awesome', e => console.log(e.detail.text()));
// As the user types, the textarea inside the form dispatches/triggers the event to fire, and uses itself as the starting point
textarea.addEventListener('input', e => e.target.dispatchEvent(eventAwesome));
参考:https://developer.mozilla.org/zh-CN/docs/Web/Guide/Events/Creating_and_triggering_events