webkit事件处理
1,概述
原文地址:http://blog.csdn.net/awebkit/article/details/8493716
瀏覽器處理事件一般有兩個過程,捕獲過程和冒泡過程,這是由addEventListener的第三個參數(shù)決定的。
- 基本事件流
-
每個事件都對應(yīng)一個事件目標(biāo)(EventTarget)(也是一個node 節(jié)點),EventTarget 有event 的target 屬性指定。 每個事件目標(biāo)注冊有若干事件監(jiān)聽者(EventListerner), 這些監(jiān)聽者在事件到達后激活,激活的順序在DOM規(guī)范中沒有定義。如果沒有定義事件capture或者bubbling,則當(dāng)事件目標(biāo)上的所有事件監(jiān)聽者 響應(yīng)完畢后,則這個事件處理完畢。
- 事件捕獲
-
事件捕獲發(fā)生在如下情況下: 一個事件監(jiān)聽者注冊在某個事件的目標(biāo)節(jié)點的祖先節(jié)點上,當(dāng)該事件發(fā)生后,在其到達目標(biāo)節(jié)點之前,祖先節(jié)點的事件監(jiān)聽者首先捕獲并處理,然后將事件逐級下傳,直到目標(biāo)節(jié)點。
- 事件冒泡
-
事件冒泡初始階段同基本事件流相同,然而在事件的目標(biāo)節(jié)點所有事件監(jiān)聽者響應(yīng)完畢后,是將將會沿著節(jié)點向其祖先方向上傳,直到document點,上傳過程中將會逐級激發(fā)在遇到節(jié)點上注冊的的所有事件監(jiān)聽者(捕獲事件除外)。
- 事件取消
-
一些事件可以規(guī)定為可取消的,這些事件一般都會應(yīng)有一個缺省的動作。當(dāng)此類事件發(fā)生時,首先傳遞給目標(biāo)節(jié)點上的事件監(jiān)聽者,事件監(jiān)聽者可以選擇是否取消該事件的缺省動作。
當(dāng)用戶在瀏覽器里觸發(fā)了點擊鼠標(biāo),敲鍵盤等事件后,瀏覽器需要處理這些事件,那么整個的流程如何呢?
首先,WebKit外部事件處理:
這些事件被GUI獲得,然后調(diào)用WebView的對應(yīng)處理函數(shù),而這些處理函數(shù)會調(diào)用當(dāng)前frame的 EventHandler 來處理對應(yīng)的事件。
WebKit內(nèi)部事件處理和保存:
EventHalder的處理函數(shù)一般還會調(diào)用到Node的dispatchGenericEvent,而這個函數(shù)會調(diào)用到EventTarget。EventTarget類是Node類的父類,里面使用map保存了事件的處理函數(shù)。
對于js事件,對應(yīng)的有JSEventListener,繼承于 EventListener,在解析script標(biāo)簽的時候,遇到event屬性,就會創(chuàng)建一個JSEventListener,并調(diào)用 EventTarget的事件處理map中。這樣,就完成了對應(yīng)事件到對應(yīng)結(jié)點的map工作。
2 JavaScript事件處理
詳細介紹參考這篇文章:http://www.cnblogs.com/binyong/articles/1750263.html
這篇文章分享的也很好:http://www.cnblogs.com/diligenceday/p/4175721.html
?
3 JavaScript事件再WebKit中的處理流程
原文地址:http://blog.csdn.net/codigger/article/details/40620721
?
本文主要探討了JavaScript事件在WebKit中的注冊和觸發(fā)機制。
JS事件有兩種注冊方式: 通過DOM節(jié)點的屬性添加或者通過node.addEventListener()函數(shù)注冊;
通過DOM節(jié)點的屬性添加如下所示,節(jié)點的屬性采用on后面緊接event name的形式,比如onclick, onload;?
1 <html> 2 <head> 3 <script type="text/javascript"> 4 function listener(e){ 5 alert("hello world!"); 6 } 7 </script> 8 </head> 9 <body> 10 <button onclick="listener(event)">click</button> 11 </body> 12 </html>?
通過addEventListener()函數(shù)注冊的形式如下, 其完整的形式是:target.addEventListener(type,?listener[,?useCapture]);其中type為事件類型,listener為響應(yīng)函數(shù), useCapture表示是否在capture階段觸發(fā),如果不指定,則為false;?
1 <div> 2 <button id="button">button</button> 3 <script type="text/javascript"> 4 document.getElementById('button').addEventListener("click", listener); 5 </script> 6 </div>?
?
?WebKit中事件相關(guān)的類關(guān)系如上圖所示:
1. EventTargetDatatMap: 全局映射表,建立了Node與EventTargetData之間的映射關(guān)系 ;
2. EventTargetData: ? 成員變量firingEventIterators是Vector, 用于記錄正在觸發(fā)的事件類型,當(dāng)該Vector非空時,也表示當(dāng)前正處于firing階段; 成員變量eventListenerMap是EventlListenerMap類型;
3. EventlListenerMap:按事件類型分類保存了EventListeners; ?成員變量m_entires是Vector,其中每一項可以簡化為std::pair<EventType, EventListenerVector>類型;
4. JSLazyEventListener: 最終響應(yīng)事件觸發(fā)的對象; 保存了JS執(zhí)行的基本信息(源碼或者JSObject類型的函數(shù)對象);?
?
?第一種情況下,開始事件注冊的時機是發(fā)生在頁面解析階段,當(dāng)創(chuàng)建了button元素以后,解析到onclick屬性時,會根據(jù)屬性值創(chuàng)建對應(yīng)的EventListener; 這種情況下的EventListener僅保存了JS源碼(還沒有轉(zhuǎn)換成JSC虛擬機內(nèi)部的函數(shù)對象), 并將EventListener添加到全局Hash表中;?
?
第二種情況下,JS在虛擬機中執(zhí)行到”addEventListener()"時,會根據(jù)JSBindings建立的映射關(guān)系,最終調(diào)用到WebCore中的native實現(xiàn)Node::addEventListener(), 該函數(shù)會根據(jù)虛擬機中傳遞過來的函數(shù)對象創(chuàng)建EventListener,并在全局Hash表中建立起target node與EventListener(即這里的button)的映射關(guān)系;?
?
?下圖是兩種情況下,事件注冊的流程對比:
?
事件觸發(fā)流程有以下幾個步驟:
1. 找到響應(yīng)事件的target node: 如果是用戶交互事件,通過Hit Test算法確定; ?如果是瀏覽器內(nèi)部生成的事件,一般有固定的響應(yīng)節(jié)點,比如load事件的target node是body節(jié)點;
2. 事件分發(fā):事件在document與target之間按照(capture, at_target, bubble)的順序進行分發(fā),capture按照從根節(jié)點document到子節(jié)點target的路徑,而bubble則相反;
3. 事件響應(yīng):分發(fā)流程中,如果事件分發(fā)到的當(dāng)前節(jié)點注冊了該類型的事件,并且useCapure與事件的分發(fā)的順序一致(即capture階段時,當(dāng)前節(jié)點注冊了useCapture == true的事件), 則進行事件響應(yīng);
? ??事件響應(yīng)分成兩步:
? ? ? ??(1) 從全局映射表中找到當(dāng)前node對應(yīng)的EventListeners;
? ? ? ? (2)將EventListeners封裝的JS(源碼或者JSC的函數(shù)對象)拋到JS虛擬機中執(zhí)行(下圖是mouseup事件的觸發(fā)時序):
?
?
如前所述,屬性中注冊的事件在EventListener中僅保存了源碼,所以開始執(zhí)行之前會對源碼進行必要的轉(zhuǎn)換,格式化成如下形式:
"(function(event) {listener(event)\n})"?簡單來講,事件注冊是建立node與響應(yīng)函數(shù)的映射關(guān)系的過程 ,這種映射關(guān)系基于事件類型進行分類; 而事件觸發(fā)則是基于這種映射關(guān)系,在不同階段(capture, bubble)響應(yīng)注冊函數(shù)的過程;?
?
4 webkit DOM事件分析
原文地址:http://blog.csdn.net/shunzi__1984/article/details/6281631 和 百度文庫,作者:upcshu
?
?
?Dom事件模型可以分為dom0 和dom2兩種事件模型,所以支持JavaScript的瀏覽器都都會支持dom0事件模型,DOM2定義了高級的事件處理API,和DOM0的API相比,有著令人矚目的不同(而且功能更強大).雖然DOM2標(biāo)準(zhǔn)并沒有把已有的API收入其中,但是DOM0級API也沒有被去除.對于基本的事件處理任務(wù),你會覺得使用這些簡單的API更自由一些.
?
DOM2事件模型被除了IE以外的所有瀏覽器支持。
?webkit在這部分的設(shè)計中,較好的這兩種事件模型統(tǒng)一了起來,在注冊的部分,稍有不同,我們知道,dom0的事件監(jiān)聽器是通過html屬性注冊,而dom2是通過類似js elem.addEventListener()的方式?,下面是一個相關(guān)的類圖。
?
EventTarget直接依賴于EventListener,EventListener是一個抽象類,然后具體的監(jiān)聽器在下面派生,注意,JSEventListener,以及JSLazeEventListener是與具體的js解釋引擎有關(guān)系的。那么事件監(jiān)聽器的注冊的流程是怎么樣的了?下面以body的onload為例進行說明?。
解析文檔階段:
對應(yīng)代碼:
?
在這里,首先會使用ScriptEventListener.cpp中的
createAttributeEventListener函數(shù)創(chuàng)建事件監(jiān)聽器(JSLazyEventListener)。
其次,會調(diào)用Document.cpp文件中的setWindowAttributeEventListener函數(shù),在該函數(shù)中,會使用文檔(Document)的DOMWindow對象(實際上是一個EventTarget)的setAttributeEventListener。
如果里面注冊得有,會先清除這個事件類型的注冊。然后再調(diào)用addEventListener。
添加到EventListenerMap的成員EventListenerVector成員中了
?
注冊流程
?
【跟監(jiān)聽器相關(guān)的類圖】
【EventTarget類中3個重要的方法】
? ? ? fireEventListeners響應(yīng)會根據(jù)EventListener來調(diào)用handleEvent
? ? ? addEventListener添加 會去操作EventListenerMap
? ? ? removeEventListener刪除 會去操作EventListenerMap
?
【一切來自于頁面】
當(dāng)創(chuàng)建節(jié)點后,會去執(zhí)行屬性解析,如果有事件,會創(chuàng)建屬性監(jiān)聽器(其中一種監(jiān)聽器),實際上是向保存監(jiān)聽器類型的vector中加入了該類型,以備響應(yīng)的時候查詢是否需要響應(yīng)。(有則響應(yīng),無則用默認(rèn)事件處理方式響應(yīng),最后這一句不知道對不?)
事件觸發(fā)與響應(yīng)
不是正規(guī)流程哦,很奇怪吧!
?
響應(yīng)流程
針對一個頁面而言,當(dāng)有事件發(fā)生時,會先找到該頁面所屬frame,再傳入該Frame下的EventHandler,首先會取得當(dāng)前點中的節(jié)點,其實由EventDispatcher來按照dom事件傳遞標(biāo)準(zhǔn) 傳遞該事件,在某些節(jié)點會處理該事件
實現(xiàn)邏輯(以鼠標(biāo)事件為例):
? 鼠標(biāo)事件發(fā)生
? 根據(jù)鼠標(biāo)事件發(fā)生的位置, 找到對應(yīng)的EventTarget 節(jié)點
? 在EventDispatcher的 ensureEventAncestors函數(shù)中,獲取到所有的父節(jié)點,保存到Vector<EventContext>中;
? 進入事件捕獲階段
? 觸發(fā)當(dāng)前EventTarget的當(dāng)前事件的EventListen
? 進入事件冒泡階段
設(shè)置階段后的操作為該操作。什么冒泡呀,捕獲呀,target階段
1 windowContext.handleLocalEvents(event.get()) 2 m_ancestors[i - 1].handleLocalEvents(event.get()); 3 m_node->handleLocalEvents(event.get()); 4 m_ancestors[i].handleLocalEvents(event.get()); 5 windowContext.handleLocalEvents(event.get());
?
在winlauncher中最終會調(diào)用jsc來處理,暫時不往下看!
執(zhí)行了兩次EventDispatchMediator::dispatchEvent,但是在這兩次之間,在執(zhí)行EventDispatchMediator::dispatchEvent中增加一次調(diào)整的機會(作用何在呢?)
最終在Target階段(說明,事件處理分為三個階段:捕獲,target,冒泡),會執(zhí)行響應(yīng)。如圖可以知道:
?
到底怎么響應(yīng)呢?需要送到事件監(jiān)聽器中去,由事件監(jiān)聽器決定。在EventTarget ::fireEventListeners函數(shù)中,先找到相應(yīng)的事件監(jiān)聽器,接著才是處理該事件。從函數(shù)實現(xiàn)中,可以知道,應(yīng)該使用了jsc來處理。(winlauncher)。
?
實例
?
?
這里需要補充說明的是:這里的事件是標(biāo)簽屬性中,如果變?yōu)閖s代碼中增加監(jiān)聽,這種情況下的執(zhí)行路徑是怎么樣呢?咨詢sekerao,應(yīng)該是js引擎來調(diào)用(回調(diào))webkit 的C++注冊監(jiān)聽器,回歸到當(dāng)前上面討論的思路
?
?Dom2
?
在DOM2高級事件模型中,當(dāng)一個文檔元素(被叫做事件的目標(biāo)(target)對象)觸發(fā)了一個事件,這個目標(biāo)對象的事件處理程序被觸發(fā)
除此之外,該目標(biāo)對象的每一個祖輩元素都有一個或者兩個機會去處理該事件.事件傳播的過程包括三個階段.
在DOM2模型中,你可以為一個特定對象的一個特定類型事件注冊任意數(shù)量的事件處理程序.
?
【備注】
其實webkit實現(xiàn)了兩個標(biāo)準(zhǔn),dom0與dom2只是標(biāo)準(zhǔn)而已,從標(biāo)準(zhǔn)角度來說,區(qū)別很大,可以說webkit實現(xiàn)的dom2標(biāo)準(zhǔn)。
?
注冊流程
響應(yīng)流程
默認(rèn)處理
點擊一個select框,這里主要想知道 事件處理流程(與js事件處理的思路 做比較)
?
?? Dom事件傳遞
Dom事件傳遞 ?主要關(guān)注“事件流”。事件流的三個階段,如下圖:
?
默認(rèn)情況下,還是在bubbling phase來處理祖先節(jié)點的事件響應(yīng)!
測試用例:file:///C:/Users/jackyshu/Desktop/js_study/js_event_handle.html(參考前面)
是目標(biāo)節(jié)點先響應(yīng),接著通過冒泡過程,祖先節(jié)點接著處理事件!
?
【代碼走讀】
dom事件傳遞過程,主要在如下函數(shù)中完成:
1 bool EventDispatcher::dispatchEvent(PassRefPtr<Event> event)第一步:設(shè)置目標(biāo)節(jié)點
1 event->setTarget(eventTargetRespectingSVGTargetRules(m_node.get()));第二步:求以該目標(biāo)節(jié)點的祖先數(shù)組(vector,最后一個節(jié)點為樹根)。
ensureEventAncestors(event.get());具體實現(xiàn)如下:
1 while (true) { 2 bool isSVGShadowRoot = ancestor->isSVGShadowRoot(); 3 if (isSVGShadowRoot || ancestor->isShadowRoot()) { 4 if (determineDispatchBehavior(event, ancestor) == StayInsideShadowDOM) 5 return; 6 #if ENABLE(SVG) 7 ancestor = isSVGShadowRoot ? ancestor->svgShadowHost() : ancestor->shadowHost(); 8 #else 9 ancestor = ancestor->shadowHost(); 10 #endif 11 if (!shouldSkipNextAncestor) 12 target = ancestor; 13 } else 14 ancestor = ancestor->parentNodeGuaranteedHostFree(); 15 16 if (!ancestor) 17 return; 18 19 #if ENABLE(SVG) 20 // Skip SVGShadowTreeRootElement. 21 shouldSkipNextAncestor = ancestor->isSVGShadowRoot(); 22 if (shouldSkipNextAncestor) 23 continue; 24 #endif 25 // FIXME: Unroll the extra loop inside eventTargetRespectingSVGTargetRules into this loop. 26 m_ancestors.append(EventContext(ancestor, eventTargetRespectingSVGTargetRules(ancestor), target)); 27 }第三步:進入捕獲階段,進行相應(yīng)事件處理(從上至下)
1 event->setEventPhase(Event::CAPTURING_PHASE); 2 3 if (windowContext.handleLocalEvents(event.get()) && event->propagationStopped()) 4 goto doneDispatching; 5 6 for (size_t i = m_ancestors.size(); i; --i) { 7 m_ancestors[i - 1].handleLocalEvents(event.get()); 8 if (event->propagationStopped()) 9 goto doneDispatching; 10 }第四步:進入目標(biāo)階段,進行相應(yīng)事件處理
1 event->setEventPhase(Event::AT_TARGET); 2 event->setTarget(originalTarget.get()); 3 event->setCurrentTarget(eventTargetRespectingSVGTargetRules(m_node.get())); 4 m_node->handleLocalEvents(event.get()); 5 if (event->propagationStopped()) 6 goto doneDispatching;第五步:進入冒泡階段,進行相應(yīng)事件處理(從下至上)
1 if (event->bubbles() && !event->cancelBubble()) { 2 // Trigger bubbling event handlers, starting at the bottom and working our way up. 3 event->setEventPhase(Event::BUBBLING_PHASE); 4 5 size_t size = m_ancestors.size(); 6 for (size_t i = 0; i < size; ++i) { 7 m_ancestors[i].handleLocalEvents(event.get()); 8 if (event->propagationStopped() || event->cancelBubble()) 9 goto doneDispatching; 10 } 11 windowContext.handleLocalEvents(event.get()); 12 }?
捕獲階段? 為什么沒有做什么事情呢,由什么決定呢?
跟蹤發(fā)現(xiàn),由于RareData為空,所以直接不執(zhí)行了!
什么是RareData呢?
?
代碼中,還能夠發(fā)現(xiàn)另外一個奇怪的問題:
事件流中采用的事件處理函數(shù)為:handleLocalEvents。
另外,如果該過程不處理的話,還有默認(rèn)事件處理函數(shù)為:defaultEventHandler
還有一個windowContext.handleLocalEvents 這個函數(shù)到底在干什么呢?
?
跟蹤
file:///C:/Users/jackyshu/Desktop/js_study/js_event_handle.html ?點擊第一幅圖,堆棧圖
?
?
從最后這個截圖知道,Node::handleLocalEvents 是當(dāng)前按下節(jié)點的響應(yīng)。
JSEventListener::handleEvent最終響應(yīng)該點擊事件,彈出一個警告框。涉及js代碼的執(zhí)行,跟js引擎相關(guān)了!在此不深入!
?
隨后是祖先節(jié)點對點擊事件的響應(yīng),按照冒泡的順序執(zhí)行!
?
?
注意EventDispatcher::dispatchEvent是一個靜態(tài)方法哦,Node類在傳遞事件的時候,都是調(diào)用的這個,所以能夠反映什么呢?
這個EventContent是干啥的呢?
還需要做一個測試頁面,測試捕獲和冒泡的問題!
?
? Dom事件響應(yīng)
事件響應(yīng)在捕獲還是冒泡階段,由addEventListener的第三個參數(shù)來決定的!
如果useCapture為false,則冒泡階段響應(yīng);反之,在捕獲階段響應(yīng)!
Webkit內(nèi)部,默認(rèn)就是在冒泡階段響應(yīng),請看代碼:
?
難道說我們不能夠在網(wǎng)頁中來控制么?讓它在捕獲階段來響應(yīng)?
?
把事件捕獲和冒泡的過程統(tǒng)稱為事件的傳播
事件的傳播是可以阻止的:
- 在W3c中,使用stopPropagation()方法
- 在IE下設(shè)置cancelBubble = true;
在捕獲的過程中stopPropagation();后,后面的冒泡過程也不會發(fā)生了~ 在IE中則使用cancelBubble(IE中只有冒泡,沒有捕獲)
3.阻止事件的默認(rèn)行為,例如click <a>后的跳轉(zhuǎn)~
- 在W3c中,使用preventDefault()方法;
- 在IE下設(shè)置window.event.returnValue = false;
不是所有的事件都能冒泡,例如:blur、focus、load、unload,(這個測試過load事件哦)還有堆棧圖呢,哈哈!
?
在跟蹤捕獲過程處理事件的時候,發(fā)現(xiàn)基本上是在這個過程就返回了。
?
對比冒泡過程
?
就是一個條件的差別而已,導(dǎo)致是否響應(yīng)!hasRareData()
RareData到底是什么意思呢?
NodeFlags 第17個
該堆棧圖是為了說明什么時候設(shè)置RareData的,這個會影響捕獲的問題。
能夠看到凡是有事件注冊的地方 ?應(yīng)該都會去設(shè)置這個標(biāo)識!
?
EventTarget::fireEventListeners
在這個里面處理的時候,也會去專門繞過捕獲階段,最終才能夠到冒泡階段!? 不知道這樣做的意義是什么?
?
參考資料:
(1)http://blog.csdn.net/shunzi__1984/article/details/6281631
(2)http://www.starwd.com/?p=340
(3)http://www.cnblogs.com/eoiioe/archive/2009/02/10/1387442.html
(4)http://en.wikipedia.org/wiki/DOM_events 一篇英文 值得學(xué)習(xí)
(5)http://blog.csdn.net/bestlxm/article/details/7450630
?
?
DOMWindow 中,addEventListener實現(xiàn)如下:
1 bool DOMWindow::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture) 2 { 3 if (!EventTarget::addEventListener(eventType, listener, useCapture)) 4 return false; 5 6 if (Document* document = this->document()) 7 document->addListenerTypeIfNeeded(eventType); 8 9 if (eventType == eventNames().unloadEvent) 10 addUnloadEventListener(this); 11 else if (eventType == eventNames().beforeunloadEvent && allowsBeforeUnloadListeners(this)) 12 addBeforeUnloadEventListener(this); 13 #if ENABLE(DEVICE_ORIENTATION) 14 else if (eventType == eventNames().devicemotionEvent && frame() && frame()->page() && frame()->page()->deviceMotionController()) 15 frame()->page()->deviceMotionController()->addListener(this); 16 else if (eventType == eventNames().deviceorientationEvent && frame() && frame()->page() && frame()->page()->deviceOrientationController()) 17 frame()->page()->deviceOrientationController()->addListener(this); 18 #endif 19 20 return true; 21 }?
5 QT WebKit 鼠標(biāo)引發(fā)事件處理
轉(zhuǎn)自:http://mobile.51cto.com/symbian-287629.htm
?
QT WebKit鼠標(biāo)引發(fā)事件處理是本文要介紹的內(nèi)容,主要是來學(xué)習(xí)QT WebKit的事件處理的機制,以鼠標(biāo)事件為案例,具體內(nèi)容的詳解來看本文。先來貼個圖,來看:
? ? ? ? ? ? ? ? ? ? ? ? ? ?Figure 1. JavaScript onclick event
?
先看一段簡單的HTML文件。在瀏覽器里打開這個文件,將看到兩張照片。把鼠標(biāo)移動到第一張照片,點擊鼠標(biāo)左鍵,將自動彈出一個窗口,上書“World”。但是當(dāng)鼠標(biāo)移動到第二張照片,或者其它任何區(qū)域,點擊鼠標(biāo),卻沒有反應(yīng)。關(guān)閉“World”窗口,自動彈出第二個窗口,上書“Hello”。
1 <html> 2 <script type="text/javascript"> 3 function myfunction(v) 4 { 5 alert(v) 6 } 7 </script> 8 9 <body onclick="myfunction('Hello')"> 10 <p> 11 <img onclick="myfunction('World')" height="250" width="290" 12 src="http://www.dirjournal.com/info/wp-content/uploads/2009/02/antarctica_mountain_mirrored.jpg"> 13 <p> 14 <img height="206" width="275" 15 src="http://media-cdn.tripadvisor.com/media/photo-s/01/26/f4/eb/hua-shan-hua-mountain.jpg"> 16 </body> 17 </html>?
這段HTML文件沒有什么特別之處,所有略知一點HTML的人,估計都會寫。但是耳熟能詳,未必等于深入了解。不妨反問自己幾個問題,
1、瀏覽器如何知道,是否鼠標(biāo)的位置,在第一個照片的范圍內(nèi)?
2、假如修改一下HTML文件,把第一張照片替換成另一張照片,前后兩張照片的尺寸不同。在瀏覽器里打開修改后的文件,我們會發(fā)現(xiàn),能夠觸發(fā)彈出窗口事件的區(qū)域面積,隨著照片的改變而自動改變。瀏覽器內(nèi)部,是通過什么樣的機制,自動識別事件觸發(fā)區(qū)域的?
3、Onclick 是HTML的元素屬性(Element attribute),還是JavaScript的事件偵聽器(EventListener)?換而言之,當(dāng)用戶點擊鼠標(biāo)以后,負(fù)責(zé)處理onclick事件的,是Webkit 還是JavaScript Engine?
4、Alert() 是HTML定義的方法,還是JavaScript提供的函數(shù)?誰負(fù)責(zé)生成那兩個彈出的窗口,是Webkit還是JavaScript Engine?
5、注意到有兩個οnclick="myfunction(...)",當(dāng)用戶在第一張照片里點擊鼠標(biāo)的時候,為什么是先后彈出,而不是同時彈出?
6、除了PC上的瀏覽器以外,手機是否也可以完成同樣的事件及其響應(yīng)?假如手機上沒有鼠標(biāo),但是有觸摸屏,如何把onclick定義成用手指點擊屏幕?
7、為什么需要深入了解這些問題? 除了滿足好奇心以外,還有沒有其它目的?
?
? ? ? ? ? ? ? ? ? ?Figure 2. Event callback stacks
?
當(dāng)用戶點擊鼠標(biāo),在OS語匯里,這叫發(fā)生了一次中斷(interrupt)。
系統(tǒng)內(nèi)核(kernel) 如何偵聽以及處理interrupt,不妨參閱“Programming Embedded Systems” 一書,Chapter 8. Interrupts。
這里不展開介紹,有兩個原因:
? ? ? 1. 這些內(nèi)容很龐雜,而且與本文主題不太相關(guān)。
? ? ? 2. 從Webkit角度看,它不必關(guān)心interrupt 以及interrupt handling 的具體實現(xiàn),
因為Webkit建筑在GUI Toolkit之上,而GUI Toolkit已經(jīng)把底層的interrupt handling,嚴(yán)密地封裝起來。
Webkit只需要調(diào)用GUI Toolkit 的相關(guān)APIs,就可以截獲鼠標(biāo)的點擊和移動,鍵盤的輸入等等諸多事件。
所以,本文著重討論Figure 2 中,位于頂部的Webkit和JavaScript兩層。
?
不同的操作系統(tǒng),有相應(yīng)的GUI Toolkit。
GUI Toolkit提供一系列APIs,方便應(yīng)用程序去管理各色窗口和控件,以及鼠標(biāo)和鍵盤等等UI事件的截獲和響應(yīng)。
1、微軟的Windows操作系統(tǒng)之上的GUI Toolkit,是MFC(Microsoft Fundation Classes)。
2、Linux操作系統(tǒng)GNOME環(huán)境的GUI Toolkit,是GTK+.
3、Linux KDE環(huán)境的,是QT。
4、Java的GUI Toolkit有兩個,一個是Sun Microsystem的Java Swing,另一個是IBM Eclipse的SWT。
?
Swing對native的依賴較小,它依靠Java 2D來繪制窗口以及控件,而Java 2D對于native的依賴基本上只限于用native library畫點畫線著色。
SWT對native的依賴較大,很多人把SWT理解為Java通過JNI,對MFC,GTK+和QT進行的封裝。
這種理解雖然不是百分之百準(zhǔn)確,但是大體上也沒錯。
?
有了GUI Toolkit,應(yīng)用程序處理鼠標(biāo)和鍵盤等等UI事件的方式,就簡化了許多,
只需要做兩件事情:
? ? ? 1. 把事件來源(event source),與事件處理邏輯(event listener) 綁定。
? ? ? 2. 實現(xiàn)事件處理邏輯的細節(jié)。
?
Figure 3 顯示的是Webkit如何綁定event source和event listener。
Figure 4 顯示的是Webkit如何調(diào)用JavaScript Engine,解析并執(zhí)行事件處理邏輯。
?
首先看看event source,注意到在HTML文件里有這么一句
<img onclick="myfunction('World')" height="250" width="290" src=".../antarctica_mountain_mirrored.jpg">這句話里"<img>" 標(biāo)識告訴Webkit,需要在瀏覽器頁面里擺放一張照片
? ? ? ? ? ?"src" 屬性明確了照片的來源
? ? ? ? ? ?"height, width" 明確了照片的尺寸
? ? ? ? ? ?"onclick" 屬性提醒Webkit,當(dāng)用戶把鼠標(biāo)移動到照片顯示的區(qū)域,并點擊鼠標(biāo)時(onclick),需要有所響應(yīng)。
響應(yīng)的方式定義在“onclick”屬性的值里面,也就是“myfunction('World')”。
?
當(dāng)Webkit解析這個HTML文件時,它依據(jù)這個HTML文件生成一棵DOM Tree,和一棵Render Tree。
對應(yīng)于這一句<img>語句,在DOM Tree里有一個HTMLElement節(jié)點,相應(yīng)地,在Render Tree里有一個RenderImage節(jié)點。
在layout() 過程結(jié)束后,根據(jù)<img>語句中規(guī)定的height和width,確定了RenderImage的大小和位置。
由于 Render Tree的RenderImage節(jié)點,與DOM Tree的HTMLElement節(jié)點一一對應(yīng),所以HTMLElement節(jié)點所處的位置和大小也相應(yīng)確定。
?
因為onclick事件與這個HTMLElement節(jié)點相關(guān)聯(lián),所以這個HTMLElement節(jié)點的位置和大小確定了以后,點擊事件的觸發(fā)區(qū)域也就自動確定。
假如修改了HTML 文件,替換了照片,經(jīng)過layout() 過程以后,新照片對應(yīng)的HTMLElement節(jié)點,它的位置和大小也自動相應(yīng)變化
所以,點擊事件的觸發(fā)區(qū)域也就相應(yīng)地自動變化。
?
在onclick屬性的值里,定義了如何處理這個事件的邏輯。
有兩種處理事件的方式:
? ? ? 1. 直接調(diào)用HTML DOM method
? ? ? 2. 間接調(diào)用外設(shè)的Script。
οnclick="alert('Hello')",是第一種方式。
alert()是W3C制訂的標(biāo)準(zhǔn)的 HTML DOM methods之一。
除此以外,也有稍微復(fù)雜一點的methods,譬如可以把這一句改成,<img οnclick="document.write('Hello')">。
本文的例子,οnclick="myfunction('world')",是第二種方式,間接調(diào)用外設(shè)的Script。
?
外設(shè)的script有多種,最常見的是JavaScript
另外,微軟的VBScript和Adobe的ActionScript,在一些瀏覽器里也能用
即便是JavaScript,也有多種版本,各個版本之間,語法上存在一些差別
為了消弭這些差別,降低JavaScript使用者,以及 JavaScript Engine開發(fā)者的負(fù)擔(dān),ECMA(歐洲電腦產(chǎn)聯(lián))試圖制訂一套標(biāo)準(zhǔn)的JavaScript規(guī)范,稱為ECMAScript。
?
各個瀏覽器使用的JavaScript Engine不同
1、微軟的IE瀏覽器,使用的JavaScript Engine是JScript Engine,渲染機是Trident。
2、Firefox瀏覽器,使用的JavaScript Engine是TraceMonkey,TraceMonkey的前身是SpiderMonkey,渲染機是Gecko。TraceMonkey JavaScript Engine借用了Adobe的Tamarin的部分代碼,尤其是Just-In-Time即時編譯機的代碼。而Tamarin也被用在Adobe Flash的Action Engine中。
3、Opera瀏覽器,使用的JavaScript Engine是Futhark,它的前身是Linear_b,渲染機是Presto。
4、Apple的Safari瀏覽器,使用的JavaScript Engine是SquirrelFish,渲染機是Webkit。
5、Google的Chrome瀏覽器,使用的JavaScript Engine是V8,渲染機也是Webkit。
6、Linux的KDE和GNOME環(huán)境中可以使用Konqueror瀏覽器,這個瀏覽器使用的JavaScript Engine是JavaScriptCore,前身是KJS,渲染機也是Webkit。
?
同樣是Webkit渲染機,可以調(diào)用不同的JavaScript Engine。
之所以能做到這一點,是因為Webkit的架構(gòu)設(shè)計,在設(shè)置JavaScript Engine的時候,利用代理器,采取了松散的調(diào)用方式
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Figure 3. The listener binding of Webkit
Figure 3 詳細描繪了Webkit 設(shè)置JavaScript Engine 的全過程
在Webkit 解析HTML文件,生成DOM Tree 和Render Tree 的過程中
當(dāng)解析到 <img οnclick="..." src="..."> 這一句的時候,生成DOM Tree中的 HTMLElement 節(jié)點,以及Render Tree 中 RenderImage 節(jié)點
如前文所述。在生成HTMLElement 節(jié)點的過程中,因為注意到有onclick屬性,Webkit決定需要給 HTMLElement 節(jié)點綁定一個 EventListener,參見Figure 3 中第7步
?
Webkit 把所有EventListener 的創(chuàng)建工作,交給Document 統(tǒng)一處理,類似于 Design Patterns中,Singleton 的用法
也就是說,DOM Tree的根節(jié)點 Document,掌握著這個網(wǎng)頁涉及的所有EventListeners。
有趣的是,當(dāng)Document 接獲請求后,不管針對的是哪一類事件,一律讓代理器 (kjsProxy) 生成一個JSLazyEventListener。
之所以說這個實現(xiàn)方式有趣,是因為有幾個問題需要特別留意:
1、一個HTMLElement節(jié)點,如果有多個類似于onclick的事件屬性,那么就需要多個相應(yīng)的EventListener object instances與之綁定。
2、每個節(jié)點的每個事件屬性,都對應(yīng)一個獨立的EventListener object instance。不同節(jié)點不共享同一個 EventListener object instance。
? ? 即便同一個節(jié)點中,不同的事件屬性,對應(yīng)的也是不同的EventListener object instances。
? ? 這是一個值得批評的地方。
? ? 不同節(jié)點不同事件對應(yīng)彼此獨立的EventListener object instances,這種做法給不同節(jié)點之間的信息傳遞,造成了很大障礙。
? ? 反過來設(shè)想一下,如果能夠有一種機制,讓同一個object instance,穿梭于多個HTMLElement Nodes之間,那么瀏覽器的表現(xiàn)能力將會大大增強
? ? 屆時,將會出現(xiàn)大量的前所未有的匪夷所思的應(yīng)用。
3、DOM Tree的根節(jié)點,Document,統(tǒng)一規(guī)定了用什么工具,去解析事件屬性的值,以及執(zhí)行這個屬性值所定義的事件處理邏輯
? ? 如前文所述,事件屬性的值,分成HTML DOM methods 和JavaScript 兩類。
? ? 但是不管某個HTMLElement節(jié)點的某個事件屬性的值屬于哪一類,Document 一律讓 kjsProxy代理器,生成一個 EventListener。
? ? 看看這個代理器的名字就知道,kjsProxy生成的 EventListener,一定是依托JavaScriptCore Engine,也就是以前的KJS JavaScript Engine,來執(zhí)行事件處理邏輯的。
? ? 核實一下源代碼,這個猜想果然正確。
4、如果想把JavaScriptCore 替換成其它JavaScript Engine,例如Google 的V8,不能簡單地更改configuration file,而需要修改一部分源代碼。
? ? 所幸的是,Webkit的架構(gòu)設(shè)計相當(dāng)清晰,所以需要改動部分不多,關(guān)鍵部位是把 Document.{h,cpp} 以及其它少數(shù)源代碼中,涉及kjsProxy 的部分,改成其它Proxy即可。
5、kjsProxy 生成的EventListener,是JSLazyEventListener。
? ? 解釋一下JSLazyEventListener 命名的寓意,JS容易理解,意思是把事件處理邏輯,交給JavaScript engine 負(fù)責(zé)
? ? 所謂 lazy 指的是,除非用戶在照片顯示區(qū)域點擊了鼠標(biāo),否則,JavaScript Engine 不主動處理事件屬性的值所規(guī)定的事件處理邏輯
?
與 lazy做法相對應(yīng)的是JIT即時編譯,譬如有一些JavaScript Engine
在用戶尚沒有觸發(fā)任何事件以前,預(yù)先編譯了所有與該網(wǎng)頁相關(guān)的JavaScript
這樣,當(dāng)用戶觸發(fā)了一個特定事件,需要調(diào)用某些 JavaScript functions時,運行速度就會加快
當(dāng)然,預(yù)先編譯會有代價,可能會有一些JavaScript functions,雖然編譯過了,但是從來沒有被真正執(zhí)行過。
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??Figure 4. The event handling of Webkit
當(dāng)解析完HTML文件,生成了完整的DOM Tree 和Render Tree 以后,Webkit就準(zhǔn)備好去響應(yīng)和處理用戶觸發(fā)的事件了。
響應(yīng)和處理事件的整個流程,如Figure 4所描述。整個流程分成兩個階段,
1、尋找 EventTargetNode
當(dāng)用戶觸發(fā)某個事件,例如點擊鼠標(biāo),根據(jù)鼠標(biāo)所在位置,從Render Tree的根節(jié)點開始,一路搜索到鼠標(biāo)所在位置對應(yīng)的葉子節(jié)點。
Render Tree根節(jié)點對應(yīng)的是整個瀏覽器頁面,而葉子節(jié)點對應(yīng)的區(qū)域面積最小。
?
從Render Tree根節(jié)點,到葉子節(jié)點,沿途每個Render Tree Node,都對應(yīng)一個DOM Tree Node
這一串DOM Tree Nodes中,有些節(jié)點響應(yīng)用戶觸發(fā)的事件,另一些不響應(yīng)。
例如在本文的例子中,<body> tag 對應(yīng)的DOM Tree Node,和第一張照片的<img> tag 對應(yīng)的DOM Tree Node,都對onclick事件有響應(yīng)。
?
第一階段結(jié)束時,Webkit得到一個EventTargetNode,這個節(jié)點是一個DOM Tree Node,而且是對事件有響應(yīng)的DOM Tree Node
如果存在多個DOM Tree Nodes對事件有響應(yīng),EventTargetNode是那個最靠近葉子的中間節(jié)點。
?
2、執(zhí)行事件處理邏輯
如果對于同一個事件,有多個響應(yīng)節(jié)點,那么JavaScript Engine 依次處理這一串節(jié)點中,每一個節(jié)點定義的事件處理邏輯
事件處理邏輯,以字符串的形式定義在事件屬性的值中
在本文的例子中,HTML文件包含<img οnclick="myfunction('World')">,和<body οnclick="myfunction('Hello')">
這意味著,有兩個DOM Tree Nodes 對onclick事件有響應(yīng),它們的事件處理邏輯分別是myfunction('World') 和myfunction('Hello'),這兩個字符串。
?
當(dāng)JavaScript Engine 獲得事件處理邏輯的字符串后,它把這個字符串,根據(jù)JavaScript的語法規(guī)則,解析為一棵樹狀結(jié)構(gòu),稱作Parse Tree。
有了這棵Parse Tree,JavaScript Engine就可以理解這個字符串中,哪些是函數(shù)名,哪些是變量,哪些是變量值。
理解清楚以后,JavaScript Engine 就可以執(zhí)行事件處理邏輯了。
本文例子的事件處理過程,如Figure 4中第16步,到第35步所示。
?
本文的例子中,“myfunction('World')" 這個字符串本身并沒有定義事件處理邏輯,而只是提供了一個JavaScript函數(shù)的函數(shù)名,以及函數(shù)的參數(shù)的值。
當(dāng)JavaScript Engine 得到這個字符串以后,解析,執(zhí)行。
執(zhí)行的結(jié)果是得到函數(shù)實體的代碼。
函數(shù)實體的代碼中,最重要的是alert(v) 這一句。JavaScript Engine 把這一句解析成Parse Tree,然后執(zhí)行。
?
注意到本文例子中,對于同一個事件onclick,有兩個不同的DOM Tree Nodes 有響應(yīng)。
處理這兩個節(jié)點的先后順序要么由capture path,要么由bubbling path決定,如Figure 5所示(Figure 5中對應(yīng)的HTML文件,不是本文所引的例子)。
在HTML文件中,可以規(guī)定event.bubbles屬性。如果沒有規(guī)定,那就按照bubbling的順序進行
所以本文的例子,是先執(zhí)行<img>,彈出“World” 的窗口,關(guān)掉“World”窗口后,接著執(zhí)行<body>,彈出“Hello” 的窗口。
?
?
6 webkit里 JS 和DOM事件處理
?文庫的文章,文庫作者:yujiawang2008
1,event傳遞到 js
所有的事件都是以WebViewWndProc作為入口點。
我們以鼠標(biāo)事件為例來分析,其它事件基本類似
在WebView里又對不同類型的事件處理做了分類主要有
鼠標(biāo)事件:handleMouseEvent
鍵盤事件:keyDown, keyUp
在EventHandler類里開始對Event進行派發(fā)
EventHandler::dispatchMouseEvent
在這里EventHandler 是frame的一個對象,見frame.h文件 mutable EventHandler m_eventHandler;
在EventHandler記錄了當(dāng)前dom樹中關(guān)于事件的結(jié)點所有信息,例如,當(dāng)前處于鼠標(biāo)下面的結(jié)點,最后處于鼠標(biāo)下面的結(jié)點,最后處于鼠標(biāo)下面的Scrollbar等。EventHandler里要做的事情就是在有事件發(fā)生的時候找到注冊了該事件的結(jié)點,然后更新這些結(jié)點,并調(diào)用相應(yīng)結(jié)點的事件處理函數(shù)。這些事情是在dom結(jié)點本身結(jié)構(gòu)的支持下完成的,凡是支持事件的dom結(jié)點都是繼承于EventNode,而所有的dom結(jié)點類型都繼承與Node。
在Node里有這樣一個方法dispatchGenericEvent將事件進一步派發(fā)到EventTarget在EventTarget里會觸發(fā)RegisteredEventListener 里注冊的結(jié)點的事件處理函數(shù)
對于js事件,到了這一步又有一個js事件的入口點:
JSEventListener::handleEvent
JSEventListener從其類型的命名可以看出它是一個js事件監(jiān)聽者對象,既然有js事件監(jiān)聽者,那可以想象就有更一般的事件監(jiān)聽者,在webcore里也確實是這樣。
上面是從處理事件的流程分析了這個過程,可能大家還會有疑問,事件是怎么派發(fā)到j(luò)s監(jiān)聽者的?下面分析事件監(jiān)聽者注冊的過程。
?
在html解析的時候即 HTMLParser::parseToken(Token* t),分析到一個token有事件屬性,就會將該屬性添加到相應(yīng)的存儲結(jié)構(gòu)里,在這里我們只分析事件屬性,在分析到該token有event屬性的時候(形如<button οnclick="myfunction('World')">)會創(chuàng)建一個EventListener,見
WebKit\WebCore\html\HTMLElement.cpp HTMLElement::parseMappedAttribute(MappedAttribute *attr)方法,
setAttributeEventListener(eventNames().clickEvent, createAttributeEventListener(this, attr));
這里是js事件監(jiān)聽者,在attr里保存了js代碼(myfunction('World'))。
接著會將創(chuàng)建出來的事件監(jiān)聽者加入到eventListenerMap,見EventTarget::addEventListener方法。
這樣該結(jié)點就對該事件具有了監(jiān)聽的能力。
結(jié)合上面的事件處理流程的分析知道在事件處理的時候是從該結(jié)點去取得其監(jiān)聽器,然后調(diào)用相應(yīng)的處理方法的,這樣事件到j(luò)s的事件處理入口點的過程是分析清楚了。
再看js引擎是怎么處理這些事件并執(zhí)行相應(yīng)的js代碼的,繼續(xù)看JSEventListener::handleEvent方法:
在該方法的開始有這樣一行代碼
JSObject* jsFunction = this->jsFunction(scriptExecutionContext);
這里的jsFunction取的就是其成員變量mutable JSC::JSObject* m_jsFunction;
可以看到它是一個JSObject對象,如果清楚了它是怎么被創(chuàng)建的話那也就清楚了事件是怎么觸發(fā)js的代碼執(zhí)行的了,(如果還不清楚,那得熟悉jscore了)。
我們繼續(xù)看m_jsFunction的創(chuàng)建,在\WebCore\bindings\js\ScriptEventListener.cpp 的方法createAttributeEventListener(這個方法就是在上面分析token的時候會調(diào)用的)里可以找到其創(chuàng)建的過程這里涉及到JSLazyEventListener這個類,熟悉jscore的人看到這里應(yīng)該就啥都清楚了,我把其定義貼在這里
?
1 mutable String m_functionName; 2 mutable String m_eventParameterName; 3 mutable String m_code; 4 mutable bool m_parsed; 5 mutable String m_sourceURL; 6 int m_lineNumber; 7 Node* m_originalNode;?
m_jsFunction 里就包含了這些數(shù)據(jù),既然有了這些數(shù)據(jù),那么執(zhí)行js代碼只是一個call的調(diào)用了,這在JSEventListener::handleEvent里有使用實例。在這就不贅述了。
?
2.Js處理dom節(jié)點
js是怎么操作dom樹里的這些結(jié)點呢,其實我們實現(xiàn)了js調(diào)car,其下面的實現(xiàn)基本原理是一樣的,car具有反射的能力,所以從腳本里去調(diào)一個car里的方法,要先對car進行反射,然后取其類,方法及參數(shù)等信息,然后構(gòu)造一個js腳本對象。
而在webcore里dom對象并不沒有反射的機制,因此也就不具備反射的能力,那它是腳本是怎么認(rèn)識dom這些對象的呢,既然反射是為了得到這些信息再去構(gòu)造js對象,那如果我直接拿這些系信息構(gòu)建js對象行不行呢,那是肯定行的,jscoer的demo就這樣寫的。而此時肯定有人會有疑問js怎么知道這些類信息的呢,回答很簡單,dom標(biāo)準(zhǔn)定義了,dom樹必須提供這些類和接口,想想看這些類和接口也不少呢,做引擎的人覺得這樣一個一個自己去寫,也太傻冒了吧,于是就寫了一個per腳本程序來生成包含這些信息的類,并一面是調(diào)用dom樹的方法,一面為js提供信息的一些類,者就是webkit里的綁定。
下面我們具體來看一個綁定的實例,那document來分析吧:
Document 是dom樹的根結(jié)點,于是操作dom樹的基本接口,在webcore里其實先就在document.cpp文件里。
Per腳本我不是很熟,所以這里就不分析其怎么用腳本生成上面所述的包含類型信息和調(diào)dom方法的類的過程,以免誤人子弟。我就直接拿生成好的類來分析他們的關(guān)系了。
與document.cpp 對應(yīng)的文件就是JSDocument.cpp ,這個里面也包含了一百多個方法,我就只拿最具有代表性的 GetElementById方法來分析了:
先分析這個文件的結(jié)構(gòu),
在這個文件里有兩個表:
兩個比較重要的函數(shù):
1 bool JSDocumentPrototype::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) 2 bool JSDocumentPrototype::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)?
這兩個函數(shù)的作用是提供給js引擎,在注冊document對象的時候使用的,也就是描述了document對象有哪些方法的表。
在js里注冊一個c/c++對象的過程這里就不贅述了,jscore demo里有一個比較簡單的例子。也就是說js調(diào)到JSDocumentPrototype里的方法的過程和一個普通的腳本調(diào)c方法沒有什么區(qū)別,再看一下在JSDocumentPrototype方法里做了一些什么事情:
1 JSValue JSC_HOST_CALL jsDocumentPrototypeFunctionGetElementById(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 2 { 3 UNUSED_PARAM(args); 4 if (!thisValue.inherits(&JSDocument::s_info)) 5 return throwError(exec, TypeError); 6 JSDocument* castedThisObj = static_cast<JSDocument*>(asObject(thisValue)); 7 Document* imp = static_cast<Document*>(castedThisObj->impl()); 8 const UString& elementId = args.at(0).toString(exec); 9 10 11 JSC::JSValue result = toJS(exec, castedThisObj->globalObject(), WTF::getPtr(imp->getElementById(elementId))); 12 return result; 13 }?
看看這個方法里做的事情也很簡單,就是一些強制轉(zhuǎn)換,然后調(diào)用document對象的里的方法。
其實簡單點理解就是jsobject包裝了一個指針在js引擎里使用,當(dāng)要調(diào)用webcoer里的方法時候就強制轉(zhuǎn)換成dom里的類型對象。
C++里要這樣使用,繼承是最好的實現(xiàn)方式了,所以可以看到webkit里繼承關(guān)系是比較復(fù)雜的。
?
?
7 問題總結(jié)
主要是這兩篇博客的介紹,大家參考下:
http://www.cnblogs.com/hustskyking/p/problem-javascript-event.html
http://www.cnblogs.com/yexiaochai/p/3477715.html
轉(zhuǎn)載于:https://www.cnblogs.com/lfsblack/p/5627809.html
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎!定制產(chǎn)品紅包拿不停!總結(jié)
以上是生活随笔為你收集整理的webkit事件处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【001:这条路很漫长,但出发了就不要想
- 下一篇: 使用Maven编译项目遇到——“mave