Files
argon-theme/.kiro/specs/resource-cpu-optimization/design.md
nanhaoluo 4515831d7f chore: 清理临时文件和测试文件
- 删除临时测试文件 (test-*.html, test-*.js)
- 删除临时文档文件 (GPU_ACCELERATION_USAGE.md, RENDER_OPTIMIZER_USAGE.md)
- 删除测试 HTML 文件 (argon-memory-manager.test.html, argon-performance.test.html)
- 整理文档到 specs 目录下
2026-01-22 10:42:19 +08:00

30 KiB
Raw Blame History

设计文档

概述

本设计文档针对 Argon WordPress 主题的资源使用和 CPU 占用问题提供系统性的优化方案。通过分析主题核心代码argontheme.js ~3700 行、style.css ~12000 行),识别出以下主要性能瓶颈:

关键问题:

  1. 滚动和 resize 事件处理器未使用节流/防抖,导致高频执行
  2. DOM 元素重复查询,缺少缓存机制
  3. 事件监听器未正确清理,存在内存泄漏风险
  4. 第三方库全量加载,未按需加载
  5. CSS 选择器复杂度高,样式计算耗时
  6. 动画未充分利用 GPU 加速

优化策略:

  • 实现事件节流和防抖机制
  • 建立 DOM 缓存系统
  • 完善事件监听器生命周期管理
  • 实现第三方库按需加载
  • 优化 CSS 选择器和动画性能
  • 添加性能监控和分析工具

本设计与现有的 global-ui-optimizationpjax-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 类

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;
	}
}

使用示例:

// 初始化
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 类

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]];
	}
}

使用示例:

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 类

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 类

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';
	}
}

使用示例:

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 类

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 类

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;
	}
}

数据模型

性能配置对象

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 缓存数据结构

// Map 结构存储 DOM 元素缓存
const domCacheStructure = {
	// key: 元素标识符(字符串)
	// value: DOM 元素引用
	'toolbar': HTMLElement,
	'content': HTMLElement,
	'leftbar': HTMLElement,
	'sidebar': HTMLElement,
	'backToTopBtn': HTMLElement,
	'readingProgress': HTMLElement,
	'comments': HTMLElement,
	'postComment': HTMLElement
};

事件监听器注册表

// Map 结构存储事件监听器
const eventListenersStructure = {
	// key: 'element_eventType' 格式的字符串
	// value: 监听器数组
	'document_scroll': [
		{
			handler: Function,
			options: { passive: true }
		}
	],
	'window_resize': [
		{
			handler: Function,
			options: {}
		}
	]
};

资源加载状态

const resourceLoadingState = {
	// 已加载的资源集合
	loaded: Set(['jquery', 'bootstrap']),
	
	// 正在加载的资源 Map
	loading: Map([
		['prism', Promise],
		['zoomify', Promise]
	])
};

性能指标数据

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 中性能监控自动禁用

错误处理策略

// 资源加载错误处理
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 次迭代
  • 使用随机生成器创建测试数据
  • 每个测试标注对应的设计属性

标注格式:

// 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

测试实现指南

属性测试示例:

// 使用 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 }
	);
});

单元测试示例:

// 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%