跳到主要内容

操作 DOM 节点方法

面试速答(30 秒版 TL;DR)

  • 查:querySelector(All)getElementByIdclosestmatchesparentNode/children
  • 增:createElementcreateTextNodeappend/appendChildprependbefore/afterinsertBeforeinsertAdjacent*DocumentFragment
  • 改:textContentinnerHTMLclassListdatasetstylesetAttribute
  • 删:removeremoveChildreplaceWith/replaceChildreplaceChildren

心智模型:对“树”的 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:查(选择器)增(创建+插入)改(内容/属性/类/样式)删(移除/替换);性能关键是“批量一次性挂载”和“减少重排”。