操作 DOM 节点方法
面试速答(30 秒版 TL;DR)
- 查:
querySelector(All)、getElementById、closest、matches、parentNode/children。 - 增:
createElement、createTextNode、append/appendChild、prepend、before/after、insertBefore、insertAdjacent*、DocumentFragment。 - 改:
textContent、innerHTML、classList、dataset、style、setAttribute。 - 删:
remove、removeChild、replaceWith/replaceChild、replaceChildren。
心智模型:对“树”的 CRUD
查(定位节点)
const el1 = document.getElementById("app"); // 单个
const el2 = document.querySelector(".item[data-id='1']");
const list = document.querySelectorAll(".item"); // NodeList(通常是静态的)
const btn = document.querySelector("button");
const form = btn?.closest("form"); // 向上找最近的祖先
console.log(btn?.matches(".primary")); // 是否匹配选择器
面试加分点:getElementsByClassName/getElementsByTagName 返回的是 HTMLCollection(通常是 live);querySelectorAll 返回的是 NodeList(通常是 static),两者在“DOM 变更后是否自动反映”上不一样。
增(创建与插入)
1)创建节点
const div = document.createElement("div");
div.className = "item";
const text = document.createTextNode("hello");
div.append(text);
2)插入节点(常用 API 对比)
const parent = document.querySelector("#app");
const child = document.createElement("div");
parent.append(child); // 可追加多个 Node 或 string,返回 void
// parent.appendChild(child); // 只能追加 Node,返回追加的 Node
parent.prepend(document.createElement("span"));
child.before(document.createComment("before child"));
child.after(document.createElement("hr"));
3)在指定位置插入
const root = document.querySelector("#app");
const a = document.createElement("a");
const ref = root.firstChild;
root.insertBefore(a, ref); // ref 为 null 等价于 appendChild
root.insertAdjacentHTML("beforeend", "<p>ok</p>");
4)批量插入:DocumentFragment(减少多次重排)
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = String(i);
frag.append(li);
}
document.querySelector("ul").append(frag); // 一次性挂到 DOM
改(内容、属性、样式)
1)改文本与 HTML
el.textContent = "<b>not html</b>"; // 安全:按文本处理
el.innerHTML = "<b>html</b>"; // 有 XSS 风险:必须保证内容可信或经过净化
2)改属性、类名、数据属性
el.setAttribute("aria-label", "关闭");
el.removeAttribute("disabled");
el.classList.add("active");
el.classList.toggle("open", true);
el.dataset.userId = "42"; // 等价于 data-user-id="42"
3)改样式
el.style.width = "100px";
el.style.setProperty("--gap", "8px");
删(移除、替换)
el.remove(); // 从父节点移除自己(现代浏览器)
const parent = document.querySelector("#app");
parent.removeChild(parent.firstChild); // 旧 API,仍常见
el.replaceWith(document.createElement("span"));
parent.replaceChildren(); // 清空并替换子节点
常见追问
Q1:append vs appendChild 有什么区别?
append:可以追加多个参数(Node或字符串),返回void。appendChild:只能追加一个Node,返回追加的Node(方便链式/拿到引用)。
Q2:如何避免频繁 DOM 操作造成卡顿?
- 批量构建后一次性挂载(
DocumentFragment、字符串拼接后一次innerHTML)。 - 把读布局与写布局分开,避免“读写交替”触发多次强制同步布局(layout thrashing)。
- 大列表用虚拟列表/分片渲染(
requestAnimationFrame/requestIdleCallback视场景)。
易错点/坑
innerHTML拼接用户输入容易 XSS,面试时要主动说“需要可信来源或净化”。- 直接把
onclick这类字符串事件处理器塞进属性不是好习惯:更推荐addEventListener,或事件委托(把监听挂在父元素上)。 - 遍历子节点要分清
childNodes(包含文本/注释)与children(仅元素)。
速记要点(可背诵)
- DOM 操作核心就是对树做 CRUD:查(选择器)增(创建+插入)改(内容/属性/类/样式)删(移除/替换);性能关键是“批量一次性挂载”和“减少重排”。