jQuery源碼學習筆記九

來源:互聯網
上載者:User

最近幾天搞了一個基於事件代理的事件系統,但即便是事件代理還是要依賴於事件註冊,因此深入研究了jQuery的事件系統,整理出來分享一下。

由於IE與標準瀏覽器鬧彆扭,我們通過雖然弄一個叫addEvent的函數來屏蔽差異。以下就是一個經典的addEvent函數:

var addEvent = function( obj, type, fn ) {    if (obj.addEventListener)        obj.addEventListener( type, fn, false );    else if (obj.attachEvent) {        obj["e"+type+fn] = fn;        obj.attachEvent( "on"+type, function() {            obj["e"+type+fn]();        } );    }};

但這簡潔函數有許多缺點,如不能處理IE下綁定的回呼函數的執行順序問題,也根本無法消除事件對象的差異。於是有

 //http://dean.edwards.name/weblog/2005/10/add-event///http://dean.edwards.name/weblog/2005/10/add-event2/function addEvent(element, type, handler) {    // assign each event handler a unique ID    //在每個回呼函數上綁定了一個UUID    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    //evets函數的鍵名為事件的類型名,或者說把事件按類型來按理    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];//DOM1.0        }    }    // 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 IDsaddEvent.guid = 1; function removeEvent(element, type, handler) {    // 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(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;};

完美的解決了IE的執行順序問題,並為IE的事件對象添加了兩個方法preventDefault與stopPropagation,jQuery的事件系統就是基於它發展而來。下面jQuery1.0.1的代碼:

 event: {    // Bind an event to an element    // Original by Dean Edwards    add: function (element, type, handler) {        // For whatever reason, IE has trouble passing the window object        // around, causing it to be cloned in the process        if (jQuery.browser.msie && element.setInterval != undefined) element = window;        // Make sure that the function being executed has a unique ID        if (!handler.guid) handler.guid = this.guid++;        // Init the element's event structure        if (!element.events) element.events = {};        // Get the current list of functions bound to this event        var handlers = element.events[type];        // If it hasn't been initialized yet        if (!handlers) {            // Init the event handler queue            handlers = element.events[type] = {};            // Remember an existing handler, if it's already there            if (element["on" + type]) handlers[0] = element["on" + type];        }        // Add the function to the element's handler list        handlers[handler.guid] = handler;        // And bind the global event handler to the element        element["on" + type] = this.handle;        //上面基本和DC大神的一致        // Remember the function in a global list (for triggering)        if (!this.global[type]) this.global[type] = [];        this.global[type].push(element);    },    guid: 1,    global: {},    // Detach an event or set of events from an element    remove: function (element, type, handler) {        if (element.events) if (type && element.events[type]) if (handler) delete element.events[type][handler.guid];        else for (var i in element.events[type]) delete element.events[type][i];        else for (var j in element.events) this.remove(element, j);    },    //觸發,為什麼不叫fire呢?!    trigger: function (type, data, element) {        // Touch up the incoming data        data = data || [];        // Handle a global trigger        if (!element) {            var g = this.global[type];            if (g) for (var i = 0; i < g.length; i++) this.trigger(type, data, g[i]);            // Handle triggering a single element        } else if (element["on" + type]) {            // Pass along a fake event            data.unshift(this.fix({                type: type,                target: element            }));            // Trigger the event            element["on" + type].apply(element, data);        }    },    handle: function (event) {        if (typeof jQuery == "undefined") return;        event = event || jQuery.event.fix(window.event);        // If no correct event was found, fail        if (!event) return;        var returnValue = true;        var c = this.events[event.type];        for (var j in c) {            if (c[j].apply(this, [event]) === false) {                event.preventDefault();                event.stopPropagation();                returnValue = false;            }        }        return returnValue;    },    fix: function (event) {        if (event) {            event.preventDefault = function () {                this.returnValue = false;            };            event.stopPropagation = function () {                this.cancelBubble = true;            };        }        return event;    }}

我們來看一個這個經典的基於事件註冊的事件系統,幾年前主流的事件系統基於是這個樣子。首先設定一個或幾個頂層對象,用於管理事件控制代碼與相關的東西,這裡正如我們看到的那樣,是用一個叫global的對象。它裝載的是元素,因為它是基於事件註冊,回呼函數都是直接綁定在元素上,後來IE7把記憶體流失的問題放大後,jQuery進一步改進,在unload時把這些註冊了事件的元素上面事件全部去掉,現在還沒有。在這些元素上有一個叫events的自訂屬性,它是一個對象,按事件類別弄型管理綁定在它上面的回呼函數,目的是讓事件按綁定時的順序執行。當我們觸發事件時,並不是直接執行我們綁定的回呼函數,因為這裡用的是DOM0的事件方式,無論綁定多少個同類型的事件,最後都只一個此類型的。因此都把它們放到一個handle函數中(DE大神的handleEvent)。handle做了三件事,讓事件對象總是作為函數的第一個參數,改造事件對象,按順序執行既定的回呼函數。最後我們留意到它有傳回值,這是用來決定它是否執行瀏覽器的預設行為。

我沒有閑情把它所有的版本都看了,只看了幾個版本,jQuery1.0.4基於還是那個樣子。到jQuery1.1增加了幾種綁定方式,著名的one,同時對toggle ,hover與ready進行大幅改進。在jQuery中,一個方法基本上都有兩個版本,一個jQuery命名空間的靜態方法,另一個是jQuery對象的執行個體方法。執行個體方法都是對應複數個元素(因為一個jQuery往往包含幾個DOM元素),而靜態方法基於上是對應一個。執行個體方法都是往外圍調用這些靜態方法,因此靜態方法的地位相當高。改進的重點都是這些靜態方法,因此我重點講它們。有興趣可以下jQuery1.1來看看,這時John Resig開始著手解決事件對象的差異問題,為IE的事件對象添加了pageX與pageY與target 屬性。unbind與one事件實現得相當傻瓜,因為事件都是用頂層對象管理,把頂層對象的事件刪掉,就是unbind了,刪除後用一個拷收貝繼續執行就是one。hover由於還沒有事先搞定relatedTarget ,因此有點複雜。

由於事件系統是個複雜的東西,我還沒有開始講jQuery最新版本的情形,就已達這樣的篇幅了。最後我總結下jQuery的事件運行流程吧:使用者為元素繫結事件(bind)=>add=>為回呼函數設定UUID=>交由頂層對象管理=>handle=>fix=>開始等待使用者觸發事件或直接調用trigger 。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.