Skip to content
On this page

JavaScript DOM 操作

DOM(文档对象模型)是HTML和XML文档的编程接口,JavaScript通过DOM API可以动态访问和更新文档内容、结构和样式。DOM将文档表示为节点树,每个节点代表文档的一部分。

DOM 基础概念

节点类型

DOM中的每个部分都是节点:

  • Document节点:文档根节点
  • Element节点:HTML标签元素
  • Text节点:元素中的文本内容
  • Attribute节点:元素的属性
  • Comment节点:HTML注释

DOM树结构

html
<!DOCTYPE html>
<html>
  <head>
    <title>Example</title>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

对应的DOM树:

  • Document
    • html (Element)
      • head (Element)
        • title (Element)
          • "Example" (Text)
      • body (Element)
        • h1 (Element)
          • "Hello World" (Text)

获取元素

通过ID获取

javascript
// 获取单个元素,最高效的方法之一
const element = document.getElementById('myId');
// 注意:getElementById只能在document对象上调用,不能在其他元素上调用

通过类名获取

javascript
// 获取HTMLCollection(动态集合)
const elements = document.getElementsByClassName('myClass');
// 或者在特定元素下搜索
const container = document.getElementById('container');
const childElements = container.getElementsByClassName('myClass');

// HTMLCollection是动态的,DOM变化时会自动更新
console.log(elements.length); // 假设为3
const newElement = document.createElement('div');
newElement.className = 'myClass';
document.body.appendChild(newElement);
console.log(elements.length); // 现在为4

通过标签名获取

javascript
// 获取HTMLCollection
const divs = document.getElementsByTagName('div');
const allElements = document.getElementsByTagName('*'); // 获取所有元素

通过CSS选择器获取

javascript
// querySelector - 返回第一个匹配的元素
const header = document.querySelector('.header');
const firstButton = document.querySelector('button');
const specificElement = document.querySelector('div#myId.myClass');

// querySelectorAll - 返回NodeList(静态集合)
const buttons = document.querySelectorAll('button');
const links = document.querySelectorAll('a[href*="example"]');

// NodeList是静态的,DOM变化不会影响已获取的NodeList
const buttons1 = document.querySelectorAll('button');
console.log(buttons1.length); // 假设为3
const newButton = document.createElement('button');
document.body.appendChild(newButton);
const buttons2 = document.querySelectorAll('button');
console.log(buttons2.length); // 为4,但buttons1.length仍然是3

选择器语法

javascript
// ID选择器
document.querySelector('#myId');

// 类选择器
document.querySelector('.myClass');

// 属性选择器
document.querySelector('[data-id="123"]');
document.querySelector('input[type="text"]');

// 伪类选择器
document.querySelector('li:first-child');
document.querySelector('tr:nth-child(even)');

// 组合选择器
document.querySelector('div > p'); // 直接子元素
document.querySelector('div p');   // 后代元素
document.querySelector('h1 + p');  // 相邻兄弟元素

操作元素内容

textContent vs innerText vs innerHTML

javascript
const element = document.getElementById('myElement');

// textContent - 获取/设置元素及其后代的纯文本内容
element.textContent = 'New text content';
// 会转义HTML标签,更安全

// innerText - 获取/设置元素的渲染文本内容
element.innerText = 'Rendered text content';
// 考虑样式,只获取可见文本

// innerHTML - 获取/设置元素的HTML内容
element.innerHTML = '<strong>New HTML content</strong>';
// 可以插入HTML,但需要注意XSS安全问题

安全地插入HTML

javascript
// 防止XSS攻击的方法
function safeInsertHTML(element, htmlString) {
  const tempDiv = document.createElement('div');
  tempDiv.textContent = htmlString; // 先转义
  element.innerHTML = tempDiv.innerHTML;
}

// 或使用DOMPurify等库进行净化
// const cleanHTML = DOMPurify.sanitize(dirtyHTML);
// element.innerHTML = cleanHTML;

操作元素属性

标准属性操作

javascript
const img = document.querySelector('img');

// getAttribute - 获取属性值
const src = img.getAttribute('src');
const alt = img.getAttribute('alt');

// setAttribute - 设置属性值
img.setAttribute('src', 'new-image.jpg');
img.setAttribute('alt', 'New description');

// removeAttribute - 移除属性
img.removeAttribute('title');

// 检查属性是否存在
if (img.hasAttribute('data-loaded')) {
  console.log('Image is loaded');
}

布尔属性

javascript
const checkbox = document.querySelector('input[type="checkbox"]');

// 对于布尔属性,存在即为true
checkbox.setAttribute('checked', ''); // 或 'checked' 或 true
checkbox.removeAttribute('checked'); // 移除则为false

dataset API(自定义数据属性)

javascript
const element = document.querySelector('.myElement');

// 设置data属性
element.dataset.userId = '123';
element.dataset.userName = 'john';

// 获取data属性
const userId = element.dataset.userId; // '123'

// 对应HTML: <div data-user-id="123" data-user-name="john">

classList API

javascript
const element = document.querySelector('.myElement');

// 添加类
element.classList.add('new-class');

// 移除类
element.classList.remove('old-class');

// 切换类
element.classList.toggle('active');

// 替换类
element.classList.replace('old-class', 'new-class');

// 检查是否有类
if (element.classList.contains('active')) {
  console.log('Element is active');
}

// 列出所有类
const classes = Array.from(element.classList); // 转换为数组

// 多个类操作(ES6)
element.classList.add('class1', 'class2', 'class3');

创建和操作元素

创建元素

javascript
// 创建新元素
const newDiv = document.createElement('div');
newDiv.textContent = 'This is a new div';

// 设置属性
newDiv.id = 'newDiv';
newDiv.className = 'container highlight';

// 创建带HTML内容的元素
const listItem = document.createElement('li');
listItem.innerHTML = '<strong>Item content</strong>';

// 创建文本节点
const textNode = document.createTextNode('Plain text');

添加元素到文档

javascript
const container = document.querySelector('#container');

// appendChild - 添加到末尾
container.appendChild(newDiv);

// insertBefore - 插入到指定位置
const referenceNode = container.firstChild;
container.insertBefore(newDiv, referenceNode);

// insertAdjacentElement - 灵活的位置插入
element.insertAdjacentElement('beforebegin', newElement);  // 元素前面
element.insertAdjacentElement('afterbegin', newElement);   // 元素内部开头
element.insertAdjacentElement('beforeend', newElement);    // 元素内部结尾
element.insertAdjacentElement('afterend', newElement);     // 元素后面

批量操作(文档片段)

javascript
// 使用文档片段提高性能
const fragment = document.createDocumentFragment();
const container = document.querySelector('#container');

for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}

// 一次性添加所有元素,只触发一次重排
container.appendChild(fragment);

DOM 遍历

父子关系遍历

javascript
const element = document.querySelector('.myElement');

// 父元素
const parent = element.parentNode;
const parentElement = element.parentElement; // 只返回元素节点

// 子元素
const children = element.children; // HTMLCollection,只包含元素节点
const childNodes = element.childNodes; // NodeList,包含所有节点类型
const firstChild = element.firstChild;
const lastChild = element.lastChild;

// 检查是否有子节点
if (element.hasChildNodes()) {
  console.log('Element has children');
}

兄弟关系遍历

javascript
const element = document.querySelector('.myElement');

// 下一个兄弟元素
const nextSibling = element.nextSibling; // 可能是任何节点类型
const nextElementSibling = element.nextElementSibling; // 只是元素节点

// 上一个兄弟元素
const previousSibling = element.previousSibling;
const previousElementSibling = element.previousElementSibling;

查询祖先元素

javascript
const element = document.querySelector('.myElement');

// closest - 查找最近的匹配祖先元素
const form = element.closest('form');
const container = element.closest('.container');

// 检查元素是否匹配选择器
if (element.matches('.special-class')) {
  console.log('Element has special class');
}

事件处理

事件监听器

javascript
const button = document.querySelector('#myButton');

// addEventListener - 推荐方法,可添加多个监听器
button.addEventListener('click', function(event) {
  console.log('Button clicked!');
  console.log(event.target); // 触发事件的元素
});

// 可以添加多个相同的事件监听器
button.addEventListener('click', function() {
  console.log('Second handler');
});

// 移除事件监听器(需要引用同一函数)
function handleClick() {
  console.log('Button clicked!');
}
button.addEventListener('click', handleClick);
// button.removeEventListener('click', handleClick);

事件对象

javascript
element.addEventListener('click', function(event) {
  // 事件基本信息
  console.log(event.type); // 'click'
  console.log(event.target); // 触发事件的实际元素
  console.log(event.currentTarget); // 当前处理事件的元素
  
  // 阻止默认行为(如链接跳转、表单提交)
  event.preventDefault();
  
  // 阻止事件冒泡
  event.stopPropagation();
  
  // 阻止事件的其他监听器执行
  event.stopImmediatePropagation();
  
  // 鼠标事件信息
  console.log(event.clientX, event.clientY); // 相对于视口的坐标
  console.log(event.button); // 鼠标按键(0=左键,1=中键,2=右键)
  
  // 键盘事件信息
  console.log(event.key); // 按键值
  console.log(event.code); // 物理按键码
  console.log(event.ctrlKey, event.shiftKey, event.altKey); // 修饰键状态
});

事件阶段

javascript
// 事件捕获阶段(从document到目标元素)
element.addEventListener('click', handler, true); // 第三个参数为true表示捕获阶段

// 事件冒泡阶段(从目标元素到document)
element.addEventListener('click', handler, false); // 或不传第三个参数

// 完整事件流:捕获 -> 目标 -> 冒泡
document.addEventListener('click', function(e) {
  console.log('Document (capture)', e.eventPhase); // 1 (捕获阶段)
}, true);

element.addEventListener('click', function(e) {
  console.log('Element (target)', e.eventPhase); // 2 (目标阶段)
}, true);

element.addEventListener('click', function(e) {
  console.log('Element (bubble)', e.eventPhase); // 2 (目标阶段)
}, false);

document.addEventListener('click', function(e) {
  console.log('Document (bubble)', e.eventPhase); // 3 (冒泡阶段)
}, false);

事件委托

利用事件冒泡机制,在父元素上处理子元素事件:

javascript
// 事件委托 - 在父元素上监听子元素事件
document.querySelector('#list').addEventListener('click', function(event) {
  // 检查点击的是否是目标元素
  if (event.target.matches('li')) {
    console.log('List item clicked:', event.target.textContent);
  }
  
  // 或者检查更复杂的条件
  if (event.target.tagName === 'BUTTON' && event.target.classList.contains('delete')) {
    event.target.closest('li').remove();
  }
});

// 动态添加的元素也能响应事件(因为事件在父元素上处理)
const newItem = document.createElement('li');
newItem.textContent = 'Dynamically added item';
document.querySelector('#list').appendChild(newItem);
// 点击新添加的项也能触发上面的事件处理器

表单操作

获取和设置表单值

javascript
const form = document.querySelector('form');
const usernameInput = document.querySelector('input[name="username"]');
const messageTextarea = document.querySelector('textarea[name="message"]');
const agreeCheckbox = document.querySelector('input[type="checkbox"]');
const genderRadio = document.querySelector('input[name="gender"]:checked');
const countrySelect = document.querySelector('select[name="country"]');

// 获取值
console.log(usernameInput.value);
console.log(agreeCheckbox.checked);
console.log(countrySelect.value);

// 设置值
usernameInput.value = 'new username';
agreeCheckbox.checked = true;
countrySelect.value = 'us';

表单事件

javascript
const form = document.querySelector('form');
const input = document.querySelector('input[name="username"]');

// 输入事件
input.addEventListener('input', function() {
  console.log('Input changed:', this.value);
});

// 变化事件(失去焦点且值改变时触发)
input.addEventListener('change', function() {
  console.log('Value changed:', this.value);
});

// 表单提交事件
form.addEventListener('submit', function(event) {
  event.preventDefault(); // 阻止默认提交行为
  
  // 获取表单数据
  const formData = new FormData(form);
  for (let [name, value] of formData) {
    console.log(name, value);
  }
  
  // 或使用Object.fromEntries
  const data = Object.fromEntries(formData);
  console.log(data);
});

表单验证

javascript
function validateForm(form) {
  const inputs = form.querySelectorAll('input[required], select[required], textarea[required]');
  let isValid = true;
  
  inputs.forEach(input => {
    if (!input.value.trim()) {
      input.classList.add('error');
      isValid = false;
    } else {
      input.classList.remove('error');
    }
  });
  
  return isValid;
}

// 实时验证
document.querySelectorAll('input').forEach(input => {
  input.addEventListener('blur', function() {
    if (this.hasAttribute('required') && !this.value.trim()) {
      this.classList.add('error');
    } else {
      this.classList.remove('error');
    }
  });
});

样式操作

内联样式操作

javascript
const element = document.querySelector('.myElement');

// 设置单个样式属性
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.fontSize = '16px';

// 批量设置样式
element.style.cssText = 'color: red; background-color: blue; font-size: 16px';

// 设置多个样式(使用对象)
Object.assign(element.style, {
  color: 'green',
  backgroundColor: 'yellow',
  fontSize: '18px'
});

CSS类操作

javascript
const element = document.querySelector('.myElement');

// 使用classList(推荐)
element.classList.add('active', 'highlighted');
element.classList.remove('inactive');
element.classList.toggle('visible');
element.classList.replace('old-class', 'new-class');

// 检查类
if (element.classList.contains('active')) {
  // 元素有active类
}

CSS自定义属性(CSS变量)

javascript
const root = document.documentElement;

// 设置CSS变量
root.style.setProperty('--main-color', '#ff0000');

// 获取CSS变量
const mainColor = getComputedStyle(root).getPropertyValue('--main-color');

// 在元素上设置CSS变量
element.style.setProperty('--element-color', 'blue');

DOM 性能优化

减少重排和重绘

javascript
// 避免频繁的样式读写操作
const element = document.querySelector('.myElement');

// 不好的做法:频繁读写
element.style.left = '10px';
console.log(element.offsetLeft); // 触发重排
element.style.top = '10px';
console.log(element.offsetTop);  // 再次触发重排

// 好的做法:批量操作
element.style.cssText = 'left: 10px; top: 10px;';
const position = element.getBoundingClientRect(); // 一次性获取多个值
console.log(position.left, position.top);

使用文档片段

javascript
// 批量添加元素时使用文档片段
const fragment = document.createDocumentFragment();
const container = document.querySelector('#container');

for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}

// 一次性添加,只触发一次重排
container.appendChild(fragment);

事件委托优化

javascript
// 不好的做法:为每个元素添加事件监听器
document.querySelectorAll('.button').forEach(button => {
  button.addEventListener('click', handleClick);
});

// 好的做法:使用事件委托
document.addEventListener('click', function(event) {
  if (event.target.matches('.button')) {
    handleClick(event);
  }
});

现代DOM API

MutationObserver(监听DOM变化)

javascript
// 创建观察器实例
const observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log('DOM changed:', mutation.type);
    if (mutation.type === 'childList') {
      console.log('Added:', mutation.addedNodes);
      console.log('Removed:', mutation.removedNodes);
    }
  });
});

// 配置观察选项
const config = {
  childList: true, // 观察子节点变化
  subtree: true,   // 观察所有后代节点
  attributes: true, // 观察属性变化
  attributeFilter: ['class', 'style'] // 只观察特定属性
};

// 开始观察目标节点
const targetNode = document.body;
observer.observe(targetNode, config);

// 停止观察
// observer.disconnect();

Intersection Observer(元素可见性检测)

javascript
// 检测元素是否进入视口(常用于懒加载)
const observer = new IntersectionObserver(function(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 元素进入视口
      console.log('Element is visible');
      // 加载图片或执行其他操作
    }
  });
}, {
  threshold: 0.1 // 当10%的元素可见时触发
});

// 观察特定元素
observer.observe(document.querySelector('.lazy-image'));

这些现代API提供了更高效的方式来处理DOM操作,减少了手动监听和计算的工作量。