jQuery 自定义事件

Posted by franki on June 11, 2019

在jQuery中常用trigger()方法来完成模拟操作,如可以使用下面的代码来触发id为test按钮的click事件。

$("#test").trigger("click");

也可以直接简化写法click(),来实现同样的效果:

$("test").click();

如何触发自定义事件

//给element绑定hello事件
element.bind("hello",function(){
    console.log("hello world!");
});

//触发hello事件
element.trigger("hello");

上面的bind方法还是如之前文章绑定的流程一样

主要讲下trigger方法

trigger: function(event, data, elem, onlyHandlers) {
    var i,
        cur,
        tmp,
        bubbleType,
        ontype,
        handle,
        special,
        lastElement,
        eventPath = [elem || document],
        type = hasOwn.call(event, 'type') ? event.type : event,
        namespaces = hasOwn.call(event, 'namespace')
            ? event.namespace.split('.')
            : [];

    cur = lastElement = tmp = elem = elem || document;

    // Don't do events on text and comment nodes
    if (elem.nodeType === 3 || elem.nodeType === 8) {
        return;
    }

    // focus/blur morphs to focusin/out; ensure we're not firing them right now
    if (rfocusMorph.test(type + jQuery.event.triggered)) {
        return;
    }

    if (type.indexOf('.') > -1) {
        // Namespaced trigger; create a regexp to match event type in handle()
        namespaces = type.split('.');
        type = namespaces.shift();
        namespaces.sort();
    }
    ontype = type.indexOf(':') < 0 && 'on' + type;

    // Caller can pass in a jQuery.Event object, Object, or just an event type string
    event = event[jQuery.expando]
        ? event
        : new jQuery.Event(type, typeof event === 'object' && event);

    // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
    event.isTrigger = onlyHandlers ? 2 : 3;
    event.namespace = namespaces.join('.');
    event.rnamespace = event.namespace
        ? new RegExp(
                '(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)'
            )
        : null;

    // Clean up the event in case it is being reused
    event.result = undefined;
    if (!event.target) {
        event.target = elem;
    }

    // Clone any incoming data and prepend the event, creating the handler arg list
    data = data == null ? [event] : jQuery.makeArray(data, [event]);

    // Allow special events to draw outside the lines
    special = jQuery.event.special[type] || {};
    if (
        !onlyHandlers &&
        special.trigger &&
        special.trigger.apply(elem, data) === false
    ) {
        return;
    }

    // Determine event propagation path in advance, per W3C events spec (#9951)
    // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
    if (!onlyHandlers && !special.noBubble && !isWindow(elem)) {
        bubbleType = special.delegateType || type;
        if (!rfocusMorph.test(bubbleType + type)) {
            cur = cur.parentNode;
        }
        for (; cur; cur = cur.parentNode) {
            eventPath.push(cur);
            tmp = cur;
        }

        // Only add window if we got to document (e.g., not plain obj or detached DOM)
        if (tmp === (elem.ownerDocument || document)) {
            eventPath.push(
                tmp.defaultView || tmp.parentWindow || window
            );
        }
    }

    // Fire handlers on the event path
    i = 0;
    while ((cur = eventPath[i++]) && !event.isPropagationStopped()) {
        lastElement = cur;
        event.type = i > 1 ? bubbleType : special.bindType || type;

        // 主要是通过这里,获取当前节点缓存的句柄函数,执行传递进来的回调函数
        // jQuery handler
        handle =
            (dataPriv.get(cur, 'events') || {})[event.type] &&
            dataPriv.get(cur, 'handle');
        if (handle) {
            handle.apply(cur, data);
        }

        // Native handler
        handle = ontype && cur[ontype];
        if (handle && handle.apply && acceptData(cur)) {
            event.result = handle.apply(cur, data);
            if (event.result === false) {
                event.preventDefault();
            }
        }
    }
    event.type = type;

    // If nobody prevented the default action, do it now
    if (!onlyHandlers && !event.isDefaultPrevented()) {
        if (
            (!special._default ||
                special._default.apply(eventPath.pop(), data) ===
                    false) &&
            acceptData(elem)
        ) {
            // Call a native DOM method on the target with the same name as the event.
            // Don't do default actions on window, that's where global variables be (#6170)
            if (ontype && isFunction(elem[type]) && !isWindow(elem)) {
                // Don't re-trigger an onFOO event when we call its FOO() method
                tmp = elem[ontype];

                if (tmp) {
                    elem[ontype] = null;
                }

                // Prevent re-triggering of the same event, since we already bubbled it above
                jQuery.event.triggered = type;

                if (event.isPropagationStopped()) {
                    lastElement.addEventListener(
                        type,
                        stopPropagationCallback
                    );
                }

                elem[type]();

                if (event.isPropagationStopped()) {
                    lastElement.removeEventListener(
                        type,
                        stopPropagationCallback
                    );
                }

                jQuery.event.triggered = undefined;

                if (tmp) {
                    elem[ontype] = tmp;
                }
            }
        }
    }

    return event.result;
},

上面的源码主要做以下的几件事:

1 命名空间的过滤

if (type.indexOf('.') > -1) {
    // Namespaced trigger; create a regexp to match event type in handle()
    namespaces = type.split('.');
    type = namespaces.shift();
    namespaces.sort();
 }

2 模拟事件对象

event = event[jQuery.expando]
        ? event
        : new jQuery.Event(type, typeof event === 'object' && event);

创建jQuery event

3 模拟事件冒泡

// Determine event propagation path in advance, per W3C events spec (#9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
if (!onlyHandlers && !special.noBubble && !isWindow(elem)) {
    bubbleType = special.delegateType || type;
    if (!rfocusMorph.test(bubbleType + type)) {
      cur = cur.parentNode;
    }
    for (; cur; cur = cur.parentNode) {
      eventPath.push(cur);
      tmp = cur;
    }

    // Only add window if we got to document (e.g., not plain obj or detached DOM)
    if (tmp === (elem.ownerDocument || document)) {
      eventPath.push(
        tmp.defaultView || tmp.parentWindow || window
      );
    }
}

通过遍历所有元素节点,排个队列出来

4 处理事件

// Fire handlers on the event path
i = 0;
while ((cur = eventPath[i++]) && !event.isPropagationStopped()) {
    lastElement = cur;
    event.type = i > 1 ? bubbleType : special.bindType || type;

    // 主要是通过这里,获取当前节点缓存的句柄函数,执行传递进来的回调函数
    // jQuery handler
    handle =
        (dataPriv.get(cur, 'events') || {})[event.type] &&
        dataPriv.get(cur, 'handle');
    if (handle) {
        handle.apply(cur, data);
    }

    // Native handler
    handle = ontype && cur[ontype];
    if (handle && handle.apply && acceptData(cur)) {
        event.result = handle.apply(cur, data);
        if (event.result === false) {
            event.preventDefault();
        }
    }
}

当然每个元素上可能有多个事件,所以先确定事件绑定类型是delegateType还是bindType

检测缓存中该元素对应事件中包含事件处理器,有则取出主处理器(jQuery handle)来控制所有分事件处理器 所以最终代码又走到了 handle.apply(cur, data);

trigger 源码分析到此为止。