【JS】DOM事件

1,100 阅读9分钟
  • 给一个孩子元素绑定了click事件,也给爸爸元素绑定了click事件,你点击了这个孩子元素,你怎么知道你到底要点击孩子还是爸爸(因为孩子是爸爸的一部分,点击了孩子不就是点击了爸爸)
  • 所以,点击了孩子就把爸爸、爷爷、曾爷爷的点击事件一起触发了吧
  • 同理,孩子也有孩子,也就是孙子。你想点击了孩子,但是要是一不小心点到孙子身上,孙子因为是孩子的一部分,也算点到了孩子,就会触发孩子的事件监听函数。所以你给孩子绑定了函数,但是触发函数的可能是孙子。
  • 所以,触发(target)监听函数的不一定是绑定元素,还可能是绑定元素的儿子

事件

定义

  1. 事件是在编程时系统内发生的动作或者发生的事情
  2. 事件是要绑定在元素上的。比如给一个div元素绑定一个鼠标悬浮事件,给一个ol元素绑定鼠标单击事件。
  3. 可以使用事件监听函数(也叫事件处理程序、侦听器)来监听事件,以便事件发生时执行相应的代码

事件类型举例(全部事件类型请看MDN文档)

  • 单击click
  • 双击dblclick
  • 鼠标放置mouseenter
  • 表单内容发生变化
  • 拖拽
  • 页面滚动
  • 触发/失去焦点
  • 键盘按下
  • 提交表单

DOM事件流

你想想,如果我按了键盘上的空格,是不是也相当于按了整个键盘?我按了空格的右下角的英文,是不是相当于按了空格。

定义

事件流描述的是 从页面接收事件 的顺序。

事件发生时元素节点之间按照特定的顺序传播,这个过程即DOM事件流。

分为三个阶段:1、事件捕获阶段;2、当前目标阶段;3、事件冒泡阶段

事件捕获阶段(window→触发元素)

首先开始事件捕获阶段,从DOM树最根部的节点window开始,沿着DOM树向下遍历每个元素,直到触发元素目标元素target。如果这些元素也注册了click事件(且为捕获阶段),就会执行他们相应的事件监听函数

事件冒泡阶段(触发元素→window)

最后是事件冒泡阶段,从触发元素目标元素target开始,向上逆着遍历DOM树,直到最根部window元素。如果这些元素也注册了click事件(且为冒泡阶段),就会执行他们相应的事件监听函数

例子

我们为tr元素绑定了一个单击click事件。当我们单击tr

若单击的是纯tr元素,即触发元素是纯tr元素

  • 绑定元素和触发元素相同
  • 事件捕获阶段:window → document → html → body → table → tbody → tr
  • 事件冒泡阶段:tr → tbody → table → body → html → document → window
  • 路径(触发元素到window,也就是冒泡阶段):tr → tbody → table → body → html → document → window

若单击的是tr元素里的td元素(也是tr元素!!!!),即触发元素是孩子

  • 绑定元素是触发元素的爸爸
  • 事件捕获阶段:window → document → html → body → table → tbody → tr → td
  • 事件冒泡阶段:td → tr → tbody → table → body → html → document → window
  • 路径(触发元素到window,也就是冒泡阶段):tr → tbody → table → body → html → document → window

如何给元素绑定事件

方法一:在html的那个元素里绑定(不要用!)

<div onclick = console.log('hi')>点我</div>

方法二:给DOM元素的onclick属性赋值事件监听函数

$div.onclick = function(e){
    console.log('你刚才单击了这个div元素')
}

缺点:一个元素的一种事件(如click)只能有一个事件监听函数,因为你是给这个元素的这个属性(onclick)赋值啊!下一个值会覆盖上一个值的呀。就像我如果要再给$div绑定一个click事件,再写了一个事件监听函数。这个事件监听函数还是会成为属性onclick的新值的呀

方法三:调用DOM元素的addEventListener函数

优点:可以添加多个事件处理函数。

$div.adddEventListener(type,listener,useCapture)

  • type : 事件类型
  • listener : 事件监听函数
  • useCapture:可选,true表示在捕获阶段调用listener,false(默认)表示在冒泡阶段调用listener。

$div.adddEventListener(type,listener,options)

  • options:
  1. 可选,是个对象。
  2. {capture: 是否捕获阶段监听,once: 是否只监听一次,passive:是否忽略preventDefault }
  3. 默认全是false

移除事件处理程序target.removeEventListener(type,handler,)

通过addEventListener()添加的事件处理程序只能使用removeEventListener() 来移除;移除时传入的参数和添加程序处理程序时使用的参数相同。这也意味通过 addEventListener() 添加的匿名函数(没有传入参数)将无法移除 target.removeEventL .istener('click', handler)

target.removeEventL .istener('click', handler, true)

例子

例子

事件对象

定义理解

var div = document.getElementById('box');
div.onclick = function(event){ };
  1. event在这里面就是事件对象,写到事件监听函数函数的小括号() 里面,可以当做形参来看。可以自己随便命名(比如 event、e、aa)。后面用不到事件对象的话,就不需要在小括号里写。

  2. 事件对象只有有了事件才会存在,事件对象是系统自动给我们创建的,不需要我们传递参数(事件对象必定和事件相关,如果没有事件它就不存在,有了事件就存在了)

  3. 事件对象是 跟事件相关的一系列相关数据的集合 ,比如说 鼠标点击事件里面就包含了鼠标相关的信息,如鼠标坐标;如果是键盘事件里面就包含了键盘事件的信息,比如判断用户按下了哪个键。

  4. 我来打印出个事件对象给你们看看,里面全是这次事件的相关信息,也就是这次事件(对象)的属性。

常见的事件对象的属性

  1. e.target:返回触发事件的元素(不一定是绑定事件的元素,如事件委托)
  2. e.type:返回事件的类型(比如click、mouseover不带on)
  3. e.preventDefault():该方法阻止默认事件(默认行为), 比如不让链接跳转
  4. e.stopPropagation():停止事件传播。在这个事件后就没了,捕获或者冒泡阶段就到此为止了。
  5. e.path:冒泡路径,从触发元素→window

注意:e.target 和 this 的区别

1、e.target 返回的是触发事件的元素;

2、this 返回的是绑定事件的元素;

区别:e.target 点击了哪个元素就返回那个元素;

this 绑定了哪个元素就返回哪个元素。那么就返回谁。

有的时候,事件的绑定元素和触发元素不是同一个。

例子

  • div绑定了单击事件,那么this指向的就是dive.target 指向我们点击的那个对象(那个元素触发的对象),点击的是div,所以e.target 指向的就是div----绑定元素和触发元素相同

  • ul绑定了单击事件,那么this指向的就是ul;e.target 指向我们点击的那个对象(那个元素触发的对象),点击的是红框右上角,就是ul,所以e.target 指向的就是ul---绑定元素和触发元素相同

  • ul绑定了单击事件,那么this指向的就是ul;e.target 指向我们点击的那个对象(那个元素触发的对象),点击的是li,所以e.target 指向的就是li---绑定元素和触发元素不同

事件委托

  • 事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素;
  • 一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
  • 事件绑定代理给父元素,由父元素根据事件来源统一处 理
  • 适用于可能会新增子元素对场景
  • 事件代理实际上是事件冒泡的应用
  • 如果在需要有多个DOM事件需要监听的情况下(比如几百条微博点击事件注册),给每一个DOM都绑定监听函数,对性能会有极大的影响,因此,有一解决方案为事件委托。
  • 事件委托利用了事件冒泡与event.target

委托的优点

  1. 减少内存消耗

试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;

因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

所以事件委托可以减少大量的内存消耗,节约效率。

  1. 动态绑定事件

比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件; 在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。

jQuery 中的事件委托

  1. $.on: 基本用法: $('.parent').on('click', 'a', function () { console.log('click event on tag a'); }),它是.parent 元素之下的a 元素的事件代理到$('.parent') 之上,只要在这个元素上有点击事件,就会自动寻找到 .parent 元素下的 a 元素,然后响应事件;
  2. $.delegate: 基本用法: $('.parent').delegate('a', 'click', function () { console.log('click event on tag a'); }),同上,并且还有相对应的 $.delegate来删除代理的事件;

零碎知识点

  1. input元素有input事件和change事件
  • oninput事件,在输入用户输入时触发,它是在元素值发生变化时立即触发;该事件在<input><textarea>元素的值发生改变时触发。
  • onchange 在元素失去焦点时触发