avatar
fireworks99
keep hungry keep foolish

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>
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy