【算法-JS-primary】手动实现 getElementById

185 阅读1分钟

问题描述

自行实现一个查 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;
}

优点:

  1. 思路清晰
  2. 实现容易
  3. 代码简单

缺点:

  1. 主要就是性能较差
  2. 还有就是容易打爆栈

树立一种意识:凡是能递归实现的,都一定能通过循环来实现

2、循环实现

体现的是“广度优先遍历”的思想。

主要是这个循环体要如何写呢?即 getNextNode() 的实现?思路就在这张图里面,嗖嘎!

image.png

/**
 * 循环实现
 * @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、性能测试

递归image.png
循环image.png
原生image.png

对比太明显了。递归毫秒级,循环比原生也差了2个数量级。

实际上getElementById浏览器是用的一个哈希map存储的,根据id直接映射到DOM结点,而getElementsByClassName就是用的这样的非递归查找。