Javascript中的樹結構

前沿

    前端中設計數據結構的方面很少,最經常使用的就是對樹結構的一些操做。從某種意義上來講,前端工做自己就是和樹結構打交道的一個工做方向。畢竟,DOM就是自然的樹結構。因此如何可以良好地對樹結構進行操做,是前端工程師不可或缺的一項能力。javascript

樹結構

定義

    什麼是樹結構呢?從數據結構的角度來說:css

  • 樹是非線性數據結構
  • 每一個節點可能會有0個或多個後代
  • 每一個節點具有惟一的父節點(若是有多個父節點,那就是圖了)

分類

樹根據節點的不一樣能夠分爲不一樣的類型,最多見的分類是:html

  • 二叉樹
  • 二叉搜索樹
  • 平衡二叉查找樹
  • 紅黑樹

具體他們之間的區別這裏就不細說了,具體請查看詳情前端

前端中常見的樹結構

DOM樹結構

下面的html結構就是一個自然的樹結構。每一個Dom節點下面,有0/1/多個子節點。java

對象樹結構

  • 數組形式
特色: 每個對象節點,下面可能會有children,也可能沒有children
let obj = [
    {
        id: 1,
        type: 'dom',
        children: [
            {
                id: 2,
                type: 'html'
            }
        ]
    },
    {
        id: 3,
        type: 'css',
        children: [
            {
                id: 4,
                type: 'javascript'
            }
        ]
    }
];
  • 對象形式

最多見的就是抽象語法樹:node

特色: 對象的屬性下面有不一樣的屬性,每個屬性下面可能還會有不一樣的屬性

這種格式常常在數據統計中出現。react

Javascript中樹結構的遍歷

    其實在我看來,樹的結構形式有不少種,可是,前端工做中不多涉及對樹節點的修改等操做,大部分是遍歷和統計數據。git

需求場景:下面以Dom樹結構爲例:
一、須要輸出每一個節點的名稱和節點深度
三、深度優先和廣度優先都須要實現
  • 假定已經有了對應的樹結構,子節點是childNodes(爲啥不用children呢?本身去查吧)

深度優先遍歷

深度優先遍歷,又叫DFS(deep first search),遍歷順序是優先遍歷節點的子節點,而後再是節點的兄弟節點。github

  • 遞歸輸出
function DeepSearch(node, deep = 0) {
    const child = node.childNodes;

    const { nodeName } = node;
    console.log(`name:${nodeName},deep:${deep}`);

    for(let i = 0, len = child.length; i < len; i++) {
        DeepSearch(child[i], deep + 1);        
    }
}
  • 非遞歸輸出
function deepSearch(node, deep = 0) {
    const stack = [];
    const deepArr = [];
    stack.push(node);
    deepArr.push(0);
    while(stack.length !== 0){
        const node = stack.shift();
        const deep = deepArr.shift();

        const { nodeName } = node;
        console.log(`name:${nodeName},deep:${deep}`);

        const nodes = child.childNodes;
        for( let i = node.length; i > 0; i--) {
            stack.unshift(nodes[i]);
            deep.unshift(deep + 1);
        }
    }
}

廣度優先遍歷

廣度優先,正好和深度優先策略相反,先遍歷節點的兄弟節點,再遍歷子節點。數組

  • 遞歸輸出
function BreadSearch(node, deep = 0) {
    const child = node.childNodes;
    const res = [];

    for(let i = 0, len = child.length; i < len; i++) {

        const { nodeName } = node;
        console.log(`name:${nodeName},deep:${deep}`);

        res.push(child[i]);
    }

    DeepSearch(res, deep + 1);        

}
  • 非遞歸輸出
function breadSearch(node, deep = 0) {
    const stack = [];
    const deepArr = [];
    stack.push(node);
    deepArr.push(0);
    while (stack.length !== 0 ) {
        const node = stack.shift();
        cosnt deep = stack.shift();
        const { nodeName } = node;
        console.log(`name:${nodeName},deep:${deep}`);

        for(let i = 0, len = child.length; i < len; i++) {
            stack.push(child[i]);
        }
    }
}

業務場景

前端中的樹操做,常常是生成特定的樹結構。常見的場景有生成路由和生成菜單。

路由

下面以react-router爲例,說明:

簡單狀況(bad)

通常狀況下,react-router的路由是下面的:

<Switch>
    <Route path="/home" component={A}/>
    <Route path="/manage" component={B}/>
    <Route path="/customer" component={C}/>
    ... ...
</Switch>

可是若是全部的都按照上面的寫法,每加一個路由,都須要取在內容下面,加一個

<Route path="/extra" component={D}/>

這樣會形成代碼不容易維護,並且可讀性很差

配置的方式(better)

配置的方式總好過,每次打開路由的內部代碼修改。

const routers = [
    {
        path: '/a',
        component: A
    },
    {
        title: '考試',
        id: 'exam',
        path: '/b',
        children: [
            {
                path: '/c',
                component: C
            },
            {
                path: '/d',
                component: D
            }
        ]
    }
];

function getRoute (routes, rootPath = '') {
    let res = [];
    for (let i = 0, len = routes.length; i < len; i++) {
        const route = routes[i];
        const { path } = route;
        if (route.children) {
            res = [...res, ...getRoute(route.children, path)];
        } else {
            res.push(
                <Route
                    path={`${rootPath}${path}`}
                    ...route
                />
            );
        }
    }
    return res;
};

<Switch>
    {
        getRoute(routers)
    }
</Switch>

菜單

菜單和路由的方式很類似,並且一般會結合在一塊兒使用,具體的寫法,這裏就不贅述了,由於實在是太類似了,留給大家吧。。

參考資料

相關文章
相關標籤/搜索