当前位置:网站首页 > 网站建设教程 > HTML教程 > 正文

浏览器内核之 HTML 解释器和 DOM 模型(下)

教程管理员 发布于2023-09-28 13:53 HTML教程 152

简介: 浏览器内核之 HTML 解释器和 DOM 模型(下)

1.3 DOM 事件机制


1.3.1 事件的工作过程


事件在工作过程中使用两个主体,第一个是事件(event),第二个是事件目标(EventTarget)。WebKit 中用 EventTarget 类来表示 DOM 规范中 Events 部分定义的事件目标。


每个 事件都有属性来标记该事件的事件目标。当事件到达事件目标(如一个元素节点)的时候,在这个目标上注册的监听者(Event Listeners)都会有触发调用,而这些监听者的调用顺序不是固定的,所以不能依赖监听者注册的顺序来决定你的代码逻辑。

图 5-17 是 EventTarget 接口的定义。图中的接口是用来注册和移除监听者的。


微信图片_20220512131421.png


事件处理最重要就是事件捕获(Event capture)和事件冒泡(Event bubbling)这两种机制。图 5-18 是事件捕获和事件冒泡的过程。


微信图片_20220512131435.png


当渲染引擎接收到一个事件的时候,它会通过 HitTest(WebKit 中的一种检查触发gkwrd哪个区域的算法)检查哪个元素是直接的事件目标。在图 5-18 中,以 “img” 为例,假设它是事件的直接目标,这样,事件会经过自顶向下和自底向上的两个过程。


事件的捕获是自顶向下,事件先是到 document 节点,然后一路到达目标节点。在图 5-18 中,顺序就是 “#document” -> "HTML" -> "body" -> "img" 这样一个顺序。事件可以在这一传递过程中被捕获,只需要在注册监听者的时候设置相应参数即可。默认情况下,其他节点不捕获这样的事件。如果网页注册了这样的监听者,那么监听者的回调函数会被调用,函数可以通过事件的 “stopPropagation” 函数来阻止事件向下传递。


事件的冒泡过程是从下向上的顺序,它的默认行为是不冒泡,但是是事件包含一个是否冒泡的属性。当这一属性为真的时候,渲染引擎会将该事件首先传递给事件的目标节点的父亲,然后是父亲的父亲,以此类推。同捕获动作一样,这此监听函数也可以使用 “stopPropagation” 函数来阻止事件向上传递。


1.3.2 WebKit 的事件处理机制


DOM 的事件分为很多种,与用户相关的只是其中的一种,称为 UIEvent ,其他的包括 CustomEvent、MutationEvent 等。UIEvent 又可以分为很多种,包括但是不限于 FocusEvent、MouseEvent、KeyboardEvent、Composition 等。


基于 WebKit 的浏览器事件处理过程,首先是做 HitTest ,查找事件发生处的元素,检查该元素有无监听者。如果网页的相关节点注册了事件的监听者,那么浏览器会把事件派发给 WebKit 内核来处理。同时,浏览器也可能需要理解和处理这样的事件。这主要是因为,有些事件浏览器必须响应从而对网页作默认处理。


EventHandler 类是处理事件的核心类,它除了需要将各种事件传给 JavaScript 引擎以调用响应的监听者之外,它还会识别鼠标事件,来触发调用右键菜单、拖放效果等与事件密切相关的工作,而且 EventHandler 类还支持网页的多框结构。EventHandler 类的接口比较容易理解,但是它的处理逻辑极其复杂。


微信图片_20220512131545.png


图 5-20 简单描述了鼠标事件的调用过程,这一过程本身是比较简单的,复杂之处在于 WebKit 的 EventHandler 类。


WebKit 中还有些跟事件处理相关的其他类,例如 EventPathWalker、EventDispatcher 类等,这些类都是为了解决事件在 DOM 树中传递的问题。


1.4 影子(Shadow)DOM


影子 DOM 是一个新东西,主要解决了一个文档中可能需要大量交互的多个 DOM 树建立和维护各自的功能边界的问题。


1.4.1 什么是影子 DOM


当开发这样一个用户界面的控件——这个控件可能由一些 HTML 的标签元素组成,这些元素可以组成一颗 DOM 树的子树。这样一个 HTML 控件可以被到处使用,但是问题随之而来,那就是每个使用控件的地方都会知道这个子树的结构。


当网页的开发者需要访问网页 DOM 树的时候,这些控件内部的 DOM 子树都会暴露出来,这些暴露的节点不仅可能给 DOM 树的遍历带来很多麻烦,而且也可能给 CSS 的样式选择带来问题,因为选择器无意中可能会改变这些内部节点的样式,从而导致很奇怪的控件界面。


如何将内部的节点信息封装起来,就像 C++ 语言的类一样,同时又能够将这些节点渲染出来呢 ? W3C 工作组提出的影子 DOM 概念。影子 DOM 的规范草案能够使得一些 DOM 节点在特定范围内可见,而在网页的 DOM 树中却不可见,但是网页渲染的结果中包含了这些节点,这就使得封装变得容易很多。


图 5-21 描述了 HTML 文档对应的 DOM 树和 “div” 元素包含的一个影子 DOM 子树。当使用 JavaScript 代码访问 HTML 文档的 DOM 树的时候,通常的接口是不能直接访问到影子 DOM 子树中的节点的,JavaScript 代码只能通过特殊的接口方式。


微信图片_20220512131619.png


HTML5 支持了很多新的特性,例如对视频、音频的支持,读者会发现这些元素其实是由很复杂的控制界面组成,这些界面也是使用 HTML 元素编写,但是在 DOM 树中,你无法找到相应的节点,这其实也是使用了影子 DOM 的思想。


因为影子 DOM 的子树在整个网页的 DOM 树中不可见,那么事件是如何处理的呢 ?事件中需要包含事件目标,这个目标当然不能是不可见的 DOM 节点,所以事件目标其实就是包含影子 DOM 子树的节点对象。事件捕获的逻辑没有发生变化,在影子 DOM 子树内也会继续传递。当影子 DOM 子树中的事件向上冒泡的时候, WebKit 会同时向整个文档的 DOM 上传递该事件,以避免一些很奇怪的行为。


1.4.2 WebKit 的支持


WebKit 已经支持影子 DOM 的规范草案,虽然还存在一些问题。支持影子 DOM 的相关类在目录 “Source/core/dom/shadow” 下,里面的主要类是 ShadowRoot ,表示的是影子 DOM 的根节点。ShadowRoot 类继承自 DocumentFragment 类,所以它同样有 Node 节点的属性和方法,因而在影子 DOM 树的内部,遍历树没有什么特别不同的地方。


当遍历 HTML 文档对应 DOM 树的时候,WebKit 需要做特别的判断,所以读者会发现在 WebKit 的 Node 类实现中存在大量的条件语句,用来检查当前节点是否是 ShadowRoot 对象,如果是该类的对象,把它作为不同 DOM 树之间的边界。有时候 WebKit 还需要对 ShadowRoot 对象作出特别处理,比如某些情况会略过它的子树,同样的,在事件处理的支持类 EventPathWalker 和 EventRetargeter 中,也需要做一些特别的处理逻辑,原理就是上面所述,细节不再介绍。


1.4.3 实践:使用影子 DOM


示例代码 5-2 给出了一个简单的使用 webkitCreateShadowRoot 接口来创建影子 DOM 子树的例子。网页只包含了一个 “div” 元素,JavaScript 代码使用该元素创建了一个影子 DOM 子树的根节点,然后该根节点下加入了两个子女,第一个是图片元素,第二个是 “div” 元素,该元素内部包含了一些文本。


微信图片_20220512131654.png


读者可以打开 Chrom 浏览器的开发者工具,然后打开控制台,在其中输入 “document.firstChild.firstChild.nextElementSibling.firstElementChild.firstElementChild” 后会发现结果是空的,根据对应关系 “#document-> html -> head -> body -> div -> null”,虽然网页中没有 ‘head’ 元素,但是 DOM 树仍然会创建该节点。同时读者会发现 “div” 元素没有子女,影子 DOM 子树真的被隐藏起来了,成为真正的影子。


琼ICP备09004296号-12