在DOM

时间:2015-12-21 18:48:14

标签: javascript jquery dom optimization

通过jQuery冒泡订阅存在perfomanse问题。 IE9和IE11告诉我80%的时间花在执行querySelectorAll上。 Analisis显示函数$.event.dispatch(在jQuery 1.8.1中,在较新版本(1.11.3)中,此功能已移至$.event.handlers),其中包含以下代码:

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 < delegateCount; i++ ) {
      handleObj = handlers[ i ];
      sel = handleObj.selector;

      if ( selMatch[ sel ] === undefined ) {
        selMatch[ sel ] = jQuery( sel, this ).index( cur ) >= 0;
      }
      if ( selMatch[ sel ] ) {
        matches.push( handleObj );
      }
    }
    if ( matches.length ) {
      handlerQueue.push({ elem: cur, matches: matches });
    }
  }
}

参加以下行:

// 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 < 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 ) >= 0;

由于订阅是在正文上进行的,因此每个此类搜索都会从整个文档中获取与选择器匹配的所有元素。并且重复次数与点击元素的深度重复。

据我所知,外循环是根据冒泡顺序保证处理程序的写入顺序。有一个缓存,但它仅在一个级别有效,并且在使用相同选择器进行多次订阅的情况下有帮助。

问题是为什么以这种方式实现缓存?为什么不保留jQuery集合并将index移到下一个if条件?

但这不是全部。我查看了1.11.3中的实际实现。它还使用多个seaches,但这行代码已更改。

在1.8.1中它是:

selMatch[ sel ] = jQuery( sel, this ).index( cur ) >= 0;

在1.11.3中它已成为:

matches[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length;

在这种情况下,相同的修改似乎并不合理。

所以,问题是:

  1. 可能导致这个不是最佳代码的原因是什么?
  2. 我应该怎样做才能解决性能问题?
  3. 以下代码段显示了问题。

    如果您打开浏览器控制台并单击Click me to get a lot of searches!。 你会看到以下几行输出了21次:

    qsa [id='sizcache041783330822363496'] section .smth
    gbc smth-other
    

    关于细分[id='sizcache041783330822363496'],有一个related question in Russian。很快,这种形式简化了id中特殊字符的转义,如果它是原始的。前段时间Sizzle has updated this place,但即使是实际的jQuery版本也不包含它。

    $(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) };
    });
    body { counter-reset: lev 1; }
    div { counter-increment: lev; }
    h1, h2 { cursor: pointer; }
    h1:hover, h2:hover { background: silver; }
    h1:after { content: " (" counter(lev) ")"; }
    <div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><h1>
      Loading...
    </h1></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
    
    <section>
      <h2 class="smth">I'm smth and i'm waiting for a click</h2>
    </section>
    
    <section>
      <h2 class="smth-other">I'm smth other and i'm waiting for a click</h2>
    </section>
    
    <script src="//code.jquery.com/jquery-1.8.1.js"></script>

    下一个代码段显示了jQuery 1.8.1 dispatch函数的完整代码(代码片段用于制作可折叠的剧透,但它不用于运行代码):

    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 && !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 && 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 && !(event.button && 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 < delegateCount; i++ ) {
              handleObj = handlers[ i ];
              sel = handleObj.selector;
    
              if ( selMatch[ sel ] === undefined ) {
                selMatch[ sel ] = jQuery( sel, this ).index( cur ) >= 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 > delegateCount ) {
        handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
      }
    
      // Run delegates first; they may want to stop propagation beneath us
      for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
        matched = handlerQueue[ i ];
        event.currentTarget = matched.elem;
    
        for ( j = 0; j < matched.matches.length && !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 && !handleObj.namespace) || event.namespace_re && 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;
    },

    PS:Same question in Russian.

1 个答案:

答案 0 :(得分:0)

我通过缓存提升了界限:

if ( delegateCount && !(event.button && event.type === "click") ) {
    selMatch = {};

将来自cahing的index来电转移到下一个条件:

if ( selMatch[ sel ] === undefined ) {
    selMatch[ sel ] = jQuery( sel, this );
}
if ( selMatch[ sel ].index( cur ) >= 0 ) {
    matches.push( handleObj );
}