Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: update filter() #8332

Merged
merged 7 commits into from
Sep 6, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4,101 +4,132 @@ slug: Web/JavaScript/Reference/Global_Objects/Array/filter
---
{{JSRef}}

**`filter()`** 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
**`filter()`** 方法创建给定数组一部分的[浅拷贝](/zh-CN/docs/Glossary/Shallow_copy),其包含通过所提供函数实现的测试的所有元素。

{{EmbedInteractiveExample("pages/js/array-filter.html")}}

## 语法

```plain
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
```js
// 箭头函数
filter((element) => { /* … */ } )
filter((element, index) => { /* … */ } )
filter((element, index, array) => { /* … */ } )

// 回调函数
filter(callbackFn)
filter(callbackFn, thisArg)

// 内联回调函数
filter(function(element) { /* … */ })
filter(function(element, index) { /* … */ })
filter(function(element, index, array){ /* … */ })
filter(function(element, index, array) { /* … */ }, thisArg)
```

### 参数

- `callback`
- `callbackFn`

- : 用来测试数组的每个元素的函数。返回 `true` 表示该元素通过测试,保留该元素,`false` 则不保留。它接受以下三个参数:
- : 用来测试数组中每个元素的函数。返回 `true` 表示该元素通过测试,保留该元素,`false` 则不保留。它接受以下三个参数:

- `element`
- : 数组中当前正在处理的元素。
- `index`{{optional_inline}}
- `index`
- : 正在处理的元素在数组中的索引。
- `array`{{optional_inline}}
- : 调用了 `filter` 的数组本身。
- `array`
- : 调用了 `filter()` 的数组本身。

- `thisArg`{{optional_inline}}
- : 执行 `callback` 时,用于 `this` 的值。
- : 执行 `callbackFn` 时,用于 `this` 的值。

### 返回值

一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

## 描述

`filter` 为数组中的每个元素调用一次 `callback` 函数,并利用所有使得 `callback` 返回 true 或[等价于 true 的值](/zh-CN/docs/Glossary/Truthy)的元素创建一个新数组。`callback` 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 `callback` 测试的元素会被跳过,不会被包含在新数组中。
`filter()` 为数组中的每个元素调用一次 `callbackFn` 函数,并利用所有使得 `callbackFn` 返回 true 或[等价于 true 的值](/zh-CN/docs/Glossary/Truthy)的元素创建一个新数组。`callbackFn` 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 `callbackFn` 测试的元素会被跳过,不会被包含在新数组中。

`callback` 被调用时传入三个参数:
`callbackFn` 被调用时传入三个参数:

1. 元素的值
2. 元素的索引
3. 被遍历的数组本身

如果为 `filter` 提供一个 `thisArg` 参数,则它会被作为 `callback` 被调用时的 `this` 值。否则,`callback` 的 `this` 值在非严格模式下将是全局对象,严格模式下为 `undefined`。`callback` 函数最终观察到的 `this` 值是根据[通常函数所看到的 "this"的规则](/zh-CN/docs/Web/JavaScript/Reference/Operators/this)确定的。
如果为 `filter()` 提供一个 `thisArg` 参数,则它会被作为 `callbackFn` 被调用时的 `this` 值。否则,`callbackFn` 的 `this` 值在非严格模式下将是全局对象,严格模式下为 `undefined`。`callbackFn` 函数最终观察到的 `this` 值是根据[通常函数所看到的 `this` 的规则](/zh-CN/docs/Web/JavaScript/Reference/Operators/this)确定的。

`filter()` 不会改变原数组。

`filter` 不会改变原数组,它返回过滤后的新数组
`filter()` 遍历的元素范围在第一次调用 `callbackFn` 之前就已经确定了。修改已经访问过的或在确定的范围之外创建的元素,将不会被 `callbackFn` 访问。如果以相同的方式删除数组中的现有元素,则不会访问它们

`filter` 遍历的元素范围在第一次调用 `callback` 之前就已经确定了。在调用 `filter` 之后被添加到数组中的元素不会被 `filter` 遍历到。如果已经存在的元素被改变了,则他们传入 `callback` 的值是 `filter` 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到
> **警告:** 在上一段中描述的并发修改通常会导致难以理解的代码,通常应该避免(特殊情况除外)

## 示例

### 筛选排除所有较小的值

下例使用 `filter` 创建了一个新数组,该数组的元素由原数组中值大于 10 的元素组成。
下例使用 `filter()` 创建了一个新数组,该数组的元素由原数组中值大于 `10` 的元素组成。

```js
function isBigEnough(element) {
return element >= 10;
function isBigEnough(value) {
return value >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);

const filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]
```

### 找出数组中所有的素数

下面的例子返回数组中的所有质数:

```js
const array = [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];

function isPrime(num) {
for (let i = 2; num > i; i++) {
if (num % i === 0) {
return false;
}
}
return num > 1;
}

console.log(array.filter(isPrime)); // [2, 3, 5, 7, 11, 13]
```

### 过滤 JSON 中的无效条目

以下示例使用 `filter()` 创建具有非零 `id` 的元素的 json。

```js
var arr = [
const arr = [
{ id: 15 },
{ id: -1 },
{ id: 0 },
{ id: 3 },
{ id: 12.2 },
{ },
{},
{ id: null },
{ id: NaN },
{ id: 'undefined' }
{ id: 'undefined' },
];

var invalidEntries = 0;

function isNumber(obj) {
return obj !== undefined && typeof(obj) === 'number' && !isNaN(obj);
}
let invalidEntries = 0;

function filterByID(item) {
if (isNumber(item.id) && item.id !== 0) {
if (Number.isFinite(item.id) && item.id !== 0) {
return true;
}
invalidEntries++;
return false;
}

var arrByID = arr.filter(filterByID);
const arrByID = arr.filter(filterByID);

console.log('Filtered Array\n', arrByID);
// Filtered Array
// 过滤后的数组
// [{ id: 15 }, { id: -1 }, { id: 3 }, { id: 12.2 }]

console.log('Number of Invalid Entries = ', invalidEntries);
Expand All @@ -109,79 +140,58 @@ console.log('Number of Invalid Entries = ', invalidEntries);

下例使用 `filter()` 根据搜索条件来过滤数组内容。

```js
var fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];

/**
* Array filters items based on search criteria (query)
*/
function filterItems(query) {
return fruits.filter(function(el) {
return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
})
}

console.log(filterItems('ap')); // ['apple', 'grapes']
console.log(filterItems('an')); // ['banana', 'mango', 'orange']
```

#### ES2015 实现

```js
const fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];

/**
* Array filters items based on search criteria (query)
* 根据搜索条件(查询)筛选数组项
*/
const filterItems = (query) => {
return fruits.filter((el) =>
el.toLowerCase().indexOf(query.toLowerCase()) > -1
);
function filterItems(arr, query) {
return arr.filter((el) => el.toLowerCase().includes(query.toLowerCase()));
}

console.log(filterItems('ap')); // ['apple', 'grapes']
console.log(filterItems('an')); // ['banana', 'mango', 'orange']
console.log(filterItems(fruits, 'ap')); // ['apple', 'grapes']
console.log(filterItems(fruits, 'an')); // ['banana', 'mango', 'orange']
```

## Polyfill
### 影响初始数组(修改、追加和删除)

`filter` 被添加到 ECMA-262 标准第 5 版中,因此在某些实现环境中不被支持。可以把下面的代码插入到脚本的开头来解决此问题,该代码允许在那些没有原生支持 `filter` 的实现环境中使用它。该算法是 ECMA-262 第 5 版中指定的算法,假定 `fn.call` 等价于 {{jsxref("Function.prototype.call")}} 的初始值,且 {{jsxref("Array.prototype.push")}} 拥有它的初始值
下面的示例测试在修改数组时 `filter()` 方法的行为

```js
if (!Array.prototype.filter){
Array.prototype.filter = function(func, thisArg) {
'use strict';
if ( ! ((typeof func === 'Function' || typeof func === 'function') && this) )
throw new TypeError();

var len = this.length >>> 0,
res = new Array(len), // preallocate array
t = this, c = 0, i = -1;
if (thisArg === undefined){
while (++i !== len){
// checks to see if the key was set
if (i in this){
if (func(t[i], i, t)){
res[c++] = t[i];
}
}
}
}
else{
while (++i !== len){
// checks to see if the key was set
if (i in this){
if (func.call(thisArg, t[i], i, t)){
res[c++] = t[i];
}
}
}
}

res.length = c; // shrink down array to proper size
return res;
};
}
// 修改每个单词
let words = ['spray', 'limit', 'exuberant', 'destruction', 'elite', 'present'];

const modifiedWords = words.filter((word, index, arr) => {
arr[index + 1] += ' extra';
return word.length < 6;
});

console.log(modifiedWords);
// 注意,在长度为6以下有三个单词,但是由于它们已经被修改,所以返回一个单词
// ["spray"]

// 添加新单词
words = ['spray', 'limit', 'exuberant', 'destruction', 'elite', 'present'];
const appendedWords = words.filter((word, index, arr) => {
arr.push('new');
return word.length < 6;
})

console.log(appendedWords);
// 只有三个符合条件,即使 `words`本身现在有更多字符长度小于 6 的单词
// ["spray" ,"limit" ,"elite"]

// 删除单词
words = ['spray', 'limit', 'exuberant', 'destruction', 'elite', 'present'];
const deleteWords = words.filter((word, index, arr) => {
arr.pop();
return word.length < 6;
})

console.log(deleteWords);
// 注意,'elite' 也没有获得,因为它在过滤器达到之前就已经从 'words' 弹出了
// ["spray" ,"limit"]
```

## 规范
Expand All @@ -194,7 +204,9 @@ if (!Array.prototype.filter){

## 参见

- [Polyfill of `Array.prototype.filter` in `core-js`](https://github.com/zloirock/core-js#ecmascript-array)
- {{jsxref("Array.prototype.forEach()")}}
- {{jsxref("Array.prototype.every()")}}
- {{jsxref("Array.prototype.some()")}}
- {{jsxref("Array.prototype.reduce()")}}
- {{jsxref("Array.prototype.find()")}}