jQuery 事件委派

Posted by franki on June 10, 2019

从上篇jQuery事件的绑定,就可以看到:

  1. 事件的信息都存储在数据缓存中
  2. 对于没有特殊事件和普通事件都用addEventListener来添加事件
  3. 有特殊事件用另外的方式来添加事件

这篇文章的重点是:

在addEventListener(type, func, false),触发事件后,回调句柄是如何处理的?

涉及的处理:

事件的读取与处理

事件对象的兼容,jQuery采用了什么机制?

委托关系的处理

jQuery引入的处理方案

jQuery.event.fix(event): 把原生event转化为可读写的event对象,并对该event的属性以及方法统一性接口

jQuery.Event(): 构造函数创建可读写的jQuery事件对象event,改对象即可以是原生事件对象event的增强版,也可以是用户自定义事件

jQuery.handlers: 用来区分原生与委托事件

这部分能学到什么?

缓存的分离

适配器模式的运用

事件兼容性处理

委托的设计


看看jQuery怎么做事件兼容的

jQuery.Event构造函数

jQuery.Event = function(src, props) {
    // Allow instantiation without the 'new' keyword
    if (!(this instanceof jQuery.Event)) {
        return new jQuery.Event(src, props);
    }

    // Event object
    if (src && src.type) {
        this.originalEvent = src;
        this.type = src.type;

        // Events bubbling up the document may have been marked as prevented
        // by a handler lower down the tree; reflect the correct value.
        this.isDefaultPrevented =
            src.defaultPrevented ||
            (src.defaultPrevented === undefined &&
                // Support: Android <=2.3 only
                src.returnValue === false)
                ? returnTrue
                : returnFalse;

        // Create target properties
        // Support: Safari <=6 - 7 only
        // Target should not be a text node (#504, #13143)
        this.target =
            src.target && src.target.nodeType === 3
                ? src.target.parentNode
                : src.target;

        this.currentTarget = src.currentTarget;
        this.relatedTarget = src.relatedTarget;

        // Event type
    } else {
        this.type = src;
    }

    // Put explicitly provided properties onto the event object
    if (props) {
        jQuery.extend(this, props);
    }

    // Create a timestamp if incoming event doesn't have one
    this.timeStamp = (src && src.timeStamp) || Date.now();

    // Mark it as fixed
    this[jQuery.expando] = true;
};

方法

jQuery.Event.prototype = {
    constructor: jQuery.Event,
    isDefaultPrevented: returnFalse,
    isPropagationStopped: returnFalse,
    isImmediatePropagationStopped: returnFalse,
    isSimulated: false,

    preventDefault: function() {
        var e = this.originalEvent;

        this.isDefaultPrevented = returnTrue;

        if (e && !this.isSimulated) {
            e.preventDefault();
        }
    },
    stopPropagation: function() {
        var e = this.originalEvent;

        this.isPropagationStopped = returnTrue;

        if (e && !this.isSimulated) {
            e.stopPropagation();
        }
    },
    stopImmediatePropagation: function() {
        var e = this.originalEvent;

        this.isImmediatePropagationStopped = returnTrue;

        if (e && !this.isSimulated) {
            e.stopImmediatePropagation();
        }

        this.stopPropagation();
    }
};

以上的方法,目的只有一个,创建一个自定义的event。

为什么要这么做,目的也很明显,为了更加直观的描述事件,更加容易操作事件。


jQuery怎么做事件数据缓存的呢?

jQuery.cache 实现了注册事件处理程序的存储,实际上绑定在DOM元素上的事件处理程序只有一个,即是jQuery.cache[elem[expando]].handle中存储的函数

handlers = (dataPriv.get(this, 'events') || {})[event.type] || [],

拿到了数据句柄,即是事件监听的回调函数。(事件触发后要执行的函数)


jQuery如何处理区分事件类型,组成事件队列?

// Add to the element's handler list, delegates in front
if (selector) {
  handlers.splice(handlers.delegateCount++, 0, handleObj);
} else {
  handlers.push(handleObj);
}

区分delegate绑定和普通绑定的方法是: delegate绑定从队列头部推入,而普通绑定从尾部推入,通过记录delegateCount来划分,delegate绑定和普通绑定。

function(event, handlers) {
    var i,
        handleObj,
        sel,
        matchedHandlers,
        matchedSelectors,
        handlerQueue = [],
        delegateCount = handlers.delegateCount,
        cur = event.target;

    // Find delegate handlers
    if (
        delegateCount &&
        // Support: IE <=9
        // Black-hole SVG <use> instance trees (trac-13180)
        cur.nodeType &&
        // Support: Firefox <=42
        // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
        // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
        // Support: IE 11 only
        // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
        !(event.type === 'click' && event.button >= 1)
    ) {
        for (; cur !== this; cur = cur.parentNode || this) {
            // Don't check non-elements (#13208)
            // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
            if (
                cur.nodeType === 1 &&
                !(event.type === 'click' && cur.disabled === true)
            ) {
                matchedHandlers = [];
                matchedSelectors = {};
                for (i = 0; i < delegateCount; i++) {
                    handleObj = handlers[i];

                    // Don't conflict with Object.prototype properties (#13203)
                    sel = handleObj.selector + ' ';

                    if (matchedSelectors[sel] === undefined) {
                        matchedSelectors[sel] = handleObj.needsContext
                            ? jQuery(sel, this).index(cur) > -1
                            : jQuery.find(sel, this, null, [cur])
                                    .length;
                    }
                    if (matchedSelectors[sel]) {
                        matchedHandlers.push(handleObj);
                    }
                }
                if (matchedHandlers.length) {
                    handlerQueue.push({
                        elem: cur,
                        handlers: matchedHandlers
                    });
                }
            }
        }
    }

    // Add the remaining (directly-bound) handlers
    cur = this;
    if (delegateCount < handlers.length) {
        handlerQueue.push({
            elem: cur,
            handlers: handlers.slice(delegateCount)
        });
    }

    return handlerQueue;
}

jQuery.event.handlers做的事情:

有序返回当前事件所要执行的所有事件处理程序

这里的事件包括直接绑定在该元素上的处理程序,也包括利用冒泡机制委托在该元素的处理程序(委托机制依赖于selector)

在返回这些事件处理程序时,委托的事件处理程序相对于直接绑定的事件处理程序在队列的更前面,委托层次越深,该事件处理程序则越靠前

返回的结果是[{elem: currentElem, handlers: handlerlist}, …]


使用jQuery处理委托的优势:

从上面的代码不难看出,在处理委托事件程序时,其内部的this指向发出委托的元素(有传入selector),而不是委托元素。

从jQuery.fn.dispatch中的代码可以看到:

ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);

以上就是对于jQuery委托方面的讲解。