Sử dụng querySelectorAll để truy xuất các con trực tiếp


119

Tôi có thể làm điều này:

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

Đó là, tôi có thể truy xuất thành công tất cả các myDivphần tử con trực tiếp của phần tử có lớp .foo.

Vấn đề là, tôi thấy phiền rằng tôi phải đưa #myDivvào bộ chọn, vì tôi đang chạy truy vấn trên myDivphần tử (vì vậy rõ ràng là thừa).

Tôi lẽ ra có thể bỏ qua #myDiv, nhưng sau đó bộ chọn không phải là cú pháp hợp pháp vì nó bắt đầu bằng a >.

Có ai biết cách viết một bộ chọn chỉ lấy phần tử con trực tiếp của phần tử mà bộ chọn đang chạy không?



Không có câu trả lời nào đạt được những gì bạn cần? Vui lòng cung cấp phản hồi hoặc chọn một câu trả lời.
Randy Hall

@mattsh - Tôi đã thêm câu trả lời (IMO) tốt hơn cho nhu cầu của bạn, hãy kiểm tra!
csuwldcat,

Câu trả lời:


85

Câu hỏi hay. Vào thời điểm nó được yêu cầu, một cách được triển khai trên toàn cầu để thực hiện "truy vấn gốc tổ hợp" (như John Resig đã gọi chúng là ) không tồn tại.

Bây giờ lớp giả : scope đã được giới thiệu. Nó không được hỗ trợ trên các phiên bản [pre-Chrominum] của Edge hoặc IE, nhưng đã được Safari hỗ trợ trong một vài năm. Sử dụng điều đó, mã của bạn có thể trở thành:

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

Lưu ý rằng trong một số trường hợp, bạn cũng có thể bỏ qua .querySelectorAllvà sử dụng các tính năng DOM API kiểu cũ tốt khác. Ví dụ, thay vì myDiv.querySelectorAll(":scope > *")bạn có thể chỉ viết myDiv.childrenchẳng hạn.

Mặt khác, nếu bạn chưa thể dựa vào :scope, tôi không thể nghĩ ra cách khác để xử lý tình huống của bạn mà không thêm logic bộ lọc tùy chỉnh hơn (ví dụ: tìm myDiv.getElementsByClassName("foo")của ai .parentNode === myDiv) và rõ ràng là không lý tưởng nếu bạn đang cố gắng hỗ trợ một đường dẫn mã thực sự chỉ muốn lấy một chuỗi bộ chọn tùy ý làm đầu vào và danh sách các kết quả phù hợp làm đầu ra! Nhưng nếu giống như tôi, bạn đã hỏi câu hỏi này đơn giản vì bạn mắc kẹt khi nghĩ rằng "tất cả những gì bạn có là một cái búa", đừng quên rằng có rất nhiều công cụ khác mà DOM cũng cung cấp.


3
myDiv.getElementsByClassName("foo")không giống như myDiv.querySelectorAll("> .foo"), nó giống hơn myDiv.querySelectorAll(".foo")(thực sự hoạt động theo cách này) ở chỗ nó tìm thấy tất cả các hậu duệ .foochứ không chỉ có trẻ em.
BoltClock

Oh phiền! Ý tôi là: bạn hoàn toàn đúng. Tôi đã cập nhật câu trả lời của mình để làm rõ hơn rằng nó không phải là một câu trả lời rất tốt trong trường hợp của OP.
natevw

cảm ơn vì liên kết (dường như trả lời nó một cách dứt khoát) và cảm ơn vì đã thêm thuật ngữ "truy vấn gốc tổ hợp".
mattsh

1
Cái mà John Resig gọi là "truy vấn bắt nguồn từ tổ hợp", Bộ chọn 4 giờ đây gọi chúng là bộ chọn tương đối .
BoltClock

@Andrew Scoped stylesheet (tức là <style scoped>) không liên quan gì đến :scopebộ chọn giả được mô tả trong câu trả lời này.
natevw

124

Có ai biết cách viết một bộ chọn chỉ lấy phần tử con trực tiếp của phần tử mà bộ chọn đang chạy không?

Cách chính xác để ghi một bộ chọn được "root" vào phần tử hiện tại là sử dụng :scope.

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

Tuy nhiên, hỗ trợ trình duyệt bị hạn chế và bạn sẽ cần miếng đệm nếu muốn sử dụng. Tôi đã xây dựng scopedQuerySelectorShim cho mục đích này.


3
Điều đáng nói là :scopethông số kỹ thuật hiện đang là "Bản thảo đang làm việc" và do đó có thể thay đổi. Có khả năng nó sẽ vẫn hoạt động như vậy nếu / khi nó được thông qua, nhưng hơi sớm để nói rằng đây là "cách chính xác" để làm điều đó theo ý kiến ​​của tôi.
Zach Lysobey

2
Trông giống như nó đã bị phản đối trong khi chờ đợi
Adrian Moisa

1
3 năm sau và bạn cứu được cơ mông của tôi!
Mehrad

5
@Adrian Moisa:: scope đã không được chấp nhận ở bất kỳ đâu. Bạn có thể làm cho nó bị trộn lẫn với thuộc tính phạm vi.
BoltClock

6

Đây là một phương pháp linh hoạt, được viết bằng vanilla JS, cho phép bạn chạy một truy vấn bộ chọn CSS chỉ trên các phần tử con trực tiếp của một phần tử:

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

Tôi khuyên bạn nên thêm một số loại dấu thời gian hoặc bộ đếm vào id bạn tạo. Có một xác suất khác 0 (mặc dù rất nhỏ) Math.random().toString(36).substr(2, 10)để tạo ra cùng một mã thông báo nhiều lần.
Frédéric Hamidi

Tôi không chắc khả năng điều đó xảy ra với tỷ lệ băm lặp lại là rất nhỏ, ID chỉ tạm thời được áp dụng cho một phần mili giây và bất kỳ bản dupe nào cũng cần phải là con của cùng một nút cha - về cơ bản, cơ hội là thiên văn. Bất kể, thêm một bộ đếm có vẻ tốt.
csuwldcat 29-07-13

Không chắc bạn muốn nói gì any dupe would need to also be a child of the same parent node, idcác thuộc tính là toàn bộ tài liệu. Bạn nói đúng tỷ lệ cược vẫn còn khá đáng kể, nhưng cảm ơn bạn đã dành thời đường cao và thêm rằng truy cập :)
Frédéric Hamidi

8
JavaScript không được thực thi song song, vì vậy cơ hội trên thực tế là không.
WGH

1
@WGH Chà, tôi không thể tin được, tôi thực sự choáng váng vì tôi sẽ đánh rắm não vì một điểm đơn giản như vậy (tôi làm việc ở Mozilla và thường xuyên kể lại sự thật này, phải ngủ nhiều hơn! LOL)
csuwldcat

5

nếu bạn biết chắc chắn phần tử là duy nhất (chẳng hạn như trường hợp của bạn với ID):

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

Để có giải pháp "toàn cầu" hơn: (sử dụng miếng đệm đầu dò que diêm )

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

elmphần tử mẹ của bạn ở đâu và sellà bộ chọn của bạn. Hoàn toàn có thể được sử dụng như một nguyên mẫu.


@lazd đó không phải là một phần của câu hỏi.
Randy Hall

Câu trả lời của bạn không phải là giải pháp "toàn cầu". Nó có những hạn chế cụ thể cần được lưu ý, do đó tôi nhận xét.
lazd

@lazd mà trả lời câu hỏi được hỏi. Vậy tại sao lại bỏ phiếu? Hay bạn chỉ tình cờ nhận xét trong vòng vài giây sau khi bỏ phiếu cho câu trả lời cũ?
Randy Hall

Giải pháp sử dụng matchesSelectorchưa sửa lỗi (thậm chí không hoạt động trong phiên bản Chrome mới nhất), nó gây ô nhiễm không gian tên chung (ret không được khai báo), nó không trả về NodeList như querySelectorMethods được mong đợi. Tôi không nghĩ rằng đó là một giải pháp tốt, do đó, người ủng hộ.
lazd

@lazd - Câu hỏi ban đầu sử dụng chức năng chưa được định sẵn và không gian tên chung. Đó là một giải pháp hoàn toàn tốt cho câu hỏi được đưa ra. Nếu bạn không nghĩ rằng đó là một giải pháp tốt, đừng sử dụng nó hoặc đề xuất một bản chỉnh sửa. Nếu nó không chính xác về mặt chức năng hoặc spam, thì hãy xem xét một phản đối.
Randy Hall

2

Giải pháp sau đây khác với những giải pháp được đề xuất cho đến nay và phù hợp với tôi.

Cơ sở lý luận là bạn chọn tất cả các con phù hợp trước, sau đó lọc ra những con không phải là con trực tiếp. Một đứa trẻ là con trực hệ nếu nó không có cha mẹ phù hợp với cùng một bộ chọn.

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!


Tôi thích điều này vì sự ngắn gọn của nó và sự đơn giản của cách tiếp cận, mặc dù nó rất có thể sẽ không hoạt động tốt nếu được gọi ở tần số cao với những cây lớn và bộ chọn quá tham lam.
brennanyoung 13/1218

1

Mình đã tạo một chức năng để xử lý tình huống này, nghĩ mình sẽ chia sẻ.

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

Về bản chất, những gì bạn đang làm là tạo một chuỗi ngẫu nhiên (hàm randomString ở đây là một mô-đun npm được nhập, nhưng bạn có thể tự tạo.) Sau đó sử dụng chuỗi ngẫu nhiên đó để đảm bảo rằng bạn nhận được phần tử mà bạn mong đợi trong bộ chọn. Sau đó, bạn có thể tự do sử dụng> sau đó.

Lý do tôi không sử dụng thuộc tính id là thuộc tính id có thể đã được sử dụng và tôi không muốn ghi đè nó.


0

Chúng ta có thể dễ dàng nhận được tất cả các phần tử con trực tiếp của một phần tử bằng cách sử dụng childNodesvà chúng ta có thể chọn tổ tiên với một lớp cụ thể querySelectorAll, vì vậy không khó để tưởng tượng chúng ta có thể tạo một hàm mới có được cả hai và so sánh hai.

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

Lưu ý: Điều này sẽ trả về một Mảng nút, không phải là Danh sách Node.

Sử dụng

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

0

Tôi muốn nói thêm rằng bạn có thể mở rộng khả năng tương thích của : phạm vi bằng cách chỉ cần gán một thuộc tính tạm thời đến nút hiện hành.

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

-1

Tôi đã đi cùng

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;

-2

Tôi chỉ đang làm điều này mà không cần thử nó. Điều này sẽ hoạt động?

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

Hãy thử, có thể nó hoạt động có thể không. Xin lỗi, nhưng tôi không sử dụng máy tính để thử nó (phản hồi từ iPhone của tôi).


3
QSA nằm trong phạm vi phần tử mà nó đang được gọi, vì vậy điều này sẽ tìm kiếm một phần tử khác bên trong myDiv có cùng ID với myDiv, sau đó là con của nó với .foo. Bạn có thể làm một cái gì đó như `document.querySelectorAll ('#' + myDiv.id + '> .foo');
có lẽ lên
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.