Проблемы с реализацией getByClassnameHierarchy

#javascript #dom

#javascript #dom

Вопрос:

Я пытаюсь реализовать getByClassnameHierarchy, но у меня возникают проблемы с реализацией. Должен ли я использовать BFS вместо DFS для обхода узлов?

Может ли кто-нибудь объяснить мне как методы, так и плюсы и минусы их использования?

     // Helper function to make output easier to read
const getIds = (elements=[]) => Array.from(elements).map(x => x.id);
    
    /**
     * Return all DOM elements who are _leaf_nodes_ that satisfy the hierarchy. 
     * Hierarchy is a string of class names separated by `>`, akin to 
     * CSS CHILD SELECTORS.
     * 
     * ex. getByClassnameHierarchy(#root, 'a>b>c') -> [<div class="c" id="c-1"></div>,<div class="c" id="c-2"></div> ]
     * "give me all the elements with class name 'c', who have a strict parent with 
     * class name 'b', who have a strict parent with class name 'a'"
     * 
     * @param root DOMElement: start the search from this DOM element
     * @param hierarchy string: `>`-delimited string of classnames
     * @return Array<DOMElement>: all DOM elements that satisfy the target hierarchy
     */
    function getByClassnameHierarchy(root, hierarchy) {
      // parentNode
      const res = [];
      const level = hierarchy.split('>');
      
      helper(root, level, 0);
      return res;
      
      function helper(root, level, cur) {
        
        if(!root) return
        
        if(root.classList amp;amp; root.classList.contains(level[cur-1])){ // b
          
           if(root.parentNode.classList.contains(level[cur-2])) { // a
              if(root.classList.contains(level[cur-1])) {
                 res.push(root);
                }
            }
           
        } //c
      
        root.childNodes.forEach(child => {
          helper(child, level, cur   1);
        });
      
      }
    }

    
    const root2 = document.getElementById('root2');

    // // basic case:
    console.log('actual: ', getIds(getByClassnameHierarchy(root2, 'a>b>c')));
    console.log(`a>b>c expected:` , `['c-1', 'c-2']`, 'n');  
     <div id="root2">
      <div class="a" id="a-1">
        <div class="b" id="b-1">
          <div class="c" id="c-1"></div>
          <div class="c" id="c-2"></div>
        </div>
      </div>
    </div>  

Проблема:

Ожидаемый возврат: [ 'b-1', 'c-1', 'c-2' ] вместо ['c-1', 'c-2']

Не уверен, где я ошибаюсь.

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

1. Пожалуйста, отредактируйте свой образец, чтобы сделать его работоспособным (с добавлением некоторого количества HTML).

2. @Jeto Готово:D Я добавил код.

3. Я имею в виду, что вы могли бы просто сделать getByClassnameHierarchy(root, hierarchy) { return root.querySelectorAll("." hierarchy.replace(/>/g,'>.'));} и покончить с этим…

4. @NiettheDarkAbsol весь смысл того, что я делаю это, заключался бы в его реализации.. Я не хочу использовать querySelectorAll. Меня спрашивали об этом в интервью. Спасибо за ответ.

Ответ №1:

правка 2020 года (благодаря комментарию @techguy2000):

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

 const getIds = (elements = []) => Array.from(elements).map(x => x.id);

function getByClassnameHierarchy(element, hierarchy, level = 0) {
  const result = [];
  
  const classNames = hierarchy.split('>');
  const currentClassName = classNames[0];
  
  const isClassFound = element.classList amp;amp; element.classList.contains(currentClassName);
  const isLastClass = classNames.length === 1;
  
  if (isClassFound) {
    if (isLastClass) {
      result.push(element);
    } else {
      element.childNodes.forEach(child => {
        result.push(...getByClassnameHierarchy(child, classNames.slice(1).join('>'), level   1));
      });
    }
  } 
  
  if (level === 0) {
    element.childNodes.forEach(child => {
      result.push(...getByClassnameHierarchy(child, hierarchy, level));
    });
  }
  
  return resu<
}

// Test

const root2 = document.getElementById('root2');

console.log('a>b>c actual:', getIds(getByClassnameHierarchy(root2, 'a>b>c')));
console.log('a>b>c expected:', ['c-1', 'c-2', 'c-3', 'c-4']);

console.log('b>c actual: ', getIds(getByClassnameHierarchy(root2, 'b>c')));
console.log('b>c expected:', ['c-1', 'c-2', 'c-3', 'c-4']);

console.log('b actual: ', getIds(getByClassnameHierarchy(root2, 'b')));
console.log('b expected:', ['b-1', 'b-2']);

console.log('a>c actual: ', getIds(getByClassnameHierarchy(root2, 'a>c')));
console.log('a>c expected:', []);  
 <div class="a" id="root2">
  <div class="a" id="a-1">
    <div class="b" id="b-1">
      <div class="c" id="c-1"></div>
      <div class="c" id="c-2"></div>
    </div>
  </div>
  <div class="b" id="b-2">
    <div class="c" id="c-3"></div>
    <div class="c" id="c-4"></div>
  </div>
</div>  

Первоначальный ответ:

Вы могли бы сделать что-то вроде этого (подробности см. в комментариях):

 const getIds = (elements = []) => Array.from(elements).map(x => x.id);

function getByClassnameHierarchy(root, hierarchy, level = 0) {
  let result = [];
  
  // Grab the class names
  const classNames = hierarchy.split('>');
  
  // Retrieve the current (first) one
  const currentClassName = classNames[0];
  
  // For each child
  root.childNodes.forEach(child => {
    // If it contains the given class
    if (child.classList amp;amp; child.classList.contains(currentClassName)) {
      // Append the result of the following selector on this child, 
      // or the child itself if we're at the last part of the selector
      result = result.concat(classNames.length > 1 
        ? getByClassnameHierarchy(child, classNames.slice(1).join('>'), level   1)
        : child);
    }
    // Otherwise, if we're still on the first part of the selector, 
    // append the result of the same selector on this child
    else if (level === 0) {
      result = result.concat(getByClassnameHierarchy(child, hierarchy, level));
    }
  });
  
  return resu<
}

// Test

const root2 = document.getElementById('root2');

console.log('a>b>c actual:', getIds(getByClassnameHierarchy(root2, 'a>b>c')));
console.log('a>b>c expected:', ['c-1', 'c-2']);

console.log('b>c actual: ', getIds(getByClassnameHierarchy(root2, 'b>c')));
console.log('b>c expected:', ['c-1', 'c-2', 'c-3', 'c-4']);

console.log('b actual: ', getIds(getByClassnameHierarchy(root2, 'b')));
console.log('b expected:', ['b-1', 'b-2']);

console.log('a>c actual: ', getIds(getByClassnameHierarchy(root2, 'a>c')));
console.log('a>c expected:', []);  
 <div id="root2">
  <div class="a" id="a-1">
    <div class="b" id="b-1">
      <div class="c" id="c-1"></div>
      <div class="c" id="c-2"></div>
    </div>
  </div>
  <div class="b" id="b-2">
    <div class="c" id="c-3"></div>
    <div class="c" id="c-4"></div>
  </div>
</div>  

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

1. Всегда пожалуйста. Я только что добавил еще несколько примеров ниже функции, дайте мне знать, если это сработает для вас.

2. Итак, мы выполняем DFS .. Можем ли мы сделать то же самое путем обхода порядка уровней? (путем поддержания очереди?) Просто любопытно, каковы были бы «ЗА» и «Против» при таком подходе!

3. @TechnoCorner Я немного незнаком с этими понятиями (я просто погуглил термины, потому что я их не запомнил). Я уверен, что существуют разные подходы к этому, но этот должен быть простым и достаточно производительным. В статье, которую я только что прочитал , говорится, что «если наша проблема заключается в поиске чего-то, что, скорее всего, ближе к root, мы бы предпочли BFS. И если целевой узел находится близко к листу, мы бы предпочли DFS.». Так что я предполагаю, что DFS здесь подойдет, поскольку, скорее всего, он довольно глубоко внедрен в DOM (по крайней мере, может).

4. Спасибо, я дочитаю статью до конца. Кроме того, похоже, что этот тест, похоже, не выполняется: // пробелы в селекторе не должны ничего возвращать: console.log(‘actual: ‘, getIds(getByClassnameHierarchy(root2, ‘a> c’))); console.log( a>c expected: , [] , ‘ n’);

5. При использовании a>c он возвращает [ 'c-1', 'c-2' ] , но фактическое значение равно []