jQuery.on()函数的工作原理是什么?

11
我没有看到这个函数的源代码,但我想知道它是不是这样工作的:
1. 通过选择器选择元素 2. 委托提供的事件处理程序给它们 3. 在该选择器上运行一个setInterval,并不断取消委派,然后重新委派相同的事件
或者有一个纯JavaScript DOM的解释吗?
2个回答

19
我假设你的问题与 事件委托 版本的 .on 相关。
在 JavaScript 中,大多数事件会 冒泡 到 DOM 层次结构中。这意味着当一个可以冒泡的事件发生在一个元素上时,它会一直冒泡到 DOM 直到达到 document 级别。
考虑这个基本的标记:
<div>
   <span>1</span>
   <span>2</span>
</div>

现在我们应用事件委托:
$('div').on('click', 'span', fn);

该事件处理程序仅附加到 div 元素。由于 spandiv 内部,因此在 spans 中的任何单击都会冒泡到 div,从而触发 div 的单击处理程序。此时,剩下的就是检查 event.target(或目标与 delegateTarget 之间的任何元素)是否与委托目标选择器匹配。

让它变得更加复杂一些:

<div id="parent">
    <span>1</span>
    <span>2 <b>another nested element inside span</b></span>
    <p>paragraph won't fire delegated handler</p>
</div>

基本逻辑如下:
//attach handler to an ancestor
document.getElementById('parent').addEventListener('click', function(e) {
    //filter out the event target
    if (e.target.tagName.toLowerCase() === 'span') {
        console.log('span inside parent clicked');
    }
});

尽管上述代码无法匹配event.target在过滤器内部嵌套的情况。我们需要一些迭代逻辑:
document.getElementById('parent').addEventListener('click', function(e) {
    var failsFilter = true,
        el = e.target;
    while (el !== this && (failsFilter = el.tagName.toLowerCase() !== 'span') && (el = el.parentNode));
    if (!failsFilter) {
        console.log('span inside parent clicked');
    }
});

Fiddle

编辑:更新代码,仅匹配jQuery的.on中的后代元素。

注意:这些片段仅用于说明目的,不应在真实环境中使用。除非您不打算支持旧版IE。对于旧版IE,您需要针对addEventListener/attachEvent进行特性测试,以及可能的其他怪异行为,例如检查事件对象是否传递给处理程序函数或是否在全局范围内可用。值得庆幸的是,jQuery在幕后无缝地为我们完成了所有这些工作。 =]


关于jQuery和如何使用jQuery.on(),一切都很好。我想知道是否有可能仅使用JavaScript和DOM编写类似的代码段? - user1386320
@Zlatan 是的。但是如果没有这个特性,你就不会有那么多花哨的委托选择器。我会添加一个纯JS的例子。 - Fabrício Matté
好的。我认为你可以用document.querySelector() 或者 document.querySelectorAll() 写一个例子 :) - user1386320
1
@Zlatan 没问题,我更新了答案,并提供了一个更为稳定的代码片段。如果你想要,我可以制作一个具有过滤器函数回调的包装器,它类似于 .on,但我猜重新发明轮子可能不太好理解。 :P - Fabrício Matté
谢谢再次提问。我已经思考这个问题几个月了,但今天决定发帖询问。不过,像乔布斯曾经说过的那样,关于重新发明,"今天,我们要重新发明(iph)on!" :P - user1386320
显示剩余2条评论

3

巫术操作:
以防有人需要将JQuery替换为Vanilla-JavaScript:

TypeScript:

/// attach an event handler, now or in the future, 
/// for all elements which match childselector,
/// within the child tree of the element maching parentSelector.
export function subscribeEvent(parentSelector: string | Element
    , eventName: string
    , childSelector: string
    , eventCallback)
{
    if (parentSelector == null)
        throw new ReferenceError("Parameter parentSelector is NULL");

    if (childSelector == null)
        throw new ReferenceError("Parameter childSelector is NULL");

    // nodeToObserve: the node that will be observed for mutations
    let nodeToObserve: Element = <Element>parentSelector;
    if (typeof (parentSelector) === 'string')
        nodeToObserve = document.querySelector(<string>parentSelector);


    let eligibleChildren: NodeListOf<Element> = nodeToObserve.querySelectorAll(childSelector);

    for (let i = 0; i < eligibleChildren.length; ++i)
    {
        eligibleChildren[i].addEventListener(eventName, eventCallback, false);
    } // Next i 

    // https://dev59.com/vnE85IYBdhLWcg3wkkbK
    function allDescendants(node: Node)
    {
        if (node == null)
            return;

        for (let i = 0; i < node.childNodes.length; i++)
        {
            let child = node.childNodes[i];
            allDescendants(child);
        } // Next i 

        // IE 11 Polyfill 
        if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;

        if ((<Element>node).matches)
        {
            if ((<Element>node).matches(childSelector))
            {
                // console.log("match");
                node.addEventListener(eventName, eventCallback, false);
            } // End if ((<Element>node).matches(childSelector))
            // else console.log("no match");

        } // End if ((<Element>node).matches) 
        // else console.log("no matchfunction");

    } // End Function allDescendants 


    // Callback function to execute when mutations are observed
    let callback:MutationCallback = function (mutationsList: MutationRecord[], observer: MutationObserver)
    {
        for (let mutation of mutationsList)
        {
            // console.log("mutation.type", mutation.type);
            // console.log("mutation", mutation);

            if (mutation.type == 'childList')
            {
                for (let i = 0; i < mutation.addedNodes.length; ++i)
                {
                    let thisNode: Node = mutation.addedNodes[i];
                    allDescendants(thisNode);
                } // Next i 

            } // End if (mutation.type == 'childList') 
            // else if (mutation.type == 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.');

        } // Next mutation 

    }; // End Function callback 

    // Options for the observer (which mutations to observe)
    let config = { attributes: false, childList: true, subtree: true };

    // Create an observer instance linked to the callback function
    let observer = new MutationObserver(callback);

    // Start observing the target node for configured mutations
    observer.observe(nodeToObserve, config);
} // End Function subscribeEvent 

JavaScript:

 /// attach an event handler, now or in the future, 
    /// for all elements which match childselector,
    /// within the child tree of the element maching parentSelector.
    function subscribeEvent(parentSelector, eventName, childSelector, eventCallback) {
        if (parentSelector == null)
            throw new ReferenceError("Parameter parentSelector is NULL");
        if (childSelector == null)
            throw new ReferenceError("Parameter childSelector is NULL");
        // nodeToObserve: the node that will be observed for mutations
        var nodeToObserve = parentSelector;
        if (typeof (parentSelector) === 'string')
            nodeToObserve = document.querySelector(parentSelector);
        var eligibleChildren = nodeToObserve.querySelectorAll(childSelector);
        for (var i = 0; i < eligibleChildren.length; ++i) {
            eligibleChildren[i].addEventListener(eventName, eventCallback, false);
        } // Next i 
        // https://dev59.com/vnE85IYBdhLWcg3wkkbK
        function allDescendants(node) {
            if (node == null)
                return;
            for (var i = 0; i < node.childNodes.length; i++) {
                var child = node.childNodes[i];
                allDescendants(child);
            } // Next i 
            // IE 11 Polyfill 
            if (!Element.prototype.matches)
                Element.prototype.matches = Element.prototype.msMatchesSelector;
            if (node.matches) {
                if (node.matches(childSelector)) {
                    // console.log("match");
                    node.addEventListener(eventName, eventCallback, false);
                } // End if ((<Element>node).matches(childSelector))
                // else console.log("no match");
            } // End if ((<Element>node).matches) 
            // else console.log("no matchfunction");
        } // End Function allDescendants 
        // Callback function to execute when mutations are observed
        var callback = function (mutationsList, observer) {
            for (var _i = 0, mutationsList_1 = mutationsList; _i < mutationsList_1.length; _i++) {
                var mutation = mutationsList_1[_i];
                // console.log("mutation.type", mutation.type);
                // console.log("mutation", mutation);
                if (mutation.type == 'childList') {
                    for (var i = 0; i < mutation.addedNodes.length; ++i) {
                        var thisNode = mutation.addedNodes[i];
                        allDescendants(thisNode);
                    } // Next i 
                } // End if (mutation.type == 'childList') 
                // else if (mutation.type == 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.');
            } // Next mutation 
        }; // End Function callback 
        // Options for the observer (which mutations to observe)
        var config = { attributes: false, childList: true, subtree: true };
        // Create an observer instance linked to the callback function
        var observer = new MutationObserver(callback);
        // Start observing the target node for configured mutations
        observer.observe(nodeToObserve, config);
    } // End Function subscribeEvent 

这就是我寻找已久的东西。非常感谢。你拯救了我的日子,让它们不再毫无价值。 - TuralAsgar
这是一个很好的替代方案,可以取代jQuery.live(),对于那些还记得这种方法的人来说也是如此。 - adjwilli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接