什麼是事件驅動?css
事件驅動編程是以事件爲第一驅動的編程模型,模塊被動等待通知(notification),行爲取決於外來的突發事件,是事件驅動的,符合事件驅動式編程(Event-Driven Programming,簡稱EDP)的模式。html
何謂事件?通俗地說,它是已經發生的某種使人關注的事情。在軟件中,它通常表現爲一個程序的某些信息狀態上的變化。基於事件驅動的系統通常提供兩類的內建事件(built-in event):一類是底層事件(low-level event)或稱原生事件(native event),在用戶圖形界面(GUI)系統中這類事件直接由鼠標、鍵盤等硬件設備觸發;一類是語義事件(semantic event),通常表明用戶的行爲邏輯,是若干底層事件的組合。好比鼠標拖放(drag-and-drop)多表示移動被拖放的對象,由鼠標按下、鼠標移動和鼠標釋放三個底層事件組成。前端
還有一類用戶自定義事件(user-defined event)。它們能夠是在原有的內建事件的基礎上進行的包裝,也能夠是純粹的虛擬事件(virtual event)。除此以外,編程者不但能定義事件,還能產生事件。雖然大部分事件是由外界激發的天然事件(natural event),但有時程序員須要主動激發一些事件,好比模擬用戶鼠標點擊或鍵盤輸入等,這類事件被稱爲合成事件(synthetic event)。這些都進一步豐富完善了事件體系和事件機制,使得事件驅動式編程更具滲透性。node
上圖爲一個典型的事件驅動式模型。事件處理器事先在關注的事件源上註冊,後者不按期地發表事件對象,通過事件管理器的轉化(translate)、合併(coalesce)、排隊(enqueue)、分派(dispatch)等集中處理後,事件處理器接收到事件並對其進行相應處理。經過事件機制,事件源與事件處理器之間創建了鬆耦合的多對多關係:一個事件源能夠有多個處理器,一個處理器能夠監聽多個事件源。再換個角度,把事件處理器視爲服務方,事件源視爲客戶方,即是一個client-server模式。每一個服務方與其客戶方之間的會話(session)是異步的,即在處理完一個客戶的請求後沒必要等待下一請求,隨時可切換(switch)到對其餘客戶的服務。jquery
在web環境中事件源由DOM充當,事件管理器對於web開發者來講是透明的,由瀏覽器內部管理,事件處理器即是咱們綁定在dom事件中的回調函數。程序員
Web事件處理流程web
DOM2.0模型將事件處理流程分爲三個階段:1、事件捕獲階段,2、事件目標階段,3、事件起泡階段。如圖:編程
事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,隨着DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程當中,事件相應的監聽函數是不會被觸發的。數組
事件目標:當到達目標元素以後,執行目標元素該事件相應的處理函數。若是沒有綁定監聽函數,那就不執行。瀏覽器
事件起泡:從目標元素開始,往頂層元素傳播。途中若是有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。若是想阻止事件起泡,可使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來組織事件的冒泡傳播。
然而在此末法時代,瀏覽器兩大派別對於事件方面的處理,經常讓前端程序員大傷腦筋,因此任何前端庫首先要對事件機制進行統一。
dojo中的事件綁定
dojo事件體系可以幫咱們解決哪些問題?
dojo中處理瀏覽器事件的代碼位於dojo/on模塊中,在官網中能夠查看該函數的簽名:
其中type能夠是一個事件名稱如:「click」
require(["dojo/on", "dojo/_base/window"], function(on, win){ var signal = on(win.doc, "click", function(){ // remove listener after first event signal.remove(); // do something else... }); });
亦能夠是由逗號分隔的多個事件名組成的字符串,如:"dblclick,click"
require("dojo/on", function(on){ on(element, "dblclick, touchend", function(e){ // handle either event }); });
亦能夠是由冒號分隔"selector:eventType"格式進行事件委託使用的字符串,如:".myClass:click"
require(["dojo/on", "dojo/_base/window", "dojo/query"], function(on, win){ on(win.doc, ".myClass:click", clickHandler); });
亦能夠是一個函數,如:touch.press、on.selector()
require(["dojo/on", "dojo/mouse", "dojo/query!css2"], function(on, mouse){ on(node, on.selector(".myClass", mouse.enter), myClassHoverHandler); });
查看一下on函數的源碼
var on = function(target, type, listener, dontFix){ if(typeof target.on == "function" && typeof type != "function" && !target.nodeType){ // delegate to the target's on() method, so it can handle it's own listening if it wants (unless it // is DOM node and we may be dealing with jQuery or Prototype's incompatible addition to the // Element prototype return target.on(type, listener); } // delegate to main listener code return on.parse(target, type, listener, addListener, dontFix, this); };
on.parse = function(target, type, listener, addListener, dontFix, matchesTarget){ if(type.call){ // event handler function // on(node, touch.press, touchListener); return type.call(matchesTarget, target, listener); } if(type instanceof Array){ // allow an array of event names (or event handler functions) events = type; }else if(type.indexOf(",") > -1){ // we allow comma delimited event names, so you can register for multiple events at once var events = type.split(/\s*,\s*/); } if(events){ var handles = []; var i = 0; var eventName; while(eventName = events[i++]){ handles.push(on.parse(target, eventName, listener, addListener, dontFix, matchesTarget)); } handles.remove = function(){ for(var i = 0; i < handles.length; i++){ handles[i].remove(); } }; return handles; } return addListener(target, type, listener, dontFix, matchesTarget); };
function addListener(target, type, listener, dontFix, matchesTarget){ // event delegation: var selector = type.match(/(.*):(.*)/); // if we have a selector:event, the last one is interpreted as an event, and we use event delegation if(selector){ type = selector[2]; selector = selector[1]; // create the extension event for selectors and directly call it return on.selector(selector, type).call(matchesTarget, target, listener); } // test to see if it a touch event right now, so we don't have to do it every time it fires if(has("touch")){ if(touchEvents.test(type)){ // touch event, fix it listener = fixTouchListener(listener); } if(!has("event-orientationchange") && (type == "orientationchange")){ //"orientationchange" not supported <= Android 2.1, //but works through "resize" on window type = "resize"; target = window; listener = fixTouchListener(listener); } } if(addStopImmediate){ // add stopImmediatePropagation if it doesn't exist listener = addStopImmediate(listener); } // normal path, the target is |this| if(target.addEventListener){ // the target has addEventListener, which should be used if available (might or might not be a node, non-nodes can implement this method as well) // check for capture conversions var capture = type in captures, adjustedType = capture ? captures[type] : type; target.addEventListener(adjustedType, listener, capture); // create and return the signal return { remove: function(){ target.removeEventListener(adjustedType, listener, capture); } }; } type = "on" + type; if(fixAttach && target.attachEvent){ return fixAttach(target, type, listener); } throw new Error("Target must be an event emitter"); }
對於上面的分析咱們能夠得出幾個結論:
var fixAttach = function(target, type, listener){ listener = fixListener(listener); if(((target.ownerDocument ? target.ownerDocument.parentWindow : target.parentWindow || target.window || window) != top || has("jscript") < 5.8) && !has("config-_allow_leaks")){ // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below. // Here we use global redirection to solve the memory leaks if(typeof _dojoIEListeners_ == "undefined"){ _dojoIEListeners_ = []; } var emitter = target[type]; if(!emitter || !emitter.listeners){ var oldListener = emitter; emitter = Function('event', 'var callee = arguments.callee; for(var i = 0; i<callee.listeners.length; i++){var listener = _dojoIEListeners_[callee.listeners[i]]; if(listener){listener.call(this,event);}}'); emitter.listeners = []; target[type] = emitter; emitter.global = this; if(oldListener){ emitter.listeners.push(_dojoIEListeners_.push(oldListener) - 1); } } var handle; emitter.listeners.push(handle = (emitter.global._dojoIEListeners_.push(listener) - 1)); return new IESignal(handle); } return aspect.after(target, type, listener, true); };
關於aspect.after的具體工做原理,請看個人這篇文章:Javascript事件機制兼容性解決方案
接下來咱們看一下委託的處理:
爲document綁定click事件,click事件出發後,判斷event.target是否知足選擇符「button.myclass」,若知足則執行clickHandler。爲何要判斷event.target是否知足選擇條件,document下可能有a、也可能有span,咱們只須要將a的click委託給document,因此要判斷是否知足選擇條件。委託過程的處理主要有兩個函數來解決:on.selector、on.matches.
紅框部分就是判斷event.target是否匹配選擇符,若是匹配則觸發事件回調clickHandler.
on.matches = function(node, selector, context, children, matchesTarget) { // summary: // Check if a node match the current selector within the constraint of a context // node: DOMNode // The node that originate the event // selector: String // The selector to check against // context: DOMNode // The context to search in. // children: Boolean // Indicates if children elements of the selector should be allowed. This defaults to // true // matchesTarget: Object|dojo/query? // An object with a property "matches" as a function. Default is dojo/query. // Matching DOMNodes will be done against this function // The function must return a Boolean. // It will have 3 arguments: "node", "selector" and "context" // True is expected if "node" is matching the current "selector" in the passed "context" // returns: DOMNode? // The matching node, if any. Else you get false // see if we have a valid matchesTarget or default to dojo/query matchesTarget = matchesTarget && matchesTarget.matches ? matchesTarget : dojo.query; children = children !== false; // there is a selector, so make sure it matches if(node.nodeType != 1){ // text node will fail in native match selector node = node.parentNode; } while(!matchesTarget.matches(node, selector, context)){ if(node == context || children === false || !(node = node.parentNode) || node.nodeType != 1){ // intentional assignment return false; } } return node; }
對比dojo與jquery的事件處理過程,能夠發現jQuery在事件存儲上更上一籌:
dojo直接綁定到dom元素上,jQuery並無將事件處理函數直接綁定到DOM元素上,而是經過.data存儲在緩存.cahce上。
聲明綁定的時候:
執行綁定的時候:
以上就是dojo事件模塊的主要內容,若是結合Javascript事件機制兼容性解決方案來看的話,更有助於理解dojo/on模塊。
若是您以爲這篇文章對您有幫助,請不吝點擊一下右下方的推薦,謝謝!
參考文章: