Как получить всех предков элемента в DOM-дереве с помощью JavaScript

Стало интересно как получить всех возможных предков элемента (и их общее количество) с заданным id?

Например, так мы можем найти всех потомков элемента:

let item = document.getElementById('menu');
for (let i in item.children){
    console.log(`${ item.children[i] }`);
}
<html>
<body>
  <h3>Переменные и типы</h3>
  <p>Тип переменной определяется типом заносимого в нее значения.</p>
  <div id="menu">
    <p class="bold">JavaScript поддерживает следующие типы данных:</p>
    <ul>
      <li>число;</li>
      <li>строка;</li>
      <li>массив;</li><li>объект.</li>
    </ul>
    <div>Допускается взаимное преобразование типов.</div>
  </div>
</body>
<div id="k"></div>]
</html>

А вот как получить предков вопрос)

Я попробовал так:

let item = document.getElementById('menu');

if(`${item.parentElement}` !== null) {      
    for(let i in item.parentElement) {
        console.log(`${item.parentElement[i]}`);
    }
}
<body>
  <h3>Переменные и типы</h3>
  <p>Тип переменной определяется типом заносимого в нее значения.</p>
  <div id="menu">
    <p class="bold">JavaScript поддерживает следующие типы данных:</p>
    <ul>
      <li>число;</li>
      <li>строка;</li>
      <li>массив;</li><li>объект.</li>
    </ul>
    <div>Допускается взаимное преобразование типов.</div>
  </div>
</body>
<div id="k"></div>

Но выводит вообще все, что не надо. Я думаю, что ошибка в цикле)


Ответы (3 шт):

Автор решения: Evgenii Evstafev

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

let item = document.getElementById('menu');
let ancestors = [];

if (item) {
  let currentParent = item.parentElement;

  while (currentParent) {
    ancestors.push(currentParent);
    currentParent = currentParent.parentElement;
  }

  console.log("Предки элемента с id='menu':");
  ancestors.forEach((ancestor, index) => {
    let description = ancestor.tagName;
    if (ancestor.id) description += `#${ancestor.id}`;
    if (ancestor.className) description += `.${ancestor.className.split(' ').join('.')}`;
    console.log(`${index + 1}: ${description}`, ancestor);
  });

  console.log(`\nОбщее количество предков: ${ancestors.length}`);

} else {
  console.log("Элемент с id 'menu' не найден.");
}
<!DOCTYPE html>
<html>

<head>
  <title>Пример</title>
</head>

<body>
  <div id="for-test-div-1">
    <h3>Переменные и типы</h3>
    <p>Тип переменной определяется типом заносимого в нее значения.</p>
    <div id="menu">
      <p class="bold">JavaScript поддерживает следующие типы данных:</p>
      <ul>
        <li>число;</li>
        <li>строка;</li>
        <li>массив;</li>
        <li>объект.</li>
      </ul>
      <div>Допускается взаимное преобразование типов.</div>
    </div>
  </div>
  <div id="k"></div>
</body>

</html>

PS: "современные" браузеры автоматически переместят <div id="k"></div> внутрь <body>

→ Ссылка
Автор решения: Vladislav G.

Функция getParents принимает 2 параметра:

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

Функция выполняет рекурсивный обход родителей до тех пор, пока не будет найден такой элемент в цепочке родителей, который соответствует селектору stopSelector. Если стоп-селектор не задан или в процессе обхода не будет найден родитель, соответствующий стоп-селектору, то обход будет производиться вплоть до корневого элемента DOM-дерева.

Функция возвращает объект с 3 свойствами:

  • parents - массив элементов в цепочке родителей, причем в начале массива - ближайший родитель, в конце - самый далекий родитель;
  • selectorFound - булевое значение, которое примет true, если обход был остановлен по условию соответствия стоп-селектору;
  • length - длина цепочки родителей.

PS: я взял вашу разметку и выполнил вызов getParents для элемента li с id=test для наглядности, т.к. он лежит "поглубже".

/**
 * @param {HTMLElement} element 
 * @param {String} stopSelector 
 */
function getParents(element, stopSelector) {
    const parent = element.parentElement;

    if (!parent) return {
        parents: [],
        selectorFound: false,
        length: 0
    }

    if (stopSelector && parent.matches(stopSelector)) return {
        parents: [parent],
        selectorFound: true,
        length: 1
    }

    const parentsOfParent = getParents(parent, stopSelector);
    
    return {
        parents: [parent, ...parentsOfParent.parents],
        selectorFound: parentsOfParent.selectorFound,
        length: 1 + parentsOfParent.length
    }
}

const element = document.querySelector('#test');
const parents = getParents(element, 'body');

console.log(parents);
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h3>Переменные и типы</h3>
    <p>Тип переменной определяется типом заносимого в нее значения.</p>
    <div id="menu">
        <p class="bold">JavaScript поддерживает следующие типы данных:</p>
        <ul>
            <li>число;</li>
            <li id="test">строка;</li>
            <li>массив;</li>
            <li>объект.</li>
        </ul>
        <div>Допускается взаимное преобразование типов.</div>
    </div>
    <script src="script.js"></script>
</body>

</html>

→ Ссылка
Автор решения: Qwertiy

function getParents(el) {
  const res = [];

  while ((el = el.parentElement)) {
    res.push(el);
  }

  return res;
}

document.body.addEventListener('click', e => {
  console.log(getParents(e.target).map(x => x.outerHTML.replace(/>(?=.*\n).*/s, ">")).join("\n"));
})
<h3>Переменные и типы</h3>
<p>Тип переменной определяется типом заносимого в нее значения.</p>
<div id="menu">
  <p class="bold">JavaScript поддерживает следующие типы данных:</p>
  <ul>
    <li>число;</li>
    <li>строка;</li>
    <li>массив;</li>
    <li>объект.</li>
  </ul>
  <div>Допускается взаимное преобразование типов.</div>
</div>

→ Ссылка