Pros and Cons of using e.stopPropagation() to prevent event bubbling

前端 未结 3 1009
忘掉有多难
忘掉有多难 2021-02-03 13:43

Many people have explained that e.stopPropagation() prevents event bubbling. However, I\'m having a hard time finding why one would want or want to

相关标签:
3条回答
  • 2021-02-03 14:26

    Do not use stopPropagation(), if possible.

    Two advantages using stopPropagation() are:

    • It is easier to write code (independent event functions)
    • Performance

    Although this function seems to be useful, it can be considered bad coding style, especially when you have no full control over the code (e. g. because you use a third-party library). stopPropagation() is a take-everything-or-nothing concept. It does not allow fine control flow. If some element between two other nested elements stops the event propagation, none of the parent elements will receive it, although there might be situations, when they should receive it.

    A more elegant (and not that complex) way to solve this problem, is to always allow the event propagation by never calling stopPropagation() at all. Elements, which define their own events which shall not be automatically executed from a child element, can use the target and currentTarget properties to check from where the initial event comes and execute own event functions only in situations when it is wanted.

    In following example, there are 3 nested DIVs. Clicking the lowest (blue) DIV shall propagate the onClick event up through the entire DOM structure but without calling the onClick event of the green DIV, which lies between:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <style>
                div {
                    min-width: 100px;
                    min-height: 50px;
                    display: inline-block;
                    padding: 2em;
                }
                #div1 { background: red; }
                #div2 { background: green; }
                #div3 { background: blue; }
                #info { display: block; white-space: pre; }
            </style>
            <script>
                window.addEventListener('load', function() {
    
                    // #div1, #div3
                    document.querySelector('#div1').addEventListener('click', function(e) {
                        if (e.target == e.currentTarget ||
                            e.target == document.querySelector('#div3')) {
                            document.querySelector('#info').textContent +=
                                'I am #1 or #3\n';
                        }
                    });
    
                    // #div2
                    document.querySelector('#div2').addEventListener('click', function(e) {
                        if (e.currentTarget == e.target) {
                            document.querySelector('#info').textContent += 'I am #2\n';
                        }
                    });
                });
            </script>
        </head>
        <body>
            <div id="div1">
                <div id="div2">
                    <div id="div3"></div>
                </div>
            </div>
            <div id="info"></div>
        </body>
    </html>
    

    So calling stopPropagation() in onClick event function of DIV #3 is not necessary required to prevent firing the onClick event if DIV #2.

    Also note, that the document structure is as follows:

    document
      document.documentElement
        document.body
          ...
    

    If an event propagation is not stopped, it will reach the document object. event.currentTarget will then be document, while event.target will be either document.documentElement, document.body or any sub element under the <body> element.

    So considering you have following code:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <style>
                html {
                    background: #009688;
                }
                body {
                    background: #bbb;
                }
            </style>
        </head>
        <body>
            <div>Hello world</div>
            <div>Hello world</div>
            <div>Hello world</div>
            <div>Hello world</div>
            <div>Hello world</div>
            <div>Hello world</div>
        </body>
    </html>
    

    Here is what it looks like and where the different parts of the document are:

    Gray is the body area. Green is the actual document "element" (top most styleable part). And behind it, there is the invisible document object.

    If you want to use event functions, which will be executed only for the elements directly under your finger / mouse cursor, you could use following code (example for onClick event):

    elm.addEventListener('click', function(e) {
        if
        (
            (
                (e.currentTarget == document) &&
                (e.target == document.documentElement || e.target == document.body)
            )
            ||
            (e.currentTarget == e.target)
        )
        {
            // ...
        }
    });
    

    It works with document.documentElement, document.body or any element on the document without needing to call stopPropagation().

    0 讨论(0)
  • 2021-02-03 14:36

    There are several ways you can handle events in javascript/jQuery. Two of them are:

    1. You can use a direct event handler on the object.
    2. You can use delegated event handling where you handle a propagated event on a parent.

    If you are using a direct event handler on the object and there are no delegated event handlers configured in the page, then there is no reason for e.stopPropagation().

    But, if you have delegated event handlers that are using propagation, you sometimes want to make sure that a higher level delegated event handler does not fire on the current event.

    In your particular example:

    $(document.body).on('click', "a.ajaxLink", function(e){ 
    

    This is a delegated event handler. It is looking for any click event that propagates up to the document.body object, yet originated on an a.ajaxLink object. Here, there is little advantage to e.stopPropagation() because the event is almost entirely propagated already (it will also go up to the document, but unless you also have a handler for click on the document object, then there's no reason to e.stopPropagation() in this handler.

    Where it would make a lot of sense would be when you have both a top level delegated event handler (like the one in your example) and you have lower level event handlers (either directly on the objects or using delegated event handling, but at a level below the document.body object. In that case, if you only want the lower level event handler to get the event, then you would call e.stopPropagation() in it's handler so that the document.body handler never saw the event.

    $("a.ajaxLink").click(function(e) {
        if (some condition) {
            // do something specific to this condition
            code here
            // stop propagation so the default behavior for click in document.body does not fire
            e.stopPropagation();
        }
    })
    

    Note: Using return false from a jQuery event handler triggers both e.stopPropagation() and e.preventDefault(). But, if you are in a delegated event handler, the e.preventDefault() doesn't do anything because the default behavior (if there was one) has already fired when the target object first saw the event. Default behaviors happen before event propagation so e.preventDefault() only works in event handlers directly on the target object.


    There is no noticeable performance degradation because you allow an event to bubble up because these are user level events and they just don't occur fast enough to matter, not is the bubbling particularly slow when there are no handlers on all the intervening objects. The system already special cases some events like mousemove that can happen rapidly to solve that issue. If you have a giant project with hundreds or thousands of event handlers, there are cases where using delegated event handling is more efficient and there are cases where direct event handlers on the actual target objects are more efficient. But, except in giant scenarios, the performance difference is probably not noticeable.

    Here's an example where bubbling/delegation is more efficient. You have a giant table with thousands of rows and each row has two buttons in it (add/delete say). With delegated event handling, you can handle all the buttons in two simple event handlers that you attach to the table object (a common parent of the buttons). This will be much quicker to install the event handlers rather than installing several thousand event handlers directly on each and every button. These delegated event handlers will also automatically work on newly created rows/objects in the table. This is the ideal scenario for event bubbling/delegated event handlers. Note, in this scenario, there is no reason to stop propagation/bubbling.

    Here's an example of where delegated event handlers are very inefficient. Supposed you have a good-sized web page with hundreds of objects and event handlers. You could make every one of the event handlers be a delegated event handler attached to the document object. But, here's what happens. A click happens. There's no event handler on the actual object so it bubbles up. Eventually, it gets to the document object. The document object has hundreds of event handlers. The event processing engine (jQuery in this case) has to look through every one of those event handlers and compare the selector in the delegated event handler for each one with the original event target to see if they match. Some of these comparisons are not fast as they can be full-blown CSS selectors. It has to do that for hundreds of delegated events. This is bad for performance. This is exactly why .live() in jQuery was deprecated because it worked this way. Instead, delegated event handlers should be placed as close to the target object as possible (the closest parent that is practical given the circumstances). And, when there is no need for a delegated event handler, handlers should be put on the actual target object as this is the most efficient at runtime.


    Back to your original question. There is no time that you want bubbling turned off generally. As I described earlier in my answer, there are specific instances where an event handler further out on the tree wants to handle the event and stop any delegated event handlers higher up in the DOM tree from processing this event. That is a time to e.stopPropatation().


    Here are several other relevant posts with useful info on this topic (as it has been widely discussed before):

    Why not take Javascript event delegation to the extreme?

    Should all jquery events be bound to $(document)?

    Does jQuery.on() work for elements that are added after the event handler is created?

    jQuery on() and stopPropagation()

    Best practice to avoid memory or performance issues related to binding a large number of DOM objects to a click event

    jQuery .live() vs .on() method for adding a click event after loading dynamic html

    0 讨论(0)
  • 2021-02-03 14:46

    Imagine you have a button and want to handle a click event:

    <button>
        <img src="icon" />
        <span>Text</span>
    </button>
    

    Without event propagation, clicking on the image or the text wouldn't trigger the click event handler bound to the button, as the event would never leave the <img> or the <span> elements.


    A case where you don't want event propagation is where nested elements have their own event handlers. Here's one example:

    <button>
        <img src="icon" />
        <span>Text</span>
        <span class="arrow">&darr;</span>
    </button>
    

    If you don't stop the event propagation with .arrow's event handler, it will trigger the button's event handler as well.

    0 讨论(0)
提交回复
热议问题