递归二字可谓相当传神。“递”表示递进,嵌套。“归”表示返回。递归调用一定要有一个临界点用于判断返回。
举个例子,获得如下数据结构,值不为对象的路径数组(顺序无关)。
{
a: {
b: {
c: {
d: {
e: 1,
f: '2',
},
g: true,
},
},
},
};
//返回[ 'a/b/c/d/e', 'a/b/c/d/f', 'a/b/c/g' ]
写成递归,大抵如下:
function getFilePaths(obj) {
const filePaths = [];
function checkFolder(folder, keyPath) {
Object.keys(folder).forEach((key) => {
const value = folder[key];
if (Object.prototype.toString.call(value) === '[object Object]') {
checkFolder(value, [...keyPath, key]);
} else {
filePaths.push([...keyPath, key].join('/'));
}
});
}
checkFolder(obj, []);
return filePaths;
}
我们知道函数调用形成帧,帧放在栈里。递归是一种特殊的、重复的函数调用。借用调用栈的思想,所有的递归都可以改成循环的形式。
function getFilePaths(folder) {
const stack = [];
const filePaths = [];
stack.push({
folder,
keyPath: [],
});
while (stack.length > 0) {
const frame = stack.pop();
const { folder, keyPath } = frame;
Object.keys(folder).forEach((key) => {
const value = folder[key];
if (Object.prototype.toString.call(value) === '[object Object]') {
stack.push({
folder: value,
keyPath: [...keyPath, key],
});
} else {
filePaths.push([...keyPath, key].join('/'));
}
});
}
return filePaths;
}
必须要说的是,把代码写成直觉上的第一反应。也就是说递归绝大多数要写成递归的形式,除非有call stack overflow问题。 不要过早把本应写成递归的,改写成循环。