JavaScript教程 / 全部 / 前端 / 技术 · 2022年5月7日 0

22 – 23 – 24 事件相关

事件和事件处理

原文地址:https://dev.to/bhagatparwinder/events-event-handling-f28

事件

你对动作(系统的或用户产生的)的响应就是调用一个事件,对事件的回应就是调用一个事件处理程序。

例如,当用户点击一个按钮后,我们可能会显示一个带信息的弹框,在这个例子中,事件是 click 处理结果就是展示一个弹框。

网页上会发生很多事件:

  1. 用户 hover 一个元素上
  2. 表单的提交
  3. 视频停止播放
  4. 用户从一个图片上滚动过去
  5. 改变浏览器的大小
  6. 按键
  7. 文档加载结束

事件处理程序

我上面已经简单提到过,事件处理程序就是我们如何响应事件的方法。它是事件发生时执行的一块代码。

我们经常会把 event listenersevetn 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");
});

发生了什么:

  1. 我们使用 querySelector获取到浏览器 DOM 中的按钮;
  2. 接着我们使用 addEventListener 添加了事件侦听器;
  3. addEventListner 接受了两个参数(实际可以接受三个参数);
  4. 第一个参数是事件类型,这个例子中的事件类型是 click;
  5. 第二个参数就是一旦点击时执行的回调函数

浏览器知道用户什么时候点击了按钮,同时为有类名 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);

这样并不仅仅是代码更简洁,它还有两个优点:

  1. 重用性:设想你有很多按钮需要打印相同的语句,一个命名函数可以被使用多次而不要写重复的代码。
  2. 移出事件侦听器:使用 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 标签,targetevent.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

16520216389621

实例:https://jsbin.com/wurabalaje/edit?html,js,output,在案例里来回切换一下true 再对照上图理解。

第三个参数并不一定要是一个对象,是一个 boolean 值 true 也行。

myButton.addEventListener("click", function() {
    console.log("The button was clicked");
}, true);

总结,DOM 事件有三个阶段:

  1. 捕获
  2. 目标元素
  3. 冒泡

通过 event.eventPhase 可以确定我们所处的阶段或在哪个事件处理程序中。

注意:若addEventListener中为了捕获使用了 true,那removeEventListener时也要使用相同的值。