從JS遍歷DOM樹學演算法

weixin_34253539發表於2018-12-05

從簡單的需求/面試題談起

自定義一個方法去檢查DOM中某個ID的元素。類似getElementById.

先推薦閱讀下:

廣度優先.

深度優先搜尋

// HTML結構如下

<div class="wrapper">
    <section class="header">
        <div class="logo"></div>
    </section>
    <section class="main">
        <div class="sidebar">
            <ul class="menu">
                <li class='li'>
                    <a href="" id='demo'>li1-a</a>
                </li>
                <li class='li'>
                    <a href="">li2</a>
                </li>
            </ul>
        </div>
    </section>
    <section class="footer">
        <div class="copyright"></div>
    </section>
</div>
複製程式碼

簡單畫了下DOM節點(只考慮元素節點)圖:

程式碼實現

  • 深度優先, 遞迴實現

const cusGetElementByIdByDFS = function (parentNode, id) {
    // 深度優先, 遞迴實現
    if (parentNode) {
        let target = null;
        const children = Array.from(parentNode.children);
        if (parentNode.id === id) {
            return parentNode;
        }
        for (let i = 0; i < children.length; i++) {
            target = cusGetElementByIdByDFS(children[i], id);
            if (target) {
                return target;
            }
        }
    }
    return null;
}

複製程式碼
// 測試程式碼
console.log(cusGetElementByIdByDFS(document.querySelector('.wrapper') , 'demo'))
複製程式碼
  • 深度優先, 非遞迴實現, 使用棧
const cusGetElementByIdByDFS2 = function (parentNode, id) {
    if (!parentNode) {
        return null;
    }
    // 深度優先, 非遞迴實現, 使用棧
    let stack = [];
    if (parentNode.id === id) {
        return parentNode;
    }
    for (let i = parentNode.children.length; i > 0; i--) {
        stack.push(parentNode.children[i - 1]);
    }
    while (stack.length) {
        let node = stack.pop();
        if (node.id === id) {
            return node;
        } else {
            if (node.children.length > 0) {
                stack = Array.from(node.children).concat(stack);
            }
        }
    }
}
複製程式碼
// 測試程式碼
console.log(cusGetElementByIdByDFS2(document.querySelector('.wrapper') , 'demo'))
複製程式碼
  • 廣度優先 非遞迴實現
const cusGetElementByIdByBFS = function (parentNode, id) {
    // 廣度優先 非遞迴實現
    // 佇列的思想: 採用出隊的方式遍歷節點,如果遍歷到的節點有子節點,則將子節點入隊
    const layer = []; // 按照順序存放每個層級的每個節點
    if (parentNode) {
        // 初始化layer
        // 節點深度從父節點開始算起
        layer.push({
            node: parentNode,
            depth: 1
        });
        while (layer.length > 0) {
            const root = layer.shift(); // 出隊
            if (root.node.id === id) {
                return root; // 包括對應節點和節點深度
            } else {
                if (root.node.children.length > 0) {
                    Array.from(root.node.children).forEach(node => {
                        layer.push({
                            node,
                            depth: root.depth + 1
                        })
                    })
                }
            }
        }
    }
    return null;
}
複製程式碼
// 測試程式碼
console.log(cusGetElementByIdByBFS(document.querySelector('.wrapper') , 'demo'))
複製程式碼

後續看細下演算法書再做補充, 初次學習有勘誤請指出。

相關文章