Рекурсивная функция для извлечения файлов из вложенного объекта, подобного JSON

У меня есть данные, которые состоят из имени каталога и его файлов. Учитывая конкретное имя каталога в аргументе, скажем, мне нужно найти все файлы, которые он заключает. мои данные :

var fileData = {
      dir : 'app',
      files : [
        'index.html',
        {
          dir : 'js',
          files: [
            'main.js',
            'app.js',
            'misc.js',
            {
              dir : 'vendor',
              files : [
                'jquery.js',
                'underscore.js'
              ]
            }
          ]
        },
        {
          dir : 'css',
          files : [
            'reset.css',
            'main.css'
          ]
        }
      ]
    };

У меня есть listfiles функции, где я передаю имя каталога

listFiles(fileData, 'js')

Ожидаемый результат:

['main.js','app.js','misc.js','jquery.js','underscore.js']

В моем коде я могу получить ответ в консоли, но не могу вернуть значение.

listFiles = (data, dirName) => {
          var filesArray = [];
          matchedDir = (data,dirName) => {
            data['files'].map(el => {
                if (typeof(el) === 'object'){
                  return matchedDir(el,dirName)
                }
                else{filesArray.push(el)}
              })
              console.info(filesArray)
            }
          if (Object.values(data).includes(dirName)) {
            return matchedDir(data,dirName)
          }
          else{
            data['files'].map(el => {
              if (typeof(el) === 'object'){
                return this.listFiles(el,dirName)
              }
            })
          }
        return filesArray
}

Что произойдет, если будет найдено более одного совпадающего имени каталога?

CertainPerformance 31.05.2019 05:54
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
1
539
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Создайте массив результатов при начальном вызове функции и передайте этот массив при каждом рекурсивном вызове:

const getAllFilesFromDirectory = ({ dir, files }, dirToFind, result = [], parentMatch = false) => {
  const addItemsThisDirectory = parentMatch || dir === dirToFind;
  files.forEach((fileOrDir) => {
    if (typeof fileOrDir === 'string') {
      if (addItemsThisDirectory) {
        result.push(fileOrDir);
      }
    } else {
      getAllFilesFromDirectory(fileOrDir, dirToFind, result, addItemsThisDirectory);
    }
  });
  return result;
};

var fileData = {
  dir: 'app',
  files: [
    'index.html',
    {
      dir: 'js',
      files: [
        'main.js',
        'app.js',
        'misc.js',
        {
          dir: 'vendor',
          files: [
            'jquery.js',
            'underscore.js'
          ]
        }
      ]
    },
    {
      dir: 'css',
      files: [
        'reset.css',
        'main.css'
      ]
    }
  ]
};
console.info(getAllFilesFromDirectory(fileData, 'js'));

Я думаю, что второй параметр - это имя каталога, а не расширение файла.

adiga 31.05.2019 05:52

Вот разложенный подход к проблеме с использованием функциональных методов.

Сначала реализуйте общую files функцию -

const emptyTree = 
  { dir: "", files: [] }

const files = (tree = emptyTree, path = "") =>
  Object(tree) === tree
    ? tree.files.flatMap(f => files(f, `${path}/${tree.dir}`))
    : [ `${path}/${tree}` ]

files(fileData)
// [ "/app/index.html"
// , "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// , "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Затем реализуйте search как функцию более высокого порядка, например Array.prototype.filter -

const identity = x =>
  x

const search = (test = identity, tree = emptyTree) =>
  files(tree).filter(test)

Наконец, мы можем использовать search интуитивно понятным способом -

search(f => f.endsWith(".js"), fileData)
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

search(f => f.startsWith("/app/css"), fileData)
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Разделение сложной проблемы на отдельные части упрощает написание, тестирование и сопровождение каждой функции. Дополнительным преимуществом является то, что files и search предлагают еще больше функциональных возможностей, чем ваша исходная функция, и их легче повторно использовать в других областях вашей программы. Надеюсь, это продемонстрирует, как функции более высокого порядка дают вам повышенную гибкость — и при этом с меньшим количеством кода.

Запустите полную программу в своем браузере, развернув приведенный ниже фрагмент —

const fileData = 
  { dir: 'app', files: [ 'index.html', { dir: 'js', files: [ 'main.js','app.js','misc.js', { dir: 'vendor', files: [ 'jquery.js','underscore.js' ] } ] }, { dir: 'css', files: [ 'reset.css','main.css' ] } ] }

const emptyTree = 
  { dir: "", files: [] }

const files = (tree = emptyTree, path = "") =>
  Object(tree) === tree
    ? tree.files.flatMap(f => files(f, `${path}/${tree.dir}`))
    : [ `${path}/${tree}` ]

console.info(files(fileData))
// [ "/app/index.html"
// , "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// , "/app/css/reset.css"
// , "/app/css/main.css"
// ]

const identity = x =>
  x

const search = (test = identity, tree = emptyTree) =>
  files(tree).filter(test)


console.info(search(f => f.endsWith(".js"), fileData))
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

console.info(search(f => f.startsWith("/app/css"), fileData))
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

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

const files = function* (tree = emptyTree, path = "")
{ if (Object(tree) === tree)
    for (const f of tree.files)  
      yield* files(f, `${path}/${tree.dir}`)
  else
    yield `${path}/${tree}`
}

const search = function* (test = identity, tree = emptyTree)
{ for (const f of files(tree))
    if (test(f))
      yield f
}

Теперь files и search возвращают ленивый результат, где результаты могут обрабатываться один за другим по мере их поступления из генератора. Или мы можем собрать все результаты, используя Array.from. Результат тот же -

Array.from(search(f => f.endsWith(".js"), fileData))
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

Array.from(search(f => f.startsWith("/app/css"), fileData))
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Поскольку генераторы могут приостанавливаться и возобновляться, эта программа имеет дополнительное преимущество, состоящее в том, что вычисления могут останавливаться досрочно. Напротив, первая программа, использующая Array.prototype.flatMap и Array.prototype.filter, всегда будет перебирать весь tree.

Разверните приведенный ниже фрагмент, чтобы проверить результаты в своем браузере.

const fileData = 
  { dir: 'app', files: [ 'index.html', { dir: 'js', files: [ 'main.js','app.js','misc.js', { dir: 'vendor', files: [ 'jquery.js','underscore.js' ] } ] }, { dir: 'css', files: [ 'reset.css','main.css' ] } ] }

const emptyTree = 
  { dir: "", files: [] }


const files = function* (tree = emptyTree, path = "")
{ if (Object(tree) === tree)
    for (const f of tree.files)  
      yield* files(f, `${path}/${tree.dir}`)
  else
    yield `${path}/${tree}`
}

console.info(Array.from(files(fileData)))
// [ "/app/index.html"
// , "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// , "/app/css/reset.css"
// , "/app/css/main.css"
// ]

const identity = x =>
  x

const search = function* (test = identity, tree = emptyTree)
{ for (const f of files(tree))
    if (test(f))
      yield f
}

console.info(Array.from(search(f => f.endsWith(".js"), fileData)))
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

console.info(Array.from(search(f => f.startsWith("/app/css"), fileData)))
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Другие вопросы по теме