11.事件系統

事件系統是一個框架很是重要的部分,用於響應用戶的各類行爲。
瀏覽器提供了3個層次的api,用於響應用戶的各類行爲。css


1.最原始的是寫在元素標籤內。
2.再次是腳本內,以el.onXXX = function綁定的方式,統稱爲DOM0事件系統。
3.最後是多投事件系統,一個元素的同一類型事件能夠綁定多個回調,統常稱爲DOM2事件系統。html

因爲瀏覽器大戰,現存兩套API。node

IE與opera:
綁定事件:el.attachEvent("on"+ type, callback)
卸載事件:el.detachEvent("on"+ type. callback)
建立事件:el.document.createEventObject()
派發事件:el.fireEvent(type,event)jquery

w3c:git

綁定事件:el.addEventListener(type,callback,[phase])
卸載事件:el.removeEventListener(type,callback,[phase])
建立事件:el.createEvent(types)
初始化事件:event.initEvent()
派發事件:el.dispatchEvent(event)github

從api的數量與形式來看,w3c提供的複雜不少,相對於也強大不少,下面咱們將逐一分析web

首先咱們先來幾個簡單的例子,不必動用框架。不過事實上,整個事件系統就創建在它們的基礎上。chrome

複製代碼
    function addEvent (el, callback, useCapture) {
        if(el.dispatchEvent){//w3c優先
            el.addEventListener(type, callback, !!useCapture );
        } else {
            el.attachEvent( "on"+ type, callback );
        }
        return callback; //返回callback方便卸載時用
    }

    function removeEvent (el, type, callback, useCapture) {
        if (el.dispatchEvent) { //w3c優先
            el.removeEventListener (type, callback, !!useCapture);
        } else {
            el.detachEvent( "on"+type, callback )
        }
    }

    function fireEvent (el, type, args, event) {
        args = args || {}
        if (el.dispatchEvent) { //w3c優先
            event = document.createEvent("HTMLEvents");
            event.initEvent(type, true, true);
        } else {
            event = document.createEventObject();
        }

        for (var i in args) if (args.hasOwnProperty(i)) {
            event[i] = args[i]
        }

        if (el.dispatchEvent) {
            el.dispatchEvent(event);
        } else {
            el.fireEvent('on'+type , event)
        }
    }
複製代碼

一,onXXX綁定方式的缺陷編程

onXXX既能夠寫在html標籤內,也能夠獨立出來,做爲元素節點的一個特殊屬性來處理,不過做爲一個古老的綁定方式,它很難預料到人們對這方面的擴展。bootstrap

總結下來有如下不足:

1.onXXX對DOM3新增的事件或FF某些私有實現沒法支持,主要有如下事件:

DOMActivate
DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMContentLoaded
DOMElementNameChanged
DOMFocusIn
DOMFocusOut
DOMMouseScroll
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDcouemnt
DOMSubtreeModified
MozMousePixelScroll

2.onXXX只容許元素每次綁定一個回調,重複綁定沖掉以前的綁定
3.onXXX在IE下回調沒有參數,在其餘瀏覽器回調的第一個參數是事件對象。
4.onXXX只能在冒泡階段可用。

二,attachEvent的缺陷

attachEvent是微軟在IE5添加的API,Opera也支持,也對於onXXX方式,它能夠容許同一種元素同一種事件綁定多個回調,也就是所謂多投事件機制。但帶來的麻煩只多很多,存在如下幾點缺陷。

1.ie下只支持微軟的事件系統,DOM3事件一律不支持。
2.IE下attchEvent回調中的this不是指向被綁定元素,而是window!
3.IE下同種事件綁定多個回調時,回調並非按照綁定時的順序依次觸發的!
4.IE下event事件對象與w3c的存在太多差別了,有的沒法對上號,好比currentTarget
5.IE仍是隻支持冒泡階段。


關於事件對象,w3c是大勢所趨,在IE9支持W3c那一套API時,這對咱們實現事件代理很是有幫助。

三,addEventListener的缺陷

w3c這一套API也不是至善至美,畢竟標準老是滯後於現實,剩下的標準瀏覽器各有本身的算盤,它們之間也有不一致的地方。

1.新事件很是不穩定,可能還有普及就開始被廢棄,在早期的sizzle選擇器引擎中,有這麼幾句。

    document.addEventListener("DOMAttrModified", invalidate, false);
    document.addEventListener("DOMNodeInserted", invalidate, false);
    document.addEventListener("DOMNodeRemoved", invalidate, false);

如今這三個事件被廢棄了(準確的說,全部變更事件都完蛋了),FF14和chrome18開始使用MutationObserver代替它。

2.Firefox不支持focusin,focus事件,也不支持DOMFocusIn,DOMFocusOut,如今也不肯意用mouseWheel代替DOMMouseScroll。chrome不支持mouseenter與mouseleave.

所以,不要覺得標準瀏覽器就確定實現了w3c標準事件,全部特徵偵測必不可少。

3.第三個,第四個,第五個標準參數。

第三個參數,useCapture只有很是新的瀏覽器纔是可選項。好比FF6或以前是可選的,爲了安全起見,確保第三個參數爲布爾。

4.事件成員的不穩定。
w3c是從瀏覽器商抄過來的,人家用了這麼久,不免與標準不一致。

ff下event.timeStamp返回0的問題,這個bug,2004年就有人提交了,直到2011年才被修復。

Safari下event.target可能返回文本節點

event.defaultPrevented,event.isTrusted與stopImmediatePropagation方法,以前標準瀏覽器都統一用getpreventDefault方法作這個事情,在jQuery源碼中,發現它是用isDefaultPrevented來處理。

isTrusted屬性用於表示當前事件是不是由用戶行爲觸發,好比是用一個真實的鼠標點擊觸發click事件,仍是由一個腳本生成的(使用事件構造方法,好比event.initEvent)。isTrusted請多關注

5.標準瀏覽器沒有辦法模擬像IE6-8的proprtychange事件。

雖然標準的瀏覽器有input, DOMAttrModified,MutationObserver,但比起propertychange弱爆了。propertychange能夠監聽多種屬性變化,而不僅僅是value值。另外它不區分attribute和property。所以,不管是經過el.xxx = yyy 仍是el.setAttribute(xxx,yyy)都接觸此事件。

http://www.cnblogs.com/rubylouvre/archive/2012/05/26/2519263.html (判斷瀏覽器是否支持DOMAttrModified)

四,Dean Edward的addEvent.js源碼分析

這是一個prototype時代早期出現的一個事件系統。jQuery事件系統源頭。亮點以下:

1.有意識的屏蔽IE與w3c在阻止默認行爲與事件傳播接口的差別。
2.處理ie執行回調時的順序問題
3.處理ie的this指向問題
4.沒有平臺檢測代碼,由於是使用最通用最原始的onXXX構建
5.徹底跨瀏覽器(IE4與NS4)。

此處省略源碼分析

http://dean.edwards.name/weblog/2005/10/add-event/

複製代碼
// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini

// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
    if (element.addEventListener) {
        element.addEventListener(type, handler, false);
    } else {
        // assign each event handler a unique ID
        if (!handler.$$guid) handler.$$guid = addEvent.guid++;
        // create a hash table of event types for the element
        if (!element.events) element.events = {};
        // create a hash table of event handlers for each element/event pair
        var handlers = element.events[type];
        if (!handlers) {
            handlers = element.events[type] = {};
            // store the existing event handler (if there is one)
            if (element["on" + type]) {
                handlers[0] = element["on" + type];
            }
        }
        // store the event handler in the hash table
        handlers[handler.$$guid] = handler;
        // assign a global event handler to do all the work
        element["on" + type] = handleEvent;
    }
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
    if (element.removeEventListener) {
        element.removeEventListener(type, handler, false);
    } else {
        // delete the event handler from the hash table
        if (element.events && element.events[type]) {
            delete element.events[type][handler.$$guid];
        }
    }
};

function handleEvent(event) {
    var returnValue = true;
    // grab the event object (IE uses a global event object)
    event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
    // get a reference to the hash table of event handlers
    var handlers = this.events[event.type];
    // execute each event handler
    for (var i in handlers) {
        this.$$handleEvent = handlers[i];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};

function fixEvent(event) {
    // add W3C standard event methods
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};
複製代碼

不過在Dean Edward對應的博文中就能夠看到許多指正與有用的patch。好比說,既然全部的修正都是衝着IE去的,那麼標準瀏覽器用addEventListener就行。有的還提到,在iframe中點擊事件時,事件對象不對的問題,提交如下有用的補丁。

    event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);


其中,第54條回覆,直接致使了jQuery數據緩存系統的產生,爲了不交錯引用產出的內存泄露,建議元素分配一個uuid,全部的回調都放在一個對象中存儲。

但隨着事件的推移,使用者發現onXXX在IE存在不可消除和彌補的內存泄露,所以,翻看jQuery早期的版本,1.01是照抄Dean Edward的,1.1.31版本,開始吸取54條uuid的建議,並使用attach/removeEventListener綁定事件——每一個元素只綁定一次。而後全部回調都在相似handleEvent的函數中調用。

五,jQuery1.8.2的事件模塊概述

jQuery的事件模塊發端於Dean Edward的addEvent,而後它不斷吸取社區的插件與補丁,發展成爲很是棒的事件系統。其中不得不提的是事件代理與事件派發機制。


早在07年,Brandon Aaron爲jQuery寫了一個劃時代的插件,叫livequery,它能夠監聽後來插入的元素的事件。好比說,一個表格,咱們爲tr元素綁定了mouseover/mouseout事件時,只有十行代碼,而後咱們又動態加載了20行,這20個tr元素一樣能執行mouseover/mouseout回調。魔術在於,它並無把事件偵探器綁定在tr元素上,而是綁定在最頂層的document上,而後經過事件冒泡,取得事件源,斷定它是否匹配給用戶給定的css表達式,才執行用戶回調、具體參考它的github:

https://github.com/brandonaaron/livequery

若是一個表格有100個 tr元素,每一個都要綁定mouseover/mouseout事件,改爲事件代理的方式,能夠節省99次綁定,這優化很好,更況且它能監聽未來添加的元素,所以被立馬吸取到jquery1.3中去,成爲它的live方法,再把一些明顯的bug修復了。jquery1.32成爲最受歡迎的版本。與後來的jquery1.42都是歷程碑式的。 

不過話說回來,live方法須要對某些不冒泡的事件作些處理,好比一些表單事件,有的只能冒泡的form,有的只能冒泡到document,有的根本就不冒泡。

  ie6 ie8 ff3.6 opera10 chome4 sfari4
submit form form document document document document
reset form form document document form form
change 不冒泡 不冒泡 document document 不冒泡 不冒泡
click document document document document document document
select 不冒泡 不冒泡 document document 不冒泡 不冒泡

對於focus, blur, change, submit, reset, select等不會冒泡的事件,在標準瀏覽器中,咱們咱們能夠設置addEventListener最後的一個參數爲true輕鬆搞定,在IE就有點麻煩了,要用focusin代替focus,focusout代替blur,selectsatrt代替select,change和submit,reset就更復雜了。必須使用其餘的事件來模擬,還要判斷事件源的類型,slelectedIndex, keyCode等相關屬性。

這個課題到最後由一個叫reglib的庫搞定,reglib的做者還寫過一篇很著名的文章,《goodbuy mouseover,hello mouseenter》,來推廣微軟系統的兩個事件,mouseenter與mouseleave。jQuery全面接納了他們。

live方法帶來的全新體驗是空前的,但畢竟要冒泡到最頂層,對IE來講有點坎坷,還會失靈。最好能指定父節點,一個綁定時已經存在的父節點。這樣就不用費力了。當時有三篇博文給出了相近的方案,他們給出的接口一篇比一篇接近jhhon Resig接納的方案。

http://danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery

http://raxanpdi.com/blog-jquery-event-delegate.html (jQuery Event Delegation)

http://blog.threedubmedia.com/2008/10/jquerydelegate.html(已經打不開)

那時,它已是這個樣子

    $('div').delegate('click','span',function (event) {
        $(this).toggleClass('selected');
        return false;
    })

並提出解除代理的API:undelegate

在jquery1.42在2010年2月19推出時,也這兩個接口,前面的兩個參數只是掉換一下。

    $('div').delegate('span','click',function( event ){
        $( this ).toggleClass('selected');
        return false;
    })

正所衆人拾柴火焰高,jquery強大無不道理,在jquery1.8中,它又吸取dperini/nwevents的點子,改進其代理事件,大大 提升了性能。

先看一下其主要接口:

Jquery事件接口:bind live delegate on one trigger hover unbind die undelegate off toggle triggerHander Event .

其中bind,unbind,one,trigger,toggle,hover,ready一開始就有。

triggerHandler是jQuery1.23增長的,內部依賴於trigger,只對當前匹配元素的第一個有效,不冒泡不觸發默認行爲。

live與die是jQuery1.3增長的,用於事件代理,統一由document代理

delegate與undelegate是jquery1.4增長的,容許指定代理元素的事件代理,它內部是利用live,die實現的。

on與off是jQuery1.7增長的,目的是統一事件接口,bind,one,live,delegate,直接由on衍生,unbind,die,dundelegate直接由off衍生。

hover用於模擬css的hover效果,內部依賴於mouseenter,mouseleave

ready能夠看作是load事件的加強版,獲取最先的DOM可用後,當即執行各類dom操做。

toggle是click的加強版,每次點擊都執行不一樣的回調,並切換到下一個。

triggle與triggleHander是jQuery的fireEvent實現

 

此外,jQuery還有25個事件類型命名的快捷方法,當參數個數爲2時表現爲綁定事件。個數爲0時表現爲派發事件

jQuery快捷事件:contextmenu, error, keyup, keypress, keydown, submit, select, change, mouseleave, mouseenter, mouseout, mouseover, blur, focus, focusin, focusout, load, resize, scroll, unload, click, dbclik,mousedown, mouseup, mousemove

不過,最重要最基礎的設施無疑是jQuery.event之下

add方法用於綁定事件。

remove方法用於卸載事件。

dispath方法用於統一用戶回調,至關於Dean Edward的handleEvent方法,所以,jQuery1.7前它的名字一直叫handle。

trigger用於事件派發。

fix用於修正事件對象。

自jQuery1.4起,jQuery大大強化自定義事件功能。special對象原本是用於修正個別事件的,如今它容許這些事件經過setup, teardown, add, remove, default這些接口實現DOM事件的各類行爲

參考:

http://benalman.com/news/2010/03/jquery-special-events/

六,jQuery.event.add源碼解讀

add方法的主要目的,將用戶全部的傳遞參數,合併成一個handleObj對象放到元素對應的緩存體中events對象的某個隊列中,而後綁定一個回調。這個回調會處理用戶的全部回調。所以,對於每一個元素的每一種事件,它只綁定一次

複製代碼
    var add = function(elem, types, handler, data, selector) {
        var elemData, eventHandle, events, t, tns, namespaces, handleObj;
        //若是elem不能添加自定義屬性,因爲ie下訪問文本節點會報錯,所以事件源不能是文本節點。
        //註釋節點原本就不該該綁定事件,註釋節點之因此混進來,是由於jQuery的html方法所致
        //若是沒有指定事件的類型或回調也當即返回,再也不向下操做
        if (elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))) {
            return;
        }
        //取得用戶回調與css表達式,handleObjIn這種結構咱們稱爲事件描述
        //記下用戶綁定此回調的各類信息,方便用於「事件拷貝」
        if (handler.handler) {
            handlerObjIn = handler;
            handler = handlerObjIn.handler;
            selector = handlerObjIn.selector;
        }
        //確保回調有uuid,用於查找和移除
        if (!handler.guid) {
            handler.guid = jQuery.guid++;
        }

        //爲元素在數據緩存系統中開闢一個叫"event"的空間來保存其全部回調與事件處理器
        events = elemData.events;
        if (!events) {
            elemData.events = events = {};
        }
        eventsHandle = elemData.handle; //事件處理器
        if(eventHandle) {
            elemData.handle = eventHandle = function(e) {
                //用戶在事件冒充時,被第二次fire或者頁面的unload後觸發
                return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventsHandle.elem, arguments) : undefined;
            };
            //原註釋是說,防止IE下非原生的事件內存泄露,(直接影響是明確了this的指向)
            eventHandle.elem  = elem;
        }
        //經過空格隔開同時綁定多個事件,好比jQuery(...).bind("mouseover mouseout", fn);
        types = jQuery.trim(hoverHack(types)).split(" ");
        for (t = 0 ; t < types.length; t++) {
            tns = rtypenamespace.exec(types[t]) || [] ; //取得命名空間
            type = tnsp[1];//取得真正的事件
            namespaces = (tns[2] || "").split(".").sort();//修正命名空間
            //並非全部事件都能直接使用,好比FF下沒有mousewheel,須要用DOMMouseScroll冒充
            special = jQuery.event.special[ type ] || {};
            //有時候,咱們只須要在事件代理時進行冒充,好比FF下的focus, blur
            type = (selector ? special.delegateType : special.bindType) || type;

            special = jQuery.event.special[type] || {};
            //構建一個事件描述對象
            handleObj = jQuery.extend({
                type : type,
                origType : tns[1],
                data : data,
                handler : handler,
                guid : handler.guid,
                selector : selector,
                needsContext : selector && jQuery.expr.match.needsContext.test(selector),
                namespace : namespaces.join(".")
            },handlerObjIn);

            //在events對象上分門別類的存儲事件描述,每種事件對於一個數組
            //每種事只綁定一次監聽器(既addEventListener, attachEvent)
            handlers = events [type];
            if (!handlers) {
                handlers = events[ type ] = [];
                handlers.delegateCount = 0; //記錄要處理的回調個數
                //若是存在special.setup而且special.setup返回0個才直接使用多投事件API
                if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
                    if (elem.addEventListener) {
                        elem.addEventListener(type, eventHandle, false);
                    } else if (elem.attachEvent) {
                        elem.attachEvent("on" + type, eventHandle);
                    }
                }
            }
            if (special.add) { //處理自定義事件
                special.add.call(elem, handleObj);
                if (!handleObj.handler.guid) {
                    handleObj.handler.guid = handler.guid;
                }
            }
            //add to the element's hanlder list, deleate in front
            if (selector) { //若是是事件代理,那麼把此事件代理描述放在數據的前面
                handlers.splice(handlers.delegateCount++, 0, handleObj);
            } else {
                handlers.push(handleObj);
            }
            //用於jQuery.event.trigger,若是此事件從未綁定過,也不必進入trigger的真正處理邏輯
            jQuery.event.golbal[ type ] = true;
        }
        //防止ie內存泄露
        elem = null
    }
複製代碼

從上面的註釋中咱們能夠得出,jQuery的回調再也不直接與元素掛鉤,而是經過uuid訪問數據緩存系統,抵達對於的events對象,再根據事件類型獲得一組事件描述。而且事件描述裏邊沒有事件源的記錄,所以,很是方便導出挪動,爲事件克隆大開方便之門。固然,其中數據緩存系統是關鍵,接着探索,就會發現,其它事件代理部分對數據緩存依賴的更嚴重。

下面是元素數緩存與事件描述之間的結構

在firebug下查看結構:以下

 

七,jQuery.event.remove的源碼解讀

remove方法的主要目的是,根據用戶傳參,找到事件隊列,從裏邊把匹配的handleObj對象移除,在參數不足的狀況,可能移除N個或全部。當隊列的長度爲零就移除事件,當events爲空對象,則清除掉UUID

複製代碼
    remove = function(elem, types, handler, selector) {
        var t, tns, type, origType,namespaces, origCount, j, events, special, eventType, handleObj,
        elemData = jQuery.hasData( elem ) && jQuery._data( elem );
        //若是不支持添加自定義屬性或沒有緩存與事件有關的東西,當即返回。
        if ( !elemData || !(events = elemData.events)) {
            return;
        }
        //hover轉換爲"mouseenter mouseleave",而且按照空格進行切割,方便移除多種事件類型
        types = jQuery.trim( hoverHack( types || "")).split(" ");
        for (t = 0; t < types.length; t++) {
            tns = rtypenamespace.exec(types[t]) || [];
            type = origType = tns[1]; //取得事件類型
            namespaces = tns[2];//取得命名空間
            if (!type) { //若是沒有指定事件類型,則移除全部事件類型或移除全部與此命名空間相關的事件類型
                for (type in events) {
                    jQuery.event.remove( elem, type + types[t], handler, selector, true);
                }
                continue;
            }
            //利用事件冒充,取得真正的用於綁定事件類型
            special = jQuery.event.special[ type ] || {};
            type = (selector ? special.delegateType : special.bindType) || type;
            eventType = events[type] || []; //取得裝載事件描繪對象的數組
            origCount = eventType.length;
            //取得用於過濾命名空間的正則,沒有爲null
            namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)" : null ;

                //移除符合條件的事件描述
                for ( j = 0; j < eventType.length; j++) {
                    handleObj = eventType[ j];
                    if (( origType === handleObj.guid ) & //比較事件類型是否一致
                        (!handler || handler.guid === handleObj.guid) && //若是傳遞了回調,斷定UUid是否相同
                        //若是types含義命名空間,用正則看是否匹配
                        //若是事件代理必有css表達式,比較與事件描述對象中是否相等
                        (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) {
                        eventType.splice(j-- , 1); //是就移除
                        if ( handleObj.selector ) { //同時delegateCount減去1
                            eventType.delegateCount--;
                        } 

                        if (special.remove) { //處理個別事件移除
                            special.remove.call( elem, handleObj);
                        }
                    }
                }
                //若是已經移除全部此類問題,則卸載框架綁定去的elemData.handle
                //origCount !== eventType.length 是爲了防止死循環
                if ( eventType.length === 0 && origCount !== eventType.length) {
                    if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle ) === false) {
                        jQuery.removeEvent (elem, type, elemData.handle); 
                    }
                    delete events[ type ];
                }
        }
        //若是events爲空,則從elemData中刪除events與handler
        if ( jQuery.isEmptyObject( events )) {
            delete elemData.handle;
            jQuery.removeData (elem, "events", true)
        }
    }
複製代碼


事件卸載部分是jQuery事件系統中最簡單的部分,主要邏輯都花在移除事件描述對象和匹配條件上。

八,jQuery.event.dispatch的源碼解讀

這是jQuery事件系統的核心。它就是利用這個dispatch方法,從緩存體中的events對象取得對於的隊列,而後修復事件對象,逐個傳入用戶的回調中執行,根據返回值決定是否斷開循環(stopImmediatePropagation),阻止默認行爲和事件傳播。

 

複製代碼
    dispatch = function(event) {
        //建立一個僞事件對象(jQuery.Event實列),從真正的實際對象上抽得響應的屬性附於其上
        //若是是iE,也能夠將其轉換成對於的W3c屬性,抹去兩大平臺的差別。
        event = jQuery.event.fix(event || window.event);

        var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
        //取得全部事件描述對象
             handlers = ((jQuery._data(this, "events") || {}) [ event.type ] || []),
            delegateCount = handlers.delegateCount,
            args = core_slice.call(arguments),
            run_all = !event.exclusive && !event.namespace,
            special = jQuery.event.special[ event.type ] || {},
            handlerQueue = [];
        //重置第一個參數爲jQuery.Event實例
        args[0] = event;
        event.delegateTarget = this; //添加一我的爲的屬性,用於事件代理
        //執行preDispatch回調,它與後面的postDispatch構成一種相似AOP的機制
        if (special.preDispatch && special.preDispatch.call(this, event) === false) {
            return;
        }

        //若是是事件代理,而且不是來自非左鍵的點擊事件
        if (delegateCount && !(event.button && event.type === "click")) {
            //從事件源開始,遍歷其全部祖先一直到綁定事件的元素
            for ( cur = event.target; cur != this; cur = cur.parentNode || this) {
                //不要觸發被disabled元素的點擊事件
                if (cur.disabled !== tru event.type !== "click") {
                    selMatch = {}; //爲了節能起見,每種CSS表達式只斷定一次,經過下面的
                    //jQuery( sel, this).index( cur ) >= 0 或 jQuery.find(sel, this, null, [ cur ].length)
                    matches = []; //用於收集符合條件的事件描述對象
                    //使用事件代理的事件描述對象老是排在最前面
                    for (i = 0; i < delegateCount; i++ ) {
                        handleObj = handler[ i ];
                        sel = handleObj.selector;

                        if (selMatch[ sel ] === undefined) {
                            //有多少個元素匹配就收集多少個事件描述對象
                            selMatch[ sel] = handleObj.needsContext ? jQuery(sel, this).index(cur) >= 0 : jQuery.find(sel, this, null, [cur]).length;
                        }
                        if (selMatch[ sel ]){
                            matches.push(handleObj);
                        }
                    }
                    if (matches.length) {
                        handlerQueue.push({elem: cur, matches : matches});
                    }
                }
            }
        }
        //取得其餘直接綁定的事件描述對象
        if (handlers.length > delegateCount) {
            handlerQueue.push({elem : this, matches : handlers.slice(delegateCount) })
        }

        //注意:這個循環是從下到上執行的
        for (i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++) {
            matched = handlerQueue[ i ];
            event.currentTarget = matched.elem;
            //執行此元素的全部與event.type同類型的回調,除非用戶調用了stopImmediatePropagation方法,它會致使isImmediatePropagetionStopped返回true,從而中斷循環
            for (j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++) {
                handleObj = matched.matches[j];
                //最後的過濾條件爲事件命名空間,好比著名的bootstrap的命名空間爲data-api
                if (run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test(handleObj.namespace)) {
                    event.data = handleObj.data;
                    event.handleObj = handleObj;
                    //執行用戶回調,(有時 可能還要外包一層,來自jQuery.event.specal[type].handle)
                    ret = ((jQuery.event.special[ handleObj.origType ] || {}).handle || 
                        handleObj.handler).apply(matched.elem, args);
                    //根據結果斷定是否阻止事件傳播與默認行爲
                    if (ret !== undefined) {
                        event.resulet = ret;
                        if (ret === false) {
                            //
                            event.preventDefault();
                            event.stopPropagation();
                        }
                    }
                }
            }
        }
        //執行postDispatch回調
        if (special.postDispatch) {
            special.postDispatch.call(this, event);
        }
        return event.resulet;
    }
複製代碼

本節的難點在於如何模擬事件的傳播機制,jQuery實際上只模擬冒泡那一階段。

九,jQuery.event.trigger解讀

籠統的來講,triggter就是dispatch的增強版。

dispatch只觸發當前元素與其底下元素(經過事件代理的方式)的回調,trigger則模擬整個冒泡過程,除了它自身,還觸發其祖先節點與window的同類型的回調。不過,從trigger的代碼來看,它比dispatch多作的事情就是觸發事件的默認行爲。

這涉及到太多的斷定,若是再把dispatch的代碼寫在一塊就很差維護了。

可是,我以爲,trigger其實用不着那麼複雜,trigger要作的事情就是在某一元素觸發一個回調(dispatch),生產一個事件對象,而後讓其順序冒泡,觸發其它的回調(dispatch)就好了。

瀏覽器提供了原生的派發機制,IE下的fireEvent,標準瀏覽器爲dispatchEvent.

若是在控制檯執行如下函數,咱們會獲得更多的事件構造器

    Object.getOwnPropertyNames(window).filter(function (p) {
        return typeof window[p] == "function" && (window[p].prototype instanceof Event)
    })
複製代碼
["MIDIMessageEvent", "MIDIConnectionEvent", "MediaKeyMessageEvent", "MediaEncryptedEvent", "webkitSpeechRecognitionEvent", "webkitSpeechRecognitionError", "StorageEvent", "SpeechSynthesisEvent", "MediaStreamEvent", "IDBVersionChangeEvent", "GamepadEvent", "DeviceOrientationEvent", "DeviceMotionEvent", "CloseEvent", "OfflineAudioCompletionEvent", "AudioProcessingEvent", "MediaKeyEvent", "XMLHttpRequestProgressEvent", "WheelEvent", "WebGLContextEvent", "UIEvent", "TransitionEvent", "TrackEvent", "TouchEvent", "TextEvent", "SecurityPolicyViolationEvent", "SVGZoomEvent", "ProgressEvent", "PopStateEvent", "PageTransitionEvent", "MutationEvent", "MouseEvent", "MessageEvent", "MediaQueryListEvent", "KeyboardEvent", "HashChangeEvent", "FocusEvent", "ErrorEvent", "CustomEvent", "CompositionEvent", "ClipboardEvent", "BeforeUnloadEvent", "AutocompleteErrorEvent", "ApplicationCacheErrorEvent", "AnimationEvent", "WebKitAnimationEvent", "WebKitTransitionEvent"]
複製代碼

(webkit包含)

但經常使用的交互都集中在HTML4.0已經定義好的事件上,咱們無需理會message storage popstate事件,更別提事件變更。咱們認爲支持如下事件就行了

HTMLEvents Load, unload, abort, error, select, change, submit, reset, focus, blur, resize, scroll
KeyboardEvent keypress keydown keyup
MouseEvents contextmenu, click, dblclick, mouseout, mouseover, mouseenter, mouseleave, mousemove,mousedown, mouseup,mousewheel

根據上面的表格,咱們作一個叫eventMap的hash出來,那麼trigger方法最大限制級能夠壓縮成以下樣子:

複製代碼
    trigger: function (type, target) {
        var doc = target.ownerDocument || target.document || target || document ;
        event = doc.createEvent(eventMap[type] || "CustomEvent");
        if (/^(focus|blur|select|submit|reset)$/.test(type)) {
            target[type] && target[type] (); //觸發默認行爲
        }
        Event.initEvent( type, true, true);
        target.dispatchEvent(event);
    }
複製代碼


可是這樣trigger出來的對象是隻讀,不能覆蓋原生屬性或方法,所以,你能夠覺得它自定義一個more屬性,裏邊裝載着你要改的東西,而後在dispatch將它包裝成一個jQuery僞事件對象後,在把循環加在僞事件對象就行 了。

十,jQuery對事件對象的修復(不更新)

十一,滾輪事件的修復(不更新)

十二,mouseenter與mouseleave事件的修復(不更新)

十三,focus與focusout事件的修復(不更新)

十四,舊版本下IE的submit的事件代理實現(不更新)

在舊版本的IE下submit不會冒泡到頂層,它只執行form元素的submit回調,並當即執行提交跳轉,所以只能用事件冒充的方式來實現。

咱們看看什麼狀況下瀏覽器觸發submit事件吧。submit事件與鼠標事件,鍵盤事件是不同的。它是一種複合事件,既可使用鼠標實現,也能夠經過鍵盤事件實現,重要的結果,能實現表單提交便可。

當焦點聚焦於input[type=text]、input[type=password]、input[type=checkbox]、input[type=radio]、input[type=button]、input[type=image]、input[type=submit]、按回車鍵就會觸發提交

 

當鼠標在input[type=image],input[type=submit]上方,經過點擊事件就會觸發提交
瀏覽器差別,IE下能夠在input=file回車後觸發
咱們也可使用form.submit()這樣的編程手段觸發

IE下submit, reset的事件代理,delegate用於檢測它有沒有用事件代理,不用管它。重點在於它在代理元素上一下綁定了兩個事件,click,keypress,若是是鍵盤事件,就判斷他的keycode是否爲13108(回車鍵)

el.submit()方法有個特異之處,它是不會執行submit回調的,像其餘click,blur,focus,select這樣的DOM方法都會同時執行回調與默認行爲。

 

 

十五,oninput事件的兼容性性處理(不更新)

相關文章
相關標籤/搜索