递归:就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。
递归的两个必要因素:
递归方程,递归结束条件
递归算法的核心:
求 1-100的和
分析:计算 1 + 2 + … + 99 + 100 的和,我们可以看成 1-n 的和
假设 n 为 3,即 和为 3 + (3-1) + ((3-1) -1),多观察不难发现递推关系 n + (n-1)的递归体
找到临界条件 1 的时候,和为1
function sum(n) {
if (n === 1) return 1
return sum(n - 1) + n
}
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, … 求第 n 项
分析:第1项和第2项都为1;第3项为第2项+第1项的和;第4项为第3项+第2项的和,可以看出递推关系:第n项为第n-1项+第n-2项的和
临界条件是:第1项和第2项都为1
function febo(n) {
if ([1, 2].includes(n)) return 1
return febo(n - 1) + febo(n - 2)
}
js 递归案例:假设楼梯有 n 个台阶,每次可走1个或2个台阶,问走完这 n 个台阶有几种走法
分析:假设n为1,只有1种走法,n为2,只有2种走法,n为3有3种走法,n为4有5种走法,n为5有8种走法,…得出递归关系
临界条件就是n 为1和2时
function climb(n) {
if (n === 1) return 1
if (n === 2) return 2
return climb(n - 1) + climb(n - 2)
}
克隆一个对象返回一个对象
function clone(obj) {
let temp = {}
for (let i in obj) {
if (typeof obj[i] === 'object') {
temp[i] = clone(obj[i])
} else {
temp[i] = obj[i]
}
}
return temp
}
计算 n 的阶乘
分析:临界条件当n为1时,值为1
假设n为2,即21,n为3,即 32*1,得出递归体:n * fn(n-1)
function fn(n) {
if (n === 1) return 1
return n * fn(n - 1)
}
测试数据
let data = [
{
name: "所有物品",
children: [
{
name: "水果",
children: [{name: "苹果", children: [{name: '青苹果'}, {name: '红苹果'}]}]
},
{
name: '主食',
children: [
{name: "米饭", children: [{name: '北方米饭'}, {name: '南方米饭'}]}
]
},
{
name: '生活用品',
children: [
{name: "电脑类", children: [{name: '联想电脑'}, {name: '苹果电脑'}]},
{name: "工具类", children: [{name: "锄头"}, {name: "锤子"}]},
{name: "生活用品", children: [{name: "洗发水"}, {name: "沐浴露"}]}
]
}
]
}
]
功能要求:拿到每一级的最后一个数据的name值,即
['青苹果', '红苹果', '北方米饭', '南方米饭', '联想电脑', '苹果电脑', '锄头','锤子', '洗发水', '沐浴露']
递归实现:
function fn (data, arr = []) {
data.forEach(item => {
if (item.children && item.children.length > 0) {
fn(item.children, arr)
} else {
arr.push(item.name)
}
})
return arr
}
需求:需要依次输出1, 2, 3, 4, 5,每个输出中间间隔1s
function fn(i) {
console.log(i)
setTimeout(() => {
if (i > 4) return
i++
return fn(i)
}, 1000)
}
fn(1)
树结构数据
const data = [
{
id: 1,
name: '根目录1',
parent_id: 0,
children: [
{
id: 2,
name: '子目录1-1',
parent_id: 1
},
{
id: 3,
name: '子目录1-2',
parent_id: 1
},
]
},
{
id: 4,
name: '根目录2',
parent_id: 0,
children: [
{
id: 5,
name: '子目录2-1',
parent_id: 8
},
{
id: 6,
name: '子目录2-2',
parent_id: 4
},
]
},
{
id: 7,
name: '根目录3',
parent_id: 0,
},
{
id: 8,
name: '根目录4',
parent_id: 0,
children: [
{
id: 9,
name: '子目录4-1',
parent_id: 8,
children: [
{
id: 10,
name: '子目录4-1-1',
parent_id: 9
},
]
},
]
},
]
递归实现
function recursive (data, parent_id = 0, arr = []) {
if (data?.length) {
for (let i = 0; i < data.length; i++) {
arr.push({
id: data[i].id,
name: data[i].name,
sort: i,
parent_id: parent_id
})
if (data[i].children?.length) {
recursive(data[i].children, data[i].id, arr)
}
}
}
return arr
}
resolveTreeData(treeData) {
const tree = treeData.map((item) => {
item.flag= true;
if (Object.prototype.hasOwnProperty.call(item, "children")) {
item.children && this.resolveTreeData(item.children);
}
return item;
})
return tree;
}
// data:树结构,id:当前节点的id,parent:父节点,第一次传空
findParentNode(data, id, parent) {
for (let item of data) {
if (item.id === id) {
return parent || item;
}
if (item?.children?.length) {
let node = this.findParentNode(item.children, id, item);
if (node) {
return node;
}
}
}
return null;
},
// 获取当前选中的菜单的rs_code和name
function getMenuInfo() {
const frame = window.location.hash.substring(1); // 页面路径
const menuList = [.....];
console.log(menuList)
if (menuList?.length) {
let obj = {
menu_code: "",
menu_name: "",
}
const resCall = (data) => {
return data.find((item) => {
if (item.frame === frame) {
obj.menu_code = item.rs_code;
obj.menu_name = item.name;
}
if (item.tabs?.length) {
resCall(item.tabs);
}
})
}
resCall(menuList);
return obj;
}
}
resolveMenu(menu) {
return menu.map((item) => {
let tabs = item.sub_list;
if (tabs?.length > 0) {
tabs = this.resolveMenu(tabs);
}
return Object.assign({}, item, {
name: item.rs_cn_name,
frame: item.rs_url,
sub_list: null,
tabs,
});
});
},
// 它接受两个参数:一个一维数组arr和一个树形结构数据treeData。其中,arr表示树结构的id列表,treeData包含整个树形结构的数据。
// 函数的主要逻辑是遍历arr中的每一个元素,并根据其对应的节点判断子节点是否全部在参数列表中。如果子节点不全在参数列表中,则将当前父节点从参数列表中删除,并继续检查下一个节点。最后返回更新后的参数列表。
function removeIncompleteParents(arr, treeData) {
const idSet = new Set(arr);
let index = 0;
while (index < arr.length) {
const node = findNodeById(treeData, arr[index]);
if (node && node.children && node.children.length > 0) {
// 判断子节点是否全部在参数列表中
const childrenIds = node.children.map(child => child.id);
const allChildrenIncluded = childrenIds.every(id => idSet.has(id));
if (!allChildrenIncluded) {
// 如果子节点不全在参数列表中,将当前父节点从参数列表中删除
idSet.delete(node.id);
arr.splice(index, 1);
continue; // 继续检查下一个节点
}
}
index++;
}
return Array.from(idSet);
}
// 为了方便查找节点,定义d的辅助函数。它接受两个参数:一个树形结构数据data和一个节点的id值id,返回对应的节点对象。函数使用递归的方式进行查找,首先比较当前节点的id值是否与目标值相等,如果相等就返回该节点对象;否则,如果当前节点有子节点,则继续递归查找子节点。如果找不到匹配的节点,则返回null。
function findNodeById(data, id) {
for (let i = 0; i < data.length; i++) {
if (data[i].id === id) {
return data[i];
} else if (data[i].children && data[i].children.length > 0) {
const node = findNodeById(data[i].children, id);
if (node) {
return node;
}
}
}
return null;
}
const data = [
{
id: '1',
name: 'Node 1',
children: [
{
id: '2',
name: 'Node 1-1',
children: [
{ id: '3', name: 'Node 1-1-1' },
{ id: '4', name: 'Node 1-1-2' },
],
},
{
id: '5',
name: 'Node 1-2',
children: [
{ id: '6', name: 'Node 1-2-1' },
{ id: '7', name: 'Node 1-2-2' },
],
},
],
},
];
const arr = ['1', '2', '4', '7'];
const updatedArr = removeIncompleteParents(arr, data);
console.log(updatedArr) // ['4', '7']
function renameTreeFields(tree, fieldMap) {
for (let oldFieldName in fieldMap) {
if (fieldMap.hasOwnProperty(oldFieldName)) {
const newFieldName = fieldMap[oldFieldName];
if (tree !== null && tree.hasOwnProperty(oldFieldName)) {
Object.defineProperty(tree, newFieldName,
Object.getOwnPropertyDescriptor(tree, oldFieldName));
delete tree[oldFieldName];
}
if (tree !== null && typeof tree === 'object') {
for (let key in tree) {
if (tree.hasOwnProperty(key)) {
renameTreeFields(tree[key], fieldMap);
}
}
}
}
}
}
// 示例
const tree = {
id: 1,
name: 'root',
children: [
{
id: 2,
name: 'child1'
},
{
id: 3,
name: 'child2',
children: [
{
id: 4,
name: 'grandchild1'
}
]
}
]
};
renameTreeFields(tree, {name: 'label', id: 'key'});
console.log(tree);
/* 输出结果:
{
key: 1,
label: 'root',
children: [
{
key: 2,
label: 'child1'
},
{
key: 3,
label: 'child2',
children: [
{
key: 4,
label: 'grandchild1'
}
]
}
]
}
*/
树结构如下
const arr = [
{ "text": "首页", "link": "/", "collapse": true },
{
"text": "Vue教程",
"link": "/vue",
"collapse": true,
"type": "folder",
"children": [
{ "text": "Vue3基础入门", "link": "/vue/vue3", "collapse": true, "type": "file" }
]
},
{ "text": "vite配置", "link": "/vite/config", "collapse": true, "type": "file" },
{
"text": "站点设置",
"link": "/site",
"collapse": true,
"children": [
{
"text": "基本信息设置",
"link": "11",
"collapse": true,
"children": [
{ "text": "如何切换站点主题?", "link": "/site/change" },
{ "text": "如何切换站点主题?", "link": "/site/exchalenge" },
]
},
{
"text": "主题样式设置",
"link": "55"
}
]
}
]
link
值的对象解析:
// 方法1
const handerData = (arr, link) => {
for(let key of arr) {
if (key.link === link){
return key;
}
if (key?.children?.length) {
const result = handerData(key.children, link)
if (result) {
return result
}
// 下面的写法是错误的,相当于return return xx
// return handerData(key.children, link)
}
}
return null
}
console.log(handerData(arr, "22"))
// 方法2,存在可能变量污染的问题
let res
const handerData = (arr, link) => {
arr.map((i) => {
if (i?.link === link) {
res = i;
}
if (i?.children?.length) {
handerData(i.children, link)
}
})
}
handerData(arr, "22")
console.log(res)
// 方法3
const handerData = (arr, link) => {
let res
const findLink = (arr, link) => {
arr.map((i) => {
if (i?.link === link) {
res = i;
}
if (i?.children?.length) {
findLink(i.children, link)
}
})
}
findLink(arr, link)
return res
}
console.log(handerData(arr, "22"))
function findItemByLink(data, link) {
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.link === link) {
return [item];
} else if (item.children) {
const result = findItemByLink(item.children, link);
if (result) {
return [item, ...result];
}
}
}
return null;
}
console.log(findItemByLink(arr, "/site/change"))
// 输出结果
[
{
text: '站点设置',
link: '/site',
collapse: true,
children: [ [Object], [Object] ]
},
{
text: '基本信息设置',
link: '11',
collapse: true,
children: [ [Object], [Object] ]
},
{ text: '如何切换站点主题?', link: '/site/change' }
]
// 给树的每个节点添加属性
resolveTreeData(treeData, layer) {
const tree = treeData.map((item) => {
if (!item.item_weight) {
item.item_weight = "";
}
item.layer = layer;
if (Object.prototype.hasOwnProperty.call(item, "children")) {
item.children && this.resolveTreeData(item.children, layer + 1);
}
return item;
});
return tree;
},
let returnData = this.resolveTreeData(data, 1);