问题描述
自行实现一个查 DOM 的功能 document.getElementById。
1、递归实现
体现的是“深度优先遍历”的思想。这也是最容易想到的一个思路,代码如下:
/**
* 递归实现
* @param {*} node
* @param {*} id
*/
export function getElementById(node, id) {
if (!node) return null;
if (node.id === id) return node;
const children = node.childNodes || [];
for (key in children) {
const el = getElementById(children[key], id);
if (el) return el;
}
return null;
}
优点:
- 思路清晰
- 实现容易
- 代码简单
缺点:
- 主要就是性能较差
- 还有就是容易打爆栈
树立一种意识:凡是能递归实现的,都一定能通过循环来实现。
2、循环实现
体现的是“广度优先遍历”的思想。
主要是这个循环体要如何写呢?即 getNextNode() 的实现?思路就在这张图里面,嗖嘎!
/**
* 循环实现
* @param {*} node
* @param {*} id
*/
export function getElementById(node, id) {
while (node) {
if (!node) return null;
if (node.id === id) return node;
node = getNextNode(node);
}
return null;
}
/**
* 获取下一个节点。注意:这下一个节点有可能是 1第一个子节点 2后置兄弟节点 3父节点的后置兄弟节点。
* 如何非递归实现呢❓难搞🔥 ~2023-12-16 15:09
* @param {*} node
*/
function getNextNode(node) {
if (!node) return null;
const children = node.childNodes || [];
if (children[0]) return children[0];
if (node.nextElementSibling) return node.nextElementSibling;
while (node.parentNode) {
const sibling = node.parentNode.nextElementSibling;
if (sibling) return sibling;
node = node.parentNode;
}
return null;
}
3、性能测试
| 递归 | |
| 循环 | |
| 原生 |
对比太明显了。递归毫秒级,循环比原生也差了2个数量级。
实际上getElementById浏览器是用的一个哈希map存储的,根据id直接映射到DOM结点,而getElementsByClassName就是用的这样的非递归查找。