轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

  • TypeScript

  • Node.js

  • Vue.js

  • 工程化

  • 浏览器与Web API

    • HTML基础全景图
    • DOM操作与浏览器模型
      • 一、DOM 简介
        • DOM 树结构
        • 节点类型
      • 二、选择 DOM 元素
        • 基本选择方法
        • 现代选择器(推荐)
        • HTMLCollection vs NodeList
      • 三、操作 DOM 元素
        • 修改内容
        • 修改属性
        • 修改样式
        • 修改类名
      • 四、创建和插入元素
        • 创建元素
        • 插入元素
        • 删除元素
        • 克隆元素
      • 五、遍历 DOM 树
        • 父子关系
        • 兄弟关系
        • 遍历示例
      • 六、事件处理
        • 添加事件监听
        • 事件对象
        • 常用事件类型
        • 事件冒泡与捕获
        • 事件委托
        • 移除事件监听
        • 事件选项
      • 七、表单处理
        • 获取表单值
        • 表单验证
        • FormData API
      • 八、浏览器对象模型(BOM)
        • window 对象
        • location 对象
        • history 对象
        • navigator 对象
        • screen 对象
      • 九、定时器
        • setTimeout(延迟执行)
        • setInterval(重复执行)
        • requestAnimationFrame(动画)
      • 十、存储 API
        • localStorage(持久化存储)
        • sessionStorage(会话存储)
        • Cookie
      • 十一、实用技巧
        • 防抖(debounce)
        • 节流(throttle)
        • 懒加载图片
        • 检测元素可见性
        • 监听 DOM 变化
      • 十二、性能优化
        • 减少重排和重绘
        • 使用文档片段
        • 避免强制同步布局
      • 十三、总结
    • HTML&CSS历代版本新特性
  • 前端
  • 浏览器与Web API
轩辕李
2025-03-18
目录

DOM操作与浏览器模型

本文介绍 JavaScript 在浏览器环境中的核心能力:DOM(文档对象模型)操作和 BOM(浏览器对象模型)。这些 API 是前端开发的基础,掌握它们能够实现动态网页交互、用户界面操作和浏览器功能调用。

# 一、DOM 简介

DOM(Document Object Model,文档对象模型)是 HTML 和 XML 文档的编程接口。浏览器将 HTML 文档解析为一个树形结构,JavaScript 通过 DOM API 可以访问和操作这个树。

# DOM 树结构

<!DOCTYPE html>
<html>
  <head>
    <title>Example</title>
  </head>
  <body>
    <h1 id="title">Hello World</h1>
    <p class="text">This is a paragraph.</p>
  </body>
</html>

DOM 树表示:

Document
└── html
    ├── head
    │   └── title
    │       └── "Example"
    └── body
        ├── h1#title
        │   └── "Hello World"
        └── p.text
            └── "This is a paragraph."

# 节点类型

DOM 中的每个元素都是一个节点(Node),常见节点类型:

  • Element(元素节点):<div>、<p> 等 HTML 标签
  • Text(文本节点):标签内的文本内容
  • Comment(注释节点):<!-- 注释 -->
  • Document(文档节点):整个文档的根节点

# 二、选择 DOM 元素

# 基本选择方法

// 通过 ID 选择(返回单个元素)
const title = document.getElementById('title');

// 通过类名选择(返回 HTMLCollection)
const texts = document.getElementsByClassName('text');

// 通过标签名选择(返回 HTMLCollection)
const paragraphs = document.getElementsByTagName('p');

// 通过 name 属性选择(常用于表单)
const inputs = document.getElementsByName('username');

# 现代选择器(推荐)

// querySelector(返回第一个匹配元素)
const title = document.querySelector('#title');
const firstText = document.querySelector('.text');
const firstDiv = document.querySelector('div');

// querySelectorAll(返回所有匹配元素,NodeList)
const allTexts = document.querySelectorAll('.text');
const allDivs = document.querySelectorAll('div');

// 支持复杂 CSS 选择器
const link = document.querySelector('div > a.active[href^="https"]');

# HTMLCollection vs NodeList

// HTMLCollection(动态集合)
const divs = document.getElementsByTagName('div');
console.log(divs.length); // 假设为 3
document.body.appendChild(document.createElement('div'));
console.log(divs.length); // 自动变为 4

// NodeList(querySelectorAll 返回的是静态集合)
const divs2 = document.querySelectorAll('div');
console.log(divs2.length); // 假设为 4
document.body.appendChild(document.createElement('div'));
console.log(divs2.length); // 仍然是 4

// 转换为数组
const divsArray = Array.from(divs);
const divsArray2 = [...divs2];

# 三、操作 DOM 元素

# 修改内容

const div = document.querySelector('div');

// innerHTML(解析 HTML 标签)
div.innerHTML = '<strong>Bold Text</strong>';

// textContent(纯文本,不解析 HTML)
div.textContent = '<strong>Not Bold</strong>'; // 显示为字符串

// innerText(考虑样式,如 display: none)
div.innerText = 'Visible Text';

安全提示:使用 innerHTML 插入用户输入时,需防止 XSS 攻击。推荐使用 textContent 或 DOM 方法。

# 修改属性

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

// 标准属性
img.src = 'new-image.jpg';
img.alt = 'New Image';

// getAttribute / setAttribute
img.setAttribute('data-id', '123');
const dataId = img.getAttribute('data-id');

// 删除属性
img.removeAttribute('data-id');

// 自定义数据属性(dataset)
img.dataset.userId = '456'; // <img data-user-id="456">
console.log(img.dataset.userId); // '456'

# 修改样式

const box = document.querySelector('.box');

// 内联样式
box.style.color = 'red';
box.style.backgroundColor = 'blue'; // 驼峰命名
box.style.fontSize = '20px';

// 批量设置
box.style.cssText = 'color: red; background-color: blue; font-size: 20px;';

// 获取计算样式(包含继承和默认样式)
const styles = window.getComputedStyle(box);
console.log(styles.color); // 'rgb(255, 0, 0)'

# 修改类名

const div = document.querySelector('div');

// className(字符串)
div.className = 'box active';

// classList(推荐)
div.classList.add('highlight');
div.classList.remove('active');
div.classList.toggle('hidden'); // 切换(有则删除,无则添加)
div.classList.contains('box');  // true
div.classList.replace('box', 'container');

# 四、创建和插入元素

# 创建元素

// 创建元素节点
const div = document.createElement('div');
div.textContent = 'New Div';
div.className = 'box';

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

// 创建文档片段(性能优化)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    fragment.appendChild(li);
}
document.querySelector('ul').appendChild(fragment);

# 插入元素

const parent = document.querySelector('#parent');
const newDiv = document.createElement('div');

// appendChild(末尾添加)
parent.appendChild(newDiv);

// insertBefore(指定位置插入)
const referenceNode = document.querySelector('#child');
parent.insertBefore(newDiv, referenceNode);

// 现代方法(推荐)
parent.append(newDiv);           // 可添加多个节点或字符串
parent.prepend(newDiv);          // 开头插入
referenceNode.before(newDiv);    // 在参考节点之前插入
referenceNode.after(newDiv);     // 在参考节点之后插入
referenceNode.replaceWith(newDiv); // 替换节点

# 删除元素

const div = document.querySelector('div');

// 传统方法
div.parentNode.removeChild(div);

// 现代方法(推荐)
div.remove();

# 克隆元素

const original = document.querySelector('.original');

// 浅克隆(不包含子元素)
const clone1 = original.cloneNode(false);

// 深克隆(包含所有子元素)
const clone2 = original.cloneNode(true);

document.body.appendChild(clone2);

# 五、遍历 DOM 树

# 父子关系

const child = document.querySelector('.child');

// 父节点
child.parentNode;
child.parentElement; // 推荐

// 子节点
const parent = document.querySelector('.parent');
parent.childNodes;       // 所有节点(包括文本、注释)
parent.children;         // 仅元素节点(推荐)
parent.firstChild;       // 第一个节点(可能是文本节点)
parent.firstElementChild; // 第一个元素节点(推荐)
parent.lastElementChild;  // 最后一个元素节点

# 兄弟关系

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

// 兄弟节点
element.nextSibling;         // 下一个节点(可能是文本节点)
element.nextElementSibling;  // 下一个元素节点(推荐)
element.previousElementSibling; // 上一个元素节点

# 遍历示例

// 递归遍历所有元素
function traverseDOM(node) {
    console.log(node);
    for (let child of node.children) {
        traverseDOM(child);
    }
}
traverseDOM(document.body);

# 六、事件处理

# 添加事件监听

const button = document.querySelector('button');

// addEventListener(推荐)
button.addEventListener('click', function(event) {
    console.log('Button clicked!', event);
});

// 箭头函数
button.addEventListener('click', (event) => {
    console.log('Clicked!', event);
});

// 事件处理器属性(不推荐,只能绑定一个处理函数)
button.onclick = function() {
    console.log('Clicked!');
};

# 事件对象

button.addEventListener('click', (event) => {
    event.target;        // 触发事件的元素
    event.currentTarget; // 绑定事件的元素
    event.type;          // 事件类型('click')
    event.preventDefault(); // 阻止默认行为
    event.stopPropagation(); // 阻止事件冒泡
});

# 常用事件类型

// 鼠标事件
element.addEventListener('click', handler);       // 单击
element.addEventListener('dblclick', handler);    // 双击
element.addEventListener('mousedown', handler);   // 鼠标按下
element.addEventListener('mouseup', handler);     // 鼠标释放
element.addEventListener('mousemove', handler);   // 鼠标移动
element.addEventListener('mouseenter', handler);  // 鼠标进入(不冒泡)
element.addEventListener('mouseleave', handler);  // 鼠标离开(不冒泡)
element.addEventListener('mouseover', handler);   // 鼠标悬停(冒泡)

// 键盘事件
element.addEventListener('keydown', handler);     // 按键按下
element.addEventListener('keyup', handler);       // 按键释放
element.addEventListener('keypress', handler);    // 按键(已弃用)

// 表单事件
form.addEventListener('submit', handler);         // 表单提交
input.addEventListener('input', handler);         // 输入变化
input.addEventListener('change', handler);        // 值改变后失焦
input.addEventListener('focus', handler);         // 获得焦点
input.addEventListener('blur', handler);          // 失去焦点

// 文档事件
document.addEventListener('DOMContentLoaded', handler); // DOM 加载完成
window.addEventListener('load', handler);         // 页面完全加载
window.addEventListener('beforeunload', handler); // 页面卸载前

# 事件冒泡与捕获

// 事件流:捕获阶段 → 目标阶段 → 冒泡阶段

// 冒泡(默认)
parent.addEventListener('click', () => console.log('Parent'));
child.addEventListener('click', () => console.log('Child'));
// 点击 child:输出 Child → Parent

// 捕获(第三个参数为 true)
parent.addEventListener('click', () => console.log('Parent'), true);
child.addEventListener('click', () => console.log('Child'), true);
// 点击 child:输出 Parent → Child

// 阻止冒泡
child.addEventListener('click', (event) => {
    event.stopPropagation();
    console.log('Child');
});
// 点击 child:仅输出 Child

# 事件委托

利用事件冒泡,在父元素上监听子元素事件:

const ul = document.querySelector('ul');

// 为所有 li 添加点击事件(包括动态添加的)
ul.addEventListener('click', (event) => {
    if (event.target.tagName === 'LI') {
        console.log('Clicked:', event.target.textContent);
    }
});

// 动态添加 li,事件自动生效
const newLi = document.createElement('li');
newLi.textContent = 'New Item';
ul.appendChild(newLi);

# 移除事件监听

function handleClick() {
    console.log('Clicked!');
}

button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick); // 必须是同一个函数引用

// 匿名函数无法移除
button.addEventListener('click', () => console.log('Clicked!')); // 无法移除

# 事件选项

button.addEventListener('click', handler, {
    once: true,      // 只触发一次
    passive: true,   // 不调用 preventDefault(提升滚动性能)
    capture: false   // 冒泡阶段触发
});

# 七、表单处理

# 获取表单值

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

// 获取值
console.log(input.value);
console.log(select.value);
console.log(checkbox.checked); // true/false

# 表单验证

form.addEventListener('submit', (event) => {
    event.preventDefault(); // 阻止默认提交

    const username = form.elements.username.value;
    const email = form.elements.email.value;

    if (username.trim() === '') {
        alert('Username is required');
        return;
    }

    if (!/\S+@\S+\.\S+/.test(email)) {
        alert('Invalid email');
        return;
    }

    // 提交表单数据
    const formData = new FormData(form);
    console.log(Object.fromEntries(formData));
});

# FormData API

const formData = new FormData(form);

// 添加字段
formData.append('age', '30');

// 获取字段
formData.get('username');

// 遍历
for (let [key, value] of formData) {
    console.log(key, value);
}

// 发送到服务器
fetch('/api/submit', {
    method: 'POST',
    body: formData
});

# 八、浏览器对象模型(BOM)

BOM(Browser Object Model)提供与浏览器交互的对象和方法,核心是 window 对象。

# window 对象

// 窗口尺寸
window.innerWidth;   // 视口宽度(包含滚动条)
window.innerHeight;  // 视口高度
window.outerWidth;   // 浏览器窗口宽度
window.outerHeight;  // 浏览器窗口高度

// 滚动
window.scrollX;      // 水平滚动距离
window.scrollY;      // 垂直滚动距离
window.scrollTo(0, 100);         // 滚动到指定位置
window.scrollBy(0, 50);          // 相对滚动
element.scrollIntoView();        // 滚动到元素可见

// 打开/关闭窗口
const newWindow = window.open('https://example.com', '_blank');
newWindow.close();

// 对话框
alert('Alert message');
const confirmed = confirm('Are you sure?');
const userInput = prompt('Enter your name:');

# location 对象

// 当前 URL 信息
location.href;       // 完整 URL
location.protocol;   // 'https:'
location.host;       // 'example.com:8080'
location.hostname;   // 'example.com'
location.port;       // '8080'
location.pathname;   // '/path/to/page'
location.search;     // '?query=value'
location.hash;       // '#section'

// URL 操作
location.assign('https://example.com'); // 跳转(可后退)
location.replace('https://example.com'); // 替换(不可后退)
location.reload();   // 刷新页面

# history 对象

// 浏览历史操作
history.back();      // 后退
history.forward();   // 前进
history.go(-2);      // 后退两步
history.go(1);       // 前进一步

// 历史记录长度
history.length;

// HTML5 History API
history.pushState({ page: 1 }, 'Title', '/page1');
history.replaceState({ page: 2 }, 'Title', '/page2');

window.addEventListener('popstate', (event) => {
    console.log('State:', event.state);
});

# navigator 对象

// 浏览器信息
navigator.userAgent;      // 用户代理字符串
navigator.language;       // 语言('zh-CN')
navigator.languages;      // 语言列表
navigator.platform;       // 操作系统平台
navigator.onLine;         // 是否在线(true/false)

// 地理位置(需要用户授权)
navigator.geolocation.getCurrentPosition(
    (position) => {
        console.log(position.coords.latitude);
        console.log(position.coords.longitude);
    },
    (error) => console.error(error)
);

// 剪贴板(需要用户授权)
navigator.clipboard.writeText('Copied text');
navigator.clipboard.readText().then(text => console.log(text));

# screen 对象

screen.width;        // 屏幕宽度
screen.height;       // 屏幕高度
screen.availWidth;   // 可用宽度(排除任务栏)
screen.availHeight;  // 可用高度
screen.colorDepth;   // 颜色深度

# 九、定时器

# setTimeout(延迟执行)

// 1 秒后执行
const timeoutId = setTimeout(() => {
    console.log('Executed after 1 second');
}, 1000);

// 取消定时器
clearTimeout(timeoutId);

// 传递参数
setTimeout((name) => {
    console.log(`Hello, ${name}`);
}, 1000, 'Alice');

# setInterval(重复执行)

// 每秒执行一次
const intervalId = setInterval(() => {
    console.log('Executed every second');
}, 1000);

// 5 秒后停止
setTimeout(() => {
    clearInterval(intervalId);
}, 5000);

# requestAnimationFrame(动画)

function animate() {
    // 动画逻辑
    box.style.left = parseInt(box.style.left || 0) + 1 + 'px';
    
    // 递归调用(约 60fps)
    requestAnimationFrame(animate);
}

const animationId = requestAnimationFrame(animate);

// 取消动画
cancelAnimationFrame(animationId);

# 十、存储 API

# localStorage(持久化存储)

// 设置
localStorage.setItem('username', 'Alice');

// 获取
const username = localStorage.getItem('username');

// 删除
localStorage.removeItem('username');

// 清空
localStorage.clear();

// 存储对象
const user = { name: 'Alice', age: 30 };
localStorage.setItem('user', JSON.stringify(user));
const storedUser = JSON.parse(localStorage.getItem('user'));

# sessionStorage(会话存储)

// API 与 localStorage 相同,但仅在当前会话有效
sessionStorage.setItem('token', 'abc123');
const token = sessionStorage.getItem('token');

# Cookie

// 设置 Cookie
document.cookie = 'username=Alice; max-age=3600; path=/';

// 读取 Cookie
console.log(document.cookie); // 'username=Alice; other=value'

// 删除 Cookie(设置过期时间为过去)
document.cookie = 'username=; max-age=0';

# 十一、实用技巧

# 防抖(debounce)

function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

// 使用场景:搜索输入
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce((event) => {
    console.log('Search:', event.target.value);
}, 500));

# 节流(throttle)

function throttle(func, delay) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastTime >= delay) {
            lastTime = now;
            func.apply(this, args);
        }
    };
}

// 使用场景:滚动监听
window.addEventListener('scroll', throttle(() => {
    console.log('Scrolled:', window.scrollY);
}, 200));

# 懒加载图片

const images = document.querySelectorAll('img[data-src]');

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.unobserve(img);
        }
    });
});

images.forEach(img => observer.observe(img));

# 检测元素可见性

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            console.log('Element is visible');
        } else {
            console.log('Element is hidden');
        }
    });
});

observer.observe(document.querySelector('.target'));

# 监听 DOM 变化

const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
        console.log('DOM changed:', mutation);
    });
});

observer.observe(document.body, {
    childList: true,     // 监听子节点变化
    attributes: true,    // 监听属性变化
    subtree: true        // 监听所有后代节点
});

// 停止监听
observer.disconnect();

# 十二、性能优化

# 减少重排和重绘

// ❌ 糟糕:多次修改样式导致多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';

// ✅ 推荐:批量修改
element.style.cssText = 'width: 100px; height: 100px; background-color: red;';

// ✅ 推荐:使用类名
element.classList.add('box-style');

# 使用文档片段

// ❌ 糟糕:每次添加都触发重排
for (let i = 0; i < 1000; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    ul.appendChild(li);
}

// ✅ 推荐:使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    fragment.appendChild(li);
}
ul.appendChild(fragment);

# 避免强制同步布局

// ❌ 糟糕:读取布局属性后立即修改样式
element.style.width = '100px';
const height = element.offsetHeight; // 触发重排
element.style.height = height + 'px';

// ✅ 推荐:先读取,再批量修改
const height = element.offsetHeight;
element.style.width = '100px';
element.style.height = height + 'px';

# 十三、总结

DOM 和 BOM 是前端开发的核心 API,掌握以下要点:

  • DOM 操作:选择、创建、修改、删除元素
  • 事件处理:addEventListener、事件冒泡、事件委托
  • 表单处理:获取值、验证、FormData
  • BOM:window、location、history、navigator
  • 存储:localStorage、sessionStorage、Cookie
  • 性能优化:减少重排重绘、使用文档片段、事件委托

配合 JavaScript 核心语法(详见JavaScript极简入门),即可构建功能完善的动态网页应用。

祝你变得更强!

编辑 (opens new window)
#JavaScript#DOM#BOM
上次更新: 2025/11/07
HTML基础全景图
HTML&CSS历代版本新特性

← HTML基础全景图 HTML&CSS历代版本新特性→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式