function handleData() {
list = data.value;
const now = new Date().getTime()
list.forEach(item => {
const isTop = item.remindEndTime > now
item.shine = isTop;
item.sort = isTop ? 0 : 1;
})
// 闪烁的放最前 再以更新时间排序
list = orderBy(list, ['sort', 'updateTime'], ['asc', 'desc'])
visibleData.value = list
list = null
}
onMounted(() => {
flightClient.subscribe(WS_PREFIX + '/xxx/xxx', res => {
data.value = res
handleData()
})
timer = setInterval(() => {
handleData()
}, 300)
})
onBeforeUnmount(() => {
clearInterval(timer)
})
每次调用我都把 list 置为空了 为什么还是会导致内存蹭蹭涨?
如果把 list = orderBy(list, ['sort', 'updateTime'], ['asc', 'desc']) 这段代码去掉 就没问题了
1
F7TsdQL45E0jmoiG 217 天前
两个 list 不是同一个 list
|
2
NessajCN 217 天前
orderBy 定义呢?
|
3
UGas2t22Svnlm2K2 217 天前
这段代码存在潜在的内存泄漏风险,主要原因是 setInterval 和数据处理方式的结合导致的。以下是详细分析:
潜在的内存泄漏原因: setInterval 和闭包: setInterval 函数会创建一个新的计时器,每 300 毫秒重复调用 handleData() 函数。 每次调用 handleData() 都会创建一个闭包,闭包会捕获周围作用域中的变量引用,包括 data.value 、list 和 visibleData.value 。 数据累积: 当新的数据通过 websocket 订阅到达时,data.value 会更新,并调用 handleData() 函数。 在 handleData() 内部,会创建一个新的数组 list ,并将 data.value 的值赋给它。 然后对 list 进行排序,并将其赋值给 visibleData.value 。 然而,原始的 list 数组,它仍然持有对旧 data.value 的引用,并没有被显式清除或垃圾回收。 闭包引用: 每个由 setInterval 创建的闭包都保留了对旧 list 数组的引用,即使它不再需要了。 这就阻止了旧的 data.value 被垃圾回收,随着时间的推移导致内存累积。 解决方案: 在卸载时清除计时器: 确保在 onBeforeUnmount 中正确调用 clearInterval(timer) 来停止计时器,并防止进一步创建闭包。 避免不必要的数组创建: 不要在每次 handleData() 调用中创建新的 list 数组,可以考虑直接对 data.value 数组进行排序,或使用更高效的排序算法。 使用弱引用 (高级): 如果需要在闭包中维护引用而不导致内存泄漏,可以考虑使用 WeakMap 或 WeakSet 数据结构。这些结构持有弱引用,不会阻止垃圾回收。 注意: 使用弱引用需要仔细考虑对象生命周期和垃圾回收行为。 其他注意事项: 内存分析: 使用浏览器开发者工具或内存分析工具来跟踪内存使用情况并识别泄漏。 数据大小: 如果 data.value 数组包含大量数据,内存影响会更显著。考虑高效处理大型数据集的策略。 改进示例 (避免创建数组): function handleData() { const now = new Date().getTime(); data.value.forEach(item => { const isTop = item.remindEndTime > now; item.shine = isTop; item.sort = isTop ? 0 : 1; }); // 直接对 data.value 进行排序 data.value = orderBy(data.value, ['sort', 'updateTime'], ['asc', 'desc']); visibleData.value = data.value; } |
4
yrk20212021 217 天前
import { onMounted, onBeforeUnmount } from 'vue'
// Assuming flightClient and WS_PREFIX are defined somewhere else let timer = null; function handleData() { const now = new Date().getTime(); const newData = data.value.map(item => { const isTop = item.remindEndTime > now; return { ...item, shine: isTop, sort: isTop ? 0 : 1 }; }); // Sort only if there are changes in shine or updateTime newData.sort((a, b) => { if (a.shine !== b.shine) { return a.shine ? -1 : 1; } return b.updateTime - a.updateTime; }); visibleData.value = newData; } onMounted(() => { const subscription = flightClient.subscribe(WS_PREFIX + '/xxx/xxx', res => { data.value = res; handleData(); }); timer = setInterval(handleData, 300); }); onBeforeUnmount(() => { clearInterval(timer); // Unsubscribe from WebSocket to prevent memory leaks flightClient.unsubscribe(subscription); }); |
5
ProxyXAI 217 天前
这可能是因为你的 `orderBy` 函数在排序时创建了新的数组,而原有的 `list` 数组并没有被垃圾回收。这可能是因为 `visibleData.value` 仍然引用着这个数组,或者其他地方也有对这个数组的引用。
你可以尝试在 `orderBy` 之后,手动将 `list` 设置为 `null`,或者使用 `list.length = 0` 清空数组,再设置 `visibleData.value = orderBy(...)`,这样可以确保 `list` 数组被垃圾回收。 另外,你的 `handleData` 函数并没有声明 `list` 为局部变量,这可能导致它成为全局变量,这也可能是内存泄露的原因。你应该使用 `let` 或 `const` 关键字声明 `list`。 示例代码: ```javascript function handleData() { let list = data.value; const now = new Date().getTime() list.forEach(item => { const isTop = item.remindEndTime > now item.shine = isTop; item.sort = isTop ? 0 : 1; }) // 闪烁的放最前 再以更新时间排序 visibleData.value = orderBy(list, ['sort', 'updateTime'], ['asc', 'desc']) list.length = 0 list = null } ``` 记住,JavaScript 的垃圾回收是基于引用的。只要一个对象没有被引用,它就会被垃圾回收。但如果你的代码中有对这个对象的引用,那么它就不会被回收,即使你手动设置为 `null`。 |
6
yxd19 217 天前 2
|
7
flyqie 217 天前 via Android
|
13
ProxyXAI 217 天前
后面不粘贴 AI
|
14
lp4298707 OP @morenacl 我改成这种方式也是不行的. visibleData.value = orderBy(list, ['sort', 'updateTime'], ['asc', 'desc'])
|
15
TVMXi8YPdB7idbjG 217 天前
建议贴上完整代码
|
16
iosyyy 217 天前
visibleData.value 不还在吗 你只把 list = null 只改了 list 的指向 gc 扫的时候 visibleData.value 还在
按 java gc 答的 不大知道 js gc 啥样 |
18
freeman12 217 天前
建议放个复现链接
|
21
qping 217 天前
这段代码看着是想实现
1. 订阅 websocket 的地址,然后更新到 data.value 2. 如果 data.value 发生改变,重新排序( remindEndTime > now 的数据显示到最前面),更新到 visibleData.value 如果上面需求理解正确的话,那实现就很奇怪 1. 为什么 list 定义不在 handleData 内,既然用完置为 null ,那就不应该是个全局变量 2. 为什么使用 setInterval 来更新数据,不使用 watch 来监听 data.value |
22
jy02534655 217 天前
list = data.value; 改成 const list = data.value;
visibleData.value = list 改成深拷贝复制一份看看 你这个每 0.3 秒就执行一次会不会太快了,setInterval 的话会不管 handleData 是否执行完成都会执行吧? 要不在 handleData 里面写 settimeout ,等 handleData 执行完,并且通过 nextTick 自调用来实现循环看看呢 |
24
lp4298707 OP @qping 因为想要实现 remindEndTime > now 就显示最前面 因为需要实时刷新 now 我就设置的 1S 执行一次 handleData, 这里我因为想要快速看到效果 把时间设置得更短了
|
25
lp4298707 OP @jy02534655 你说的深拷贝 递归调用 我都试过了 都是一样的效果
|
27
NessajCN 217 天前
@lp4298707
list = orderBy(list, ['sort', 'updateTime'], ['asc', 'desc']); 改成 orderBy(list, ['sort', 'updateTime'], ['asc', 'desc']); 或者 const ordered_list = orderBy(list, ['sort', 'updateTime'], ['asc', 'desc']) visibleData.value = ordered_list |
30
nevermoreluo 217 天前
打印过时间吗?
有没有可能数据太多了。。。。setInterval 300 毫秒来不及处理? |
32
Arrowing 217 天前
全是闭包,不出问题才怪呢。
最主要的 orderBy 定义没写出来 |
33
g0python32 217 天前 4
|
35
lp4298707 OP @nevermoreluo 我改成 2 秒也是一样的 只是内存增长速度变缓了
|
36
lisongeee 217 天前
看起来你的 data 是 vue 里的的 ref ,如果是 vue 的话
你换成 shallowRef 试试呢,ref 有深度劫持的问题,shallowRef 不会有这个问题 |
37
asdjgfr 217 天前
你这代码复制下来运行了一下也没有内存泄漏呀
|
38
retanoj 217 天前
@qping #21 我想问个小白问题
subscribe 订阅了指定 topic 之后,每次 topic 有新消息来不就会触发 res => {} 函数吗? 为啥还要用 setInterval 做个 timer |
39
gitdoit 217 天前
看了下 orderBy 的源码, 会创建新的数组;但是搞不懂为什么内存会持续增长. 大对象会被立即回收吗?
|
40
chairuosen 217 天前
zhua 一下内存快照,看看谁增长的快
|
41
chairuosen 217 天前
你 onBeforeUnmount 没有 unsubscribe ,会不会是这个,组件如果是每次父级刷新全新创建,就有问题了
|
42
asasjajsajsd 217 天前
不是,我有点奇怪,就不能后端给你干这个事情么,数据库排序很快啊
|
43
ysc3839 217 天前 via Android
提一下,js 取时间戳可以直接写 +new Date()
|
44
ysc3839 217 天前 via Android
@ysc3839 刚刚去测了一下性能,发现 Date.now()性能最好,+new Date()性能最差,new Date().getTime()排在中间。
|
45
edward1987 217 天前
神奇,不好复现啊主要,OP 看下能不能整个最小复现代码放出来看下。
|
46
wawaguo 217 天前
来自 Code Copilot 的回答:
|
47
wawaguo 217 天前
这段代码的内存泄露可能由多种因素引起。我会逐一分析可能的原因,并提供解决方案:
1. 闭包中的变量未被释放 闭包(比如在 setInterval 、forEach 或事件订阅回调中定义的函数)常常会意外捕获并持久保持一些变量,尤其是在你反复设置定时器或不断订阅事件时。如果这些闭包保持对外部变量(如 list )的引用,这些变量就不会被垃圾回收。 解决方案: 确保在不需要时解除对变量的引用,或者在组件卸载时清除所有相关的订阅和定时器。 2. 定时器未正确清除 在代码中,onBeforeUnmount 用来清除定时器,但如果因为某种原因这个生命周期钩子没有被正确调用(或者定时器的引用 timer 被意外修改),定时器将继续运行,从而可能导致内存泄露。 解决方案: 确保 timer 变量不会在其他地方被误修改。 在组件的生命周期中确保 onBeforeUnmount 正确调用。 3. Websocket 订阅未取消 如果你的 Websocket 订阅在组件卸载时没有被取消,订阅中的回调函数可能持续持有一些变量的引用,从而导致内存泄露。 解决方案: 在 onBeforeUnmount 钩子中添加取消 Websocket 订阅的逻辑。 4. 数据绑定导致的内存泄露 如果使用了双向数据绑定或响应式数据系统(如 Vue 、React 状态),并且数据更新频率非常高,可能会导致旧数据对象未能及时回收。 解决方案: 减少不必要的数据变动。 手动触发垃圾回收(这种方式并不推荐,因为它依赖于具体的 JavaScript 引擎实现)。 5. 高频度的 DOM 操作或重渲染 高频的更新可能导致 DOM 重绘或组件重渲染,如果处理不当,旧的 DOM 节点或组件实例可能无法被垃圾回收。 解决方案: 使用虚拟 DOM 或有效的重渲染策略来优化渲染过程。 确保在更新数据前,不必要的 DOM 元素已被正确清除或重用。 检查和调试方法: 使用浏览器的开发者工具中的内存分析工具。 检查定时器和事件监听器是否都被正确清理。 观察内存使用情况,看是否随时间持续增长。 通过这些分析和调整,你应该能够识别并修复代码中的内存泄露问题。如果需要具体的代码修改建议,我可以帮助进一步修改。 |
48
RICKEYGONG 217 天前
|
49
RICKEYGONG 217 天前 1
@Livid ai 生成的
|
50
Livid MOD @RICKEYGONG 47 楼的账号已经被彻底 ban 。
|
51
zhhbstudio 217 天前
Vue2 还是 Vue3 ,数据量大概多少
PS:Vue2 可以通过 composition-api 使用类似 Vue3 的 API |
52
xiangyuecn 217 天前
你这点代码能泄露个啥,甩锅给 vue ,寿命+1 。
|
53
TKI 217 天前
把 flightClient.subscribe 去掉,试了下也没有内存泄漏
https://codesandbox.io/p/sandbox/vant-3-issue-template-forked-vjkxqz?file=%2Fsrc%2FTest.vue%3A16%2C20 |
54
wellerman 217 天前
1. setInterval 执行用了匿名函数,由于匿名函数定义在全局,可能会导致内存泄露
按 #4 楼给的方法,直接调用命名函数: timer = setInterval(handleData, 300); 2. 防止 orderBy( 处理时间超过 300ms var working = false; function handleData() { if (working) { return; } working = true; ... working = false; } |
55
lulinchuanllc 216 天前
flightClient.subscribe 在卸载时也应该 off 掉吧,否则这里的变量都被消息订阅列表里的订阅函数缓存了
|
57
DOLLOR 216 天前
建议把 data 和 visibleData 都改成浅响应( shallowRef )。
我推测 orderBy 在处理 list 的时候,因为 vue 的响应式处理造成了过重的负担。 |
58
visper 216 天前
这里代码应该没有什么内存泄露,猜测可能是因为 timeout 300 毫秒执行频率太高,然后数组又太大,orderby 里面每次又生成一个新的 list 重新给 vue 再重新处理初始化响应。造成浏览器内存回收没这么及时。
|
59
coderHu 216 天前
现在解决了么?蹲个解决方案
|
60
ColdBird 216 天前
如果 visibleData.value 不是 null ,说明 list 不是原来的 list 了,不然一定是 null
话说这代码写的也有点抽象 |
62
ColdBird 216 天前
感觉大概率就是 list 没有内部声明的原因,handleData 内声明一下 const list = XXX ,或者干脆不在前面生成 list ,直接 const list = orderBy
|
64
davin 216 天前
变量作用域被污染。另外,可以使用 requestAnimationFrame 替代 setInterval 。
|
65
privatezcoding 216 天前
没有人关心从 flightClient 获取到的数据大小吗?
|
66
privatezcoding 216 天前
我猜测你这里是因为原始网络数据本身就是个比较大的 list 。然后你又用 setInterval 每隔 300ms 用 orderby 创建新的数组。
解决方案: 1. flightClient 获取数据的时候根据 remindEndTime 和 updateTime 排序 2. setInterval 里直接对 remindEndTime 进行判断原地对数据进行处理 |
67
chill777 216 天前
0.3s 执行一次,是不是太频繁了
|
68
Marthemis 216 天前
这里没有内存泄漏,setinterval + orderBy 会创建大量的对象,因为 orderBy 的返回是一个新的数组。在内存快速增长到一定的阈值时会强制触发垃圾回收,从而最终稳定在一个较大的内存使用率值。(你可以加一个按钮去中断这个定时器,看看过一段时间内存会不会降下来)
|
69
lp4298707 OP @DOLLOR 感谢,这样确实解决了问题, 应该是 vue 内部没有及时回收这个资源? 在用 ref 的时候 内存只增不减 用 shallowRef 之后 到了一定的时间就会自动减少内存占用. 再次感谢
|
74
lp4298707 OP @privatezcoding 数据其实不大的, 最后我根据楼上的大佬的方式 使用 shallowRef 解决了这个问题
|