JavaScript filter() 方法的混淆

10

作为Learn JavaScript Properly的一部分,我正在学习《JavaScript权威指南》,但在第七章有关数组方法的部分中,我对filter()方法的推理感到困难。

以下是提供的示例:

filter()方法返回一个包含调用它的数组的子集的数组。你传递给它的函数应该是谓词:一个返回true或false的函数。谓词被调用就像对forEach()map()一样。如果返回值为true或可以转换为true的值,则传递给谓词的元素是子集的成员,并添加到将成为返回值的数组中。

例如:

a = [5, 4, 3, 2, 1];
smallvalues = a.filter(function(x) { return x < 3 });   // [2, 1]
everyother = a.filter(function(x,i) { return i%2==0 }); // [5, 3, 1] 

我感到困惑的是在everyother行中如何将i应用于x。这是我认为正在发生的事情:
  1. i(a[]的索引)被传递到函数x,该函数将谓词应用于a[]的每个元素并返回[4,2]

  2. 然后函数说“从a[]中过滤出[4,2]”...我对此感到非常模糊。

当我在控制台上尝试时,我尝试了:

everyother = a.filter(function(i) { return i%2==0 });  // returns [4, 2]

这是我所期望的,但是当我将上面的代码更改为下面的代码时,我不理解JS在处理参数时内部发生了什么。

everyother = a.filter(function(x,i) { return i%2==0 }); // returns [5, 3, 1]

我知道数组方法的应用方式是这样的:function(element, index, array)
对于这个特定的例子,我很清楚我可以用另一种方式得到期望的结果:
everyother = a.filter(function(x) { return x%2!=0 }); // returns [5, 3, 1]

但我怀疑这种思路恰恰错过了示例试图传达的重点......我只是没有理解它。


3
在JavaScript中,参数始终是可选的。过滤器回调函数的签名为fn(element,index,arr)。第一个例子中,i指的是元素,而在第二个例子中,它指的是索引。在第二个例子中,您只是获取其索引为偶数的元素。 - Benjamin Gruenbaum
我认为这个例子只是试图向您说明,您可以同时使用值本身和数组中的索引来作为谓词。不确定有什么不清楚的地方。 - Bergi
6个回答

9

你的例子非常简单清晰:

a = [5, 4, 3, 2, 1];
smallvalues = a.filter(function(x) { return x < 3 });   // [2, 1]
everyother = a.filter(function(x,i) { return i%2==0 }); // [5, 3, 1]

第一个语句的意思是:“返回所有小于3的元素(x)”,结果并不惊人。
第二个语句的意思是:“返回所有索引(i)为偶数(包括0)的元素”。
其中,x被忽略了。
你也可以写成 [5, 4, 3, 2, 1].filter(function(_,x){return x%2===0}) 有关Array.prototype.filter()的更多信息,请参见MDN文档

@Think-Twice非常感谢您对我的回答进行了改进。但是关键是,OP本人使用的是_ES5_。为了解释他的代码为什么会产生这样的结果,没有必要解释它在ES6下的工作原理。他没有问如何使用ES6来改进代码。因此,我还原了原始答案,这是_技术上正确的_。 - Thomas Junk
@Think-Twice 我也在使用 ES6 ;) - Thomas Junk

7
当您使用两个参数的函数调用filter时,第一个参数绑定到数组元素值,第二个参数(可选)绑定到元素索引。
您的困惑源于输入数组[5,4,3,2,1]有点特殊-具有偶数索引(5、3、1)的元素是奇数,而具有奇数索引(4、2)的元素是偶数。
这使得这个过滤谓词...%2始终选择相同“种类”的元素,取决于您传递作为谓词参数的内容(值或索引),您将获得奇数或偶数元素。
我的建议是选择另一个数组来测试您的过滤方法以消除混淆。该数组应混合索引和值的奇偶性,例如[1,3,4,5,7,8]。当谓词考虑到值或索引时,您将立即观察到发生的情况。
还要记住,在创建过滤谓词时,正式参数的名称是任意的,重要的是它们的位置。无论如何称呼第一个参数,它都代表值,第二个代表索引。如果不小心冲突了参数名称,并且您将第一个参数称为i,然后将第二个参数称为i,那么它在两种情况下都绑定到其他内容。

啊,我一看到你的第二句话就恍然大悟了。我之前是从 i 包含的值的角度来看待它,而不是它所在位置的数字。感谢你的帮助。 - benvenker

1
a = [5, 4, 3, 2, 1];
everyother = a.filter(function(x,i) { return i%2==0 }); // [5, 3, 1] 

x是数组中的每个元素。(第一次迭代时x=5,第二次迭代时x=4,以此类推..)
i是索引-(第一次迭代时i = 0,第二次迭代时i = 1,以此类推..)
所以,在问题中(对于第一次迭代-i%2变为0%2,等于0且条件成立。第一个元素被返回到数组中..因此返回5,)。 接下来,1%2!=0,因此4被删除。 2%2 == 0,因此3保留。 (以此类推..)
在这个语法中:x在每次迭代中都有一个值,但它没有在条件中使用。
提示:filter()始终期望布尔值。无论返回什么(true或false)决定了该值(或元素)是否留在数组中。

1

这很简单...在everyother中,i是每个元素的索引,i可能的值只有0、1、2、3、4,因为数组中有五个元素。

现在,在所有的i值中,只有0、2、4可以被2整除,这就是为什么你得到那些索引值的值,即[5,3,1]


0

a = [5, 4, 3, 2, 1];
smallvalues = a.filter(obj => obj < 3); console.log(smallvalues)
everyother = a.filter(ele => ele % 2); console.log(everyother)// 

过滤函数可以简单地编写成这样;


0

const state.contactList = [{
name: 'jane',
email: 'jane@gmail.com'
},{},{},...]

const fileredArray = state.contactsList.filter((contactItem) => {
  const regex = new RegExp(`${action.payload}`, 'gi');
  return contactItem.nameProperty.match(regex) || contactItem.emailProperty.match(regex);
});


// contactList: all the contacts stored in state
// action.payload: whatever typed in search field


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接