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. 事件冒泡階段