DOM将用例如XML、HTML等标记语言写成的结构化文档当作一颗树,该树上的节点也便是文档内的节点。简单来讲,DOM是一组API,可使用户经过任何实现了DOM API的语言操纵任何知足DOM标准的文档。javascript
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>Hello World</title> </head> <body> <div class='test'>Test</div> <script> let div = document.getElementsByClassName('test')[0]; div.onclick = function(){ console.log('click'); } </script> </body> </html>
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>Hello World</title> </head> <body> <div class='test' onclick='console.log("click)'>Test</div> </body> </html>
上述代码都为类名为test的div绑定了一个click事件发生时触发的方法。这种为DOM元素设置事件函数的方法即是DOM 0级事件模型中规定的。除此以外,DOM 0级事件模型还不容许为一个元素的同一事件绑定多个处理方法。若绑定了多个,则最后一个函数覆盖以前的。html
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>Hello World</title> </head> <body> <div class='test'>Test</div> <script> let div = document.getElementsByClassName('test')[0]; div.onclick = function(){ console.log('click'); } //若是用户点击Test则会在控制台输出click again而不是click。 div.onclick = function(){ console.log('click again'); } </script> </body> </html>
DOM 2级事件模型为咱们提供两个方法分别用来添加和移除事件处理方法。前端
这两个方法的前两个参数是相同的,分别是事件名和事件处理函数(remove方法没法移除匿名函数)。而add方法则多出了一个参数,若传入true,则事件处理方法在事件捕获阶段被调用,反之则在事件冒泡阶段被调用。java
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>Hello World</title> </head> <body> <div class='test'>Test</div> <script> let div = document.getElementsByClassName('test')[0]; div.addEventListener('click', function(){ console.log('click'); }, false); div.addEventListener('click', function(){ console.log('click again'); }, true); </script> </body> </html>
与DOM 0级不一样,DOM 2级事件模型容许你为同个元素的同个事件添加多个事件处理函数,在触发时间时将会按照文档顺序被前后调用。浏览器
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>Hello World</title> </head> <body> <div id='one'> <div id='two'> <div id='three'> <p>Test</p> </div> </div> </div> <script> let div1 = document.getElementById('one'); let div2 = document.getElementById('two'); let div3 = document.getElementById('three'); div1.addEventListenr('click' , function(event){ console.log('one'); }, false); div2.addEventListenr('click' , function(event){ console.log('two'); }, false); div3.addEventListenr('click' , function(event){ console.log('three'); }, false); div1.addEventListenr('click' , function(event){ console.log('one'); }, true); div2.addEventListenr('click' , function(event){ console.log('two'); }, true); div3.addEventListenr('click' , function(event){ console.log('three'); }, true); </script> </body> </html>
说实话,如今不管是什么框架你都离不开操做DOM啊,毕竟这是你展现的最基本元素,就像人的细胞。想起了dom事件流原理,好多人不明白,只知道click mouseout等实用场景,真要理解和更进一步的前端是要必须:理论+实践 并行的。
固然,DOM事件所囊括的知识较为庞杂,因此本文专一与本身学习时所碰到的难点,DOM事件流。markdown
流的概念,在现今的JavaScript中随处可见。好比说React中的单向数据流,Node中的流,又或是今天本文所讲的DOM事件流。都是流的一种生动体现。至于流的具体概念,咱们采用下文的解释:网络
用术语说流是对输入输出设备的抽象。以程序的角度说,流是具备方向的数据。框架
事件流之事件冒泡与事件捕获dom
在浏览器发展的过程当中,开发团队遇到了一个问题。那就是页面中的哪一部分拥有特定的事件?函数
能够想象画在一张纸上的一组同心圆,若是你把手指放在圆心上,那么你的手指指向的其实不是一个圆,而是纸上全部的圆。放到实际页面中就是,你点击一个按钮,事实上你还同时点击了按钮全部的父元素。
开发团队的问题就在于,当点击按钮时,是按钮最外层的父元素先收到事件并执行,仍是具体元素先收到事件并执行?因此这儿引入了事件流的概念。
事件流所描述的就是从页面中接受事件的顺序。
由于有两种观点,因此事件流也有两种,分别是事件冒泡和事件捕获。现行的主流是事件冒泡。
事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),而后逐级传播到较为不具体的节点。
举个栗子,就很容易明白了。
而后,咱们给 button 和它的父元素,加入点击事件。
效果如图所示:
在代码所示的页面中,若是点击了button,那么这个点击事件会按以下的顺序传播(Chrome浏览器):
1. button
2. body
3. document
4. window
也就是说,click事件首先在 <button> 元素上发生,而后逐级向上传播。这就是事件冒泡。
事件捕获的概念,与事件冒泡正好相反。它认为当某个事件发生时,父元素应该更早接收到事件,具体元素则最后接收到事件。好比说刚才的demo,若是是事件捕获的话,事件发生顺序会是这样的:
1. window
2. document
3. body
4. button
固然,因为时代更迭,事件冒泡方式更胜一筹。因此放心的使用事件冒泡,有特殊须要再使用事件捕获便可。
DOM事件流
DOM事件流包括三个阶段:
1. 事件捕获阶段
2. 处于目标阶段
3. 事件冒泡阶段
如图所示(图片源于网络,若侵权请告知):
事件捕获阶段
也就是说,当事件发生时,首先发生的是事件捕获,为父元素截获事件提供了机会。
例如,我把上面的Demo中,window点击事件更改成使用事件捕获模式。(addEventListener最后一个参数, 为true则表明使用事件捕获模式 ,false则表示使用事件冒泡模式。不理解的能够去学习一下addEventListener函数的使用)
此时,点击button的效果是这样的。
能够看到,点击事件先被父元素截获了,且该函数只在事件捕获阶段起做用。
处于目标与事件冒泡阶段
事件到了具体元素时,在具体元素上发生,而且被当作冒泡阶段的一部分。随后,冒泡阶段发生,事件开始冒泡。
阻止事件冒泡
事件冒泡过程,是能够被阻止的。防止事件冒泡而带来没必要要的错误和困扰。
这个方法就是: stopPropagation()
咱们对 button 的click事件作一些改造。
点击后,效果以下图:
不难看出,事件在到达具体元素后,中止了冒泡。但不影响父元素的事件捕获。
小结
事件流:描述的就是从页面中接受事件的顺序。
分有事件冒泡与事件捕获两种。
DOM事件流的三个阶段:
1. 事件捕获阶段
2. 处于目标阶段
3. 事件冒泡阶段