通过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;
在这种情况下,相同的修改似乎并不合理。
所以,问题是:
以下代码段显示了问题。
如果您打开浏览器控制台并单击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;
},
答案 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 );
}