递归的边界是什么
What are the boundaries of recursion?
给定
let doAsynchronousStuff = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve("abcdefg"[Math.floor(Math.random() * 7)])
}, Math.PI * 1 + Math.random())
})
.then(data => console.log(data))
.then(doAsynchronousStuff)
}
该模式是的实现吗
- 递归
- 尾呼优化
- 迭代
- 一个非终止程序,恰好指代它自己
或;上面没有列出的其他常见模式?
从可信和/或官方来源寻找答案。
我重写了代码,删除了所有不相关的内容,并使用了一种我认为在这种情况下更可读、更舒适的风格。
function doAsynchronousStuff()
{
return new Promise((resolve, reject) =>
{
setTimeout(() => {resolve("test")}, 0)
})
.then(console.log)
.then(doAsynchronousStuff);
}
我们应该分析执行流,记住JS有一个事件循环,特别是
setTimeout
发布其参数函数,以便在事件循环的下一个1循环中执行then
发布其参数函数,以便在事件循环的下一个循环中执行
事件循环的存在很重要,因为在重新进入循环之前,在其上发布消息的函数会运行到完成。
还需要对promise有很好的了解,例如知道then
返回了一个新的promise。
当执行doAsynchronousStuff
时,会构造Promise
对象,并立即调用其参数函数。
Execution stack Event loop messages
doAsynchronousStuff
Promise constructor
Closure (resolve, reject)
这反过来调用发布事件并返回的setTimeout
。
Execution stack Event loop messages
doAsynchronousStuff resolve("test")
Promise constructor
Closure (resolve, reject)
setTimeout
执行返回到doAsynchronousStuff
,它为Promise
对象设置了continuation,但当然不执行它们。所以最后doAsynchronousStuff
返回,我们有一个运行到完成的情况。
Execution stack Event loop messages
resolve("test")
事件循环执行resolve("test")
(或者更好的是包含它的闭包(,它将promise设置为已解决,并将其继续安排在下一个循环上
Execution stack Event loop messages
resolve console.log
resolve
结束,我们再次出现运行到完成的情况。
Execution stack Event loop messages
console.log
执行CCD_ 12。实际上,执行了一个调用console.log
的函数,该函数是在调用then
时由promise对象设置的
当console.log
返回时,它的promise解析并且doAsynchronousStuff
被发布在事件循环上。
Execution stack Event loop messages
resolve doAsynchronousStuff
当resolve
结束时,我们将运行以完成,并且doAsynchronousStuff
将再次执行。
现在,我不会在数学意义上或CS理论意义上对你的问题中的列表中的术语进行过多的挖掘,这样做没有实际好处,因为我不认为这是一个理论问题
相反,我将把自己限制在编程的角度。
当调用第二个doAsynchronousStuff
实例时,第一个实例早已不复存在(它运行到完成(
基本上,这种情况相当于进行
let f = () => { console.log('hi!'); setTimeout(f, 0); }
我不会将此函数称为递归,因为递归意味着将问题分解为更小的自动相似部分
递归函数不必直接调用自己,也不必"使堆栈增长",但它必须根据自身进行定义。
如果它像
let f = () => { f(); }
我会称之为(糟糕的(递归。那是什么呢
我想说的是,如果你不能在不完成它对自己的所有调用的情况下完成它,那么函数在编程意义上是递归的
第一个例子可以在不等待f
的后续调用完成的情况下完成,而第二个例子则不能
在我看来,我称f
的第一个版本为计划。
关于尾调用优化,它与此无关 它在编程意义上也不是迭代(而在理论意义上是(,因为迭代是用特定的构造(如 最后,这当然是的情况,它是一个碰巧引用自身的非终止过程。 1我们在这里做了一个简化,它不一定是下一个周期,它只是一个未来的周期。
TCO将特定类型的递归转换为循环,这是编译器的优化,而不是代码的属性
尾调用是代码的一个属性,但此代码不是尾部调用for
、while
、goto
(实现的
由于迭代、递归和调度的重叠,这里的边界是模糊的。
以上都不是。有问题的代码不是递归的,也不是完全迭代的(尽管从英语的角度来看,它是迭代的,从我们在编程中通常称之为迭代的角度来看它不是,请注意,从英语的观点来看,递归是迭代的但我们没有说它在编程中(,因为它不是递归的,所以短语"tail call optimized"不适用,并且它不是非终止的,因为函数以返回结束。
它是一个安排一系列函数在以后执行的函数,其中一个就是它自己。
调度是一种设计模式。调度最古老的例子之一是操作系统所做的进程调度。下一个最古老的示例是cron。
调度的工作原理是,运行时环境(Linux内核、Windows内核、cron进程、javascript(保存一个"数据库"(可能像链表一样简单,也可能像SQL一样高级,也可能是文本文件一样低技术(,它引用了应该运行的代码和触发它们的条件(查看AWS Lambda服务,了解这个想法的高级实现(,并定期检查条件是否满足,然后执行代码。
对于操作系统内核,这组条件包括某种公平算法,以确保所有程序都能使用CPU。对于cron,条件是crontab中的时间规范。对于javascript,条件是回调注册的事件(对于setTimeout,它是超时事件(。
传统上,如果你要为此编写自己的软件,你会把它写成一个简单的状态机。下面是类似C的伪代码,实现了与上面的示例相同的东西
int tick = 0;
// Assume that there is an API for registering 1ms periodic interrupt
interrupt_1ms periodic () {
tick++;
}
int main (void) {
int timeout = PI + rand(); // a fairly silly way to randomly select 3 or 4 ms
char state = 0;
char result = nul;
char* data = "abcdefg";
while (1) {
if (tick >= timeout && state == 0) {
state = 1;
tick = 0;
timeout = PI + rand();
}
switch (state) {
case 1:
result = data[floor(rand() * 7)];
state = 2;
break;
case 2:
printf("%c", result);
state = 3;
break;
case 3:
state = 0; // reschedule the doAsynchronousStuff
break;
}
}
}
这是一种传统的方式。javascript所做的并不完全相同,但在概念上是相似的。它仍然使用一个永久循环作为事件循环的核心,但它不会连续运行(这会浪费CPU时间,加热CPU并耗尽电池(。相反,它会阻止调用其中一个异步I/O API(选择、轮询、epoll、kqueue等-libuv将在编译时选择(,并将控制权传递给OS,OS将使进程进入休眠状态,直到触发其中一个注册的I/O事件。
现在,请注意您的代码:
let doAsynchronousStuff = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve("abcdefg"[Math.floor(Math.random() * 7)])
}, Math.PI * 1 + Math.random())
})
.then(data => console.log(data))
.then(doAsynchronousStuff)
}
我不知道你是怎么想的,但对我来说,这比传统的状态机更容易推理。好吧,对于这个非常简单的例子,上面的C伪代码很容易理解,但考虑一个真实世界的node.js或jQuery应用程序,它有几十或数百个复杂的事件(在传统的jQuery应用中,这些事件甚至可能会自行取消计划或安排更多的事件处理程序(。随着您必须处理的事件数量的增加,javascript在其语法中为您提供的内容变得更加可读,尽管对于一个事件,不熟悉匿名函数和异步代码的初学者可能更喜欢我的伪C示例。
即使是老式的非承诺回调也比伪C代码更可读:
function doAsynchronousStuff () {
setTimeout(function () {
console.log("abcdefg"[Math.floor(Math.random() * 7)]);
doAsynchronousStuff();
}, Math.PI * 1 + Math.random());
}
因此,语法可能是新的(好吧,不是那么新,Lispers在70年代一直在做这种事情(,但这个想法是旧的。由于语法的原因,核心概念可能无法识别,所以不要被语法分散注意力。这只是安排用计时器运行一些东西。我们简单地将重复调度称为"重复调度"(谷歌日历和苹果日历都称之为"重复"(。
- 这是什么 ==- javascript 运算符
- 我的单元测试选项是什么
- 打破承诺链的好方法是什么
- 在AngularJS应用程序中使用封装指令和路由的推荐方式是什么
- Javascript中的空白是什么
- 是什么让一个“;Uncaught RangeError:超过了最大调用堆栈大小“;错误(Chrome,在其他浏览器中显示
- 在JavaScript中拆分日期字符串的更好方法是什么
- 将jQuery.ech()方法转换为本地JavaScript抽象的最佳方法是什么
- 处理浮点错误的最佳方法是什么
- javascript导入的最佳实践是什么
- 基于窗口宽度jquery的函数的替代方法是什么
- 在ng重复循环中显示条件内容的最佳方式是什么
- 我的客户端选项是什么
- 这是什么“;要求“;事情
- 未捕获的类型错误:topFrame.window.changeSelectedBarStyle不是函数,原因是什么
- 实现比较方法的最佳实践是什么;s的比较类型是在运行时选择的
- @@(“at at”)在ES6 JavaScript中是什么意思
- 这个php正则表达式边界集的等效JavaScript正则表达式是什么
- 递归的边界是什么
- 在HTML5画布游戏中为“房间”创建移动边界的好方法是什么