feat: 实现 ArgonRenderOptimizer 类
- 实现构造函数,初始化读写队列和调度状态 - 实现 read() 方法:将 DOM 读取操作加入队列 - 实现 write() 方法:将 DOM 写入操作加入队列 - 实现 _schedule() 私有方法:使用 requestAnimationFrame 调度执行 - 实现 _flush() 私有方法:批量执行队列操作(先读后写) - 实现 enableGPU() 方法:设置 will-change 属性启用 GPU 加速 - 实现 disableGPU() 方法:移除 will-change 属性释放资源 - 添加错误处理机制,确保单个操作失败不影响其他操作 - 在测试文件中添加完整的渲染优化模块测试 - 创建使用指南文档 RENDER_OPTIMIZER_USAGE.md - 导出 ArgonRenderOptimizer 类供其他模块使用 验证需求:2.3, 2.4, 17.1, 17.2
This commit is contained in:
272
.kiro/specs/resource-cpu-optimization/tasks.md
Normal file
272
.kiro/specs/resource-cpu-optimization/tasks.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 实现计划:资源和 CPU 优化
|
||||
|
||||
## 概述
|
||||
|
||||
本实现计划将 Argon 主题的资源和 CPU 优化设计转化为可执行的编码任务。优化重点包括:DOM 缓存、事件节流/防抖、按需加载、内存管理和性能监控。
|
||||
|
||||
所有任务都基于设计文档中定义的组件和接口,采用渐进式实现策略,确保每个步骤都能独立验证和测试。
|
||||
|
||||
## 任务
|
||||
|
||||
- [x] 1. 创建性能优化基础架构
|
||||
- 创建 `argon-performance.js` 文件作为优化模块的容器
|
||||
- 定义全局配置对象 `ArgonPerformanceConfig`
|
||||
- 设置模块导出和初始化接口
|
||||
- _需求:1.1, 2.1, 3.1_
|
||||
|
||||
- [ ] 2. 实现 DOM 缓存模块
|
||||
- [x] 2.1 创建 ArgonDOMCache 类
|
||||
- 实现构造函数和 Map 存储结构
|
||||
- 实现 init() 方法缓存常用元素
|
||||
- 实现 get()、set()、clear() 方法
|
||||
- _需求:1.1, 1.2, 1.3_
|
||||
|
||||
- [ ]* 2.2 编写 DOM 缓存属性测试
|
||||
- **属性 1: DOM 缓存初始化完整性**
|
||||
- **验证:需求 1.1**
|
||||
|
||||
- [ ]* 2.3 编写 DOM 缓存单元测试
|
||||
- 测试不存在元素返回 null
|
||||
- 测试缓存清空功能
|
||||
- _需求:1.5_
|
||||
|
||||
- [ ] 3. 实现事件管理模块
|
||||
- [x] 3.1 创建 ArgonEventManager 类基础结构
|
||||
- 实现构造函数和监听器注册表
|
||||
- 实现 on()、off()、clear() 方法
|
||||
- _需求:2.5, 11.2, 11.4_
|
||||
|
||||
- [x] 3.2 实现节流(throttle)机制
|
||||
- 实现 throttle() 方法
|
||||
- 使用 requestAnimationFrame 优化
|
||||
- 支持可配置的等待时间
|
||||
- _需求:2.1_
|
||||
|
||||
- [x] 3.3 实现防抖(debounce)机制
|
||||
- 实现 debounce() 方法
|
||||
- 支持可配置的延迟时间
|
||||
- 实现取消待执行任务逻辑
|
||||
- _需求:3.1, 3.2_
|
||||
|
||||
- [ ]* 3.4 编写事件管理属性测试
|
||||
- **属性 3: 滚动事件节流频率限制**
|
||||
- **属性 5: Resize 事件防抖延迟**
|
||||
- **验证:需求 2.1, 3.1**
|
||||
|
||||
- [ ]* 3.5 编写事件清理属性测试
|
||||
- **属性 4: 事件监听器清理完整性**
|
||||
- **验证:需求 2.5, 11.2, 11.4**
|
||||
|
||||
- [x] 4. 检查点 - 基础模块验证
|
||||
- 确保所有测试通过,询问用户是否有问题
|
||||
|
||||
- [ ] 5. 实现资源加载模块
|
||||
- [x] 5.1 创建 ArgonResourceLoader 类
|
||||
- 实现构造函数和加载状态管理
|
||||
- 实现 loadScript() 异步加载方法
|
||||
- 实现 needsLibrary() 检测方法
|
||||
- _需求:7.1, 7.2, 7.3, 7.5_
|
||||
|
||||
- [x] 5.2 实现第三方库按需加载
|
||||
- 实现 loadPrismIfNeeded() 方法
|
||||
- 实现 loadZoomifyIfNeeded() 方法
|
||||
- 实现 loadTippyIfNeeded() 方法
|
||||
- _需求:7.1, 7.2, 7.3_
|
||||
|
||||
- [ ]* 5.3 编写资源加载属性测试
|
||||
- **属性 8: 第三方库按需加载**
|
||||
- **属性 9: 第三方库加载缓存**
|
||||
- **验证:需求 7.1, 7.2, 7.3, 7.5**
|
||||
|
||||
- [ ]* 5.4 编写资源加载错误处理测试
|
||||
- 测试加载失败降级方案
|
||||
- _需求:19.4_
|
||||
|
||||
- [ ] 6. 实现渲染优化模块
|
||||
- [x] 6.1 创建 ArgonRenderOptimizer 类
|
||||
- 实现构造函数和读写队列
|
||||
- 实现 read() 和 write() 方法
|
||||
- 实现 _schedule() 和 _flush() 批量处理
|
||||
- _需求:2.3, 2.4, 17.1, 17.2_
|
||||
|
||||
- [~] 6.2 实现 GPU 加速管理
|
||||
- 实现 enableGPU() 方法设置 will-change
|
||||
- 实现 disableGPU() 方法移除 will-change
|
||||
- 实现动画数量限制逻辑
|
||||
- _需求:5.2, 5.3, 5.5_
|
||||
|
||||
- [ ]* 6.3 编写渲染优化属性测试
|
||||
- **属性 6: GPU 加速生命周期**
|
||||
- **属性 7: 同时运行动画数量限制**
|
||||
- **验证:需求 5.2, 5.3, 5.5**
|
||||
|
||||
- [ ] 7. 实现内存管理模块
|
||||
- [~] 7.1 创建 ArgonMemoryManager 类
|
||||
- 实现构造函数和 ID 跟踪集合
|
||||
- 实现 setTimeout()、setInterval()、requestAnimationFrame() 包装方法
|
||||
- 实现对应的清理方法
|
||||
- _需求:12.5, 13.4_
|
||||
|
||||
- [~] 7.2 实现统一清理接口
|
||||
- 实现 clearAll() 方法
|
||||
- 确保所有定时器和动画帧被取消
|
||||
- _需求:13.4_
|
||||
|
||||
- [ ]* 7.3 编写内存管理属性测试
|
||||
- **属性 10: 定时器和动画帧清理**
|
||||
- **验证:需求 12.5, 13.4**
|
||||
|
||||
- [~] 8. 检查点 - 核心模块验证
|
||||
- 确保所有测试通过,询问用户是否有问题
|
||||
|
||||
- [ ] 9. 实现性能监控模块
|
||||
- [~] 9.1 创建 ArgonPerformanceMonitor 类
|
||||
- 实现构造函数和指标存储
|
||||
- 实现 recordMetrics() 使用 Performance API
|
||||
- _需求:18.1_
|
||||
|
||||
- [~] 9.2 实现性能问题检测
|
||||
- 实现 detectIssues() 方法
|
||||
- 检测 DOM 查询频率、事件监听器数量、长任务
|
||||
- 输出警告信息
|
||||
- _需求:18.2_
|
||||
|
||||
- [~] 9.3 实现性能报告功能
|
||||
- 实现 report() 方法
|
||||
- 支持开发模式和生产模式
|
||||
- 提供优化建议
|
||||
- _需求:18.3, 18.4, 18.5_
|
||||
|
||||
- [ ]* 9.4 编写性能监控单元测试
|
||||
- 测试指标记录功能
|
||||
- 测试问题检测和警告
|
||||
- 测试开发/生产模式差异
|
||||
- _需求:18.1, 18.2, 18.3, 18.4, 18.5_
|
||||
|
||||
- [ ] 10. 实现缓存策略优化
|
||||
- [~] 10.1 扩展 DOM 缓存支持 LRU 策略
|
||||
- 添加缓存大小上限配置
|
||||
- 实现访问时间跟踪
|
||||
- 实现 LRU 淘汰逻辑
|
||||
- _需求:14.3, 14.4_
|
||||
|
||||
- [ ]* 10.2 编写缓存策略属性测试
|
||||
- **属性 11: 缓存大小上限**
|
||||
- **属性 12: LRU 缓存淘汰策略**
|
||||
- **验证:需求 14.3, 14.4**
|
||||
|
||||
- [ ] 11. 集成优化模块到主题
|
||||
- [~] 11.1 在 argontheme.js 中引入优化模块
|
||||
- 在文件开头引入 argon-performance.js
|
||||
- 初始化所有优化模块实例
|
||||
- _需求:1.1, 2.1, 3.1_
|
||||
|
||||
- [~] 11.2 替换现有滚动事件处理器
|
||||
- 使用 eventManager.throttle() 包装滚动处理器
|
||||
- 替换 changeToolbarTransparency 函数
|
||||
- 替换 changeLeftbarStickyStatus 函数
|
||||
- _需求:2.1, 2.2, 2.3_
|
||||
|
||||
- [~] 11.3 替换现有 resize 事件处理器
|
||||
- 使用 eventManager.debounce() 包装 resize 处理器
|
||||
- 优化瀑布流布局重新计算
|
||||
- 优化移动端布局切换
|
||||
- _需求:3.1, 3.2, 3.3_
|
||||
|
||||
- [~] 11.4 使用 DOM 缓存替换重复查询
|
||||
- 缓存 toolbar、leftbar、sidebar 等常用元素
|
||||
- 替换所有 querySelector 和 getElementById 调用
|
||||
- _需求:1.1, 1.2_
|
||||
|
||||
- [ ] 12. 实现 PJAX 集成
|
||||
- [~] 12.1 在 PJAX 事件中集成优化模块
|
||||
- 在 pjax:beforeReplace 中清理事件监听器
|
||||
- 在 pjax:end 中重新初始化 DOM 缓存
|
||||
- 清理定时器和动画帧
|
||||
- _需求:1.3, 2.5, 11.4, 13.4_
|
||||
|
||||
- [ ]* 12.2 编写 PJAX 集成属性测试
|
||||
- **属性 2: DOM 缓存 PJAX 重置**
|
||||
- **验证:需求 1.3**
|
||||
|
||||
- [~] 13. 检查点 - 集成验证
|
||||
- 确保所有测试通过,询问用户是否有问题
|
||||
|
||||
- [ ] 14. 实现响应式图片优化
|
||||
- [~] 14.1 添加响应式图片加载逻辑
|
||||
- 检测设备像素比
|
||||
- 根据像素比选择合适尺寸图片
|
||||
- _需求:15.1_
|
||||
|
||||
- [~] 14.2 实现 WebP 格式优先加载
|
||||
- 检测浏览器 WebP 支持
|
||||
- 优先加载 WebP 格式图片
|
||||
- _需求:15.2_
|
||||
|
||||
- [ ]* 14.3 编写图片优化属性测试
|
||||
- **属性 13: 响应式图片尺寸选择**
|
||||
- **属性 14: WebP 格式优先级**
|
||||
- **验证:需求 15.1, 15.2**
|
||||
|
||||
- [ ] 15. 实现模块按需加载
|
||||
- [~] 15.1 重构第三方库加载逻辑
|
||||
- 使用 resourceLoader 替换直接加载
|
||||
- 在需要时才加载 Prism、Zoomify、Tippy
|
||||
- _需求:7.1, 7.2, 7.3_
|
||||
|
||||
- [~] 15.2 实现功能模块动态加载
|
||||
- 识别可拆分的功能模块
|
||||
- 实现交互触发时加载
|
||||
- 避免重复加载
|
||||
- _需求:19.3, 19.5_
|
||||
|
||||
- [ ]* 15.3 编写模块加载属性测试
|
||||
- **属性 15: 模块按需加载和缓存**
|
||||
- **验证:需求 19.3, 19.5**
|
||||
|
||||
- [ ] 16. CSS 性能优化
|
||||
- [~] 16.1 审查和优化 CSS 选择器
|
||||
- 识别复杂选择器并简化
|
||||
- 减少嵌套层级
|
||||
- 使用类选择器替代标签选择器
|
||||
- _需求:4.1, 4.2, 4.3_
|
||||
|
||||
- [~] 16.2 优化高成本 CSS 属性
|
||||
- 减少 box-shadow 使用
|
||||
- 限制 backdrop-filter 模糊半径
|
||||
- 优化动画属性使用
|
||||
- _需求:6.1, 6.2, 6.3, 5.4_
|
||||
|
||||
- [ ] 17. 添加性能监控和报告
|
||||
- [~] 17.1 在页面加载时记录性能指标
|
||||
- 调用 performanceMonitor.recordMetrics()
|
||||
- 在开发模式下输出详细报告
|
||||
- _需求:18.1, 18.3_
|
||||
|
||||
- [~] 17.2 添加性能问题自动检测
|
||||
- 定期调用 detectIssues()
|
||||
- 在控制台输出警告和建议
|
||||
- _需求:18.2, 18.5_
|
||||
|
||||
- [~] 18. 最终检查点 - 完整性验证
|
||||
- 运行所有单元测试和属性测试
|
||||
- 进行性能基准测试
|
||||
- 验证优化目标达成
|
||||
- 确保所有测试通过,询问用户是否有问题
|
||||
|
||||
- [ ] 19. 文档和注释
|
||||
- [~] 19.1 添加代码注释
|
||||
- 为所有优化模块添加 JSDoc 注释
|
||||
- 说明关键算法和优化原理
|
||||
|
||||
- [~] 19.2 更新主题文档
|
||||
- 记录性能优化配置选项
|
||||
- 提供性能调优指南
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 任务标记 `*` 的为可选测试任务,可根据项目需求决定是否实施
|
||||
- 每个检查点都应该运行测试并验证功能正确性
|
||||
- 优化过程中要保持向后兼容,不破坏现有功能
|
||||
- 所有性能优化都应该有可测量的指标支持
|
||||
- 遵循项目代码规范(Tab 缩进、单引号、严格相等等)
|
||||
295
RENDER_OPTIMIZER_USAGE.md
Normal file
295
RENDER_OPTIMIZER_USAGE.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# ArgonRenderOptimizer 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
`ArgonRenderOptimizer` 类用于优化 DOM 操作性能,通过批量读写避免布局抖动(Layout Thrashing),并提供 GPU 加速管理功能。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 批量读写 DOM
|
||||
|
||||
**问题:** 频繁交替读写 DOM 会导致浏览器多次重排(reflow),严重影响性能。
|
||||
|
||||
**解决方案:** 使用 `read()` 和 `write()` 方法将操作加入队列,在下一帧统一执行(先读后写)。
|
||||
|
||||
### 2. GPU 加速管理
|
||||
|
||||
**功能:** 通过 `will-change` 属性提示浏览器创建合成层,利用 GPU 加速动画。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```javascript
|
||||
// 创建实例
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
|
||||
// 批量读取 DOM 属性
|
||||
let scrollTop, windowHeight;
|
||||
|
||||
optimizer.read(() => {
|
||||
scrollTop = document.documentElement.scrollTop;
|
||||
windowHeight = window.innerHeight;
|
||||
});
|
||||
|
||||
// 批量写入 DOM
|
||||
optimizer.write(() => {
|
||||
const toolbar = document.querySelector('.navbar');
|
||||
if (toolbar) {
|
||||
toolbar.style.opacity = scrollTop > 100 ? '1' : '0.8';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 滚动事件优化
|
||||
|
||||
```javascript
|
||||
// 错误示例:频繁交替读写导致布局抖动
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollTop = document.documentElement.scrollTop; // 读
|
||||
toolbar.style.opacity = scrollTop > 100 ? '1' : '0.8'; // 写
|
||||
|
||||
const windowHeight = window.innerHeight; // 读
|
||||
sidebar.style.height = windowHeight + 'px'; // 写
|
||||
// 每次滚动都会触发多次重排!
|
||||
});
|
||||
|
||||
// 正确示例:使用 ArgonRenderOptimizer
|
||||
window.addEventListener('scroll', () => {
|
||||
let scrollTop, windowHeight;
|
||||
|
||||
// 批量读取
|
||||
optimizer.read(() => {
|
||||
scrollTop = document.documentElement.scrollTop;
|
||||
windowHeight = window.innerHeight;
|
||||
});
|
||||
|
||||
// 批量写入
|
||||
optimizer.write(() => {
|
||||
toolbar.style.opacity = scrollTop > 100 ? '1' : '0.8';
|
||||
sidebar.style.height = windowHeight + 'px';
|
||||
});
|
||||
// 只会触发一次重排!
|
||||
});
|
||||
```
|
||||
|
||||
### GPU 加速动画
|
||||
|
||||
```javascript
|
||||
const element = document.querySelector('.animated-element');
|
||||
|
||||
// 动画开始前启用 GPU 加速
|
||||
optimizer.enableGPU(element);
|
||||
|
||||
// 执行动画
|
||||
element.style.transform = 'translateX(100px)';
|
||||
|
||||
// 动画结束后禁用 GPU 加速(释放资源)
|
||||
element.addEventListener('transitionend', () => {
|
||||
optimizer.disableGPU(element);
|
||||
}, { once: true });
|
||||
```
|
||||
|
||||
### 复杂场景示例
|
||||
|
||||
```javascript
|
||||
// 瀑布流布局重新计算
|
||||
function recalculateWaterfallLayout() {
|
||||
const items = document.querySelectorAll('.waterfall-item');
|
||||
const itemHeights = [];
|
||||
const columnHeights = [0, 0, 0]; // 3 列
|
||||
|
||||
// 第一步:批量读取所有元素高度
|
||||
optimizer.read(() => {
|
||||
items.forEach(item => {
|
||||
itemHeights.push(item.offsetHeight);
|
||||
});
|
||||
});
|
||||
|
||||
// 第二步:批量写入所有元素位置
|
||||
optimizer.write(() => {
|
||||
items.forEach((item, index) => {
|
||||
// 找到最短的列
|
||||
const minColumn = columnHeights.indexOf(Math.min(...columnHeights));
|
||||
|
||||
// 设置元素位置
|
||||
item.style.position = 'absolute';
|
||||
item.style.left = (minColumn * 33.33) + '%';
|
||||
item.style.top = columnHeights[minColumn] + 'px';
|
||||
|
||||
// 更新列高度
|
||||
columnHeights[minColumn] += itemHeights[index];
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### `read(readFn)`
|
||||
|
||||
将 DOM 读取操作加入队列。
|
||||
|
||||
**参数:**
|
||||
- `readFn` (Function): 读取函数,应该只包含 DOM 读取操作(如 `offsetHeight`、`scrollTop` 等)
|
||||
|
||||
**返回值:** `void`
|
||||
|
||||
### `write(writeFn)`
|
||||
|
||||
将 DOM 写入操作加入队列。
|
||||
|
||||
**参数:**
|
||||
- `writeFn` (Function): 写入函数,应该只包含 DOM 写入操作(如修改 `style`、`className` 等)
|
||||
|
||||
**返回值:** `void`
|
||||
|
||||
### `enableGPU(element)`
|
||||
|
||||
为元素启用 GPU 加速提示。
|
||||
|
||||
**参数:**
|
||||
- `element` (Element): 需要加速的 DOM 元素
|
||||
|
||||
**返回值:** `void`
|
||||
|
||||
**注意:** 不要滥用此功能,过多的合成层会占用大量内存。
|
||||
|
||||
### `disableGPU(element)`
|
||||
|
||||
移除元素的 GPU 加速提示。
|
||||
|
||||
**参数:**
|
||||
- `element` (Element): DOM 元素
|
||||
|
||||
**返回值:** `void`
|
||||
|
||||
## 性能优势
|
||||
|
||||
### 布局抖动消除
|
||||
|
||||
**优化前:**
|
||||
```javascript
|
||||
// 交替读写,触发 4 次重排
|
||||
const h1 = elem1.offsetHeight; // 读 → 重排
|
||||
elem1.style.height = h1 + 10 + 'px'; // 写
|
||||
|
||||
const h2 = elem2.offsetHeight; // 读 → 重排
|
||||
elem2.style.height = h2 + 10 + 'px'; // 写
|
||||
|
||||
const h3 = elem3.offsetHeight; // 读 → 重排
|
||||
elem3.style.height = h3 + 10 + 'px'; // 写
|
||||
|
||||
const h4 = elem4.offsetHeight; // 读 → 重排
|
||||
elem4.style.height = h4 + 10 + 'px'; // 写
|
||||
```
|
||||
|
||||
**优化后:**
|
||||
```javascript
|
||||
// 批量读写,只触发 1 次重排
|
||||
let h1, h2, h3, h4;
|
||||
|
||||
optimizer.read(() => {
|
||||
h1 = elem1.offsetHeight;
|
||||
h2 = elem2.offsetHeight;
|
||||
h3 = elem3.offsetHeight;
|
||||
h4 = elem4.offsetHeight;
|
||||
});
|
||||
|
||||
optimizer.write(() => {
|
||||
elem1.style.height = h1 + 10 + 'px';
|
||||
elem2.style.height = h2 + 10 + 'px';
|
||||
elem3.style.height = h3 + 10 + 'px';
|
||||
elem4.style.height = h4 + 10 + 'px';
|
||||
});
|
||||
```
|
||||
|
||||
**性能提升:** 减少 75% 的重排次数!
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **分离读写操作**:始终将读取和写入操作分开
|
||||
2. **避免在循环中使用**:不要在循环内部调用 `read()` 或 `write()`
|
||||
3. **合理使用 GPU 加速**:只在动画元素上使用,动画结束后及时移除
|
||||
4. **结合事件节流**:在高频事件(如 scroll、resize)中配合使用节流机制
|
||||
|
||||
## 测试
|
||||
|
||||
运行测试文件验证功能:
|
||||
|
||||
```bash
|
||||
# 在浏览器中打开
|
||||
argon-performance.test.html
|
||||
```
|
||||
|
||||
测试包括:
|
||||
- ✓ 读写队列正常工作
|
||||
- ✓ 批量操作顺序正确(先读后写)
|
||||
- ✓ GPU 加速属性设置和移除
|
||||
- ✓ 错误处理机制
|
||||
|
||||
## 相关需求
|
||||
|
||||
- **需求 2.3**: 批量读取 DOM 属性,避免布局抖动
|
||||
- **需求 2.4**: 批量写入 DOM,在读取完成后统一执行
|
||||
- **需求 17.1**: 批量读取所有需要的属性
|
||||
- **需求 17.2**: 在读取完成后批量写入
|
||||
|
||||
## 技术原理
|
||||
|
||||
### 布局抖动(Layout Thrashing)
|
||||
|
||||
当 JavaScript 交替读写 DOM 时,浏览器会被迫多次计算布局:
|
||||
|
||||
1. 读取属性(如 `offsetHeight`)→ 浏览器计算布局
|
||||
2. 修改样式 → 标记布局为脏
|
||||
3. 再次读取属性 → 浏览器重新计算布局
|
||||
4. 再次修改样式 → 再次标记为脏
|
||||
5. ...循环往复
|
||||
|
||||
### 批量处理原理
|
||||
|
||||
`ArgonRenderOptimizer` 使用 `requestAnimationFrame` 将所有操作延迟到下一帧:
|
||||
|
||||
1. 收集所有读取操作到 `readQueue`
|
||||
2. 收集所有写入操作到 `writeQueue`
|
||||
3. 在下一帧统一执行:
|
||||
- 先执行所有读取操作
|
||||
- 再执行所有写入操作
|
||||
4. 浏览器只需计算一次布局
|
||||
|
||||
### GPU 加速原理
|
||||
|
||||
`will-change` 属性提示浏览器:
|
||||
|
||||
1. 为元素创建独立的合成层(Composite Layer)
|
||||
2. 将该层的渲染交给 GPU 处理
|
||||
3. 动画时只需更新该层,不影响其他元素
|
||||
4. 大幅提升动画性能(60fps)
|
||||
|
||||
## 注意事项
|
||||
|
||||
⚠️ **不要滥用 GPU 加速**
|
||||
|
||||
每个合成层都会占用内存,过多的层会导致:
|
||||
- 内存占用过高
|
||||
- 反而降低性能
|
||||
- 移动设备可能崩溃
|
||||
|
||||
建议:
|
||||
- 只在动画元素上使用
|
||||
- 动画结束后立即移除
|
||||
- 同时运行的动画不超过 3 个
|
||||
|
||||
⚠️ **错误处理**
|
||||
|
||||
`_flush()` 方法会捕获并记录错误,但不会中断其他操作的执行。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2026-01-16
|
||||
- ✅ 实现 ArgonRenderOptimizer 类
|
||||
- ✅ 实现读写队列和批量处理
|
||||
- ✅ 实现 GPU 加速管理
|
||||
- ✅ 添加完整的测试用例
|
||||
- ✅ 添加错误处理机制
|
||||
@@ -146,6 +146,77 @@ class ArgonEventManager {
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* 限制函数执行频率,使用 requestAnimationFrame 优化性能
|
||||
*
|
||||
* @param {Function} func - 要节流的函数
|
||||
* @param {number} wait - 等待时间(毫秒),默认 16ms(约 60fps)
|
||||
* @returns {Function} 节流后的函数
|
||||
*/
|
||||
throttle(func, wait = 16) {
|
||||
let lastTime = 0;
|
||||
let timeoutId = null;
|
||||
|
||||
return function(...args) {
|
||||
const now = Date.now();
|
||||
const remaining = wait - (now - lastTime);
|
||||
|
||||
if (remaining <= 0) {
|
||||
// 如果距离上次执行已超过等待时间,立即执行
|
||||
if (timeoutId) {
|
||||
cancelAnimationFrame(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
lastTime = now;
|
||||
func.apply(this, args);
|
||||
} else if (!timeoutId) {
|
||||
// 否则使用 requestAnimationFrame 在下一帧执行
|
||||
timeoutId = requestAnimationFrame(() => {
|
||||
lastTime = Date.now();
|
||||
timeoutId = null;
|
||||
func.apply(this, args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* 延迟执行函数,直到事件停止触发指定时间后才执行
|
||||
* 如果在等待期间再次触发,则重新计时
|
||||
*
|
||||
* @param {Function} func - 要防抖的函数
|
||||
* @param {number} wait - 等待时间(毫秒),默认 150ms
|
||||
* @returns {Function} 防抖后的函数,包含 cancel 方法用于取消待执行任务
|
||||
*/
|
||||
debounce(func, wait = 150) {
|
||||
let timeoutId = null;
|
||||
|
||||
const debounced = function(...args) {
|
||||
// 取消之前的待执行任务
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
// 设置新的延迟执行任务
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
func.apply(this, args);
|
||||
}, wait);
|
||||
};
|
||||
|
||||
// 提供取消方法,允许手动取消待执行任务
|
||||
debounced.cancel = function() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
};
|
||||
|
||||
return debounced;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* 自动跟踪监听器以便后续清理
|
||||
@@ -230,6 +301,271 @@ class ArgonEventManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 资源加载模块
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* 资源加载类
|
||||
* 提供第三方库按需加载和缓存管理功能
|
||||
*
|
||||
* @class ArgonResourceLoader
|
||||
*/
|
||||
class ArgonResourceLoader {
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化加载状态管理
|
||||
*/
|
||||
constructor() {
|
||||
this.loaded = new Set(); // 已加载的资源集合
|
||||
this.loading = new Map(); // 正在加载的资源 Promise Map
|
||||
}
|
||||
|
||||
/**
|
||||
* 按需加载脚本
|
||||
* 如果资源已加载或正在加载,则复用缓存
|
||||
*
|
||||
* @param {string} name - 资源名称(如 'prism', 'zoomify', 'tippy')
|
||||
* @param {string} url - 资源 URL
|
||||
* @returns {Promise<void>} 加载完成的 Promise
|
||||
*/
|
||||
async loadScript(name, url) {
|
||||
// 如果已加载,直接返回
|
||||
if (this.loaded.has(name)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// 如果正在加载,返回现有的 Promise
|
||||
if (this.loading.has(name)) {
|
||||
return this.loading.get(name);
|
||||
}
|
||||
|
||||
// 创建新的加载 Promise
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
|
||||
script.onload = () => {
|
||||
this.loaded.add(name);
|
||||
this.loading.delete(name);
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
this.loading.delete(name);
|
||||
reject(new Error(`Failed to load ${name} from ${url}`));
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
this.loading.set(name, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查页面是否需要某个库
|
||||
* 通过 CSS 选择器判断页面中是否存在需要该库的元素
|
||||
*
|
||||
* @param {string} selector - CSS 选择器
|
||||
* @returns {boolean} 如果页面包含匹配的元素则返回 true
|
||||
*/
|
||||
needsLibrary(selector) {
|
||||
return document.querySelector(selector) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件加载 Prism 代码高亮库
|
||||
* 仅在页面包含代码块时加载
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadPrismIfNeeded() {
|
||||
// 检查是否需要加载
|
||||
if (!this.needsLibrary('pre code')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if (typeof window.Prism !== 'undefined' && window.Prism.highlightAll) {
|
||||
this.loaded.add('prism');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载 Prism
|
||||
if (typeof argonConfig !== 'undefined' && argonConfig.prism_url) {
|
||||
await this.loadScript('prism', argonConfig.prism_url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件加载 Zoomify 图片放大库
|
||||
* 仅在页面包含图片时加载
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadZoomifyIfNeeded() {
|
||||
// 检查是否需要加载
|
||||
if (!this.needsLibrary('.post-content img')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if (typeof window.Zoomify !== 'undefined') {
|
||||
this.loaded.add('zoomify');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载 Zoomify
|
||||
if (typeof argonConfig !== 'undefined' && argonConfig.zoomify_url) {
|
||||
await this.loadScript('zoomify', argonConfig.zoomify_url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件加载 Tippy 提示框库
|
||||
* 仅在页面包含提示框元素时加载
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadTippyIfNeeded() {
|
||||
// 检查是否需要加载
|
||||
if (!this.needsLibrary('[data-tippy-content]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if (typeof window.tippy !== 'undefined') {
|
||||
this.loaded.add('tippy');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载 Tippy
|
||||
if (typeof argonConfig !== 'undefined' && argonConfig.tippy_url) {
|
||||
await this.loadScript('tippy', argonConfig.tippy_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 渲染优化模块
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* 渲染优化类
|
||||
* 批量读写 DOM 避免布局抖动,管理 GPU 加速属性
|
||||
*
|
||||
* @class ArgonRenderOptimizer
|
||||
*/
|
||||
class ArgonRenderOptimizer {
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化读写队列和调度状态
|
||||
*/
|
||||
constructor() {
|
||||
this.readQueue = []; // DOM 读取操作队列
|
||||
this.writeQueue = []; // DOM 写入操作队列
|
||||
this.scheduled = false; // 是否已调度执行
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量读取 DOM 属性
|
||||
* 将读取操作加入队列,在下一帧统一执行
|
||||
*
|
||||
* @param {Function} readFn - 读取函数,应该只包含 DOM 读取操作
|
||||
* @returns {void}
|
||||
*/
|
||||
read(readFn) {
|
||||
this.readQueue.push(readFn);
|
||||
this._schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量写入 DOM
|
||||
* 将写入操作加入队列,在所有读取操作完成后统一执行
|
||||
*
|
||||
* @param {Function} writeFn - 写入函数,应该只包含 DOM 写入操作
|
||||
* @returns {void}
|
||||
*/
|
||||
write(writeFn) {
|
||||
this.writeQueue.push(writeFn);
|
||||
this._schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度执行
|
||||
* 使用 requestAnimationFrame 在下一帧执行队列中的操作
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_schedule() {
|
||||
if (this.scheduled) return;
|
||||
|
||||
this.scheduled = true;
|
||||
requestAnimationFrame(() => {
|
||||
this._flush();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行队列
|
||||
* 先执行所有读取操作,再执行所有写入操作
|
||||
* 这样可以避免布局抖动(layout thrashing)
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_flush() {
|
||||
// 先执行所有读取操作
|
||||
while (this.readQueue.length) {
|
||||
const readFn = this.readQueue.shift();
|
||||
try {
|
||||
readFn();
|
||||
} catch (error) {
|
||||
console.error('Error in read operation:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 再执行所有写入操作
|
||||
while (this.writeQueue.length) {
|
||||
const writeFn = this.writeQueue.shift();
|
||||
try {
|
||||
writeFn();
|
||||
} catch (error) {
|
||||
console.error('Error in write operation:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.scheduled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 GPU 加速提示
|
||||
* 设置 will-change 属性,提示浏览器创建合成层
|
||||
*
|
||||
* @param {Element} element - DOM 元素
|
||||
* @returns {void}
|
||||
*/
|
||||
enableGPU(element) {
|
||||
if (!element) return;
|
||||
element.style.willChange = 'transform, opacity';
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 GPU 加速提示
|
||||
* 移除 will-change 属性,释放资源
|
||||
*
|
||||
* @param {Element} element - DOM 元素
|
||||
* @returns {void}
|
||||
*/
|
||||
disableGPU(element) {
|
||||
if (!element) return;
|
||||
element.style.willChange = 'auto';
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 模块导出和初始化接口
|
||||
// ==========================================================================
|
||||
@@ -253,5 +589,7 @@ if (typeof window !== 'undefined') {
|
||||
window.ArgonPerformanceConfig = ArgonPerformanceConfig;
|
||||
window.ArgonDOMCache = ArgonDOMCache;
|
||||
window.ArgonEventManager = ArgonEventManager;
|
||||
window.ArgonResourceLoader = ArgonResourceLoader;
|
||||
window.ArgonRenderOptimizer = ArgonRenderOptimizer;
|
||||
window.initArgonPerformance = initArgonPerformance;
|
||||
}
|
||||
|
||||
587
argon-performance.test.html
Normal file
587
argon-performance.test.html
Normal file
@@ -0,0 +1,587 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Argon 性能优化模块 - 基础测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.test-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.test-section h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.test-result {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.test-result.pass {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.test-result.fail {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.test-result.info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
button {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
#test-elements {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🚀 Argon 性能优化模块 - 基础测试</h1>
|
||||
|
||||
<!-- 测试用的 DOM 元素 -->
|
||||
<div id="test-elements">
|
||||
<div class="navbar">Navbar</div>
|
||||
<div id="content">Content</div>
|
||||
<div id="leftbar">Leftbar</div>
|
||||
<div id="sidebar">Sidebar</div>
|
||||
<div id="fabtn_back_to_top">Back to Top</div>
|
||||
<div id="fabtn_reading_progress">Reading Progress</div>
|
||||
<div id="comments">Comments</div>
|
||||
<div id="post_comment">Post Comment</div>
|
||||
</div>
|
||||
|
||||
<!-- DOM 缓存测试 -->
|
||||
<div class="test-section">
|
||||
<h2>1. DOM 缓存模块测试</h2>
|
||||
<button onclick="testDOMCache()">运行测试</button>
|
||||
<div id="dom-cache-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- 事件管理测试 -->
|
||||
<div class="test-section">
|
||||
<h2>2. 事件管理模块测试</h2>
|
||||
<button onclick="testEventManager()">运行测试</button>
|
||||
<div id="event-manager-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- 节流测试 -->
|
||||
<div class="test-section">
|
||||
<h2>3. 节流(Throttle)功能测试</h2>
|
||||
<button onclick="testThrottle()">运行测试</button>
|
||||
<div id="throttle-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- 防抖测试 -->
|
||||
<div class="test-section">
|
||||
<h2>4. 防抖(Debounce)功能测试</h2>
|
||||
<button onclick="testDebounce()">运行测试</button>
|
||||
<div id="debounce-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- 资源加载测试 -->
|
||||
<div class="test-section">
|
||||
<h2>5. 资源加载模块测试</h2>
|
||||
<button onclick="testResourceLoader()">运行测试</button>
|
||||
<div id="resource-loader-results"></div>
|
||||
<!-- 测试用的元素 -->
|
||||
<div style="display: none;">
|
||||
<pre><code>console.log('test');</code></pre>
|
||||
<div class="post-content"><img src="test.jpg" alt="test"></div>
|
||||
<div data-tippy-content="Test tooltip">Hover me</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 渲染优化测试 -->
|
||||
<div class="test-section">
|
||||
<h2>6. 渲染优化模块测试</h2>
|
||||
<button onclick="testRenderOptimizer()">运行测试</button>
|
||||
<div id="render-optimizer-results"></div>
|
||||
<div id="test-animation-element" style="width: 100px; height: 100px; background: #4CAF50; margin: 10px 0;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 综合测试 -->
|
||||
<div class="test-section">
|
||||
<h2>7. 综合测试</h2>
|
||||
<button onclick="runAllTests()">运行所有测试</button>
|
||||
<div id="all-tests-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- 加载性能优化模块 -->
|
||||
<script src="argon-performance.js"></script>
|
||||
|
||||
<script>
|
||||
// 工具函数:输出测试结果
|
||||
function logResult(containerId, message, type = 'info') {
|
||||
const container = document.getElementById(containerId);
|
||||
const div = document.createElement('div');
|
||||
div.className = `test-result ${type}`;
|
||||
div.textContent = message;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function clearResults(containerId) {
|
||||
document.getElementById(containerId).innerHTML = '';
|
||||
}
|
||||
|
||||
// 测试 1: DOM 缓存模块
|
||||
function testDOMCache() {
|
||||
clearResults('dom-cache-results');
|
||||
logResult('dom-cache-results', '开始测试 DOM 缓存模块...', 'info');
|
||||
|
||||
try {
|
||||
// 创建实例
|
||||
const cache = new ArgonDOMCache();
|
||||
logResult('dom-cache-results', '✓ ArgonDOMCache 实例创建成功', 'pass');
|
||||
|
||||
// 测试初始化
|
||||
cache.init();
|
||||
if (cache.initialized) {
|
||||
logResult('dom-cache-results', '✓ 缓存初始化成功', 'pass');
|
||||
} else {
|
||||
logResult('dom-cache-results', '✗ 缓存初始化失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试获取存在的元素
|
||||
const toolbar = cache.get('toolbar');
|
||||
if (toolbar && toolbar.classList.contains('navbar')) {
|
||||
logResult('dom-cache-results', '✓ 成功获取缓存的 toolbar 元素', 'pass');
|
||||
} else {
|
||||
logResult('dom-cache-results', '✗ 获取 toolbar 元素失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试获取不存在的元素
|
||||
const nonExistent = cache.get('non-existent');
|
||||
if (nonExistent === null) {
|
||||
logResult('dom-cache-results', '✓ 不存在的元素正确返回 null', 'pass');
|
||||
} else {
|
||||
logResult('dom-cache-results', '✗ 不存在的元素应返回 null', 'fail');
|
||||
}
|
||||
|
||||
// 测试设置缓存
|
||||
const testElement = document.createElement('div');
|
||||
cache.set('test-element', testElement);
|
||||
if (cache.get('test-element') === testElement) {
|
||||
logResult('dom-cache-results', '✓ 设置和获取自定义缓存成功', 'pass');
|
||||
} else {
|
||||
logResult('dom-cache-results', '✗ 设置自定义缓存失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试清空缓存
|
||||
cache.clear();
|
||||
if (!cache.initialized && cache.get('toolbar') === null) {
|
||||
logResult('dom-cache-results', '✓ 清空缓存成功', 'pass');
|
||||
} else {
|
||||
logResult('dom-cache-results', '✗ 清空缓存失败', 'fail');
|
||||
}
|
||||
|
||||
logResult('dom-cache-results', '✅ DOM 缓存模块测试完成', 'info');
|
||||
} catch (error) {
|
||||
logResult('dom-cache-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 2: 事件管理模块
|
||||
function testEventManager() {
|
||||
clearResults('event-manager-results');
|
||||
logResult('event-manager-results', '开始测试事件管理模块...', 'info');
|
||||
|
||||
try {
|
||||
// 创建实例
|
||||
const eventManager = new ArgonEventManager();
|
||||
logResult('event-manager-results', '✓ ArgonEventManager 实例创建成功', 'pass');
|
||||
|
||||
// 测试添加事件监听器
|
||||
let clickCount = 0;
|
||||
const testElement = document.createElement('button');
|
||||
const handler = () => { clickCount++; };
|
||||
|
||||
eventManager.on(testElement, 'click', handler);
|
||||
testElement.click();
|
||||
|
||||
if (clickCount === 1) {
|
||||
logResult('event-manager-results', '✓ 事件监听器添加和触发成功', 'pass');
|
||||
} else {
|
||||
logResult('event-manager-results', '✗ 事件监听器触发失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试移除事件监听器
|
||||
eventManager.off(testElement, 'click');
|
||||
testElement.click();
|
||||
|
||||
if (clickCount === 1) {
|
||||
logResult('event-manager-results', '✓ 事件监听器移除成功', 'pass');
|
||||
} else {
|
||||
logResult('event-manager-results', '✗ 事件监听器移除失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试清空所有监听器
|
||||
let count1 = 0, count2 = 0;
|
||||
const elem1 = document.createElement('div');
|
||||
const elem2 = document.createElement('div');
|
||||
|
||||
eventManager.on(elem1, 'click', () => { count1++; });
|
||||
eventManager.on(elem2, 'click', () => { count2++; });
|
||||
|
||||
eventManager.clear();
|
||||
elem1.click();
|
||||
elem2.click();
|
||||
|
||||
if (count1 === 0 && count2 === 0) {
|
||||
logResult('event-manager-results', '✓ 清空所有监听器成功', 'pass');
|
||||
} else {
|
||||
logResult('event-manager-results', '✗ 清空所有监听器失败', 'fail');
|
||||
}
|
||||
|
||||
logResult('event-manager-results', '✅ 事件管理模块测试完成', 'info');
|
||||
} catch (error) {
|
||||
logResult('event-manager-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 3: 节流功能
|
||||
function testThrottle() {
|
||||
clearResults('throttle-results');
|
||||
logResult('throttle-results', '开始测试节流功能...', 'info');
|
||||
|
||||
try {
|
||||
const eventManager = new ArgonEventManager();
|
||||
let execCount = 0;
|
||||
|
||||
const throttledFunc = eventManager.throttle(() => {
|
||||
execCount++;
|
||||
}, 50);
|
||||
|
||||
// 快速调用 10 次
|
||||
for (let i = 0; i < 10; i++) {
|
||||
throttledFunc();
|
||||
}
|
||||
|
||||
logResult('throttle-results', `立即调用 10 次,实际执行: ${execCount} 次`, 'info');
|
||||
|
||||
// 等待一段时间后检查
|
||||
setTimeout(() => {
|
||||
logResult('throttle-results', `等待 200ms 后,总执行次数: ${execCount} 次`, 'info');
|
||||
|
||||
if (execCount >= 1 && execCount <= 5) {
|
||||
logResult('throttle-results', '✓ 节流功能正常工作(执行次数在合理范围内)', 'pass');
|
||||
} else {
|
||||
logResult('throttle-results', `✗ 节流功能异常(执行次数: ${execCount})`, 'fail');
|
||||
}
|
||||
|
||||
logResult('throttle-results', '✅ 节流功能测试完成', 'info');
|
||||
}, 200);
|
||||
|
||||
} catch (error) {
|
||||
logResult('throttle-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 4: 防抖功能
|
||||
function testDebounce() {
|
||||
clearResults('debounce-results');
|
||||
logResult('debounce-results', '开始测试防抖功能...', 'info');
|
||||
|
||||
try {
|
||||
const eventManager = new ArgonEventManager();
|
||||
let execCount = 0;
|
||||
|
||||
const debouncedFunc = eventManager.debounce(() => {
|
||||
execCount++;
|
||||
logResult('debounce-results', `防抖函数执行,当前执行次数: ${execCount}`, 'info');
|
||||
}, 100);
|
||||
|
||||
// 快速调用 10 次
|
||||
for (let i = 0; i < 10; i++) {
|
||||
debouncedFunc();
|
||||
}
|
||||
|
||||
logResult('debounce-results', '快速调用 10 次,等待防抖延迟...', 'info');
|
||||
|
||||
// 等待防抖延迟后检查
|
||||
setTimeout(() => {
|
||||
if (execCount === 1) {
|
||||
logResult('debounce-results', '✓ 防抖功能正常工作(只执行了 1 次)', 'pass');
|
||||
} else {
|
||||
logResult('debounce-results', `✗ 防抖功能异常(执行了 ${execCount} 次)`, 'fail');
|
||||
}
|
||||
|
||||
// 测试取消功能
|
||||
execCount = 0;
|
||||
debouncedFunc();
|
||||
debouncedFunc.cancel();
|
||||
|
||||
setTimeout(() => {
|
||||
if (execCount === 0) {
|
||||
logResult('debounce-results', '✓ 防抖取消功能正常工作', 'pass');
|
||||
} else {
|
||||
logResult('debounce-results', '✗ 防抖取消功能失败', 'fail');
|
||||
}
|
||||
|
||||
logResult('debounce-results', '✅ 防抖功能测试完成', 'info');
|
||||
}, 150);
|
||||
}, 150);
|
||||
|
||||
} catch (error) {
|
||||
logResult('debounce-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 5: 资源加载模块
|
||||
function testResourceLoader() {
|
||||
clearResults('resource-loader-results');
|
||||
logResult('resource-loader-results', '开始测试资源加载模块...', 'info');
|
||||
|
||||
try {
|
||||
// 创建实例
|
||||
const loader = new ArgonResourceLoader();
|
||||
logResult('resource-loader-results', '✓ ArgonResourceLoader 实例创建成功', 'pass');
|
||||
|
||||
// 测试 needsLibrary 方法
|
||||
const needsPrism = loader.needsLibrary('pre code');
|
||||
if (needsPrism) {
|
||||
logResult('resource-loader-results', '✓ needsLibrary 检测到页面包含代码块', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ needsLibrary 未能检测到代码块', 'fail');
|
||||
}
|
||||
|
||||
const needsZoomify = loader.needsLibrary('.post-content img');
|
||||
if (needsZoomify) {
|
||||
logResult('resource-loader-results', '✓ needsLibrary 检测到页面包含图片', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ needsLibrary 未能检测到图片', 'fail');
|
||||
}
|
||||
|
||||
const needsTippy = loader.needsLibrary('[data-tippy-content]');
|
||||
if (needsTippy) {
|
||||
logResult('resource-loader-results', '✓ needsLibrary 检测到页面包含提示框元素', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ needsLibrary 未能检测到提示框元素', 'fail');
|
||||
}
|
||||
|
||||
// 测试不存在的元素
|
||||
const needsNonExistent = loader.needsLibrary('.non-existent-element');
|
||||
if (!needsNonExistent) {
|
||||
logResult('resource-loader-results', '✓ needsLibrary 正确返回 false(不存在的元素)', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ needsLibrary 应返回 false', 'fail');
|
||||
}
|
||||
|
||||
// 测试 loadScript 方法(使用测试 URL)
|
||||
logResult('resource-loader-results', '测试 loadScript 方法...', 'info');
|
||||
|
||||
// 创建一个测试脚本
|
||||
const testScriptContent = 'window.testLibLoaded = true;';
|
||||
const blob = new Blob([testScriptContent], { type: 'application/javascript' });
|
||||
const testUrl = URL.createObjectURL(blob);
|
||||
|
||||
loader.loadScript('test-lib', testUrl)
|
||||
.then(() => {
|
||||
if (window.testLibLoaded) {
|
||||
logResult('resource-loader-results', '✓ loadScript 成功加载脚本', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ loadScript 加载脚本失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试缓存机制
|
||||
if (loader.loaded.has('test-lib')) {
|
||||
logResult('resource-loader-results', '✓ 加载的脚本已添加到缓存', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ 脚本未添加到缓存', 'fail');
|
||||
}
|
||||
|
||||
// 测试重复加载
|
||||
const startTime = Date.now();
|
||||
return loader.loadScript('test-lib', testUrl).then(() => {
|
||||
const loadTime = Date.now() - startTime;
|
||||
if (loadTime < 10) {
|
||||
logResult('resource-loader-results', '✓ 重复加载使用缓存(立即返回)', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', `⚠ 重复加载耗时 ${loadTime}ms(可能未使用缓存)`, 'fail');
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// 测试加载失败的情况
|
||||
return loader.loadScript('invalid-lib', 'https://invalid-url-that-does-not-exist.com/script.js')
|
||||
.then(() => {
|
||||
logResult('resource-loader-results', '✗ 加载失败应该抛出错误', 'fail');
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.message.includes('Failed to load')) {
|
||||
logResult('resource-loader-results', '✓ 加载失败正确抛出错误', 'pass');
|
||||
} else {
|
||||
logResult('resource-loader-results', '✗ 错误信息不正确', 'fail');
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logResult('resource-loader-results', '✅ 资源加载模块测试完成', 'info');
|
||||
URL.revokeObjectURL(testUrl);
|
||||
})
|
||||
.catch((error) => {
|
||||
logResult('resource-loader-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logResult('resource-loader-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 6: 渲染优化模块
|
||||
function testRenderOptimizer() {
|
||||
clearResults('render-optimizer-results');
|
||||
logResult('render-optimizer-results', '开始测试渲染优化模块...', 'info');
|
||||
|
||||
try {
|
||||
// 创建实例
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
logResult('render-optimizer-results', '✓ ArgonRenderOptimizer 实例创建成功', 'pass');
|
||||
|
||||
// 测试读写队列
|
||||
let readValue = 0;
|
||||
let writeValue = 0;
|
||||
|
||||
optimizer.read(() => {
|
||||
readValue = document.body.offsetHeight; // 读取操作
|
||||
logResult('render-optimizer-results', `读取操作执行,获取高度: ${readValue}px`, 'info');
|
||||
});
|
||||
|
||||
optimizer.write(() => {
|
||||
const testDiv = document.createElement('div');
|
||||
testDiv.textContent = '测试写入操作';
|
||||
writeValue = 1;
|
||||
logResult('render-optimizer-results', '写入操作执行', 'info');
|
||||
});
|
||||
|
||||
// 等待 requestAnimationFrame 执行
|
||||
setTimeout(() => {
|
||||
if (readValue > 0 && writeValue === 1) {
|
||||
logResult('render-optimizer-results', '✓ 读写队列正常工作', 'pass');
|
||||
} else {
|
||||
logResult('render-optimizer-results', '✗ 读写队列执行失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试批量操作顺序
|
||||
const operations = [];
|
||||
|
||||
optimizer.read(() => {
|
||||
operations.push('read1');
|
||||
});
|
||||
|
||||
optimizer.write(() => {
|
||||
operations.push('write1');
|
||||
});
|
||||
|
||||
optimizer.read(() => {
|
||||
operations.push('read2');
|
||||
});
|
||||
|
||||
optimizer.write(() => {
|
||||
operations.push('write2');
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const expectedOrder = ['read1', 'read2', 'write1', 'write2'];
|
||||
const orderCorrect = JSON.stringify(operations) === JSON.stringify(expectedOrder);
|
||||
|
||||
if (orderCorrect) {
|
||||
logResult('render-optimizer-results', '✓ 批量操作顺序正确(先读后写)', 'pass');
|
||||
} else {
|
||||
logResult('render-optimizer-results', `✗ 操作顺序错误: ${operations.join(', ')}`, 'fail');
|
||||
}
|
||||
|
||||
// 测试 GPU 加速
|
||||
const testElement = document.getElementById('test-animation-element');
|
||||
|
||||
optimizer.enableGPU(testElement);
|
||||
const willChangeEnabled = testElement.style.willChange === 'transform, opacity';
|
||||
|
||||
if (willChangeEnabled) {
|
||||
logResult('render-optimizer-results', '✓ enableGPU 正确设置 will-change 属性', 'pass');
|
||||
} else {
|
||||
logResult('render-optimizer-results', '✗ enableGPU 设置失败', 'fail');
|
||||
}
|
||||
|
||||
optimizer.disableGPU(testElement);
|
||||
const willChangeDisabled = testElement.style.willChange === 'auto';
|
||||
|
||||
if (willChangeDisabled) {
|
||||
logResult('render-optimizer-results', '✓ disableGPU 正确移除 will-change 属性', 'pass');
|
||||
} else {
|
||||
logResult('render-optimizer-results', '✗ disableGPU 移除失败', 'fail');
|
||||
}
|
||||
|
||||
// 测试错误处理
|
||||
optimizer.read(() => {
|
||||
throw new Error('测试错误');
|
||||
});
|
||||
|
||||
optimizer.write(() => {
|
||||
logResult('render-optimizer-results', '✓ 错误处理正常,后续操作继续执行', 'pass');
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
logResult('render-optimizer-results', '✅ 渲染优化模块测试完成', 'info');
|
||||
}, 100);
|
||||
}, 100);
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
logResult('render-optimizer-results', `✗ 测试出错: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行所有测试
|
||||
function runAllTests() {
|
||||
clearResults('all-tests-results');
|
||||
logResult('all-tests-results', '🚀 开始运行所有测试...', 'info');
|
||||
|
||||
testDOMCache();
|
||||
testEventManager();
|
||||
testThrottle();
|
||||
testDebounce();
|
||||
testResourceLoader();
|
||||
testRenderOptimizer();
|
||||
|
||||
setTimeout(() => {
|
||||
logResult('all-tests-results', '✅ 所有测试已启动,请查看各模块的测试结果', 'info');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 页面加载完成后的提示
|
||||
window.addEventListener('load', () => {
|
||||
console.log('Argon 性能优化模块测试页面已加载');
|
||||
console.log('ArgonPerformanceConfig:', ArgonPerformanceConfig);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user