paper-burner/js/utils/performance-helpers.js

496 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 性能优化工具模块
*
* 提供常用的性能优化工具函数:
* - 防抖 (debounce)
* - 节流 (throttle)
* - LRU 缓存
* - 安全定时器管理
*
* @module PerformanceHelpers
*/
export const PerformanceHelpers = {
/**
* 防抖函数
* 在事件触发后等待指定时间才执行,如果在等待期间再次触发则重新计时
*
* @param {Function} fn - 要执行的函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function} 防抖后的函数
*
* @example
* const debouncedSearch = PerformanceHelpers.debounce((query) => {
* console.log('Searching for:', query);
* }, 300);
*
* input.addEventListener('input', (e) => debouncedSearch(e.target.value));
*/
debounce(fn, delay) {
let timer = null;
return function debounced(...args) {
const context = this;
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 设置新的定时器
timer = setTimeout(() => {
timer = null;
fn.apply(context, args);
}, delay);
};
},
/**
* 节流函数
* 在指定时间内只执行一次,无论触发多少次
*
* @param {Function} fn - 要执行的函数
* @param {number} delay - 时间间隔(毫秒)
* @param {Object} options - 配置选项
* @param {boolean} options.leading - 是否在开始时立即执行(默认 true
* @param {boolean} options.trailing - 是否在结束时执行(默认 true
* @returns {Function} 节流后的函数
*
* @example
* const throttledScroll = PerformanceHelpers.throttle(() => {
* console.log('Scroll position:', window.scrollY);
* }, 200);
*
* window.addEventListener('scroll', throttledScroll);
*/
throttle(fn, delay, options = {}) {
const { leading = true, trailing = true } = options;
let lastCall = 0;
let timer = null;
return function throttled(...args) {
const context = this;
const now = Date.now();
const timeSinceLastCall = now - lastCall;
// 首次调用且 leading 为 true
if (leading && lastCall === 0) {
lastCall = now;
return fn.apply(context, args);
}
// 清除之前的尾调用定时器
if (timer) {
clearTimeout(timer);
timer = null;
}
// 如果距离上次调用超过了延迟时间
if (timeSinceLastCall >= delay) {
lastCall = now;
return fn.apply(context, args);
}
// 设置尾调用
if (trailing) {
timer = setTimeout(() => {
lastCall = Date.now();
timer = null;
fn.apply(context, args);
}, delay - timeSinceLastCall);
}
};
},
/**
* LRU (Least Recently Used) 缓存
* 当缓存达到最大容量时,自动删除最久未使用的项
*
* @param {number} maxSize - 最大缓存数量
* @returns {Object} 缓存对象,包含 get/set/clear/getStats 方法
*
* @example
* const cache = PerformanceHelpers.createLRUCache(100);
* cache.set('key1', 'value1');
* const value = cache.get('key1'); // 'value1'
* const stats = cache.getStats(); // { size: 1, hits: 1, misses: 0, ... }
*/
createLRUCache(maxSize = 1000) {
const cache = new Map();
// 性能统计
const stats = {
hits: 0,
misses: 0,
evictions: 0,
sets: 0
};
return {
/**
* 获取缓存值
* @param {*} key - 缓存键
* @returns {*} 缓存值,不存在返回 undefined
*/
get(key) {
if (!cache.has(key)) {
stats.misses++;
return undefined;
}
stats.hits++;
const value = cache.get(key);
// 移到最后(标记为最近使用)
cache.delete(key);
cache.set(key, value);
return value;
},
/**
* 设置缓存值
* @param {*} key - 缓存键
* @param {*} value - 缓存值
*/
set(key, value) {
stats.sets++;
// 如果已存在,先删除(会重新添加到末尾)
if (cache.has(key)) {
cache.delete(key);
} else if (cache.size >= maxSize) {
// 删除最久未使用的(第一个)
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
stats.evictions++;
}
cache.set(key, value);
},
/**
* 检查键是否存在
* @param {*} key - 缓存键
* @returns {boolean}
*/
has(key) {
return cache.has(key);
},
/**
* 删除指定键
* @param {*} key - 缓存键
* @returns {boolean} 是否删除成功
*/
delete(key) {
return cache.delete(key);
},
/**
* 清空缓存
*/
clear() {
cache.clear();
stats.hits = 0;
stats.misses = 0;
stats.evictions = 0;
stats.sets = 0;
},
/**
* 获取当前大小
* @returns {number}
*/
get size() {
return cache.size;
},
/**
* 获取统计信息
* @returns {Object} 包含命中率、大小等信息
*/
getStats() {
const total = stats.hits + stats.misses;
return {
size: cache.size,
maxSize: maxSize,
hits: stats.hits,
misses: stats.misses,
sets: stats.sets,
evictions: stats.evictions,
hitRate: total > 0 ? (stats.hits / total) : 0,
hitRatePercent: total > 0 ? ((stats.hits / total) * 100).toFixed(2) + '%' : '0%'
};
}
};
},
/**
* 安全定时器管理器
* 自动跟踪所有定时器,确保在页面卸载时清理
*
* @returns {Object} 定时器管理器,包含 setTimeout/setInterval/clearAll 方法
*
* @example
* const timers = PerformanceHelpers.createManagedTimer();
* const id = timers.setTimeout(() => console.log('Hello'), 1000);
* // 页面卸载时自动清理
* window.addEventListener('beforeunload', () => timers.clearAll());
*/
createManagedTimer() {
const timers = new Set();
return {
/**
* 设置延迟定时器
* @param {Function} fn - 回调函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {number} 定时器 ID
*/
setTimeout(fn, delay) {
const id = setTimeout(() => {
timers.delete(id);
fn();
}, delay);
timers.add(id);
return id;
},
/**
* 设置循环定时器
* @param {Function} fn - 回调函数
* @param {number} delay - 时间间隔(毫秒)
* @returns {number} 定时器 ID
*/
setInterval(fn, delay) {
const id = setInterval(fn, delay);
timers.add(id);
return id;
},
/**
* 清除指定定时器
* @param {number} id - 定时器 ID
*/
clear(id) {
clearTimeout(id);
clearInterval(id);
timers.delete(id);
},
/**
* 清除所有定时器
*/
clearAll() {
timers.forEach(id => {
clearTimeout(id);
clearInterval(id);
});
timers.clear();
},
/**
* 获取活跃定时器数量
* @returns {number}
*/
get activeCount() {
return timers.size;
}
};
},
/**
* 轮询管理器
* 支持页面可见性检测,在页面隐藏时自动暂停
*
* @param {Function} checkFn - 轮询执行的函数
* @param {number} interval - 轮询间隔(毫秒)
* @param {Object} options - 配置选项
* @param {boolean} options.pauseWhenHidden - 页面隐藏时是否暂停(默认 true
* @returns {Object} 轮询管理器,包含 start/stop/pause/resume 方法
*
* @example
* const poller = PerformanceHelpers.createPoller(
* () => checkForUpdates(),
* 5000,
* { pauseWhenHidden: true }
* );
* poller.start();
*/
createPoller(checkFn, interval = 1000, options = {}) {
const { pauseWhenHidden = true } = options;
let timerId = null;
let isActive = false;
let isPaused = false;
const poll = () => {
if (!isActive) return;
// 如果页面隐藏且配置了暂停,则跳过执行
if (!isPaused && (!pauseWhenHidden || !document.hidden)) {
try {
checkFn();
} catch (error) {
console.error('[Poller] Error in check function:', error);
}
}
// 继续下一次轮询
timerId = setTimeout(poll, interval);
};
const handleVisibilityChange = () => {
if (document.hidden) {
console.log('[Poller] Page hidden, pausing...');
} else {
console.log('[Poller] Page visible, resuming...');
}
};
// 设置页面可见性监听
if (pauseWhenHidden) {
document.addEventListener('visibilitychange', handleVisibilityChange);
}
return {
/**
* 启动轮询
*/
start() {
if (isActive) {
console.warn('[Poller] Already active');
return;
}
isActive = true;
isPaused = false;
poll();
},
/**
* 停止轮询
*/
stop() {
isActive = false;
isPaused = false;
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
},
/**
* 暂停轮询(不清除定时器)
*/
pause() {
isPaused = true;
},
/**
* 恢复轮询
*/
resume() {
isPaused = false;
},
/**
* 获取状态
* @returns {Object}
*/
getStatus() {
return {
isActive,
isPaused,
interval
};
},
/**
* 销毁轮询器
*/
destroy() {
this.stop();
if (pauseWhenHidden) {
document.removeEventListener('visibilitychange', handleVisibilityChange);
}
}
};
},
/**
* 性能测量工具
* 简化 Performance API 的使用
*
* @example
* PerformanceHelpers.measure('render', () => {
* renderList();
* });
*/
measure: {
/**
* 测量函数执行时间
* @param {string} label - 标签
* @param {Function} fn - 要测量的函数
* @returns {*} 函数返回值
*/
sync(label, fn) {
const startMark = `${label}-start`;
const endMark = `${label}-end`;
performance.mark(startMark);
const result = fn();
performance.mark(endMark);
performance.measure(label, startMark, endMark);
const measure = performance.getEntriesByName(label)[0];
console.log(`[Measure] ${label}: ${measure.duration.toFixed(2)}ms`);
// 清理
performance.clearMarks(startMark);
performance.clearMarks(endMark);
performance.clearMeasures(label);
return result;
},
/**
* 测量异步函数执行时间
* @param {string} label - 标签
* @param {Function} fn - 要测量的异步函数
* @returns {Promise<*>} 函数返回值
*/
async async(label, fn) {
const startMark = `${label}-start`;
const endMark = `${label}-end`;
performance.mark(startMark);
const result = await fn();
performance.mark(endMark);
performance.measure(label, startMark, endMark);
const measure = performance.getEntriesByName(label)[0];
console.log(`[Measure] ${label}: ${measure.duration.toFixed(2)}ms`);
// 清理
performance.clearMarks(startMark);
performance.clearMarks(endMark);
performance.clearMeasures(label);
return result;
}
}
};
// 导出默认对象(兼容 CommonJS
if (typeof module !== 'undefined' && module.exports) {
module.exports = PerformanceHelpers;
}