为什么带有 setTimeout 的函数不会导致堆栈溢出

2024-01-07

我正在编写一个处理大量数据的测试。令我惊讶的是,如果我在函数中添加 setTimeout,它将不再导致堆栈溢出(对于这个网站来说多么合适)。 这怎么可能,代码看起来真的是递归的。 每个 setTimeout 调用都会创建自己的堆栈吗?

有没有办法在不增加所需内存的情况下实现这种行为(异步且按顺序处理巨大的数组/数字)?


function loop(
    left: number,
    callbackFunction: (callback: () => void) => void,
) {
    if (left === 0) {
        return
    }
    console.log(left)
    callbackFunction(() => {
        loop(left - 1, callbackFunction)
    })
}

function setTimeoutCallback(callback: () => void) {
    setTimeout(
        () => {
            callback()
        },
        Math.random() * 5
    )
}

function nonSetTimeoutCallback(callback: () => void) {
    callback()
}

loop(100000, setTimeoutCallback) //no stack overflow

loop(100000, nonSetTimeoutCallback) //stack overflow

因为它不再是递归的。至少在技术上不是。

源代码看起来确实是递归的,因此程序员可能会编写这样的代码,就好像它是递归的一样,但从 CPU 的角度来看,它不再是递归的。它在循环中按顺序处理。

递归和堆栈

递归函数调用自身。当这种情况发生时,堆栈会不断增加,直到最后一个函数返回。在函数返回之前,函数的堆栈帧不会从堆栈中删除(现在让我们忽略闭包),因此,因为递归函数调用自身,所以在对自身的调用返回之前,它不会返回。这就是导致堆栈增长的原因。

尾递归

Lisp、Haskell 和 Scala 等语言认识到,在某些情况下,在进行递归时可以释放堆栈帧。一般来说,如果递归调用是函数中的最后一条指令,并且没有对返回值进行其他处理,则可以删除当前堆栈帧,因为在递归函数返回后将不再使用它。因此,此类语言实现了所谓的尾递归:在不增加堆栈的情况下无限递归的能力。

这对于非常纯粹的函数式语言特别有用,在这种语言中,唯一的编程结构就是函数,因为如果没有语句的存在,就不可能有循环语句或条件语句等。尾递归使得 Lisp 中的无限循环成为可能。

然而,Javascript 没有尾递归。所以这不会影响 Javascript 中递归的行为方式。我提到这一点是为了注意并非所有递归都需要增加堆栈。

调度

定时器功能如setTimeout() and setInterval()不调用传递给它们的函数。不仅没有立即给他们打电话,甚至根本就没有给他们打电话。他们所做的就是将函数以及何时调用该函数的信息传递给事件循环。

事件循环本质上是javascript的核心。当且仅当没有更多的 javascript 需要执行时,解释器才会进入事件循环。您可以将事件循环视为解释器的空闲状态。事件循环不断检查事件(I/O、UI、计时器等)并执行附加到事件的相关函数。这是您传递给的函数setTimeout().

设置超时时间

因此,根据上面给出的事实,我们可以看到“递归”是如何通过setTimeout并不是真正的递归。

  1. 首先你的函数调用setTimeout并将其自身传递给它。

  2. setTimeout将函数引用保存到事件侦听器列表中,并设置计时器来触发将触发函数的事件

  3. 您的函数继续并返回,请注意“递归”函数尚未被调用。由于您的函数返回,因此它的堆栈帧会从堆栈中删除.

  4. JavaScript 进入事件循环(不再需要处理 JavaScript)。

  5. 函数的计时器到期,事件循环调用它。重复直到停止呼叫setTimeout

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么带有 setTimeout 的函数不会导致堆栈溢出 的相关文章

  • 为什么 Eclipse 有时会对 JavaScript 中的数组数组发出警告?

    在 Eclipse 中 以下 JavaScript 行 var a1 1 2 3 4 生成警告 Type mismatch cannot convert from Number to any Type mismatch cannot con
  • moment.calendar() 没有时间

    我想使用不带时间的 moment calendar 选项 所以而不是 上周二下午 5 点 我想要 上周二 有人知道 Moment 现在是否有解决方案吗 我找到了这个小提琴http jsfiddle net nawxZ http jsfidd
  • 使用javascript在谷歌地图中绘制多边形

    我想在谷歌地图中绘制多边形 我正在使用示例代码http jsfiddle net rvsMH 1 http jsfiddle net rvsMH 1 但它不起作用并且没有获取数组或纬度 经度值 我不知道出了什么问题
  • jqgrid 在编辑框中选择不正确的下拉选项值

    我正在使用表单编辑 表单中有两个选择框 一个选择框是国家 地区 另一个选择框是州 州选择框取决于所选的国家 地区 并将动态填充 例如 Country 美国 期权价值 1 英国 期权价值 2 美国的状态 阿拉巴马州 选项值 1 加利福尼亚州
  • 单击量角器中元素的给定坐标

    我想点击我的特定位置canvas元素 所以我编写了以下量角器代码 var canvas element by id canvas var clickCanvas function toRight toBottom browser actio
  • javascript中文本区域限制每行的字符数

    我试图用 javascript 限制文本区域中每行的字符数 我在这里看到了一些例子 但并不完全符合我的要求 我写了一些东西 只有当你每次添加超过限制时才可以 换句话说 我每行有 10 个字符的限制 如果你总是输入至少 10 个字符就可以正常
  • 如何处理 d3 中 Beeswarm 图中的碰撞?

    我一直在玩这个例子here https gf neocities org co2bs co2bee html一会儿 我想做的是突出显示图中的单个节点 圆圈 通过使用边框使其变大 稍后我也想在其中添加文本或字母 目前 我已经圈了Bhutan图
  • 使用什么事件来在选择文本框中的值时显示警报消息

    我正在使用 jquery 的自动完成 api 来从数据库中获取名称 但是我想在从显示的文本框中选择名称时显示一条警报消息 我将显示一个图像以便更好地理解 当我输入 S 时 它将显示所有包含 S 的记录 所以问题是 如果我选择例如 Spars
  • 从 JavaScript 将参数传递给 p:remoteCommand

    我想将值传递给remoteCommand来自 JavaScript 如果这是可能的 我该如何做到这一点以及如何在支持 bean 中接收它们 对的 这是可能的 如何执行此操作取决于 PrimeFaces 版本 你可以在PrimeFaces 用
  • JavaScript 逻辑赋值是如何工作的?

    在 javascript 中 如果我们有一些代码 例如 var a one var b q a alert b 逻辑 OR 运算符会将 a 的值分配给 b 并且警报将为 一 这仅限于作业还是我们可以在任何地方使用它 似乎空字符串被视为与未定
  • 无法从 Twin.macro 中的 Prop 获取值

    您可以在这里查看我正在尝试执行的操作的示例 https codesandbox io s vibrant leaf qj8vz https codesandbox io s vibrant leaf qj8vz 注意 这个特定的例子使用双宏
  • 从 url 角度加载模板并在 div 内编译

    由于我是 Angular JS 的新手 我想知道如何加载外部模板并将其与一些数据一起编译到目标中div 例如我有这个模板
  • 设置股票数据 Highcharts xAxis 的格式

    我已经浏览了需要为 xAxis 属性设置的 Highcharts 选项来格式化时间标签 但没有运气了解这对于这种情况到底是如何工作的 我在白天 盘中 检索了股票的动态数据 我需要显示这些数据 因为检索的数据每天从 9 30 开始到 17 0
  • 测量填写部分的时间 - 谷歌表单

    我正在尝试使用谷歌表单进行研究调查问卷 对于某些部分 我想自动测量用户填写所需的时间 谷歌表单中没有这样的选项 我尝试复制表单源 并用 javascript 填充时间 但它不起作用 跨源问题 未能成功托管复制的表单 如何做到 我如何衡量回答
  • 如何按值删除数组中的多个项目?

    我正在尝试做一个removeAll 函数 它将删除具有该特定值 而不是索引 的数组的所有元素 当我们对循环进行任何更改时 棘手的部分就出现了 索引往往会移动 使其很难像我们想要的那样工作 并且每次更改时都重新启动循环 这在大数组上效率非常低
  • Material.Angular.io mat-autocomplete [displayWith] 函数更新范围变量

    我遇到了一个问题 我可以在实例化 mat autocomplete 的组件控制器中访问本地声明的变量 我面临的问题是局部变量被困在这个范围内 我无法更新它们 有关更新 mat autocomplete 范围变量的任何想法或想法 最终我要做的
  • 使用 Javascript 检测 Pepper (PPAPI) Flash

    我们使用的是专有的文档查看器 它与某些 Chrome 版本中的 Pepper 版本的 Flash 配合得不太好 所以我希望能够检测到它并重定向到不同格式的相同内容 由于这个版本似乎落后于 NPAPI 版本 所以我一直在使用闪光检测 http
  • 将 javascript 变量作为参数传递给 @url.Action()

    是否可以将javascript变量作为参数传递给 url Action 因为据我所知可能存在服务器和客户端问题 我的要求是我必须根据过滤器下载文件 并进行ajax调用不适用于下载文件 所以我对 url Action 进行了编码 但无法实现这
  • 开玩笑 setTimeout 不暂停测试

    it has working hooks async gt setTimeout gt console log Why don t I run expect true toBe true 15000 我已经查看了这个答案 Jest 文档和几
  • 角度 2 ngIf 与可观察?

    我有一个非常简单的服务 它的工作是从 api authenticate url 获取 200 或 401 auth service ts Injectable export class AuthService constructor pri

随机推荐