querySelector mencari turunan langsung

99

Saya memiliki beberapa fungsi seperti jquery:

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

Pertanyaannya adalah bagaimana saya bisa melakukan hal yang sama querySelector()?

Masalahnya adalah >selector in querySelector()membutuhkan induk untuk ditentukan secara eksplisit. Apakah ada solusi lain?

disfated
sumber
Saya menambahkan jawaban yang mungkin bekerja lebih baik untuk Anda, beri tahu saya;)
csuwldcat

Jawaban:

121

Meskipun ini bukan jawaban lengkap, Anda harus tetap memperhatikan W3C Selector API v.2 yang sudah tersedia di sebagian besar browser, baik desktop maupun seluler, kecuali IE (Edge tampaknya mendukung): lihat daftar dukungan lengkap .

function(elem) {
  return elem.querySelectorAll(':scope > someselector');
};
avetisk
sumber
13
Ini jawaban yang benar. Namun, dukungan peramban terbatas dan Anda memerlukan shim jika ingin menggunakannya. Saya membuat scopedQuerySelectorShim untuk tujuan ini.
lazd
3
Atribut: scope telah dihapus dari spesifikasi saat ini .
tobi
6
Sebenarnya, :scopemasih ditentukan di drafts.csswg.org/selectors-4/#the-scope-pseudo . Sejauh ini hanya <style scoped>atribut yang dihapus; tidak jelas apakah itu juga akan menyebabkan :scopepenghapusan (karena <style scoped>merupakan kasus penggunaan penting untuk :scope).
John Mellor
1
Betapa mengejutkan: hanya Edge yang tidak mendukung ini
:)
32

Tidak boleh. Tidak ada pemilih yang akan mensimulasikan titik awal Anda.

Cara jQuery melakukannya (lebih karena cara qsaberperilaku yang tidak sesuai dengan keinginan mereka), adalah mereka memeriksa untuk melihat apakah elemmemiliki ID, dan jika tidak, mereka menambahkan ID sementara, lalu membuat string pemilih lengkap.

Pada dasarnya Anda akan melakukan:

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 = '';
}

Ini tentu saja bukan kode jQuery, tetapi dari apa yang saya ingat, pada dasarnya itulah yang mereka lakukan. Tidak hanya dalam situasi ini, tetapi dalam situasi apa pun di mana Anda menjalankan pemilih dari konteks elemen bertingkat.

Kode sumber untuk Sizzle

pengguna113716
sumber
1
Benar pada saat penulisan, tetapi lihat jawaban @ avetisk untuk metode yang diperbarui.
lazd
24

Lengkap: ruang lingkup polyfill

Seperti avetisk telah disebutkan Penyeleksi API 2 menggunakan :scopesemu-pemilih.
Untuk membuat ini berfungsi di semua browser (yang mendukung querySelector), inilah polyfill

(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);

Pemakaian

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

Untuk alasan historis, solusi saya sebelumnya

Berdasarkan semua jawaban

// 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);
    }
};

Pemakaian

elem.find('> a');
disfated
sumber
Date.now()tidak didukung oleh browser lama, dan dapat diganti dengannew Date().getTime()
Christophe
6

Jika Anda ingin menemukan turunan langsung (dan bukan misalnya > div > span), Anda dapat mencoba Element.matches () :

const elem = document.body
const elems = [...elem.children].filter(e => e.matches('b'))

console.log(elems)
<a>1</a>
<b>2</b>
<b>3</b>
<b>4</b>
<s>5</s>

tuomassalo
sumber
5

Jika Anda mengetahui nama tag dari elemen yang Anda cari, Anda dapat menggunakannya di selektor untuk mendapatkan apa yang Anda inginkan.

Misalnya jika Anda memiliki a <select>yang memiliki <option>s dan <optgroups>, dan Anda hanya menginginkan <option>s yang merupakan anak langsungnya, bukan yang di dalamnya <optgoups>:

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

Jadi, dengan memiliki referensi ke elemen select, Anda bisa - secara mengejutkan - mendapatkan turunan langsungnya seperti ini:

selectElement.querySelectorAll('select > option')

Tampaknya berfungsi di Chrome, Safari, dan Firefox, tetapi tidak menguji di IE. = /

Vlad GURDIGA
sumber
8
Peringatan: Jawaban ini sebenarnya tidak membatasi ruang lingkup. Jika pola diulangi tingkat sarang yang lebih dalam, itu masih akan dipilih meskipun tidak mengarahkan anak-anak. <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'); -> Semua 4 elemen li akan dipilih!
Risord
1
Tuhan melarang ada dua <select>elemen dalam induk yang sama.
Tomáš Zato - Kembalikan Monica
4

KLAIM

Secara pribadi saya akan mengambil jawaban dari patrick dw, dan memberi +1 jawabannya, jawaban saya adalah untuk mencari solusi alternatif. Saya tidak berpikir itu layak mendapat suara negatif.

Inilah usaha saya:

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];
    }
}

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

Liangliang Zheng
sumber
1
Beberapa masalah dengan itu. @disfated ingin berfungsi querySelector, yang artinya :eq()tidak dapat digunakan. Bahkan jika bisa, pemilih Anda akan mengembalikan elemen yang ditampilkan :eq()ke halaman, bukan :eq()ke indeks induknya (di mana Anda mendapatkan idx).
pengguna113716
+1 tentang: eq () & querySelector. Dan saya harus menambahkan konteks $ (elem) .parent ().
Liangliang Zheng
Sayangnya itu tidak akan berhasil. Ketika pemilih berjalan dari konteks induk, itu dimulai dengan anak paling kiri dan menemukan semua elemen yang cocok tidak peduli seberapa dalam bersarang, terus berlanjut. Jadi katakanlah elemada di indeks 4, tetapi ada saudara sebelumnya yang memiliki perbedaan tagName, tetapi di dalamnya telah disarangkan elemen dengan pencocokan tagName, elemen bertingkat itu akan disertakan dalam hasil sebelum yang Anda targetkan, sekali lagi membuang Indeks. Berikut ini contohnya
pengguna113716
... bagaimanapun, yang akhirnya Anda lakukan adalah versi kode yang lebih kompleks dalam pertanyaan, yang berfungsi. $('> someselector', elem);; o)
pengguna113716
Ya, tetapi Anda perlu membuat kode keras untuk kenaikan tunggal itu. Itu membutuhkan pengetahuan sebelumnya tentang itu. Jika saya menambahkan elemen serupa lainnya, itu rusak lagi. ; o) jsfiddle.net/Lgaw5/3
pengguna113716
2

Itu berhasil untuk saya:

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);
};

Anda harus meneruskan @this keywork sebelum> ketika Anda ingin mencari anak langsung.

Davi
sumber
Sepertinya sama dengan yang diterima saat ini jawaban yang ... Btw, apa gunanya sintaks "@this" yang aneh? Mengapa tidak menguji ">" dengan regexp saja?
disfated
2

Berikut ini adalah disederhanakan, metode generik untuk menjalankan setiap CSS selector permintaan atas anak-anak hanya langsung - itu juga menyumbang query gabungan, seperti "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"]');
csuwldcat.dll
sumber
2

Ada lib query-relative , yang merupakan pengganti yang sangat berguna untuk query-selector . Ini mem-polyfill selektor anak '> *'dan :scope(termasuk IE8), serta menormalkan :rootselektor. Juga menyediakan beberapa pseudos relatif khusus seperti :closest, :parent, :prev, :next, hanya dalam kasus.

dy_
sumber
0

periksa apakah elemen memiliki id lain tambahkan id acak dan lakukan pencarian berdasarkan itu

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

akan melakukan hal yang sama seperti

$("> someselector",$(elem))
Zeeshan Anjum
sumber