浏览器知识点整理(二)事件机制

认识Dom

(Document Object Model),W3C 制定的标准接口规范,是一种处理 HTML 和 XML 文件的标准 API。它把文档作为一个树形结构,树的每个结点表示了一个 HTML 标签或标签内的文本项.

DOM 树结构精确地描述了 HTML 文档中标签间的相互关联性。将 HTML 或 XML 文档转化为 DOM 树的过程称为解析(parse)

可以试着在浏览器把一个 DOM 节点输出来看一下:

let body = document.body;
for (var key in obj) {
    console.log(`属性:${key},值:${body[key]}`)
}

这个节点有很多属性,其中 onclick 的默认值是 null,当把 onclick 赋予一个函数,就可以作为一个事件函数存在。我们可以通过给对应的属性绑定事件来修改、添加和删除DOM 树的结点和内容。

image.png

DOM 元素的查找

通过 JavaScript 查找 DOM 元素有以下几种方法:

  • 通过 id 查找 DOM 元素
  • 通过标签名查找 DOM 元素
  • 通过类名查找 DOM 元素
  • 通过 CSS 选择器查找 DOM 元素
// 通过 id 查找 DOM 元素
let id = document.getElementById("id"); // 查找 id="id" 的元素
// 通过标签名查找 DOM 元素
let p = document.getElementsByTagName("p"); // 查找所有 <p> 元素
// 通过类名查找 DOM 元素
let cls = document.getElementsByClassName("cls"); // 返回包含 class="cls" 的所有元素的列表
// 通过 CSS 选择器查找 DOM 元素
let pCls = document.querySelectorAll("p.cls"); // 返回 class="cls" 的所有 <p> 元素列表

事件注册

我们一般使用 addEventListener 来注册事件,它接受三个参数:

  • 处理的事件名称,如点击事件 click
  • 事件处理程序,即要绑定的函数体;
  • 指定是在事件冒泡还是事件捕获阶段处理参数,可以是布尔值,也可以是对象
    • true 则作为捕获事件处理;
    • false 则作为冒泡事件处理(默认)。

第三个参数是对象时,可以使用以下几个属性:

  • capture:布尔值,和第三个参数作为布尔值时作用一样
  • once:布尔值,值为 true 表示该回调只会调用一次,调用后会移除监听
  • passive:布尔值,表示永远不会调用 preventDefault

一般来说,我们可以通过使用 stopPropagation 来阻止事件的进一步传播,即阻止事件冒泡。

// stopPropagation() 可以阻止捕获和冒泡
e.stopPropagation(); 
// stopImmediatePropagation() 也可以阻止捕获和冒泡,但主要作用是阻止监听同一事件的其他事件监听器被调用
e.stopImmediatePropagation(); 

事件触发三阶段

  • 事件捕获阶段window 往事件触发处传播,遇到注册的捕获事件会触发
  • 处于目标阶段:传播到事件触发处时触发注册的事件
  • 事件冒泡阶段:从事件触发处往 window 传播,遇到注册的冒泡事件会触发

首先发生的是事件捕获为截获事件提供机会,然后的是实际的目标接收事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件做出响应。 如图:

image.png

假设在 DOM 结构里面有 text 的这样一个标签,给这个标签绑定了一个点击事件,那么在点击这个标签的时候是怎么执行事件的呢?

  • 首先是事件捕获阶段,会通过 windowdocumentbodydivtext 这样的顺序一直往下捕获事件。
  • 然后是处于目标阶段,到 text 标签处触发绑定的点击事件。
  • 最后是事件冒泡阶段,事件是在冒泡阶段做出响应的。冒泡阶段通过 textdivbodydocumentwindow 这样的顺序往上冒泡,假如在 div 或者 body 上面也绑定了对应的 onclick 事件,那么会按顺序触发响应。

事件触发一般来说会按照上面的顺序进行,但是也会有特例,如果给一个 body 中的子节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。

感谢@dayTimeAffect的指出,以上说法不够严谨,在火狐和360浏览器是按照注册的顺序执行,但是在谷歌上面是按照先捕获后冒泡的顺序执行的(测试了火狐、360、谷歌、Edge四款浏览器),也就是说这个注册的顺序不同在不同的浏览器会有不同的表现,要注意区分。所以建议注册按照先捕获再冒泡的顺序来

// 经测试,在火狐(Firefox 81.0)、360(版本12.2)浏览器是按照注册顺序执行的
// 即先打印“冒泡事件”再打印“捕获事件”

// 在谷歌(Chrome 90.0)、Edge(和谷歌同内核)浏览器是按照先捕获再冒泡的顺序执行的,
// 即先打印“捕获事件”再打印“冒泡事件”
var el = document.getElementById('el');
// 第三个参数为 false 即为冒泡事件
el.addEventListener('click', event => {
  console.log('冒泡事件')
}, false);
// 第三个参数为 true 即为捕获事件
el.addEventListener('click', event => {
  console.log('捕获事件')
}, true)

事件代理

事件代理是指利用事件冒泡,只指定一个事件处理程序来管理某一类型的所有事件

如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话可以注册在父节点上。

<ul id="proxy">
  <li>主页</li>
  <li>文章</li>
  <li>公告</li>
  <li>简介</li>
</ul>

事件代理:

let proxy = document.querySelector('#proxy')
proxy.addEventListener('click', (event) => {
  let target = event.target; // 当前点击的元素
  if (target.nodeName.toLowerCase() == 'li') {
    console.log('click:' + target.innerHTML);
  }
})

这种方式相较于直接给目标注册事件来说,有以下优点:

  • 可以减少内存占用,减少事件注册
  • 不需要给子节点注销事件

总结

  • DOM 是一个树形结构,树的每个结点表示了一个 HTML 标签或标签内的文本项。
  • 可以通过 addEventListener 来注册事件。
  • 事件触发有三个阶段,分别是事件捕获阶段、处于目标阶段、事件冒泡阶段
  • 通过利用事件冒泡来实现事件代理,可以提高性能。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光机上敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值