重学前端4 - 浏览器

浏览器


10 | 浏览器:一个浏览器是如何工作的?(阶段一)

网络部分

对浏览器的实现者来说,他们做的事情,就是把一个 URL 变成一个屏幕上显示的网页。

  • 浏览器首先使用 HTTP 协议或者 HTTPS 协议,向服务端请求页面;
  • 把请求回来的 HTML 代码经过解析,构建成 DOM 树;
  • 计算 DOM 树上的 CSS 属性;
  • 最后根据 CSS 属性对元素逐个进行渲染,得到内存中的位图;
  • 一个可选的步骤是对位图进行合成,这会极大地增加后续绘制的速度;
  • 合成之后,再绘制到界面上。

HTTP 协议

HTTP 协议是基于 TCP 协议出现的,对 TCP 协议来说,TCP 协议是一条双向的通讯通道,HTTP 在 TCP 的基础上,规定了 Request-Response 的模式。这个模式决定了通讯必定是由浏览器端首先发起的。

使用 telnet 客户端,这个客户端是一个纯粹的 TCP 连接工具。

HTTP Method(方法)

  • GET
  • POST
  • HEAD
  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE

浏览器通过地址栏访问页面都是 GET 方法。

表单提交产生 POST 方法。

HEAD 则是跟 GET 类似,只返回请求头,多数由 JavaScript 发起

PUT 和 DELETE 分别表示添加资源和删除资源,但是实际上这只是语义上的一种约定,并没有强约束。

CONNECT 现在多用于 HTTPS 和 WebSocket。

OPTIONS 和 TRACE 一般用于调试,多数线上服务都不支持。

HTTP Status code(状态码)和 Status text(状态文本)

  • 1xx:临时回应,表示客户端请继续。
  • 2xx:请求成功。
    • 200:请求成功。
  • 3xx: 表示请求的目标有变化,希望客户端进一步处理。
    • 301 & 302:永久性与临时性跳转。
    • 304:跟客户端缓存没有更新。
  • 4xx:客户端请求错误。
    • 403:无权限。
    • 404:表示请求的页面不存在。
    • 418:It’s a teapot. 这是一个彩蛋,来自 ietf 的一个愚人节玩笑。
  • 5xx:服务端请求错误。
    • 500:服务端错误。
    • 503:服务端暂时性错误,可以一会再试。

对我们前端来说,1xx 系列的状态码是非常陌生的,原因是 1xx 的状态被浏览器 http 库直接处理掉了,不会让上层应用知晓。

2xx 系列的状态最熟悉的就是 200,这通常是网页请求成功的标志,也是大家最喜欢的状态码。

3xx 系列比较复杂,301 和 302 两个状态表示当前资源已经被转移,只不过一个是永久性转移,一个是临时性转移。实际上 301 更接近于一种报错,提示客户端下次别来了。

304 又是一个每个前端必知必会的状态,产生这个状态的前提是:客户端本地已经有缓存的版本,并且在 Request 中告诉了服务端,当服务端通过时间或者 tag,发现没有更新的时候,就会返回一个不含 body 的 304 状态。

HTTP Head (HTTP 头)

我们先来看看 Request Header。

接下来看一下 Response Header。

HTTP Request Body

HTTP 请求的 body 主要用于提交表单场景。实际上,http 请求的 body 是比较自由的,只要浏览器端发送的 body 服务端认可就可以了。一些常见的 body 格式是:

  • application/json
  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/xml

我们使用 html 的 form 标签提交产生的 html 请求,默认会产生 application/x-www-form-urlencoded 的数据格式,当有文件上传时,则会使用 multipart/form-data。

HTTPS

HTTPS 有两个作用,一是确定请求的目标服务端身份,二是保证传输的数据不会被网络中间节点窃听或者篡改

HTTPS 是使用加密通道来传输 HTTP 的内容。但是 HTTPS 首先与服务端建立一条 TLS 加密通道。TLS 构建于 TCP 协议之上,它实际上是对传输的内容做一次加密,所以从传输内容上看,HTTPS 跟 HTTP 没有任何区别。

HTTP 2

HTTP 2.0 最大的改进有两点,一是支持服务端推送,二是支持 TCP 连接复用

服务端推送能够在客户端发送第一个请求到服务端时,提前把一部分内容推送给客户端,放入缓存当中,这可以避免客户端请求顺序带来的并行度不高,从而导致的性能问题。

TCP 连接复用,则使用同一个 TCP 连接来传输多个 HTTP 请求,避免了 TCP 连接建立时的三次握手开销,和初建 TCP 连接时传输窗口小的问题。


11 | 浏览器:一个浏览器是如何工作的?(阶段二)

今天我们主要来看两个过程:如何解析请求回来的 HTML 代码,DOM 树又是如何构建的。

HTML 的结构不算太复杂,我们日常开发需要的 90% 的“词”(指编译原理的术语 token,表示最小的有意义的单元),种类大约只有标签开始、属性、标签结束、注释、CDATA 节点几种。

词(token)是如何被拆分的

<p class="a">text text text</p>

那么,只用 p 标签的开头是不是合适吗?我们考虑到起始标签也是会包含属性的,最小的意义单元其实是“<p” ,所以“ <p” 就是我们的第一个词(token)。

我们继续拆分,可以把这段代码依次拆成词(token):

  • <p“标签开始”的开始;
  • class=“a” 属性;
  • “标签开始”的结束;

  • text text text 文本;
  • 标签结束。

这是一段最简单的例子,类似的还有什么呢?现在我们可以来来看看这些词(token)长成啥样子:

状态机

绝大多数语言的词法部分都是用状态机实现的。那么我们来把部分词(token)的解析画成一个状态机看看:

构建 DOM 树

接下来我们要把这些简单的词变成 DOM 树,这个过程我们是使用来实现的,任何语言几乎都有栈,为了给你跑着玩,我们还是用 JavaScript 来实现吧,毕竟 JavaScript 中的栈只要用数组就好了。

根据一些编译原理中常见的技巧,我们使用的栈正是用于匹配开始和结束标签的方案。

通过这个栈,我们可以构建 DOM 树:

  • 栈顶元素就是当前节点;
  • 遇到属性,就添加到当前节点;
  • 遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点;
  • 遇到注释节点,作为当前节点的子节点;
  • 遇到 tag start 就入栈一个节点,当前节点就是这个节点的父节点;
  • 遇到 tag end 就出栈一个节点(还可以检查是否匹配)。

12 | 浏览器:一个浏览器是如何工作的(阶段三)

整体过程

构建 DOM 的过程是:从父到子,从先到后,一个一个节点构造,并且挂载到 DOM 树上的,那么这个过程中,我们是否能同步把 CSS 属性计算出来呢?答案是肯定的。

在这个过程中,我们依次拿到上一步构造好的元素,去检查它匹配到了哪些规则,再根据规则的优先级,做覆盖和调整。所以,从这个角度看,所谓的选择器,应该被理解成“匹配器”才更合适。

我在 CSS 语法部分,已经总结了选择器的各种符号,这里再把它列出来,我们回顾一下:

  • 空格: 后代,选中它的子节点和所有子节点的后代节点。
  • : 子代,选中它的子节点。

  • +:直接后继选择器,选中它的下一个相邻节点。
  • ~:后继,选中它之后所有的相邻节点。
  • ||:列,选中表格中的一列。

13 | 浏览器:一个浏览器是如何工作的?(阶段四)

基本概念

在现代浏览器中,仍然借用了这个概念,但是排版的内容更加复杂,包括文字、图片、图形、表格等等,我们把浏览器确定它们位置的过程,叫作排版

浏览器最基本的排版方案是正常流排版,它包含了顺次排布和折行等规则,这是一个跟我们提到的印刷排版类似的排版方案,也跟我们平时书写文字的方式一致,所以我们把它叫做正常流。

浏览器的文字排版遵循公认的文字排版规范,文字排版是一个复杂的系统,它规定了行模型和文字在行模型中的排布。行模型规定了行顶、行底、文字区域、基线等对齐方式。(你还记得小时候写英语的英语本吗?英语本上的四条线就是一个简单的行模型)

此外,浏览器支持不同语言,因为不同语言的书写顺序不一致,所以浏览器的文本排版还支持双向文字系统。

浏览器又可以支持元素和文字的混排,元素被定义为占据长方形的区域,还允许边框、边距和留白,这个就是所谓的盒模型

在正常流的基础上,浏览器还支持两类元素:绝对定位元素和浮动元素。

  • 绝对定位元素把自身从正常流抽出,直接由 top 和 left 等属性确定自身的位置,不参加排版计算,也不影响其它元素。绝对定位元素由 position 属性控制。
  • 浮动元素则是使得自己在正常流的位置向左或者向右移动到边界,并且占据一块排版空间。浮动元素由 float 属性控制。

除了正常流,浏览器还支持其它排版方式,比如现在非常常用的 flex 排版,这些排版方式由外部元素的 display 属性来控制(注意,display 同时还控制元素在正常流中属于 inline 等级还是 block 等级)。

正常流文字排版

我们会在 CSS 部分详细介绍正常流排版的行为,我们这里主要介绍浏览器中的正常流。正常流是唯一一个文字和盒混排的排版方式,我们先从文字来讲起。

实际上浏览器环境也很类似。但是因为浏览器支持改变排版方向,不一定是从左到右从上到下,所以我们把文字依次书写的延伸方向称为主轴或者主方向,换行延伸的方向,跟主轴垂直交叉,称为交叉轴或者交叉方向。

正常流中的盒

在正常流中,display 不为 inline 的元素或者伪元素,会以盒的形式跟文字一起排版。多数 display 属性都可以分成两部分:内部的排版和是否 inline,带有 inline- 前缀的盒,被称作行内级盒。

根据盒模型,一个盒具有 margin、border、padding、width/height 等属性,它在主轴方向占据的空间是由对应方向的这几个属性之和决定的,而 vertical-align 属性决定了盒在交叉轴方向的位置,同时也会影响实际行高。

所以,浏览器对行的排版,一般是先行内布局,再确定行的位置,根据行的位置计算出行内盒和文字的排版位置。

块级盒比较简单,它总是单独占据一整行,计算出交叉轴方向的高度即可。

绝对定位元素

position 属性为 absolute 的元素,我们需要根据它的包含块来确定位置,这是完全跟正常流无关的一种独立排版模式,逐层找到其父级的 position 非 static 元素即可。

浮动元素排版

float 元素非常特别,浏览器对 float 的处理是先排入正常流,再移动到排版宽度的最左 / 最右(这里实际上是主轴的最前和最后)。

移动之后,float 元素占据了一块排版的空间,因此,在数行之内,主轴方向的排版距离发生了变化,直到交叉轴方向的尺寸超过了浮动元素的交叉轴尺寸范围,主轴排版尺寸才会恢复。

flex 排版

CSS 的每一种排版都有一个很复杂的规定,实际实现形式也各不相同。比如 flex 排版,支持了 flex 属性,flex 属性将每一行排版后的剩余空间平均分配给主轴方向的 width/height 属性。浏览器支持的每一种排版方式,都是按照对应的标准来实现的。


14 | 浏览器:一个浏览器是如何工作的?(阶段五)

  • 把 URL 变成字符流
  • 把字符流变成词(token)流
  • 把词(token)流构造成 DOM 树,把不含样式信息的 DOM 树应用 CSS 规则,变成包含样式信息的 DOM 树
  • 根据样式信息,计算了每个元素的位置和大小
  • 浏览器确定元素的位置
  • 根据这些样式信息和大小信息,为每个元素在内存中渲染它的图形,并且把它绘制到对应的位置。

渲染

在计算机图形学领域里,英文 render 这个词是一个简写,它是特指把模型变成位图的过程。我们把 render 翻译成“渲染”,是个非常有意思的翻译,中文里“渲染”这个词是一种绘画技法,是指沾清水把墨涂开的意思。

这里的位图就是在内存里建立一张二维表格,把一张图片的每个像素对应的颜色保存进去(位图信息也是 DOM 树中占据浏览器内存最多的信息,我们在做内存占用优化时,主要就是考虑这一部分)。

浏览器中渲染这个过程,就是把每一个元素对应的盒变成位图。这里的元素包括 HTML 元素和伪元素,一个元素可能对应多个盒(比如 inline 元素,可能会分成多行)。每一个盒对应着一张位图。

这个渲染过程是非常复杂的,但是总体来说,可以分成两个大类:图形文字

盒的背景、边框、SVG 元素、阴影等特性,都是需要绘制的图形类。

一般的操作系统会提供一个底层库,比如在 Android 中,有大名鼎鼎的 Skia,而 Windows 平台则有 GDI,一般的浏览器会做一个兼容层来处理掉平台差异。

盒中的文字,也需要用底层库来支持,叫做字体库。字体库提供读取字体文件的基本能力,它能根据字符的码点抽取出字形。

字形分为像素字形和矢量字形两种。通常的字体,会在 6px 8px 等小尺寸提供像素字形,比较大的尺寸则提供矢量字形。矢量字形本身就需要经过渲染才能继续渲染到元素的位图上去。目前最常用的字体库是 Freetype,这是一个 C++ 编写的开源的字体库。

但是理想和现实是有差距的,很多属性会影响渲染位图的大小,比如阴影,它可能非常巨大,或者渲染到非常遥远的位置,所以为了优化,浏览器实际的实现中会把阴影作为一个独立的盒来处理。

注意,我们这里讲的渲染过程,是不会把子元素绘制到渲染的位图上的,这样,当父子元素的相对位置发生变化时,可以保证渲染的结果能够最大程度被缓存,减少重新渲染。

合成

合成是英文术语 compositing 的翻译,这个过程实际上是一个性能考量,它并非实现浏览器的必要一环。

我们上一小节中讲到,渲染过程不会把子元素渲染到位图上面,合成的过程,就是为一些元素创建一个“合成后的位图”(我们把它称为合成层),把一部分子元素渲染到合成的位图上面

看到这句话,我想你一定会问问题,到底是为哪些元素创建合成后的位图,把哪些子元素渲染到合成的位图上面呢?

那么好的合成策略是什么呢,好的合成策略是“猜测”可能变化的元素,把它排除到合成之外。

绘制

绘制是把“位图最终绘制到屏幕上,变成肉眼可见的图像”的过程,不过,一般来说,浏览器并不需要用代码来处理这个过程,浏览器只需要把最终要显示的位图交给操作系统即可。

一般最终显式的位图位于显存中,也有一些情况下,浏览器只需要把内存中的一张位图提交给操作系统或者显式驱动就可以了,这取决于浏览器运行的环境。不过无论如何,我们把任何位图合成到这个“最终位图”的操作称为绘制。

这个过程听上去非常简单,这是因为在前面两个小节中,我们已经得到了每个元素的位图,并且对它们部分进行了合成,那么绘制过程,实际上就是按照 z-index 把它们依次绘制到屏幕上

然而如果在实际中这样做, 会带来极其糟糕的性能。

有一个一度非常流行于前端群体的说法,讲做 CSS 性能优化,应该尽量避免"重排"和"重绘",前者讲的是我们上一课的排版行为,后者模糊地指向了我们本课程三小节讲的三个步骤,而实际上,这个说法大体不能算错,却不够准确。

因为,实际上,“绘制”发生的频率比我们想象中要高得多。我们考虑一个情况:鼠标划过浏览器显示区域。这个过程中,鼠标的每次移动,都造成了重新绘制,如果我们不重新绘制,就会产生大量的鼠标残影。

这个时候,限制绘制的面积就很重要了。如果鼠标某次位置恰巧遮盖了某个较小的元素,我们完全可以重新绘制这个元素来完成我们的目标,当然,简单想想就知道,这种事情不可能总是发生的。

计算机图形学中,我们使用的方案就是“脏矩形”算法,也就是把屏幕均匀地分成若干矩形区域。

当鼠标移动、元素移动或者其它导致需要重绘的场景发生时,我们只重新绘制它所影响到的几个矩形区域就够了。比矩形区域更小的影响最多只会涉及 4 个矩形,大型元素则覆盖多个矩形。

设置合适的矩形区域大小,可以很好地控制绘制时的消耗。设置过大的矩形会造成绘制面积增大,而设置过小的矩形则会造成计算复杂。

我们重新绘制脏矩形区域时,把所有与矩形区域有交集的合成层(位图)的交集部分绘制即可。

总结

在这一节课程中,我们讲解了浏览器中的位图操作部分,这包括了渲染、合成和绘制三个部分。

  • 渲染过程把元素变成位图
  • 合成把一部分位图变成合成层
  • 绘制过程把合成层显示到屏幕上

当绘制完成时,就完成了浏览器的最终任务,把一个 URL 最后变成了一个可以看的网页图像。当然了,我们对每一个部分的讲解,都省略了大量的细节,比如我们今天讲到的绘制,就有意地无视了滚动区域。


22 | 浏览器DOM:你知道HTML的节点有哪几种吗?

DOM API 介绍

顾名思义,文档对象模型是用来描述文档,这里的文档,是特指 HTML 文档(也用于 XML 文档,但是本课不讨论 XML)。同时它又是一个“对象模型”,这意味着它使用的是对象这样的概念来描述 HTML 文档。

  • 节点:DOM 树形结构中的节点相关 API。
  • 事件:触发和监听事件相关 API。
  • Range:操作文字范围相关 API。
  • 遍历:遍历 DOM 需要的 API。

节点

Node

Node 是 DOM 树继承关系的根节点,它定义了 DOM 节点在 DOM 树上的操作,首先,Node 提供了一组属性,来表示它在 DOM 树中的关系,它们是:

  • parentNode
  • childNodes
  • firstChild
  • lastChild
  • nextSibling
  • previousSibling

从命名上,我们可以很清晰地看出,这一组属性提供了前、后、父、子关系,有了这几个属性,我们可以很方便地根据相对位置获取元素。当然,Node 中也提供了操作 DOM 树的 API,主要有下面几种。

  • appendChild
  • insertBefore
  • removeChild
  • replaceChild

到此为止,Node 提供的 API 已经可以很方便(大概吧)地对树进行增、删、遍历等操作了。

除此之外,Node 还提供了一些高级 API,我们来认识一下它们。

  • compareDocumentPosition 是一个用于比较两个节点中关系的函数。
  • contains 检查一个节点是否包含另一个节点的函数。
  • isEqualNode 检查两个节点是否完全相同。
  • isSameNode 检查两个节点是否是同一个节点,实际上在 JavaScript 中可以用“===”。
  • cloneNode 复制一个节点,如果传入参数 true,则会连同子元素做深拷贝。

DOM 标准规定了节点必须从文档的 create 方法创建出来,不能够使用原生的 JavaScript 的 new 运算。于是 document 对象有这些方法。

  • createElement
  • createTextNode
  • createCDATASection
  • createComment
  • createProcessingInstruction
  • createDocumentFragment
  • createDocumentType

Element 与 Attribute

首先,我们可以把元素的 Attribute 当作字符串来看待,这样就有以下的 API:

  • getAttribute
  • setAttribute
  • removeAttribute
  • hasAttribute

如果你追求极致的性能,还可以把 Attribute 当作节点:

  • getAttributeNode
  • setAttributeNode

此外,如果你喜欢 property 一样的访问 attribute,还可以使用 attributes 对象,比如 document.body.attributes.class = “a” 等效于 document.body.setAttribute(“class”, “a”)。

查找元素

document 节点提供了查找元素的能力。比如有下面的几种。

  • querySelector
  • querySelectorAll
  • getElementById
  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName

我们需要注意,getElementById、getElementsByName、getElementsByTagName、getElementsByClassName,这几个 API 的性能高于 querySelector。

而 getElementsByName、getElementsByTagName、getElementsByClassName 获取的集合并非数组,而是一个能够动态更新的集合。

遍历

前面已经提到过,通过 Node 的相关属性,我们可以用 JavaScript 遍历整个树。实际上,DOM API 中还提供了 NodeIterator 和 TreeWalker 来遍历树。

Range

Range API 是一个比较专业的领域,如果不做富文本编辑类的业务,不需要太深入。这里我们就仅介绍概念和给出基本用法的示例,你只要掌握即可。

命名空间

在 HTML 场景中,需要考虑命名空间的场景不多。最主要的场景是 SVG。创建元素和属性相关的 API 都有带命名空间的版本:

  • document
    • createElementNS
    • createAttributeNS
  • Element
    • getAttributeNS
    • setAttributeNS
    • getAttributeNodeNS
    • setAttributeNodeNS
    • removeAttributeNS
    • hasAttributeNS
    • attributes.setNamedItemNS
    • attributes.getNamedItemNS
    • attributes.removeNamedItemNS

若要创建 Document 或者 Doctype,也必须要考虑命名空间问题。DOM 要求从 document.implementation 来创建。

  • document.implementation.createDocument
  • document.implementation.createDocumentType

除此之外,还提供了一个快捷方式,你也可以动手尝试一下。

  • document.implementation.createHTMLDocument

25 | 浏览器CSSOM:如何获取一个元素的准确位置

随着学习的深入,我才知道,这样的设计是有背后的逻辑的,正如 HTML 和 CSS 分别承担了语义和表现的分工,DOM 和 CSSOM 也有语义和表现的分工

DOM 中的所有的属性都是用来表现语义的属性,CSSOM 的则都是表现的属性,width 和 height 这类显示相关的属性,都属于我们今天要讲的 CSSOM。

CSSOM 是 CSS 的对象模型,在 W3C 标准中,它包含两个部分:描述样式表和规则等 CSS 的模型部分(CSSOM),和跟元素视图相关的 View 部分(CSSOM View)。

CSSOM

获取文档中所有的样式表:

document.styleSheets

document 的 styleSheets 属性表示文档中的所有样式表,这是一个只读的列表,我们可以用方括号运算符下标访问样式表,也可以使用 item 方法来访问,它有 length 属性表示文档中的样式表数量。

CSSOM View

CSSOM View 这一部分的 API,可以视为 DOM API 的扩展,它在原本的 Element 接口上,添加了显示相关的功能,这些功能,又可以分成三个部分:窗口部分,滚动部分和布局部分,下面我来分别带你了解一下。

窗口 API

窗口 API 用于操作浏览器窗口的位置、尺寸等。

滚动 API

要想理解滚动,首先我们必须要建立一个概念,在 PC 时代,浏览器可视区域的滚动和内部元素的滚动关系是比较模糊的,但是在移动端越来越重要的今天,两者必须分开看待,两者的性能和行为都有区别。

视口滚动 API

可视区域(视口)滚动行为由 window 对象上的一组 API 控制,我们先来了解一下:

元素滚动 API

接下来我们来认识一下元素滚动 API,在 Element 类(参见 DOM 部分),为了支持滚动,加入了以下 API。

布局 API

最后我们来介绍一下布局 API,这是整个 CSSOM 中最常用到的部分,我们同样要分成全局 API 和元素上的 API。

全局尺寸信息

window 对象上提供了一些全局的尺寸信息,它是通过属性来提供的,我们一起来了解一下来这些属性。

元素的布局信息

实际上,我们首先应该从脑中消除“元素有宽高”这样的概念,我们课程中已经多次提到了,有些元素可能产生多个盒,事实上,只有盒有宽和高,元素是没有的。

所以我们获取宽高的对象应该是“盒”,于是 CSSOM View 为 Element 类添加了两个方法:

  • getClientRects();
  • getBoundingClientRect()。

36 | 浏览器事件:为什么会有捕获过程和冒泡过程?

事件概述

  • 键盘;
  • 鼠标;
  • 触摸屏。

这其中,触摸屏和鼠标又有一定的共性,它们被称作 pointer 设备,所谓 pointer 设备,是指它的输入最终会被抽象成屏幕上面的一个点。

我们现代的 UI 系统,都源自 WIMP 系统。WIMP 即 Window Icon Menu Pointer 四个要素,它最初由施乐公司研发,后来被微软和苹果两家公司应用在了自己的操作系统上(关于这个还有一段有趣的故事,我附在文末了)。

WIMP 是如此成功,以至于今天很多的前端工程师会有一个观点,认为我们能够“点击一个按钮”,实际上并非如此,我们只能够点击鼠标上的按钮或者触摸屏,是操作系统和浏览器把这个信息对应到了一个逻辑上的按钮,再使得它的视图对点击事件有反应。这就引出了我们第一个要讲解的机制:捕获与冒泡。

捕获与冒泡

  • 捕获过程是从外向内,
  • 冒泡过程是从内向外。

我们刚提到,实际上点击事件来自触摸屏或者鼠标,鼠标点击并没有位置信息,但是一般操作系统会根据位移的累积计算出来,跟触摸屏一样,提供一个坐标给浏览器。

那么,把这个坐标转换为具体的元素上事件的过程,就是捕获过程了。而冒泡过程,则是符合人类理解逻辑的:当你按电视机开关时,你也按到了电视机。

所以我们可以认为,捕获是计算机处理事件的逻辑,而冒泡是人类处理事件的逻辑。

在我们实际监听事件时,我建议这样使用冒泡和捕获机制:默认使用冒泡模式,当开发组件时,遇到需要父元素控制子元素的行为,可以使用捕获机制。

addEventListener 有三个参数:

  • 事件名称;
  • 事件处理函数;
  • 捕获还是冒泡。

焦点

我们讲完了 pointer 事件是由坐标控制,而我们还没有讲到键盘事件。

键盘事件是由焦点系统控制的,一般来说,操作系统也会提供一套焦点系统,但是现代浏览器一般都选择在自己的系统内覆盖原本的焦点系统。

焦点系统认为整个 UI 系统中,有且仅有一个“聚焦”的元素,所有的键盘事件的目标元素都是这个聚焦元素。

Tab 键被用来切换到下一个可聚焦的元素,焦点系统占用了 Tab 键,但是可以用 JavaScript 来阻止这个行为。

浏览器 API 还提供了 API 来操作焦点,如:

document.body.focus();

document.body.blur();

其实原本键盘事件不需要捕获过程,但是为了跟 pointer 设备保持一致,也规定了从外向内传播的捕获过程。

自定义事件

除了来自输入设备的事件,还可以自定义事件,实际上事件也是一种非常好的代码架构,但是 DOM API 中的事件并不能用于普通对象,所以很遗憾,我们只能在 DOM 元素上使用自定义事件。

总结

捕获与冒泡机制来自 pointer 设备输入的处理,捕获总是在冒泡之前发生。

捕获是计算机处理输入的逻辑,

冒泡是人类理解事件的思维,

焦点机制则来自操作系统的思路,用于处理键盘事件。

除了我们讲到的这些,随着输入设备的不断丰富,还有很多新的事件加入,如 Geolocation 和陀螺仪等。


37 | 浏览器API(小实验):动手整理全部API

JavaScript 中规定的 API

大部分的 API 属于 Window 对象(或者说全局对象),我们可以用反射来看一看现行浏览器中已经实现的 API。

我们首先调用 Object.getOwnPropertyNames(window)。

DOM 中的元素构造器

接下来我们看看已经讲过的 DOM 部分,DOM 部分包含了 document 属性和一系列的构造器,我们可以用 JavaScript 的 prototype 来过滤构造器。

Window 对象上的属性

这里有一个 Window 接口,是使用 WebIDL 定义的,我们手工把其中的函数和属性整理出来,如下:

window,self,document,name,location,history,customElements,locationbar,menubar, personalbar,scrollbars,statusbar,toolbar,status,close,closed,stop,focus, blur,frames,length,top,opener,parent,frameElement,open,navigator,applicationCache,alert,confirm,prompt,print,postMessage

其它属性

这些既不属于 Window 对象,又不属于 JavaScript 语言的 Global 对象的属性,它们究竟是什么呢?

ECMAScript 2018 Internationalization API

在我的浏览器环境中,第一个属性是:Intl。

查找这些属性来历的最佳文档是 MDN,当然,你也可以使用 Google。

Streams 标准

ByteLengthQueuingStrategy

WebGL

WebGLContext​Event

Web Audio API

WaveShaperNode。这个属性名听起来就跟声音有关,这个属性来自 W3C 的 Web Audio API 标准。

Encoding 标准

TextDecoder,经过查阅得知,这个属性也来自一份 WHATWG 的标准

Web Background Synchronization

SyncManager,这个属性比较特殊,它并没有被标准化,但是我们仍然可以找到它的来源文档

Web Cryptography API

SubtleCrypto,这个属性来自 Web Cryptography API,也是 W3C 的标准。

Media Source Extensions

SourceBufferList

The Screen Orientation API

ScreenOrientation,它来自 W3C 的 The Screen Orientation API 标准

结语

到 Screen Orientation API,我这里看到还剩 300 余个属性没有处理,剩余部分,我想把它留给大家自己来完成。

我们可以看到,在整理 API 的过程中,我们可以找到各种不同组织的标准,比如:

  • ECMA402 标准来自 ECMA;
  • Encoding 标准来自 WHATWG;
  • WebGL 标准来自 Khronos;
  • Web Cryptography 标准来自 W3C;
  • 还有些 API,根本没有被标准化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值