У меня есть данные, которые состоят из имени каталога и его файлов. Учитывая конкретное имя каталога в аргументе, скажем, мне нужно найти все файлы, которые он заключает. мои данные :
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
}
Создайте массив результатов при начальном вызове функции и передайте этот массив при каждом рекурсивном вызове:
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'));
Я думаю, что второй параметр - это имя каталога, а не расширение файла.
Вот разложенный подход к проблеме с использованием функциональных методов.
Сначала реализуйте общую 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"
// ]
Что произойдет, если будет найдено более одного совпадающего имени каталога?