Ich gehe davon aus, dass bei einem Element, bei dem alle untergeordneten Elemente nacheinander im Dokument angeordnet sind, der schnellste Weg darin bestehen sollte, eine binäre Suche durchzuführen und die Dokumentpositionen der Elemente zu vergleichen. Wie in der Schlussfolgerung eingeführt, wird die Hypothese jedoch zurückgewiesen. Je mehr Elemente Sie haben, desto größer ist das Leistungspotenzial. Wenn Sie beispielsweise 256 Elemente hätten, müssten Sie (optimalerweise) nur 16 davon überprüfen! Für 65536 nur 256! Die Leistung wächst auf 2! Weitere Zahlen / Statistiken anzeigen. Besuchen Sie Wikipedia
(function(constructor){
'use strict';
Object.defineProperty(constructor.prototype, 'parentIndex', {
get: function() {
var searchParent = this.parentElement;
if (!searchParent) return -1;
var searchArray = searchParent.children,
thisOffset = this.offsetTop,
stop = searchArray.length,
p = 0,
delta = 0;
while (searchArray[p] !== this) {
if (searchArray[p] > this)
stop = p + 1, p -= delta;
delta = (stop - p) >>> 1;
p += delta;
}
return p;
}
});
})(window.Element || Node);
Die Art und Weise, wie Sie es verwenden, besteht darin, die Eigenschaft 'parentIndex' eines beliebigen Elements abzurufen. Schauen Sie sich zum Beispiel die folgende Demo an.
(function(constructor){
'use strict';
Object.defineProperty(constructor.prototype, 'parentIndex', {
get: function() {
var searchParent = this.parentNode;
if (searchParent === null) return -1;
var childElements = searchParent.children,
lo = -1, mi, hi = childElements.length;
while (1 + lo !== hi) {
mi = (hi + lo) >> 1;
if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) {
hi = mi;
continue;
}
lo = mi;
}
return childElements[hi] === this ? hi : -1;
}
});
})(window.Element || Node);
output.textContent = document.body.parentIndex;
output2.textContent = document.documentElement.parentIndex;
Body parentIndex is <b id="output"></b><br />
documentElements parentIndex is <b id="output2"></b>
Einschränkungen
- Diese Implementierung der Lösung funktioniert in IE8 und darunter nicht.
Binäre VS Lineare Suche Auf 200.000 Elementen (könnte einige mobile Browser zum Absturz bringen, ACHTUNG!):
- In diesem Test werden wir sehen, wie lange es dauert, bis eine lineare Suche das mittlere Element im Vergleich zu einer binären Suche findet. Warum das mittlere Element? Da es sich am durchschnittlichen Standort aller anderen Standorte befindet, werden alle möglichen Standorte am besten dargestellt.
Binäre Suche
(function(constructor){
'use strict';
Object.defineProperty(constructor.prototype, 'parentIndexBinarySearch', {
get: function() {
var searchParent = this.parentNode;
if (searchParent === null) return -1;
var childElements = searchParent.children,
lo = -1, mi, hi = childElements.length;
while (1 + lo !== hi) {
mi = (hi + lo) >> 1;
if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) {
hi = mi;
continue;
}
lo = mi;
}
return childElements[hi] === this ? hi : -1;
}
});
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
var child=test.children.item(99.9e+3);
var start=performance.now(), end=Math.round(Math.random());
for (var i=200 + end; i-- !== end; )
console.assert( test.children.item(
Math.round(99.9e+3+i+Math.random())).parentIndexBinarySearch );
var end=performance.now();
setTimeout(function(){
output.textContent = 'It took the binary search ' + ((end-start)*10).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
test.remove();
test = null; // free up reference
}, 125);
}, 125);
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Rückwärts (`lastIndexOf`) Lineare Suche
(function(t){"use strict";var e=Array.prototype.lastIndexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
var child=test.children.item(99e+3);
var start=performance.now(), end=Math.round(Math.random());
for (var i=2000 + end; i-- !== end; )
console.assert( test.children.item(
Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
var end=performance.now();
setTimeout(function(){
output.textContent = 'It took the backwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
test.remove();
test = null; // free up reference
}, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Vorwärts (`indexOf`) Lineare Suche
(function(t){"use strict";var e=Array.prototype.indexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
var child=test.children.item(99e+3);
var start=performance.now(), end=Math.round(Math.random());
for (var i=2000 + end; i-- !== end; )
console.assert( test.children.item(
Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
var end=performance.now();
setTimeout(function(){
output.textContent = 'It took the forwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
test.remove();
test = null; // free up reference
}, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
PreviousElementSibling Counter Search
Zählt die Anzahl der PreviousElementS Geschwister, um den parentIndex abzurufen.
(function(constructor){
'use strict';
Object.defineProperty(constructor.prototype, 'parentIndexSiblingSearch', {
get: function() {
var i = 0, cur = this;
do {
cur = cur.previousElementSibling;
++i;
} while (cur !== null)
return i; //Returns 3
}
});
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
var child=test.children.item(99.95e+3);
var start=performance.now(), end=Math.round(Math.random());
for (var i=100 + end; i-- !== end; )
console.assert( test.children.item(
Math.round(99.95e+3+i+Math.random())).parentIndexSiblingSearch );
var end=performance.now();
setTimeout(function(){
output.textContent = 'It took the PreviousElementSibling search ' + ((end-start)*20).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
test.remove();
test = null; // free up reference
}, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Keine Suche
Für das Benchmarking wäre das Ergebnis des Tests, wenn der Browser die Suche optimiert hätte.
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
var start=performance.now(), end=Math.round(Math.random());
for (var i=2000 + end; i-- !== end; )
console.assert( true );
var end=performance.now();
setTimeout(function(){
output.textContent = 'It took the no search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
test.remove();
test = null; // free up reference
}, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden></div>
Der Zusammenstoß
Nach dem Anzeigen der Ergebnisse in Chrome sind die Ergebnisse jedoch das Gegenteil von den erwarteten Ergebnissen. Die dümmer vorwärts gerichtete lineare Suche war überraschend 187 ms, 3850%, schneller als die binäre Suche. Offensichtlich hat Chrome das auf magische Weise überlistet console.assert
und optimiert, oder (optimistischer) Chrome verwendet intern ein numerisches Indizierungssystem für das DOM, und dieses interne Indizierungssystem wird durch die Optimierungen verfügbar gemacht, die Array.prototype.indexOf
bei der Verwendung auf ein HTMLCollection
Objekt angewendet werden.
parent.childNodes
, anstattparent.children
? Letzteres listet nur dieElements
, mit Ausnahme bestimmterText
Knoten, auf ... Einige der Antworten hier, z. B. usingpreviousSibling
, basieren auf der Verwendung aller untergeordneten Knoten, während andere sich nur mit Kindern beschäftigen, dieElement
... (!)