@dch3

Как применить рекурсию для получения данных из DOM?

Хочу с помощью рекурсии сделать код более лаконичным.
Задача - перебрать все элементы формы с помощью рекурсии и запушить их value в результирующий массив.

HTML
<form>
   <div>

      <div>
         <label for="">one</label>
         <input type="text" name="one" value="one">
      </div>

      <div>
          <label for="">two</label>
         <input type="text" name="two" value="two">
      </div>

   </div>

   <div>

      <div>
         <label for="">two</label>
         <select name="select">
            <option value="1">Test1</option>
            <option value="2">Test2</option>
         </select>
      </div>

      <div>
         <label for="">two</label>
         <select name="select">
            <option value="3">Test3</option>
            <option value="4">Test4</option>
         </select>
      </div>

   </div>
</form>

const conteiner = document.querySelector('form').childNodes;
let result = [];

let tableValues = function (nodeList) {
   for (let element of nodeList) {
      if (element.nodeType == 1 && element.nodeName == 'INPUT' || element.nodeName == 'SELECT') {
         result.push(element.value);
      } else {
      tableValues(element);
      }
   }
 }

 tableValues(conteiner);

Вызов этой функции выводит в консоль ошибку:

lists.js?t=1687071771463:359 Uncaught TypeError: nodeList is not iterable

В чем тут моя ошибка?
  • Вопрос задан
  • 145 просмотров
Решения вопроса 2
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
Вы вызываете функцию, передавая ей Element, а она ожидает NodeList.
tableValues(element);
Но тут рекурсия вообще не нужна.
const result = [...document.querySelectorAll('form input, form select')]
  .map((el) => el.value);
Ответ написан
0xD34F
@0xD34F Куратор тега JavaScript
Главный косяк:

tableValues(element);

Как вы собираетесь перебирать элемент? Это абсурд. Должно быть element.children/element.childNodes.

Косяки помельче:

document.querySelector('form').childNodes

Поскольку вам нужны только input'ы и select'ы, то не надо перебирать заведомо лишнее - используйте children вместо childNodes. Ну и погуглите, в чём между ними разница.

if (element.nodeType == 1 && element.nodeName == 'INPUT' || element.nodeName == 'SELECT') {

Проверка значения nodeType лишняя. Кроме того, вам не помешает разобраться с приоритетом выполнения операторов - nodeType вы тут проверяете только для input'а.

let result = [];

Почему эта штука объявляется вне функции, а наполняется внутри? Что, перед каждым вызовом функции будете вручную обнулять результат? Надо создавать массив со значениями внутри функции, и возвращать его как результат её выполнения.

Ну и конечно всё это делается несколько короче:

const getValues = el =>
  el instanceof Element
    ? [ 'INPUT', 'SELECT' ].includes(el.tagName)
      ? [ el.value ]
      : [...el.children].flatMap(getValues)
    : [];

// или

const getValues = el =>
  [ 'INPUT', 'SELECT' ].includes(el?.tagName)
    ? [ el.value ]
    : [].flatMap.call(el.children ?? [], getValues);



const values = getValues(document.querySelector('form'));

Или ещё короче - если без рекурсии:

const values = Array.from(document.querySelector('form').elements, n => n.value);

А вообще, можно и в более общем виде задачу решить - если сделать проверку узла и получение данных из него параметрами функции:

const getFromDOMNodes = (node, test, getVal) =>
  node instanceof Node
    ? Array.prototype.reduce.call(
        node.childNodes,
        (acc, n) => (acc.push(...getFromDOMNodes(n, test, getVal)), acc),
        test(node) ? [ getVal(node) ] : []
      )
    : [];


const values = getFromDOMNodes(
  document.querySelector('form'),
  node => [ 'INPUT', 'SELECT' ].includes(node.nodeName),
  node => node.value
);
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы