这里是阮大佬的解释
源代码下方自取
function tco(f) {
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
};
}
var sum = tco(function(x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
}
else {
return x
}
});
sum(1, 100000)
本人愚钝第一次看未能很好的理解,如果你也和我有一样的困惑请跟我一起从调用栈的角度来看。
一、执行37行代码调用sum函数,我们在16行打断点可以看到该调用栈为tco函数,所有变量都为初始值。
二、执行到20行,可以看到此时调用栈为accumulator函数(ps:该函数为后续调用函数的父函数,第五步会用到),tco函数执行完毕已被清除。但是大家可以注意到在scope中多了一个Closure(tco)对象,该对象上有accumulated、active、value以及函数f,这些变量和函数都是在tco函数中产生的(ps:由于tco函数最后一步抛出的accumulator函数用到了tco函数内部的变量及函数,所以形成闭包,toc函数调用栈执行完毕被销毁,内部变量被保留到Closure(tco)中,只有accumulator函数调用栈才可以访问,也就是闭包的使用是专属的,谁产生的谁才可以用)。
三、执行传入tco函数的回调,断点在31行,y>0,再次调用sum函数。可以发现Scope中的Closure(tco)对象没有了,印证了只有accumulator函数调用栈才可以访问Closure(tco)的说法。
四、再次调用sum函数也就是accumulator函数(ps:可以理解为子函数),此时代码执行到17行,可以看到此时又可以访问到Closure对象了。由于active为true,所以这一次的子函数只会将传入的参数push到accumulated数组中,不会执行if代码块内的代码。
五、往下执行,进入20行的断点,可以看到此时又退回到了父函数的调用栈,继续执行while函数,判断while条件语句是否成立(第四步子函数将新的参数传入了accumulated数值,所以此时while条件语句成立)。满足条件继续执行while函数体内代码,再次调用新的sum函数,生成新的子函数并调用,也就是重复第四步。
父函数做了什么事呢
- 将传入的参数push到accumulated中
- 将active置为true
- 循环创建新的accumulator函数并调用(也就是子函数)
- 将active置为false
- 抛出结果
子函数做了什么事呢
仅仅只做了一件事,就是将传入的参数push到accumulated中
所以整个过程就是父函数只执行了一次,当到父函数执行到第3步时。开始不断地在while中创建新的子函数,调用-执行-销毁(做子函数该做的事儿),直到不再满足第30行的条件,退出while循环。开始执行第4、5步,至此所有代码执行完毕。
这个代码执行的过程就是父函数不断的创建执行并销毁新的子函数的过程(当然执行和销毁跟父函数没有关系,父函数只负责创建并调用)。因此在函数执行期间栈空间内同时只会存在两个栈帧,因此永远不会触发栈空间的上限,所以更不会报栈溢出的错误。
以上均为个人见解,如有不同见解,欢迎讨论!