DOM 操作是前端开发的基础。掌握如何创建、修改和删除 DOM 节点,才能动态构建页面内容。
🎯 创建节点#
createElement#
创建元素节点:
// 创建 div 元素const div = document.createElement('div')div.textContent = '新元素'div.className = 'box'div.id = 'myDiv'
// 创建带属性的元素const link = document.createElement('a')link.href = 'https://example.com'link.textContent = '链接'link.target = '_blank'
// 创建表单元素const input = document.createElement('input')input.type = 'text'input.name = 'username'input.placeholder = '请输入用户名'createTextNode#
创建文本节点:
const text = document.createTextNode('这是文本内容')
// 通常直接用 textContentconst div = document.createElement('div')div.textContent = '这是文本内容' // 更简洁createDocumentFragment#
创建文档片段(性能优化):
// 创建文档片段const fragment = document.createDocumentFragment()
// 批量添加元素到片段for (let i = 0; i < 1000; i++) { const li = document.createElement('li') li.textContent = `项目 ${i}` fragment.appendChild(li)}
// 一次性插入 DOM(只触发一次重排)document.getElementById('list').appendChild(fragment)
// 对比直接插入(会触发多次重排)const list = document.getElementById('list')for (let i = 0; i < 1000; i++) { const li = document.createElement('li') li.textContent = `项目 ${i}` list.appendChild(li) // 每次都触发重排}cloneNode#
克隆节点:
const original = document.getElementById('template')
// 浅克隆(只克隆节点本身)const shallow = original.cloneNode(false)
// 深克隆(包含所有子节点)const deep = original.cloneNode(true)
// 注意:克隆的节点没有事件监听器// 需要重新添加事件插入节点#
appendChild#
在末尾添加子节点:
const parent = document.getElementById('container')const child = document.createElement('div')child.textContent = '新子元素'
parent.appendChild(child)
// 如果节点已存在于 DOM,会移动而不是复制const existing = document.getElementById('existing')parent.appendChild(existing) // 移动到 parent 末尾insertBefore#
在指定节点前插入:
const parent = document.getElementById('container')const newNode = document.createElement('div')const reference = document.getElementById('reference')
// 在 reference 前插入 newNodeparent.insertBefore(newNode, reference)
// 如果 reference 是 null,效果等同于 appendChildparent.insertBefore(newNode, null)现代插入方法#
const element = document.getElementById('target')
// prepend:在开头插入element.prepend('文本', document.createElement('span'))
// append:在末尾插入element.append('文本', document.createElement('span'))
// before:在元素前插入element.before(document.createElement('div'))
// after:在元素后插入element.after(document.createElement('div'))
// 这些方法支持多个参数,包括字符串insertAdjacentHTML#
插入 HTML 字符串:
const element = document.getElementById('target')
// 在元素前element.insertAdjacentHTML('beforebegin', '<p>之前</p>')
// 在元素内部开头element.insertAdjacentHTML('afterbegin', '<p>内部开头</p>')
// 在元素内部末尾element.insertAdjacentHTML('beforeend', '<p>内部末尾</p>')
// 在元素后element.insertAdjacentHTML('afterend', '<p>之后</p>')
// 结果结构:// <p>之前</p>// <div id="target">// <p>内部开头</p>// 原有内容// <p>内部末尾</p>// </div>// <p>之后</p>insertAdjacentElement / insertAdjacentText#
const element = document.getElementById('target')const newElement = document.createElement('span')
// 插入元素element.insertAdjacentElement('beforebegin', newElement)
// 插入文本element.insertAdjacentText('afterend', '纯文本')删除节点#
removeChild#
删除子节点:
const parent = document.getElementById('container')const child = document.getElementById('child')
// 删除并返回被删除的节点const removed = parent.removeChild(child)
// 可以重新插入document.body.appendChild(removed)remove#
直接删除元素:
const element = document.getElementById('target')
// 直接删除自己element.remove()
// 不返回任何值清空所有子��点#
const container = document.getElementById('container')
// 方法1:innerHTMLcontainer.innerHTML = ''
// 方法2:循环删除while (container.firstChild) { container.removeChild(container.firstChild)}
// 方法3:replaceChildren(现代方法)container.replaceChildren()
// 方法4:textContentcontainer.textContent = ''替换节点#
replaceChild#
替换子节点:
const parent = document.getElementById('container')const oldChild = document.getElementById('old')const newChild = document.createElement('div')newChild.textContent = '新元素'
parent.replaceChild(newChild, oldChild)replaceWith#
替换自身:
const element = document.getElementById('target')const newElement = document.createElement('div')
element.replaceWith(newElement)
// 支持多个参数element.replaceWith( document.createElement('span'), '文本', document.createElement('div'))replaceChildren#
替换所有子节点:
const container = document.getElementById('container')
// 清空并添加新子节点container.replaceChildren( document.createElement('div'), document.createElement('span'))
// 不传参数则清空container.replaceChildren()修改属性#
标准属性#
const element = document.getElementById('target')
// 直接访问标准属性element.id = 'newId'element.className = 'new-class'element.title = '提示文本'
// 特殊属性const link = document.querySelector('a')link.href = 'https://example.com'link.target = '_blank'
const img = document.querySelector('img')img.src = 'image.jpg'img.alt = '描述'
const input = document.querySelector('input')input.value = '输入值'input.disabled = trueinput.checked = true // checkbox/radiogetAttribute / setAttribute#
const element = document.getElementById('target')
// 获取属性const value = element.getAttribute('data-id')
// 设置属性element.setAttribute('data-id', '123')element.setAttribute('aria-label', '标签')
// 移除属性element.removeAttribute('data-id')
// 检查属性是否存在if (element.hasAttribute('data-id')) { // ...}
// 获取所有属性const attrs = element.attributesfor (const attr of attrs) { console.log(attr.name, attr.value)}dataset#
访问 data-* 属性:
// <div id="user" data-user-id="123" data-role="admin">
const element = document.getElementById('user')
// 读取(驼峰命名)console.log(element.dataset.userId) // "123"console.log(element.dataset.role) // "admin"
// 设置element.dataset.status = 'active'// 结果:data-status="active"
// 删除delete element.dataset.role修改样式#
style 属性#
const element = document.getElementById('target')
// 设置单个样式element.style.color = 'red'element.style.fontSize = '16px'element.style.backgroundColor = '#f0f0f0'
// 带连字符的属性用驼峰element.style.marginTop = '10px'element.style.borderRadius = '5px'
// 设置多个样式Object.assign(element.style, { width: '100px', height: '100px', border: '1px solid black',})
// 使用 cssText 一次性设置element.style.cssText = 'color: red; font-size: 16px;'
// 移除样式element.style.color = ''element.style.removeProperty('font-size')
// 读取计算后的样式const computed = getComputedStyle(element)console.log(computed.width)console.log(computed.getPropertyValue('font-size'))classList#
操作类名:
const element = document.getElementById('target')
// 添加类element.classList.add('active')element.classList.add('highlight', 'visible') // 多个
// 移除类element.classList.remove('active')element.classList.remove('highlight', 'visible')
// 切换类element.classList.toggle('active')element.classList.toggle('hidden', condition) // 根据条件
// 替换类element.classList.replace('old-class', 'new-class')
// 检查类是否存在if (element.classList.contains('active')) { // ...}
// 获取所有类console.log(element.classList.length)console.log(element.classList[0])console.log([...element.classList])修改内容#
textContent vs innerHTML#
const element = document.getElementById('target')
// textContent:纯文本,更安全element.textContent = '文本内容'element.textContent = '<script>alert("xss")</script>'// 显示原文本,不会执行
// innerHTML:解析 HTMLelement.innerHTML = '<strong>粗体</strong>'// ⚠️ 有 XSS 风险,不要插入用户输入
// innerText:类似 textContent,但考虑 CSS// (隐藏元素的文本不会返回)
// 读取内容console.log(element.textContent) // 所有文本console.log(element.innerHTML) // HTML 字符串outerHTML#
const element = document.getElementById('target')
// 读取包含自身的 HTMLconsole.log(element.outerHTML)
// 替换整个元素element.outerHTML = '<div class="new">新元素</div>'// element 变量仍指向旧元素(已从 DOM 移除)性能优化#
批量操作#
// 🔶 不好:多次 DOM 操作const list = document.getElementById('list')for (let i = 0; i < 100; i++) { list.innerHTML += `<li>项目 ${i}</li>` // 每次都重新解析}
// ✅ 好:一次性设置let html = ''for (let i = 0; i < 100; i++) { html += `<li>项目 ${i}</li>`}list.innerHTML = html
// ✅ 更好:使用 DocumentFragmentconst fragment = document.createDocumentFragment()for (let i = 0; i < 100; i++) { const li = document.createElement('li') li.textContent = `项目 ${i}` fragment.appendChild(li)}list.appendChild(fragment)离线操作#
// 将元素从 DOM 移除后操作,再插回const element = document.getElementById('target')const parent = element.parentNodeconst nextSibling = element.nextSibling
// 移除parent.removeChild(element)
// 大量操作for (let i = 0; i < 1000; i++) { element.appendChild(document.createElement('div'))}
// 插回parent.insertBefore(element, nextSibling)避免强制同步布局#
// 🔶 不好:读写交替导致多次重排for (const el of elements) { el.style.width = el.offsetWidth + 10 + 'px' // 读 -> 写}
// ✅ 好:批量读,批量写const widths = elements.map((el) => el.offsetWidth)elements.forEach((el, i) => { el.style.width = widths[i] + 10 + 'px'})实用函数#
// 创建元素的工具函数function createElement(tag, options = {}) { const element = document.createElement(tag)
if (options.className) { element.className = options.className }
if (options.id) { element.id = options.id }
if (options.text) { element.textContent = options.text }
if (options.html) { element.innerHTML = options.html }
if (options.attrs) { for (const [key, value] of Object.entries(options.attrs)) { element.setAttribute(key, value) } }
if (options.styles) { Object.assign(element.style, options.styles) }
if (options.children) { options.children.forEach((child) => { if (typeof child === 'string') { element.appendChild(document.createTextNode(child)) } else { element.appendChild(child) } }) }
return element}
// 使用const card = createElement('div', { className: 'card', children: [ createElement('h2', { text: '标题' }), createElement('p', { text: '内容' }), ],})总结#
| 操作 | 方法 | 说明 |
|---|---|---|
| 创建 | createElement | 创建元素 |
| 创建 | createDocumentFragment | 创建片段 |
| 插入 | appendChild | 末尾添加 |
| 插入 | insertBefore | 指定位置前 |
| 插入 | append/prepend | 现代方法 |
| 删除 | removeChild | 删除子节点 |
| 删除 | remove | 删除自身 |
| 替换 | replaceChild | 替换子节点 |
| 替换 | replaceWith | 替换自身 |
核心要点:
- 使用
DocumentFragment批量操作 textContent比innerHTML更安全- 用
classList操作类名 - 避免读写交替导致的重排
- 大量操作时考虑离线操作