如何理解JavaScript中的这段代码片段

时间:2019-08-26 06:43:13

标签: javascript arrays reactjs recursion search

我在Internet上的js中发现了该函数的实现,该函数递归地过滤了一组对象,每个对象可能具有属性“ children”(即对象数组),并且该对象也可能具有子对象,依此类推。该功能正常运行,但我对此不太了解。

这是我的功能:

getFilteredArray (array, key, searchString) {
    const res = array.filter(function iter(o) {

      if (o[key].toLowerCase().includes(searchString.toLowerCase())) {
        return true;
      }

      if(o.children){
        return (o.children = o.children.filter(iter)).length;
      }
    });

    this.setState({
      filteredArray: res
    });
  }

我在这段代码中看不懂:

if(o.children){
        return (o.children = o.children.filter(iter)).length;
      }  

我们可以简化表达式(o.children = o.children.filter(iter)).length;吗?

为什么我们返回数组的长度而不是数组本身?

函数“ iter”接受一个作为对象的参数。为什么我们只编写o.children.filter(iter)而不在此处传递任何参数给“ iter”?根据递归教程,如果函数需要它们,则总是传递参数。但是在这里我们没有通过,这很奇怪。

5 个答案:

答案 0 :(得分:4)

这是一个重新编写,它力求清晰,并简化了逻辑以消除干扰:

const recursivelyFilter = (arr, key, searchString) => {
  return arr.filter(function iter(obj) {
    if (obj[key].includes(searchString)) {
      return true;
    }

    if (obj.children) {
      obj.children = obj.children.filter(child => iter(child));
      return obj.children.length > 0;
    }

    return false;
  });
};

Array#filter是这段代码的内容。 filter接受一个回调,该回调应返回一个布尔值以确定元素是否将包含在结果数组中。 in-place无效。

base cases(递归的终止条件)是:

  • 如果当前对象(node中的tree)具有与key匹配的searchTerm键,则返回true。
  • 如果当前节点与searchTerm不匹配并且没有子节点,则返回false。在原始代码中,返回undefined默认为falsey。

递归的情况是:

  • 如果当前节点有子节点,则使用iter函数的布尔结果来递归过滤它们。如果当前节点的至少一个后代通过了过滤条件,则将当前节点包括在其父节点的children数组中,否则将其删除。该代码将新子数组的长度作为布尔值来实现。

return (o.children = o.children.filter(iter)).length;首先执行对o.children的赋值,这是必要的,因为o.children.filter返回数组的新副本。分配完成后,表达式将解析为新的o.children并返回其length属性。然后根据上述递归情况规则,将长度视为真/假。总计:

obj.children = obj.children.filter(child => iter(child));
return obj.children.length > 0;

如果我们返回数组本身,则所有内容都将被视为true,因为空数组[]的计算结果为true。另一方面,[].length的计算结果为假,这是期望的结果。

对于o.children.filter(iter)Array#filter接受回调作为其第一个参数,该参数可以是诸如iter之类的函数变量。另一种选择是直接在参数列表中创建一个匿名函数。这通常是这样做的。上面的版本添加了一个箭头包装器,但这显然是不必要的额外间接层,因为lone参数只是通过包装器传递。我们也可以在这里使用function关键字;无论如何,目标都是相同的,那就是我们将函数传递到filter中以调用每个元素。

顺便说一句,该函数假定keyarray中嵌套对象的所有节点上都已设置,并且已定义obj[key].includes。显然,作者脑海中有一个非常具体的数据结构和目的,对过早的概括不感兴趣。

以下是说明其操作的测试代码。尝试使用它应该有助于您的理解。

const recursivelyFilter = (arr, key, searchString) => {
  return arr.filter(function iter(obj) {
    if (obj[key].includes(searchString)) {
      return true;
    }

    if (obj.children) {
      obj.children = obj.children.filter(child => iter(child));
      return obj.children.length > 0;
    }

    return false;
  });
};

const arr = [
  {
    foo: "bar", 
    children: [
      {
        foo: "baz", 
        children: [
          {foo: "quux"},
          {foo: "quuz"},          
        ]
      }
    ]
  },
  {
    foo: "corge", 
    children: [
      {foo: "quux"}
    ]
  },
  {
    foo: "grault",
    children: [{foo: "bar"}]
  }
];

console.log(recursivelyFilter(arr, "foo", "quux"));

答案 1 :(得分:1)

也许一些代码更改将帮助您了解发生了什么。

function iter(o){
      if (o[key].toLowerCase().includes(searchString.toLowerCase())) {
        return true;
      }

      if(o.children){
        o.children = o.children.filter(iter);
        return o.children.length;
      }
 }

getObject (array, key, searchString) {
    const res = array.filter(iter);
    this.setState({
      filteredArray: res
    });
}

array.filter对数组中的每个元素执行iter函数,如果返回true,则将元素添加到结果中,否则将其排除。

在这种情况下,如果项目本身不是直接匹配项,但我们希望保留子项目。该函数通过使用相同条件过滤o.children数组来处理该问题。数组的过滤后的版本已重新分配给o.children。

然后,将过滤后的数组的长度作为前一个array.filter寻找的true / false值返回。如果数组为空,则长度为零,这是错误的,因此该项目被排除在外。否则,将返回非零值,这是正确的,因此将保留该项目。

答案 2 :(得分:1)

class A {
  static getFilteredArray(array, key, searchString) {
    const query = searchString.toLowerCase()

    const res = array.filter(function searchText(item) {
      const text = item[key].toLowerCase()

      if (text.includes(query)) {
        return true
      }

      if (item.children) { // if object has children, do same filtering for children
        item.children = item.children.filter(searchText)
        return item.children.length
        // same as below, but shorter
        // item.children = item.children.filter(function (child) {
        //     return searchText(child)
        // })
      }
    })

    return res
  }
}

const input = [{
  name: 'John',
  children: [{
    name: 'Alice'
  }]
}]
const output1 = A.getFilteredArray(input, 'name', 'Alic')
const output2 = A.getFilteredArray(input, 'name', 'XY')
console.log('Alic => ', output1)
console.log('XY =>', output2)

答案 3 :(得分:0)

返回不用于getObject。用于.filter()回调。

因此答案很简单:filter希望其回调返回一个true / false值,具体取决于天气,是否要保留或从结果数组中删除该对象。因此,返回长度就足够了,因为0falsy,而其他所有数字都是truthy

答案 4 :(得分:0)

ggorlen的回答很好地说明了此功能的工作原理。

但是我相信这个功能和ggorlen的简化不能实现filter要做的事情:它们会改变初始数据结构。如果您在ggorlen的示例中的调用之前和之后检查此值,您会注意到它从2变为1

arr[0].children[0].children.length

这个问题也出现在原始代码中。据我所知,没有一种简单的方法可以通过基于Array.prototype.filter的实现来解决此问题。因此,另一种实现是有道理的。这是我想出的,并通过ggorlen的测试用例进行了演示:

const getFilteredArray  = (arr, test) => arr .reduce 
  ( ( acc
    , {children = undefined, ...rest}
    , _  // index, ignored
    , __ // array, ignored
    , kids = children && getFilteredArray  (children, test)
  ) => test (rest) || (kids && kids .length)
    ? acc .concat ({
        ... rest, 
        ...(kids && kids .length ? {children: kids} : {})
      })
    : acc
  , []
  )

const arr = [
  {foo: "bar", children: [{foo: "baz", children: [{foo: "quux"}, {foo: "quuz"}]}]},
  {foo: "corge", children: [{foo: "quux"}]},
  {foo: "grault", children: [{foo: "bar"}]}
];

const test= (item) => item.foo == 'quux' 

console .log (
  getFilteredArray  (arr, test)
)

请注意,我使它比请求的通用一些,它使用任意谓词进行测试,而不是测试key属性是否与searchString值匹配。这使代码更简单,逻辑上的故障也更清楚。但是,如果您需要该确切的API,则可以做一个较小的更改。

const getFilteredArray  = (arr, key, searchString) => arr .reduce 
  ( ( acc
    , {children = undefined, ...rest}
    , _  // index, ignored
    , __ // array, ignored
    , kids = children && getFilteredArray  (children, key, searchString)
  ) => rest[key] === searchString || (kids && kids .length)
    ? acc .concat ({
        ... rest, 
        ...(kids && kids .length ? {children: kids} : {})
      })
    : acc
  , []
  )

可能缺少的一件事是谓词针对不包含子代的值运行。如果您希望能够将它们包括在内,那么工作就不多了。我们必须传递item来代替{children = undefined, ...rest}并将它们在函数主体内进行销毁。这将需要将主体从单个表达式更改为{-}一个表达式。

如果我不尝试与他人的API紧密匹配,我还将签名更改为

const getFilteredArray  = (test) => (arr) => arr .reduce ( /* ... */ )

此版本将允许我们部分应用谓词并获得可以针对不同输入运行的函数。

相关问题