自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Chrome源碼看瀏覽器的事件機(jī)制

系統(tǒng) 瀏覽器
本文從源碼角度介紹了事件的數(shù)據(jù)結(jié)構(gòu),從一個(gè)側(cè)面解綁事件介紹事件和DOM節(jié)點(diǎn)的聯(lián)系,然后重點(diǎn)分析了事件的捕獲及冒泡過程。相信看完本文,對(duì)事件的本質(zhì)會(huì)有一個(gè)更透徹的理解。

在上一篇《從Chrome源碼看瀏覽器如何構(gòu)建DOM樹》介紹了blink如何創(chuàng)建一棵DOM樹,在這一篇將介紹事件機(jī)制。

上一篇還有一個(gè)地方未提及,那就是在構(gòu)建完DOM之后,瀏覽器將會(huì)觸發(fā)DOMContentLoaded事件,這個(gè)事件是在處理tokens的時(shí)候遇到EndOfFile標(biāo)志符時(shí)觸發(fā)的:

 

  1. if (it->type() == HTMLToken::EndOfFile) {  
  2. // The EOF is assumed to be the last token of this bunch.  
  3. ASSERT(it + 1 == tokens->end());  
  4. // There should never be any chunks after the EOF.  
  5. ASSERT(m_speculations.isEmpty());  
  6. prepareToStopParsing();  
  7. break;  

上面代碼第1行,遇到結(jié)尾的token時(shí),將會(huì)在第6行停止解析。這是***一個(gè)待處理的token,一般是跟在后面的一個(gè)\EOF標(biāo)志符來的。

第6行的prepareToStopParsing,會(huì)在Document的finishedParseing里面生成一個(gè)事件,再調(diào)用dispatchEvent,進(jìn)一步調(diào)用監(jiān)聽函數(shù):

 

  1. void Document::finishedParsing() {  
  2. dispatchEvent(Event::createBubble(EventTypeNames::DOMContentLoaded));  

這個(gè)dispatchEvent是EventTarget這個(gè)類的成員函數(shù)。在上一篇描述DOM的結(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)時(shí)將Node作為根結(jié)點(diǎn),其實(shí)Node上面還有一個(gè)類,就是EventTarget。我們先來看一下事件的數(shù)據(jù)結(jié)構(gòu)是怎么樣的:

1. 事件的數(shù)據(jù)結(jié)構(gòu)

畫出事件相關(guān)的類圖:

從Chrome源碼看瀏覽器的事件機(jī)制

在最頂層的EventTarget提供了三個(gè)函數(shù),分別是添加監(jiān)聽add、刪除監(jiān)聽remove、觸發(fā)監(jiān)聽fire。一個(gè)典型的訪問者模式我在《Effective前端5:減少前端代碼耦合》提到了,這里重點(diǎn)看一下blink實(shí)際上是怎么實(shí)現(xiàn)的。

在Node類組合了一個(gè)EventTargetDataMap,這是一個(gè)哈希map,并且它是靜態(tài)成員變量。它的key值是當(dāng)前結(jié)點(diǎn)Node實(shí)例的指針,value值是事件名稱和對(duì)應(yīng)的listeners。如果畫一個(gè)示例圖,它的存儲(chǔ)是這樣的:

從Chrome源碼看瀏覽器的事件機(jī)制

如上,按照正常的思維,存放事件名稱和對(duì)應(yīng)的訪問者應(yīng)該是用一個(gè)哈希map,但是blink卻是用的向量vector + pair,這就導(dǎo)致在查找某個(gè)事件的訪問者的時(shí)候,需要循環(huán)所有已添加的事件名稱依次比較字符串值是否相等。為什么要用循環(huán)來做而不是map,這在它的源碼注釋做了說明:

 

  1. // We use HeapVector instead of HeapHashMap because  
  2. // - HeapVector is much more space efficient than HeapHashMap.  
  3. // - An EventTarget rarely has event listeners for many event types, and  
  4. // HeapVector is faster in such cases.  
  5. HeapVector>, 2> m_entries; 

意思是說使用vector比使用map更加節(jié)省空間,并且一個(gè)dom節(jié)點(diǎn)往往不太可能綁了太多的事件類型。這就啟示我們寫代碼要根據(jù)實(shí)際情況靈活處理。

同時(shí)還有一個(gè)比較有趣的事情,就是webkit用了一個(gè)EventTargetDataMap存放所有節(jié)點(diǎn)綁定的事件,它是一個(gè)static靜態(tài)成員變量,被所有Node的實(shí)例所共享,由于不同的實(shí)例的內(nèi)存地址不一樣,所以它的key不一樣,就可以通過內(nèi)存地址找到它綁的所有事件,即上面說的vector結(jié)構(gòu)。為什么它要用一個(gè)類似于全局的變量?按照正常思維,每個(gè)Node結(jié)點(diǎn)綁的事件是獨(dú)立的,那應(yīng)該把綁的事件作為每個(gè)Node實(shí)例獨(dú)立的數(shù)據(jù),搞一個(gè)全局的還得用一個(gè)map作一個(gè)哈希映射。

一個(gè)可能的原因是EventTarget是作為所有DOM結(jié)點(diǎn)的事件目標(biāo)的類,除了Node之外,還有FileReader、AudioNode等也會(huì)繼承于EventTarget,它們有另外一個(gè)EventTargetData。把所有的事件都放一起了,應(yīng)該會(huì)方便統(tǒng)一處理。

這個(gè)時(shí)候你可能會(huì)冒出另外一個(gè)問題,這個(gè)EventTargetDataMap是什么釋放綁定的事件的,我把一個(gè)DOM結(jié)點(diǎn)刪了,它會(huì)自動(dòng)去釋放綁定的的事件嗎?換句話說,刪除掉一個(gè)結(jié)點(diǎn)前需不需要先off掉它的事件?

2. DOM結(jié)點(diǎn)刪除與事件解綁

從源碼可以看到,Node的析構(gòu)函數(shù)并沒有去釋放當(dāng)前Node綁定的事件,所以它是不是不會(huì)自動(dòng)釋放事件?為驗(yàn)證,我們?cè)谔砑咏壎ㄒ粋€(gè)事件后、刪掉結(jié)點(diǎn)后分別打印這個(gè)map里面的數(shù)據(jù),為此給Node添加一個(gè)打印的函數(shù):

 

  1. void Node::printEventMap(){  
  2. EventTargetDataMap::iterator it = eventTargetDataMap().begin();  
  3. LOG (INFO) << "print event map: " 
  4. while(it != eventTargetDataMap().end()){  
  5. LOG(INFO) << ((Element*)it->key.get())->tagName();  
  6. ++it;  
  7.  

在上面的第5行,循環(huán)打印出所有Node結(jié)點(diǎn)的標(biāo)簽名。

同時(shí)試驗(yàn)的html如下:

  1. <p id="text">hello, world</p> 
  2. <script> 
  3.     function clickHandle(){ 
  4.         console.log("click"); 
  5.     } 
  6.     document.getElementById("text").addEventListener("click", clickHandle); 
  7.     document.getElementById("text").remove(); 
  8.     document.addEventListener("DOMContentLoaded"function(){ 
  9.         console.log("loaded"); 
  10.     });  
  11. </script> 

 

打印的結(jié)果如下:

 

  1. [21755:775:0204/181452.402843:INFO:Node.cpp(1910)] print event map:  
  2. [21755:775:0204/181452.403048:INFO:Node.cpp(1912)] “P”  
  3. [21755:775:0204/181452.404114:INFO:Node.cpp(1910)] print event map:  
  4. [21755:775:0204/181452.404287:INFO:Node.cpp(1912)] “P”  
  5. [21755:775:0204/181452.404466:INFO:Node.cpp(1912)] “#document” 

可以看到remove了p結(jié)點(diǎn)之后,它的事件依然存在。

我們看一下blink在remove里面做了什么:

 

  1. void Node::remove(ExceptionState& exceptionState) {  
  2. if (ContainerNode* parent = parentNode())  
  3. parent->removeChild(this, exceptionState);  

remove是后來W3C新加的api,所以在remove里面調(diào)的是老的removeChild,removeChild的關(guān)鍵代碼如下:

 

  1. Node* previousChild = child->previousSibling();  
  2. Node* nextChild = child->nextSibling();  
  3. if (nextChild)  
  4. nextChild->setPreviousSibling(previousChild);  
  5. if (previousChild)  
  6. previousChild->setNextSibling(nextChild);  
  7. if (m_firstChild == &oldChild)  
  8. setFirstChild(nextChild);  
  9. if (m_lastChild == &oldChild)  
  10. setLastChild(previousChild);  
  11. oldChild.setPreviousSibling(nullptr); 
  12. oldChild.setNextSibling(nullptr);  
  13. oldChild.setParentOrShadowHostNode(nullptr); 

前面幾行是重新設(shè)置DOM樹的結(jié)點(diǎn)關(guān)系,比較好理解。***面三行,把刪除掉的結(jié)點(diǎn)的兄弟指針和父指針置為null,注意這里并沒有把它delete掉,只是把它隔離開來。所以把它remove掉之后, 這個(gè)結(jié)點(diǎn)在內(nèi)存里面依舊存在,你依然可以獲取它的innerText,把它重新append到body里面(但是不推薦這么做)。同時(shí)事件依然存在那個(gè)map里面。

什么時(shí)候這個(gè)節(jié)點(diǎn)會(huì)被真正的析構(gòu)呢?發(fā)生在GC回收的時(shí)候,GC回收的時(shí)候會(huì)把DOM結(jié)點(diǎn)的內(nèi)存釋放,并且會(huì)刪掉map里面的數(shù)據(jù)。為驗(yàn)證,在啟動(dòng)Chrome的時(shí)候加上參數(shù):

  1. chromium test.html --js-flags='--expose_gc' 

這樣可以調(diào)用window.gc觸發(fā)gc回收,然后在上面的js demo代碼后面加上:

 

  1. setTimeout(function(){  
  2. //添加這個(gè)事件是為了觸發(fā)Chrome源碼里面添加的打印log  
  3. document.addEventListener("DOMContentLoaded"function(){});  
  4. setTimeout(function(){  
  5. window.gc();  
  6. document.addEventListener("DOMContentLoaded"function(){});  
  7. }, 3000);  
  8. }, 3000); 

打印的結(jié)果:

 

  1. [Node.cpp(1912)] print event map:  
  2. [Node.cpp(1914)] “P”  
  3. [Node.cpp(1914)] “#document”  
  4. [Element.cpp(186)] destroy element “p”  
  5. [Node.cpp(1912)] print event map:  
  6. [Node.cpp(1914)] “#document” 

后面三行是執(zhí)行了GC回收后的結(jié)果——析構(gòu)p標(biāo)簽并更新存放事件的數(shù)據(jù)結(jié)構(gòu)。

所以說刪掉一個(gè)DOM結(jié)點(diǎn),并不需要手動(dòng)去釋放它的事件。

需要注意的是DOM結(jié)點(diǎn)一旦存在一個(gè)引用,即使你把它remove掉了,GC也不會(huì)去回收,如下:

  1. <script> 
  2.     var p = document.getElementById("text"); 
  3.     p.remove(); 
  4.     window.gc(); 
  5. </script> 

 

執(zhí)行了window.gc之后并不會(huì)去回收p的內(nèi)存空間以及它的事件。因?yàn)檫€存在一個(gè)p的變量指向它,而如果將p置為null,如下:

  1. <script> 
  2.     var p = document.getElementById("text"); 
  3.     p.remove(); 
  4.     p = null
  5.     window.gc(); 
  6. </script> 

 

***的GC就管用了,或者p離開了作用域:

  1. <script> 
  2. !function(){ 
  3.     var p = document.getElementById("text"); 
  4.     p.remove(); 
  5. }() 
  6. window.gc(); 
  7. </script> 

 

自動(dòng)銷毀,p結(jié)點(diǎn)沒有人引用了,能夠自動(dòng)GC回收。

還有一個(gè)問題一直困擾著我,那就是監(jiān)聽X按鈕的click,然后把它的父容器如彈框給刪了,這樣它自已本身也刪了,但是監(jiān)聽函數(shù)還可以繼續(xù)執(zhí)行,實(shí)體都沒有了,為什么綁在它身上的函數(shù)還可以繼續(xù)執(zhí)行呢?通過上面的分析,應(yīng)該可以找到答案:刪掉之后GC并不會(huì)立刻回收和釋放事件,因?yàn)樵趫?zhí)行監(jiān)聽函數(shù)的時(shí)候,里面有個(gè)this指針指向了該節(jié)點(diǎn),并且this是只讀的,你不能把它置成null。所以只有執(zhí)行完了回調(diào)函數(shù),離開了作用域,this才會(huì)銷毀,才有可能被GC回收。

還有一種綁事件的方式,沒有討論:

3. DOM Level 0事件

就是使用dom結(jié)點(diǎn)的onclick、onfocus等屬性,添加事件,由于這個(gè)提得比較早,所以它的兼容性***。如下:

 

  1. function clickHandle(){  
  2. console.log("addEventListener click");  
  3.  
  4. var p = document.getElementById("text");  
  5. p.addEventListener("click", clickHandle);  
  6. p.onclick = function(){  
  7. console.log("onclick trigger");  
  8. }; 

如果點(diǎn)擊p標(biāo)簽,將會(huì)觸發(fā)兩次,一次是addEventListener綁定的,另一次是onclick綁定的。onclick是如何綁定的呢:

 

  1. bool EventTarget::setAttributeEventListener(const AtomicString& eventType,  
  2. EventListener* listener) {  
  3. clearAttributeEventListener(eventType);  
  4. if (!listener)  
  5. return false 
  6. return addEventListener(eventType, listener, false);  

可以看到,***還是調(diào)的上面的addEventListener,只是在此之前要先clear掉上一次綁的屬性事件:

 

  1. bool EventTarget::clearAttributeEventListener(const AtomicString& eventType) {  
  2. EventListener* listener = getAttributeEventListener(eventType);  
  3. if (!listener)  
  4. return false 
  5. return removeEventListener(eventType, listener, false);  

在clear函數(shù)里面會(huì)去獲取上一次的listener,然后調(diào)removeEventListener,關(guān)鍵在于它怎么根據(jù)事件名稱eventType獲取上次listener呢:

 

  1. EventListener* EventTarget::getAttributeEventListener(  
  2. const AtomicString& eventType) {  
  3. EventListenerVector* listenerVector = getEventListeners(eventType);  
  4. if (!listenerVector)  
  5. return nullptr;  
  6. for (auto& eventListener : *listenerVector) {  
  7. EventListener* listener = eventListener.listener();  
  8. if (listener->isAttribute() /* && ... */)  
  9. return listener;  
  10.  
  11. return nullptr;  

在代碼上看很容易理解,首先獲取該DOM結(jié)點(diǎn)該事件名稱的所有l(wèi)istener做個(gè)循環(huán),然后判斷這個(gè)listener是否為屬性事件。判斷成立,則返回。怎么判斷是否為屬性事件?那個(gè)是實(shí)例化事件的時(shí)候封裝好的了。

從上面的源代碼可以很清楚地看到onclick等屬性事件只能綁一次,并且和addEventListener的事件不沖突。

關(guān)于事件,還有一個(gè)很重要的概念,那就是事件的捕獲和冒泡。

4. 事件的捕獲和冒泡

用以下html做試驗(yàn):

  1. <div id="div-1"
  2.     <div id="div-2"
  3.         <div id="div-3">hello, world</div> 
  4.     </div> 
  5. </div> 

 

 

 

js綁事件如下:

 

  1. var div1 = document.getElementById("div-1"),  
  2. div2 = document.getElementById("div-2"),  
  3. div3 = document.getElementById("div-3");  
  4. function printInfo(event){  
  5. console.log(“eventPhase=“ + ””event.eventPhase + " " + this.id);  
  6.  
  7. div1.addEventListener("click", printInfo, true);  
  8. div2.addEventListener("click", printInfo, true);  
  9. div3.addEventListener("click", printInfo, true); 
  10. div1.addEventListener("click", printInfo);  
  11. div2.addEventListener("click", printInfo);  
  12. div3.addEventListener("click", printInfo); 

第三個(gè)參數(shù)為true,表示監(jiān)聽在捕獲階段,點(diǎn)擊p標(biāo)簽之后控制臺(tái)打印出:

 

  1. [CONSOLE] “eventPhase=1 div-1”  
  2. [CONSOLE] “eventPhase=1 div-2”  
  3. [CONSOLE] “eventPhase=2 div-3”  
  4. [CONSOLE] “eventPhase=2 div-3”  
  5. [CONSOLE] “eventPhase=3 div-2”  
  6. [CONSOLE] “eventPhase=3 div-1” 

在Event類定義里面可以找到關(guān)到eventPhase的定義:

 

  1. enum PhaseType {  
  2. kNone = 0,  
  3. kCapturingPhase = 1,  
  4. kAtTarget = 2,  
  5. kBubblingPhase = 3  
  6. }; 

1表示捕獲取階段,2表示在當(dāng)前目標(biāo),3表示冒泡階段。把上面的phase轉(zhuǎn)化成文字,并把html/body/document也綁上事件,同時(shí)at-target只綁一次,那么整一個(gè)過程將是這樣的:

 

  1. “capture document”  
  2. “capture HTML”  
  3. “capture BODY”  
  4. “capture DIV#div-1”,  
  5. “capture DIV#div-2”,  
  6. at-target DIV#div-3”,  
  7. “bubbling DIV#div-2”,  
  8. “bubbling DIV#div-1”,  
  9. “bubbling BODY”  
  10. “bubbling HTML”  
  11. “bubbling document” 

從document一直捕獲到目標(biāo)div3,然后再一直冒泡到document,如果在某個(gè)階段執(zhí)行了:

  1. event.stopPropagation() 

那么后續(xù)的過程將不會(huì)繼續(xù),例如在document的capture階段的click事件里面執(zhí)行了上面的阻止傳播函數(shù),那么控制臺(tái)只會(huì)打印出上面輸出的***行。

在研究blink是如何實(shí)現(xiàn)之前,我們先來看一下事件是怎么觸發(fā)和封裝的

5. 事件的觸發(fā)和封裝

以click事件為例,Blink在RenderViewImpl里面收到了外面的進(jìn)程的消息:

 

  1. // IPC::Listener implementation ----------------------------------------------  
  2. bool RenderViewImpl::OnMessageReceived(const IPC::Message& message) {  
  3. // Have the super handle all other messages.  
  4. IPC_MESSAGE_UNHANDLED(handled = RenderWidget::OnMessageReceived(message))  

上文已提到,RenderViewImpl是頁面最基礎(chǔ)的一個(gè)類,當(dāng)它收到IPC發(fā)來的消息時(shí),根據(jù)消息的類型,調(diào)用相應(yīng)的處理函數(shù),由于這是一個(gè)input消息,所以它會(huì)調(diào):

  1. IPC_MESSAGE_HANDLER(InputMsg_HandleInputEvent, OnHandleInputEvent) 

上面的IPC_MESSAGE_HANDLER其實(shí)是Blink定義的一個(gè)宏,這個(gè)宏其實(shí)就是一個(gè)switch-case里面的case。

這個(gè)處理函數(shù)又會(huì)調(diào):

 

  1. WebInputEventResult WebViewImpl::handleInputEvent(  
  2. const WebInputEvent& inputEvent) {  
  3. switch (inputEvent.type) {  
  4. case WebInputEvent::MouseUp:  
  5. eventType = EventTypeNames::mouseup;  
  6. gestureIndicator = WTF::wrapUnique(  
  7. new UserGestureIndicator(m_mouseCaptureGestureToken.release()));  
  8. break;  
  9.  

它里面會(huì)根據(jù)輸入事件的類型如mouseup、touchstart、keybord事件等類型去調(diào)不同的函數(shù)。click是在mouseup里面處理的,接著在MouseEventManager里面創(chuàng)建一個(gè)MouseEvent,并調(diào)度事件,即捕獲和冒泡:

 

  1. WebInputEventResult MouseEventManager::dispatchMouseEvent(EventTarget* target, const AtomicString& mouseEventType, const PlatformMouseEvent& mouseEvent, EventTarget* relatedTarget, bool checkForListener) { 
  2. MouseEvent* event = MouseEvent::create( mouseEventType, targetNode->document().domWindow(), mouseEvent/*...*/);  
  3. DispatchEventResult dispatchResult = target->dispatchEvent(event);  
  4. return EventHandlingUtil::toWebInputEventResult(dispatchResult);  

上面代碼第2行創(chuàng)建MouseEvent,第3行dispatch。我們來看一下這個(gè)事件是如何層層封裝成一個(gè)MouseEvent的:

從Chrome源碼看瀏覽器的事件機(jī)制

上圖展示了從原始的msg轉(zhuǎn)化成了W3C標(biāo)準(zhǔn)的MouseEvent的過程。Blink的消息處理引擎把msg轉(zhuǎn)化成了WebInputEvent,這個(gè)event能夠直接靜態(tài)轉(zhuǎn)化成可讀的WebMouseEvent,也就是事件在底層的時(shí)候已經(jīng)被封裝成帶有相關(guān)數(shù)據(jù)且可讀的事件了,上層再把它這些數(shù)據(jù)轉(zhuǎn)化成W3C規(guī)定格式的MouseEvent。

我們重點(diǎn)看下MouseEvent的create函數(shù):

 

  1. MouseEvent* MouseEvent::create(const AtomicString& eventType, AbstractView* view, const PlatformMouseEvent& event, Node* relatedTarget) {  
  2. bool isMouseEnterOrLeave = eventType == EventTypeNames::mouseenter ||  
  3. eventType == EventTypeNames::mouseleave;  
  4. bool isCancelable = !isMouseEnterOrLeave;  
  5. bool isBubbling = !isMouseEnterOrLeave;  
  6. return MouseEvent::create 
  7. eventType, isBubbling, isCancelable, view, event.position().x()  
  8. /*.../*, &event);  

從代碼第五行可以看到鼠標(biāo)事件的mouseenter和mouseleave是不會(huì)冒泡的。

另外,每個(gè)Event都有一個(gè)EventPath,記錄它冒泡的路徑:

從Chrome源碼看瀏覽器的事件機(jī)制

在dispatchEvent的時(shí)候,會(huì)初始化EventPath:

 

  1. void EventPath::initialize() {  
  2. if (eventPathShouldBeEmptyFor(*m_node, m_event))  
  3. return 
  4. calculatePath();  
  5. calculateAdjustedTargets();  
  6. calculateTreeOrderAndSetNearestAncestorClosedTree();  

第五行會(huì)去計(jì)算Path,而這個(gè)計(jì)算Path的核心邏輯非常簡(jiǎn)單:

 

  1. void EventPath::calculatePath() {  
  2. // For performance and memory usage reasons we want to store the  
  3. // path using as few bytes as possible and with as few allocations  
  4. // as possible which is why we gather the data on the stack before  
  5. // storing it in a perfectly sized m_nodeEventContexts Vector.  
  6. HeapVector, 64> nodesInPath;  
  7. Node* current = m_node;  
  8. nodesInPath.push_back(current);  
  9. while (current) {  
  10. current = current->parentNode();  
  11. if (current 
  12. nodesInPath.push_back(current);  
  13.  
  14. m_nodeEventContexts.reserveCapacity(nodesInPath.size());  
  15. for (Node* nodeInPath : nodesInPath) {  
  16. m_nodeEventContexts.push_back(NodeEventContext(  
  17. nodeInPath, eventTargetRespectingTargetRules(*nodeInPath)));  
  18.  

第9行的while循環(huán)不斷地獲取當(dāng)前node的父節(jié)點(diǎn)并把它push到一個(gè)vector里面,直到null即沒有父節(jié)點(diǎn)為止。***再把這個(gè)vector push到真正用來存儲(chǔ)成員變量。這段代碼我們又發(fā)現(xiàn)一個(gè)有趣的注釋,它說明了為什么不直接push到成員變量里面——因?yàn)関ector變量會(huì)自動(dòng)擴(kuò)展本身大小,當(dāng)push的時(shí)候容量不足時(shí),會(huì)不斷地開辟內(nèi)存,blink的實(shí)現(xiàn)是開辟一個(gè)單位元素的空間,剛好存放一個(gè)元素:

  1. ptr = expandCapacity(size() + 1, ptr); 

所以如果直接push_back到成員變量,會(huì)不斷地開辟新內(nèi)存。于是它一開始就初始化了一個(gè)size為64的棧變量來存放,減少開辟內(nèi)存的操作。另外有些vector自動(dòng)擴(kuò)充容量的實(shí)現(xiàn),可能是size * 1.5或者size + 10,而不是size + 1,這種情況就會(huì)導(dǎo)致有多余的空間沒用到。

通過這樣的手段,就有了記錄事件冒泡路徑的EventPath。

6. 事件捕獲和冒泡的實(shí)現(xiàn)

上面第5點(diǎn)提到的MouseEventManager會(huì)調(diào)dispatchEvent,這個(gè)函數(shù)會(huì)先創(chuàng)建一個(gè)dispatcher,這個(gè)dispatcher實(shí)例化的時(shí)候就會(huì)去初始化上面的EventPath,然后再進(jìn)行dispatch/事件調(diào)度:

 

  1. EventDispatcher dispatcher(node, &mediator->event());  
  2. DispatchEventResult dispatchResult = dispatcher.dispatch(); 

所以核心函數(shù)就是第2行調(diào)的dispatch,而這個(gè)函數(shù)最核心的3行代碼為:

 

  1. if (dispatchEventAtCapturing() == ContinueDispatching) {  
  2. if (dispatchEventAtTarget() == ContinueDispatching)  
  3. dispatchEventAtBubbling();  

(1)先執(zhí)行Capturing,然后再執(zhí)行AtTarget,***再Bubbling,我們來看一下Capturing函數(shù):

 

  1. inline EventDispatchContinuation EventDispatcher::dispatchEventAtCapturing() { 
  2.  // Trigger capturing event handlers, starting at the top and working our way  
  3. // down.  
  4. //改變event的階段為冒泡  
  5. m_event->setEventPhase(Event::kCapturingPhase);  
  6. //先處理綁在window上的事件,并且如果event的m_propagationStopped被設(shè)置為true  
  7. //則返回done狀態(tài),不再繼續(xù)傳播  
  8. if (m_event->eventPath().windowEventContext().handleLocalEvents(*m_event) &&  
  9. m_event->propagationStopped())  
  10. return DoneDispatching; 

上面做了一些初始化的工作后,循環(huán)EventPath依次觸發(fā)響應(yīng)函數(shù):

 

  1. //從EventPath***一個(gè)元素,即最頂層的父結(jié)點(diǎn)開始下濾  
  2. for (size_t i = m_event->eventPath().size() - 1; i > 0; --i) { 
  3. const NodeEventContext& eventContext = m_event->eventPath()[i];  
  4. //觸發(fā)事件響應(yīng)函數(shù)  
  5. eventContext.handleLocalEvents(*m_event);  
  6. //如果響應(yīng)函數(shù)設(shè)置了stopPropagation,則返回done  
  7. if (m_event->propagationStopped())  
  8. return DoneDispatching;  
  9.  
  10. return ContinueDispatching;  

注意上面的for循環(huán)終止條件的i是大于0,i為0則為currentTarget。而總的size為6,與我們上面demo控制臺(tái)打印一致。

(2)at-target的處理就很簡(jiǎn)單了,取i為0的那個(gè)Node并觸發(fā)它的listeners:

 

  1. inline EventDispatchContinuation EventDispatcher::dispatchEventAtTarget() {  
  2. m_event->setEventPhase(Event::kAtTarget);  
  3. m_event->eventPath()[0].handleLocalEvents(*m_event);  
  4. return m_event->propagationStopped() ? DoneDispatching : ContinueDispatching;  

(3)bubbling的處理稍復(fù)雜,因?yàn)樗€要處理cancleBubble的情況,不過總體的邏輯是類似的,核心代碼如下:

 

  1. inline void EventDispatcher::dispatchEventAtBubbling() {  
  2. // Trigger bubbling event handlers, starting at the bottom and working our way  
  3. // up.  
  4. size_t size = m_event->eventPath().size();  
  5. for (size_t i = 1; i < size; ++i) {  
  6. const NodeEventContext& eventContext = m_event->eventPath()[i];  
  7. if (m_event->bubbles() && !m_event->cancelBubble()) {  
  8. m_event->setEventPhase(Event::kBubblingPhase);  
  9.  
  10. eventContext.handleLocalEvents(*m_event);  
  11. if (m_event->propagationStopped())  
  12. return 
  13.  

可以看到bubbling的for循環(huán)是從i = 1開始,和capturing相反。因?yàn)閎ubble是三個(gè)階段***處理的,所以它不用再返回一個(gè)標(biāo)志了。

上面介紹完了事件的捕獲和冒泡,我們注意到一個(gè)細(xì)節(jié),所有的事件都會(huì)先在capture階段在windows上觸發(fā)。

綜合以上,本文從源碼角度介紹了事件的數(shù)據(jù)結(jié)構(gòu),從一個(gè)側(cè)面解綁事件介紹事件和DOM節(jié)點(diǎn)的聯(lián)系,然后重點(diǎn)分析了事件的捕獲及冒泡過程。相信看完本文,對(duì)事件的本質(zhì)會(huì)有一個(gè)更透徹的理解。

責(zé)任編輯:未麗燕 來源: 碼農(nóng)網(wǎng)
相關(guān)推薦

2017-02-28 10:05:56

Chrome源碼

2017-11-21 14:56:59

2017-02-07 09:44:12

Chrome源碼DOM樹

2012-07-04 17:00:06

獵豹瀏覽瀏覽器

2011-06-21 16:52:48

2020-12-23 07:37:17

瀏覽器HTML DOM0

2022-02-07 21:49:06

瀏覽器渲染chromium

2017-04-05 20:00:32

ChromeObjectJS代碼

2017-01-05 09:07:25

JavaScript瀏覽器驅(qū)動(dòng)

2010-01-28 10:13:43

2009-11-26 10:55:41

2018-02-02 15:48:47

ChromeDNS解析

2015-01-21 15:45:50

斯巴達(dá)瀏覽器

2011-11-11 10:35:04

2017-04-26 14:15:35

瀏覽器緩存機(jī)制

2016-10-09 08:38:01

JavaScript瀏覽器事件

2021-10-15 09:56:10

JavaScript異步編程

2012-08-08 09:18:47

Chrome瀏覽器

2010-01-10 17:50:17

2019-02-15 15:15:59

ChromeJavascriptHtml
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)