gitbook/重学前端/docs/90485.md
2022-09-03 22:05:03 +08:00

161 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 浏览器事件:为什么会有捕获过程和冒泡过程?
你好我是winter。这一节课我们进入了浏览器的部分一起来学习一下事件。
## 事件概述
在开始接触具体的API之前我们要先了解一下事件。一般来说事件来自输入设备我们平时的个人设备上输入设备有三种
* 键盘;
* 鼠标;
* 触摸屏。
**这其中触摸屏和鼠标又有一定的共性它们被称作pointer设备所谓pointer设备是指它的输入最终会被抽象成屏幕上面的一个点。**但是触摸屏和鼠标又有一定区别,它们的精度、反应时间和支持的点的数量都不一样。
我们现代的UI系统都源自WIMP系统。WIMP即Window Icon Menu Pointer四个要素它最初由施乐公司研发后来被微软和苹果两家公司应用在了自己的操作系统上关于这个还有一段有趣的故事我附在文末了
WIMP是如此成功以至于今天很多的前端工程师会有一个观点认为我们能够“点击一个按钮”实际上并非如此我们只能够点击鼠标上的按钮或者触摸屏是操作系统和浏览器把这个信息对应到了一个逻辑上的按钮再使得它的视图对点击事件有反应。这就引出了我们第一个要讲解的机制捕获与冒泡。
## 捕获与冒泡
很多文章会讲到捕获过程是从外向内,冒泡过程是从内向外,但是这里我希望讲清楚,为什么会有捕获过程和冒泡过程。
我们刚提到,实际上点击事件来自触摸屏或者鼠标,鼠标点击并没有位置信息,但是一般操作系统会根据位移的累积计算出来,跟触摸屏一样,提供一个坐标给浏览器。
那么,把这个坐标转换为具体的元素上事件的过程,就是捕获过程了。而冒泡过程,则是符合人类理解逻辑的:当你按电视机开关时,你也按到了电视机。
所以我们可以认为,捕获是计算机处理事件的逻辑,而冒泡是人类处理事件的逻辑。
以下代码展示了事件传播顺序:
```HTML
<body>
<input id="i"/>
</body>
```
```JavaScript
document.body.addEventListener("mousedown", () => {
console.log("key1")
}, true)
document.getElementById("i").addEventListener("mousedown", () => {
console.log("key2")
}, true)
document.body.addEventListener("mousedown", () => {
console.log("key11")
}, false)
document.getElementById("i").addEventListener("mousedown", () => {
console.log("key22")
}, false)
```
我们监听了body和一个body的子元素上的鼠标按下事件捕获和冒泡分别监听可以看到最终产生的顺序是
* “key1”
* “key2”
* “key22”
* “key11”
这是捕获和冒泡发生的完整顺序。
在一个事件发生时,捕获过程跟冒泡过程总是先后发生,跟你是否监听毫无关联。
在我们实际监听事件时,我建议这样使用冒泡和捕获机制:默认使用冒泡模式,当开发组件时,遇到需要父元素控制子元素的行为,可以使用捕获机制。
理解了冒泡和捕获的过程我们再看监听事件的API就非常容易理解了。
addEventListener有三个参数
* 事件名称;
* 事件处理函数;
* 捕获还是冒泡。
事件处理函数不一定是函数也可以是个JavaScript具有handleEvent方法的对象看下例子
```JavaScript
var o = {
handleEvent: event => console.log(event)
}
document.body.addEventListener("keydown", o, false);
```
第三个参数不一定是bool值也可以是个对象它提供了更多选项。
* once只执行一次。
* passive承诺此事件监听不会调用preventDefault这有助于性能。
* useCapture是否捕获否则冒泡
实际使用,在现代浏览器中,还可以不传第三个参数,我建议默认不传第三个参数,因为我认为冒泡是符合正常的人类心智模型的,大部分业务开发者不需要关心捕获过程。除非你是组件或者库的使用者,那就总是需要关心冒泡和捕获了。
## 焦点
我们讲完了pointer事件是由坐标控制而我们还没有讲到键盘事件。
键盘事件是由焦点系统控制的,一般来说,操作系统也会提供一套焦点系统,但是现代浏览器一般都选择在自己的系统内覆盖原本的焦点系统。
焦点系统也是视障用户访问的重要入口,所以设计合理的焦点系统是非常重要的产品需求,尤其是不少国家对可访问性有明确的法律要求。
在旧时代有一个经典的问题是如何去掉输入框上的虚线框这个虚线框就是Windows焦点系统附带的UI表现。
现在Windows的焦点已经不是用虚线框表示了但是焦点系统的设计几十年间没有太大变化。
焦点系统认为整个UI系统中有且仅有一个“聚焦”的元素所有的键盘事件的目标元素都是这个聚焦元素。
Tab键被用来切换到下一个可聚焦的元素焦点系统占用了Tab键但是可以用JavaScript来阻止这个行为。
浏览器API还提供了API来操作焦点
```JavaScript
document.body.focus();
document.body.blur();
```
其实原本键盘事件不需要捕获过程但是为了跟pointer设备保持一致也规定了从外向内传播的捕获过程。
## 自定义事件
除了来自输入设备的事件还可以自定义事件实际上事件也是一种非常好的代码架构但是DOM API中的事件并不能用于普通对象所以很遗憾我们只能在DOM元素上使用自定义事件。
自定义事件的代码示例如下来自MDN
```JavaScript
var evt = new Event("look", {"bubbles":true, "cancelable":false});
document.dispatchEvent(evt);
```
这里使用Event构造器来创造了一个新的事件然后调用dispatchEvent来在特定元素上触发。
我们可以给这个Event添加自定义属性、方法。
注意这里旧的自定义事件方法使用document.createEvent和initEvent已经被废弃。
## 总结
今天这一节课,我们讲了浏览器中的事件。
我们分别介绍了事件的捕获与冒泡机制、焦点机制和自定义事件。
捕获与冒泡机制来自pointer设备输入的处理捕获是计算机处理输入的逻辑冒泡是人类理解事件的思维捕获总是在冒泡之前发生。
焦点机制则来自操作系统的思路用于处理键盘事件。除了我们讲到的这些随着输入设备的不断丰富还有很多新的事件加入如Geolocation和陀螺仪等。
最后给你留个小问题。请你找出你所知道的所有事件类型,和它们的目标元素类型。
## WIMP的小故事
WIMP是由Alan Kay主导设计的这位巨匠同时也是面向对象之父和Smalltalk语言之父。
乔布斯曾经受邀参观施乐他见到当时的WIMP界面认为非常惊艳不久后就领导苹果研究了新一代麦金塔系统。
后来在某次当面对话中乔布斯指责比尔盖茨抄袭了WIMP的设计盖茨淡定地回答“史蒂夫我觉得应该用另一种方式看待这个问题。这就像我们有个叫施乐的有钱邻居当我闯进去想偷走电视时却发现你已经这么干了。”
但是不论如何苹果和微软的数十代操作系统极大地发展了这个体系才有了我们今天的UI界面。