Есть ли простой способ выполнить вычитание между двумя наборами узлов в jQuery?

#javascript #jquery

#javascript #jquery ( jquery )

Вопрос:

Пожалуйста, обратите внимание: этот вопрос очень похож на ряд других вопросов, которые я нашел здесь, но он не дублируется, поэтому, пожалуйста, внимательно прочитайте:

По сути, я хочу исключить узел и все его потомки из коллекции по (скажем) имени класса.

Расширение, которое я сделал (проверьте фрагмент), идеально работает в данном примере, однако я готов найти более простой / элегантный способ сделать это (если он существует в jquery в настоящее время).

Я ищу общий способ выполнения $(this).find('<some_selector>').exclude_found_nodes_that_have_the_following_between_aforementioned_this_and_itself('<exclusion_selector>') , однако что-то вроде $(this).find('<exclusion_selector>:ignore_branch_if_this_node_met() <some_selector>') тоже справилось бы с этой задачей.

Идентификаторы на самом деле не присутствуют в коде, они добавляются исключительно в демонстрационных целях («начальный узел» выбирается пользователем).

 $.fn.find_exclude = function(original_selector,exclude_selector) {
  var counter=$(this).parents(exclude_selector).length;
  if ($(this).is(exclude_selector)){
    counter  ;
  }
  var tmp=$(this).find(original_selector); 
  return tmp.filter(function(){
      if($(this).is(exclude_selector) || $(this).parents(exclude_selector).length>counter){
        return false;
    }
      return true
    });
};

$(function(){
  var all_b_in_main_not_in_groups=$("#main").find_exclude('b','.group');
  var all_b_in_inner_not_in_groups=$("#inner").find_exclude('b','.group');
  
  all_b_in_main_not_in_groups.each(function(){
    $(this).css('color','red');
  });
  all_b_in_inner_not_in_groups.each(function(){
    $(this).css('opacity','0.5');
  });
  
  
  
}) 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<span id="main">
<b>I should be red</b>
<div class="group" id="inner">
  <span>
    <b>I should be opaque but not red</b>
  </span>
</div>
<div>
  <b>I should be red</b>
  <div class="group">
     <b>Don't touch me, please.</b>
        <div>
          <b>Don't touch me, please.</b>
            <p>
              <b>Don't touch me, please.</b>
            </p>
        </div>
  </div>
</div>
</span> 

ПРЕДУПРЕЖДЕНИЕ: прежде чем предлагать просто использовать :not или .not() или даже какое-либо использование .clone() , пожалуйста, перечитайте вопрос и приведенный пример еще раз и проверьте, действительно ли ваше решение работает — предлагаемое решение должно работать с обоими #main и #inner в качестве начальных узлов (без фактического знания селектора начального узла — они известны как $(this) click прослушиватель событий внутри)

Комментарии:

1. Не уверен, что вы подразумеваете под «не по мере необходимости».? Чего вы пытаетесь достичь, .not() чего не удается достичь?

2. Читайте как «это не соответствует моим потребностям». Это предложение было включено, чтобы поощрить попытки достичь аналогичного результата с :not() помощью или .not() , прежде чем предлагать его использовать.

3. Какой актуальный вопрос?

4. «Есть ли простой способ выполнить вычитание между двумя наборами узлов в jQuery?»

5. Отредактировал вопрос, пытаясь сделать его более понятным / менее запутанным.

Ответ №1:

 function customFind (findWhere, findWhat, ignoreWhat){
    var totalIgnore = $(findWhere).find(ignoreWhat   ' '   findWhat);
    return $(findWhere).find(findWhat).not(totalIgnore);
}
 

Используйте:

 var zzz= customFind(this,"b",".group");
zzz.css('text-decoration','underline');
 

Комментарии:

1. Вы используете идентификаторы. Пожалуйста, перечитайте (я отредактировал вопрос некоторое время назад) «Идентификаторы на самом деле не присутствуют в коде, они добавлены исключительно для демонстрационных целей («начальный узел» выбирается пользователем)».

2. PS: Извините за не очень хорошо приведенный пример с такими ограничениями — фактический код / HTML слишком сложен, поэтому я попытался упростить, не теряя основной идеи. Плюс, что, если я захочу, $(this).find('input, textarea,button') — мне нужно будет включать их not() каждый раз, когда мне нужно что-то еще? Я просил общее решение (вычитание наборов).

3. Извините, я не пытаюсь быть придирчивым, но основная проблема в том, что я не могу знать containerSelector , прежде чем начать> («начальный узел» выбирается пользователем). Так что единственное, что у меня есть, — это $(this) для запуска узла.

4. @haldagan как насчет сейчас?

5. Еще раз извините за сбивчиво сформулированный вопрос в начале. Я ставлю вам , но ответ тринко на самом деле более точен. Как я уже упоминал ранее, мне нужно было общее решение. В вашем случае что-то вроде findWhat="input,textarea,button" просто не будет работать (как .find(ignoreWhat ' ' findWhat) станет .find('.something input,textarea,button') ), что вернет то же, .find('.something input') что и plus .find('textarea') plus .find('button') , вместо желаемого .find('.something input, .something textarea, .something button')

Ответ №2:

Вы могли бы использовать следующую версию плагина. Он использует тот факт, что .not() может принимать другой выбор jQuery:

 $.fn.find_exclude = function(original_selector, exclude_selector) {
    return $(this).find(original_selector).not(exclude_selector)
            .not($(this).find(exclude_selector).find(original_selector));
}

$(function(){
  var all_b_in_main_not_in_groups = $("#main").find_exclude('b','.group');
  all_b_in_main_not_in_groups.css('color', 'red');

  var all_b_in_inner_not_in_groups = $("#inner").find_exclude('b','.group');
  all_b_in_inner_not_in_groups.css('opacity', '0.5');
}); 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id="main">
    <b>I should be red</b>
    <div class="group" id="inner">
      <span>
        <b>I should be opaque but not red</b>
      </span>
    </div>
    <div>
      <b>I should be red</b>
      <div class="group">
         <b>Don't touch me, please.</b>
            <div>
              <b>Don't touch me, please.</b>
                <p>
                  <b>Don't touch me, please.</b>
                </p>
            </div>
      </div>
    </div>
</span> 

Последнее .not() является основной частью: оно исключает узлы, которые являются потомками исключаемых родителей. В случае, если узел удовлетворяет как исходному, так и селектору исключения, он не будет исключен этим .not() , и поэтому .not() для обработки этих случаев применяется другой (первый).

Не связано с вашим вопросом, но обратите внимание, что вам не нужны .each() циклы для применения .css() вызова метода. Вы можете применить его непосредственно к выделенному jQuery.

Альтернатива: не искать исключенные поддеревья

В комментариях вы спросили, можно ли выполнить поиск, для которого не нужно сначала сопоставлять все узлы с помощью исходного селектора, чтобы затем удалить те, которые должны быть исключены в соответствии с селектором исключения.

Это можно сделать, выполнив поиск узла с помощью рекурсивного вызова функции (поиск в глубину), опуская этот рекурсивный поиск при достижении узла, который должен быть исключен:

 $.fn.find_exclude = function(original_selector, exclude_selector) {
    if ($(this).length == 0) return $();
    var $elems = $(this).children().not(exclude_selector);
    return $elems.filter(original_selector)
                 .add($elems.find_exclude(original_selector, exclude_selector));
}
 

Однако обратите внимание, что DOM API относительно быстро находит узлы с помощью селекторов (ср. .querySelectorAll используется jQuery), поэтому выполнение этого «самостоятельно» с помощью этой альтернативы сопряжено с некоторыми накладными расходами, что в конечном итоге может потребовать больше времени, чем при использовании первого метода. Это зависит от того, насколько велики поддеревья ниже исключаемых узлов. Чем они больше, тем более вероятно, что альтернативный рекурсивный метод работает лучше. Но я думаю, что в «нормальных» ситуациях первое решение будет быстрее.

Комментарии:

1. Мне понравился ваш ответ — я не совсем уверен, как, но это работает. Я проведу небольшое расследование, чтобы проверить, не слишком ли много обходов, от которых я пытался избавиться в первую очередь. Вы, кажется, достаточно квалифицированы, поэтому, пожалуйста, ответьте: вы уверены, что в моем случае нет реального способа предотвратить включение чего-либо в коллекцию, вместо того, чтобы вычитать его позже? Я имею в виду без переписывания / изменения оригинала jquery .find() .

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

3. Спасибо вам за ответ. > вам не нужны .each() циклы для применения .css() … Я знаю это, часть вопроса, в которой говорится, что .css() на самом деле это не то, для чего мне нужны эти узлы, теряется при редактировании вопроса.