【问题标题】:what happens when a function returns null?当函数返回 null 时会发生什么?
【发布时间】:2021-08-04 08:21:35
【问题描述】:

在这个js代码中,从1开始,反复加5或乘3,产生一个数。

当函数'find'返回null时会发生什么以及机器如何找到匹配数字24的解决方案

function findSolution(target) {
    function find (current, history) {
        if (current == target) {
            return history;
        } else if (current > target) {
            return null;
        } else {
            return find(current + 5, `(${history} + 5)`) || find(current * 3, `(${history} * 3)`);
        }
    }
    return find(1, '1');
}

console.log(findSolution(24));

【问题讨论】:

  • null || <any value> = <any value>
  • falsy || <any value> = <any value> - 数字 0 也是假的

标签: javascript null


【解决方案1】:

要理解这段代码,最好的办法是做两件事:

  1. 稍微修改代码,使其更容易通过调试器进行单步调试。 (代码原样错误,它不是为初学者调试而设置的。)

  2. 使用内置于 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 中用于表示值的术语,当用作布尔值时,它们分别转换为 truefalse。只有几个虚假值:nullundefined""NaN0,当然还有false(在浏览器上,document.all 出于历史原因)。所有其他值都是真实的。

【讨论】:

  • 首先非常感谢您的评论,真的没想到这么详细的回答!我的问题出现在哪里......当当前值超过目标数字时,它使用 3 乘法,这是可以理解的。但即便如此,它还是比目标大(就像过程中的 26 到 63),那么现在为什么它反复减少之前调用的 5 的加法?也许,因为 null 是“虚假”值,它会以某种方式回调?我不知道。
  • @PallabBiswas - 当find(current * 3, (${history} * 3)); 返回null 时,调用它的find 返回null .如果后一个调用是find(current + 5, (${history} + 5));,则调用 itfind 将使用* 3 再次尝试;但如果是另一个find(current * 3, (${history} * 3));,它也会将null返回给调用它的find。您可以在上面显示的输出中看到这一点。 first 调用find 尝试+ 5 并且有很多递归,但它最终会沿着* 3 路径走下去,并且总是会越过目标。所以所有的递归都通过 ...
  • ...返回null,直到它回到第一次调用find,然后尝试* 3 分支,最终找到解决方案。您可以用铅笔和纸尝试一下,自己完成find 的工作。有时这有助于理解这个概念。当你开始一个新的递归调用时,记得在每一行上多缩进一点,然后在调用返回时撤消它。
  • 好的,为什么要返回 null 反转到第一次调用?
  • @PallabBiswas - 因为const timesThree = find(current * 3, `(${history} * 3)`); 之后的下一行是return timesThree;
猜你喜欢
  • 1970-01-01
  • 2017-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 2020-04-19
  • 2016-09-05
相关资源
最近更新 更多