Skip to content

DOM 操作

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('这是文本内容')
// 通常直接用 textContent
const 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 前插入 newNode
parent.insertBefore(newNode, reference)
// 如果 reference 是 null,效果等同于 appendChild
parent.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:innerHTML
container.innerHTML = ''
// 方法2:循环删除
while (container.firstChild) {
container.removeChild(container.firstChild)
}
// 方法3:replaceChildren(现代方法)
container.replaceChildren()
// 方法4:textContent
container.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 = true
input.checked = true // checkbox/radio

getAttribute / 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.attributes
for (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:解析 HTML
element.innerHTML = '<strong>粗体</strong>'
// ⚠️ 有 XSS 风险,不要插入用户输入
// innerText:类似 textContent,但考虑 CSS
// (隐藏元素的文本不会返回)
// 读取内容
console.log(element.textContent) // 所有文本
console.log(element.innerHTML) // HTML 字符串

outerHTML#

const element = document.getElementById('target')
// 读取包含自身的 HTML
console.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
// ✅ 更好:使用 DocumentFragment
const 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.parentNode
const 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替换自身

核心要点