JS recursion
Description
当接口返回数据存在层层嵌套的情况,且嵌套层数不固定时。
用多层for循环处理数据会超时,导致页面卡顿,且代码逻辑混乱。
改用递归处理数据则耗时短,代码逻辑清晰。
Scene
接口返回数据如下:
当ChildGroup不是空数组,同时Functions是空数组,则意味着有子级当它是空数组且同级
当ChildGroup是空数组,同时Functions不是空数组,则意味着当前对象是叶子节点
[
{
"ID": "zw",
"Name": "植物",
"ApplicationSystems": [
{
"ApplicationID": "lszw",
"ApplicationName": "陆生植物",
"Functions": [],
"ChildGroup": [
{
"ID": "bzzw",
"Name": "被子植物",
"Functions": [
{
"ID": "xrk",
"Name": "向日葵"
}
],
"ChildGroup": []
}
]
}
]
},
{
"ID": "dw",
"Name": "动物",
"ApplicationSystems": [
{
"ApplicationID": "lsdw",
"ApplicationName": "陆生动物",
"Functions": [],
"ChildGroup": [
{
"ID": "jzdw",
"Name": "脊椎动物",
"Functions": [],
"ChildGroup": [
{
"ID": "brdw",
"Name": "哺乳动物",
"Functions": [
{
"ID": "lh",
"Name": "老虎"
}
],
"ChildGroup": []
}
]
}
]
}
]
}
]
目标是将数据处理成下面这种结构:(即找出所有的叶子结点)
[
{
"ApplicationName": "陆生植物",
"Functions": [
{
"ID": "xrk",
"Name": "向日葵"
}
]
},
{
"ApplicationName": "陆生动物",
"Functions": [
{
"ID": "lh",
"Name": "老虎"
}
]
}
]
Solution
NodeJS模拟后端:
router.get('/applications', (req, res) => {
const list = [
{
"ID": "zw",
"Name": "植物",
"ApplicationSystems": [
{
"ApplicationID": "lszw",
"ApplicationName": "陆生植物",
"Functions": [],
"ChildGroup": [
{
"ID": "bzzw",
"Name": "被子植物",
"Functions": [
{
"ID": "xrk",
"Name": "向日葵"
}
],
"ChildGroup": [],
"Layer": 1
}
]
}
]
},
{
"ID": "dw",
"Name": "动物",
"ApplicationSystems": [
{
"ApplicationID": "lsdw",
"ApplicationName": "陆生动物",
"Functions": [],
"ChildGroup": [
{
"ID": "jzdw",
"Name": "脊椎动物",
"Functions": [],
"ChildGroup": [
{
"ID": "brdw",
"Name": "哺乳动物",
"Functions": [
{
"ID": "lh",
"Name": "老虎"
}
],
"ChildGroup": [],
"Layer": 3
}
],
"Layer": 2
}
]
}
]
}
];
res.send(list);
})
前端界面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>递归</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.staticfile.org/vue/2.5.2/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<el-autocomplete popper-class="my-autocomplete" v-model="state" :fetch-suggestions="querySearch"
placeholder="请输入内容" id="mySelect">
<i class="el-icon-circle-close el-input__icon" slot="suffix" @click="handleIconClick">
</i>
<template slot-scope="{ item }">
<div class="ApplicationName">{{ item.ApplicationName }}</div>
<div class="FunctionName" v-for="func in item.Functions"
@click="selectFunc(func, item.ApplicationName)">{{ func.Name }}
</div>
</template>
</el-autocomplete>
</div>
<script>
const app = new Vue({
el: "#app",
data() {
return {
Applications: [],
state: ''
};
},
methods: {
querySearch(queryString, cb) {
let Applications = this.Applications;
let results = queryString ? this.afterFilterList(queryString) : Applications;
// 调用 callback 返回建议列表的数据
cb(results);
},
loadAll() {
axios({
url: "http://localhost:3000/applications",
method: "get"
}).then(response => {
if (response && response.data) {
this.processData(response.data);
}
})
},
processData(list, isLeaf = false, ApplicationName = "") {
for (let i = 0; i < list.length; ++i) {
if (list[i].ApplicationSystems) {
this.processData(list[i].ApplicationSystems, false, ApplicationName);
}
if (list[i].ChildGroup) {
if (list[i].ApplicationName) this.processData(list[i].ChildGroup, false, list[i].ApplicationName);
else this.processData(list[i].ChildGroup, false, ApplicationName);
}
if (list[i].Functions) {
this.processData(list[i].Functions, true, ApplicationName);
}
if (isLeaf) {
let flag = false;
for (let j = 0; j < this.Applications.length; ++j) {
if (this.Applications[j].ApplicationName === ApplicationName) {
this.Applications[j].Functions.push({ Name: list[i].Name, ID: list[i].ID });
flag = true;
break;
}
}
if (!flag) {
this.Applications.push({
ApplicationName: ApplicationName,
Functions: [{ Name: list[i].Name, ID: list[i].ID }]
});
}
}
}
},
//过滤后列表
afterFilterList(queryString) {
let results = [];
for (let i = 0; i < this.Applications.length; ++i) {
let functions = this.Applications[i].Functions.filter(f => f.Name.toLowerCase().indexOf(queryString.toLowerCase()) !== -1);
if (functions.length > 0) {
results.push({ ApplicationName: this.Applications[i].ApplicationName, Functions: functions });
}
}
return results;
},
//选项的选择
selectFunc(func, ApplicationName) {
this.FuncName = ApplicationName + '/' + func.Name;
this.FuncId = func.ID;
setTimeout(() => {
mySelect.value = ApplicationName + '/' + func.Name;
})
},
handleIconClick() {
}
},
created() {
var mySelect = document.getElementById("mySelect");
},
mounted() {
this.app = this;
this.loadAll();
}
})
</script>
</body>
<style>
.ApplicationName {
font-weight: 600;
line-height: 30px;
color: #bcb9b9;
}
.FunctionName {
color: rgb(68, 68, 68);
line-height: 40px;
display: block;
text-indent: 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
}
.el-autocomplete-suggestion li.highlighted,
.el-autocomplete-suggestion li:hover {
background-color: #ffffff !important;
}
.el-autocomplete-suggestion li.highlighted .FunctionName,
.el-autocomplete-suggestion li .FunctionName:hover {
background-color: #F5F7FA;
}
.el-input__inner {
min-height: 40px;
}
</style>
</html>