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

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

想用 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 作用域

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