2026年元旦以来,中股市场继续牛市上涨。我们观测的 UfqiFina 有福金融 里的基金也出现了大量呈现买入信号可选标的,随之而来的是计算量也逐渐增大。
由于在设计上,UfqiFina 的一些计算需要放到前端浏览器内进行,随着计算量的增大,依赖 JavaScript 的程序在运行中逐渐曝露出运行时间长,内存消耗逐步增加的问题。这对计算资源的需求宛如过去一年中国证券市场一样,逐步上扬。迫不得已,我们借助 AI助手 抖音豆包 ,有针对性地对 UfqiFina 金融涉及到的 Hanjst 进行性能及内存方面的优化和提升。
大致结论,通过下面这些改进,UfqiFina 实现对一组候选基金进行扫描时,所占用的内存由修改前的:
峰值 3000MB~5000MB ,降低为 500MB~1500MB 。大约 2 倍 至 10 倍 的优化提升。
Hanjst 此番的修改主要是在变量声明和使用中 使用 let / const 替换 var ,并增加相应大数据对象的显式销毁声明。
A1. JavaScript 的内存管理 GC 回收
JavaScript 是自动垃圾回收的语言,不需要手动分配 / 释放内存,GC 的核心目标是回收 “不再被使用” 的内存空间。GC 自动执行,但如果代码写得不当,会导致 “内存泄漏”(本该回收的内存无法被回收),最终引发页面卡顿、崩溃。以下是最实用的优化手段:
1. 避免意外的全局变量
全局变量挂载在 window 上,生命周期和页面一致,不会被 GC 回收,是最常见的内存泄漏原因。
// 错误示例:未声明的变量隐式成为全局变量
function test() {
// 未用 let/const/var 声明,会挂到 window 上
unGlobalVar = { data: new Array(1000000) };
}
test(); // 这个大数组永远不会被回收
// 正确做法:
// 1. 使用 let/const 限制作用域
function test() {
const localVar = { data: new Array(1000000) };
// 用完后手动置 null(非必需,但大对象建议)
localVar = null;
}
2. 及时清理 DOM 引用
如果变量保存了 DOM 元素的引用,即使 DOM 被从页面移除,该引用仍会阻止 GC 回收,导致内存泄漏。javascript运行
// 错误示例:DOM 被删除但引用还在
const btn = document.getElementById('btn');
document.body.removeChild(btn);
// btn 变量仍引用该 DOM,内存无法回收
// 正确做法:删除 DOM 后清空引用
const btn = document.getElementById('btn');
document.body.removeChild(btn);
btn = null; // 解除引用,GC 可回收
3. 清除定时器 / 事件监听器
定时器(setInterval/setTimeout)和事件监听器如果不手动清除,其回调函数及内部引用的对象永远不会被回收。javascript运行
// 错误示例:定时器未清除
setInterval(() => {
console.log('未清除的定时器');
// 内部引用的大对象永远无法回收
}, 1000);
// 正确做法:保存定时器ID,不用时清除
const timerId = setInterval(() => {
console.log('可回收的定时器');
}, 1000);
// 页面卸载/不需要时清除
clearInterval(timerId);
4. 使用弱引用(WeakMap/WeakSet)
Map/Set 的键是强引用,会阻止 GC 回收;而 WeakMap/WeakSet 的键是弱引用,不会阻止 GC 回收,适合存储临时关联数据。javascript运行
// Map(强引用):即使 dom 被删除,Map 仍引用它,无法回收
const map = new Map();
const dom = document.getElementById('dom');
map.set(dom, '附加数据');
document.body.removeChild(dom);
// dom 仍被 Map 引用,内存泄漏
// WeakMap(弱引用):dom 被删除后,GC 自动回收对应的键值对
const weakMap = new WeakMap();
const dom = document.getElementById('dom');
weakMap.set(dom, '附加数据');
document.body.removeChild(dom);
// dom 无其他引用,GC 会回收 dom 及 weakMap 中的对应条目
以上4个方面对改善 JavaScript 运行时的内存占用分析,都有一定的道理。在 UfqiFina 的实践中,主要进行了前 3 项的改进,替换变量的声明关键词,及时清理 DOM 引用 和 清除 定时器,
先说清除 JavaScript 中的定时器,JavaScript 本身没有内置 API 直接获取所有已创建的定时器 ID,因为浏览器会自动分配递增的数字 ID,但不会暴露所有 ID 的列表。因此核心思路是:
- 拦截并重写原生定时器方法,记录所有创建的定时器 ID;
- 遍历记录的 ID 统一清除,同时利用浏览器的兼容特性(
clearTimeout和clearInterval可以互相替代,清除对方类型的定时器)简化逻辑。
// 1. 保存原生的 setTimeout/setInterval 方法,避免覆盖后无法恢复
const nativeSetTimeout = window.setTimeout;
const nativeSetInterval = window.setInterval;
// 2. 定义数组存储所有定时器 ID
const timerIds = new Set(); // 使用 Set 避免重复 ID(虽然理论上 ID 不会重复,但增加容错)
// 3. 重写 setTimeout,记录所有创建的 timer ID
window.setTimeout = function (callback, delay, ...args) {
const timerId = nativeSetTimeout(callback, delay, ...args);
timerIds.add(timerId); // 记录 ID
return timerId; // 保留原生返回值,不影响原有逻辑
};
// 4. 重写 setInterval,记录所有创建的 interval ID
window.setInterval = function (callback, delay, ...args) {
const intervalId = nativeSetInterval(callback, delay, ...args);
timerIds.add(intervalId); // 记录 ID
return intervalId; // 保留原生返回值
};
// 5. 核心函数:一次性清除所有定时器(timer + interval)
function clearAllTimers() {
// 遍历所有记录的 ID 并清除
for (const id of timerIds) {
try {
// 兼容处理:clearTimeout 可清除 interval,clearInterval 也可清除 timeout
clearTimeout(id);
clearInterval(id);
} catch (e) {
// 忽略无效 ID 的报错(比如已被手动清除的 ID)
console.debug('清除定时器 ID 失败:', id, e);
}
}
// 清空记录的 ID 列表,避免重复清除
timerIds.clear();
}
// 6. 可选:恢复原生定时器方法(如果需要)
function restoreNativeTimers() {
window.setTimeout = nativeSetTimeout;
window.setInterval = nativeSetInterval;
timerIds.clear();
}
下一篇继续写 使用 let / const 替换 var .











下一篇:
https://ufqi.com/go/?mod=blog&id=3732&tit=JavaScript 内存优化 GC 回收 与 var let 和 const 2/2
Pingback引用通告: JavaScript 内存优化 GC 回收 与 var let 和 const 2/2 | -UFQI-Blog