ใช้ querySelectorAll เพื่อดึงข้อมูลลูกโดยตรง


119

ฉันสามารถทำได้:

<div id="myDiv">
   <div class="foo"></div>
</div>
myDiv = getElementById("myDiv");
myDiv.querySelectorAll("#myDiv > .foo");

นั่นคือฉันสามารถดึงข้อมูลลูกโดยตรงทั้งหมดของmyDivองค์ประกอบที่มีคลาสได้.fooสำเร็จ

ปัญหาคือมันรบกวนฉันที่ฉันต้องรวม#myDivตัวเลือกไว้ในตัวเลือกเนื่องจากฉันกำลังเรียกใช้การสืบค้นในmyDivองค์ประกอบ (ดังนั้นจึงเห็นได้ชัดว่าซ้ำซ้อน)

ฉันควรจะสามารถออกจากระบบได้#myDivแต่ตัวเลือกนั้นไม่ใช่ไวยากรณ์ทางกฎหมายเนื่องจากเริ่มต้นด้วยไฟล์>.

ไม่มีใครรู้วิธีเขียนตัวเลือกที่ได้รับเฉพาะลูกโดยตรงขององค์ประกอบที่ตัวเลือกทำงานอยู่



ไม่มีคำตอบใดที่บรรลุสิ่งที่คุณต้องการ? โปรดให้ข้อเสนอแนะหรือเลือกคำตอบ
Randy Hall

@mattsh - ฉันได้เพิ่มคำตอบ (IMO) ที่ดีกว่าสำหรับความต้องการของคุณลองดูสิ!
csuwldcat

คำตอบ:


85

คำถามที่ดี. ในขณะที่ถูกถามไม่มีวิธีที่ใช้กันอย่างแพร่หลายในการทำ "combinator rooted queries" (ตามที่John Resig เรียกมันว่า )

ตอนนี้: ขอบเขตหลอกคลาสได้รับการแนะนำ ไม่รองรับ Edge หรือ IE เวอร์ชัน [pre-Chrominum] แต่ Safari ได้รับการสนับสนุนมาสองสามปีแล้ว เมื่อใช้รหัสนั้นรหัสของคุณอาจกลายเป็น:

let myDiv = getElementById("myDiv");
myDiv.querySelectorAll(":scope > .foo");

โปรดทราบว่าในบางกรณีคุณสามารถข้าม.querySelectorAllและใช้คุณลักษณะ DOM API ที่ล้าสมัยอื่น ๆ ได้เช่นกัน ตัวอย่างเช่นแทนที่จะmyDiv.querySelectorAll(":scope > *")เขียนmyDiv.childrenก็ได้เช่น

มิฉะนั้นหากคุณยังพึ่งพาไม่ได้:scopeฉันไม่สามารถคิดวิธีอื่นในการจัดการกับสถานการณ์ของคุณได้โดยไม่ต้องเพิ่มตรรกะตัวกรองที่กำหนดเอง (เช่นค้นหาว่าmyDiv.getElementsByClassName("foo")ใคร.parentNode === myDiv) และแน่นอนว่าไม่เหมาะอย่างยิ่งหากคุณกำลังพยายามสนับสนุนเส้นทางรหัสเดียวที่เป็นจริง เพียงแค่ต้องการใช้สตริงตัวเลือกโดยพลการเป็นอินพุตและรายการการแข่งขันเป็นเอาต์พุต! แต่ถ้าชอบฉันคุณก็ถามคำถามนี้เพียงเพราะคุณคิดว่า "สิ่งที่คุณมีคือค้อน" อย่าลืมว่ามีเครื่องมืออื่น ๆ อีกมากมายที่ DOM นำเสนอเช่นกัน


3
myDiv.getElementsByClassName("foo")ไม่เหมือนกับmyDiv.querySelectorAll("> .foo")มันเหมือนmyDiv.querySelectorAll(".foo")(ซึ่งใช้งานได้จริง) ตรงที่พบว่าผู้สืบเชื้อสายทั้งหมด.fooไม่เหมือนกับเด็ก ๆ
BoltClock

โอ้รำคาญ! โดยที่ฉันหมายถึง: คุณพูดถูก ฉันได้อัปเดตคำตอบของฉันเพื่อให้ชัดเจนยิ่งขึ้นว่ามันไม่ใช่สิ่งที่ดีมากในกรณีของ OP
natevw

ขอบคุณสำหรับลิงค์ (ที่ดูเหมือนจะตอบได้ชัดเจน) และขอบคุณที่เพิ่มคำศัพท์ "combinator rooted queries"
mattsh

1
สิ่งที่จอห์นเรซิกเรียกว่า "คำสั่ง Combinator ที่หยั่งราก" Selectors 4 ตอนนี้เรียกพวกเขาเตอร์ญาติ
BoltClock

@Andrew Scoped สไตล์ชีต (เช่น<style scoped>) ไม่มีส่วนเกี่ยวข้องกับ:scopeตัวเลือกหลอกที่อธิบายไว้ในคำตอบนี้
natevw

124

ไม่มีใครรู้วิธีเขียนตัวเลือกที่ได้รับเฉพาะลูกโดยตรงขององค์ประกอบที่ตัวเลือกทำงานอยู่

วิธีที่ถูกต้องในการเขียนเลือกที่ "ฝังราก" :scopeเพื่อองค์ประกอบปัจจุบันคือการใช้งาน

var myDiv = getElementById("myDiv");
var fooEls = myDiv.querySelectorAll(":scope > .foo");

อย่างไรก็ตาม การรองรับเบราว์เซอร์มีข้อ จำกัดและคุณจะต้องใช้ shim หากต้องการใช้งาน ฉันสร้างscopedQuerySelectorShimเพื่อจุดประสงค์นี้


3
สิ่งที่ควรค่าแก่การกล่าวถึง:scopeคือข้อมูลจำเพาะปัจจุบันเป็น "Working Draft" และอาจมีการเปลี่ยนแปลงได้ เป็นไปได้ว่ามันจะยังคงทำงานเช่นนี้หาก / เมื่อมีการนำมาใช้ แต่เร็วหน่อยที่จะบอกว่านี่เป็น "วิธีที่ถูกต้อง" ในความคิดของฉัน
Zach Lysobey

2
ดูเหมือนว่าจะเลิกใช้แล้วในระหว่างนี้
Adrian Moisa

1
3 ปีต่อมาและคุณช่วยคุณประหยัด gluteus maximus ของฉัน!
Mehrad

5
@Adrian Moisa :: ขอบเขตยังไม่เลิกใช้งานที่ไหน คุณอาจจะผสมกับแอตทริบิวต์ขอบเขต
BoltClock

6

ต่อไปนี้เป็นวิธีการที่ยืดหยุ่นซึ่งเขียนด้วย vanilla JS ซึ่งช่วยให้คุณสามารถรันคิวรีตัวเลือก CSS เฉพาะลูกโดยตรงขององค์ประกอบ:

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

ฉันขอแนะนำให้คุณเพิ่มการประทับเวลาหรือตอบโต้กับรหัสที่คุณสร้างขึ้น มีความน่าจะเป็นที่ไม่ใช่ศูนย์ (แม้ว่าจะมีขนาดเล็ก) ในMath.random().toString(36).substr(2, 10)การสร้างโทเค็นเดียวกันมากกว่าหนึ่งครั้ง
Frédéric Hamidi

ฉันไม่แน่ใจว่าโอกาสที่จะได้รับอัตราต่อรองของแฮชซ้ำนั้นมีค่าน้อยเพียงใด ID จะถูกนำไปใช้ชั่วคราวเพียงเศษเสี้ยวของมิลลิวินาทีเท่านั้นและการหลอกลวงใด ๆ จะต้องเป็นลูกของโหนดแม่เดียวกันด้วยโดยพื้นฐานแล้ว โอกาสทางดาราศาสตร์ ไม่ว่าจะเพิ่มเคาน์เตอร์ก็ดูดี
csuwldcat

ไม่แน่ใจว่าสิ่งที่คุณหมายถึงany dupe would need to also be a child of the same parent node, idแอตทริบิวต์เอกสารกว้าง คุณคิดถูกที่อัตราต่อรองยังค่อนข้างเล็กน้อย แต่ขอขอบคุณที่ใช้ถนนสูงและเพิ่มเคาน์เตอร์นั้น :)
Frédéric Hamidi

8
JavaScript ไม่ได้ทำงานแบบขนานดังนั้นโอกาสที่จะเป็นศูนย์ในความเป็นจริง
WGH

1
@WGH ว้าวฉันไม่อยากจะเชื่อเลยว่าฉันตะลึงจริงๆฉันจะสมองผายลมในประเด็นง่ายๆแบบนี้ (ฉันทำงานที่ Mozilla และอ่านข้อเท็จจริงนี้บ่อยๆต้องนอนหลับให้มากขึ้นฮ่า ๆ )
csuwldcat

5

หากคุณแน่ใจว่าองค์ประกอบนั้นไม่ซ้ำกัน (เช่นกรณีของคุณที่มี ID):

myDiv.parentElement.querySelectorAll("#myDiv > .foo");

สำหรับโซลูชัน "ทั่วโลก" เพิ่มเติม: (ใช้ตัวเลือกmatchSelector )

function getDirectChildren(elm, sel){
    var ret = [], i = 0, l = elm.childNodes.length;
    for (var i; i < l; ++i){
        if (elm.childNodes[i].matchesSelector(sel)){
            ret.push(elm.childNodes[i]);
        }
    }
    return ret;
}

elmองค์ประกอบหลักของคุณอยู่ที่ไหนและselเป็นตัวเลือกของคุณ สามารถใช้เป็นต้นแบบได้โดยสิ้นเชิง


@lazd ที่ไม่ได้เป็นส่วนหนึ่งของคำถาม
Randy Hall

คำตอบของคุณไม่ใช่วิธีแก้ปัญหา "ทั่วโลก" มีข้อ จำกัด เฉพาะที่ควรสังเกตดังนั้นความคิดเห็นของฉัน
lazd

@lazd ซึ่งตอบคำถามที่ถาม แล้วทำไมการโหวตลง? หรือคุณเพิ่งจะแสดงความคิดเห็นภายในไม่กี่วินาทีของการโหวตลงคะแนนในคำตอบของปี?
Randy Hall

โซลูชันนี้ใช้แบบไม่matchesSelectorเติมคำนำหน้า (ซึ่งไม่สามารถใช้งานได้ใน Chrome เวอร์ชันล่าสุด) ทำให้เนมสเปซทั่วโลก (ไม่ได้ประกาศ ret) ไม่ส่งคืน NodeList เช่น querySelectorMethods ฉันไม่คิดว่ามันจะเป็นทางออกที่ดีดังนั้นการโหวตลด
lazd

@lazd - คำถามเดิมใช้ฟังก์ชันที่ไม่มีคำนำหน้าและเนมสเปซส่วนกลาง เป็นวิธีแก้ปัญหาที่ดีอย่างสมบูรณ์สำหรับคำถามที่ให้มา หากคุณไม่คิดว่าเป็นวิธีแก้ปัญหาที่ดีอย่าใช้หรือแนะนำการแก้ไข หากฟังก์ชันไม่ถูกต้องหรือเป็นสแปมให้พิจารณาลดคะแนน
Randy Hall

2

วิธีแก้ปัญหาต่อไปนี้แตกต่างจากที่เสนอจนถึงตอนนี้และใช้ได้กับฉัน

เหตุผลคือคุณต้องเลือกเด็กที่ตรงกันทั้งหมดก่อนจากนั้นกรองเด็กที่ไม่ใช่ลูกโดยตรงออก เด็กเป็นลูกโดยตรงหากไม่มีผู้ปกครองที่ตรงกันกับตัวเลือกเดียวกัน

function queryDirectChildren(parent, selector) {
    const nodes = parent.querySelectorAll(selector);
    const filteredNodes = [].slice.call(nodes).filter(n => 
        n.parentNode.closest(selector) === parent.closest(selector)
    );
    return filteredNodes;
}

HTH!


ฉันชอบสิ่งนี้เพราะความสั้นและความเรียบง่ายของแนวทางแม้ว่ามันจะทำงานได้ไม่ดีหากเรียกด้วยความถี่สูงด้วยต้นไม้ใหญ่และตัวเลือกที่โลภมากเกินไป
brennanyoung

1

ฉันสร้างฟังก์ชันเพื่อจัดการสถานการณ์นี้คิดว่าจะแบ่งปัน

getDirectDecendent(elem, selector, all){
    const tempID = randomString(10) //use your randomString function here.
    elem.dataset.tempid = tempID;

    let returnObj;
    if(all)
        returnObj = elem.parentElement.querySelectorAll(`[data-tempid="${tempID}"] > ${selector}`);
    else
        returnObj = elem.parentElement.querySelector(`[data-tempid="${tempID}"] > ${selector}`);

    elem.dataset.tempid = '';
    return returnObj;
}

โดยพื้นฐานแล้วสิ่งที่คุณกำลังทำคือการสร้างสตริงแบบสุ่ม (ฟังก์ชัน randomString ในที่นี้คือโมดูล npm ที่นำเข้า แต่คุณสามารถสร้างขึ้นเองได้) จากนั้นใช้สตริงแบบสุ่มเพื่อรับประกันว่าคุณจะได้รับองค์ประกอบที่คุณคาดหวังในตัวเลือก จากนั้นคุณมีอิสระที่จะใช้>หลังจากนั้น

เหตุผลที่ฉันไม่ใช้แอตทริบิวต์ id คือแอตทริบิวต์ id อาจถูกใช้ไปแล้วและฉันไม่ต้องการแทนที่สิ่งนั้น


0

เราสามารถรับลูกโดยตรงทั้งหมดขององค์ประกอบโดยใช้childNodesและเราสามารถเลือกบรรพบุรุษที่มีคลาสเฉพาะได้ด้วยquerySelectorAllดังนั้นจึงไม่ยากที่จะจินตนาการว่าเราสามารถสร้างฟังก์ชันใหม่ที่ได้รับทั้งสองและเปรียบเทียบทั้งสอง

HTMLElement.prototype.queryDirectChildren = function(selector){
  var direct = [].slice.call(this.directNodes || []); // Cast to Array
  var queried = [].slice.call(this.querySelectorAll(selector) || []); // Cast to Array
  var both = [];
  // I choose to loop through the direct children because it is guaranteed to be smaller
  for(var i=0; i<direct.length; i++){
    if(queried.indexOf(direct[i])){
      both.push(direct[i]);
    }
  }
  return both;
}

หมายเหตุ: สิ่งนี้จะส่งคืน Array of Nodes ไม่ใช่ NodeList

การใช้

 document.getElementById("myDiv").queryDirectChildren(".foo");

0

ฉันต้องการเพิ่มว่าคุณสามารถขยายความเข้ากันได้ของ: ขอบเขตโดยกำหนดแอตทริบิวต์ชั่วคราวให้กับโหนดปัจจุบัน

let node = [...];
let result;

node.setAttribute("foo", "");
result = window.document.querySelectorAll("[foo] > .bar");
// And, of course, you can also use other combinators.
result = window.document.querySelectorAll("[foo] + .bar");
result = window.document.querySelectorAll("[foo] ~ .bar");
node.removeAttribute("foo");


-2

ฉันแค่ทำสิ่งนี้โดยไม่ได้ลองเลย จะได้ผลไหม

myDiv = getElementById("myDiv");
myDiv.querySelectorAll(this.id + " > .foo");

ลองดูสิบางทีมันอาจจะใช้งานไม่ได้ Apolovies แต่ตอนนี้ฉันไม่ได้ใช้คอมพิวเตอร์เพื่อลองใช้งาน (ตอบสนองจาก iPhone ของฉัน)


3
QSA ถูกกำหนดขอบเขตไว้ที่องค์ประกอบที่ถูกเรียกใช้ดังนั้นสิ่งนี้จะมองหาองค์ประกอบอื่นภายใน myDiv ที่มี ID เดียวกับ myDiv จากนั้นก็ย่อยด้วย. foo คุณสามารถทำบางสิ่งเช่น document.querySelectorAll ('#' + myDiv.id + '> .foo');
อาจขึ้น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.