递归 JavaScript 函数正在丢失返回值

Posted

技术标签:

【中文标题】递归 JavaScript 函数正在丢失返回值【英文标题】:Recursive JavaScript function is losing the return value 【发布时间】:2013-03-18 11:08:03 【问题描述】:

我想在嵌套的 JSON 对象中搜索字符串。如果在对象中找到字符串,我需要返回该对象。

我正在使用递归函数来实现这一点。问题是,该函数一直在递归直到结束并且没有返回找到的对象。

Please see the entire code in jsfiddle

function search(obj, name) 
    console.log(obj["name"], ",", name, obj["name"] == name);

    if (obj["name"] == name) 
        return obj; //NOT RETURNING HERE
     
    if (obj.children || obj._children) 
        var ch = obj.children || obj._children;
        //console.log(ch);
        ch.forEach(function(val) 
            search(val, name)
        );
    
    return -1;


search(myJson, "VM10-Proc4")

我不知道出了什么问题。

【问题讨论】:

【参考方案1】:

正确的返回值在递归函数调用链中丢失了。找到正确的值后,从该点开始进行的任何其他搜索都将返回不正确的值。

几种处理方法:

1.取消搜索

找到正确的值后,立即将其返回到递归堆栈,不再搜索当前数组或嵌套数组。换句话说,取消其余的搜索。

@Barmer 的回答就是一个例子。他的代码的关键部分是使用for 循环而不是each 方法来遍历数组,因为中断for 循环要容易得多。

2。将值存储在安全的地方

找到正确的值后,将其存储在安全的地方,让其余的搜索继续,并在初始函数调用完成后访问该值。最简单的方法是将正确的值存储在全局变量中,但这不是一个好习惯,因为它违反了函数的封装。

@shyam 的回答提供了一个更简洁的解决方案:将全局变量的引用作为函数参数传递,找到正确值时设置参数,然后在初始函数调用完成后访问全局变量。

在两者之间选择

通俗地说,函数的预期逻辑可以概括如下:当你找到你要找的东西时,停下来,并立即告诉我它是什么。继续搜索的唯一原因是需要找到多条数据。我假设这里不是这种情况。

在这两种方法中,#2 是一种快速修复的解决方法,应该可以正常工作,但会进一步混淆任何试图理解函数预期逻辑的人。如果只查找已找到的单条数据,为什么还要继续搜索?

#1 是对函数的重构,使其行为与预期逻辑更加一致,这将使函数更易于理解。该函数在找到所需内容时停止搜索。

【讨论】:

谢谢马特!你能解释一下关于选项 1 的更多信息吗? @Aneesh:我更新了答案,对这两种方法进行了更彻底的解释,哪一种可能更好。【参考方案2】:

当找到匹配的子节点时,您需要停止循环遍历子节点。

function search(obj, name) 

    console.log(obj.name, ",", name, obj.name == name);

    if (obj.name == name) 
        return obj;
    
    if (obj.children || obj._children) 
        var ch = obj.children || obj._children;
        for (var i = 0; i < ch.length; i++) 
            var found = search(ch[i], name);
            if (found) 
                return found;
            
        
    
    return false;

FIDDLE demo

【讨论】:

另外值得一提的是,将返回值从-1 更改为false 很重要:)【参考方案3】:

由于您正在递归,因此返回可能嵌套得太深而无法获得有意义的结果。相反,您可以尝试传递一个额外的参数来收集结果。

function search(obj, name, ret) 
  console.log(obj["name"], ",", name, obj["name"] == name);

  if (obj["name"] == name) 
    ret.push(obj);
    return
  
  if (obj.children || obj._children) 
    var ch = obj.children || obj._children;
    ch.forEach(function(val) 
      search(val, name, ret);
    );
  


var result = [];
search(myJson, "VM10-Proc4", result)

【讨论】:

谢谢希亚姆!!但在这种情况下,该函数也会重复到最后。对吗? 是的。如果你想立即停止,你必须使用循环而不是使用 forEach【参考方案4】:

我知道这是一篇旧帖子,但我可以看到 2 个问题:

1) 递归调用没有将结果返回到调用堆栈,即

search(val, name)

应该是

return search(val, name)

2) 看起来您正在使用array.forEach()。文档指出:

除了抛出异常之外,没有其他方法可以停止或中断 forEach() 循环。如果您需要这种行为,那么 forEach() 方法是错误的工具。请改用普通循环。如果您正在测试谓词的数组元素并且需要布尔返回值,则可以使用 every() 或 some() 代替。如果可用,新方法 find() 或 findIndex() 也可用于在真谓词上提前终止。

这意味着,实际上,当您找到要查找的结果时,您希望将其发送回调用堆栈。使用 array.forEach 将继续递归地查看层次结构并返回所有值,而不仅仅是您感兴趣的值。因此最后返回的值可能是您不期望的值,例如 undefined!因此使用不同的迭代方法,即

    for (var i = 0; i < ch.length; i++) 
        var val = ch[i];
        return search(val, name, ret);
    

接受的答案确实会为您提供部分答案,但没有解释原因。因此这个答案

【讨论】:

那件事情让我的一天都丧命了。谢谢!【参考方案5】:

这是使用object-scan的解决方案

// const objectScan = require('object-scan');

const myJson = "name":"UCS - San Jose","type":"Minor","children":["name":"VM1","type":"Clear","children":["name":"VM1-Proc1","type":"Clear","children":["name":"VM1-Proc1-child1","type":"Clear"],"name":"VM1-Proc2","type":"Clear","name":"VM1-Proc3","type":"Clear","name":"VM1-Proc4","type":"Clear","name":"VM1-Proc5","type":"Clear","name":"VM1-Proc6","type":"Clear","name":"VM1-Proc7","type":"Clear","name":"VM1-Proc8","type":"Clear","name":"VM1-Proc9","type":"Clear","name":"VM1-Proc10","type":"Clear"],"name":"VM2","type":"Clear","children":["name":"VM2-Proc1","type":"Clear","name":"VM2-Proc2","type":"Clear","name":"VM2-Proc3","type":"Clear","name":"VM2-Proc4","type":"Clear","name":"VM2-Proc5","type":"Clear","name":"VM2-Proc6","type":"Clear","name":"VM2-Proc7","type":"Clear","name":"VM2-Proc8","type":"Clear","name":"VM2-Proc9","type":"Clear","name":"VM2-Proc10","type":"Clear"],"name":"VM3","type":"Clear","children":["name":"VM3-Proc1","type":"Clear","name":"VM3-Proc2","type":"Clear","name":"VM3-Proc3","type":"Clear","name":"VM3-Proc4","type":"Clear","name":"VM3-Proc5","type":"Clear","name":"VM3-Proc6","type":"Clear","name":"VM3-Proc7","type":"Clear","name":"VM3-Proc8","type":"Clear","name":"VM3-Proc9","type":"Clear","name":"VM3-Proc10","type":"Clear"],"name":"VM4","type":"Minor","children":["name":"VM4-Proc1","type":"Clear","name":"VM4-Proc2","type":"Clear","name":"VM4-Proc3","type":"Minor","name":"VM4-Proc4","type":"Clear","name":"VM4-Proc5","type":"Clear","name":"VM4-Proc6","type":"Minor","name":"VM4-Proc7","type":"Clear","name":"VM4-Proc8","type":"Clear","name":"VM4-Proc9","type":"Clear","name":"VM4-Proc10","type":"Clear"],"name":"VM5","type":"Clear","children":["name":"VM5-Proc1","type":"Clear","name":"VM5-Proc2","type":"Clear","name":"VM5-Proc3","type":"Clear","name":"VM5-Proc4","type":"Clear","name":"VM5-Proc5","type":"Clear","name":"VM5-Proc6","type":"Clear","name":"VM5-Proc7","type":"Clear","name":"VM5-Proc8","type":"Clear","name":"VM5-Proc9","type":"Clear","name":"VM5-Proc10","type":"Clear"],"name":"VM6","type":"Minor","children":["name":"VM6-Proc1","type":"Clear","name":"VM6-Proc2","type":"Clear","name":"VM6-Proc3","type":"Minor","name":"VM6-Proc4","type":"Clear","name":"VM6-Proc5","type":"Clear","name":"VM6-Proc6","type":"Clear","name":"VM6-Proc7","type":"Minor","name":"VM6-Proc8","type":"Clear","name":"VM6-Proc9","type":"Clear","name":"VM6-Proc10","type":"Clear"],"name":"VM7","type":"Clear","children":["name":"VM7-Proc1","type":"Clear","name":"VM7-Proc2","type":"Clear","name":"VM7-Proc3","type":"Clear","name":"VM7-Proc4","type":"Clear","name":"VM7-Proc5","type":"Clear","name":"VM7-Proc6","type":"Clear","name":"VM7-Proc7","type":"Clear","name":"VM7-Proc8","type":"Clear","name":"VM7-Proc9","type":"Clear","name":"VM7-Proc10","type":"Clear"],"name":"VM8","type":"Clear","children":["name":"VM8-Proc1","type":"Clear","name":"VM8-Proc2","type":"Clear","name":"VM8-Proc3","type":"Clear","name":"VM8-Proc4","type":"Clear","name":"VM8-Proc5","type":"Clear","name":"VM8-Proc6","type":"Clear","name":"VM8-Proc7","type":"Clear","name":"VM8-Proc8","type":"Clear","name":"VM8-Proc9","type":"Clear","name":"VM8-Proc10","type":"Clear"],"name":"VM9","type":"Clear","children":["name":"VM9-Proc1","type":"Clear","name":"VM9-Proc2","type":"Clear","name":"VM9-Proc3","type":"Clear","name":"VM9-Proc4","type":"Clear","name":"VM9-Proc5","type":"Clear","name":"VM9-Proc6","type":"Clear","name":"VM9-Proc7","type":"Clear","name":"VM9-Proc8","type":"Clear","name":"VM9-Proc9","type":"Clear","name":"VM9-Proc10","type":"Clear"],"name":"VM10","type":"Clear","children":["name":"VM10-Proc1","type":"Clear","name":"VM10-Proc2","type":"Clear","name":"VM10-Proc3","type":"Clear","name":"VM10-Proc4","type":"Clear","name":"VM10-Proc5","type":"Clear","name":"VM10-Proc6","type":"Clear","name":"VM10-Proc7","type":"Clear","name":"VM10-Proc8","type":"Clear","name":"VM10-Proc9","type":"Clear","name":"VM10-Proc10","type":"Clear"]];

const search = (obj, name) => objectScan(['**.name'], 
  rtn: 'parent',
  abort: true,
  filterFn: ( value ) => value === name
)(obj);

console.log(search(myJson, 'VM10-Proc4'));
// =>  name: 'VM10-Proc4', type: 'Clear' 
.as-console-wrapper max-height: 100% !important; top: 0
&lt;script src="https://bundle.run/object-scan@13.7.1"&gt;&lt;/script&gt;

免责声明:我是object-scan的作者

【讨论】:

以上是关于递归 JavaScript 函数正在丢失返回值的主要内容,如果未能解决你的问题,请参考以下文章

javascript函数(声明,传参,返回值,递归)

如何将递归函数的返回值保存在变量 JavaScript 中

如何打破递归函数内的 for 循环并在 Javascript 中返回?

Javascript - 遍历元素的递归函数

使用 Javascript 递归地进行线性搜索

带返回的 Powershell 递归