JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。

事件流

从页面中接收事件的顺序称为事件流。

IE --> 事件冒泡流

Netscape --> 事件捕获流

查看源码:DOM2事件-捕获-冒泡

事件冒泡

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

我们先来个简单的例子,这是HTML结构


<!DOCTYPE html> <html lang="en"> <head> <title>js事件流</title> </head> <body> <div id="div"> 我是div </div> </body> </html>

有一个div元素。如果我们点击<div>元素,那么这个click事件的顺序会怎么呢?

我们给几个元素都添加监听事件,

element.addEventListener(event, function, useCapture)

参数说明:

  • event 字符串。指定事件名,比如 click、mouseenter、mouseleave
  • function 函数。指定要事件触发时执行的函数。
  • useCapture 布尔值。指定事件是否在捕获或冒泡阶段执行。默认值是false,即事件在冒泡阶段执行。true,在捕获阶段执行

var div = document.getElementById('div') var body = document.body var html = document.documentElement div.addEventListener('click', function () { console.log('div标签') }, false) body.addEventListener('click', function () { console.log('body ') }, false) html.addEventListener('click', function () { console.log('html') }, false) document.addEventListener('click', function () { console.log('document') }, false)

然后点击<div>元素,查看控制台输出,

由上面的输出结果可以看出,这个click事件会按照如下顺序传播:

<div> ---> <body>---> <html> --->document

也就是说,click事件首先发生在目标元素,然后,click事件沿着DOM树向上传播到document对象。这就是事件冒泡。

所有现代浏览器都支持事件冒泡。IE9、Firefox、Chrome和Safari则将事件一直冒泡到window对象。

如果,我们在每个DOM元素上都设置监听事件,会得到的事件的传播顺序是:

<div> ---><body> ---> <html> --->document ---> window

事件捕获

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

我们还是可以看刚才的例子,DOM结构不变,我们修改下监听事件


var div = document.getElementById('div') var body = document.body var html = document.documentElement div.addEventListener('click', function () { console.log('div标签') }, true) body.addEventListener('click', function () { console.log('body ') }, true) html.addEventListener('click', function () { console.log('html') }, true) document.addEventListener('click', function () { console.log('document') }, true)

之后,还是点击<div>元素,得到的结果

从上面例子可以看出,事件走向: document ---><html> ---> <body> ---><div>

也就是说,在事件捕获过程中,document对象首先接收到click事件,然后事件沿着DOM树依次向下,一直传播到事件的实际目标,即<div>元素。这就是事件捕获。与事件冒泡过程,截然相反。

尽管“DOM2级事件”规范要求事件应该从document对象开始传播,但是现代浏览器大部分都是从window对象开始捕获事件的。

如果,我们在每个DOM元素上都设置监听事件,会得到的事件的传播顺序是:

window ---> document ---><html> ---> <body> ---><div>

由于在老版本的浏览器中不支持,因此事件捕获用的人比较少,除非在特殊需要的时候才使用。

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件作出响应。



  • 事件捕获阶段:在DOM事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到<html>再到<body>后就停止了。

  • 处于目标阶段:事件在<div>上发生,并在事件处理中被看成冒泡阶段的一部分。

  • 事件冒泡阶段:冒泡阶段发生,事件又传播回文档。

虽然,“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但是IE9及以上的现代浏览器都会在捕获阶段触发事件目标对象上的事件。结果,就是有两个机会在目标对象上面操作事件,也就是说上图中的步骤4,既可以在捕获阶段发生,也可以在冒泡阶段发生。

IE8级更早版本不支持DOM事件流,现代浏览器都支持DOM事件流。

事件流的应用

事件流比较典型应用是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理一类型的所有事件。

我们查看一个常用例子,这是一个无序列表的DOM结构:


<ul> <li id="li1">我是第1个li</li> <li id="li2">我是第2个li</li> <li id="li3">我是第3个li</li> </ul>

我们的需求是,点击不同列,输出不同的消息。

  • 第一种做法:给每个<li>添加点击事件,这样能分别处理事件,展示不同的内容。

document.getElementById('li1').addEventListener('click', function(e) { console.log('我是第一个li') }, false) document.getElementById('li2').addEventListener('click', function(e) { console.log('我是第2个li') }, false) document.getElementById('li3').addEventListener('click', function(e) { console.log('我是第3个li') }, false)

单击每一个<li>,会输出对应的内容。

  • 第二种做法:给<li>元素的父元素<ul>添加一个处理事件,

document.querySelector('ul').addEventListener('click', function (e) { console.log(e.target.innerText) }, false)

单击每一个<li>,会展示不同的<li>中文本元素内容。

在这段代码中,我们只为<ul>元素添加了一个onclick事件处理程序。由于所有<li>都是<ul>元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被这个函数处理。

以上两种方式,第二种所具有的优势:

  1. 事前消耗更低。因为只取得了一个DOM元素,只添加了一个事件处理程序。

  2. 占用的内存更少。每个函数都是对象,都会占用内存。

  3. 性能更优。 内存中的对象越多,性能就越差。

  4. 如果以后要增减<li>元素,也不用修改事件方法,可以获取相同的处理结果。

所以,比较推荐使用第二种方式。

最适合采用事件委托技术的事件包括clickmousedownmouseupkeydownkeyupkeypress。虽然mouseovermouseout事件也冒泡,但要适当处理它们并不容易,而且经常要计算元素的位置。

参考资料:

JavaScript高级程序设计(第三版)- 第13章 事件

查看源码:DOM2事件-捕获-冒泡

js事件流机制冒泡和捕获的更多相关文章

  1. Dom事件流、冒泡、捕获

    Dom事件流 dom的结构是一个倒立的树状结构.当一个html元素触发事件时,事件会在dom的根节点和触发事件的元素节点之间传播,中间的节点都会收到该事件. 捕获:div元素触发事件时,事件先从根节点 ...

  2. js 事件流和事件冒泡阻止

    js 事件流和事件冒泡阻止 事件流 当浏览器发展到第四代的时候(IE4与Netscape4)浏览器开发团队遇到一个有意思的的问题: 页面的哪一部分会拥有某个特定的事件? 比如在纸上画上一组同心圆,如果 ...

  3. [转]as3事件流机制彻底理解

    题记: 看过网上一些as3事件流的教程,觉得大多都讲得不甚清楚,让人不能直观的理解事件流.而这篇教程以将事件流过程比喻成捕鱼过程,形象简单. 在此基础上对于as3事件流总算有了全面的理解.事件流机制说 ...

  4. 一次关于js事件出发机制反常的解决记录

    起因:正常情况下我点击s2时是先弹出我是children,再弹出我是father,但是却出现了先弹出我是father,后弹出我是children的情况,这种情况是在和安卓app交互的h5页面中出现的, ...

  5. js 事件详解 冒泡

    起因:正常情况下我点击s2时是先弹出我是children,再弹出我是father,但是却出现了先弹出我是father,后弹出我是children的情况,这种情况是在和安卓app交互的h5页面中出现的, ...

  6. JS事件流、事件监听、事件对象、事件委托

    JS事件流: 01.DOM级别和DOM事件 02.JS事件流:页面中接收事件的顺序 事件冒泡阶段-->处于目标阶段-->事件捕获阶段 (事件捕获总发生在事件冒泡前面) 03.捕获:从外向里 ...

  7. JS事件流模型

    JS事件流模型 事件捕获Event Capturing是一种从上而下的传播方式,以click事件为例,其会从最外层根节向内传播到达点击的节点,为从最外层节点逐渐向内传播直到目标节点的方式. 事件冒泡E ...

  8. js事件流、事件处理程序/事件侦听器

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

  9. JS事件流理解

    事件是用户或浏览器自身执行的某种动作,如click,load和mouseover都是事件的名字. 事件是javaScript和DOM之间的桥梁. 你若触发,我便执行--事件发生,调用它的处理函数执行相 ...

随机推荐

  1. [Storage]RPM series linux rescan disk / RPM系Linux重新扫描硬盘

    echo "- - -" > /sys/class/scsi_host/host0/scan echo "- - -" > /sys/class/s ...

  2. python中的sequence(序列)

    摘要 这篇文章主要是为了让自己记住字典不是序列,python中序列的类型 序列化的定义 有个朋友问我,什么是序列化,我瞬间懵了,然后查了一下,发现廖雪峰老师给出了一个很舒服的解释: 序列化:我们把变量 ...

  3. git合并常见冲突

    如果一个文件在服务器上已经做了修改,然后在本地开发中又做了一些修改的时候,再发布这个文件时很容易造成代码冲突,错误如下, error: Your local changes to the follow ...

  4. linux下实用的快速随机生成复杂密码

    linux下实用的快速随机生成复杂密码 [root@test.db-audit.1 ~]# </dev/urandom tr -dc '1234567890!@#$%abcdefghigklmn ...

  5. JS基础-第5天

    复习函数 函数 作用:封装一段代码,封装的功能可以被反复调用执行. 定义函数 // 第一种 - 函数声明 function 函数名(){ } // 第二种 - 函数表达式 var 函数名 = func ...

  6. 2018-2019-2 《Java程序设计》第7周学习总结

    20175319 2018-2019-2 <Java程序设计>第7周学习总结 教材学习内容总结 本周学习<Java程序设计>第8章: 1.String类: Java专门提供了用 ...

  7. numpy&pandas补充常用示例

    Numpy [数组切片] In [115]: a = np.arange(12).reshape((3,4)) In [116]: a Out[116]: array([[ 0, 1, 2, 3], ...

  8. JavaScript传递参数方法

    1.SetTimer传递参数 setTimeout(function (obj) { obj.myScroll = new IScroll('#wrapper', { click: true }); ...

  9. 推送提交(git push)

    当需要同别人共享某个分支上的工作成果时,就要把它推送到一个具有写权限的远程仓库.你的本地分支并不会自动同步到远程仓库,必须要显式地推送那些你想要与别人共享的分支.这样一来,你可以使用私有分支做一些不想 ...

  10. 记一次解决netty半包问题的经历

    最近学习了netty,想写一个简单的rpc,结果发现发送消息时遇到难题了,网上搜了一下,这种情况是半包问题和粘包问题,主要是出现在并发高一些的时候. talk is cheap 客户端编码: prot ...