querySelector sucht sofort untergeordnete Elemente


95

Ich habe eine jquery-ähnliche Funktion:

function(elem) {
    return $('> someselector', elem);
};

Die Frage ist, wie kann ich dasselbe tun querySelector()?

Das Problem ist, dass die >Auswahl in querySelector()der übergeordneten Angabe explizit angegeben werden muss. Gibt es eine Problemumgehung?


Ich habe eine Antwort hinzugefügt, die für Sie vielleicht besser funktioniert, lassen Sie es mich wissen;)
csuwldcat

Antworten:


118

Obwohl dies keine vollständige Antwort ist, sollten Sie die W3C Selector API v.2 im Auge behalten, die in den meisten Browsern sowohl auf dem Desktop als auch auf dem Handy bereits verfügbar ist, mit Ausnahme des IE (Edge scheint dies zu unterstützen): Siehe vollständige Supportliste .

function(elem) {
  return elem.querySelectorAll(':scope > someselector');
};

12
Dies ist die richtige Antwort. Die Browserunterstützung ist jedoch begrenzt und Sie benötigen eine Unterlegscheibe, wenn Sie sie verwenden möchten. Zu diesem Zweck habe ich scopedQuerySelectorShim erstellt .
Lazd


6
Ist eigentlich :scopenoch unter Drafts.csswg.org/selectors-4/#the-scope-pseudo angegeben . Bisher wurde nur das <style scoped>Attribut entfernt. Es ist unklar, ob dies auch zur :scopeEntfernung führen wird (da dies <style scoped>ein wichtiger Anwendungsfall war :scope).
John Mellor

1
Was für eine Überraschung: Nur Edge unterstützt dies nicht
:)

31

Das kannst du nicht. Es gibt keinen Selektor, der Ihren Startpunkt simuliert.

Die Art und Weise, wie jQuery dies tut (eher aufgrund eines qsaVerhaltens, das nicht ihren Wünschen entspricht), besteht darin, dass sie prüfen, ob elemeine ID vorhanden ist, und wenn nicht, vorübergehend eine ID hinzufügen und dann eine vollständige Auswahlzeichenfolge erstellen.

Grundsätzlich würden Sie tun:

var sel = '> someselector';
var hadId = true;
if( !elem.id ) {
    hadID = false;
    elem.id = 'some_unique_value';
}

sel = '#' + elem.id + sel;

var result = document.querySelectorAll( sel );

if( !hadId ) {
    elem.id = '';
}

Dies ist sicherlich kein jQuery-Code, aber soweit ich mich erinnere, ist es im Grunde das, was sie tun. Nicht nur in dieser Situation, sondern in jeder Situation, in der Sie einen Selektor aus dem Kontext eines verschachtelten Elements ausführen.

Quellcode für Sizzle


1
Zum Zeitpunkt des Schreibens korrigieren, eine aktualisierte Methode finden Sie in der Antwort von @ avetisk.
Lazd

23

Komplett: Scope Polyfill

Wie avetisk hat genannten Selektoren API 2 verwendet :scopePseudo-Selektor.
Damit dies in allen Browsern (die diese unterstützen querySelector) funktioniert , ist hier die Polyfüllung

(function(doc, proto) {
  try { // check if browser supports :scope natively
    doc.querySelector(':scope body');
  } catch (err) { // polyfill native methods if it doesn't
    ['querySelector', 'querySelectorAll'].forEach(function(method) {
      var nativ = proto[method];
      proto[method] = function(selectors) {
        if (/(^|,)\s*:scope/.test(selectors)) { // only if selectors contains :scope
          var id = this.id; // remember current element id
          this.id = 'ID_' + Date.now(); // assign new unique id
          selectors = selectors.replace(/((^|,)\s*):scope/g, '$1#' + this.id); // replace :scope with #ID
          var result = doc[method](selectors);
          this.id = id; // restore previous id
          return result;
        } else {
          return nativ.call(this, selectors); // use native code for other selectors
        }
      }
    });
  }
})(window.document, Element.prototype);

Verwendung

node.querySelector(':scope > someselector');
node.querySelectorAll(':scope > someselector');

Aus historischen Gründen meine bisherige Lösung

Basierend auf allen Antworten

// Caution! Prototype extending
Node.prototype.find = function(selector) {
    if (/(^\s*|,\s*)>/.test(selector)) {
        if (!this.id) {
            this.id = 'ID_' + new Date().getTime();
            var removeId = true;
        }
        selector = selector.replace(/(^\s*|,\s*)>/g, '$1#' + this.id + ' >');
        var result = document.querySelectorAll(selector);
        if (removeId) {
            this.id = null;
        }
        return result;
    } else {
        return this.querySelectorAll(selector);
    }
};

Verwendung

elem.find('> a');

Date.now()wird von älteren Browsern nicht unterstützt und könnte durchnew Date().getTime()
Christophe

4

Wenn Sie den Tag-Namen des Elements kennen, das Sie untersuchen, können Sie ihn im Selektor verwenden, um das zu erreichen, was Sie möchten.

Zum Beispiel, wenn Sie ein haben <select>, das <option>s und hat <optgroups>, und Sie nur das <option>s wollen, das seine unmittelbaren Kinder sind, nicht die darin <optgoups>:

<select>
  <option>iPhone</option>
  <optgroup>
    <option>Nokia</option>
    <option>Blackberry</option>
  </optgroup>
</select>

Wenn Sie also einen Verweis auf das ausgewählte Element haben, können Sie - überraschenderweise - seine unmittelbaren Kinder wie folgt erhalten:

selectElement.querySelectorAll('select > option')

Es scheint in Chrome, Safari und Firefox zu funktionieren, wurde jedoch nicht in IEs getestet. = /


8
Warnung: Diese Antwort schränkt den Umfang nicht ein. Wenn das Muster in einer tieferen Nest-Ebene wiederholt wird, wird es immer noch ausgewählt, auch wenn es keine direkten Kinder sind. <ul id="start"> <li>A</li> <li> <ul> <li>b</li> <li>c</li> </ul> </li> </ul> var result = document.getElementById('start').querySelectorAll('ul>li'); -> Alle 4 li Elemente werden ausgewählt!
Risord

1
Gott bewahre, dass es zwei <select>Elemente in demselben Elternteil gab.
Tomáš Zato - Wiedereinsetzung Monica

4

Wenn Sie eventuell direkte Kinder finden möchten (und nicht zB > div > span), können Sie Element.matches () ausprobieren :

const elems = Array.from(elem.children).filter(e => e.matches('.my-class'))

2

ANSPRUCH

Persönlich würde ich die Antwort von Patrick Dw nehmen und +1 seine Antwort, meine Antwort ist für die Suche nach einer alternativen Lösung. Ich denke nicht, dass es eine Ablehnung verdient.

Hier ist mein Versuch:

function q(elem){
    var nodes = elem.querySelectorAll('someSeletor');
    console.log(nodes);
    for(var i = 0; i < nodes.length; i++){
        if(nodes[i].parentNode === elem) return nodes[i];
    }
}

siehe http://jsfiddle.net/Lgaw5/8/


1
Ein paar Probleme damit. @disfated möchte, dass es funktioniert querySelector, was bedeutet, dass :eq()es nicht verwendet werden kann. Selbst wenn dies möglich wäre, würde Ihr Selektor das Element, das :eq()auf der Seite angezeigt wird, und nicht :eq()den Index des übergeordneten Elements (wo Sie das erhalten idx) zurückgeben.
user113716

+1 über: eq () & querySelector. Und ich sollte den Kontext $ (elem) .parent () hinzufügen.
Liangliang Zheng

Leider funktioniert das auch nicht. Wenn der Selektor aus dem Kontext des übergeordneten Elements ausgeführt wird, beginnt er mit dem am weitesten links stehenden untergeordneten Element und findet alle übereinstimmenden Elemente, unabhängig davon, wie tief verschachtelt sie sind. Nehmen wir also an, das elemist bei Index 4, aber es gibt ein vorheriges Geschwister, das ein anderes hat tagName, aber es hat ein Element mit dem passenden tagNameElement darin verschachtelt. Dieses verschachtelte Element wird in die Ergebnisse aufgenommen, bevor das Element, auf das Sie abzielen, erneut abgeworfen wird Der Index. Hier ist ein Beispiel
user113716

... was Sie letztendlich tun, ist eine komplexere Version des Codes in der Frage, die funktioniert. $('> someselector', elem);; o)
user113716

Ja, aber Sie mussten dieses einzelne Inkrement fest codieren. Das würde Vorkenntnisse erfordern. Wenn ich ein anderes ähnliches Element hinzufüge, ist es wieder kaputt. ; o) jsfiddle.net/Lgaw5/3
user113716

1

Das hat bei mir funktioniert:

Node.prototype.search = function(selector)
{
    if (selector.indexOf('@this') != -1)
    {
        if (!this.id)
            this.id = "ID" + new Date().getTime(); 
        while (selector.indexOf('@this') != -1)
            selector = selector.replace('@this', '#' + this.id);
        return document.querySelectorAll(selector);
    } else 
        return this.querySelectorAll(selector);
};

Sie müssen das @ dieses Schlüssels vor dem> übergeben, wenn Sie nach unmittelbaren Kindern suchen möchten.


Scheint dasselbe zu sein wie die aktuell akzeptierte Antwort ... Übrigens, wozu dient die seltsame "@this" -Syntax? Warum nicht einfach mit regulärem Ausdruck auf ">" testen?
8:52 disfated

1

Das Folgende ist eine vereinfachte, generische Methode zum Ausführen einer CSS-Selektorabfrage nur über direkte untergeordnete Abfragen. Sie berücksichtigt auch kombinierte Abfragen wie "foo[bar], baz.boo":

var count = 0;
function queryChildren(element, selector) {
  var id = element.id,
      guid = element.id = id || 'query_children_' + count++,
      attr = '#' + guid + ' > ',
      selector = attr + (selector + '').replace(',', ',' + attr, 'g');
  var result = element.parentNode.querySelectorAll(selector);
  if (!id) element.removeAttribute('id');
  return result;
}


*** Example Use ***

queryChildren(someElement, '.foo, .bar[xyz="123"]');

1

Es gibt eine abfragebezogene Bibliothek, die ein praktischer Ersatz für die Abfrageselektion ist . Es füllt den Kinder-Selektor '> *'und :scope(inkl. IE8) mehrfach aus und normalisiert den :rootSelektor. Außerdem stellt es einige spezielle relativ pseudos wie :closest, :parent, :prev, :next, für alle Fälle.


0

Überprüfen Sie, ob das Element eine andere ID hat. Fügen Sie eine zufällige ID hinzu und suchen Sie danach

function(elem) {
      if(!elem.id)
          elem.id = Math.random().toString(36).substr(2, 10);
      return elem.querySelectorAll(elem.id + ' > someselector');
    };

wird das Gleiche tun wie

$("> someselector",$(elem))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.