Javascript中的事件

Javascript中的事件

Javascript与HTML的交互是通过事件实现的,事件代表文档或浏览器窗口中某个具有意义的时刻。可以使用仅在事件发生时执行的监听器(也叫处理程序)订阅事件。在传统软件工程领域,这个模型叫观察者模式,其能够做到页面行为(在JavaScript中定义)与页面展示(在HTML和CSS中定义)的分离。

事件流

当你点击一个按钮时,实际上不光点击了这个按钮,还点击了它的容器以及整个页面。

事件流描述了页面接收事件的顺序。IENetscape开发团队提出了几乎完全相反的事件流方案。IE将支持事件冒泡流,而Netscape Communicator将支持事件捕获流。

事件冒泡

IE事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树种最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。比如有如下HTML页面:

<!DOCTYPE html>
<html onclick="handleClick('html')">
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body onclick="handleClick('body')">
        <div class="box" onclick="handleClick('div')">
            <button onclick="handleClick('button')">点击</button>
        </div>
        <script>
            //处理点击事件
            function handleClick(e){
                console.log(e)
            }
            //监听document点击事件
            document.addEventListener("click",function(){
                console.log('document')
            })
        </script>
    </body>
</html>

在点击页面中的button元素后,click事件会以如下顺序发生

  1. button
  2. div
  3. body
  4. html
  5. document

QQ20220312-202131

也就是说,button元素,即被点击的元素,最先触发click事件。然后,click事件沿着DOM树一路向上,在经过的每个节点上依次触发,直至到达document对象。

所有现代浏览器都支持事件冒泡,只是在实现方式上会有一些变化。IE5.5及早期版本会跳过html元素(从body直接到document)。现代浏览器中的事件会一直冒泡到window对象。

事件捕获

Netscape Communicator 团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。

在事件捕获中,click事件首先由document元素捕获,然后沿DOM树依次向下传播,直至到达实际目标元素。

虽然这是Netscape Communicator 唯一的事件流模型,但事件捕获得到了所有现代浏览器的支持。实际上,所有浏览器都是从window对象开始捕获事件,而DOM2 Events 规范规定的是从document开始。

DOM事件流

DOM2 Events 规范规定事件流分为三个阶段:事件捕获到达目标事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡。最迟要在这个阶段响应事件。

在DOM事件流中,实际的目标在捕获阶段不会接收到事件。下一阶段,即会在实际元素上触发事件的“到达目标”阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传播至文档。

事件委托

事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件

比如有以下HTML:

<ul id="list">
    <li id="item1">1</li>
    <li id="item2">2</li>
    <li id="item3">3</li>
</ul>

这里的HTML包含3个列表项,在被点击时应该执行某个操作。对此,通常的做法是像这样指定3个事件处理程序

const item1 = document.getElementById('item1')
const item2 = document.getElementById('item2')
const item3 = document.getElementById('item3')

item1.addEventListener("click",function(event){
    //TODO
})
item2.addEventListener("click",function(event){
    //TODO
})
item3.addEventListener("click",function(event){
    //TODO
})

如果对页面中所有需要使用onclick事件处理程序的元素都如法炮制,结果就会出现大片雷同的指定事件处理程序的代码。使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序,就可以解决问题。比如:

const list = document.getElementById('list')
list.addEventListener("click",function(event){
    //event.target 可以获取事件的目标元素
    const target = event.target
    console.log(target)
})

使用事件委托的好处:

  • document对象随时可用,任何时候都可以给它添加事件处理程序(不用等待DOMContentLoaded或load事件)。这意味着只要页面渲染出可以点击的元素,就可以无延迟的起作用。
  • 节省花在设置页面事件处理程序上的事件。只指定一个事件处理程序即可以节省DOM引用,也可以节省时间。
  • 减少整个页面所需要的内存,提升整体性能。