如何利用requestAnimationFrame终止类中的递归循环并避免内存泄漏?
如何终止基于requestAnimationFrame的递归循环并避免内存泄漏
咱们先来拆解下你遇到的问题:
- 你用
bind传递isLoopOnFlg的方式没用——因为bind会把调用那一刻的变量值固定下来,后续修改isLoopOnFlg并不会更新已经绑定的参数,所以_loop里永远拿到的是初始的true。 - 没有取消
requestAnimationFrame的回调,导致即使你想终止,已经调度的回调还会继续执行,甚至持续递归,造成内存泄漏。
下面是调整后的完整代码,我会逐段解释关键改进点:
class Foo { private isLoopOnFlg: boolean = false; private animationRequestId: number | null = null; // 新增:保存动画请求ID,用于取消回调 public StartLoop() { // 防重复启动:如果已经在循环中,先停止再重启 if (this.isLoopOnFlg) { this.StopLoop(); } this.isLoopOnFlg = true; this.recursiveLoopWithDelay(() => { // 这里写你的DOM操作逻辑 console.log('循环执行中...'); }, 1000); } public StopLoop() { this.isLoopOnFlg = false; // 取消未执行的动画请求,彻底终止循环并避免内存泄漏 if (this.animationRequestId !== null) { window.cancelAnimationFrame(this.animationRequestId); this.animationRequestId = null; } } private recursiveLoopWithDelay(loopFn: any, delay: number) { let stamp = Date.now(); // 用箭头函数捕获类实例的this,确保能访问到最新的isLoopOnFlg const _loop = () => { // 先检查循环是否被终止,是则直接返回,不再递归 if (!this.isLoopOnFlg) { this.animationRequestId = null; return; } // 原有的延迟执行逻辑 if (Date.now() - stamp >= delay) { loopFn(); stamp = Date.now(); } // 保存当前的动画请求ID,方便StopLoop时取消 this.animationRequestId = window.requestAnimationFrame(_loop); }; // 启动第一个循环 this.animationRequestId = window.requestAnimationFrame(_loop); } }
关键改进点说明:
- 实时访问
isLoopOnFlg:用箭头函数定义_loop,它会自动捕获外部的this(也就是Foo类的实例),这样每次执行_loop时,访问的都是最新的isLoopOnFlg状态,而不是固定的初始值。 - 取消动画请求:新增
animationRequestId变量保存每次requestAnimationFrame的返回ID,在StopLoop中调用cancelAnimationFrame取消还没执行的回调,彻底切断递归链,避免内存泄漏。 - 防重复启动:在
StartLoop里先检查是否已经在循环中,避免多次调用导致多个循环同时运行。
为什么你的原方案无效?
你之前用_loop.bind(this, this.isLoopOnFlg)传递参数,本质是把调用bind时的isLoopOnFlg值(也就是true)复制了一份作为参数传给_loop,后续修改isLoopOnFlg并不会改变这个已经绑定的参数值,所以_loop永远认为循环应该继续,自然无法终止。
这样调整后,调用StopLoop()就能立即终止递归循环,同时也不会留下内存泄漏的隐患啦!
内容的提问来源于stack exchange,提问作者young-ceo
相关产品推荐
相关产品推荐

