querySelector ищет непосредственных потомков


102

У меня есть функция, похожая на jquery:

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

Вопрос в том, как я могу сделать то же самое с querySelector()?

Проблема в том, что >селектор querySelector()требует явного указания родителя. Есть ли обходной путь?


Я добавил ответ, который может сработать для вас лучше, дайте мне знать;)
csuwldcat

Ответы:


127

Хотя это не полный ответ, вам следует следить за W3C Selector API v.2, который уже доступен в большинстве браузеров, как настольных, так и мобильных, за исключением IE (Edge, похоже, поддерживает): см. Полный список поддержки .

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

13
Это правильный ответ. Однако поддержка браузера ограничена, и вам понадобится прокладка, если вы хотите ее использовать. Для этого я построил scopedQuerySelectorShim .
lazd


7
Собственно, :scopeэто все еще указано на drafts.csswg.org/selectors-4/#the-scope-pseudo . Пока что удален только <style scoped>атрибут; неясно, приведет ли это к :scopeудалению (поскольку это <style scoped>был важный вариант использования :scope).
Джон Меллор

1
Какой сюрприз: только Edge не поддерживает это
Xenos

33

Вы не можете. Нет селектора, который имитировал бы вашу отправную точку.

Способ, которым jQuery делает это (в большей степени из-за qsaповедения, которое им не нравится), заключается в том, что они проверяют, elemесть ли идентификатор, а если нет, они временно добавляют идентификатор, а затем создают полную строку селектора.

В основном вы бы сделали:

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

Это, конечно, не код jQuery, но, насколько я помню, это в основном то, что они делают. Не только в этой ситуации, но и в любой ситуации, когда вы запускаете селектор из контекста вложенного элемента.

Исходный код для Sizzle


1
Правильно на момент написания, но см. Ответ @avetisk для получения обновленного метода.
lazd

25

Завершено: полифил прицела

Как упоминал avetisk , Selectors API 2 использует псевдоселектор. Чтобы это работало во всех браузерах (которые поддерживают ), вот полифилл:scope
querySelector

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

Применение

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

По историческим причинам мое предыдущее решение

На основе всех ответов

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

Применение

elem.find('> a');

Date.now()не поддерживается старыми браузерами и может быть заменен наnew Date().getTime()
Christophe

6

Если вы хотите в конечном итоге найти прямых потомков (а не например > div > span), вы можете попробовать 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>


5

ЗАПРОС

Лично я бы взял ответ Патрика Ду и +1 его ответ, мой ответ - поиск альтернативного решения. Я не думаю, что он заслуживает отрицательного голоса.

Вот моя попытка:

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

см. http://jsfiddle.net/Lgaw5/8/


1
Пара проблем с этим. @disfated хочет, чтобы он работал querySelector, а это значит, что :eq()его нельзя использовать. Даже если бы это было возможно, ваш селектор вернул бы элемент :eq()в соответствии с его внешним видом на странице, а не :eq()в индекс его родительского элемента (откуда вы и получаете idx).
user113716

+1 о: eq () и querySelector. И я должен добавить контекст $ (elem) .parent ().
Liangliang Zheng

К сожалению, это тоже не сработает. Когда селектор запускается из контекста родителя, он начинается с самого левого дочернего элемента и находит все совпадающие элементы, независимо от того, насколько глубоко они вложены, продолжается. Скажем, elemиндекс находится под индексом 4, но есть предыдущий брат, который имеет другой tagName, но в него вложен элемент с сопоставлением tagName, этот вложенный элемент будет включен в результаты до того, на который вы нацеливаетесь, снова отбрасывая индекс. Вот пример
user113716

... в любом случае, то, что вы в конечном итоге делаете, - это более сложная версия кода в вопросе, которая действительно работает. $('> someselector', elem);; o)
user113716

Да, но вам нужно было жестко запрограммировать это единичное приращение. Это потребует предварительного знания об этом. Если я добавлю еще один подобный элемент, он снова сломается. ; o) jsfiddle.net/Lgaw5/3
user113716

5

Если вам известно имя тега элемента, который вы изучаете, вы можете использовать его в селекторе для достижения желаемого.

Например, если у вас есть a <select>, у которого есть <option>s и <optgroups>, и вам нужны только <option>s, которые являются его непосредственными дочерними элементами, а не те, которые находятся внутри <optgoups>:

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

Итак, имея ссылку на элемент select, вы можете - что удивительно - получить его непосредственных дочерних элементов следующим образом:

selectElement.querySelectorAll('select > option')

Кажется, он работает в Chrome, Safari и Firefox, но не тестировался в IE. знак равно


9
Предупреждение: этот ответ на самом деле не ограничивает объем. Если шаблон повторяется на более глубоком уровне вложенности, он все равно будет выбран, даже если это не прямые дочерние элементы. <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'); -> Все 4 элемента li будут выбраны!
Risord 07

1
Не дай бог было два <select>элемента в одном родителе.
Tomáš Zato - Reinstate Monica

2

Ниже приводится упрощенный общий метод выполнения любого запроса селектора CSS только для прямых дочерних элементов - он также учитывает комбинированные запросы, например "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"]');

2

Там в запросе-относительный Lib, что очень удобно для замены запроса-селектор . Он полифицирует дочерний селектор '> *'и :scope(включая IE8), а также нормализует :rootселектор. Кроме того, она обеспечивает некоторый специальный относительный pseudos как :closest, :parent, :prev, :next, на всяком случае.


1

Это сработало для меня:

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

вам нужно будет передать @this keywork перед>, если вы хотите найти непосредственных дочерних элементов.


Похоже на текущий принятый ответ ... Кстати, в чем смысл странного синтаксиса "@this"? Почему бы просто не проверить ">" с помощью регулярного выражения?
дискредитирован 01

0

проверьте, есть ли у элемента идентификатор, еще добавьте случайный идентификатор и выполните поиск на его основе

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

будет делать то же самое, что и

$("> someselector",$(elem))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.