title: Multiple searching of the same elements in DOM
body: <p>There are perfomanse problems with bubbling subscribing via jQuery. IE9 and IE11 tell that 80% of time are spent on executing <code>querySelectorAll</code>. Analisis shows the function <code>$.event.dispatch</code> (in jQuery 1.8.1, in newer version (1.11.3) this functionality was moved to <code>$.event.handlers</code>), which containes following code:</p>
  
  <pre><code>for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
  
    // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
    if ( cur.disabled !== true || event.type !== "click" ) {
      selMatch = {};
      matches = [];
      for ( i = 0; i &lt; delegateCount; i++ ) {
        handleObj = handlers[ i ];
        sel = handleObj.selector;
  
        if ( selMatch[ sel ] === undefined ) {
          selMatch[ sel ] = jQuery( sel, this ).index( cur ) &gt;= 0;
        }
        if ( selMatch[ sel ] ) {
          matches.push( handleObj );
        }
      }
      if ( matches.length ) {
        handlerQueue.push({ elem: cur, matches: matches });
      }
    }
  }
  </code></pre>
  
  <p>Attension to following lines:</p>
  
  <pre><code>// For each element from clicked and above
  for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
      // Clear the search cache
      selMatch = {};
  
      // For each subscriber
      for ( i = 0; i &lt; delegateCount; i++ ) {
        // Take the subscriber's selector
        sel = handleObj.selector;
  
        // If it's out of cache
        if ( selMatch[ sel ] === undefined ) {
          // Search for elements matching to the selector
          // And remember if current element is among of found ones
          selMatch[ sel ] = jQuery( sel, this ).index( cur ) &gt;= 0;
  </code></pre>
  
  <p>As the subscription is made on body, every such search takes all elements matching the selector from the whole document. And this is repeated as many times as the depth of the clicked element.</p>
  
  <p>As I understand, the outer loop is to garantee write order of handlers according to bubbling order. There is a cache, but it works only at one level and helps in case of multiple subscription with the same selector.</p>
  
  <p>The question is why cache is implemented in such manner? Why not to keep the jQuery collection and move <code>index</code> to the next <code>if</code> condition?</p>
  
  <hr>
  
  <p>But that's not all. I looked the actual implementation in 1.11.3. It also uses multiple seaches but this line of code is changed.</p>
  
  <p>In 1.8.1 it was:</p>
  
  <pre><code>selMatch[ sel ] = jQuery( sel, this ).index( cur ) &gt;= 0;
  </code></pre>
  
  <p>In 1.11.3 it have become:</p>
  
  <pre><code>matches[ sel ] = handleObj.needsContext ?
  jQuery( sel, this ).index( cur ) &gt;= 0 :
  jQuery.find( sel, this, null, [ cur ] ).length;
  </code></pre>
  
  <p>Same modification in this case doesn't seem to be reasonable.</p>
  
  <hr>
  
  <p>So, the questions are:</p>
  
  <ol>
  <li>What reason could've lead to this not optimal code?</li>
  <li>What should I do to solve perfomance problems?</li>
  </ol>
  
  <hr>
  
  <p>The following snippet shows the problem.</p>
  
  <p>If you open the browser console and click <code>Click me to get a lot of searches!</code>.
  you'll see following lines outputted 21 times:</p>
  
  <pre><code>qsa [id='sizcache041783330822363496'] section .smth
  gbc smth-other
  </code></pre>
  
  <p>About the segment <code>[id='sizcache041783330822363496']</code> there is a <a href="https://ru.stackoverflow.com/q/465128/178988">related question in Russian</a>. Shortly, such form simplifies escaping of special characters in id if it was original. Some time ago Sizzle <a href="https://github.com/jquery/sizzle/commit/1a8958f6bc25ffb1ff2a1306ffd28d0539f93f84" rel="nofollow noreferrer">has updated this place</a>, but even actual jQuery version doesn't contain it.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="false" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>$(function () {
    $("body")
    .on("click", "section .smth", function () { console.log("clicked", "section .smth") })
    .on("click", ".smth-other", function () { console.log("clicked", ".smth-other") });
  
    $("h1").text("Click me to get a lot of searches!");
  
    var qsa = Element.prototype.querySelectorAll, gbc = Element.prototype.getElementsByClassName;
    Element.prototype.querySelectorAll = function(s) { console.log('qsa', s); return qsa.apply(this, arguments) };
    Element.prototype.getElementsByClassName = function(s) { console.log('gbc', s); return gbc.apply(this, arguments) };
  });</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>body { counter-reset: lev 1; }
  div { counter-increment: lev; }
  h1, h2 { cursor: pointer; }
  h1:hover, h2:hover { background: silver; }
  h1:after { content: " (" counter(lev) ")"; }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h1&gt;
    Loading...
  &lt;/h1&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
  
  &lt;section&gt;
    &lt;h2 class="smth"&gt;I'm smth and i'm waiting for a click&lt;/h2&gt;
  &lt;/section&gt;
  
  &lt;section&gt;
    &lt;h2 class="smth-other"&gt;I'm smth other and i'm waiting for a click&lt;/h2&gt;
  &lt;/section&gt;
  
  &lt;script src="//code.jquery.com/jquery-1.8.1.js"&gt;&lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <hr>
  
  <p>The next snippet shows the full code of jQuery 1.8.1 <code>dispatch</code> function (snippet is used to make collapsible spoiler, it's not for running the code):</p>
  
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>dispatch: function( event ) {
  
    // Make a writable jQuery.Event from the native event object
    event = jQuery.event.fix( event || window.event );
  
    var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
      handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
      delegateCount = handlers.delegateCount,
      args = [].slice.call( arguments ),
      run_all = !event.exclusive &amp;&amp; !event.namespace,
      special = jQuery.event.special[ event.type ] || {},
      handlerQueue = [];
  
    // Use the fix-ed jQuery.Event rather than the (read-only) native event
    args[0] = event;
    event.delegateTarget = this;
  
    // Call the preDispatch hook for the mapped type, and let it bail if desired
    if ( special.preDispatch &amp;&amp; special.preDispatch.call( this, event ) === false ) {
      return;
    }
  
    // Determine handlers that should run if there are delegated events
    // Avoid non-left-click bubbling in Firefox (#3861)
    if ( delegateCount &amp;&amp; !(event.button &amp;&amp; event.type === "click") ) {
  
      for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
  
        // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
        if ( cur.disabled !== true || event.type !== "click" ) {
          selMatch = {};
          matches = [];
          for ( i = 0; i &lt; delegateCount; i++ ) {
            handleObj = handlers[ i ];
            sel = handleObj.selector;
  
            if ( selMatch[ sel ] === undefined ) {
              selMatch[ sel ] = jQuery( sel, this ).index( cur ) &gt;= 0;
            }
            if ( selMatch[ sel ] ) {
              matches.push( handleObj );
            }
          }
          if ( matches.length ) {
            handlerQueue.push({ elem: cur, matches: matches });
          }
        }
      }
    }
  
    // Add the remaining (directly-bound) handlers
    if ( handlers.length &gt; delegateCount ) {
      handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
    }
  
    // Run delegates first; they may want to stop propagation beneath us
    for ( i = 0; i &lt; handlerQueue.length &amp;&amp; !event.isPropagationStopped(); i++ ) {
      matched = handlerQueue[ i ];
      event.currentTarget = matched.elem;
  
      for ( j = 0; j &lt; matched.matches.length &amp;&amp; !event.isImmediatePropagationStopped(); j++ ) {
        handleObj = matched.matches[ j ];
  
        // Triggered event must either 1) be non-exclusive and have no namespace, or
        // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
        if ( run_all || (!event.namespace &amp;&amp; !handleObj.namespace) || event.namespace_re &amp;&amp; event.namespace_re.test( handleObj.namespace ) ) {
  
          event.data = handleObj.data;
          event.handleObj = handleObj;
  
          ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
              .apply( matched.elem, args );
  
          if ( ret !== undefined ) {
            event.result = ret;
            if ( ret === false ) {
              event.preventDefault();
              event.stopPropagation();
            }
          }
        }
      }
    }
  
    // Call the postDispatch hook for the mapped type
    if ( special.postDispatch ) {
      special.postDispatch.call( this, event );
    }
  
    return event.result;
  },</code></pre>
  </div>
  </div>
  </p>
  
  <p>PS: <a href="https://ru.stackoverflow.com/q/476703/178988">Same question in Russian.</a></p>