要理解这段代码,最好的办法是做两件事:
-
稍微修改代码,使其更容易通过调试器进行单步调试。 (代码原样错误,它不是为初学者调试而设置的。)
-
使用内置于 IDE 和/或浏览器中的调试器逐句执行代码,观察变量的值等。
以下是我对代码所做的更改,以便初学者使用调试器更容易理解:
function findSolution(target) {
function find (current, history) {
if (current == target) {
return history;
} else if (current > target) {
return null;
} else {
const plusFive = find(current + 5, `(${history} + 5)`);
if (plusFive) { // if `plusFive` is truthy
return plusFive;
} else { // `plusFive` was falsy (probably `null`, could be `0`)
const timesThree = find(current * 3, `(${history} * 3)`);
return timesThree;
}
}
}
return find(1, '1');
}
console.log(findSolution(24));
你可以看到我已经替换了行
return find(current + 5, `(${history} + 5)`) || find(current * 3, `(${history} * 3)`);
在|| 的每一侧使用这个if/else 块:
const plusFive = find(current + 5, `(${history} + 5)`);
if (plusFive) { // if `plusFive` is truthy
return plusFive;
} else { // `plusFive` was falsy (probably `null`, could be `0`)
const timesThree = find(current * 3, `(${history} * 3)`);
return timesThree;
}
这样更容易理解那里发生的事情。 JavaScript 的 || 运算符非常强大(正如我多年前在 this blog post 中所描述的那样)。它评估其左侧操作数,如果该值是 truthy,¹ 将该值作为结果;否则,它评估其右手操作数并将该值作为其结果。所以fn1() || fn2() 调用fn1 并且,如果它的返回值是真实的,则使用该值作为它的结果(从不调用fn2);但是,如果 fn1 的返回值是假的,它会调用 fn2() 并将该值作为结果(无论是真还是假)。
代码通过从 1 开始,然后通过递归重复将 5 加到 current 值或将其乘以 3 来找到 24 的“解决方案”。如果超过目标值,则返回null。在我用if/else 块替换的|| 表达式中,它将当前值加5,但如果这使我们超过了目标,则它会乘以3。 (这有点奇怪的逻辑,不会为你给它的每个值找到一个“解决方案”。)
找到它的“解决方案”需要相当多的步骤(实际上,24 个步骤,我认为只是一个奇怪的巧合)。下面是它的作用,使用缩进来指示递归发生的深度:
检查 1 => 1
检查 (1 + 5) => 6
检查 ((1 + 5) + 5) => 11
检查 (((1 + 5) + 5) + 5) => 16
检查 ((((1 + 5) + 5) + 5) + 5) => 21
检查 (((((1 + 5) + 5) + 5) + 5) + 5) => 26
检查 (((((1 + 5) + 5) + 5) + 5) * 3) => 63
检查 ((((1 + 5) + 5) + 5) * 3) => 48
检查 (((1 + 5) + 5) * 3) => 33
检查 ((1 + 5) * 3) => 18
检查 (((1 + 5) * 3) + 5) => 23
检查 ((((1 + 5) * 3) + 5) + 5) => 28
检查 ((((1 + 5) * 3) + 5) * 3) => 69
检查 (((1 + 5) * 3) * 3) => 54
检查 (1 * 3) => 3
检查 ((1 * 3) + 5) => 8
检查 (((1 * 3) + 5) + 5) => 13
检查 ((((1 * 3) + 5) + 5) + 5) => 18
检查 (((((1 * 3) + 5) + 5) + 5) + 5) => 23
检查 ((((((1 * 3) + 5) + 5) + 5) + 5) + 5) => 28
检查 ((((((1 * 3) + 5) + 5) + 5) + 5) * 3) => 69
检查 (((((1 * 3) + 5) + 5) + 5) * 3) => 54
检查 ((((1 * 3) + 5) + 5) * 3) => 39
检查 (((1 * 3) + 5) * 3) => 24
(((1 * 3) + 5) * 3)
对于它的价值,这是我用来获取该输出的代码,但我还是建议使用调试器,而不是 console.log(尽管 console.log 实际上有助于可视化递归发生的事情):
function findSolution(target) {
function find (current, history, depth) {
console.log(`${" ".repeat(depth)} Checking ${history} => ${current}`);
if (current == target) {
return history;
} else if (current > target) {
return null;
} else {
const plusFive = find(current + 5, `(${history} + 5)`, depth + 1);
if (plusFive) {
return plusFive;
} else { // `plusFive` was probably `null`, could be `0`
const timesThree = find(current * 3, `(${history} * 3)`, depth + 1);
return timesThree;
}
}
}
return find(1, '1', 0);
}
console.log(findSolution(24));
.as-console-wrapper {
max-height: 100% !important;
}
¹ “truthy”和“falsy”是 JavaScript 中用于表示值的术语,当用作布尔值时,它们分别转换为 true 或 false。只有几个虚假值:null、undefined、""、NaN、0,当然还有false(在浏览器上,document.all 出于历史原因)。所有其他值都是真实的。