DOM事件
-
事件
简单来说,事件就是交互动作。要实现用户与页面的交互,先要对目标元素绑定特定的事件、设置事件处理函数,然后用户触发事件,事件处理函数执行,产生交互效果。
-
DOM事件级别
DOM级别一共可以分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。而DOM事件分为3个级别:DOM 0级事件处理,DOM 2级事件处理和DOM 3级事件处理。由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件。
-
DOM 0级事件
1 2 3 4 5
let btn = document.getElementById('btn') btn.onclick = function btnClick(){ console.log('这个btn被点击了!') } //btn.onclick = null; 解绑事件
上述代码构造了一个DOM 0级事件,即Id为btn的元素绑定了一个名为onclick的事件,一旦事件(点击btn)触发,就会执行指定的btnClick函数,从而达成既定效果。
很简单,但是也仅限于此。DOM 0级事件无法使元素绑定多个同类型事件,如果你想实现3、4种不同功能的点击事件,使用这种写法是无法达成的。
-
DOM 2级事件
1 2 3 4
let btn = document.getElementById('btn') btn.addEventListener("click", btnClick, false); btn.addEventListener("click", btnClickAgain, false); // btn.removeEventListener('click', btnClick, false); 解绑事件
DOM 2级事件在0级事件的基础上弥补了这种缺陷,允许为同一个事件绑定不同的处理函数,同时定义了
.addEventListener()
和.removeEventListener()
两种方法,用来绑定/解绑事件。addEventListener()
方法一般会接收三个参数addEventListener(eventType,callBack,useCapture)
,分别表示事件名称类型,可执行的回调函数及是否在捕获阶段执行回调(默认为false)。 -
DOM 3级事件
3级事件在2级事件的基础上添加了更多的事件类型,同时允许一些自定义事件存在。
- UI事件,当用户与页面上的元素交互时触发,如:
load
、scroll
- 焦点事件,当元素获得或失去焦点时触发,如:
blur
、focus
- 鼠标事件,当用户通过鼠标在页面执行操作时触发如:
dblclick
、mouseup
- 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:
mousewheel
- 文本事件,当在文档中输入文本时触发,如:
textInput
- 键盘事件,当用户通过键盘在页面上执行操作时触发,如:
keydown
、keypress
- 合成事件,当为IME(输入法编辑器)输入字符时触发,如:
compositionstart
- 变动事件,当底层DOM结构发生变化时触发,如:
DOMsubtreeModified
- UI事件,当用户与页面上的元素交互时触发,如:
-
事件流 & 事件模型
-
事件流
可以尝试把页面理解为一个二维的平面,想象有一张白纸,我们在这张纸上画下了一层一层的同心圆,当我们用手指按住最内的圆圈时,也按住了纸上所有的同心圆,也按住了整张纸。所以单击事件不仅仅发生在被击中的元素上,换句话说,当我们用鼠标单击其中一个元素时,你也是在单击元素的容器,甚至一层一层向外传递(也可以是向内),甚至是在单击整个页面。[1]
DOM事件模型分为捕获和冒泡两个阶段,一个事件发生后,会在子元素和父元素之间进行传播,这种传播一般分为三个阶段:捕获阶段、目标阶段、冒泡阶段
◎ 事件流
-
事件捕获
根据上图,事件捕获即事件从window开始向下进行层层传递,最终到达目标元素。
1 2 3
<div id = "parent"> <span id = "child">事件捕获</span> </div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
let parent = document.getElementById('parent') let child = document.getElementById('child') child.addEventListener('click', function() { console.log('是的点的就是我!') }, true) parent.addEventListener('click', function() { console.log('parent 向下传递!') }, true) document.body.addEventListener('click', function() { console.log('body 向下传递!') }, true) document.addEventListener('click', function() { console.log('document 向下传递!') }, true) window.addEventListener('click', function() { console.log('window 向下传递!') }, true)
◎ 事件捕获
-
事件冒泡
事件冒泡与事件捕获正相反,目标元素触发事件后,逐级向上传播
1 2 3
<div id = "parent"> <span id = "child">事件冒泡</span> </div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
let parent = document.getElementById('parent') let child = document.getElementById('child') child.addEventListener('click', function() { console.log('是的点的就是我!') }, false) parent.addEventListener('click', function() { console.log('parent 向上冒泡!') }, false) document.body.addEventListener('click', function() { console.log('body 向上冒泡!') }, false) document.addEventListener('click', function() { console.log('document 向上冒泡!') }, false) window.addEventListener('click', function() { console.log('window 向上冒泡!') }, false)
◎ 事件冒泡
事件对象
-
Event对象
在用户触发事件后,执行事件的回调函数时,默认会向该函数传入一个event对象,这个参数记录了该事件的状态和行为。
-
Event对象属性与方法
-
Event对象常用方法
event.stopPropagation()
:阻止事件冒泡到父元素,阻止任何父事件处理程序被执行,从而中断冒泡。event.preventDefault()
:阻止默认事件行为触发。
-
Event对象常见属性
event.type
:获取事件类型名称。event.bubbles
:指示事件是否冒泡,所有的冒泡都可以取消。event.cancelable
:指示是否可以阻止默认事件,与冒泡无关。event.target
:获取事件的目标对象(用户交互的)event.currentTarget
:获取事件绑定的对象(监听事件的)
1 2 3 4 5 6 7
<div id = "a"> <div id="b"> <div id="c"> 点击这里 </div> </div> </div>
1 2 3 4 5 6 7 8 9 10 11
let a = document.getElementById('a') a.addEventListener('click', function(e) { console.log('e.target.id是:',e.target.id) console.log('e.currentTarget.id是:',e.currentTarget.id) }, false) //输出: //e.target.id是:c //e.currentTarget.id是:a //可以明显看出,target为用户点击的目标元素,currentTarget是开发者监听的元素
-
事件委托
-
为什么要进行事件委托?
简单设想一下,这里有一份非常非常长,且随时都会进行增删改操作的列表⬇️
1 2 3 4 5 6 7
<ul id="list"> <li>item 1</li> <li>item 2</li> <li>item 3</li> ...... <li>item 999</li> </ul>
现在要求我们点击每个列表项时都会响应一个事件,比如点击后打印出每一项的内容。应该怎么做呢?最简单粗暴的方法莫过于将我们的click事件写满每一项,成为名副其实的“窃听狂魔”,但这也是基于我们目前有的所有项,一旦进行了修改,就要给新增的元素重新绑定事件或者给删去的元素解绑事件……(・_・;光是脑补,就已经是生命不可承受之痛。
对于机器也是一样的,无数条雷同的事件挤占着内存空间,将会极大地拉低它的运行效率。
那么有没有什么办法能够一举两得地解决这个问题?想想我们之前写到的,DOM事件模型分为事件捕获、事件冒泡,冒泡,冒泡,像一口煮着粥的大锅,里面不断有气泡生成又消失,但是它们都在锅里————
它们都在这口锅里!是的,所有的
<li>
元素都在<ul>
这个偌大的容器内,我们只需要给<ul>
这个父容器绑定一个适当的、能够实现我们设想的方法,这样一来,无论容器内部的数据发生怎样的变化,只要它们符合我们的要求,都可以根据事件冒泡的传递机制,将绑定在父容器上的事件触发,提供我们想要的事件信息。正是由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)又称事件委托。[2]
-
实现它!
1 2 3 4 5 6 7
// 给父容器绑定事件 document.getElementById('list').addEventListener('click', function (e) { if (target.nodeName.toLocaleLowerCase === 'li') { // 判断点击的目标元素是否为列表项 console.log('该项内容为: ', target.innerHTML); } })
文章参考自🥰