事件和事件处理
原文地址:https://dev.to/bhagatparwinder/events-event-handling-f28
事件
你对动作(系统的或用户产生的)的响应就是调用一个事件,对事件的回应就是调用一个事件处理程序。
例如,当用户点击一个按钮后,我们可能会显示一个带信息的弹框,在这个例子中,事件是 click 处理结果就是展示一个弹框。
网页上会发生很多事件:
- 用户 hover 一个元素上
- 表单的提交
- 视频停止播放
- 用户从一个图片上滚动过去
- 改变浏览器的大小
- 按键
- 文档加载结束
事件处理程序
我上面已经简单提到过,事件处理程序就是我们如何响应事件的方法。它是事件发生时执行的一块代码。
我们经常会把 event listeners 和 evetn handlers 交替使用,同样你也可以像这样随意使用。
可是,它俩有点小区别,listeners 是监听一个事件的发生而 handler 是执行的具体代码。
案例
假设我们的页面有一个按钮。
<button class="btn-primary">Click Me!</button>
我们为按钮绑定了一个事件,当点击它时打印一条消息。
const myButton = document.querySelector(".btn-primary");
myButton.addEventListener("click", function() {
console.log("The button was clicked");
});
发生了什么:
- 我们使用 querySelector获取到浏览器 DOM 中的按钮;
- 接着我们使用 addEventListener 添加了事件侦听器;
- addEventListner 接受了两个参数(实际可以接受三个参数);
- 第一个参数是事件类型,这个例子中的事件类型是 click;
- 第二个参数就是一旦点击时执行的回调函数
浏览器知道用户什么时候点击了按钮,同时为有类名 btn-primary 的按钮注册了一个事件,然后执行相关的事件处理程序,将会打印:
The button was clicked
回调方法是一个匿名函数,它不能被其它地方引用。我们不经常使用匿名函数,可以创建一个命名函数然后传递给它。命名函数是可重用性的首选,它使我们能够在以后删除事件侦听器。
使用命名函数
事件处理器可以是一个命名函数。
const myButton = document.querySelector(".btn-primary");
const handleClick = function() {
console.log("The button was clicked");
};
myButton.addEventListener("click", handleClick);
这样并不仅仅是代码更简洁,它还有两个优点:
- 重用性:设想你有很多按钮需要打印相同的语句,一个命名函数可以被使用多次而不要写重复的代码。
- 移出事件侦听器:使用
removeEventListener
来移出事件处理程序,为了移出它需要传递两个关键参数。第一个是实际类型,第二个是事件处理程序。若事件处理程序是一个匿名函数我们无法指定第二个参数。在这个例子中是命名函数,我们可以这样做:
myButton.removeEventListener("click", handleClick);
事件冒泡
原文地址:https://dev.to/bhagatparwinder/event-bubbling-pb3
简介
上面我们谈了事件和事件处理程序,以及为事件添加事件处理程序。当事件发生时事件处理程序将会被调用。
JavaScript 中的事件冒泡是指当元素上发生一个事件时,关联的事件处理程序会被调用,紧接着是父级元素和更上层元素的事件处理程序也会被调用。
例子:
<div onclick="alert('Click Event Happened')">
<p>If you have click this paragraph in the browser, the onclick handler of the div will get invoked.</p>
</div>
上面的例子是:点击 p 标签内的文本时,会触发 div 上的 onclick
事件。这就是 p 上发生的事件冒泡到了 div 上。
即使有 n 多层嵌套的元素上面的模式依旧也会发生。
例子:
<span onclick="alert('Span Clicked')">
<div onclick="alert('Div Clicked')">
<p onclick="alert('Paragraph Clicked')">Click Me.</p>
</div>
</span>
若我们点击了 p 标签,浏览器会触发三次弹框。
找到事件的源头元素
当事件冒泡经过多层时,很难追踪到是哪个元素产生了这一串的事件。可是 JavaScript 中很容易做到。
像上面的例子,若我们点击了 p 标签,target
或 event.target
将会指向它,无论事件冒泡了多少层,而 event.target
永远不会改变,指向事件产生的源头。
如何阻止事件冒泡?
冒泡的事件将一直传递到 <html>
元素,有些还会到 document
,其中一些进入window
对象。
我们如果不想父级元素的事件发生,可以使用 event.stopPropagation()
。
例子:
<div onclick="alert('This will not alert')">
<button onclick="event.stopPropagation()">Click me</button>
</div>
若我们点击了带有 stopPropagation()
的按钮,div 的事件处理程序或 alert 不会触发。
如何阻止同一个元素上的多个事件?
有时候我们会为同一个元素绑定同一个事件绑定多个事件处理程序,有时候期望阻止冒泡也想后面注册的同类型事件也被阻止,event.stopImmediatePropagation()
就可以做到。
<div onclick="alert(1)">
<div onclick="alert(2)">
<div id="target">ppp</div>
</div>
</div>
let target = document.querySelector('#target')
function testClick1(event){
alert(33)
}
function testClick2(event){
alert(44)
event.stopImmediatePropagation()
//event.stopPropagation()
}
function testClick3(){
alert(55)
}
function moveHandler(){
alert('move')
}
target.addEventListener('click',testClick1)
target.addEventListener('click',testClick2)
target.addEventListener('click',testClick3)
上面会依次弹出:33,44,若把上面的注释换一下则依次弹出:33,44,55。
在线案例:https://jsbin.com/xilorahomi/edit?html,js,output
参考文档:http://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation
为何阻止事件冒泡可能会是错误的?
如果用户点击的元素事件处理程序带有stopPropagation()
, document 上的点击事件处理程序(为了跟踪、分析或调用弹框)也不会被触发。所以谨慎的阻止事件冒泡。这个仅仅是一个例子,还有许多其它的副作用。
事件冒泡的例外情况
并不是所有的事件都会冒泡,任何与特定元素绑定事件不会冒泡,如下一些事件:
- load
- unload
- focus
- blur
事件捕获
原文地址:https://dev.to/bhagatparwinder/event-capturing-40o
事件捕获刚好和事件冒泡相反,事件冒泡中事件是从最内层元素逐渐向外扩散,而事件捕获则是从最外面元素向内直到目标元素。
事件捕获很少用到,开启事件捕获可以给 addEvenListener
传递第三个参数。
例子:
const myButton = document.querySelector(".btn-primary");
myButton.addEventListener("click", function() {
console.log("The button was clicked");
}, { capture : true });
第三个参数设置为 true 来开启捕获,现在当一个事件发生时,它会从顶部一直向内流到目标元素,之后事件再冒泡。
<div id="p">
<div id="m">
<div id="c">Bubble and Capture</div>
</div>
</div>
上面的结构只在最内层的事件开启捕获也是不行的:
let p = document.querySelector('#p')
let m = document.querySelector('#m')
let c = document.querySelector('#c')
p.addEventListener('click',function(e){
alert(e.eventPhase)
alert('p')
})
m.addEventListener('click',function(e){
alert(e.eventPhase)
alert('m')
})
c.addEventListener('click',function(e){
alert(e.eventPhase)
alert('c')
},true)
结果一次弹出:2,c,3,m,3,p
其实还是冒泡的顺序,只有都加上或外面两层的事件加上才会有捕获的效果:
let p = document.querySelector('#p')
let m = document.querySelector('#m')
let c = document.querySelector('#c')
p.addEventListener('click',function(e){
alert(e.eventPhase)
alert('p')
},true)
m.addEventListener('click',function(e){
alert(e.eventPhase)
alert('m')
},true)
c.addEventListener('click',function(e){
alert(e.eventPhase)
alert('c')
})
弹框结果:1,p,1,m,2,c
,若只给中间的开启捕获呢?
let p = document.querySelector('#p')
let m = document.querySelector('#m')
let c = document.querySelector('#c')
p.addEventListener('click',function(e){
alert(e.eventPhase)
alert('p')
})
m.addEventListener('click',function(e){
alert(e.eventPhase)
alert('m')
},true)
c.addEventListener('click',function(e){
alert(e.eventPhase)
alert('c')
})
结果:1,m,2,c,3,p
。
实例:https://jsbin.com/wurabalaje/edit?html,js,output,在案例里来回切换一下true 再对照上图理解。
第三个参数并不一定要是一个对象,是一个 boolean 值 true
也行。
myButton.addEventListener("click", function() {
console.log("The button was clicked");
}, true);
总结,DOM 事件有三个阶段:
- 捕获
- 目标元素
- 冒泡
通过 event.eventPhase
可以确定我们所处的阶段或在哪个事件处理程序中。
注意:若addEventListener
中为了捕获使用了 true
,那removeEventListener
时也要使用相同的值。