← 返回Web开发目录
← 返回学习笔记首页
DOM操作与事件编程
Web开发专题 · 掌握页面交互的核心技术
专题: JavaScript与Web前端开发
关键词: DOM操作, 事件处理, addEventListener, 事件委托, BOM, JavaScript
一、DOM概述
1.1 DOM的概念
DOM(Document Object Model,文档对象模型)是浏览器将HTML文档解析为树状结构后暴露给JavaScript的编程接口。它将网页中的每个元素都映射为一个对象,开发者可以通过操作这些对象来动态修改网页的内容、结构和样式。DOM是独立于平台和语言的,W3C将其标准化为推荐规范,目前主流浏览器均实现了DOM Level 1至Level 4的核心规范。
DOM的核心思想在于:它将一个HTML文档抽象为一棵节点树(Node Tree),每个节点代表文档中的一部分(元素、文本、属性、注释等)。通过这棵树,JavaScript可以任意遍历、查询和修改文档内容,这就是网页动态交互的根本原理。
1.2 DOM树结构
当浏览器加载一个HTML页面时,会解析HTML标签并构建一棵DOM树。树根是 document 节点,其下是唯一的 html 根元素,html 元素又包含 head 和 body 两个子节点,依此类推形成层级结构。每个HTML标签对应一个元素节点(Element Node),标签内的文本对应文本节点(Text Node),属性对应属性节点(Attribute Node,在DOM4中已归入元素节点)。
例如,对于以下HTML代码:<div id="app">Hello</div>,DOM树中会创建一个 div 元素节点,其 id 属性作为该元素节点的属性保存,其下有一个文本节点内容为"Hello"。理解这种树状结构是掌握后续所有DOM操作的基础。
1.3 document对象
document 对象是DOM树的入口点,也是浏览器提供给JavaScript操作网页的核心对象。通过 document 对象,开发者可以访问页面中的所有元素、创建新节点、修改内容和样式、注册事件监听等。document 对象是 window 对象的一个属性,但由于其重要性,在全局作用域中可以直接访问。
document 对象还提供了一些重要的子对象和集合,如 document.documentElement(即 <html> 元素)、document.head、document.body、document.forms(所有表单的集合)、document.links(所有链接的集合)等。掌握这些快捷属性可以大大简化DOM操作的代码量。
1.4 浏览器渲染过程
理解浏览器的渲染过程对于优化DOM操作至关重要。大致流程如下: ① HTML解析:浏览器将HTML字节流解析为DOM树(DOM Tree); ② CSS解析:同时解析CSS,构建CSSOM树(CSS Object Model); ③ 合并渲染树:将DOM树和CSSOM树结合为Render Tree(渲染树),只包含可见元素; ④ 布局(Layout):计算每个可见元素的几何位置和大小; ⑤ 绘制(Paint):将像素绘制到屏幕上。
当JavaScript修改DOM时,浏览器可能需要重新执行布局和绘制过程,这被称为回流(Reflow)或重绘(Repaint)。频繁的DOM操作会严重影响性能,因此需要采取批量更新、脱离文档流操作、使用DocumentFragment等优化策略。
二、DOM元素选择
2.1 getElementById()
document.getElementById(id) 是最直接、最高效的单个元素选择方法。它接收一个id字符串作为参数,返回匹配的第一个元素(理论上id应在页面中唯一)。如果找不到匹配元素,返回 null。这个方法在现代浏览器中的性能极佳,因为浏览器内部会维护一个id映射表,查找时间复杂度为O(1)。
// 基本用法
const header = document.getElementById('main-header');
if (header) {
header.style.color = 'blue';
}
2.2 getElementsByClassName() / getElementsByTagName()
document.getElementsByClassName(className) 根据类名选择元素,返回一个 HTMLCollection 实时集合。这意味着当DOM发生变化时,集合会自动更新。getElementsByTagName(tagName) 根据标签名选择元素,同样返回实时 HTMLCollection。两者的共同特点是返回的是"类数组"而非真正的数组,不能直接使用数组方法(如 forEach)。
一个重要的细节是:HTMLCollection 是实时的(live),这既是优点也是潜在的风险点。在循环中遍历并修改DOM时,可能会因为集合的自动更新导致意外的行为。建议在使用时将集合转换为数组:Array.from(collection) 或 [...collection]。
// 选择所有 class 为 "item" 的元素
const items = document.getElementsByClassName('item');
console.log(items.length); // 实时反映DOM状态
// 选择所有 <p> 元素
const paras = document.getElementsByTagName('p');
// 转换为真数组以便使用数组方法
const itemArray = Array.from(items);
itemArray.forEach(el => console.log(el.textContent));
2.3 querySelector() / querySelectorAll()
document.querySelector(selector) 和 document.querySelectorAll(selector) 是现代DOM编程中最推荐的选择器方法。它们接受任何有效的CSS选择器字符串作为参数,querySelector 返回匹配的第一个元素,querySelectorAll 返回所有匹配元素的静态 NodeList。NodeList 不是实时集合,不会随着DOM变化而自动更新。
querySelectorAll 返回的 NodeList 拥有 forEach 方法(现代浏览器中),但仍不是真正的数组。如果需要使用 map、filter 等高级数组方法,仍需先转换为数组:[...nodeList] 或 Array.from(nodeList)。此外,NodeList 也支持 length 属性和索引访问。
// 使用querySelector
const firstBtn = document.querySelector('.btn.primary');
const container = document.querySelector('#app > .content');
// 使用querySelectorAll
const allLinks = document.querySelectorAll('nav a[href^="http"]');
const evenRows = document.querySelectorAll('tr:nth-child(even)');
// NodeList可以forEach
allLinks.forEach(link => {
link.target = '_blank';
});
2.4 选择器性能对比
从性能角度比较:getElementById 最快(O(1)时间复杂度),其次是 getElementsByClassName 和 getElementsByTagName。querySelector 和 querySelectorAll 因为需要解析CSS选择器字符串并进行匹配,性能相对较慢,但在绝大多数实际场景中差异可以忽略不计。
建议的选型原则:如果需要根据id查找,优先使用 getElementById;如果需要批量操作且对实时性有要求,使用 getElementsByClassName;在大多数场景下,推荐使用 querySelector/querySelectorAll,因为它们选择能力最强、写法最灵活、语义最清晰。微小的性能差异在实际开发中很少成为瓶颈。
三、DOM操作
3.1 创建元素与文本节点
document.createElement(tagName) 用于创建新的元素节点,参数为要创建的标签名。新创建的元素还不在DOM树中,需要通过插入方法将其添加到文档中。document.createTextNode(text) 用于创建文本节点,通常 appendChild 可以直接接受字符串(浏览器会隐式创建文本节点),但在需要精细控制时仍会用到。
// 创建元素
const div = document.createElement('div');
div.id = 'new-div';
div.className = 'box';
// 创建文本节点并追加
const text = document.createTextNode('这是内容');
div.appendChild(text);
3.2 插入元素
插入元素有多种方法,每种适用于不同的场景。appendChild(child) 将子节点追加到父节点的末尾,是经典方法。insertBefore(newNode, referenceNode) 在参考节点之前插入新节点,若 referenceNode 为 null 则等同于 appendChild。现代方法 append() 和 prepend() 更为灵活,可以同时插入多个节点或字符串。
const parent = document.getElementById('list');
const newItem = document.createElement('li');
newItem.textContent = '新项目';
// 追加到末尾
parent.appendChild(newItem);
// 在第一个子元素前插入
parent.insertBefore(newItem, parent.firstChild);
// 现代方法:可插入多个
parent.append('文本', document.createElement('span'));
parent.prepend('前置内容');
3.3 删除元素
removeChild(child) 是传统删除方法,需要先获取父元素再调用。现代方法 remove() 更为简洁,元素可以自我删除,无需引用父节点。需要注意的是 remove() 方法在较老浏览器中不支持,但现代项目基本可以放心使用。
// 传统方式:需要父元素
const parent = document.getElementById('container');
const child = document.getElementById('to-remove');
parent.removeChild(child);
// 现代方式:元素自删除
document.getElementById('to-remove').remove();
3.4 替换与克隆元素
replaceChild(newChild, oldChild) 用新节点替换现有子节点,需要父元素调用。cloneNode(deep) 用于克隆元素,deep 参数为 true 时会执行深克隆(包括子节点),false 只克隆元素自身(不包含子节点和事件监听器)。
特别注意:cloneNode 默认(或不传参数)为浅克隆。克隆的元素不会复制事件监听器(通过 addEventListener 绑定的),但内联事件属性(如 onclick="...")会被复制。此外,克隆出的元素 id 也不会自动改变,需要注意避免 id 冲突。
// 替换
const parent = document.getElementById('parent');
const newSpan = document.createElement('span');
newSpan.textContent = '替换内容';
parent.replaceChild(newSpan, parent.children[0]);
// 克隆
const original = document.querySelector('.card');
const clone = original.cloneNode(true); // 深克隆
clone.id = 'card-clone'; // 避免id冲突
document.body.appendChild(clone);
3.6 innerHTML vs textContent
innerHTML 和 textContent 是读取和修改元素内容的两个主要属性,但它们的语义和行为有本质区别。innerHTML 将内容解析为HTML,可以包含标签和结构;textContent 将内容视为纯文本,会自动转义HTML标签。从安全性角度看,innerHTML 存在XSS(跨站脚本攻击)风险,如果设置的内容包含用户输入,攻击者可能注入恶意脚本。因此,当只是操作纯文本时,应始终优先使用 textContent。
从性能角度看,innerHTML 会触发浏览器的HTML解析器,同时由于它可能导致DOM子树完全重建,会带来更大的性能开销。textContent 仅修改文本节点,效率更高。当需要插入大量结构化HTML时,建议使用 createElement + appendChild 的方式替代 innerHTML,以保持事件监听器的绑定和更好的性能。
// innerHTML:解析HTML
const div = document.getElementById('content');
div.innerHTML = '<strong>加粗文字</strong>';
// textContent:纯文本,自动转义
div.textContent = '<strong>不会加粗</strong>';
// 安全实践:用户输入必须用textContent
const userInput = '<script>alert("xss")</script>';
// ❌ 危险
div.innerHTML = userInput;
// ✅ 安全
div.textContent = userInput;
四、DOM属性与样式
4.1 获取/设置/移除属性
getAttribute(name) 读取元素的HTML属性值,返回字符串或 null。setAttribute(name, value) 设置或更新属性值。removeAttribute(name) 移除指定属性。这些方法操作的是HTML属性(attribute),与元素对象的属性(property)有时并不完全一致。例如,input元素的 value 属性在使用 getAttribute('value') 获取的是HTML中的初始值,而直接访问 element.value 获取的是当前值。
对于自定义属性(data-*),现在更推荐使用 dataset API(见4.4节),而不是 getAttribute/setAttribute。但布尔类型的属性(如 disabled、checked、readonly)仍需通过 getAttribute/setAttribute 或直接访问元素的 property。
const link = document.querySelector('a');
// 读取属性
console.log(link.getAttribute('href'));
// 设置属性
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
// 移除属性
link.removeAttribute('disabled');
// attribute vs property的区别
const input = document.querySelector('input');
input.value = '新值';
console.log(input.getAttribute('value')); // HTML中的初始值
console.log(input.value); // 当前值
4.2 classList操作
classList 是元素上的一个 DOMTokenList 对象,提供了专门操作CSS类名的API。相比直接操作 className 字符串,classList 更加安全、简洁且不易出错。主要方法包括:add(className) 添加一个或多个类;remove(className) 移除一个或多个类;toggle(className, force) 切换类(返回布尔值表示操作后类是否存在);contains(className) 检查类是否存在。
classList 还支持多个类名同时操作:element.classList.add('a', 'b', 'c')。在条件切换场景下 toggle 非常实用,而且支持第二个可选参数 force(布尔值),可以强制添加或移除而不进行切换判断。
const el = document.getElementById('menu');
// 添加类
el.classList.add('active', 'visible');
// 移除类
el.classList.remove('hidden');
// 切换类
el.classList.toggle('open');
// 带条件的切换
el.classList.toggle('expanded', isExpanded);
// 检查类
if (el.classList.contains('active')) {
console.log('元素处于激活状态');
}
// 替代className繁琐操作
// ❌ 旧方式
el.className = el.className + ' new-class';
// ✅ 新方式
el.classList.add('new-class');
4.3 style属性与计算样式
element.style 属性是一个 CSSStyleDeclaration 对象,用于读写元素的内联样式(即HTML中 style 属性的内容)。通过 style 设置的样式优先级最高(仅次于 !important),适合需要动态改变的样式属性。注意属性名使用驼峰命名法(camelCase),如 backgroundColor 对应CSS中的 background-color。
getComputedStyle(element) 方法返回元素经过计算后的最终样式,包括内联样式、样式表中的规则和浏览器默认样式。返回的样式值是只读的,不能修改。这在需要读取元素实际渲染尺寸、颜色等参数时非常有用。
const box = document.getElementById('box');
// 设置内联样式
box.style.backgroundColor = '#3498db';
box.style.padding = '20px';
box.style.borderRadius = '8px';
box.style.cssFloat = 'left'; // float是保留字,用cssFloat
// 批量设置样式(效率较低)
Object.assign(box.style, {
color: '#fff',
fontSize: '16px',
marginTop: '10px'
});
// 获取计算样式
const styles = getComputedStyle(box);
console.log(styles.backgroundColor);
console.log(styles.fontSize);
console.log(styles.width);
4.4 data-* 自定义属性
HTML5的 data-* 属性允许在元素上存储自定义数据。通过元素的 dataset 属性可以方便地访问这些数据:dataset 会将 data- 后的属性名转换为驼峰命名(如 data-user-name 对应 dataset.userName)。dataset 的值始终是字符串类型,如果需要数值或布尔值,需要手动转换。
与 setAttribute/getAttribute 操作 data-* 属性相比,dataset API 更加直观和易读。但在性能敏感的循环中,setAttribute/getAttribute 可能略快。此外,dataset 是实时映射(live),修改 dataset 会同步到 HTML 属性,反之亦然。
<div id="user" data-user-id="123" data-role="admin" data-last-login="2026-05-01"></div>
<script>
const user = document.getElementById('user');
// 读取data属性
console.log(user.dataset.userId); // "123"
console.log(user.dataset.role); // "admin"
console.log(user.dataset.lastLogin); // "2026-05-01"
// 设置data属性
user.dataset.status = 'active';
// HTML中会新增: data-status="active"
// 删除data属性
delete user.dataset.role;
// HTML中会移除: data-role
</script>
五、事件处理
5.1 事件监听:addEventListener()
addEventListener(type, listener, options) 是现代事件绑定的标准方法。第一个参数是事件类型字符串(如 'click'、'mouseover'),第二个参数是回调函数,第三个可选参数可以是一个配置对象或布尔值。相比传统 onclick 属性赋值的方式,addEventListener 可以为同一元素的同一事件绑定多个处理函数,且更灵活地控制事件行为。
第三个参数 options 对象支持以下属性:capture(是否在捕获阶段触发)、once(是否只执行一次后自动移除)、passive(是否不调用 preventDefault,用于提升滚动性能)。其中 passive 在滚动事件监听中非常有用,可以显著提高滚动的流畅度。
const btn = document.getElementById('myBtn');
// 基本用法
btn.addEventListener('click', function(event) {
console.log('按钮被点击了');
});
// 箭头函数
btn.addEventListener('click', (e) => {
console.log(e.target);
});
// options:只执行一次,在捕获阶段触发
btn.addEventListener('click', handler, { once: true, capture: true });
// passive:优化滚动性能
document.addEventListener('touchstart', handler, { passive: true });
5.2 事件移除:removeEventListener()
removeEventListener(type, listener, options) 用于移除通过 addEventListener 绑定的事件监听。需要注意:移除时必须传入与绑定时完全相同的函数引用。如果绑定时使用了匿名函数或箭头函数,将无法移除该事件监听,因为无法获取相同的函数引用。因此,需要动态移除的事件监听,必须使用命名函数或保存函数引用。
// ✅ 可以移除:命名函数
function handleClick(e) {
console.log('点击');
}
btn.addEventListener('click', handleClick);
btn.removeEventListener('click', handleClick);
// ❌ 无法移除:匿名函数
btn.addEventListener('click', function(e) {
console.log('点击');
});
btn.removeEventListener('click', function(e) { // 新的函数,不是同一个引用
console.log('点击');
}); // 移除失败!
5.3 事件对象(event)
当事件发生时,浏览器会创建一个事件对象作为参数传递给事件处理函数。不同事件类型的事件对象有所不同,但都继承自 Event 基类。常用属性和方法包括: · type:事件类型字符串(如 'click'); · target:触发事件的原始元素(实际点击的目标); · currentTarget:绑定事件处理器的元素(与 this 相同); · preventDefault():阻止默认行为; · stopPropagation():阻止事件传播; · stopImmediatePropagation():阻止事件传播并阻止同一元素上的其他同类事件处理函数执行。
document.addEventListener('click', function(e) {
console.log('事件类型:', e.type); // "click"
console.log('触发元素:', e.target); // 实际点击的元素
console.log('绑定元素:', e.currentTarget); // document
console.log('事件阶段:', e.eventPhase); // 1捕获 2目标 3冒泡
console.log('时间戳:', e.timeStamp);
console.log('鼠标坐标:', e.clientX, e.clientY);
});
5.4 常见事件类型
浏览器支持大量事件类型,可分为以下几大类别: ① 鼠标事件:click、dblclick、mousedown、mouseup、mouseover、mouseout、mousemove、mouseenter、mouseleave(后两者不冒泡); ② 键盘事件:keydown(按下任意键)、keypress(已废弃)、keyup(释放键); ③ 表单事件:submit、reset、change、input、focus、blur、focusin、focusout; ④ 文档/窗口事件:load、DOMContentLoaded、beforeunload、unload、scroll、resize、hashchange; ⑤ 触摸/移动事件:touchstart、touchmove、touchend、touchcancel。
其中 DOMContentLoaded 事件在HTML文档被完全加载和解析后触发(不需要等待样式表、图片和子框架完成加载),而 load 事件需要等待所有资源加载完成。理解两者的区别对性能优化至关重要。
5.5 事件捕获与冒泡
事件流(Event Flow)描述了事件从页面中接收的顺序,W3C标准将事件流分为三个阶段: ① 捕获阶段(Capture Phase):事件从 window 对象开始,沿着DOM树向下传播,直到到达目标元素的父节点; ② 目标阶段(Target Phase):事件到达目标元素本身; ③ 冒泡阶段(Bubble Phase):事件从目标元素开始,沿着DOM树向上传播,直到 window 对象。
默认情况下,所有事件处理都在冒泡阶段触发。可以通过 addEventListener 的第三个参数设置为 true 来让事件在捕获阶段触发。利用事件冒泡机制可以实现事件委托(下一节详述),但有时也需要阻止冒泡以避免不期望的父级事件触发。
<div id="outer">
<div id="inner">
<button id="btn">点击</button>
</div>
</div>
<script>
// 捕获阶段监听
document.getElementById('outer').addEventListener('click', () => {
console.log('outer 捕获');
}, true);
// 冒泡阶段监听(默认)
document.getElementById('outer').addEventListener('click', () => {
console.log('outer 冒泡');
});
document.getElementById('inner').addEventListener('click', () => {
console.log('inner 冒泡');
});
document.getElementById('btn').addEventListener('click', (e) => {
console.log('btn 目标阶段');
// e.stopPropagation(); // 取消注释可阻止冒泡
});
// 点击按钮时的输出顺序:
// "outer 捕获" → "btn 目标阶段" → "inner 冒泡" → "outer 冒泡"
</script>
5.6 事件委托(Event Delegation)
事件委托是利用事件冒泡机制的一种优化模式。核心思想是:不在每个子元素上单独绑定事件监听,而是将监听器绑定在共同的父元素上,通过事件对象的 target 属性判断实际触发的子元素。这带来了两大好处: ① 减少内存占用:只需一个监听器代替多个监听器; ② 动态元素支持:新添加的子元素自动继承事件处理能力,无需重新绑定。
事件委托的典型应用场景包括:列表项点击、表格行操作、动态生成的菜单按钮等。实现时需要判断 target 是否匹配目标选择器,可以使用 Element.matches(selector) 方法进行检测。
// ❌ 不好的做法:为每个列表项绑定事件
document.querySelectorAll('.list-item').forEach(item => {
item.addEventListener('click', () => {
console.log(item.textContent);
});
});
// ✅ 事件委托:父元素统一处理
document.getElementById('item-list').addEventListener('click', (e) => {
const target = e.target.closest('.list-item');
if (target) {
console.log(target.dataset.id, target.textContent);
}
});
// 更通用的委托模式
function delegate(parent, selector, eventType, handler) {
parent.addEventListener(eventType, function(e) {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e);
}
});
}
delegate(document.getElementById('list'), '.item', 'click', function(e) {
console.log('点击了:', this.textContent);
});
5.7 阻止默认行为与事件传播
preventDefault() 阻止元素的默认行为。例如:阻止链接跳转、阻止表单提交、阻止右键菜单弹出等。stopPropagation() 阻止事件在DOM树中的进一步传播(包括捕获和冒泡阶段)。stopImmediatePropagation() 更强,除了阻止传播,还会阻止当前元素上后续事件监听器的执行。
使用建议:preventDefault 是安全常用的操作,但应谨慎使用 stopPropagation。过度阻止事件传播可能破坏页面中其他依赖于事件冒泡的功能(如分析统计、全局点击处理等)。更好的做法是使用自定义事件标识或状态判断来区分事件来源。
// 阻止链接跳转
document.querySelectorAll('a[href="#"]').forEach(link => {
link.addEventListener('click', e => e.preventDefault());
});
// 阻止表单提交(用于AJAX表单)
document.getElementById('ajax-form').addEventListener('submit', e => {
e.preventDefault();
// 通过fetch/XMLHttpRequest提交
const formData = new FormData(e.target);
fetch('/api/submit', { method: 'POST', body: formData });
});
// 阻止右键菜单
document.addEventListener('contextmenu', e => {
e.preventDefault();
showCustomContextMenu(e.clientX, e.clientY);
});
六、BOM浏览器对象模型
6.1 window对象
BOM(Browser Object Model,浏览器对象模型)提供了与浏览器窗口交互的接口,其核心是 window 对象。window 对象在浏览器中扮演着全局对象(Global Object)的角色:所有全局变量和全局函数都是 window 的属性和方法。window 还提供了控制浏览器窗口的方法,如 open()(打开新窗口)、close()(关闭窗口)、resizeTo()(调整窗口大小)、moveTo()(移动窗口位置)、scrollTo()(滚动到指定位置)等。
常见的窗口尺寸属性包括:window.innerWidth/innerHeight(视口宽高,包含滚动条)、window.outerWidth/outerHeight(浏览器窗口总宽高)、window.screenX/screenY(窗口相对于屏幕左上角的偏移)。这些属性在实现响应式布局和弹窗定位时非常实用。
// 窗口尺寸
console.log('视口尺寸:', window.innerWidth, 'x', window.innerHeight);
console.log('屏幕尺寸:', window.screen.width, 'x', window.screen.height);
// 滚动
window.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollBy({ top: 100, left: 0, behavior: 'smooth' });
// 定时器
const timeoutId = setTimeout(() => {
console.log('2秒后执行');
}, 2000);
const intervalId = setInterval(() => {
console.log('每秒执行');
}, 1000);
// 清除定时器
clearTimeout(timeoutId);
clearInterval(intervalId);
// 弹窗(谨慎使用)
// alert('警告');
// const confirmed = confirm('确定吗?');
// const name = prompt('请输入姓名:');
6.2 location对象
window.location 对象(或简写为 location)提供了当前URL的详细信息以及导航控制能力。其核心属性包括:href(完整URL)、protocol(协议,如 https:)、host(主机名+端口)、hostname(主机名)、port(端口号)、pathname(路径部分)、search(查询参数,含?)、hash(哈希部分,含#)、origin(来源,只读)。
location 对象还提供了导航方法:location.assign(url) 加载新文档(可回退)、location.replace(url) 替换当前文档(不可回退)、location.reload(force) 重新加载当前页面(force为true时强制从服务器加载而非缓存)。
// 获取当前URL信息
console.log(location.href); // https://example.com/page?id=1#section
console.log(location.protocol); // https:
console.log(location.host); // example.com
console.log(location.pathname); // /page
console.log(location.search); // ?id=1
console.log(location.hash); // #section
// URL参数解析
function getQueryParams() {
const params = new URLSearchParams(location.search);
return Object.fromEntries(params.entries());
}
console.log(getQueryParams()); // { id: '1' }
// 页面跳转
// location.href = 'https://other.com'; // 方式一
// location.assign('https://other.com'); // 方式二
// location.replace('https://other.com'); // 方式三:不会留下历史记录
// 页面刷新
// location.reload(); // 可能从缓存加载
// location.reload(true); // 强制从服务器加载
6.3 history对象
window.history 对象提供了对浏览器会话历史(当前标签页的访问记录)的访问能力。主要方法包括:history.back()(相当于用户点击后退按钮)、history.forward()(前进)、history.go(delta)(相对当前页面跳转指定步数,正数前进、负数后退)。history.length 属性表示当前会话历史中的页面数量。
在现代单页应用(SPA)开发中,HTML5提供了 history.pushState(state, title, url) 和 history.replaceState(state, title, url) 方法,允许在不刷新页面的情况下修改URL和浏览器历史记录。配合 popstate 事件监听,可以实现无刷新路由导航。
// 基本导航
history.back(); // 后退 → 相当于 history.go(-1)
history.forward(); // 前进 → 相当于 history.go(1)
history.go(-2); // 后退2页
// HTML5 History API - SPA路由核心
// pushState: 添加新历史记录
history.pushState({ page: 1 }, '标题', '/page/1');
// replaceState: 替换当前历史记录(不会增加新条目)
history.replaceState({ page: 2 }, '标题', '/page/2');
// 监听历史变化
window.addEventListener('popstate', (e) => {
console.log('状态变化:', e.state);
// 根据e.state更新页面内容
});
6.4 navigator对象
window.navigator 对象包含了浏览器的相关信息,常用于浏览器检测、功能判断和用户代理分析。常用属性包括:userAgent(浏览器用户代理字符串)、platform(操作系统平台)、language(首选语言)、languages(语言列表)、online(是否在线)、cookieEnabled(是否启用Cookie)、geolocation(地理定位API)、clipboard(剪贴板API)等。
需要注意:userAgent 字符串可以被用户或浏览器插件修改,不应仅依赖它做关键功能判断。推荐使用特性检测(Feature Detection)而非浏览器检测(Browser Detection)。例如,通过检测 navigator.geolocation 是否存在来判断浏览器是否支持地理定位,而不是检测是否为 Chrome。
// 浏览器信息
console.log('User-Agent:', navigator.userAgent);
console.log('平台:', navigator.platform);
console.log('语言:', navigator.language);
console.log('在线状态:', navigator.onLine);
// 网络状态监听
window.addEventListener('online', () => console.log('网络已连接'));
window.addEventListener('offline', () => console.log('网络已断开'));
// 特性检测示例
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
pos => console.log('纬度:', pos.coords.latitude),
err => console.error('定位失败:', err.message)
);
} else {
console.log('该浏览器不支持地理定位');
}
// 剪贴板API
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('复制成功');
} catch (err) {
console.error('复制失败:', err);
}
}
6.5 localStorage / sessionStorage
Web Storage API 提供了在浏览器中存储键值对数据的机制,是Cookie的现代替代方案(存储容量更大,约5-10MB,且不会自动随HTTP请求发送到服务器)。两种存储方式的核心区别在于生命周期和作用域:
localStorage 持久化存储,数据不会过期,除非手动删除或用户清除浏览器数据。同一域名下所有标签页共享 localStorage 数据,且页面关闭后数据仍然保留。sessionStorage 会话级存储,数据仅在当前标签页(会话)有效,关闭标签页即被清除。即使是同域名下的不同标签页,sessionStorage 也是独立的。
两者的API完全一致:setItem(key, value)、getItem(key)、removeItem(key)、clear() 清空所有存储。需要注意的是:存储的值只能是字符串类型,存储对象或数组时需要先 JSON.stringify 序列化,读取后需要 JSON.parse 反序列化。
// localStorage - 持久存储
localStorage.setItem('theme', 'dark');
localStorage.setItem('fontSize', '16');
const theme = localStorage.getItem('theme');
console.log(theme); // "dark"
localStorage.removeItem('fontSize');
// localStorage.clear(); // 慎用:清除所有
// sessionStorage - 会话存储
sessionStorage.setItem('tempData', JSON.stringify({ id: 1, name: '临时' }));
const data = JSON.parse(sessionStorage.getItem('tempData'));
// 存储/读取复杂数据
const user = { name: '张三', age: 28, roles: ['admin', 'editor'] };
localStorage.setItem('user', JSON.stringify(user));
const cachedUser = JSON.parse(localStorage.getItem('user'));
console.log(cachedUser.name); // "张三"
// storage 事件:跨标签页通信
window.addEventListener('storage', (e) => {
console.log('存储变化:', e.key, e.oldValue, '→', e.newValue);
// 注意:该事件只在其他标签页修改storage时触发
});
// 封装简易存储工具
const storage = {
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get(key) {
try {
const val = localStorage.getItem(key);
return val ? JSON.parse(val) : null;
} catch {
return localStorage.getItem(key);
}
},
remove(key) { localStorage.removeItem(key); },
clear() { localStorage.clear(); }
};
storage.set('settings', { lang: 'zh-CN', volume: 0.8 });
console.log(storage.get('settings')); // { lang: 'zh-CN', volume: 0.8 }
总结与核心要点
1. DOM本质 :DOM是将HTML文档映射为树状结构的编程接口,是JavaScript操作网页的基础。
2. 选择器原则 :优先使用 querySelector/querySelectorAll(灵活性高),需要id定位时用 getElementById(性能最优)。
3. 操作安全 :操作文本用 textContent(防XSS),操作HTML用 createElement + appendChild(保留事件绑定)。
4. 样式操作 :类名操作使用 classList API,读取计算样式用 getComputedStyle,内联样式用 style 属性。
5. 事件机制 :事件流分捕获→目标→冒泡三阶段,委托模式利用冒泡提升性能和动态性。
6. 性能优化 :批量DOM操作使用 DocumentFragment,高频事件防抖节流,滚动监听启用 passive 选项。
7. 存储策略 :持久数据用 localStorage,会话数据用 sessionStorage,涉及跨标签页通信监听 storage 事件。
8. 最佳实践 :使用特性检测代替浏览器检测;事件委托代替批量绑定;addEventListener 代替 onclick 属性;removeEventListener 务必使用命名函数。