Skip to content

DOM操作

DOM(Document Object Model,文档对象模型)是 JavaScript 操作网页的接口。通过 DOM,JavaScript 可以访问和修改网页的内容、结构和样式。本章将详细介绍 DOM 操作的各种方法。

DOM 简介

什么是 DOM

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>DOM 示例</title>
</head>
<body>
    <div id="container">
        <h1 class="title">标题</h1>
        <p>段落内容</p>
        <ul>
            <li>列表项 1</li>
            <li>列表项 2</li>
        </ul>
    </div>
    
    <script>
        // DOM 将 HTML 文档表示为一个树形结构
        // 每个HTML元素都是树中的一个节点
        
        // document 对象是整个文档的入口
        console.log(document);  // 整个 HTML 文档
        console.log(document.documentElement);  // <html> 元素
        console.log(document.head);   // <head> 元素
        console.log(document.body);   // <body> 元素
    </script>
</body>
</html>

DOM 节点类型

javascript
// DOM 节点类型
// Element(元素节点):HTML 元素,如 <div>、<p>
// Text(文本节点):元素中的文本内容
// Attribute(属性节点):元素的属性
// Comment(注释节点):HTML 注释
// Document(文档节点):整个文档

// 获取节点类型
let element = document.getElementById('container');
console.log(element.nodeType);  // 1(Element 节点)
console.log(element.nodeName);  // 'DIV'

// 节点类型常量
console.log(Node.ELEMENT_NODE);      // 1
console.log(Node.TEXT_NODE);         // 3
console.log(Node.COMMENT_NODE);      // 8
console.log(Node.DOCUMENT_NODE);     // 9

获取元素

通过 ID 获取

javascript
// getElementById():通过 ID 获取单个元素
let container = document.getElementById('container');
console.log(container);

// 如果 ID 不存在,返回 null
let notExist = document.getElementById('not-exist');
console.log(notExist);  // null

// ID 是唯一的,只返回第一个匹配的元素

通过标签名获取

javascript
// getElementsByTagName():通过标签名获取元素集合
let paragraphs = document.getElementsByTagName('p');
console.log(paragraphs);  // HTMLCollection

// 遍历
for (let i = 0; i < paragraphs.length; i++) {
    console.log(paragraphs[i]);
}

// 转换为数组
let arr = Array.from(paragraphs);
// 或
let arr2 = [...paragraphs];

// 在特定元素内查找
let container = document.getElementById('container');
let items = container.getElementsByTagName('li');

通过类名获取

javascript
// getElementsByClassName():通过类名获取元素集合
let titles = document.getElementsByClassName('title');
console.log(titles);  // HTMLCollection

// 多个类名(空格分隔)
let elements = document.getElementsByClassName('class1 class2');

// 在特定元素内查找
let container = document.getElementById('container');
let items = container.getElementsByClassName('item');

通过选择器获取

javascript
// querySelector():获取匹配选择器的第一个元素
let firstParagraph = document.querySelector('p');
let titleElement = document.querySelector('.title');
let container = document.querySelector('#container');
let firstItem = document.querySelector('ul li:first-child');

// querySelectorAll():获取匹配选择器的所有元素
let allParagraphs = document.querySelectorAll('p');
let allItems = document.querySelectorAll('ul li');
let allTitles = document.querySelectorAll('.title');

// NodeList 可以使用 forEach
allParagraphs.forEach(p => {
    console.log(p.textContent);
});

// 复杂选择器
let element = document.querySelector('div.container > p.active');
let inputs = document.querySelectorAll('input[type="text"]');

特殊元素获取

javascript
// 获取 html 元素
let html = document.documentElement;

// 获取 head 元素
let head = document.head;

// 获取 body 元素
let body = document.body;

// 获取所有表单
let forms = document.forms;

// 获取所有链接
let links = document.links;

// 获取所有图片
let images = document.images;

// 获取当前焦点元素
let activeElement = document.activeElement;

// 获取或设置文档标题
console.log(document.title);
document.title = '新标题';

操作元素内容

innerHTML

javascript
let div = document.getElementById('container');

// 获取 HTML 内容
console.log(div.innerHTML);

// 设置 HTML 内容
div.innerHTML = '<p>新的内容</p>';

// 追加内容
div.innerHTML += '<p>追加的内容</p>';

// 清空内容
div.innerHTML = '';

// 注意:innerHTML 有安全风险(XSS攻击)
// 不要直接插入用户输入的内容
let userInput = '<script>alert("XSS")</script>';
// div.innerHTML = userInput;  // 危险!

textContent

javascript
let div = document.getElementById('container');

// 获取文本内容(包括后代元素的文本)
console.log(div.textContent);

// 设置文本内容(会转义 HTML 标签)
div.textContent = '<p>这不会变成标签</p>';
// 结果:<p>这不会变成标签</p>(纯文本显示)

// textContent 比 innerHTML 更安全
// 适合显示用户输入的内容
div.textContent = userInput;  // 安全

innerText

javascript
let div = document.getElementById('container');

// innerText 与 textContent 类似
// 但 innerText 会考虑 CSS 样式(如 display: none)

// 考虑样式
console.log(div.innerText);  // 不包含隐藏元素的文本
console.log(div.textContent);  // 包含所有文本

// 性能:textContent 更快

outerHTML

javascript
let div = document.getElementById('container');

// 获取元素及其内容的 HTML
console.log(div.outerHTML);
// <div id="container">内容</div>

// 替换整个元素
div.outerHTML = '<section id="new">新内容</section>';
// 原来的 div 被替换为 section

操作元素属性

标准属性

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

// 获取属性
console.log(img.src);
console.log(img.alt);
console.log(img.id);
console.log(img.className);  // 注意:是 className 不是 class

// 设置属性
img.src = 'new-image.jpg';
img.alt = '新图片';
img.id = 'new-id';
img.className = 'new-class';

// 表单元素
let input = document.querySelector('input');
console.log(input.value);    // 输入值
console.log(input.type);     // 输入类型
console.log(input.checked);  // 是否选中(checkbox/radio)
console.log(input.disabled); // 是否禁用

input.value = '新值';
input.checked = true;
input.disabled = true;

getAttribute 和 setAttribute

javascript
let link = document.querySelector('a');

// 获取属性
console.log(link.getAttribute('href'));
console.log(link.getAttribute('target'));
console.log(link.getAttribute('data-id'));  // 自定义属性

// 设置属性
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');
link.setAttribute('title', '点击访问');

// 移除属性
link.removeAttribute('target');

// 检查属性是否存在
console.log(link.hasAttribute('href'));  // true

// 获取所有属性
let attrs = link.attributes;
for (let attr of attrs) {
    console.log(attr.name, attr.value);
}

data-* 自定义属性

html
<div id="user" data-id="1001" data-user-name="张三" data-age="25">
    用户信息
</div>
javascript
let user = document.getElementById('user');

// 使用 dataset 访问 data-* 属性
console.log(user.dataset.id);        // '1001'
console.log(user.dataset.userName);  // '张三'(驼峰命名)
console.log(user.dataset.age);       // '25'

// 设置 data-* 属性
user.dataset.id = '1002';
user.dataset.city = '北京';  // 添加新属性

// 删除 data-* 属性
delete user.dataset.age;

// 使用 getAttribute/setAttribute
console.log(user.getAttribute('data-id'));  // '1002'
user.setAttribute('data-role', 'admin');

操作元素样式

style 属性

javascript
let div = document.querySelector('div');

// 获取内联样式
console.log(div.style.color);
console.log(div.style.backgroundColor);  // 驼峰命名

// 设置内联样式
div.style.color = 'red';
div.style.backgroundColor = '#f0f0f0';
div.style.fontSize = '20px';
div.style.marginTop = '10px';
div.style.display = 'none';

// 设置多个样式
div.style.cssText = 'color: red; font-size: 20px; margin: 10px;';

// 清除样式
div.style.color = '';
// 或
div.removeAttribute('style');

className 和 classList

javascript
let div = document.querySelector('div');

// className:获取/设置类名
console.log(div.className);  // 'box active'
div.className = 'new-class';
div.className += ' another-class';  // 追加类名

// classList:更方便的操作方式
console.log(div.classList);  // DOMTokenList

// 添加类
div.classList.add('active');
div.classList.add('class1', 'class2');  // 添加多个

// 移除类
div.classList.remove('active');
div.classList.remove('class1', 'class2');

// 切换类(有则移除,无则添加)
div.classList.toggle('active');
div.classList.toggle('active', true);  // 强制添加
div.classList.toggle('active', false); // 强制移除

// 检查是否包含类
console.log(div.classList.contains('active'));  // true/false

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

// 遍历类名
div.classList.forEach(className => {
    console.log(className);
});

获取计算样式

javascript
let div = document.querySelector('div');

// getComputedStyle():获取最终应用的样式
let style = window.getComputedStyle(div);
console.log(style.color);
console.log(style.backgroundColor);
console.log(style.width);
console.log(style.fontSize);

// 获取特定伪元素的样式
let beforeStyle = window.getComputedStyle(div, '::before');
console.log(beforeStyle.content);

// 注意:getComputedStyle 返回的是只读对象
// style.color = 'red';  // 无效

创建和插入元素

创建元素

javascript
// createElement():创建元素节点
let div = document.createElement('div');
div.id = 'new-div';
div.className = 'box';
div.textContent = '新创建的 div';

// createTextNode():创建文本节点
let text = document.createTextNode('文本内容');

// createDocumentFragment():创建文档片段(用于批量操作)
let fragment = document.createDocumentFragment();

// createComment():创建注释节点
let comment = document.createComment('这是注释');

插入元素

javascript
let container = document.getElementById('container');
let newDiv = document.createElement('div');
newDiv.textContent = '新元素';

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

// insertBefore():插入到指定元素之前
let reference = document.getElementById('reference');
container.insertBefore(newDiv, reference);

// 插入到第一个子元素之前
container.insertBefore(newDiv, container.firstChild);

// append():添加多个节点到末尾 - ES2015
container.append(newDiv, '文本', anotherElement);

// prepend():添加到开头
container.prepend(newDiv);

// before():插入到当前元素之前
reference.before(newDiv);

// after():插入到当前元素之后
reference.after(newDiv);

// replaceWith():替换当前元素
reference.replaceWith(newDiv);

克隆元素

javascript
let original = document.getElementById('original');

// cloneNode():克隆元素
// 参数 false(默认):只克隆元素本身
// 参数 true:深度克隆,包括后代元素

let shallow = original.cloneNode();  // 浅克隆
let deep = original.cloneNode(true);  // 深克隆

// 注意:克隆的元素不会复制事件监听器
// 克隆的元素 id 会重复,需要修改
deep.id = 'cloned-element';

删除元素

javascript
let element = document.getElementById('to-remove');

// remove():删除元素本身
element.remove();

// removeChild():删除子元素
let parent = document.getElementById('parent');
let child = document.getElementById('child');
parent.removeChild(child);

// replaceChild():替换子元素
let newChild = document.createElement('div');
parent.replaceChild(newChild, child);

// 清空元素内容
parent.innerHTML = '';
// 或
while (parent.firstChild) {
    parent.removeChild(parent.firstChild);
}

遍历 DOM

父子关系

javascript
let element = document.getElementById('element');

// 父节点
console.log(element.parentNode);      // 父节点
console.log(element.parentElement);   // 父元素节点

// 子节点
console.log(element.childNodes);      // 所有子节点(包括文本节点)
console.log(element.children);        // 所有子元素节点

// 第一个/最后一个子节点
console.log(element.firstChild);       // 第一个子节点(可能是文本节点)
console.log(element.firstElementChild); // 第一个子元素
console.log(element.lastChild);        // 最后一个子节点
console.log(element.lastElementChild); // 最后一个子元素

// 子节点数量
console.log(element.childNodes.length);
console.log(element.children.length);
console.log(element.childElementCount); // 子元素数量

兄弟关系

javascript
let element = document.getElementById('element');

// 下一个兄弟节点
console.log(element.nextSibling);          // 下一个兄弟节点(可能是文本节点)
console.log(element.nextElementSibling);   // 下一个兄弟元素

// 上一个兄弟节点
console.log(element.previousSibling);          // 上一个兄弟节点
console.log(element.previousElementSibling);   // 上一个兄弟元素

// 遍历所有兄弟元素
let sibling = element.nextElementSibling;
while (sibling) {
    console.log(sibling);
    sibling = sibling.nextElementSibling;
}

遍历所有后代元素

javascript
let container = document.getElementById('container');

// getElementsByTagName()
let allDivs = container.getElementsByTagName('div');

// querySelectorAll()
let allElements = container.querySelectorAll('*');

// 递归遍历
function traverse(element, callback) {
    callback(element);
    for (let child of element.children) {
        traverse(child, callback);
    }
}

traverse(container, el => {
    console.log(el.tagName);
});

// TreeWalker API
let walker = document.createTreeWalker(
    container,
    NodeFilter.SHOW_ELEMENT,
    null,
    false
);

let node;
while (node = walker.nextNode()) {
    console.log(node.tagName);
}

元素尺寸和位置

offset 系列

javascript
let element = document.getElementById('element');

// offsetWidth / offsetHeight:元素可见宽高(包括 padding、border)
console.log(element.offsetWidth);
console.log(element.offsetHeight);

// offsetLeft / offsetTop:相对于 offsetParent 的偏移
console.log(element.offsetLeft);
console.log(element.offsetTop);

// offsetParent:最近的定位祖先元素
console.log(element.offsetParent);

client 系列

javascript
let element = document.getElementById('element');

// clientWidth / clientHeight:内容区域宽高(包括 padding,不包括 border、滚动条)
console.log(element.clientWidth);
console.log(element.clientHeight);

// clientLeft / clientTop:左边框/上边框宽度
console.log(element.clientLeft);
console.log(element.clientTop);

scroll 系列

javascript
let element = document.getElementById('element');

// scrollWidth / scrollHeight:内容实际宽高(包括溢出部分)
console.log(element.scrollWidth);
console.log(element.scrollHeight);

// scrollLeft / scrollTop:滚动位置
console.log(element.scrollLeft);
console.log(element.scrollTop);

// 设置滚动位置
element.scrollTop = 100;
element.scrollLeft = 50;

// 滚动到指定位置
element.scrollTo({
    top: 100,
    left: 0,
    behavior: 'smooth'  // 平滑滚动
});

// 滚动到指定元素
element.scrollIntoView({
    behavior: 'smooth',
    block: 'start'
});

getBoundingClientRect

javascript
let element = document.getElementById('element');

// getBoundingClientRect():获取元素相对于视口的位置和尺寸
let rect = element.getBoundingClientRect();
console.log(rect.top);     // 上边距视口顶部的距离
console.log(rect.bottom);  // 下边距视口顶部的距离
console.log(rect.left);    // 左边距视口左侧的距离
console.log(rect.right);   // 右边距视口左侧的距离
console.log(rect.width);   // 元素宽度
console.log(rect.height);  // 元素高度
console.log(rect.x);       // 等同于 left
console.log(rect.y);       // 等同于 top

// 检测元素是否在视口中
function isInViewport(element) {
    let rect = element.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= window.innerHeight &&
        rect.right <= window.innerWidth
    );
}

// 获取元素在文档中的绝对位置
function getAbsolutePosition(element) {
    let rect = element.getBoundingClientRect();
    return {
        top: rect.top + window.scrollY,
        left: rect.left + window.scrollX
    };
}

表单操作

获取表单元素

javascript
// 通过 name 属性获取
let form = document.forms['myForm'];
// 或
let form = document.forms.myForm;

// 通过索引获取
let firstForm = document.forms[0];

// 获取表单中的元素
let username = form.elements.username;
let password = form.elements['password'];
let submitBtn = form.elements.submitBtn;

// 通过索引获取
let firstInput = form.elements[0];

表单属性和方法

javascript
let form = document.getElementById('myForm');

// 表单属性
console.log(form.action);    // 提交地址
console.log(form.method);    // 提交方式
console.log(form.enctype);   // 编码类型
console.log(form.name);      // 表单名称

// 表单方法
form.submit();   // 提交表单
form.reset();    // 重置表单

// 表单元素属性
let input = document.getElementById('username');
console.log(input.value);     // 输入值
console.log(input.defaultValue);  // 默认值
console.log(input.type);      // 输入类型
console.log(input.name);      // 名称
console.log(input.disabled);  // 是否禁用
console.log(input.readOnly);  // 是否只读
console.log(input.required);  // 是否必填

// 设置值
input.value = '新值';
input.disabled = true;
input.focus();   // 获取焦点
input.blur();    // 失去焦点
input.select();  // 选中内容

表单验证

javascript
let form = document.getElementById('myForm');

// HTML5 验证属性
// required:必填
// pattern:正则验证
// min/max:最小/最大值
// minlength/maxlength:最小/最大长度
// type="email":邮箱格式
// type="url":URL 格式

// 检查表单有效性
form.addEventListener('submit', function(e) {
    if (!form.checkValidity()) {
        e.preventDefault();  // 阻止提交
        
        // 显示验证信息
        let invalidInputs = form.querySelectorAll(':invalid');
        invalidInputs.forEach(input => {
            console.log(input.name + ': ' + input.validationMessage);
        });
    }
});

// 手动验证
let input = document.getElementById('email');
console.log(input.validity.valid);        // 是否有效
console.log(input.validity.valueMissing); // 是否缺失必填值
console.log(input.validity.typeMismatch); // 类型是否匹配
console.log(input.validity.patternMismatch); // 是否匹配模式

// 设置自定义验证信息
input.setCustomValidity('请输入有效的邮箱地址');
if (input.validity.valid) {
    input.setCustomValidity('');  // 清除验证信息
}

// 报告验证结果
input.reportValidity();  // 显示验证提示

处理表单提交

javascript
let form = document.getElementById('myForm');

// 监听提交事件
form.addEventListener('submit', function(e) {
    e.preventDefault();  // 阻止默认提交
    
    // 获取表单数据
    let formData = new FormData(form);
    
    // 遍历数据
    for (let [key, value] of formData) {
        console.log(key, value);
    }
    
    // 获取特定字段
    let username = formData.get('username');
    let password = formData.get('password');
    
    // 获取多选值
    let hobbies = formData.getAll('hobby');
    
    // 转换为对象
    let data = Object.fromEntries(formData);
    console.log(data);
    
    // 转换为 JSON
    let json = JSON.stringify(data);
    
    // 发送 AJAX 请求
    fetch('/api/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: json
    })
    .then(response => response.json())
    .then(data => {
        console.log('成功:', data);
    });
});

小结

本章学习了 DOM 操作的核心知识:

  • DOM 简介:DOM 树结构、节点类型
  • 获取元素:getElementById、getElementsByTagName、getElementsByClassName、querySelector
  • 操作内容:innerHTML、textContent、innerText、outerHTML
  • 操作属性:标准属性、getAttribute/setAttribute、data-* 属性
  • 操作样式:style、className、classList、getComputedStyle
  • 创建插入:createElement、appendChild、insertBefore、append/prepend
  • 遍历 DOM:父子关系、兄弟关系
  • 尺寸位置:offset、client、scroll、getBoundingClientRect
  • 表单操作:获取表单、表单验证、处理提交

下一章我们将学习 事件处理,了解如何响应用户交互。