理解Event冒泡模型
本文探索一下Event的冒泡过程和初学遇到的几个小bug
DOM Event概述
Event接口是检测在DOM中的发生的所有事件,我们一直在用,而且从DOM的很早的版本就一直在用着。早期的网景(后来的火狐)和IE是各自为战,直到W3C一统江湖,DOM版本一路发展而来,经历了DOM-0(洪荒时代)、DOM-1(只有两章核心内容)、DOM-2(划时代的一个版本,我们学的Event就在这个版本,而且目前的用的也是这个版本)、DOM-3、DOM-4(草案阶段)。
- 通过一个例子唤醒对Event的认识
//1、有一个js函数如下
function print(){
console.log(1)
}
//2、在html的button里面点击触发上面的函数
<button id=button onclick="?">点我</button>
//问号处填可以填什么 A. print() B.print C.print.call()
//在js里面的onclick里面触发
button.onclick = ?
//问号处可以填什么 A. print() B.print C.print.call()
- 很明显第一个问号应该选
AC,第二个问号应该选B - 第一处在HTM中,点击事件要立刻执行代码,肯定选择带
()的,而第二处在JS中,onclick是一个属性,不需要立刻执行,等用户点击了,浏览器再反应,不需要()。
既然onclick等on事件在JS中是一个属性,那么后面的就会覆盖前面的,所以DOM2里面引入了一个重要的EventListener,是一个队列。
addEventListener
这是一个队列,例子1,先进先出的特点,为后面的冒泡模型做准备。
function f(){
console.log("eventListener不会覆盖")
}
button2.addEventListener('click', function(){
console.log("eventListener不会覆盖1")
})
button2.addEventListener('click', f)
button2.removeEventListener('click', f)
button2.addEventListener('click', function(){
console.log("eventListener不会覆盖3")
})
- 会打印出什么呢,答案是
eventListener不会覆盖1eventListener不会覆盖3 - 所以说既然on可以一个打印出结果,就可以借助
remove来实现one执行一次的操作
function f(){
console.log("eventListener不会覆盖2")
button2.removeEventListener('click', f)
}
button2.addEventListener('click', f)
只会打印一次,不会一直打印了,也就是one的原理。
- 具体的模型可以看W3C
冒泡模型
上面的官方文档中,我只研究一下捕获阶段(capture phase)和冒泡阶段(bubbling phase)。
- 什么是冒泡呢?我们先看一段代码
grand.addEventListener('click', function(){
console.log('我是你爷爷')
})
dad.addEventListener('click', function(){
console.log('我是你爸爸')
})
son.addEventListener('click', function(){
console.log('我是你儿子')
})
这是三个
div的事件,当你点击的时候,控制台打印必然会有顺序。那么应该是什么顺序呢,正常人的思维不外乎两种结果- 第一种:我是你的儿子 我是你爸爸 我是你爷爷
- 第二种: 我是你爷爷 我是你爸爸 我是你儿子
- 到底是那种呢,W3C说都行,看你代码咋写的了,上面的代码打印顺序是第一个中,也就是冒泡。
- 如果你想实现第二种打印方式,也就是捕获阶段,应该修改代码如下
grand.addEventListener('click', function(){
console.log('我是你爷爷')
}, true)
dad.addEventListener('click', function(){
console.log('我是你爸爸')
}, true)
son.addEventListener('click', function(){
console.log('我是你儿子')
}, true)
- 也就是说
addEventListener后面的参数决定了顺序,当你不写的时候是undefined,也就是false的意思。 复习一下五个
falsey值0NaN''nullundefined除此之外都是true
上图是简单的图解,注意优先运行为true的部分,再运行false的部分。
简单的实例====================>demo
- 一个变式
grand.addEventListener('click', function(){
console.log('我是你爷爷')
}, true)
dad.addEventListener('click', function(){
console.log('我是你爸爸')
})
son.addEventListener('click', function(){
console.log('我是你儿子')
- 上述代码应该是什么顺序呢
- 谁是
true,先打印谁,都是false,继续按照冒泡顺序打印。
一个奇葩的问题
son.addEventListener('click', function(){
console.log('我是你儿子true')
}, true)
son.addEventListener('click', function(){
console.log('我是你儿子false')
})
- 给同一个元素
falsetrue,应该打印什么呢 - 答案是: 按照书写的顺序,谁在前面先打印谁。
意想不到的Bug
parent是关键字不能使用,一不小心使用的话会出问题。
- 你用了关键字做变量,把鼠标点烂也看不到效果。
点击空白,对话框消失的案例
- 领导说有一个需求,点击某个按钮,弹出对话框,点击空白会消失。
- 你的第一个思路:先把div设为none,点击按钮的时候,再让这个
div的display是block,点击其他地方变为none。 - 很好,你去实现一下吧。
第一个bug
很快你会碰到了第一个bug
- 第一个错误:监听错了对象
正常来说,应该点击body控制台打印数字1,你点烂了你的罗技鼠标也没出来。为什么呢?
- 我们使用border大法,看看它到底在哪
使用了红色border之后,发现body的高度太矮了,点击不到啊。
- 你明白监听错对象了,那你就换了一个对象,监听文档呗,肯定没问题了。
第二个bug
很好,你进入了第二个bug了
- 第二个bug:你都能点击到,但是弹不出对话框了
根据图片 中的控制台可以发现,确实都点击到了,监听没问题,而且点击后,也是按照冒泡的顺序打印的结果。
- 那为什么没有对话框了呢
注释掉出问题的代码后,上图是正常的点击出现对话框啊,说明问题就出在注释的代码上。
- bug出现的原因就在于:默认冒泡的影响,当你点击的浮层那个
div,之后,往bodydocument上冒泡,在document上立刻被杀死,display变为none,你做梦能看到 弹出框啊。
修复第二个bug
我们既然知道了第二个bug产生的原因,那么我们阻止冒泡顺序
- 解决的方案,不让其往上冒泡,自己管理。
clickMe.addEventListener('click', function(){
popover.style.display = 'block'
console.log('点击浮层了')
})
wrapper.addEventListener('click', function(e){
e.stopPropagation()
})
document.addEventListener('click', function(){
popover.style.display = 'none'
console.log('点击文档了')
})
- 但是随之而来的是一个关于内存占用的问题,现在你是只有一个popover,只有一个函数,等你有了很多个popover,如果按照这个写法会有很多个函数,所以不能这么写,采用下面的写法,节省内存。
$(clickMe).on('click', function(){
$(popover).show()
console.log('show')
setTimeout(function(){
console.log('one click')
$(document).one('click', function(){
console.log('我觉的他不会执行')
$(popover).hide()
})
},0)
})
// $(wrapper).on('click', function(e){
// e.stopPropagation()
// })
$(document).on('click', function(){
console.log('走到document啦')
})
- 只有点击的时候才用,设置settimeout是为了让他异步,不至于立刻隐藏,产生第一个bug。
- 注意一下,jQuery的
show()hide()
- 当你点击按钮,只会打印图中这两句话,另外两句只有再次点击才会打印。
JS版本的节省内存的版本==================>节省内存
jQuery版本的节省内存版本=================>jQuery节省内存
对话框小三角的制作
.popover{
display: inline-block;
border: 1px solid red;
position: relative;
padding: 10px;
margin:10px;
}
.popover::before{
position: absolute;
content: '';
top: 5px;
right: 100%;
border: 10px solid transparent;
border-right-color:red;
}
.popover::after{
content: '';
border: 10px solid transparent;
position: absolute;
right: 100%;
top: 5px;
border-right-color: white;
margin-right: -1px;
}
主要利用boder-right-color以及两个伪元素。
浮层三角的实例=============================>demo
冒泡的直观体现
点击一下会有惊喜的https://github.com/codevvvv9/bubble/blob/master/bubble.gif
理解Event冒泡模型的更多相关文章
- 《深入理解Java内存模型》读书总结
概要 文章是<深入理解Java内容模型>读书笔记,该书总共包括了3部分的知识. 第1部分,基本概念 包括"并发.同步.主内存.本地内存.重排序.内存屏障.happens befo ...
- 深入理解java内存模型系列文章
转载关于java内存模型的系列文章,写的非常好. 深入理解java内存模型(一)--基础 深入理解java内存模型(二)--重排序 深入理解java内存模型(三)--顺序一致性 深入理解java内存模 ...
- 【Todo】【转载】深入理解Java内存模型
提纲挈领地说一下Java内存模型: 什么是Java内存模型 Java内存模型定义了一种多线程访问Java内存的规范.Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几 ...
- 深入理解Java内存模型(一)——基础(转)
转自程晓明的"深入理解Java内存模型"的博客 http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发 ...
- 理解CSS盒子模型
概述 网页设计中常听的属性名:内容(content).填充(padding).边框(border).边界(margin),CSS盒子模型都具备这些属性,也主要是这些属性. 这些属性我们可以把它转移到我 ...
- <转>HTML+CSS总结/深入理解CSS盒子模型
原文地址:http://www.chinaz.com/design/2010/1229/151993.shtml 前言:前阵子在做一个项目时,在页面布局方面遇到了一点小问题,于是上stackoverf ...
- JavaScript 运行机制详解:深入理解Event Loop
Philip Roberts的演讲<Help, I'm stuck in an event-loop>,详细.完整.正确地描述JavaScript引擎的内部运行机制. 一.为什么JavaS ...
- jQuery event,冒泡,默认事件用法
jQuery event,冒泡,默认事件用法 <%@ page language="java" import="java.util.*" pageEnco ...
- 深入理解Java内存模型之系列篇[转]
原文链接:http://blog.csdn.net/ccit0519/article/details/11241403 深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需 ...
随机推荐
- ActiveMQ单机部署及简单应用
系统版本:Centos 7 前言 MQ是消息中间件,是一种在分布式系统中应用程序借以传递消息的媒介,常用的有ActiveMQ,RabbitMQ,kafka.ActiveMQ是Apache下的开源项目, ...
- ubuntu/debian将sh改为bash
1. 查看现在环境 可以看到,现在的默认环境是sh.我们想把它变为bash,可以这样做: 2. 运行sudo dpkg-reconfigure dash,出现以下画面: 这里提示我们是否要用默认的s ...
- 日常工作问题解决:centos/linux系统如何检测端口是否打开
1.telnet命令 格式: telnet ip 端口号 [root@centos7-127 ~]# telnet 192.168.87.128 22 Trying 192.168.87.128... ...
- Bloom Filter布隆过滤器原理和实现(1)
引子 <数学之美>介绍布隆过滤器非常经典: 在日常生活中,包括设计计算机软件时,经常要判断一个元素是否在一个集合中.比如: 在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断它 ...
- UWP 保存音乐或视频缩略图图片到本地
开发项目时,有时需要将本地媒体文件的缩略图保存到本地,下面是源码. 需要打开Package.appxmanifest 功能 图片库 访问权限. <Page x:Class="SaveB ...
- vim bundle安装
一.准备工作 安装Git(因为下面我们选择的插件管理器需要使用到它)安装其他插件前首先需要选择一个Vim插件管理器,我这里选择的是Vundle,Vundle的工作过程中需要通过Git自动从远程创库同步 ...
- lua添加自定义模块的步骤
以下方法在lua 5.2.4版本下成功实现: 1. lua.c为所有函数的主程序,参考Makefile的编译链接2. lua.c中int main (int argc, char **argv) { ...
- [转帖]「知乎知识库」— 5G
「知乎知识库」— 5G 甜草莓 https://zhuanlan.zhihu.com/p/55998832 通信 话题的优秀回答者 已关注 881 人赞同了该文章 谢 知识库 邀请~本文章是几个答 ...
- Python基础『一』
内置数据类型 数据名称 例子 数字: Bool,Complex,Float,Integer True/False; z=a+bj; 1.23; 123 字符串: String '123456' 元组: ...
- 学习GTK+ (1) ——编写helloworld
环境 我使用的是新安装的manjaro 18.1 (kde版),安装新系统后后直接可以开始写代码,不需要安装各种调用的库等. 推荐一个网站,gnome开发者 https://developer.gno ...