1224 lines
30 KiB
Markdown
1224 lines
30 KiB
Markdown
|
|
# 设计文档
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本设计文档针对 Argon WordPress 主题的资源使用和 CPU 占用问题,提供系统性的优化方案。通过分析主题核心代码(argontheme.js ~3700 行、style.css ~12000 行),识别出以下主要性能瓶颈:
|
|||
|
|
|
|||
|
|
**关键问题:**
|
|||
|
|
1. 滚动和 resize 事件处理器未使用节流/防抖,导致高频执行
|
|||
|
|
2. DOM 元素重复查询,缺少缓存机制
|
|||
|
|
3. 事件监听器未正确清理,存在内存泄漏风险
|
|||
|
|
4. 第三方库全量加载,未按需加载
|
|||
|
|
5. CSS 选择器复杂度高,样式计算耗时
|
|||
|
|
6. 动画未充分利用 GPU 加速
|
|||
|
|
|
|||
|
|
**优化策略:**
|
|||
|
|
- 实现事件节流和防抖机制
|
|||
|
|
- 建立 DOM 缓存系统
|
|||
|
|
- 完善事件监听器生命周期管理
|
|||
|
|
- 实现第三方库按需加载
|
|||
|
|
- 优化 CSS 选择器和动画性能
|
|||
|
|
- 添加性能监控和分析工具
|
|||
|
|
|
|||
|
|
本设计与现有的 `global-ui-optimization` 和 `pjax-lazyload-optimization` 互补,专注于底层性能优化。
|
|||
|
|
|
|||
|
|
## 架构
|
|||
|
|
|
|||
|
|
### 整体架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ Argon 主题性能优化层 │
|
|||
|
|
├─────────────────────────────────────────────────────────────┤
|
|||
|
|
│ │
|
|||
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|||
|
|
│ │ DOM 缓存层 │ │ 事件管理层 │ │ 资源加载层 │ │
|
|||
|
|
│ │ │ │ │ │ │ │
|
|||
|
|
│ │ - 元素缓存 │ │ - 节流/防抖 │ │ - 按需加载 │ │
|
|||
|
|
│ │ - 查询优化 │ │ - 监听器管理 │ │ - 预加载策略 │ │
|
|||
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|||
|
|
│ │ 渲染优化层 │ │ 内存管理层 │ │ 监控分析层 │ │
|
|||
|
|
│ │ │ │ │ │ │ │
|
|||
|
|
│ │ - GPU 加速 │ │ - 清理机制 │ │ - 性能指标 │ │
|
|||
|
|
│ │ - 布局优化 │ │ - 引用管理 │ │ - 问题检测 │ │
|
|||
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 现有主题代码 │
|
|||
|
|
│ argontheme.js │ style.css │ functions.php │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 模块划分
|
|||
|
|
|
|||
|
|
**1. DOM 缓存模块 (ArgonDOMCache)**
|
|||
|
|
- 缓存频繁访问的 DOM 元素
|
|||
|
|
- 提供统一的查询接口
|
|||
|
|
- PJAX 页面切换时自动更新缓存
|
|||
|
|
|
|||
|
|
**2. 事件管理模块 (ArgonEventManager)**
|
|||
|
|
- 提供节流和防抖工具函数
|
|||
|
|
- 管理事件监听器的生命周期
|
|||
|
|
- 支持批量清理和重新绑定
|
|||
|
|
|
|||
|
|
**3. 资源加载模块 (ArgonResourceLoader)**
|
|||
|
|
- 按需加载第三方库
|
|||
|
|
- 实现资源预加载策略
|
|||
|
|
- 管理加载状态和缓存
|
|||
|
|
|
|||
|
|
**4. 渲染优化模块 (ArgonRenderOptimizer)**
|
|||
|
|
- 批量读写 DOM 避免布局抖动
|
|||
|
|
- 管理 GPU 加速属性
|
|||
|
|
- 优化动画性能
|
|||
|
|
|
|||
|
|
**5. 内存管理模块 (ArgonMemoryManager)**
|
|||
|
|
- 清理事件监听器
|
|||
|
|
- 管理闭包引用
|
|||
|
|
- 清理定时器和动画帧
|
|||
|
|
|
|||
|
|
**6. 性能监控模块 (ArgonPerformanceMonitor)**
|
|||
|
|
- 记录关键性能指标
|
|||
|
|
- 检测性能问题
|
|||
|
|
- 提供优化建议
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 组件和接口
|
|||
|
|
|
|||
|
|
### 1. DOM 缓存模块
|
|||
|
|
|
|||
|
|
**ArgonDOMCache 类**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ArgonDOMCache {
|
|||
|
|
constructor() {
|
|||
|
|
this.cache = new Map();
|
|||
|
|
this.initialized = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化缓存
|
|||
|
|
*/
|
|||
|
|
init() {
|
|||
|
|
this.cache.clear();
|
|||
|
|
|
|||
|
|
// 缓存常用元素
|
|||
|
|
this.set('toolbar', document.querySelector('.navbar'));
|
|||
|
|
this.set('content', document.getElementById('content'));
|
|||
|
|
this.set('leftbar', document.getElementById('leftbar'));
|
|||
|
|
this.set('sidebar', document.getElementById('sidebar'));
|
|||
|
|
this.set('backToTopBtn', document.getElementById('fabtn_back_to_top'));
|
|||
|
|
this.set('readingProgress', document.getElementById('fabtn_reading_progress'));
|
|||
|
|
this.set('comments', document.getElementById('comments'));
|
|||
|
|
this.set('postComment', document.getElementById('post_comment'));
|
|||
|
|
|
|||
|
|
this.initialized = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取缓存的元素
|
|||
|
|
* @param {string} key - 元素键名
|
|||
|
|
* @returns {Element|null}
|
|||
|
|
*/
|
|||
|
|
get(key) {
|
|||
|
|
if (!this.cache.has(key)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return this.cache.get(key);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置缓存
|
|||
|
|
* @param {string} key - 元素键名
|
|||
|
|
* @param {Element} element - DOM 元素
|
|||
|
|
*/
|
|||
|
|
set(key, element) {
|
|||
|
|
this.cache.set(key, element);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清空缓存
|
|||
|
|
*/
|
|||
|
|
clear() {
|
|||
|
|
this.cache.clear();
|
|||
|
|
this.initialized = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用示例:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 初始化
|
|||
|
|
const domCache = new ArgonDOMCache();
|
|||
|
|
domCache.init();
|
|||
|
|
|
|||
|
|
// 使用缓存而非重复查询
|
|||
|
|
const toolbar = domCache.get('toolbar');
|
|||
|
|
if (toolbar) {
|
|||
|
|
toolbar.style.opacity = '0.8';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PJAX 页面切换时重新初始化
|
|||
|
|
$(document).on('pjax:end', function() {
|
|||
|
|
domCache.init();
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 事件管理模块
|
|||
|
|
|
|||
|
|
**ArgonEventManager 类**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ArgonEventManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.listeners = new Map();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 节流函数
|
|||
|
|
* @param {Function} func - 要节流的函数
|
|||
|
|
* @param {number} wait - 等待时间(毫秒)
|
|||
|
|
* @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) {
|
|||
|
|
timeoutId = requestAnimationFrame(() => {
|
|||
|
|
lastTime = Date.now();
|
|||
|
|
timeoutId = null;
|
|||
|
|
func.apply(this, args);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 防抖函数
|
|||
|
|
* @param {Function} func - 要防抖的函数
|
|||
|
|
* @param {number} wait - 等待时间(毫秒)
|
|||
|
|
* @returns {Function}
|
|||
|
|
*/
|
|||
|
|
debounce(func, wait = 150) {
|
|||
|
|
let timeoutId = null;
|
|||
|
|
|
|||
|
|
return function(...args) {
|
|||
|
|
if (timeoutId) {
|
|||
|
|
clearTimeout(timeoutId);
|
|||
|
|
}
|
|||
|
|
timeoutId = setTimeout(() => {
|
|||
|
|
func.apply(this, args);
|
|||
|
|
}, wait);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加事件监听器
|
|||
|
|
* @param {Element} element - DOM 元素
|
|||
|
|
* @param {string} event - 事件名称
|
|||
|
|
* @param {Function} handler - 处理函数
|
|||
|
|
* @param {Object} options - 选项
|
|||
|
|
*/
|
|||
|
|
on(element, event, handler, options = {}) {
|
|||
|
|
if (!element) return;
|
|||
|
|
|
|||
|
|
const key = this._getKey(element, event);
|
|||
|
|
element.addEventListener(event, handler, options);
|
|||
|
|
|
|||
|
|
if (!this.listeners.has(key)) {
|
|||
|
|
this.listeners.set(key, []);
|
|||
|
|
}
|
|||
|
|
this.listeners.get(key).push({ handler, options });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 移除事件监听器
|
|||
|
|
* @param {Element} element - DOM 元素
|
|||
|
|
* @param {string} event - 事件名称
|
|||
|
|
*/
|
|||
|
|
off(element, event) {
|
|||
|
|
if (!element) return;
|
|||
|
|
|
|||
|
|
const key = this._getKey(element, event);
|
|||
|
|
const handlers = this.listeners.get(key);
|
|||
|
|
|
|||
|
|
if (handlers) {
|
|||
|
|
handlers.forEach(({ handler, options }) => {
|
|||
|
|
element.removeEventListener(event, handler, options);
|
|||
|
|
});
|
|||
|
|
this.listeners.delete(key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除所有事件监听器
|
|||
|
|
*/
|
|||
|
|
clear() {
|
|||
|
|
this.listeners.forEach((handlers, key) => {
|
|||
|
|
const [element, event] = this._parseKey(key);
|
|||
|
|
handlers.forEach(({ handler, options }) => {
|
|||
|
|
element.removeEventListener(event, handler, options);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
this.listeners.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_getKey(element, event) {
|
|||
|
|
return `${element}_${event}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_parseKey(key) {
|
|||
|
|
const parts = key.split('_');
|
|||
|
|
return [parts[0], parts[1]];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用示例:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const eventManager = new ArgonEventManager();
|
|||
|
|
|
|||
|
|
// 使用节流处理滚动事件
|
|||
|
|
const handleScroll = eventManager.throttle(() => {
|
|||
|
|
const scrollTop = document.documentElement.scrollTop;
|
|||
|
|
// 处理滚动逻辑
|
|||
|
|
}, 16);
|
|||
|
|
|
|||
|
|
eventManager.on(document, 'scroll', handleScroll, { passive: true });
|
|||
|
|
|
|||
|
|
// 使用防抖处理 resize 事件
|
|||
|
|
const handleResize = eventManager.debounce(() => {
|
|||
|
|
// 处理 resize 逻辑
|
|||
|
|
}, 150);
|
|||
|
|
|
|||
|
|
eventManager.on(window, 'resize', handleResize);
|
|||
|
|
|
|||
|
|
// PJAX 页面切换时清理
|
|||
|
|
$(document).on('pjax:beforeReplace', function() {
|
|||
|
|
eventManager.clear();
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 3. 资源加载模块
|
|||
|
|
|
|||
|
|
**ArgonResourceLoader 类**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ArgonResourceLoader {
|
|||
|
|
constructor() {
|
|||
|
|
this.loaded = new Set();
|
|||
|
|
this.loading = new Map();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 按需加载脚本
|
|||
|
|
* @param {string} name - 资源名称
|
|||
|
|
* @param {string} url - 资源 URL
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
async loadScript(name, url) {
|
|||
|
|
if (this.loaded.has(name)) {
|
|||
|
|
return Promise.resolve();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (this.loading.has(name)) {
|
|||
|
|
return this.loading.get(name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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}`));
|
|||
|
|
};
|
|||
|
|
document.head.appendChild(script);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.loading.set(name, promise);
|
|||
|
|
return promise;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查页面是否需要某个库
|
|||
|
|
* @param {string} selector - CSS 选择器
|
|||
|
|
* @returns {boolean}
|
|||
|
|
*/
|
|||
|
|
needsLibrary(selector) {
|
|||
|
|
return document.querySelector(selector) !== null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 条件加载 Prism 代码高亮
|
|||
|
|
*/
|
|||
|
|
async loadPrismIfNeeded() {
|
|||
|
|
if (!this.needsLibrary('pre code')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof window.Prism !== 'undefined' && window.Prism.highlightAll) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await this.loadScript('prism', argonConfig.prism_url);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 条件加载 Zoomify 图片放大
|
|||
|
|
*/
|
|||
|
|
async loadZoomifyIfNeeded() {
|
|||
|
|
if (!this.needsLibrary('.post-content img')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof window.Zoomify !== 'undefined') {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await this.loadScript('zoomify', argonConfig.zoomify_url);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 条件加载 Tippy 提示框
|
|||
|
|
*/
|
|||
|
|
async loadTippyIfNeeded() {
|
|||
|
|
if (!this.needsLibrary('[data-tippy-content]')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof window.tippy !== 'undefined') {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await this.loadScript('tippy', argonConfig.tippy_url);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 渲染优化模块
|
|||
|
|
|
|||
|
|
**ArgonRenderOptimizer 类**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ArgonRenderOptimizer {
|
|||
|
|
constructor() {
|
|||
|
|
this.readQueue = [];
|
|||
|
|
this.writeQueue = [];
|
|||
|
|
this.scheduled = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量读取 DOM 属性
|
|||
|
|
* @param {Function} readFn - 读取函数
|
|||
|
|
*/
|
|||
|
|
read(readFn) {
|
|||
|
|
this.readQueue.push(readFn);
|
|||
|
|
this._schedule();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量写入 DOM
|
|||
|
|
* @param {Function} writeFn - 写入函数
|
|||
|
|
*/
|
|||
|
|
write(writeFn) {
|
|||
|
|
this.writeQueue.push(writeFn);
|
|||
|
|
this._schedule();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 调度执行
|
|||
|
|
*/
|
|||
|
|
_schedule() {
|
|||
|
|
if (this.scheduled) return;
|
|||
|
|
|
|||
|
|
this.scheduled = true;
|
|||
|
|
requestAnimationFrame(() => {
|
|||
|
|
this._flush();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 执行队列
|
|||
|
|
*/
|
|||
|
|
_flush() {
|
|||
|
|
// 先执行所有读取操作
|
|||
|
|
while (this.readQueue.length) {
|
|||
|
|
const readFn = this.readQueue.shift();
|
|||
|
|
readFn();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 再执行所有写入操作
|
|||
|
|
while (this.writeQueue.length) {
|
|||
|
|
const writeFn = this.writeQueue.shift();
|
|||
|
|
writeFn();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.scheduled = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加 GPU 加速提示
|
|||
|
|
* @param {Element} element - DOM 元素
|
|||
|
|
*/
|
|||
|
|
enableGPU(element) {
|
|||
|
|
if (!element) return;
|
|||
|
|
element.style.willChange = 'transform, opacity';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 移除 GPU 加速提示
|
|||
|
|
* @param {Element} element - DOM 元素
|
|||
|
|
*/
|
|||
|
|
disableGPU(element) {
|
|||
|
|
if (!element) return;
|
|||
|
|
element.style.willChange = 'auto';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用示例:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const renderOptimizer = new ArgonRenderOptimizer();
|
|||
|
|
|
|||
|
|
// 批量读写避免布局抖动
|
|||
|
|
let scrollTop, windowHeight;
|
|||
|
|
|
|||
|
|
renderOptimizer.read(() => {
|
|||
|
|
scrollTop = document.documentElement.scrollTop;
|
|||
|
|
windowHeight = window.innerHeight;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
renderOptimizer.write(() => {
|
|||
|
|
const toolbar = domCache.get('toolbar');
|
|||
|
|
if (toolbar) {
|
|||
|
|
toolbar.style.opacity = scrollTop > 100 ? '1' : '0.8';
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 动画前启用 GPU 加速
|
|||
|
|
const element = document.querySelector('.animated-element');
|
|||
|
|
renderOptimizer.enableGPU(element);
|
|||
|
|
|
|||
|
|
// 动画完成后禁用
|
|||
|
|
element.addEventListener('animationend', () => {
|
|||
|
|
renderOptimizer.disableGPU(element);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 内存管理模块
|
|||
|
|
|
|||
|
|
**ArgonMemoryManager 类**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ArgonMemoryManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.timers = new Set();
|
|||
|
|
this.frames = new Set();
|
|||
|
|
this.intervals = new Set();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置定时器并跟踪
|
|||
|
|
* @param {Function} callback - 回调函数
|
|||
|
|
* @param {number} delay - 延迟时间
|
|||
|
|
* @returns {number} timer ID
|
|||
|
|
*/
|
|||
|
|
setTimeout(callback, delay) {
|
|||
|
|
const id = setTimeout(() => {
|
|||
|
|
this.timers.delete(id);
|
|||
|
|
callback();
|
|||
|
|
}, delay);
|
|||
|
|
this.timers.add(id);
|
|||
|
|
return id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置间隔定时器并跟踪
|
|||
|
|
* @param {Function} callback - 回调函数
|
|||
|
|
* @param {number} interval - 间隔时间
|
|||
|
|
* @returns {number} interval ID
|
|||
|
|
*/
|
|||
|
|
setInterval(callback, interval) {
|
|||
|
|
const id = setInterval(callback, interval);
|
|||
|
|
this.intervals.add(id);
|
|||
|
|
return id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 请求动画帧并跟踪
|
|||
|
|
* @param {Function} callback - 回调函数
|
|||
|
|
* @returns {number} frame ID
|
|||
|
|
*/
|
|||
|
|
requestAnimationFrame(callback) {
|
|||
|
|
const id = requestAnimationFrame(() => {
|
|||
|
|
this.frames.delete(id);
|
|||
|
|
callback();
|
|||
|
|
});
|
|||
|
|
this.frames.add(id);
|
|||
|
|
return id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除定时器
|
|||
|
|
* @param {number} id - timer ID
|
|||
|
|
*/
|
|||
|
|
clearTimeout(id) {
|
|||
|
|
clearTimeout(id);
|
|||
|
|
this.timers.delete(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除间隔定时器
|
|||
|
|
* @param {number} id - interval ID
|
|||
|
|
*/
|
|||
|
|
clearInterval(id) {
|
|||
|
|
clearInterval(id);
|
|||
|
|
this.intervals.delete(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 取消动画帧
|
|||
|
|
* @param {number} id - frame ID
|
|||
|
|
*/
|
|||
|
|
cancelAnimationFrame(id) {
|
|||
|
|
cancelAnimationFrame(id);
|
|||
|
|
this.frames.delete(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理所有定时器和动画帧
|
|||
|
|
*/
|
|||
|
|
clearAll() {
|
|||
|
|
this.timers.forEach(id => clearTimeout(id));
|
|||
|
|
this.intervals.forEach(id => clearInterval(id));
|
|||
|
|
this.frames.forEach(id => cancelAnimationFrame(id));
|
|||
|
|
|
|||
|
|
this.timers.clear();
|
|||
|
|
this.intervals.clear();
|
|||
|
|
this.frames.clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. 性能监控模块
|
|||
|
|
|
|||
|
|
**ArgonPerformanceMonitor 类**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ArgonPerformanceMonitor {
|
|||
|
|
constructor() {
|
|||
|
|
this.metrics = {};
|
|||
|
|
this.enabled = argonConfig.debug_mode || false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 记录性能指标
|
|||
|
|
*/
|
|||
|
|
recordMetrics() {
|
|||
|
|
if (!this.enabled || !window.performance) return;
|
|||
|
|
|
|||
|
|
const perfData = performance.getEntriesByType('navigation')[0];
|
|||
|
|
if (!perfData) return;
|
|||
|
|
|
|||
|
|
this.metrics = {
|
|||
|
|
dns: perfData.domainLookupEnd - perfData.domainLookupStart,
|
|||
|
|
tcp: perfData.connectEnd - perfData.connectStart,
|
|||
|
|
request: perfData.responseStart - perfData.requestStart,
|
|||
|
|
response: perfData.responseEnd - perfData.responseStart,
|
|||
|
|
dom: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
|
|||
|
|
load: perfData.loadEventEnd - perfData.loadEventStart,
|
|||
|
|
total: perfData.loadEventEnd - perfData.fetchStart
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检测性能问题
|
|||
|
|
*/
|
|||
|
|
detectIssues() {
|
|||
|
|
if (!this.enabled) return;
|
|||
|
|
|
|||
|
|
const issues = [];
|
|||
|
|
|
|||
|
|
// 检查 DOM 查询频率
|
|||
|
|
if (this._getDOMQueryCount() > 100) {
|
|||
|
|
issues.push('警告:DOM 查询次数过多,建议使用缓存');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查事件监听器数量
|
|||
|
|
if (this._getEventListenerCount() > 50) {
|
|||
|
|
issues.push('警告:事件监听器数量过多,可能存在内存泄漏');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查长任务
|
|||
|
|
if (this._hasLongTasks()) {
|
|||
|
|
issues.push('警告:检测到长任务,可能阻塞主线程');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (issues.length > 0) {
|
|||
|
|
console.warn('Argon 性能问题:', issues);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 输出性能报告
|
|||
|
|
*/
|
|||
|
|
report() {
|
|||
|
|
if (!this.enabled) return;
|
|||
|
|
|
|||
|
|
console.group('Argon 性能报告');
|
|||
|
|
console.table(this.metrics);
|
|||
|
|
this.detectIssues();
|
|||
|
|
console.groupEnd();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_getDOMQueryCount() {
|
|||
|
|
// 简化实现,实际需要更复杂的跟踪
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_getEventListenerCount() {
|
|||
|
|
// 简化实现,实际需要更复杂的跟踪
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_hasLongTasks() {
|
|||
|
|
// 简化实现,实际需要使用 PerformanceObserver
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 数据模型
|
|||
|
|
|
|||
|
|
### 性能配置对象
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const ArgonPerformanceConfig = {
|
|||
|
|
// 事件节流配置
|
|||
|
|
throttle: {
|
|||
|
|
scroll: 16, // 滚动事件节流间隔(毫秒)
|
|||
|
|
resize: 16, // resize 事件节流间隔(毫秒)
|
|||
|
|
mousemove: 16 // 鼠标移动事件节流间隔(毫秒)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 事件防抖配置
|
|||
|
|
debounce: {
|
|||
|
|
resize: 150, // resize 事件防抖延迟(毫秒)
|
|||
|
|
input: 300, // 输入事件防抖延迟(毫秒)
|
|||
|
|
search: 500 // 搜索事件防抖延迟(毫秒)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 资源加载配置
|
|||
|
|
lazyLoad: {
|
|||
|
|
prism: true, // 按需加载 Prism
|
|||
|
|
zoomify: true, // 按需加载 Zoomify
|
|||
|
|
tippy: true // 按需加载 Tippy
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 缓存配置
|
|||
|
|
cache: {
|
|||
|
|
maxSize: 100, // 最大缓存数量
|
|||
|
|
ttl: 300000 // 缓存过期时间(毫秒)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 性能监控配置
|
|||
|
|
monitor: {
|
|||
|
|
enabled: false, // 是否启用监控
|
|||
|
|
reportInterval: 60000 // 报告间隔(毫秒)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### DOM 缓存数据结构
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// Map 结构存储 DOM 元素缓存
|
|||
|
|
const domCacheStructure = {
|
|||
|
|
// key: 元素标识符(字符串)
|
|||
|
|
// value: DOM 元素引用
|
|||
|
|
'toolbar': HTMLElement,
|
|||
|
|
'content': HTMLElement,
|
|||
|
|
'leftbar': HTMLElement,
|
|||
|
|
'sidebar': HTMLElement,
|
|||
|
|
'backToTopBtn': HTMLElement,
|
|||
|
|
'readingProgress': HTMLElement,
|
|||
|
|
'comments': HTMLElement,
|
|||
|
|
'postComment': HTMLElement
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 事件监听器注册表
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// Map 结构存储事件监听器
|
|||
|
|
const eventListenersStructure = {
|
|||
|
|
// key: 'element_eventType' 格式的字符串
|
|||
|
|
// value: 监听器数组
|
|||
|
|
'document_scroll': [
|
|||
|
|
{
|
|||
|
|
handler: Function,
|
|||
|
|
options: { passive: true }
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
'window_resize': [
|
|||
|
|
{
|
|||
|
|
handler: Function,
|
|||
|
|
options: {}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 资源加载状态
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const resourceLoadingState = {
|
|||
|
|
// 已加载的资源集合
|
|||
|
|
loaded: Set(['jquery', 'bootstrap']),
|
|||
|
|
|
|||
|
|
// 正在加载的资源 Map
|
|||
|
|
loading: Map([
|
|||
|
|
['prism', Promise],
|
|||
|
|
['zoomify', Promise]
|
|||
|
|
])
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能指标数据
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const performanceMetrics = {
|
|||
|
|
dns: 50, // DNS 查询时间(毫秒)
|
|||
|
|
tcp: 100, // TCP 连接时间(毫秒)
|
|||
|
|
request: 200, // 请求时间(毫秒)
|
|||
|
|
response: 300, // 响应时间(毫秒)
|
|||
|
|
dom: 500, // DOM 解析时间(毫秒)
|
|||
|
|
load: 100, // 加载事件时间(毫秒)
|
|||
|
|
total: 1250, // 总加载时间(毫秒)
|
|||
|
|
|
|||
|
|
// 自定义指标
|
|||
|
|
domQueries: 45, // DOM 查询次数
|
|||
|
|
eventListeners: 23, // 事件监听器数量
|
|||
|
|
memoryUsage: 50.5 // 内存使用(MB)
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 错误处理
|
|||
|
|
|
|||
|
|
### 错误类型
|
|||
|
|
|
|||
|
|
**1. 资源加载失败**
|
|||
|
|
- 场景:第三方库加载失败
|
|||
|
|
- 处理:降级到基础功能,记录错误日志
|
|||
|
|
- 示例:Prism 加载失败时,代码块仍然显示但无高亮
|
|||
|
|
|
|||
|
|
**2. DOM 元素不存在**
|
|||
|
|
- 场景:缓存的元素在 PJAX 后不存在
|
|||
|
|
- 处理:返回 null,调用方检查后跳过操作
|
|||
|
|
- 示例:评论区元素不存在时,不显示"前往评论"按钮
|
|||
|
|
|
|||
|
|
**3. 事件监听器清理失败**
|
|||
|
|
- 场景:尝试移除不存在的监听器
|
|||
|
|
- 处理:静默失败,不影响其他清理操作
|
|||
|
|
- 示例:PJAX 清理时某个监听器已被移除
|
|||
|
|
|
|||
|
|
**4. 性能监控 API 不可用**
|
|||
|
|
- 场景:旧浏览器不支持 Performance API
|
|||
|
|
- 处理:禁用监控功能,不影响主要功能
|
|||
|
|
- 示例:IE11 中性能监控自动禁用
|
|||
|
|
|
|||
|
|
### 错误处理策略
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 资源加载错误处理
|
|||
|
|
async function loadResourceSafely(name, url) {
|
|||
|
|
try {
|
|||
|
|
await resourceLoader.loadScript(name, url);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(`Failed to load ${name}:`, error);
|
|||
|
|
// 降级处理
|
|||
|
|
if (name === 'prism') {
|
|||
|
|
// 代码块仍然显示,只是没有高亮
|
|||
|
|
document.querySelectorAll('pre code').forEach(el => {
|
|||
|
|
el.classList.add('no-highlight');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DOM 操作错误处理
|
|||
|
|
function updateElementSafely(element, property, value) {
|
|||
|
|
if (!element) {
|
|||
|
|
console.warn('Element not found, skipping update');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
element.style[property] = value;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to update element:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 事件清理错误处理
|
|||
|
|
function clearEventsSafely() {
|
|||
|
|
try {
|
|||
|
|
eventManager.clear();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to clear events:', error);
|
|||
|
|
// 尝试手动清理关键监听器
|
|||
|
|
try {
|
|||
|
|
document.removeEventListener('scroll', handleScroll);
|
|||
|
|
window.removeEventListener('resize', handleResize);
|
|||
|
|
} catch (e) {
|
|||
|
|
// 静默失败
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 性能监控错误处理
|
|||
|
|
function monitorPerformanceSafely() {
|
|||
|
|
if (!window.performance || !window.performance.getEntriesByType) {
|
|||
|
|
console.info('Performance API not available, monitoring disabled');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
performanceMonitor.recordMetrics();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Performance monitoring failed:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 降级方案
|
|||
|
|
|
|||
|
|
**1. 节流/防抖降级**
|
|||
|
|
- 如果 requestAnimationFrame 不可用,使用 setTimeout
|
|||
|
|
- 如果 Date.now 不可用,使用 new Date().getTime()
|
|||
|
|
|
|||
|
|
**2. 缓存降级**
|
|||
|
|
- 如果 Map 不可用,使用普通对象
|
|||
|
|
- 如果 Set 不可用,使用数组
|
|||
|
|
|
|||
|
|
**3. 资源加载降级**
|
|||
|
|
- 如果动态加载失败,使用静态引入
|
|||
|
|
- 如果 Promise 不可用,使用回调函数
|
|||
|
|
|
|||
|
|
**4. 性能监控降级**
|
|||
|
|
- 如果 Performance API 不可用,禁用监控
|
|||
|
|
- 如果 PerformanceObserver 不可用,使用简化版监控
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 测试策略
|
|||
|
|
|
|||
|
|
### 测试方法
|
|||
|
|
|
|||
|
|
本优化项目采用双重测试策略:
|
|||
|
|
|
|||
|
|
**1. 单元测试**
|
|||
|
|
- 测试具体的优化功能实现
|
|||
|
|
- 验证边界条件和错误处理
|
|||
|
|
- 测试特定场景的行为
|
|||
|
|
|
|||
|
|
**2. 属性测试**
|
|||
|
|
- 验证优化机制的通用性质
|
|||
|
|
- 使用随机生成的输入测试
|
|||
|
|
- 确保优化在各种情况下都有效
|
|||
|
|
|
|||
|
|
**3. 性能测试**
|
|||
|
|
- 测量优化前后的性能指标
|
|||
|
|
- 验证 CPU 占用降低
|
|||
|
|
- 确认内存使用优化
|
|||
|
|
|
|||
|
|
### 测试工具
|
|||
|
|
|
|||
|
|
**JavaScript 测试框架:**
|
|||
|
|
- Jest 或 Mocha - 单元测试框架
|
|||
|
|
- fast-check - 属性测试库
|
|||
|
|
- Lighthouse - 性能测试工具
|
|||
|
|
|
|||
|
|
**性能监控工具:**
|
|||
|
|
- Chrome DevTools Performance
|
|||
|
|
- Performance API
|
|||
|
|
- Memory Profiler
|
|||
|
|
|
|||
|
|
### 测试配置
|
|||
|
|
|
|||
|
|
**属性测试配置:**
|
|||
|
|
- 每个属性测试至少运行 100 次迭代
|
|||
|
|
- 使用随机生成器创建测试数据
|
|||
|
|
- 每个测试标注对应的设计属性
|
|||
|
|
|
|||
|
|
**标注格式:**
|
|||
|
|
```javascript
|
|||
|
|
// Feature: resource-cpu-optimization, Property 1: DOM 缓存初始化
|
|||
|
|
test('DOM cache initializes all frequent elements', () => {
|
|||
|
|
// 测试代码
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 正确性属性
|
|||
|
|
|
|||
|
|
*属性是一个特征或行为,应该在系统的所有有效执行中保持为真——本质上是关于系统应该做什么的形式化陈述。属性作为人类可读规范和机器可验证正确性保证之间的桥梁。*
|
|||
|
|
|
|||
|
|
### 属性反思
|
|||
|
|
|
|||
|
|
在编写正确性属性之前,我对 prework 分析进行了反思,识别出以下可以合并或优化的属性:
|
|||
|
|
|
|||
|
|
**合并的属性:**
|
|||
|
|
1. 属性 7.1、7.2、7.3(按需加载 Prism、Zoomify、Tippy)可以合并为一个通用的"按需加载"属性
|
|||
|
|
2. 属性 11.2 和 11.4(组件销毁和 PJAX 清理事件监听器)可以合并为一个"事件监听器清理"属性
|
|||
|
|
3. 属性 19.3 和 19.5(交互时加载和避免重复加载)可以合并为一个"模块按需加载"属性
|
|||
|
|
|
|||
|
|
**移除的冗余属性:**
|
|||
|
|
- 属性 1.2(优先使用缓存)被属性 1.1(缓存初始化)隐含包含
|
|||
|
|
- 属性 3.2(取消待执行任务)是防抖机制的核心,已被属性 3.1 包含
|
|||
|
|
|
|||
|
|
经过反思,最终保留 20 个独立且有价值的正确性属性。
|
|||
|
|
|
|||
|
|
### 属性 1: DOM 缓存初始化完整性
|
|||
|
|
|
|||
|
|
*对于任意* 页面结构,当初始化 DOM 缓存时,所有预定义的频繁访问元素(如果存在)都应该被正确缓存
|
|||
|
|
|
|||
|
|
**验证:需求 1.1**
|
|||
|
|
|
|||
|
|
### 属性 2: DOM 缓存 PJAX 重置
|
|||
|
|
|
|||
|
|
*对于任意* PJAX 页面切换,缓存应该被清空并使用新页面的元素重新初始化
|
|||
|
|
|
|||
|
|
**验证:需求 1.3**
|
|||
|
|
|
|||
|
|
### 属性 3: 滚动事件节流频率限制
|
|||
|
|
|
|||
|
|
*对于任意* 快速连续的滚动事件序列,节流后的处理器执行频率不应超过每 16ms 一次
|
|||
|
|
|
|||
|
|
**验证:需求 2.1**
|
|||
|
|
|
|||
|
|
### 属性 4: 事件监听器清理完整性
|
|||
|
|
|
|||
|
|
*对于任意* 已注册的事件监听器集合,当触发清理时,所有监听器都应该被正确移除
|
|||
|
|
|
|||
|
|
**验证:需求 2.5, 11.2, 11.4**
|
|||
|
|
|
|||
|
|
### 属性 5: Resize 事件防抖延迟
|
|||
|
|
|
|||
|
|
*对于任意* 快速连续的 resize 事件序列,防抖后的处理器应该只在事件停止 150ms 后执行一次
|
|||
|
|
|
|||
|
|
**验证:需求 3.1, 3.2**
|
|||
|
|
|
|||
|
|
### 属性 6: GPU 加速生命周期
|
|||
|
|
|
|||
|
|
*对于任意* 需要动画的元素,will-change 属性应该在动画开始时设置,在动画结束时移除
|
|||
|
|
|
|||
|
|
**验证:需求 5.2, 5.3**
|
|||
|
|
|
|||
|
|
### 属性 7: 同时运行动画数量限制
|
|||
|
|
|
|||
|
|
*对于任意* 动画启动请求序列,系统应该限制同时运行的动画数量不超过 3 个
|
|||
|
|
|
|||
|
|
**验证:需求 5.5**
|
|||
|
|
|
|||
|
|
### 属性 8: 第三方库按需加载
|
|||
|
|
|
|||
|
|
*对于任意* 第三方库(Prism、Zoomify、Tippy),当页面不包含对应功能的元素时,该库不应该被加载
|
|||
|
|
|
|||
|
|
**验证:需求 7.1, 7.2, 7.3**
|
|||
|
|
|
|||
|
|
### 属性 9: 第三方库加载缓存
|
|||
|
|
|
|||
|
|
*对于任意* 第三方库,多次请求加载时应该只实际加载一次,后续请求使用缓存
|
|||
|
|
|
|||
|
|
**验证:需求 7.5**
|
|||
|
|
|
|||
|
|
### 属性 10: 定时器和动画帧清理
|
|||
|
|
|
|||
|
|
*对于任意* 创建的定时器和动画帧集合,当触发清理时,所有待执行的回调都应该被取消
|
|||
|
|
|
|||
|
|
**验证:需求 12.5, 13.4**
|
|||
|
|
|
|||
|
|
### 属性 11: 缓存大小上限
|
|||
|
|
|
|||
|
|
*对于任意* 缓存系统,当添加的数据超过设定的上限时,应该淘汰旧数据保持在上限内
|
|||
|
|
|
|||
|
|
**验证:需求 14.3**
|
|||
|
|
|
|||
|
|
### 属性 12: LRU 缓存淘汰策略
|
|||
|
|
|
|||
|
|
*对于任意* 缓存数据访问序列,当缓存满时,应该淘汰最少最近使用的数据
|
|||
|
|
|
|||
|
|
**验证:需求 14.4**
|
|||
|
|
|
|||
|
|
### 属性 13: 响应式图片尺寸选择
|
|||
|
|
|
|||
|
|
*对于任意* 设备像素比,系统应该加载与该像素比匹配的合适尺寸图片
|
|||
|
|
|
|||
|
|
**验证:需求 15.1**
|
|||
|
|
|
|||
|
|
### 属性 14: WebP 格式优先级
|
|||
|
|
|
|||
|
|
*对于任意* 支持 WebP 的浏览器,当图片有多种格式可用时,应该优先加载 WebP 格式
|
|||
|
|
|
|||
|
|
**验证:需求 15.2**
|
|||
|
|
|
|||
|
|
### 属性 15: 模块按需加载和缓存
|
|||
|
|
|
|||
|
|
*对于任意* 功能模块,应该在用户交互触发时才加载,且多次触发时不应重复加载
|
|||
|
|
|
|||
|
|
**验证:需求 19.3, 19.5**
|
|||
|
|
|
|||
|
|
### 边界情况和示例测试
|
|||
|
|
|
|||
|
|
以下是需要通过单元测试验证的特定场景:
|
|||
|
|
|
|||
|
|
**示例 1: 不存在的元素返回 null**
|
|||
|
|
- 场景:请求缓存中不存在的元素
|
|||
|
|
- 预期:返回 null 而不抛出异常
|
|||
|
|
- **验证:需求 1.5**
|
|||
|
|
|
|||
|
|
**示例 2: 移动端方向改变延迟**
|
|||
|
|
- 场景:移动设备方向改变
|
|||
|
|
- 预期:布局调整延迟 300ms 执行
|
|||
|
|
- **验证:需求 3.5**
|
|||
|
|
|
|||
|
|
**示例 3: 字体加载失败降级**
|
|||
|
|
- 场景:自定义字体加载失败
|
|||
|
|
- 预期:优雅降级到系统字体
|
|||
|
|
- **验证:需求 8.3**
|
|||
|
|
|
|||
|
|
**示例 4: 性能指标记录**
|
|||
|
|
- 场景:页面加载完成
|
|||
|
|
- 预期:使用 Performance API 记录关键指标
|
|||
|
|
- **验证:需求 18.1**
|
|||
|
|
|
|||
|
|
**示例 5: 性能问题检测警告**
|
|||
|
|
- 场景:检测到性能问题(如过多 DOM 查询)
|
|||
|
|
- 预期:在控制台输出警告信息
|
|||
|
|
- **验证:需求 18.2**
|
|||
|
|
|
|||
|
|
**示例 6: 开发模式详细数据**
|
|||
|
|
- 场景:启用开发模式
|
|||
|
|
- 预期:提供详细的性能分析数据
|
|||
|
|
- **验证:需求 18.3**
|
|||
|
|
|
|||
|
|
**示例 7: 生产模式精简数据**
|
|||
|
|
- 场景:启用生产模式
|
|||
|
|
- 预期:仅记录关键指标
|
|||
|
|
- **验证:需求 18.4**
|
|||
|
|
|
|||
|
|
**示例 8: 性能异常优化建议**
|
|||
|
|
- 场景:性能指标异常
|
|||
|
|
- 预期:提供针对性的优化建议
|
|||
|
|
- **验证:需求 18.5**
|
|||
|
|
|
|||
|
|
**示例 9: 模块加载失败降级**
|
|||
|
|
- 场景:功能模块加载失败
|
|||
|
|
- 预期:提供降级方案或友好提示
|
|||
|
|
- **验证:需求 19.4**
|
|||
|
|
|
|||
|
|
### 测试实现指南
|
|||
|
|
|
|||
|
|
**属性测试示例:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 使用 fast-check 进行属性测试
|
|||
|
|
const fc = require('fast-check');
|
|||
|
|
|
|||
|
|
// Feature: resource-cpu-optimization, Property 3: 滚动事件节流频率限制
|
|||
|
|
test('scroll event throttle limits execution frequency', () => {
|
|||
|
|
fc.assert(
|
|||
|
|
fc.property(
|
|||
|
|
fc.array(fc.integer({ min: 0, max: 1000 }), { minLength: 10, maxLength: 100 }),
|
|||
|
|
(timestamps) => {
|
|||
|
|
const eventManager = new ArgonEventManager();
|
|||
|
|
const executions = [];
|
|||
|
|
|
|||
|
|
const handler = eventManager.throttle(() => {
|
|||
|
|
executions.push(Date.now());
|
|||
|
|
}, 16);
|
|||
|
|
|
|||
|
|
// 模拟快速滚动事件
|
|||
|
|
timestamps.forEach(ts => {
|
|||
|
|
setTimeout(handler, ts);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 等待所有事件处理完成
|
|||
|
|
return new Promise(resolve => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
// 验证执行间隔不小于 16ms
|
|||
|
|
for (let i = 1; i < executions.length; i++) {
|
|||
|
|
expect(executions[i] - executions[i-1]).toBeGreaterThanOrEqual(16);
|
|||
|
|
}
|
|||
|
|
resolve();
|
|||
|
|
}, 2000);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
),
|
|||
|
|
{ numRuns: 100 }
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**单元测试示例:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// Feature: resource-cpu-optimization, Example 1: 不存在的元素返回 null
|
|||
|
|
test('cache returns null for non-existent elements', () => {
|
|||
|
|
const cache = new ArgonDOMCache();
|
|||
|
|
cache.init();
|
|||
|
|
|
|||
|
|
const result = cache.get('non-existent-element');
|
|||
|
|
|
|||
|
|
expect(result).toBeNull();
|
|||
|
|
expect(() => cache.get('non-existent-element')).not.toThrow();
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能基准测试
|
|||
|
|
|
|||
|
|
除了功能正确性测试,还需要进行性能基准测试:
|
|||
|
|
|
|||
|
|
**测试指标:**
|
|||
|
|
1. 滚动事件处理器 CPU 占用(优化前 vs 优化后)
|
|||
|
|
2. Resize 事件处理器执行时间
|
|||
|
|
3. DOM 查询次数和耗时
|
|||
|
|
4. 内存使用量
|
|||
|
|
5. 页面加载时间
|
|||
|
|
6. 首次内容绘制(FCP)时间
|
|||
|
|
7. 最大内容绘制(LCP)时间
|
|||
|
|
|
|||
|
|
**性能目标:**
|
|||
|
|
- 滚动时 CPU 占用降低 50%
|
|||
|
|
- DOM 查询次数减少 70%
|
|||
|
|
- 内存泄漏为 0
|
|||
|
|
- 页面加载时间减少 20%
|
|||
|
|
- FCP 和 LCP 时间各减少 15%
|
|||
|
|
|