# 事件流和事件委托

  • 事件流:从页面中接收事件的顺序

  • IE和 Netscape 开发团队居然提出了差不多是完全相反的事件流的概念:

    • IE 的事件流是事件冒泡流
    • Netscape Communicator 的事件流是事件捕获流

# DOM事件流

# 1. 事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(document文档)。

所有现代浏览器都支持事件冒泡,但在具体实现上还有一些差别。IE 5.5 及更早的版本中的事件冒泡会跳过

元素。IE9、firefox、 Chrome 和 Safari 则将事件一直冒泡到 window 对象。

# 2. 事件捕获

Netscape Communicator 团队提出的另一种事件流叫做事件捕获(event capturing)。事件波活的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件事件捕获的用意在于它事件到达预定目标之前捕获它。

虽然事件捕获是 Netscape Communicator 唯一支持的事件流模型,但IE9、Safari、Chrome、Opera 和 Firefox 目前也支持这种事件流模型。进管DOM2 及事件规范要求事件应该从document 对象开始传播,但这些浏览器都是从 window 对象开始捕获事件的。

由于老版本的浏览器不支持,因此很少有人使用事件捕获。所以建议使用事件冒泡,在有特殊需要是在使用事件捕获

# 3. DOM2事件流

DOM2事件规范制定了事件流的三个阶段:捕获阶段目标阶段和冒泡阶段

  • 事件捕获(Capture Phase):事件从最外层的window对象到目标节点的父节点依次触发的阶段。(从外到内)

  • 目标阶段(Target Phase):事件在目标节点上触发时的阶段。

  • 事件冒泡(Bubbing Phase):事件从目标节点的父节点到最外层的window对象依次触发的阶段。(从内到外)

通过addEventListener()注册事件,此方法的第三个参数可设置本次注册是捕获阶段还是冒泡阶段,也就是说我们在注册事件时,可以设置事件的触发顺序。

# 事件处理

通过addEventListener() 方法添加的事件处理程序只能使用 removeEventListener() 来移除,移除时传入的参数与添加处理程序使用的参数相同。这也意味着通过 addEventListener() 添加的匿名函数将无法移除。

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候才将事件处理程序添加到捕获阶段。如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。

# 事件对象

属性/方法 类型 读/写 说明
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素。(绑定事件的元素)
target Element 只读 事件的目标。(触发事件的元素)
type String 只读 被触发的事件类型。
bubbles Boolean 只读 表明事件是否冒泡。
stopImmediatePropagation() Function 只读 取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用(DOM3 级事件中新增)
stopPropagation() Function 只读 取消事件的进一步捕获或冒泡。如果bubbles 为 true,则可以使用这个方法。
eventPhase Integer 只读 调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段。
cancelable Boolean 只读 表明是否可以取消事件的默认行为。
preventDefault() Function 只读 取消事件的默认行为。如果cancelable 是 true,则可以使用这个方法。
defaultPrevented Boolean 只读 为true 表示已经调用了 preventDefault() (DOM3 级事件中新增)。
detail Integer 只读 与事件相关的细节信息。
trusted Element 只读 为true 表示见见是浏览器生成的。为false 表示事件是由开发人员通过调用JavaScript创建的(DOM3级事件中新增)。
view AbstractView 只读 与事件关联的抽象视图。等同于发生事件的 window 对象。

# 事件类型

Web浏览器中可能发生的事件有很多类型。不同的事件类型具有不同的信息,而DOM3级事件规定了一下几类事件。

  • UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发。
  • 焦点事件,当元素获得或失去焦点时触发。
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发。
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发。
  • 文本事件,当在文档中输入文本时触发。
  • 键盘事件,当用户通过键盘在页面上执行操作时触发。
  • 合成事件,当为IME(Input Method Editor,输入法编辑器)输入字符时触发。
  • 变动(mutation)事件,当底层DOM结构发生变化是触发。
  • 变动名称事件,当元素或属性名变动时触发。此类事件已被废弃,没有任何浏览器实现它们。

除了这几类事件之外,HTML5 也定义了一组事件,而有些浏览器还会在DOM 和BOM 中实现其他专有事件。这些事件一般都是根据开发人员需求定制的,没有什么规范,因此不同浏览器实现有可能不一致。

# 事件委托

在JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,导致这一问题的原因是多方面的:

  • 每个函数都是对象,都会占用内存,内存中对象越多,性能就越差
  • 必须事先绑定所有事件,而导致对DOM访问次数过多,会延迟整个页面的交互就绪事件

因此就出现了事件委托,事件委托利用了事件冒泡——只指定一个事件处理程序,就可以管理某一类型的所有事件。也就是说使用事件委托,只需要在DOM树中尽量最高的层次上添加一个事件处理程序就可以了。

也可以考虑为document对象添加一个事件处理程序,最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup

例子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        ul {
            width: 300px;
            border: 1px dashed #f0f;
            margin: 50px auto;
            font-size: 24px;
            line-height: 48px;
            list-style: none;
        }
        li {
            padding-left: 20px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <ul id="list">
        <li>腾讯</li>
        <li>拉钩</li>
        <li>百度</li>
        <li>京东</li>
        <li>阿里</li>
    </ul>
    <script>
        // 让每个 li 被点击后,自己添加特殊的背景色,而其他兄弟不添加
        // 以前的思路:获取所有的 li 标签元素,批量添加事件
        // 事件委托:可以将一些子级的公共类型的事件委托给他们的父级添加,在父级内部想办法找到真正触发事件的最底层的事件源
        // 获取元素
        var list = document.getElementById("list");
        var lis = list.children;
        // 给 ul 添加点击事件
        list.onclick = function (e) {
            // 在内部要想办法找到真正触发事件的 li
            // 借用事件函数内部的一个参数 e,e 是事件对象
            // 只要触发事件,函数内部都可以得到一个事件对象,对象中存储了关于事件的一系列数据
            // e.target 属性记录的就是真正触发事件的事件源
            // 排除其他
            for (var i = 0 ; i < lis.length ; i++) {
                lis[i].style.backgroundColor = "";
            }
            e.target.style.backgroundColor = "pink";
        };
    </script>
</body>

</html>