一、DOM事件机制
1. 事件的工作过程
事件在工作过程中使用两个主体,第一个是事件(event),第二个是事件目标(EventTarget)。每个事件都有属性来标记该事件的事件目标。当事件到达事件目标的时候,在这个目标上注册的监听者(EventListeners)都会被触发调用,当然这些监听者的调用顺序是不固定的,所以不能依赖监听者注册的顺序来决定你的代码逻辑。
事件处理最重要的部分就是事件捕获(Event capture)和事件冒泡(Event bubbling)这两种机制。如下图:
当渲染引擎接收到一个事件的时候,它会通过HitTest(Webkit中的一种检查触发事件在哪个区域的算法)检查哪个元素是直接的事件目标。在上图中,以“img”元素为例,假设它是事件的直接目标,这样,事件会经过自顶向下和自底向上两个过程。
事件的捕获是自顶向下,这就是说,事件是先到document节点,然后一路到达目标节点。在上图中的顺序就是“#document-->html-->body-->img
”。事件可以在这一传递过程中被捕获,只需要在注册监听函数的时候设置相应的参数即可。addEventListener
的第三个参数就是表示这个含义。默认情况下,其他节点不捕获这样的事件。如果网页注册了这样的监听函数,那么监听函数的回调函数会被调用,函数可以通过事件的stopPropagation
函数来阻止事件向下传递。
事件的冒泡过程是自底向上,它的默认行为是不冒泡,但是事件包含一个时候冒泡的属性。当这一属性为真的时候,渲染引擎会将该事件首先传递给事件目标节点的父亲,然后是父亲的父亲,一次类推。同捕获动作一样,这些监听函数可以使用preventDefault
函数来阻止。
2. Webkit的事件处理机制
DOM的事件分为很多种,与用户相关的只是其中一种,称为UIEvent,其他的包括CustomEvent、MutationEvent等。UIEvent又可以分为很多种:FocusEvent、MouseEvent、KeyboardEvent、CompositionEvent等。
基于Webkit的浏览器事件处理过程,首先是做HitTest,查找事件发生处的元素,检测该元素有无监听者。如果网页的相关节点注册了事件的监听者,那么浏览器会把事件派发给Webkit内核来处理。同时,浏览器也有可能需要理解和处理这样的事件。这主要是因为有些事件浏览器必须响应,从而对网页作默认处理。比如用户使用鼠标翻滚网页。假如当前鼠标的位置就在一个元素之上,并且该元素注册了监听函数,就会导致竞争冲突,所以我们应该在监听代码中调用preventDefault
函数来阻止浏览器触发它的默认行为,也就是不需要滚动整个网页。
当事件的派发机制遇到网页的盒模型特别是很多个盒的时候,情况变得比较复杂,这是因为事件需要在多个盒子和多个DOM树之间传递。当触控事件(Touch Events)被引入之后,情况更复杂了。
最后再来看看之前提到的事件从浏览器到达Webkit内核之后,Webkit内部调用的过程,这一过程比较简单,主要是EventHandler类。EventHandler类是处理事件的核心类,它除了需要将各种事件传递给JavaScript引擎以调用相应的监听者之外,还会识别鼠标事件来触发调用右键菜单、拖放效果等工作。
二、影子(Shadow)DOM
影子DOM主要解决了一个HTML文档中可能需要大量交互的多个DOM建立和维护各自功能边界的问题。影子DOM 为Web组件中的DOM和CSS提供了封装,使得这些东西与主文档的DOM保持分离,也可以在一个Web组件外部使用影子DOM本身。
1. 什么是Shadow DOM
想象一下网页的基础库开发者想要开发这样一个用户界面的控件:
- 这个控件可能由一些HTML元素组成,
- 这些元素可以组成一颗DOM树的子树,
- 这个控件可以被到处使用。
问题随之而来,每个使用控件的地方都会知道这个子树的结构。当网页的开发者需要访问网页DOM的时候,这些控件内部的DOM子树就会暴露出来,这些控件内部的节点不仅可能会给DOM的遍历带来麻烦,而且也可能因为无意中被CSS选中而改变样式。
于是W3C提出了Shadow DOM,它可以使得一些DOM节点在特定的范围内可见,而在网页的DOM树中却不可见。这使得封装组件变得容易很多。当使用JavaScript代码访问HTML文档的DOM树的时候,普通的接口是不能直接访问到Shadow DOM中的节点的,JavaScript需要特殊的接口才能访问。具体可以看一下Shadow DOM文档
HTML5中有很多新特性,例如视频音频,我们会发现这些元素都会比较复杂,但是在DOM树中就只会看见audio和video标签,这其实就是使用了Shadow DOM的思想。
既然Shadow DOM在整个网页DOM树中不可见,那么事件如何处理呢?事件中需要包含事件目标,这个目标当然不能是不可见的节点,所以事件目标其实就是包含Shadow DOM子树的节点对象。事件捕获的逻辑没有变化,在Shadow DOM子树内也会继续传递。当Shadow DOM子树中事件向上冒泡的时候,Webkit会同时向整个文档的DOM上传递该事件,以避免一些奇怪的行为。