JavaScript 为什么这段代码第三次返回出来的就变成了 undefined

rubyKC · 2021年09月12日 · 最后由 eedkevin 回复于 2021年09月19日 · 745 次阅读

想用 js 试试延迟求值
看上去好像也没什么问题啊。
到条件就 push 到数组,返回一个含递归的闭包,需要时执行. 不到条件就继续递归直到生成一个.

这种情况我称之为 CoffeeScript 用多了(考虑到是 Ruby 社区(不排除用多了其他一切皆表达式的语言

(另外好奇怪欸你的 VSCode 既然还有窗口装饰 (以及 Chromium 似乎早就放弃了尾递归优化?

第二次 return 的地点在()=> {this.genD(start + 1, end)}右括号旁,代码继续向下执行 dualGen 函数直到代码块完毕,然后输出 undefined。

在this.genD(start + 1, end)后面加 call 就好了。

对 1079702627 回复

是不是因为闭包是之前 return 出来的, 最早的 gend 已经"return 过了". 所以当它返回的时候就在右括号旁. (然后那个最早的 gend 又返回了?还是 undifened)

对 459650075 回复

是不是因为 return 是个语句. 第一次 return 出来的闭包的时候. 已经 return 过了,执行那个闭包的时候,由于闭包的捕获,在开始的函数还没运行结束并释放. 当闭包返回的时候,已经没有 return 可以用了。

而如果是默认返回最后的表达式的话,是不是就是说, 最早的函数会"恢复"然后,再计算一个返回值。

对 rubyKC 回复

有必要这么复杂吗?在每个分支处都要 return 就行了呗?而你写的不光在 else 没 return,在返回的闭包也没 return(箭头函数如果只需要写一行的话,去掉花括号,整个箭头函数的值就是那一行的值了)

保证返回这不是递归的基本盘嘛?你的第一次 call 能行,是因为走了 if 分支确实返回了那个函数,有这个函数可以用。第二次就不行了,因为不光返回的“延迟执行”函数没有值,而且还走了一边 else,那个分支也没有值。给一个变量赋值为没有值的表达式,那可不就是 undefined 嘛。

所以才问是不是一切皆表达式的语言用习惯了。我看你旁边开的是 Rust,估计就是这么想的,还心想这个很容易看出来啊。

另外我讨厌叫 JavaScript 的函数为闭包,箭头函数是引用捕获而非值捕获,说明闭包是可以说是漏的,充其量算函数一等公民。

还有你的生成器好像会崩啊,怎么只在 if 分支判断了 start <= end。生成器运行次数到了,永远走 else 分支就一定会爆栈的吧?

要写生成器的话不如去看看 Babel 是怎么转换的,一般用 for 循环比较多,递归毕竟还是有深度限制的。


至于直接回答你的问题的话,如果运行到 if 分支就是个普通函数调用,是不会产生递归栈的增长的,而 else 分支才会有,而且由于你的设计是逢偶数便“中断”(其实就是退出递归),递归栈帧最大的深度也就是 1 而已(相对它)。对于 if 分支,上层函数已经返回了函数给你,上层函数 执行结束并释放,指令指针早就恢复到调用者(主入口),已经没它事了 ,在外面执行这个函数有没有值只和这个函数本身有没有值有关。

你会用这种“已经没有 return 可以用了”、“函数会恢复”的描述我都要怀疑你在乱想 CPS 变换了,但不是这个理啊。在这里能在逻辑上恢复之前的进度,是因为你把状态通过引用捕获的箭头函数保存在了箭头函数里,具体而言是箭头函数里某个函数调用的参数列表里。确实很符合无栈协程的样子,生成器本身也是无栈协程的前身或者说一部分,但这不改变语言本身的性质。

另外一提,用参数列表保存状态不该是不可变语言中的常见操作嘛?JavaScript 主流实现连尾递归优化都没有,也不是不可变语言,使用这种感觉好奇怪。

对 459650075 回复

谢谢。大佬好厉害。 😍😋

rust 只是刚刚囫囵吞了官网上那本圣经书。弄了个练习. 挺久没有碰 javescript.以为箭头函数内部会默认返回最后的表达式了..(else 最后就没 return😓 ).

对 rubyKC 回复

这……还真是有迷惑性。因为 if 分支确实写了,我还以为只是 else 分支和里面的箭头函数忘写了,还分析了半天。

因为在一切皆表达式的语言里,是不会做出只会隐式返回你看到最后一条表达式的值这种蠢事的,而是会确保每一条控制流都会有值。具体来说就是函数中线性控制流的最后一条表达式是 if-else 整体,而 if-else 表达式的值是其内部每条控制流的值,也就是对应的线性控制流中最后一条表达式。

(所以我完全没有想到这种可能性,原来你的 else 和里层箭头函数居然是刻意空开的)

(如果以后出一个 Babel 插件能够让 JavaScript 变成一切皆表达式就好了)

注意箭头函数 this 作用域

需要 登录 后方可回复, 如果你还没有账号请 注册新账号