整理了一系列的JavaScript樹操作方法,不用再一遍又一遍的百度了

xmanlin 發表於 2021-10-10
JavaScript

前言

樹結構的資料操作對於一個開發者來說是一個必備的技能。在實際的業務開發中,我們也會遇到許多樹結構的體現,比如最常見的地域樹,以及企業結構樹、校級組織樹等等。

下面整理了一系列的關於JavaScript樹的操作方法,結合示例,,相信大家在實際開發工作中或多或少都會用到。

陣列扁平化

示例

const arr = [1, [2, [3, 4]], 5, [6]];

方法

1、遞迴

const flatten = (arr) => {
    let res = [];
    arr.map(item => {
        if(Array.isArray(item)) {
            res = res.concat(flatten(item));
        } else {
            res.push(item);
        }
    });
    return res;
}

2、reduce

const flatten = (arr) => {
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}

3、flat

const flatten = (arr) => {
    return arr.flat(Infinity)
}

執行結果

const result = flatten(arr);
console.log(result);

// 執行結果
[1, 2, 3, 4, 5, 6]

陣列轉樹形結構

示例

const arr = [
    {
        name: '小明',
        id: 1,
        pid: 0,
    },
    {
        name: '小花',
        id: 11,
        pid: 1,
    },
    {
        name: '小華',
        id: 111,
        pid: 11,
    },
    {
        name: '小李',
        id: 112,
        pid: 11,
    },
    {
        name: '小紅',
        id: 12,
        pid: 1,
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
    },
    {
        name: '小林',
        id: 21,
        pid: 2,
    },
    {
        name: '小李',
        id: 22,
        pid: 2,
    }
]

方法

1、非遞迴

 const arrayToTree = (arr) => {
    let result = [];
    if (!Array.isArray(arr) || arr.length === 0) {
        return result
    }
    let map = {};
    arr.forEach(item => map[item.id] = item);
    arr.forEach(item => {
        const parent = map[item.pid];
        if(parent){
            (parent.children || (parent.children=[])).push(item);
        } else {
            result.push(item);
        }
    })
    return result
}

2、遞迴

const arrayToTree = (arr, pid) => {
    let res = [];
    arr.forEach(item => {
        if(item.pid === pid){
            let itemChildren = arrayToTree(arr,item.id);
            if(itemChildren.length) {
                item.children = itemChildren;
            }
            res.push(item);
        }
    });
    return res;
}

執行結果

// const result = arrayToTree(arr);
const result = arrayToTree(arr, 0);
console.log(result);

// 執行結果
[
    {
        "name": "小明",
        "id": 1,
        "pid": 0,
        "children": [
            {
                "name": "小花",
                "id": 11,
                "pid": 1,
                "children": [
                    {
                        "name": "小華",
                        "id": 111,
                        "pid": 11
                    },
                    {
                        "name": "小李",
                        "id": 112,
                        "pid": 11
                    }
                ]
            },
            {
                "name": "小紅",
                "id": 12,
                "pid": 1
            }
        ]
    },
    {
        "name": "小王",
        "id": 2,
        "pid": 0,
        "children": [
            {
                "name": "小林",
                "id": 21,
                "pid": 2
            },
            {
                "name": "小李",
                "id": 22,
                "pid": 2
            }
        ]
    }
]

樹形結構轉陣列(扁平化)

示例

const tree = [
    {
        name: '小明',
        id: 1,
        pid: 0,
        children: [
            {
                name: '小花',
                id: 11,
                pid: 1,
                children: [
                    {
                        name: '小華',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李',
                        id: 112,
                        pid: 11,
                    }
                ]
            },
            {
                name: '小紅',
                id: 12,
                pid: 1,
            }
        ]
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
        children: [
            {
                name: '小林',
                id: 21,
                pid: 2,
            },
            {
                name: '小李',
                id: 22,
                pid: 2,
            }
        ]
    }
]

方法

1、深度優先遍歷

const treeToArray = (tree) => {
    let stack = tree,
        result = [];
    while(stack.length !== 0){
        let pop = stack.pop();
        result.push({
            id: pop.id,
            name: pop.name,
            pid: pop.pid
        })
        let children = pop.children
        if(children){
            for(let i = children.length-1; i >=0; i--){
                stack.push(children[i])
            }
        }
    }
    return result
}

2、廣度優先遍歷

const treeToArray = (tree) => {
    let queue = tree,
        result = [];
    while(queue.length !== 0){
        let shift = queue.shift();
        result.push({
            id: shift.id,
            name: shift.name,
            pid: shift.pid
        })
        let children = shift.children
        if(children){
            for(let i = 0; i < children.length; i++){
                queue.push(children[i])
            }
        }
    }
    return result
}

3、不用考慮除children外的其他屬性

const treeToArray = (source)=>{
    let res = []
    source.forEach(item=>{
        res.push(item)
        item.children && res.push(...treeToArray(item.children))
    })
    return res.map((item) => {
        if (item.children) {
            delete item.children
        }
        return item
    })
}

執行結果

const result = treeToArray(tree);
console.log(result);

// 執行結果
[
    {
        "name": "小明",
        "id": 1,
        "pid": 0
    },
    {
        "name": "小花",
        "id": 11,
        "pid": 1
    },
    {
        "name": "小華",
        "id": 111,
        "pid": 11
    },
    {
        "name": "小李",
        "id": 112,
        "pid": 11
    },
    {
        "name": "小紅",
        "id": 12,
        "pid": 1
    },
    {
        "name": "小王",
        "id": 2,
        "pid": 0
    },
    {
        "name": "小林",
        "id": 21,
        "pid": 2
    },
    {
        "name": "小李",
        "id": 22,
        "pid": 2
    }
]

樹篩選,保留符合條件的資料並返回樹結構

示例

const tree = [
    {
        name: '小明',
        id: 1,
        pid: 0,
        show: true,
        children: [
            {
                name: '小花',
                id: 11,
                pid: 1,
                show: true,
                children: [
                    {
                        name: '小華',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李',
                        id: 112,
                        pid: 11,
                        show: true,
                    }
                ]
            },
            {
                name: '小紅',
                id: 12,
                pid: 1,
            }
        ]
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
        show: true,
        children: [
            {
                name: '小林',
                id: 21,
                pid: 2,
            },
            {
                name: '小李',
                id: 22,
                pid: 2,
            }
        ]
    }
]

方法

篩選出show為true資料

const filterTreeByFunc = (tree, func) => {
    if (!Array.isArray(tree) || tree.length === 0) {
        return []
    }
    return tree.filter(item => {
        item.children = item.children && filterTreeByFunc(item.children, func)
        return func(item) || (item.children && item.children.length)
    })
}

const func = (item) => {
    return item.show === true
}

執行結果

const result = filterTreeByFunc(tree, func);
console.log(result);

// 執行結果
[
    {
        "name": "小明",
        "id": 1,
        "pid": 0,
        "show": true,
        "children": [
            {
                "name": "小花",
                "id": 11,
                "pid": 1,
                "show": true,
                "children": [
                    {
                        "name": "小李",
                        "id": 112,
                        "pid": 11,
                        "show": true
                    }
                ]
            }
        ]
    },
    {
        "name": "小王",
        "id": 2,
        "pid": 0,
        "show": true,
        "children": []
    }
]

查詢某一節點在樹中路徑

示例

const tree = [
    {
        name: '小明',
        id: 1,
        pid: 0,
        children: [
            {
                name: '小花',
                id: 11,
                pid: 1,
                children: [
                    {
                        name: '小華',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李',
                        id: 112,
                        pid: 11,
                    }
                ]
            },
            {
                name: '小紅',
                id: 12,
                pid: 1,
            }
        ]
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
        children: [
            {
                name: '小林',
                id: 21,
                pid: 2,
            },
            {
                name: '小李',
                id: 22,
                pid: 2,
            }
        ]
    }
]

方法

const getNodePath = (tree, id) => {
    if (!Array.isArray(tree) || tree.length === 0) {
        return []
    }
    const path = []
    const treeFindPath = (tree, id, path) => {
        for (const item of tree) {
            path.push(item.id);
            if (item.id === id) {
                return path
            }
            if (item.children) {
                const findChildren = treeFindPath(item.children,id, path);
                if (findChildren.length) {
                    return findChildren;
                }
            }
            path.pop();
        }
        return [];
    }
    return treeFindPath(tree, id, path)
}

執行結果

const result = getNodePath(tree, 112);
console.log(result);

// 執行結果
[1, 11, 112]

模糊查詢樹

示例

const tree = [
    {
        name: '小明前端專家',
        id: 1,
        pid: 0,
        children: [
            {
                name: '小花前端程式媛',
                id: 11,
                pid: 1,
                children: [
                    {
                        name: '小華划水運動員',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李摸魚運動員',
                        id: 112,
                        pid: 11,
                    }
                ]
            },
            {
                name: '小紅摸魚程式設計師',
                id: 12,
                pid: 1,
            }
        ]
    },
    {
        name: '小王內卷王',
        id: 2,
        pid: 0,
        children: [
            {
                name: '小林摸魚王',
                id: 21,
                pid: 2,
            },
            {
                name: '小李後端程式設計師',
                id: 22,
                pid: 2,
            }
        ]
    }
]

方法

const fuzzyQueryTree = (arr, value) => {
    if (!Array.isArray(arr) || arr.length === 0) {
        return []
    }
    let result = [];
    arr.forEach(item => {
        if (item.name.indexOf(value) > -1) {
            const children = fuzzyQueryTree(item.children, value);
            const obj = { ...item, children }
            result.push(obj);
        } else {
            if (item.children && item.children.length > 0) {
                const children = fuzzyQueryTree(item.children, value);
                const obj = { ...item, children }
                if (children && children.length > 0) {
                    result.push(obj);
                }
            }
        }
    });
    return result;
};

執行結果

const result = fuzzyQueryTree(tree,'程式');
console.log(result);

// 執行結果
[
    {
        "name": "小明前端專家",
        "id": 1,
        "pid": 0,
        "children": [
            {
                "name": "小花前端程式媛",
                "id": 11,
                "pid": 1,
                "children": []
            },
            {
                "name": "小紅摸魚程式設計師",
                "id": 12,
                "pid": 1,
                "children": []
            }
        ]
    },
    {
        "name": "小王內卷王",
        "id": 2,
        "pid": 0,
        "children": [
            {
                "name": "小李後端程式設計師",
                "id": 22,
                "pid": 2,
                "children": []
            }
        ]
    }
]

樹節點新增屬性

示例

const tree = [
    {
        name: '小明',
        id: 1,
        pid: 0,
        children: [
            {
                name: '小花',
                id: 11,
                pid: 1,
                children: [
                    {
                        name: '小華',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李',
                        id: 112,
                        pid: 11,
                    }
                ]
            },
            {
                name: '小紅',
                id: 12,
                pid: 1,
            }
        ]
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
        children: [
            {
                name: '小林',
                id: 21,
                pid: 2,
            },
            {
                name: '小李',
                id: 22,
                pid: 2,
            }
        ]
    }
]

方法

const addAttrToNodes = (tree) => {
    tree.forEach((item) => {
        item.title = '新生代農民工'
        if (item.children && item.children.length > 0) {
            addAttrToNodes(item.children)
        }
    })
    return tree
}

執行結果

const result = addAttrToNodes(tree);
console.log(result);

// 執行結果
[
    {
        "name": "小明",
        "id": 1,
        "pid": 0,
        "children": [
            {
                "name": "小花",
                "id": 11,
                "pid": 1,
                "children": [
                    {
                        "name": "小華",
                        "id": 111,
                        "pid": 11,
                        "title": "新生代農民工"
                    },
                    {
                        "name": "小李",
                        "id": 112,
                        "pid": 11,
                        "title": "新生代農民工"
                    }
                ],
                "title": "新生代農民工"
            },
            {
                "name": "小紅",
                "id": 12,
                "pid": 1,
                "title": "新生代農民工"
            }
        ],
        "title": "新生代農民工"
    },
    {
        "name": "小王",
        "id": 2,
        "pid": 0,
        "children": [
            {
                "name": "小林",
                "id": 21,
                "pid": 2,
                "title": "新生代農民工"
            },
            {
                "name": "小李",
                "id": 22,
                "pid": 2,
                "title": "新生代農民工"
            }
        ],
        "title": "新生代農民工"
    }
]

樹節點刪除屬性

示例

這裡直接使用上面——樹形結構節點新增屬性的執行結果

方法

const removeAttrFromNode = (tree) => {
    tree.forEach((item) => {
        delete item.title
        if (item.children && item.children.length > 0) {
            removeAttrFromNode(item.children)
        }
    })
    return tree
}

執行結果

const result = removeAttrFromNode(tree);
console.log(result);

// 執行結果
[
    {
        "name": "小明",
        "id": 1,
        "pid": 0,
        "children": [
            {
                "name": "小花",
                "id": 11,
                "pid": 1,
                "children": [
                    {
                        "name": "小華",
                        "id": 111,
                        "pid": 11
                    },
                    {
                        "name": "小李",
                        "id": 112,
                        "pid": 11
                    }
                ]
            },
            {
                "name": "小紅",
                "id": 12,
                "pid": 1
            }
        ]
    },
    {
        "name": "小王",
        "id": 2,
        "pid": 0,
        "children": [
            {
                "name": "小林",
                "id": 21,
                "pid": 2
            },
            {
                "name": "小李",
                "id": 22,
                "pid": 2
            }
        ]
    }
]

刪除樹中的空children

示例

const tree = [
    {
        name: '小明',
        id: 1,
        pid: 0,
        children: [
            {
                name: '小花',
                id: 11,
                pid: 1,
                children: [
                    {
                        name: '小華',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李',
                        id: 112,
                        pid: 11,
                        children: []
                    }
                ]
            },
            {
                name: '小紅',
                id: 12,
                pid: 1,
                children: []
            }
        ]
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
        children: [
            {
                name: '小林',
                id: 21,
                pid: 2,
            },
            {
                name: '小李',
                id: 22,
                pid: 2,
                children: []
            }
        ]
    }
]

方法

const removeEmptyChildren = (tree) => {
    tree.forEach((item) => {
        if (item.children && item.children.length ===0) {
            delete item.children
        } else if (item.children && item.children.length > 0) {
            removeEmptyChildren(item.children)
        }
    })
    return tree
}

執行結果

const result = removeEmptyChildren(tree);
console.log(result);

// 執行結果
[
    {
        "name": "小明",
        "id": 1,
        "pid": 0,
        "children": [
            {
                "name": "小花",
                "id": 11,
                "pid": 1,
                "children": [
                    {
                        "name": "小華",
                        "id": 111,
                        "pid": 11
                    },
                    {
                        "name": "小李",
                        "id": 112,
                        "pid": 11
                    }
                ]
            },
            {
                "name": "小紅",
                "id": 12,
                "pid": 1
            }
        ]
    },
    {
        "name": "小王",
        "id": 2,
        "pid": 0,
        "children": [
            {
                "name": "小林",
                "id": 21,
                "pid": 2
            },
            {
                "name": "小李",
                "id": 22,
                "pid": 2
            }
        ]
    }
]

獲取樹中所有的葉子節點

示例

const tree = [
    {
        name: '小明',
        id: 1,
        pid: 0,
        children: [
            {
                name: '小花',
                id: 11,
                pid: 1,
                children: [
                    {
                        name: '小華',
                        id: 111,
                        pid: 11,
                    },
                    {
                        name: '小李',
                        id: 112,
                        pid: 11,
                    }
                ]
            },
            {
                name: '小紅',
                id: 12,
                pid: 1,
            }
        ]
    },
    {
        name: '小王',
        id: 2,
        pid: 0,
        children: [
            {
                name: '小林',
                id: 21,
                pid: 2,
            },
            {
                name: '小李',
                id: 22,
                pid: 2,
            }
        ]
    }
]

方法

const getAllLeaf = (tree) => {
    const result = []
    const getLeaf = (tree) => {
        tree.forEach((item) => {
            if (!item.children) {
                result.push(item)
            } else {
                getLeaf(item.children)
            }
        })
    }
    getLeaf(tree)
    return result
}

執行結果

const result = getAllLeaf(tree);
console.log(result);

// 執行結果
[
    {
        "name": "小華",
        "id": 111,
        "pid": 11
    },
    {
        "name": "小李",
        "id": 112,
        "pid": 11
    },
    {
        "name": "小紅",
        "id": 12,
        "pid": 1
    },
    {
        "name": "小林",
        "id": 21,
        "pid": 2
    },
    {
        "name": "小李",
        "id": 22,
        "pid": 2
    }
]

參考

https://wintc.top/article/20

https://www.cnblogs.com/mengf...

https://blog.csdn.net/susuzhe...

https://blog.csdn.net/web_yue...

最後

本文整理了一系列的關於JavaScript樹的操作方法,相當於平時的一個總結。大家可以拿來即用,或者根據實際的業務進行參考修改。

如果大家有更好的實現方式,或者自己在開發中遇到的,但是上面沒有涉及到的,歡迎提出來,大家一起討論一起進步~