Використання querySelectorAll для отримання прямих дітей


119

Я вмію це робити:

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

Тобто я можу успішно отримати всіх прямих дітей myDivелемента, які мають клас .foo.

Проблема полягає в тому, що мене турбує те, що я повинен включати #myDivв селектор, тому що я виконую запит на myDivелементі (тому він, очевидно, зайвий).

Я повинен мати змогу залишити #myDivвимкнено, але тоді селектор не є юридичним синтаксисом, оскільки він починається з а >.

Хтось знає, як написати селектор, який отримує лише прямих дітей елемента, на якому працює селектор?



Чи жодна з відповідей не виконала те, що вам потрібно? Надайте відгук або виберіть відповідь.
Ранді Холл

@mattsh - Я додав кращу відповідь (ІМО) для ваших потреб, перевірте це!
csuwldcat

Відповіді:


85

Гарне питання. На той момент, коли його запитували, не існував універсально реалізований спосіб робити "запити з укоріненими комбінаторами" (як їх називав Джон Ресіг ).

Тепер : введено псевдоклас сфери . Він не підтримується у [до-Chrominum] версіях Edge або IE, але він підтримується 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

О, набридь! Під чим я маю на увазі: ви абсолютно праві. Я оновив свою відповідь, щоб зробити більш зрозумілим, що це не дуже добре у випадку з ОП.
natevw

спасибі за посилання (яке, здається, відповідає на нього остаточно), і дякуємо за додавання термінології "запити комбінатора вкорінені".
маттш

1
Те, що Джон Резіг називає "корінтерні запитами", селектори 4 тепер називають їх відносними селекторами .
BoltClock

@Andrew Обсяг таблиць стилів (тобто <style scoped>) не має нічого спільного з псевдоселектором, :scopeописаним у цій відповіді.
natevw

124

Хтось знає, як написати селектор, який отримує лише прямих дітей елемента, на якому працює селектор?

Правильний спосіб записати селектор, який "укорінений" до поточного елемента, - це використовувати :scope.

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

Однак підтримка веб-переглядача обмежена, і вам потрібно буде шим, якщо ви хочете ним скористатися. Для цього я створив scopedQuerySelectorShim .


3
Варто зазначити, що :scopeспецифікація наразі є "Робочим проектом" і тому може бути змінена. Ймовірно, він все ще працюватиме так, якщо / коли він буде прийнятий, але трохи рано сказати, що це "правильний спосіб" зробити це на мій погляд.
Зах Лісобей

2
Схоже, його тим часом застаріли
Адріан Моїза

1
3 роки потому, і ти врятуєш мій глютеновий максимум!
Мехрад

5
@Adrian Moisa:: область ніде не застаріла. Можливо, ви змішаєте його з атрибутом "масштаб".
BoltClock

6

Ось гнучкий метод, написаний у ванільній 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)створювати один і той же маркер не один раз.
Фредерік Хаміді

Я не впевнений, наскільки ймовірність того, що шанси повторних хешів є мізерними, ідентифікатор застосовується лише тимчасово на частку мілісекунди, і будь-яка дупа повинна також бути дитиною того ж батьківського вузла - в основному, шанси астрономічні. Незалежно від того, додавання лічильника здається прекрасним.
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 і часто переказую цей факт, треба більше поспати! LOL)
csuwldcat

5

якщо ви точно знаєте, елемент унікальний (наприклад, ваш випадок із ідентифікатором):

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

Для більш "глобального" рішення: (використовуйте matchSelector shim )

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, це не є питанням.
Ранді Холл

Ваша відповідь не є "глобальним" рішенням. Він має конкретні обмеження, які слід зазначити, звідси мій коментар.
lazd

@lazd, який відповідає на поставлене запитання. То чому ж голосування вниз? Або вам просто траплялось коментувати протягом декількох секунд після того, як голосування було прихильне до відповіді на рік?
Ранді Холл

У рішенні використовується matchesSelectorнефіксований (який не працює навіть в останній версії Chrome), він забруднює глобальну область імен (ret не було оголошено), не повертає NodeList, як очікується querySelectorMethods. Я не думаю, що це вдале рішення, звідси і голосування.
lazd

@lazd - в оригінальному запитанні використовується нефіксована функція та глобальний простір імен. Це абсолютно чудове рішення для даного питання. Якщо ви не вважаєте, що це гарне рішення, не використовуйте його або не пропонуйте його редагувати. Якщо це функціонально неправильно або спам, то розгляньте зворотну скаргу.
Ранді Холл

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

Примітка. Це поверне масив вузлів, а не NodeList.

Використання

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

0

Хочеться додати, що ви можете розширити сумісність : obseg , просто призначивши тимчасовий атрибут тимчасового атрибуту.

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

Спробуйте, може, це може, можливо, і ні. Вибачте, але я зараз не на комп’ютері, щоб спробувати це (відповідаючи зі свого iPhone).


3
QSA визначається елементом, на який він викликається, тому він шукатиме інший елемент всередині myDiv з тим самим ідентифікатором, що і myDiv, а потім його діти з .foo. Ви можете зробити щось на кшталт `document.querySelectorAll ('#' + myDiv.id + '> .foo');
ймовірно,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.