什么是DOM?
文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。
DOM事件流
-
事件流描述的是从页面中接收事件的顺序
-
事件发送时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流
-
请看下图:
<div class="爷爷">
<div class="爸爸">
<div class="儿子">
文字
</div>
</div>
</div>
我们可以看到分别是
- 捕获阶段
- 网景公司最早提出的,由DOM最顶层节点开始,然后逐级向下传播到最具体的元素的过程。
- 浏览器检查元素的最外层祖先
<html>
,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。 - 然后,它移动到
<html>
中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。 即:从外向内找监听函数
- 冒泡阶段
- E最早提出的,事件开始由最具体的元素接受,然后逐级向上传播到DOM最顶层节点的过程。
- 浏览器首先检查被点击元素,(就是文字),然后看是否在冒泡阶段中有Onclick事件,如果是,就运行
- 然后,寻找下一个parentNode, (就是儿子div),然后看是否在冒泡阶段中有Onclick事件,如果是,就运行
- 然后再找下一个ParentNode, (就是爸爸div)
即:从内向外找监听函数
- 当前目标阶段
浏览器调用顺序
文档名为DOMLevel2EventsSpecification,规定浏览器应该同时支持两种调用顺序,首先捕获顺序,然后是冒泡顺序,有监听就调用,并提供时间信息,没有就跳过
- 那是不是事件会调用两次呢?
- 提供了api总的参数可以让开发者自己选择是事件放在捕获阶段还是冒泡阶段
- 使用addEventListener监听事件时,addEventListener('click', fn, bool)
- 如果第三个参数bool 不传,或者传false, 那么我们会在冒泡阶段调用fn
- 如果第三个参数Bool传值为true, 那么我们会在捕获阶段调用fn
eventTarget.onclick = funciton(event){
// 这个 evnet 就是事件对象,我比较喜欢缩写成 e
}
eventTarget.addEventListener('click',function(event){
// 这个 evnet 就是事件对象,我比较喜欢缩写成 e
})
事件对象阻止默认行为
捕获是不可以阻止,取消的, 冒泡可以。
- e.stopPropagation() 可取消冒泡,浏览器就不再向上走了。
- e.preventDefault()可以取消默认事件
e.target 和 e.currentTarget 的区别
-
e.target: 返回触发事件的对象。 即用户操作的对象。(假设:你点击了谁就是谁)
-
e.currentTarget : 程序员监听的元素, 即你绑定了谁就是谁
-
this就是e.currentTarget
捕获和冒泡一句话
- 捕获:当用户点击按钮,浏览器会从 window 从上向下遍历至用户点击的按钮,逐个触发事件处理函数。
- 冒泡:浏览器从用户点击的按钮从下往上遍历至 window,逐个触发事件处理函数。
事件委托
什么是事件委托?
- 不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
- 由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数,因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件
- 这样我们只操作了一次DOM, 省内存、可以监听动态元素
常用场景
场景一
- 怎么给100个按钮添加点击事件?
- 找到他们的共同属于那个元素,那就监听这个共有元素,等冒泡的时候判断 target是不是这100个按钮其中的一个
场景二
- 怎么监听目前不存在的元素的点击事件呢?
- 监听共有元素,等到点击的时候看看是不是我想要监听的元素
封装事件委托
写出一个函数 on('click','#div1','button',fn) 当用户点击#div1的button元素时,调用fn函数。要求用事件委托。
on('click', '#test', 'li', ()=>{
console.log('用户点击了li')
})
function on(eventType, element, selector, fn) {
if (!(element instanceof Element)) {
element = document.querySelectorAll(element)
}
element.addEventListener(eventType, (e)=>{
let target = e.target
// 如果匹配到了selector就跳出循环
while(!target.matches(selector)){
if (target === element){
//已经找到了父元素,说明还没找到,就设置为null
target = null
break
}
target = target.parentNode
}
// 找到了target, 就调用函数
target && fn.call(target, e)
})
}