惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
The Last Watchdog
The Last Watchdog
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
Last Week in AI
Last Week in AI
量子位
雷峰网
雷峰网
宝玉的分享
宝玉的分享
美团技术团队
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗
P
Privacy International News Feed
NISL@THU
NISL@THU
V
V2EX
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
IT之家
IT之家
T
Tor Project blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Threatpost
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
V
Vulnerabilities – Threatpost
V
Visual Studio Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The Hacker News
The Hacker News
Scott Helme
Scott Helme
H
Hacker News: Front Page
罗磊的独立博客
Hacker News - Newest:
Hacker News - Newest: "LLM"
K
Kaspersky official blog
S
Secure Thoughts
www.infosecurity-magazine.com
www.infosecurity-magazine.com
The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
小众软件
小众软件
月光博客
月光博客
人人都是产品经理
人人都是产品经理
AI
AI
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
Webroot Blog
Webroot Blog
博客园 - 【当耐特】
PCI Perspectives
PCI Perspectives
A
Arctic Wolf

步步走

mac 环境变量与 shell · 步步走 如何反编译微信小程序 · 步步走 mac 终端命令大全 · 步步走 深入学习 TypeScript 类型体操 · 步步走 微前端的几种架构介绍 · 步步走 git clone 的时候如何通过 SSH 链接来下载远程项目 · 步步走 从 ESLint 开始,说透我如何在团队项目中基于 Vue 做代码校验 · 步步走 死磕 36 个 JS 手写题(搞懂后,提升真的大) · 步步走 1.5 万字 CSS 基础拾遗(核心知识、常用需求) · 步步走 为你的网站加上 WebP 格式的图片吧 · 步步走 让人爱不释手的 JS 扩展操作符 13 用 · 步步走 6 分钟了解 HTTP 发展史 · 步步走 Resourse Hints 知多少 · 步步走 探究网页资源究竟是如何阻塞浏览器加载的 · 步步走 很多人知道 Web Storage,但是你清楚 Cookie 嘛 · 步步走 浏览器专题之缓存篇 · 步步走 从输入 URL 到页面显示发生了什么 · 步步走 浏览器专题之安全篇 · 步步走 「建议收藏」送你一份精心总结的3万字ES6实用指南(全) · 步步走
浏览器专题之事件机制 · 步步走
typeR · 2020-12-11 · via 步步走

浏览器专题之事件机制

事件流

在早期 IENetscape 团队在开发第四代浏览器的时候,遇到一个问题:当点击一个按钮的时候,是应该先处理父级的事件呢?还是应该先处理按钮的事件呢?IENetscape 给出了 2 种完全相反的答案,IE 提出事件冒泡的概念,而 Netscape 则支持事件捕获。

事件冒泡

事件冒泡认为事件应该由最具体的元素开始触发,然后层层往父级传播:

事件捕获

而事件捕获则相反,认为最外层的元素应该最先收到事件,然后层层往下级传递:

DOM 事件流

为了在浏览器中兼容这 2 种事件流,在 DOM2 Events 规范中将事件流分为 3 个阶段:事件捕获阶段、到底目标阶段、事件冒泡阶段。

可以通过指定 addEventListener 的第三个参数为 true 来设置事件是在捕获阶段调用事件处理程序,默认是 false 指在冒泡阶段调用事件处理程序。

所有现代浏览器都支持 DOM 事件流,只有 IE8 及更早版本不支持。

事件处理程序

HTML 事件处理程序

就是将事件处理程序直接绑定到 HTML 的属性中:

1
2
3
4
5
6
7
8
// 方式一
<div onclick="console.log('hello world')"></div>

方式二
<div onclick="print(event)"></div>
<script>
function print(e) { }
</script>

HTML 事件处理程序修改事件相对麻烦,可能需要同时修改 HTMLJS,所以大家都不爱使用这种方式绑定事件。

DOM0 事件处理程序

将一个函数赋值给 DOM 元素的一个事件处理程序属性,比如 onclick

1
2
3
4
5
6
7
let btn = document.getElementById('div')


btn.onclick = function() { }


btn.onclick = null

DOM2 事件处理程序

通过 addEventListener 可以添加 DOM2 级别的事件处理程序,它接收 3 个参数:事件名、事件处理程序和 useCapture (它是一个可选参数,是个布尔值,默认为 false 表示在冒泡阶段调用事件处理程序)

1
2
3
4
let btn = document.getElementById('div')
btn.addEventListener('click', () => {

}, false)

DOM0 事件处理程序的区别:

  • addEventListener 可以改变事件流,即可以在捕获阶段触发事件,而 DOM0 是不行的;
  • addEventListener 可以为同一个元素多次添加同一类型的事件处理程序,先添加的事件处理程序会先触发,而 DOM0 如果给同一个元素绑定多个相同类型的事件处理程序的话,则后面添加的会覆盖前面定义的;

它有几个注意事项:

  • 如果不需要在捕获阶段进行拦截操作,则 useCapture 即第三个参可以不传;
  • 通过 addEventListener 添加的事件处理程序只能通过 removeEventListener 移除,而且绑定的事件处理程序必须是同一个。
1
2
3
4
let btn = document.getElementById('div')
let handler = function() { }
btn.addEventListener("click", handler)
btn.removeEventListener("click", handler)

IE 事件处理函数

由于 addEventListener 无法兼容 IE8 及更早版本,所以此时就可以使用 attachEvent 添加事件处理程序和用 detachEvent 移除事件处理程序。

1
2
let btn = document.getElementById('div')
btn.attachEvent("onclick", function() { })

它有这么几个注意事项:

  • 注册的事件名和 DOM0 一样,需要带上 on,比如 onclick
  • 在通过 attachEvent 添加的事件处理程序内部 this 会指向 window,而 DOM0DOM2this 会指向元素本身;
  • addEventListener 一样, attachEvent 也可以针对同一元素多次添加同一个事件类型的处理程序,但是触发顺序是后定义的先触发;
  • 通过 detachEvent 移除事件处理程序的时候,处理函数必须是和注册的同一个,这点和 addEventListener 保持一致;

attachEventdetachEventIE 专属的 API,所以如果有兼容性要求,我们可以写出跨浏览器的事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false)
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler)
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false)
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler)
} else {
element["on" + type] = null
}
}
}

事件对象

通过不同的事件处理程序添加的事件,event 对象的属性略有不同,我们不需要记住他们的差异,只需要在平时写代码的时候养成一个写兼容代码的习惯即可,如下是一个兼容各种 event 对象的事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let handler = function(event) {

let event = event || window.event


let target = event.target || event.srcElement


if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}


if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
}

事件类型

DOM3 Events 定义了如下事件类型:

  • 用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件,比如 onloadresizescrollinputselect 等;
  • 焦点事件(FocusEvent):在元素获得和失去焦点时触发,比如 focusblur
  • 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发,比如 clickmousedownmouseover 等;
  • 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发,比如 mousewheel
  • 输入事件(InputEvent):向文档中输入文本时触发,比如 textInput
  • 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发,比如 keydownkeypress
  • 合成事件(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入字符时触发,比如 compositionstart

事件委托

事件委托是指将多个元素上绑定的事件通过利用事件冒泡的原理从而转移到他们共同的父级上去绑定,从而在一定程度上起到优化的作用,有的人也喜欢叫它事件代理。比如在 Vue 中经常会将事件绑定到每个列表项中:

1
2
3
4
5
6
7
8
9
10
11
<ul>
<li v-for="item in list" :key="item" @click="handleClick(item)">{{item}}</li>
</ul>
```

其实更好的做法是利用事件委托,将事件绑定到 `ul` 上:

```html
<ul @click="handleClick">
<li v-for="item in list" :key="item" :data-item="item">{{item}}</li>
</ul>
1
2
3
4
5
6
handleClick(event) {
let target = event.target
if (target === 'li') {
let data = target.dataset.item
}
}