en.javascript.info/archive/3-jquery-stub/3-jquery-traversal/article.md
2015-03-12 10:26:02 +03:00

18 KiB
Raw Blame History

Навигация по jQuery-коллекции

Мы умеем перебирать коллекцию, получать нужный элемент по номеру. Достаточно ли этого?

Конечно нет! По коллекции нужно путешествовать. Получать детей, искать родителей, выбирать элементы по условию, и чтобы это было удобно. [cut]

Шпаргалка по методам

Методов для навигации по коллекции много. Их гораздо проще воспринять, если разбить на группы.

Итак, на картинке ниже:

  • По центру -- действия с текущей коллекцией: фильтрация, добавление, изменение и т.п.
  • Сверху -- методы для доступа к родителям.
  • Снизу -- для поиска среди потомков.
  • Слева/справа -- для поиска соседей.

[pre]

  parent parents parentsUntil
closest
 
prev prevAll prevUntil
siblings
filter not has
eq first last slice
is add map
next nextAll nextUntil
siblings
  find
children contents
 
[/pre]

Так как методов много, то запомнить их все сложно, да и не нужно. Достаточно знать, какие есть, а при необходимости -- обратиться к этой странице или к официальной документации.

У этих методов очень хорошие названия, они станут очевидны, как только мы их рассмотрим чуть подробнее.

По текущей коллекции

filter(фильтр), not(фильтр), has(фильтр)
Возвращают отфильтрованную коллекцию, содержащую только элементы...
  • для `filter` -- подходящие под фильтр,
  • для `not` -- не подходящие под фильтр,
  • для `has` -- содержащие потомка, подходящего под фильтр.
<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li>...</li>
</ul>

<script>
  var list = $('li');

  // вызовом css('color', ..) поставим цвет элементам:

*!*
  list.filter(':even').css('color', 'blue'); // ФИЛЬТР: чётные 
  list.not(':even').css('color', 'green'); // НЕ чётные
  list.has('em').css('background', 'pink'); // СОДЕРЖАТ em
*/!*
</script>

В качестве фильтра может быть использована также функция, принимающая элемент как this и возвращающая true/false:

<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li>...</li>
</ul>

<script>
  var list = $('li');

  // ФИЛЬТР: первый ребёнок - текстовый узел
*!*
  var result = list.filter(function() {
    return this.firstChild.nodeType == 3;
  });
*/!*

  alert( result.length ); // 1, это последний LI
</script>
eq(n), first, last, slice(start, end)
Возвращает новую коллекцию...
  • `eq(n)` -- с одним элементом по его номеру,
  • `first/last` -- из первого/последнего элемента,
  • `slice(start[, end])` -- из элементов с номера `start` до `end`.
is(фильтр)
Возвращает `true`, если *какой-нибудь* элемент из коллекции подходит под фильтр.
<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li class="other">...</li>
</ul>

<script>
  var list = $('ul').children();

*!*
  alert( list.is('li') ); // true
  alert( list.is('.other') ); // true
  alert( list.is(':hidden') ); // false
*/!*
</script>
add(элементы)
Возвращает новую коллекцию из элементов текущей и новых, по селектору.
<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li class="other">...</li>
</ul>

<script>
  var elems = $('ul');

  // можно добавить элемент DOM
  var li = document.getElementsByTagName('li')[0];
*!*
  elems = elems.add(li);
*/!*
  alert( elems.length ); // 2

*!*
  elems = elems.add('.other'); // можно - по селектору
*/!*
  alert( elems.length ); // 3

*!*
  elems = elems.add($('li')); // можно - коллекцию
*/!*
  alert( elems.length ); // 5, дубликаты удаляются
</script>

Важно: эта функция не меняет текущую коллекцию, она создаёт новую из существующих и добавленных элементов.

map(функция)
Этот метод стоит особняком. Он не меняет коллекцию, не ищет в ней, а пропускает её через функцию и возвращает результаты.
<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li class="other">...</li>
</ul>

<script>
  $('code')
    .css('color', 'red') // подсветить CODE красным
*!*
    .map(function() {
      return this.parentNode; // получить коллекцию родителей CODE
    })
*/!*
    .css('color', 'green'); // подсветить их зелёным
</script>

Заметим две приятные особенности:

  • Большинство методов, которые осуществляют фильтрацию, могут принимать как селектор так и фильтрующую функцию. jQuery любит функции.
  • Большинство методов, которые принимают элементы, могут получать их в виде jQuery-коллекции или селектора. Главное, чтобы найти по этому можно было.

По родителям

[parent()](http://api.jquery.com/parent/), [parents(фильтр)](http://api.jquery.com/parents/), [parentsUntil(стоп, фильтр)](http://api.jquery.com/parentsUntil/)
Родители -- один `parent`, все `parents(фильтр)` (с фильтром по селектору) и до определённого `parentsUntil(где остановиться, селектор для элементов)`.
[closest(фильтр, элемент-контейнер)](http://api.jquery.com/closest/)
Ищет одного, ближайшего, родителя, подходящего под селектор.

Второй, необязательный, аргумент элемент-контейнер, если он передан, ограничивает поиск. jQuery будет идти вверх до тех пор, пока не встретит этот DOM-элемент.

<!--+ run -->
<script src="http://code.jquery.com/jquery.js"></script>

<ul id="1">
  <li><a href="http://blog.jquery.com">jQuery Blog</a></li>
  <li><a href="http://sizzlejs.com">Sizzle</a></li>
</ul>

<script>
  var link = $('a[href*="blog"]'); // ссылка, атрибут href содержит "blog"

*!*
  var ul = link.closest('ul'); // ближайший сверху UL
*/!*
  alert( ul[0].id ); // 1

*!*
  var li = $('li')[0];
  ul = link.closest('ul', li); // ближайший сверху UL, но внутри LI
*/!*
  alert( ul.length ); // 0, нет таких
</script>

По потомкам

[find(селектор)](http://api.jquery.com/find/)
Ищет в потомках по селектору.
// для каждого LI искать CODE среди потомков, вернуть их коллекцию
$('li').find('code');
[children(селектор)](http://api.jquery.com/children/), [contents()](http://api.jquery.com/contents/)
Выбирает детей по селектору, без аргументов -- всех детей:
<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li class="other">...</li>
</ul>

<script>
  $('li')
*!*
    .children('code') // вернуть всех детей LI, подходящих под селектор code  
*/!*
    .css('color', 'red'); // подсветить
</script>

Метод contents() -- также возвращает детей, но в отличие от children -- узлы всех типов, включая текстовые и комментарии, а не только узлы-элементы.

<!--+ jquery run -->
<ul>
  <li><code>filter</code>: <i>только подходящие</i>.</li>
  <li><code>not</code>: <em>не подходящие</em>.</li>
  <li><code>has</code>: <i>по потомкам</i>.</li>
  <li class="other">...</li>
</ul>

<script>
  $('li')
*!*
    .contents() // все узлы-дети LI
*/!*
    .map(function() { // обернуть текстовые узлы в скобки
      if (this.nodeType == 3) this.data = "(" + this.data + ")"
    });
</script>

По соседям

prev(), prevAll(фильтр), prevUntil(элемент, фильтр)
Получить левого соседа `prev`, всех левых соседей `prevAll` или всех левых соседей `prevUntil` до указанного (`элемент`), подходящих под фильтр.
next(), nextAll(фильтр), nextUntil(элемент, фильтр)
То же самое, но правые соседи.
siblings()
Получить коллекцию всех соседей.

Стек селекторов: методы end и addBack

Все методы не влияют на текущую коллекцию, они создают и возвращают новую.

При каждом новом поиске возвращается jQuery-объект с результатом.

Предыдущий jQuery-объект при этом не теряется, к нему всегда можно вернуться вызовом end().

Посмотрим это на примере задачи. Допустим, мы нашли форму $('form') и хотим выбрать все чекбоксы в ней.

Можно сделать это так:

//+ no-beautify
$('form')
  .find(':checkbox')
  .each(function() { ... }); // сделать что-то с элементами коллекции

...И теперь хотим поискать в этой же форме что-то ещё.

Самый очевидный способ это сделать -- сохранить $('form') в переменной:

//+ no-beautify
var form = $('form');

form
  .find(':checkbox')
  .each(function() { ... });

form
  .find(':radio')
  .each(function() { ... });

...Но на самом деле в этом нет необходимости.

jQuery, при вызове find сохраняет предыдущую найденную коллекцию, к которой можно вернуться вызовом end():

//+ no-beautify
$('form')
  .find(':checkbox') // нашли чекбоксы внутри
  .each(function() { ... }) // обработали
*!*
  .end() // вернулись обратно на форму
*/!*
  .find(':radio') // поискали другие элементы внутри..
  .each(function() { ... }); // сделали с ними что-то ещё

Метод addBack(selector) добавляет элементы из предыдущей коллекции к текущей.

Если указать селектор, то он отфильтрует их.

Этот метод бывает очень удобен, когда какую-то операцию нужно сделать как с детьми, так и с самим элементом, например:

//+ no-beautify
$('ul')       // коллекция: UL
  .children() // получить коллекцию LI
  .addBack()  // добавить к ней сам UL
  .each(...)   // теперь у нас коллекция LI и UL-родитель

Полный список методов вы найдёте в разделе Traversing документации. При использовании jQuery, вы часто будете иметь с ними дело и отлично запомните.

Неэффективность jQuery

Для ряда задач jQuery-методы поиска по коллекции неэффективны.

Например, нужно найти первого потомка. Некоторые способы:

elem.children(':first');
elem.children().first();
$(':first-child', elem);

Все эти способы неэффективны. Особенно первые два.

  1. Первый проходит всех детей, по псевдо-фильтру выбирая нужного.
  2. Второй копирует детей в коллекцию, а потом получает из неё первый элемент.
  3. Третий запускает `querySelectorAll(':first-child)` (на самом деле там чуть сложнее, но не суть важно) в контексте `elem`. Здесь, с первого взгляда, нет копирования всех детей, но внутренний алгоритм браузера для выполнения `querySelectorAll` всё равно работает полным перебором. Впрочем, это будет намного быстрее, чем предыдущие решения.

Обычный же вызов elem[0].children[0] на порядки обгонит все вышеприведённые способы, особенно если детей много.

Какой вывод из этого?

Там, где потеря в производительности некритична, используем jQuery -- для удобства. Это большинство случаев. Там, где она важна -- обращаемся к обычному DOM.