面试题常考
DOM事件模型
DOM事件机制主要有2个阶段,分别是:捕获阶段和冒泡阶段
事件
- 事件
是用户或者浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等,这里的click就是事件的名称。JS与html之间的交互是通过事件实现的,DOM支持大量的事件。
- DOM事件
event.target.nodeName //获取事件触发元素标签名(li,p,div,img,button…)
event.target.id //获取事件触发元素id
event.target.className //获取事件触发元素classname
event.target.innerHTML //获取事件触发元素的内容(li)
事件流
事件流是一个事件沿着特定数据结构传播的过程。冒泡和捕获是事件流在DOM中两种不同的传播方法
事件捕获&事件冒泡
当一个事件发生在具有父元素的元素上时,现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段。
DOM是树形的,当我们在页面上单击一个按钮时,先发生事件捕获,也就是从不具体的节点到最具体的节点,一般是从document对象开始从外网内传播,然后再事件冒泡,也就是事件是由最具体的元素接收,然后逐级向上传播,在每一级的节点上都会发生,直到传播到document对象
- 从外向内找监听函数,叫事件捕获
- 从内向外找监听函数,叫事件冒泡
- 注意:两个阶段都会走一遍,且先捕获后冒泡
当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件
fn 分别在捕获阶段和冒泡阶段监听click事件
addEventListener(事件绑定API)
捕获和冒泡两个阶段都会走一遍,但具体执行取决于事件绑定API的参数设定
addEventListener(‘click’,fn,bool)
- 如果第三个参数bool 不传,或者传false, 那么我们会在冒泡阶段调用fn
- 如果第三个参数Bool传值为true, 那么我们会在捕获阶段调用fn
target 和 currentTarget的区别
可能相同,也可能不同
- e.target 用户在操作的元素
- e.currentTarget 程序员监听的元素
- 事件 前的对象 即为监听的对象
- 如
level1.addEventListener('click',(e) => {console.log('npc')})
level1为监听的元素
- 如
- 事件 前的对象 即为监听的对象
- this是 e.currentTarget ,监听代码里不推荐使用它
- target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是一样的,而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(一般为父级)。
假设我们监听的是div, 但用户实际点击的是文字,那么<div>
<span>文字</span>
</div>
e.target就是span标签
e.currentTarget就是div标签
示例:
无捕获和冒泡阶段的特例
当监听的元素就是用户操作的元素,且没有父子的包含关系,那么谁先监听谁就先执行
阻止冒泡
- 捕获不能取消,但是冒泡有时候可以
stopPropagation()可以中断冒泡,浏览器不再往上走
一般用于封装独立组件
- 有些特定冒泡不可取消
如 scroll event(滚动事件)
具体可以在MDN上查询
- Bubbles 该事件是否冒泡
- Cancelable 开发者是否可以取消冒泡
自定义事件
CustomEvent( type,detail ) 创建一个自定义事件
type
事件的类型名称。
detail
当事件初始化时传递的数据。 ```javascript // 添加一个适当的事件监听器 obj.addEventListener(“cat”, function(e) { process(e.detail) })
// 创建并分发事件 var event = new CustomEvent(“cat”, {“detail”:{“hazcheeseburger”:true}}) obj.dispatchEvent(event)
<a name="v4tKG"></a>
# DOM事件委托
<a name="IpC7R"></a>
## 事件委托原理机制
简而言之:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数,<br />因此**可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件**
举例:<br />给最外面的节点添加事件,那么里面的子节点在做事件的时候,都会冒泡到最外层的父节点上,所以都会触发,这就是事件委托,委托它们父级 代为执行事件
<a name="jftw1"></a>
## 应用场景
出现多个嵌套的深层元素,并且需要在最深层处触发事件时,将相同类型的事件绑定在父级元素上进行统一管理<br />eg.<br />点击表格中的各个单元格,触发对应事件,若表格单元格过多,需要绑定大量事件,使用事件委托给单元格的上级table标签,只在table标签处绑定事件,在事件声明中使用event.target输出真正点击到的单元格
<a name="fcni2"></a>
## 获取触发事件的元素的值
根据元素的类型使用:<br />input类型:event.target.value<br />其他类型:event.target.innerHTML
<a name="ycKvL"></a>
## 事件委托的优势
- 节省监听器(节省内存),提升js性能,减少DOM压力
监听父级节点,等冒泡的时候,判断event.target是不是目标子节点的一个
- 动态监听(可监听目前不存在的节点)
监听父级节点,等到冒泡时,判断target是不是想要监听的元素
<a name="RcBC8"></a>
## 封装事件委托函数
需求:监听所有的li标签,如果用户点击li标签,就console.log('用户点击了Li标签')
```javascript
<ul id="test">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
那么实现的JS代码就是:
// 监听父元素 ul#test
test.addEventListener('click', (e)=> {
//通过浏览器传进来的e参数,找到当前点击元素
const t = e.target
// 判断当前元素是不是Li标签
if(t.matches('li') {
console.log('用户点击了li')
}
})
- 首先监听父元素,
- 然后根据浏览器传进去的事件信息,拿到当前点击元素,
- 再判断当前点击元素是不是li元素, 如果是,就console.log(‘用户点击Li标签’)
基于此,我们可以封装一个事件委托函数
on('click', '#test', 'li', ()=>{
console.log('用户点击了li')
})
function on(eventType, parentElement, selector, fn) {
// 先判断是不是element,
//如果传进来的是选择器,不是element本身,就先变成element,
// 因为只有element才能监听事件
if (!(parentElement instanceof Element)) {
parentElement = parentElement.querySelectorAll(parentElement)
}
parentElement.addEventListener(eventType, (e)=>{
let target = e.target
if (target.matches(selector)) {
fn(e)
}
})
}