DAY_11JavaScript BOM与DOM深度解析:底层原理与工程实践(上)

JavaScript BOM与DOM深度解析:底层原理与工程实践

摘要:浏览器对象模型(BOM)与文档对象模型(DOM)构成了JavaScript与浏览器交互的核心抽象层。本文从ECMAScript规范出发,深入剖析BOM/DOM的底层实现机制、内存模型、事件循环架构,以及在实际工程中的最佳实践。通过对API设计模式的解构和性能优化策略的探讨,帮助开发者建立从原理到应用的完整认知体系。
关键词:JavaScript运行时、BOM/DOM架构、事件驱动模型、性能优化、前端工程化

目录

  1. 内置对象回顾与进阶

  2. DOM核心概念

    • [2.1 DOM规范架构演进](#2.1 DOM规范架构演进)
    • [2.2 DOM节点类型层次结构](#2.2 DOM节点类型层次结构)
    • [2.3 DOM渲染流水线与内存模型](#2.3 DOM渲染流水线与内存模型)
    • [2.4 DOM操作的黄金四件套](#2.4 DOM操作的黄金四件套)
    • [2.5 DOM事件模型:捕获、冒泡与委托](#2.5 DOM事件模型:捕获、冒泡与委托)
    • [2.6 现代 Observer API](#2.6 现代 Observer API)
    • [2.7 DOM 查询策略与集合类型](#2.7 DOM 查询策略与集合类型)
    • [2.8 原生 API 与现代框架映射](#2.8 原生 API 与现代框架映射)
    • [2.9 Shadow DOM 与 Web Components](#2.9 Shadow DOM 与 Web Components)
    • [2.10 classList 与 dataset 实战](#2.10 classList 与 dataset 实战)
    • [2.11 DOM 节点遍历与 DOMContentLoaded](#2.11 DOM 节点遍历与 DOMContentLoaded)
  3. BOM完整体系

    • BOM对象层次结构图
    • BOM与DOM关系图
    • [3.4 Window对象](#3.4 Window对象)
      • [① 弹框系列](#① 弹框系列)
      • [② 窗口打开与关闭](#② 窗口打开与关闭)
      • [③ 页面滚动控制](#③ 页面滚动控制)
      • [④ 定时器机制](#④ 定时器机制)
      • [⑤ Window对象属性总结](#⑤ Window对象属性总结)
    • [3.5 History对象](#3.5 History对象)
      • [3.5.1 History API 与 SPA 路由](#3.5.1 History API 与 SPA 路由)
    • [3.6 Location对象](#3.6 Location对象)
    • [3.7 Navigator对象](#3.7 Navigator对象)
    • [3.8 Screen对象](#3.8 Screen对象)
    • [3.9 Web Storage:localStorage 与 sessionStorage](#3.9 Web Storage:localStorage 与 sessionStorage)
    • [3.10 同源策略与跨窗口通信](#3.10 同源策略与跨窗口通信)
  4. 实战案例详解

  5. 性能优化与最佳实践

    • [5.1 定时器使用注意事项](#5.1 定时器使用注意事项)
    • [5.2 DOM操作优化](#5.2 DOM操作优化)
      • [5.2.2 防抖与节流](#5.2.2 防抖与节流)
      • [5.2.3 强制同步布局(Layout Thrashing)](#5.2.3 强制同步布局(Layout Thrashing))
    • [5.3 BOM API兼容性处理](#5.3 BOM API兼容性处理)
    • [5.4 安全注意事项](#5.4 安全注意事项)
    • [5.5 Performance API 与 Web Vitals](#5.5 Performance API 与 Web Vitals)
    • [5.6 requestAnimationFrame 动画帧调度](#5.6 requestAnimationFrame 动画帧调度)
  6. 事件循环全景:宏任务、微任务与渲染帧

  7. [BOM/DOM 理论体系深化](#BOM/DOM 理论体系深化)

    • [9.1 三层抽象:ECMAScript、Web IDL、宿主对象](#9.1 三层抽象:ECMAScript、Web IDL、宿主对象)
    • [9.2 HTML 解析与 DOM 构建理论](#9.2 HTML 解析与 DOM 构建理论)
    • [9.3 渲染引擎:样式、布局、层叠与合成](#9.3 渲染引擎:样式、布局、层叠与合成)
    • [9.4 事件系统:分发、默认行为与命中测试](#9.4 事件系统:分发、默认行为与命中测试)
    • [9.5 浏览上下文、Realm 与 WindowProxy](#9.5 浏览上下文、Realm 与 WindowProxy)
    • [9.6 会话历史、导航与 bfcache](#9.6 会话历史、导航与 bfcache)
    • [9.7 安全信任模型:SOP、CSP 与 Trusted Types](#9.7 安全信任模型:SOP、CSP 与 Trusted Types)
    • [9.8 可达性与内存:DOM 为何容易泄漏](#9.8 可达性与内存:DOM 为何容易泄漏)
  8. 经典业务场景与可实现的业务

    • [6.1 完整业务场景架构图](#6.1 完整业务场景架构图)
    • [6.2 可实现的核心业务清单](#6.2 可实现的核心业务清单)
    • [6.3 综合业务案例实现流程](#6.3 综合业务案例实现流程)
    • [6.4 性能监控业务](#6.4 性能监控业务)
    • [6.5 设备适配业务](#6.5 设备适配业务)
    • [6.6 Web Components 与 Shadow DOM](#6.6 Web Components 与 Shadow DOM)
    • [6.7 主流网站经典场景与业务价值](#6.7 主流网站经典场景与业务价值)
      • [6.7.4 按业务目标再归类](#6.7.4 按业务目标再归类)
  9. 总结

附录. 工程化实践补充

  • [A.1 代码风格:ES5 与 ES6+ 对照](#A.1 代码风格:ES5 与 ES6+ 对照)
  • [A.2 TypeScript 核心类型定义](#A.2 TypeScript 核心类型定义)
  • [A.3 单元测试示例](#A.3 单元测试示例)
  • [A.4 性能基准参考数据](#A.4 性能基准参考数据)
  • [A.5 错误处理模式](#A.5 错误处理模式)

关于文中代码风格 :正文部分示例沿用课程场景的 ES5 写法var、function 声明),便于与课堂笔记对照。生产工程请采用 [附录 A.1](#附录 A.1) 的 ES6+ 写法;新增工具函数与测试均以现代语法为准。


1. JavaScript内置对象机制深度剖析

1.1 Function对象:函数式编程的核心抽象

Function对象不仅是JavaScript中所有函数的构造函数,更是函数式一等公民(First-Class Citizen)特性的具体体现。从ECMAScript规范角度,Function对象具备以下核心特征:

  1. 原型链特性:Function.prototype是所有函数的原型对象,包含call、apply、bind等核心方法
  2. 闭包机制:通过[[Environment]]内部槽实现词法作用域的持久化
  3. 动态this绑定:通过[[Call]]和[[Construct]]内部方法实现不同的执行语义
1.1.1 函数类型体系架构

运行时特征
ECMAScript规范视角
不支持
继承外层
透传
不支持
预设
Function Objects
Ordinary Functions
Arrow Functions
Bound Functions
Built-in Functions
可调用性 Callable
可构造性 Constructible
this绑定机制
原型链继承

设计模式分析

设计模式 应用场景 实现机制
原型模式 方法复用 Function原型链继承
代理模式 this控制 call/apply/bind实现
装饰器模式 函数增强 高阶函数组合
观察者模式 异步通知 事件回调机制
1.1.2 this绑定的底层机制

this的指向并非简单的"调用对象决定",而是由JavaScript引擎的执行上下文(Execution Context)中的ThisBinding组件决定。根据ECMAScript规范,this绑定分为以下五种类型:
渲染错误: Mermaid 渲染失败: Parse error on line 14: ...F --> J[[Construct]]内部方法] G --> -----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'SHAPE_DATA', 'STYLE_SEPARATOR', 'START_LINK', 'LINK', 'LINK_ID', got 'UNICODE_TEXT'

绑定优先级(从高到低):

  1. new绑定:构造函数调用优先级最高
  2. 显式绑定:call/apply/bind硬编码
  3. 隐式绑定:对象方法调用
  4. 默认绑定:独立函数调用
1.1.3 call/apply/bind的规范级实现

执行上下文 Function对象 调用者 执行上下文 Function对象 调用者 call()执行流程 bind()执行流程 func.call(thisArg, ...args) 准备thisArg (null/undefined → global) 创建新的执行上下文 设置ThisBinding = thisArg 展开...args为参数列表 执行[[Call]]内部方法 返回执行结果 func.bind(thisArg, ...args) 创建BoundFunction对象 存储[[BoundTargetFunction]] 存储[[BoundThis]] 存储[[BoundArguments]] 返回BoundFunction

核心属性对比

特性 call() apply() bind()
执行时机 立即执行 立即执行 延迟执行
参数传递 逐个传递 数组展开 预设参数
返回值 函数返回值 函数返回值 新函数
this永久性 临时 临时 永久绑定
性能开销 最小 小(需展开数组) 中(创建闭包)
内部槽 - - [[BoundTargetFunction]] [[BoundThis]] [[BoundArguments]]

独立函数调用
对象方法调用
call/apply/bind
new 构造函数
箭头函数
函数调用
调用方式
this → window/undefined

严格模式
this → 调用对象
this → 指定对象
this → 新实例
this → 外层作用域this
默认绑定
隐式绑定
显式绑定
new绑定
箭头函数绑定

call/apply/bind 对比图

bind
函数.bind

this, arg1, arg2
返回新函数
apply
函数.apply

this, args
call
函数.call

this, arg1, arg2
立即执行
返回函数

需手动调用
参数逐个传递
参数数组传递
参数预设/this绑定

完整示例代码
javascript 复制代码
function func(num01, num02) {
    console.log('func被调用了:', num01 * num02, this);
    return num01 + num02;
}

console.log(func.length);
console.log(parseInt.length);
console.log(Array.length);
console.log(isNaN.length);
console.log([].join.length);

代码解释: length属性返回函数定义时声明的形参个数。func有2个形参,parseInt有1个形参,Array构造函数有1个形参,join方法有1个形参(分隔符)。


javascript 复制代码
var user = {
    name: '曹操',
    getInfo: func
};

func(10, 20);

user.getInfo(4, 6);

代码解释: 函数中this的指向取决于函数的调用方式。作为独立函数调用时,this默认指向window;作为对象方法调用时,this指向调用该方法的对象(user)。


javascript 复制代码
var res = func.call({name: '高小乐'}, 10, 6);
console.log(res);

user.getInfo.call([10, 20, 30], 8, 5);

代码解释: call()方法可以借用其他对象的方法,并灵活改变this指向。第一个参数设置this指向,后续参数逐个传递给函数。虽然通过user.getInfo调用,但this被强制指向数组[10, 20, 30]


javascript 复制代码
var res1 = func.apply('hello', [19, 2]);
console.log(res1);

user.getInfo.apply(new Date());

代码解释: apply()call()的区别在于第二个参数必须是数组,数组元素会被展开作为函数参数。this指向字符串'hello'(被包装成String对象),数组[19, 2]中的元素分别传递给num01num02apply常用于参数数组展开的场景。


javascript 复制代码
var str = 'Hello高小乐';

[].forEach.call(str, function(item, index, arr) {
    console.log(item, index);
});

代码解释: 这是call()的高级用法,让字符串"借用"数组的遍历方法。字符串被当作类数组对象,forEach可以遍历每个字符(H 0, e 1, l 2, l 3, o 4, 高 5, 小 6, 乐 7)。


javascript 复制代码
var str1 = [].reduceRight.call(str, function(prev, item) {
    return prev + item;
});
console.log(str1);

代码解释: reduceRight()从右向左遍历,配合call()实现字符串反转。每个字符被拼接到prev前面,最终输出"乐高小olleH"。


javascript 复制代码
[].forEach.apply(str, [function(item, index, arr) {
    console.log(item, index);
}]);

代码解释: 这种写法展示了apply()的特殊用法,虽然不太常用,但体现了JavaScript函数式编程的特性。apply将回调函数作为forEachthis(即str),输出结果与示例5相同。


javascript 复制代码
var fn01 = func.bind([10, 20, 30, 40, 50]);
console.log(fn01(10, 20));

代码解释: bind()创建一个新函数,this被永久绑定到[10, 20, 30, 40, 50]。无论fn01如何调用,this始终指向该数组,常用于事件处理函数中保持上下文。


javascript 复制代码
var fn02 = func.bind([10, 20, 30], 10000);
console.log(fn02(2));

var fn03 = func.bind([10, 20, 30], 100, 200);
console.log(fn03());

代码解释: bind()的参数预设特性可以实现偏函数(Partial Function)。第一个参数10000被预设给num01,调用时只需传第二个参数;两个参数都被预设后,调用时不需要传参数。


实战案例1:字符串转换工具(短横线转小驼峰)
javascript 复制代码
function toCamel(str) {
    return str.split('-').map(function(item, index) {
        return index === 0
            ? item.toLowerCase()
            : item[0].toUpperCase() + item.slice(1).toLowerCase();
    }).join('');
}

console.log(toCamel('get-element-by-id'));
console.log(toCamel('set-name-by-class-name'));

代码解释: 将短横线分隔的字符串转为小驼峰形式。执行过程:

  1. split('-')将字符串按短横线分割成数组
  2. map()遍历数组,第一个单词全小写,后续单词首字母大写其余小写
  3. join('')将数组重新拼接成字符串

'get-element-by-id'['get', 'element', 'by', 'id']['get', 'Element', 'By', 'Id']'getElementById'


1.2 Global 对象与浏览器环境

ECMAScript标准规定的全局对象,在浏览器环境中,window对象即为Global的实现。像ArrayNumberStringisNaNisFinite等都是Global的属性。

完整示例代码
javascript 复制代码
var msg = 'var address = "上海"';
eval(msg);
console.log(address);

代码解释: eval()可以将字符串解析为可执行代码,声明了全局变量address。但由于安全风险和性能问题,应避免在生产环境使用。


javascript 复制代码
var url = 'http://www.atguigu.com/image/小乐.html?name=101&age=20';
console.log(url);

var url01 = encodeURI(url);
console.log(url01);

代码解释: encodeURI()用于编码完整的URL,只编码非URL字符(如中文),保留URL的结构符号。中文"小乐"被编码为%E5%B0%8F%E4%B9%90,而http://?&=等URL符号保持不变。


javascript 复制代码
var url02 = decodeURI(url01);
console.log(url02);

代码解释: decodeURI()encodeURI()的逆操作,将编码后的URL还原为原始形式。


2. DOM核心概念与规范体系

2.1 DOM规范架构演进

DOM(Document Object Model)是由W3C标准化的跨平台语言中立接口,其规范体系经历了四个主要发展阶段:
浏览器实现
DOM规范演进
废弃
DOM Level 1

1998年

核心模型
DOM Level 2

2000年

事件/样式
DOM Level 3

2004年

加载/验证
DOM4 / WHATWG

Living Standard

持续演进
Blink

Chrome/Edge
Gecko

Firefox
WebKit

Safari
Trident

IE已废弃

2.2 DOM节点类型层次结构

<<abstract>>
Node
+nodeType: number
+nodeName: string
+nodeValue: string
+childNodes: NodeList
+parentNode: Node
+ownerDocument: Document
+appendChild(child)
+removeChild(child)
+insertBefore(new, ref)
+cloneNode(deep)
+contains(node)
Document
+documentElement: Element
+body: HTMLElement
+head: HTMLElement
+doctype: DocumentType
+getElementById(id)
+getElementsByTagName(tag)
+getElementsByClassName(cls)
+querySelector(sel)
+createElement(tag)
+createTextNode(txt)
Element
+tagName: string
+className: string
+id: string
+innerHTML: string
+attributes: NamedNodeMap
+getAttribute(name)
+setAttribute(name, value)
+removeAttribute(name)
+querySelector(sel)
+querySelectorAll(sel)
Text
+data: string
+length: number
+wholeText: string
+splitText(offset)
Comment
DocumentFragment
DocumentType
HTMLElement
SVGElement
HTMLDivElement
HTMLSpanElement
HTMLAnchorElement

节点类型常量规范

节点类型 常量名 nodeType nodeName nodeValue
Element ELEMENT_NODE 1 元素标签名 null
Attr ATTRIBUTE_NODE 2 属性名 属性值
Text TEXT_NODE 3 #text 文本内容
CDATASection CDATA_SECTION_NODE 4 #cdata-section CDATA内容
EntityReference ENTITY_REFERENCE_NODE 5 实体引用名 -
Entity ENTITY_NODE 6 实体名 -
ProcessingInstruction PROCESSING_INSTRUCTION_NODE 7 target data
Comment COMMENT_NODE 8 #comment 注释内容
Document DOCUMENT_NODE 9 #document null
DocumentType DOCUMENT_TYPE_NODE 10 doctype名 null
DocumentFragment DOCUMENT_FRAGMENT_NODE 11 #document-fragment null
Notation NOTATION_NODE 12 符号名 -

2.3 DOM渲染流水线与内存模型

绘制合成
布局计算
渲染构建
样式处理
解析阶段
HTML字节流
词法分析 Tokenizer
语法分析 Parser
DOM树构建
CSS解析
样式规则计算
Attach渲染对象
渲染树 RenderTree
布局 Layout
层分层 Layer
绘制 Paint
合成 Composite
位图输出

内存布局分析
堆内存
弱引用
强引用
强引用
占用
占用
引用关系
DOM树节点
渲染对象
布局对象
层对象 Layer
~10-50MB/千节点
~20-100MB/千节点

2.4 DOM操作的黄金四件套

javascript 复制代码
var boxEle = document.getElementById('box');

boxEle.onclick = function() {
    boxEle.style.backgroundColor = '#900';
    boxEle.style.color = '#fff';
    boxEle.style.fontSize = '14px';

    boxEle.innerHTML = '哈哈哈,我被点了一下!';
};

代码解释: DOM操作的黄金四步:获取元素→绑定事件→修改样式→更新内容。点击后背景变红、文字变白、字体变小、内容改变,是所有交互效果的基础。


完整示例:交互式盒子

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>DOM交互示例</title>
    <style>
        #box {
            width: 600px;
            padding: 20px;
            height: 250px;
            background: #ccc;
        }
    </style>
</head>
<body>
    <h1>JavaScript</h1>
    <div id="box">流会法,个会惶言盲亲我,觉就许...</div>

    <script>
        var boxEle = document.getElementById('box');

        boxEle.onclick = function() {
            boxEle.style.backgroundColor = '#900';
            boxEle.style.color = '#fff';
            boxEle.style.fontSize = '14px';

            boxEle.innerHTML = '哈哈哈,我被点了一下!';
        };
    </script>
</body>
</html>

代码解释: 完整的DOM交互示例。点击灰色盒子后,背景变红(#900)、文字变白(#fff)、字体变小(14px)、内容改变,展示了DOM操作的实际效果。


2.5 DOM事件模型:捕获、冒泡与委托

DOM Events 规范(DOM Level 2+)定义了**事件流(Event Flow)**的三阶段模型,这是理解 addEventListener、事件委托与性能优化的基础。
监听器 目标元素 Body Document Window 监听器 目标元素 Body Document Window ① 捕获阶段 Capture(由外向内) ② 目标阶段 Target ③ 冒泡阶段 Bubble(由内向外) 事件向下传播 事件向下传播 事件到达目标 触发目标上的监听器 事件向上传播 事件向上传播 传播结束

三阶段对比

阶段 传播方向 event.eventPhase 典型用途
捕获 window → target 1 (CAPTURING_PHASE) 全局拦截、下拉菜单关闭
目标 停在 target 2 (AT_TARGET) 元素自身逻辑
冒泡 target → window 3 (BUBBLING_PHASE) 事件委托(最常用)
2.5.1 addEventListener 与属性绑定的工程差异
特性 element.onclick = fn addEventListener(type, fn, options)
绑定数量 仅一个处理器(后者覆盖前者) 可注册多个独立监听器
捕获阶段 不支持 options.capture === true
被动监听 不支持 { passive: true } 告知浏览器不调用 preventDefault
一次性 不支持 { once: true } 执行后自动移除
移除方式 赋值为 null removeEventListener 需传入同一函数引用

passive 与滚动性能 :在 touchstart/wheel 等高频事件上,浏览器默认可能将监听器视为 passive(Chrome 56+)。若需阻止默认滚动,必须显式 { passive: false },否则控制台会警告且 preventDefault() 无效。

javascript 复制代码
// 推荐:可组合、可移除、支持委托
document.getElementById('list').addEventListener('click', function(e) {
    var item = e.target.closest('[data-id]');
    if (!item || !this.contains(item)) return;
    console.log('选中项:', item.dataset.id);
}, false); // false = 冒泡阶段(默认)
2.5.2 事件委托:以空间换时间的经典模式

原理:利用冒泡阶段,在祖先节点统一处理子节点事件,避免为 N 个子元素各绑 N 个监听器。

javascript 复制代码
var tbody = document.querySelector('#user-table tbody');

tbody.addEventListener('click', function(e) {
    var btn = e.target.closest('button[data-action]');
    if (!btn) return;

    var action = btn.dataset.action;
    var row = btn.closest('tr');
    var id = row && row.dataset.id;

    if (action === 'edit') editUser(id);
    if (action === 'delete') deleteUser(id);
});

适用场景:动态列表、表格操作列、无限滚动列表------子节点增删时无需重新绑定。

注意事项

  • e.target 可能是文本节点或嵌套子元素,使用 closest() 向上匹配
  • 不是所有事件都冒泡(如 focus/blur 不冒泡,可用 focusin/focusout
  • 委托层不宜过深,否则每次点击都要遍历祖先链
2.5.3 DOM 事件与事件循环的协作

用户交互事件(click、input 等)由浏览器渲染进程 捕获后,将回调封装为宏任务(Task)入队;Promise.thenMutationObserver 等为微任务(Microtask),在当前宏任务清空后、下一个宏任务前全部执行。
用户点击
浏览器合成事件对象
宏任务:dispatch 事件
执行监听器同步代码
微任务队列清空
可能触发重排/重绘
requestAnimationFrame 回调

工程启示 :在事件处理器中避免长时间同步计算;批量 DOM 写入可配合 requestAnimationFrameDocumentFragment,防止阻塞后续事件与帧渲染。


2.6 现代 Observer API

除传统事件外,WHATWG 与 W3C 提供了异步观察器,将「何时处理」交给浏览器调度,减少 scroll/resize 轮询。

API 观察对象 典型场景 与 BOM/DOM 关系
IntersectionObserver 元素与视口交叉 图片懒加载、曝光埋点 替代 scroll + getBoundingClientRect
MutationObserver DOM 树变更 第三方组件监控、富文本同步 微任务回调,批量记录 mutations
ResizeObserver 元素尺寸变化 图表自适应、容器查询 window.resize 更细粒度
javascript 复制代码
// 图片懒加载:进入视口前不请求真实 src
var io = new IntersectionObserver(function(entries) {
    entries.forEach(function(entry) {
        if (!entry.isIntersecting) return;
        var img = entry.target;
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
        io.unobserve(img);
    });
}, { rootMargin: '200px' }); // 提前 200px 预加载

document.querySelectorAll('img[data-src]').forEach(function(img) {
    io.observe(img);
});

2.7 DOM 查询策略与集合类型

元素查询是 DOM 编程的入口。不同 API 在时间复杂度、返回值类型、是否 Live 上差异显著,直接影响性能与正确性。

API 返回类型 Live? 匹配方式 性能特征
getElementById(id) `Element null` - ID 哈希表
getElementsByClassName HTMLCollection ✓ Live 类名 文档变更时自动更新
getElementsByTagName HTMLCollection ✓ Live 标签名 同上
querySelector(sel) `Element null` - CSS 选择器
querySelectorAll(sel) 静态 NodeList CSS 选择器 快照,后续 DOM 变更不影响

Live vs Static 的工程陷阱

javascript 复制代码
var items = document.getElementsByClassName('item'); // Live HTMLCollection
for (var i = 0; i < items.length; i++) {
    items[i].classList.remove('item'); // 每移除一个,length 减 1,可能跳过元素!
}

// 正确:倒序遍历,或转为静态数组
Array.prototype.slice.call(items).forEach(function(el) {
    el.classList.remove('item');
});

querySelectorgetElementById 选型

  • 已知唯一 id → 优先 getElementById(不触发选择器引擎全树扫描)
  • 复杂组合条件(main .card[data-active])→ querySelector(All)
  • 同一父节点 内反复查询 → 缓存父节点:var root = document.getElementById('app'); root.querySelector('.btn')

辅助 API

方法 作用
element.closest(selector) 向上匹配祖先,事件委托核心
element.matches(selector) 当前节点是否匹配,过滤用
element.contains(node) 是否包含子节点(含自身 false)

2.8 原生 API 与现代框架映射

理解框架并非替代 BOM/DOM,而是在其之上建立声明式抽象层。读源码或调试生产问题时,必须能还原到原生 API。
React 18+
Vue 3
原生层
document.querySelector
addEventListener
history.pushState
classList / style
ref / template ref
v-on / @click
vue-router history模式
响应式 class 绑定
useRef + ref.current
合成事件 SyntheticEvent
react-router useNavigate
className / style prop

能力 原生 Vue 3 React
元素引用 document.getElementById ref="el" / useTemplateRef useRef(null).current
事件 addEventListener @click(编译为原生监听) 根节点委托 + 合成事件池
路由 hash / pushState vue-routercreateWebHistory() react-routercreateBrowserRouter
列表渲染 手动 createElement v-for → patch DOM map → Virtual DOM diff
状态驱动 UI 手动改 innerHTML 响应式 → 自动 patch setState → reconcile

合成事件(React)与原生事件的区别 :React 17+ 将委托挂载到根容器 而非 document,事件对象在冒泡到根时统一包装为 SyntheticEvente.nativeEvent 可访问原生事件。调试时注意 stopPropagation 在合成层与原生层的行为一致性问题。

Vue 的 nextTick :DOM 更新异步批处理,等价于在微任务中等待 Virtual DOM patch 完成后再读布局------底层常结合 Promise 微任务与 MutationObserver


2.9 Shadow DOM 与 Web Components

Shadow DOM 在元素内部维护独立的 DOM 子树,样式与 id 与主文档隔离,是 Web Components 封装的基础。
Shadow Root 封闭树
Light DOM 主文档
投影
attachShadow mode: open
custom-card
slot 插槽
内部结构 / 样式
主文档中的子节点

javascript 复制代码
class UserCard extends HTMLElement {
    constructor() {
        super();
        var shadow = this.attachShadow({ mode: 'open' });
        shadow.innerHTML =
            '<style>:host { display: block; border: 1px solid #ccc; }</style>' +
            '<h3><slot name="title">默认标题</slot></h3>' +
            '<p><slot></slot></p>';
    }
}
customElements.define('user-card', UserCard);
概念 说明
:host 选中 Shadow 宿主元素本身
::slotted() 样式化被投影进来的 slotted 节点
mode: 'closed' 外部无法通过 element.shadowRoot 访问
事件重定向 部分事件在 Shadow 边界上 event.target 会「重定向」到宿主

与框架关系 :Vue 3 的 scoped、React 的 CSS Modules 解决的是样式隔离 ;Shadow DOM 解决的是 DOM 树隔离 + 原生组件复用。Element Plus、Lit、Stencil 等库在底层均可能使用 Shadow DOM。


2.10 classList 与 dataset 实战

2.10.1 classList:类名的最优操作方式

element.classList 返回 DOMTokenList 对象,比直接操作 className 字符串更安全、更语义化。

方法 说明 示例
add(...classes) 添加一个或多个类 el.classList.add('active', 'visible')
remove(...classes) 移除一个或多个类 el.classList.remove('hidden')
toggle(class, force?) 有则移除,无则添加 el.classList.toggle('open')
contains(class) 是否含有该类,返回 boolean el.classList.contains('active')
replace(old, new) 替换类名 el.classList.replace('btn-blue', 'btn-red')
item(index) 按索引取类名 el.classList.item(0)
length 类的数量 el.classList.length
javascript 复制代码
var btn = document.getElementById('btn');

// 切换激活状态(最常用)
btn.onclick = function() {
    btn.classList.toggle('active');
};

// 根据条件动态添加/移除
function setLoading(el, isLoading) {
    if (isLoading) {
        el.classList.add('loading');
        el.classList.remove('idle');
    } else {
        el.classList.remove('loading');
        el.classList.add('idle');
    }
}

// toggle 的 force 参数:true = 强制添加,false = 强制移除
var isNight = true;
document.body.classList.toggle('dark-mode', isNight);

// 判断后再操作(避免重复添加的无意义调用)
if (!btn.classList.contains('disabled')) {
    btn.classList.add('disabled');
    btn.setAttribute('disabled', 'disabled');
}

// 遍历所有类名
Array.prototype.forEach.call(btn.classList, function(cls) {
    console.log(cls);
});

className 的对比

javascript 复制代码
// ❌ 旧写法:容易破坏已有类名
element.className = element.className + ' active'; // 可能出现重复
element.className = element.className.replace('active', ''); // 不稳健

// ✅ 新写法:classList 保证幂等性
element.classList.add('active');
element.classList.remove('active');
2.10.2 dataset:自定义数据属性

element.dataset 提供对 data-* HTML 属性的读写访问,是"将数据嵌入 DOM"的官方方式,常与事件委托配合使用。

命名规则 :HTML data-foo-bar → JS element.dataset.fooBar(短横线转小驼峰)

html 复制代码
<ul id="product-list">
    <li data-id="101" data-price="299" data-category="phone">手机 A</li>
    <li data-id="102" data-price="99"  data-category="earphone">耳机 B</li>
    <li data-id="103" data-price="1999" data-category="laptop">笔记本 C</li>
</ul>
javascript 复制代码
var list = document.getElementById('product-list');

// 读取 dataset
list.addEventListener('click', function(e) {
    var item = e.target.closest('li[data-id]');
    if (!item) return;

    var id       = item.dataset.id;       // '101'(始终是字符串)
    var price    = Number(item.dataset.price);  // 转数字
    var category = item.dataset.category; // 'phone'

    console.log('商品ID:', id, '价格:', price, '分类:', category);
});

// 写入 dataset(会同步到 HTML 属性)
var li = document.querySelector('li');
li.dataset.selected = 'true';    // → data-selected="true"
li.dataset.viewCount = '5';      // → data-view-count="5"

// 删除 dataset 属性
delete li.dataset.selected;

// 遍历所有 data-* 属性
Object.keys(li.dataset).forEach(function(key) {
    console.log(key, ':', li.dataset[key]);
});

getAttribute 的对比

方式 写法 特点
element.getAttribute('data-id') 原始字符串操作 兼容所有属性,名称不转换
element.dataset.id 语义化读写 仅限 data-*,自动驼峰转换
javascript 复制代码
// 等价写法
var id1 = el.getAttribute('data-product-id');
var id2 = el.dataset.productId; // 推荐

2.11 DOM 节点遍历与 DOMContentLoaded

2.11.1 节点遍历 API

DOM 树导航分为节点级 (含文本/注释节点)和元素级(只含元素节点)两套属性。生产环境几乎总是用元素级:
节点级(含文本节点)
parentNode
childNodes
firstChild
lastChild
previousSibling
nextSibling
元素级(推荐)
parentElement
children
firstElementChild
lastElementChild
previousElementSibling
nextElementSibling

属性 说明 返回类型
element.children 所有直接子元素 HTMLCollection(Live)
element.childNodes 所有直接子节点(含文本) NodeList(Live)
element.firstElementChild 第一个子元素 `Element
element.lastElementChild 最后一个子元素 `Element
element.previousElementSibling 前一个兄弟元素 `Element
element.nextElementSibling 后一个兄弟元素 `Element
element.parentElement 父元素 `Element
element.parentNode 父节点(可能是 Document) `Node
javascript 复制代码
var ul = document.querySelector('ul');

// 遍历所有直接子元素
Array.prototype.forEach.call(ul.children, function(li, index) {
    console.log(index, li.textContent);
});

// 找到某元素的下一个兄弟元素
var current = document.getElementById('item2');
var next = current.nextElementSibling;
var prev = current.previousElementSibling;

// 向上查找特定祖先(等价于 closest,但手动版)
function findAncestor(el, tagName) {
    while (el && el.tagName !== tagName.toUpperCase()) {
        el = el.parentElement;
    }
    return el;
}

// children 是 Live 集合,删除时倒序遍历
var items = ul.children;
for (var i = items.length - 1; i >= 0; i--) {
    if (items[i].classList.contains('deleted')) {
        ul.removeChild(items[i]);
    }
}
2.11.2 document.readyState 与 DOMContentLoaded

document.readyState 反映页面加载的三个阶段,决定了脚本访问 DOM 的时机。

状态值 含义 触发事件
"loading" HTML 正在解析,DOM 未完整 ---
"interactive" HTML 解析完成,子资源仍在加载 DOMContentLoaded
"complete" 页面所有资源加载完毕 load
javascript 复制代码
// 方法一:监听 DOMContentLoaded(推荐,无需等待图片/样式)
document.addEventListener('DOMContentLoaded', function() {
    // DOM 已可用,可以安全操作元素
    var app = document.getElementById('app');
    console.log('DOM 就绪,子元素数:', app.children.length);
});

// 方法二:监听 readystatechange,兼容所有阶段
document.addEventListener('readystatechange', function() {
    console.log('当前状态:', document.readyState);
    // 'loading' → 'interactive' → 'complete'
});

// 方法三:若脚本已在 body 末尾或使用 defer,可直接访问 DOM
// <script src="app.js" defer></script>

// window.onload:等待所有子资源(图片/字体)加载完
window.addEventListener('load', function() {
    var img = document.querySelector('img');
    console.log('图片自然尺寸:', img.naturalWidth, img.naturalHeight);
});

脚本执行 子资源(图/CSS) DOM 树 HTML 解析 脚本执行 子资源(图/CSS) DOM 树 HTML 解析 readyState = 'loading' readyState = 'interactive' readyState = 'complete' 边解析边建树 解析完成 触发 DOMContentLoaded 所有资源加载完 触发 window.load

工程建议

  • 脚本放 <body> 末尾 或使用 defer → 不阻塞解析,无需等 DOMContentLoaded
  • 需要图片尺寸等子资源信息 → 用 window.load
  • 动态注入脚本要操作 DOM → 判断 document.readyState !== 'loading' 后再操作,否则监听事件

3. BOM完整体系与浏览器架构

3.1 BOM规范背景与实现差异

BOM(Browser Object Model)最初由Netscape Navigator 2引入,是非W3C标准的浏览器接口集合。由于历史原因,各浏览器实现存在差异,HTML5规范逐步统一了部分BOM API。

核心特征

  1. 非标准化:长期缺乏官方规范,依赖事实标准
  2. 浏览器依赖:功能紧密耦合特定浏览器能力
  3. 安全限制:受同源策略、沙箱机制约束
  4. 异步模型:基于事件循环的异步交互模式

3.2 浏览器多进程架构与BOM

BOM执行上下文
浏览器进程架构
IPC通信
IPC通信
IPC通信
IPC通信
Browser Process

主进程

管理UI/存储/网络
Renderer Process

渲染进程

执行JS/渲染DOM
GPU Process

GPU进程

图形渲染
Plugin Process

插件进程

Flash/PDF等
Utility Process

工具进程

音频/视频等
Window对象

全局上下文
BOM APIs

专用接口
Event Loop

事件循环

进程隔离模型

  • Chrome/Edge:多进程架构,每个标签页独立Renderer进程
  • Firefox: Electrolysis (e10s) 多进程架构
  • Safari:WebKit多进程架构
  • 隔离优势:崩溃隔离、安全沙箱、并行渲染

3.3 BOM对象层次结构图

window对象 - 顶层
document对象 - DOM树
html
head
body
meta/title/link等
div/p/span等
document

DOM入口
history

历史记录
location

地址信息
navigator

浏览器信息
screen

屏幕信息
frames

框架集合

BOM与DOM关系图

JavaScript运行环境
BOM

浏览器对象模型
DOM

文档对象模型
操作浏览器窗口

历史记录/地址栏/屏幕
操作页面内容

元素/样式/事件
Window/History

Location/Navigator/Screen
Document/Element

Node/Event
业务场景:

页面跳转/刷新控制

窗口管理/设备检测
业务场景:

动态内容/交互效果

表单处理/数据展示

3.4 Window对象:浏览器窗口的抽象

3.4.1 Window对象的双重身份

Window对象在JavaScript运行时中具有双重身份:

  1. ECMAScript Global Object:作为全局作用域,存储全局变量和内置函数
  2. BOM Window Interface:作为浏览器窗口接口,提供窗口操作能力

BOM Window职责
Global Object职责
Window对象
Global Object身份

ECMAScript规范
BOM Window接口

HTML规范
全局变量存储
内置构造函数
全局函数
窗口管理
定时器
导航控制
弹窗对话框

3.4.2 同步对话框机制(Modal Dialogs)

同步对话框会阻塞主线程,暂停JavaScript执行和页面渲染,直到用户响应。这种机制在现代Web开发中已被弃用,原因包括:

方法 返回值 阻塞特性 弃用状态
alert() undefined 完全阻塞 ⚠️ 调试用途
confirm() boolean 完全阻塞 ⚠️ 避免使用
prompt() `string null` 完全阻塞

技术原理
用户 浏览器UI JavaScript主线程 用户 浏览器UI JavaScript主线程 alert('message') 暂停执行 阻塞事件循环 显示模态对话框 点击确定 返回undefined 恢复执行

现代替代方案

  • alert替代:自定义Toast/Notification组件
  • confirm替代:自定义Modal/Dialog组件
  • prompt替代:自定义Form/Input组件
javascript 复制代码
var res1 = alert('警告!');
console.log(res1);

代码解释: alert()会阻塞代码执行,直到用户点击确定,返回值是undefined,常用于调试和重要提示。


javascript 复制代码
var res2 = confirm('你确定吗?');
console.log(res2);

代码解释: confirm()返回布尔值,点击确定返回true,点击取消返回false,常用于删除操作前的二次确认。


javascript 复制代码
var res3 = prompt('请输入您的银行卡密码:');
console.log(res3);

代码解释: prompt()返回用户输入的内容(字符串),点击取消返回null。由于样式不可控且阻塞执行,实际开发中很少使用。


② 窗口打开与关闭
javascript 复制代码
open();
open('网页地址');
open('网页地址', '窗口名称');
open('网页地址', '', 'width=400,height=300');
close();

代码解释: open()close()用于窗口管理。

  • open():打开新的空白浏览器窗口
  • open('网页地址'):在新窗口中打开指定网页
  • open('网页地址', '窗口名称'):在指定窗口或iframe中打开
  • open('网页地址', '', 'width=400,height=300'):设置窗口尺寸
  • close():关闭当前窗口,但只能关闭由JavaScript打开的窗口(安全限制)

完整示例:窗口操作控制台
html 复制代码
<button id="btn01">打开空白窗口</button>
<button id="btn02">打开新窗口指定页面地址</button>
<button id="btn03">指定窗口打开网页</button>
<button id="btn04">打开新窗口指定尺寸</button>
<br><br>
<button id="btn05">双击关闭窗口</button>

<hr>

<iframe src="" frameborder="1" name="dalao" width="1000" height="400"></iframe>

<script>
    console.log(name);
    name = 'xiaole';

    var btn01 = document.getElementById('btn01');
    btn01.onclick = function() {
        open();
    };

    var btn02 = document.getElementById('btn02');
    btn02.onclick = function() {
        open('./02-打开关闭窗口.html');
    };

    var btn03 = document.getElementById('btn03');
    btn03.onclick = function() {
        open('http://www.taobao.com', 'dalao');
    };

    var btn04 = document.getElementById('btn04');
    btn04.onclick = function() {
        open('http://www.taobao.com', '', 'width=400,height=300');
    };

    var btn05 = document.getElementById('btn05');
    btn05.ondblclick = function() {
        close();
    };
</script>

代码解释: 完整的窗口操作示例。

  • name属性:可读写,用于标识窗口
  • open():打开新的空白标签页
  • open('地址'):在新窗口打开指定页面
  • open('地址', 'dalao'):在name为'dalao'的iframe中打开
  • open('地址', '', 'width=400,height=300'):设置窗口尺寸
  • close():双击关闭窗口,只能关闭由open()打开的窗口

③ 页面滚动控制
方法 说明 参数类型
scrollTo(x, y) 滚动到指定坐标 绝对位置
scrollBy(x, y) 滚动指定距离 相对距离
javascript 复制代码
scrollTo(0, 0);

scrollTo({
    left: 0,
    top: 0,
    behavior: 'smooth'
});

代码解释: scrollTo()是绝对滚动,直接跳转到指定坐标。

  • 两个参数形式:scrollTo(x, y),滚动到指定坐标
  • 对象参数形式:支持behavior属性,可选'auto'(默认,瞬间跳转)或'smooth'(平滑滚动)
  • 适合"返回顶部"等功能

javascript 复制代码
scrollBy(100, 100);

scrollBy({
    top: 600,
    behavior: "smooth"
});

代码解释: scrollBy()是相对滚动,基于当前位置进行偏移。

  • 两个参数形式:scrollBy(x, y),从当前位置向右/下滚动指定像素
  • 对象参数形式:可设置lefttop,支持behavior: 'smooth'平滑滚动
  • 适合"加载更多"等功能

完整示例:页面滚动控制
html 复制代码
<style>
    .section {
        height: 500px;
        border-bottom: 2px dashed #999;
    }

    p {
        width: 2000px;
    }

    .nav {
        position: fixed;
        right: 10px;
        bottom: 100px;
        width: 100px;
    }
</style>

<h2>第一部分</h2>
<div class="section"></div>
<h2>第二部分</h2>
<div class="section">
    <p>朋亡秦由吞评揽种仁国方宋念智...</p>
</div>
<h2>第三部分</h2>
<div class="section"></div>
<h2>第四部分</h2>
<div class="section"></div>
<h2>第五部分</h2>
<div class="section"></div>
<h2>第六部分</h2>

<div class="nav">
    <button id="btn01">scrollTo</button>
    <button id="btn02">返回页面顶部</button>
    <button id="btn03">scrollBy</button>
</div>

<script>
    var btn01 = document.getElementById('btn01');
    btn01.onclick = function() {
        scrollTo(400, 200);
    };

    var btn02 = document.getElementById('btn02');
    btn02.onclick = function() {
        scrollTo({
            left: 0,
            top: 0,
            behavior: 'smooth'
        });
    };

    var btn03 = document.getElementById('btn03');
    btn03.onclick = function() {
        scrollBy({
            top: 600,
            behavior: "smooth"
        });
    };
</script>

代码解释: 完整的页面滚动示例。

  • CSS:.section高度500px,p宽度2000px使页面可水平滚动,.nav固定在右下角
  • scrollTo(400, 200):瞬间滚动到指定坐标
  • scrollTo({top: 0, behavior: 'smooth'}):平滑滚动回页面顶部
  • scrollBy({top: 600, behavior: 'smooth'}):从当前位置向下平滑滚动600px

④ 定时器机制与事件循环

定时器是JavaScript异步编程的核心基础设施,其实现基于事件循环(Event Loop)机制。

4.1 定时器在事件循环中的位置

定时器生命周期
事件循环机制
调用
Event Loop
优先执行
Call Stack

执行栈
Web APIs

定时器/DOM/AJAX
Task Queue

任务队列
Microtask Queue

微任务队列
setTimeout/setInterval

注册定时器
浏览器计时

Web APIs处理
任务入队

Timer Task
执行回调

Call Stack

4.2 定时器精度与限制
浏览器 最小延迟 延迟漂移 嵌套限制
Chrome/Edge 4ms (第5次+ ) 存在 5层后强制4ms
Firefox 4ms (第5次+ ) 存在 无强制限制
Safari 4ms (第5次+ ) 存在 无强制限制

精度限制因素

  1. 浏览器最小粒度:现代浏览器强制最小4ms延迟(第5次嵌套开始)
  2. 主线程阻塞:同步任务阻塞会影响定时器触发时机
  3. 时间片调度:操作系统进程调度引入额外延迟
4.3 定时器类型对比
特性 setTimeout setInterval
执行模式 单次延迟执行 周性重复执行
时间保证 相对准确 存在累积误差
内存风险 低(自动清理) 高(需手动清理)
推荐场景 延迟操作、防抖 定时轮询、倒计时
最佳实践 递归调用代替setInterval 严格管理生命周期

多次定时器:

javascript 复制代码
setInterval(回调函数, 时间间隔);
clearInterval(定时器标记);

单次定时器:

javascript 复制代码
setTimeout(回调函数, 时间间隔);
clearTimeout(定时器标记);

代码解释:

  • setInterval():每隔指定时间执行一次,时间间隔单位是毫秒,返回定时器标记用于清除
  • clearInterval():通过定时器标记清除指定的定时器
  • setTimeout():延迟指定时间后执行一次,返回定时器标记用于取消
  • clearTimeout():在定时器执行前取消它

完整示例1:多次定时器
html 复制代码
<button id="btn">停止定时器</button>
<div id="box">10</div>

<style>
    #box {
        font-size: 200px;
    }
</style>

<script>
    var intervalId01 = setInterval(function(num) {
        console.log('hello 高小乐', Math.random(), num);
    }, 1000, Math.random());

    var intervalId02 = setInterval(randBgColor, 50);
    function randBgColor() {
        var r = Math.floor(Math.random() * 256);
        var g = Math.floor(Math.random() * 256);
        var b = Math.floor(Math.random() * 256);
        document.body.style.backgroundColor = 'rgb(' + r + ',' + g + ',' + b + ')';
    }

    var num = 10;
    var boxEle = document.getElementById('box');

    var intervalId03 = setInterval(function() {
        num--;
        boxEle.innerHTML = num;

        if (num === 0) {
            clearInterval(intervalId03);
        }
    }, 1000);

    var btn = document.getElementById('btn');
    btn.onclick = function() {
        clearInterval(intervalId02);
    };
</script>

代码解释: setInterval()的三种用法:

  1. 带额外参数:第三个参数及之后的参数会传递给回调函数,每秒执行一次输出随机值
  2. 随机背景色 :每50毫秒生成随机RGB值并设置背景色,产生闪烁效果。Math.random()生成0-1的随机数,乘256取整得到0-255的随机值
  3. 倒计时:每1000毫秒(1秒)执行一次,数字递减,倒计时结束时必须清除定时器,否则会一直执行产生负数
  4. 停止定时器 :点击按钮调用clearInterval()停止指定的定时器

完整示例2:单次定时器
html 复制代码
<button id="btn">清除单次定时器</button>
<div id="box">10</div>

<style>
    #box {
        font-size: 400px;
    }
</style>

<script>
    var timeId = setTimeout(function() {
        console.log('啊,我执行了!');
    }, 4000);

    var btn = document.getElementById('btn');
    btn.onclick = function() {
        clearTimeout(timeId);
    };

    var num = 11;
    var box = document.getElementById('box');

    function runTime() {
        num--;
        box.innerHTML = num;

        if (num === 0) {
            return;
        }

        setTimeout(runTime, 1000);
    }

    runTime();
</script>

代码解释: 单次定时器setTimeout()的使用:

  1. 基础用法:4秒后执行回调函数,只执行一次。在4秒内点击按钮可清除定时器
  2. 递归倒计时(推荐) :通过递归调用setTimeout实现周期执行效果。倒计时结束时直接返回不再递归。相比setInterval,递归方式可以避免时间漂移问题(时间累积误差)

⑤ Window对象属性总结
属性/方法 说明 示例值
name 窗口名字,可读写 'xiaole'
innerWidth 视口宽度(像素) 1920
innerHeight 视口高度(像素) 1080
document 文档对象(DOM入口) #document
history 历史记录对象 History {...}
location 地址信息对象 Location {...}
navigator 浏览器信息对象 Navigator {...}
screen 屏幕信息对象 Screen {...}
javascript 复制代码
console.log(innerWidth, innerHeight);

代码解释: innerWidthinnerHeight获取浏览器视口(可见区域)的宽度和高度,不含浏览器边框和工具栏,是响应式设计、动态布局、动画计算的重要参数。


3.5 History 对象

History表示本窗口的历史记录,提供页面导航能力,但只能访问当前窗口的历史记录,无法获取其他窗口的历史记录。

属性/方法 说明 参数
length 获取历史记录数量
back() 回到历史记录上一个
forward() 回到历史记录下一个
go(n) 前进或后退n步 数字(正负数)
javascript 复制代码
console.log('历史记录的数量:', history.length);

history.back();

history.go(-2);

history.forward();

history.go(2);

代码解释: history对象模拟浏览器的前进后退功能:

  • length:获取历史记录数量
  • back():后退一步,等同于点击浏览器后退按钮,等同于history.go(-1)
  • go(-2):一次后退两页
  • forward():前进一步,等同于点击浏览器前进按钮,等同于history.go(1)
  • go(2):一次前进两页

完整示例:历史记录导航
html 复制代码
<button id="btn01">后退一步</button>
<button id="btn02">后退两步</button>
<button id="btn03">前进一步</button>
<button id="btn04">前进两步</button>

<script>
    console.log(history);
    console.log('历史记录的数量:', history.length);

    var btn01 = document.getElementById('btn01');
    btn01.onclick = function() {
        history.back();
    };

    var btn02 = document.getElementById('btn02');
    btn02.onclick = function() {
        history.go(-2);
    };

    var btn03 = document.getElementById('btn03');
    btn03.onclick = function() {
        history.forward();
    };

    var btn04 = document.getElementById('btn04');
    btn04.onclick = function() {
        history.go(2);
    };
</script>

代码解释: 完整的历史记录导航示例。假设用户访问历史为01.html → 百度 → 01.html → 尚硅谷 → 01.html(当前页)history.length = 5

  • 后退一步:回到上一页(尚硅谷)
  • 后退两步:回到两页前(百度)
  • 前进一步:前进到下一页(如果已经是最新页面则无效)
  • 前进两页:前进两页(如果历史记录不足则无效)

3.5.1 History API 与 SPA 路由

HTML5 引入的 History API 使单页应用(SPA)能在不整页刷新 的前提下修改 URL,并与浏览器前进/后退栈协同。这是现代前端路由(Vue Router history 模式、React Router 等)的底层基础。

方法 作用 是否触发页面加载 历史栈
pushState(state, title, url) 压入新历史记录 新增一条
replaceState(state, title, url) 替换当前记录 不增加
popstate 事件 用户点击前进/后退时触发 读取 event.state

SPA 路由流程
hash
history
用户点击链接
路由模式
修改 location.hash
history.pushState
hashchange 事件
根据 pathname 渲染视图
用户点击后退
popstate 事件

javascript 复制代码
// 最小 history 路由示例
function navigate(path) {
    history.pushState({ path: path }, '', path);
    renderView(path);
}

window.addEventListener('popstate', function(e) {
    var path = (e.state && e.state.path) || location.pathname;
    renderView(path);
});

function renderView(path) {
    document.getElementById('app').textContent = '当前路由:' + path;
}

document.getElementById('link-about').addEventListener('click', function(e) {
    e.preventDefault();
    navigate('/about');
});

location.hash 路由对比

维度 Hash 路由 (#/path) History 路由 (/path)
兼容性 IE8+ IE10+(需服务端 fallback)
SEO 较差(hash 不参与部分爬虫) 友好(配合 SSR/预渲染)
服务端配置 无需 需将所有路径回退到 index.html
锚点冲突 hash 兼作路由与锚点 无冲突

state 对象限制pushStatestate 会被结构化克隆 存储,不能保存 DOM 节点或函数;刷新页面后 state 可能丢失,生产环境常结合服务端数据或 sessionStorage 恢复。


3.6 Location 对象

Location表示本窗口的地址信息,提供URL解析、修改和页面跳转能力。

相关推荐
会编程的土豆1 小时前
Go ini 配置加载:`ini.MapTo` 详细解析
开发语言·数据库·golang
冴羽yayujs1 小时前
GitHub 前端热榜项目 - 日榜(2026-05-17)
前端·github
ChoSeitaku1 小时前
04.数组
java·开发语言·数据结构
老马95271 小时前
opencode8-桌面应用实战 3
前端·人工智能·后端
逆yan_1 小时前
🧭 基于 pnpm Workspace 和 Turborepo 的 Monorepo 最佳实践
前端·javascript·架构
techdashen1 小时前
半小时读懂 Rust:从语法符号到所有权思维
开发语言·rust
郭龙_Jack1 小时前
Java 17 到 Java 25:LTS 升级的全面收益与迁移指南
java·开发语言·python
广州华水科技1 小时前
单北斗形变监测一体机在大坝安全监测中的应用与技术优势
前端
TIEM_691 小时前
C++ vector容器全面解析:从入门到精通
开发语言·c++