496 lines
14 KiB
JavaScript
496 lines
14 KiB
JavaScript
/**
|
||
* 性能优化工具模块
|
||
*
|
||
* 提供常用的性能优化工具函数:
|
||
* - 防抖 (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;
|
||
}
|