让我们来做一个大胆的声明:for循环通常是无用的,而且还导致代码难以理解。当涉及迭代数组、查找元素、或对其排序或者你想到的任何东西,都可能有一个你可以使用的数组方法。

然而,尽管这些方法很有用,但是其中一些仍然没有被人所熟知和使用。我将会为你介绍一些有用的方法。可以将这篇文章作为你学习JavaScript数组方法的指南。

注:在开始之前,你需要知道一件事情:我对于函数式编程有偏见。所以我倾向使用不直接改变原始数组的方法。这样,我避免了副作用。我不是说你永远不应该改变一个数组,但至少要知道有些方法可以做到这点并且会导致副作用。副作用会导致一些不必要的改变,而不必要的改变会导致BUG.

好了,知道这点,我们就开始吧。

要点

当你使用数组时,你将会想知道这四个方法,分别是:map、filter、reduce和展开运算符。它们非常强大及实用。

map

你会经常使用到这个方法。基本上,每次你需要修改数组元素的时候,应该考虑使用map。

该方法接收一个参数:一个在数组每个元素上调用的方法。并且会返回一个新的数组,所以没有产生副作用。

const numbers = [1, 2, 3, 4]
const numbersPlusOne = numbers.map(n => n + 1) // 每个元素值都加1
console.log(numbersPlusOne) // [2, 3, 4, 5]

你也可以创建一个新的数组,只保留对象其中的一个特定属性值。

const allActivities = [
{ title: 'My activity', coordinates: [50.123, 3.291] },
{ title: 'Another activity', coordinates: [1.238, 4.292] },
// etc.
] const allCoordinates = allActivities.map(activity => activity.coordinates)
console.log(allCoordinates) // [[50.123, 3.291], [1.238, 4.292]]

所以,要记住,无论什么时候如果需要转换一个数组,可以考虑使用map。

filter

这里方法的名字表示的很明确了:当你想筛选数组时候就可以使用它。

如同map方法那样,filter方法会接收一个函数,数组中的每个元素都会去调用这个函数。这个函数需要返回一个布尔值:

  • 返回true-表示保留元素在数组
  • 返回false-表示不需要保留元素在数组

然后你就会得到你想要的一个新数组了。

比如下面例子,你可以只保留数组中的奇数

const numbers = [1, 2, 3, 4, 5, 6]
const oddNumbers = numbers.filter(n => n % 2 !== 0)
console.log(oddNumbers) // [1, 3, 5]

或者移除数组中某些特定的项目

const participants = [
{ id: 'a3f47', username: 'john' },
{ id: 'fek28', username: 'mary' },
{ id: 'n3j44', username: 'sam' },
] function removeParticipant(participants, id) {
return participants.filter(participant => participant.id !== id)
} console.log(removeParticipant(participants, 'a3f47')) // [{ id: 'fek28', username: 'mary' }, { id: 'n3j44', username: 'sam' }];

reduce

在我看来,这是最难理解的一个方法。但是一旦你掌握了,你可以使用它做很多事情。

基本用法是,reduce是用来将一个数组的值合并成一个值。它需要两个参数,一个回调函数,它是我们的reducer和一个可选的初始值(默认是数组的第一项)。reducer本身有四个参数:

  • 累加器:它会累加reducer中返回的值。
  • 数组当前值
  • 当前索引
  • The array reduce was called upon(暂未理解)

    大多数情况下,你只需用到前面两个参数:累加器和当前值。

    我们不用过于理论化了,下面是reduce的常见示例:
const numbers = [37, 12, 28, 4, 9]
const total = numbers.reduce((total, n) => total + n)
console.log(total) // 90

在第一次迭代中,累加器total,采用初始值37。返回的值为37+n,并且n的值为12,因此得到值为49。在第二次迭代,累加器值为49,返回的值为49+28=77。以此类推。

reduce十分强大,你可以用它来实现许多数组方法,比如map或filter:

const map = (arr, fn) => {
return arr.reduce((mappedArr, element) => {
return [...mappedArr, fn(element)]
}, [])
} console.log(map([1, 2, 3, 4], n => n + 1)) // [2, 3, 4, 5] const filter = (arr, fn) => {
return arr.reduce((filteredArr, element) => {
return fn(element) ? [...filteredArr] : [...filteredArr, element]
}, [])
} console.log(filter([1, 2, 3, 4, 5, 6], n => n % 2 === 0)) // [1, 3, 5]

基本上,我们给reduce一个空数组[]的初始值:我们的累加器。对于map方法,我们执行了一个函数,其结果在累加器的末尾添加,这要归功于扩展运算符(不要担心,后面会讲到)。对于filter方法,除了要在元素上执行过滤函数为外,几乎跟刚才的一样。如果返回true,则返回之前的数组,否则将元素加到数组里。

让我们看一个更高级的例子,深度扁平化一个数组,也就是说将类似[1, 2, 3, [4, [[[5, [6, 7]]]], 8]]的数组转化为[1, 2, 3, 4, 5, 6, 7, 8]。

function flatDeep(arr) {
return arr.reduce((flattenArray, element) => {
return Array.isArray(element)
? [...flattenArray, ...flatDeep(element)]
: [...flattenArray, element]
}, [])
} console.log(flatDeep([1, 2, 3, [4, [[[5, [6, 7]]]], 8]])) // [1, 2, 3, 4, 5, 6, 7, 8]

这个例子类似map,只是我们这里使用了递归。我不会去解析这个代码,因为超出了这篇文章的范围。如果你想了解更多关于递归的知识,可以查看这里的优秀资源

展开运算符(ES6)

我同意,这其实不是一个方法。但是使用展开运算符可以帮组你在使用数组时实现很多功能。实际上,你可以用来展开一个数组中的数组值。因此,你也可以用来复制数组或合并多个数组。

const numbers = [1, 2, 3]
const numbersCopy = [...numbers]
console.log(numbersCopy) // [1, 2, 3] const otherNumbers = [4, 5, 6]
const numbersConcatenated = [...numbers, ...otherNumbers]
console.log(numbersConcatenated) // [1, 2, 3, 4, 5, 6]

注意:展开运算符只是对原数组进行了浅拷贝。但浅拷贝是什么呢?

好吧,浅拷贝就是尽可能少地复制原始元素。所以,当你有一个包含数字、字符串或者布尔值(原始数据类型)的数组时,这是没有问题的,值的确是复制了。然而,对于对象和数组就不一样了,只会复制值的引用地址。因此,如果你浅拷贝一个包含对象的数组并且修改复制后数组的对象值,原始对象值同样会被修改,因为指向一样的引用地址。

const arr = ['foo', 42, { name: 'Thomas' }]
let copy = [...arr] copy[0] = 'bar' console.log(arr) // No mutations: ["foo", 42, { name: "Thomas" }]
console.log(copy) // ["bar", 42, { name: "Thomas" }] copy[2].name = 'Hello' console.log(arr) // /!\ MUTATION ["foo", 42, { name: "Hello" }]
console.log(copy) // ["bar", 42, { name: "Hello" }]

所以,如果你想实现对包含对象或数组的数组真正的拷贝(即深拷贝),可以使用lodash的cloneDeep函数。但是不要觉得你必须做这样的事情。这里的目标是要了解事情是如何运作的。

数组方法一

您将在下面找到其他方法,这些方法很有用,可以帮助您解决一些问题,例如搜索数组中的元素,获取数组的一部分等等。

includes(ES2015)

你是否使用过indexOf去判断数组中是否存在某个东西?这是糟糕的方法对吧?幸运的是,includes可以实现同样的事情。incluses方法接收一个参数,将会搜索数组中是否存在这个元素。

const sports = ['football', 'archery', 'judo']
const hasFootball = sports.includes('football')
console.log(hasFootball) // true

concat

concat方法可用于合并两个或多个数组。

const numbers = [1, 2, 3]
const otherNumbers = [4, 5, 6] const numbersConcatenated = numbers.concat(otherNumbers)
console.log(numbersConcatenated) // [1, 2, 3, 4, 5, 6] // You can merge as many arrays as you want
function concatAll(arr, ...arrays) {
return arr.concat(...arrays)
} console.log(concatAll([1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12])) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

forEach

每当你想为每个数组元素执行某些操作时,你都会想要使用它forEach。它需要一个函数作为一个参数,它自己带有三个参数:当前值,索引和数组:

const numbers = [1, 2, 3, 4, 5]
numbers.forEach(console.log)
// 1 0 [ 1, 2, 3 ]
// 2 1 [ 1, 2, 3 ]
// 3 2 [ 1, 2, 3 ]

indexOf

它用于返回在数组中找到给定元素的第一个索引。indexOf也被广泛用于检查元素是否存在数组中。说实话,我今天不会那么用。

const sports = ['football', 'archery', 'judo']

const judoIndex = sports.indexOf('judo')
console.log(judoIndex) // 2

find

find方法与filter方法非常相似。您必须为它提供一个测试每个数组元素的函数。但是find方法一旦找到测试通过的元素,就会停止测试。而不是filter,filter方法无论怎样都会迭代整个数组。

const users = [
{ id: 'af35', name: 'john' },
{ id: '6gbe', name: 'mary' },
{ id: '932j', name: 'gary' },
] const user = users.find(user => user.id === '6gbe')
console.log(user) // { id: '6gbe', name: 'mary' }

因此,当你想过滤整个数组时,考虑使用filter方法,当你想在数组查找特定元素时,考虑使用find方法。

findIndex

它与find方法完全相同,只是它返回找到的第一个元素的索引而不是直接返回元素。

const users = [
{ id: 'af35', name: 'john' },
{ id: '6gbe', name: 'mary' },
{ id: '932j', name: 'gary' },
] const user = users.findIndex(user => user.id === '6gbe')
console.log(user) // 1

你可能认为这findIndex是一样的indexOf。嗯...不完全是。第一个参数indexOf是原始值(boolean, number, string, null, undefined or a symbol),而第一个参数findIndex是回调函数。

因此,当你需要在原始值数组中搜索元素的索引时,可以使用indexOf。如果您有更复杂的元素,如对象,请使用findIndex。

slice

无论何时需要获取数组的一部分或复制数组,都可以使用slice。但要小心,就像展开运算符一样,slice返回该部分的浅拷贝!

在文章的开头我说过,for循环通常是无用的。让我举个例子说明如何摆脱它。

假设您想要从API检索一定数量的聊天消息,并且你只想显示其中的五个。有两种方法可以实现:一个带有for循环另一个带有slice。

// The "traditional way" to do it:
// Determine the number of messages to take and use a for loop
const nbMessages = messages.length < 5 ? messages.length : 5
let messagesToShow = []
for (let i = 0; i < nbMessages; i++) {
messagesToShow.push(posts[i])
} // Even if "arr" has less than 5 elements,
// slice will return an entire shallow copy of the original array
const messagesToShow = messages.slice(0, 5)

some

如果你想测试一个数组的至少一个元素通过测试,你可以使用some。就像map,filter或者find,some将回调函数作为其唯一参数。如果至少一个元件通过测试则返回true,否则返回false。

你可以在处理权限时使用some

const users = [
{
id: 'fe34',
permissions: ['read', 'write'],
},
{
id: 'a198',
permissions: [],
},
{
id: '18aa',
permissions: ['delete', 'read', 'write'],
},
] const hasDeletePermission = users.some(user =>
user.permissions.includes('delete')
)
console.log(hasDeletePermission) // true

every

类似于some方法,不同之处在于every方法测试所有元素是否通过测试(而不是至少一个)

const users = [
{
id: 'fe34',
permissions: ['read', 'write'],
},
{
id: 'a198',
permissions: [],
},
{
id: '18aa',
permissions: ['delete', 'read', 'write'],
},
] const hasAllReadPermission = users.every(user =>
user.permissions.includes('read')
)
console.log(hasAllReadPermission) // false

flat(ES2019)

这些是JavaScript世界中出现的全新的方法。基本上,flat通过将所有子数组元素连接到其中来创建新数组。它接受一个参数,一个数字,表示您想要展平数组的深度:

const numbers = [1, 2, [3, 4, [5, [6, 7]], [[[[8]]]]]]

const numbersflattenOnce = numbers.flat()
console.log(numbersflattenOnce) // [1, 2, 3, 4, Array[2], Array[1]] const numbersflattenTwice = numbers.flat(2)
console.log(numbersflattenTwice) // [1, 2, 3, 4, 5, Array[2], Array[1]] const numbersFlattenInfinity = numbers.flat(Infinity)
console.log(numbersFlattenInfinity) // [1, 2, 3, 4, 5, 6, 7, 8]

flatMap(ES2019)

你能猜出这种方法有用吗?我打赌你可以用这个名字。

首先,它在每个元素上运行map函数。然后它将数组一次展平。十分简单!

const sentences = [
'This is a sentence',
'This is another sentence',
"I can't find any original phrases",
] const allWords = sentences.flatMap(sentence => sentence.split(' '))
console.log(allWords) // ["This", "is", "a", "sentence", "This", "is", "another", "sentence", "I", "can't", "find", "any", "original", "phrases"]

在这个例子中,数组中有很多句子并且你想得到所有的单词。你可以直接使用flatMap方法而不是使用map方法来分片这些句子为单词然后进行展平化。

一个跟flatMap无关的例子,你可以使用reduce函数来计算单词的数量(只是展示另一个reduce的使用例子)

const wordsCount = allWords.reduce((count, word) => {
count[word] = count[word] ? count[word] + 1 : 1
return count
}, {})
console.log(wordsCount) // { This: 2, is: 2, a: 1, sentence: 2, another: 1, I: 1, "can't": 1, find: 1, any: 1, original: 1, phrases: 1, }

flatMap也用于响应式编程,可以看看这个例子

join

如果你需要根据数组的元素创建一个字符串,那么你可以使用join。它允许通过连接所有数组的元素来创建一个新的字符串,由提供的分隔符分隔。

例如,你可以使用join实现一目了然地显示活动的所有参与者。

onst participants = ['john', 'mary', 'gary']
const participantsFormatted = participants.join(', ')
console.log(participantsFormatted) // john, mary, gary

这是一个更真实的单词示例,你可能希望在获取其名称之前过滤参与者:

const potentialParticipants = [
{ id: 'k38i', name: 'john', age: 17 },
{ id: 'baf3', name: 'mary', age: 13 },
{ id: 'a111', name: 'gary', age: 24 },
{ id: 'fx34', name: 'emma', age: 34 },
] const participantsFormatted = potentialParticipants
.filter(user => user.age > 18)
.map(user => user.name)
.join(', ') console.log(participantsFormatted) // gary, emma

from

这是一个静态方法,可以从类似数组或可迭代的对象(例如字符串)创建新的数组。当你使用dom时它会很有用。

const nodes = document.querySelectorAll('.todo-item') // this is an instance of NodeList
const todoItems = Array.from(nodes) // now, you can use map, filter, etc. as you're workin with an array!

是否看到我们使用的是Array代替数组实例?这就是from被称为静态方法的原因。

然后你可以操作这些节点,例如通过forEach在每个节点上注册一个事件监听器:

todoItems.forEach(item => {
item.addEventListener('click', function() {
alert(`You clicked on ${item.innerHTML}`)
})
})

isArray

在这里,让我们来谈谈另一种数组的静态方法isArray。毫无意外,它会告诉你传递的值是否为数组。

基于以上的例子,这里我们得到:

const nodes = document.querySelectorAll('.todo-item')
console.log(Array.isArray(nodes)) // false
const todoItems = Array.from(nodes)
console.log(Array.isArray(todoItems)) // true

数组方法二(变更原始数组)

在下面找到其他常见的数组方法。不同之处在于它们修改了原始数组。改变数组没有错,但了解这个方法使用也是一件好事!

对于所有这些方法,如果你不想改变原始数组,只需事先制作浅拷贝或深拷贝:

const arr = [1, 2, 3, 4, 5]
const copy = [...arr] // or arr.slice()

sort

是的,sort方法会修改原始数组。实际上,它对数组的元素进行了排序。默认排序方法将所有元素转换为字符串并按字母顺序对它们进行排序。

const names = ['john', 'mary', 'gary', 'anna']
names.sort()
console.log(names) // ['anna', 'gary', 'john', 'mary']

因此,如果你来自Python编程背景,请小心,sort在数字数组上执行结果并不会如你期望那样:

const numbers = [23, 12, 17, 187, 3, 90]
numbers.sort()
console.log(numbers) // [12, 17, 187, 23, 3, 90]

【译】你应该了解的JavaScript数组方法的更多相关文章

  1. JavaScript数组方法--every、some、fill

    接上一篇,JavaScript数组方法--concat.push,继续其他的数组方法. every:every() 方法测试数组的所有元素是否都通过了指定函数的测试. 先看其使用方法: functio ...

  2. JavaScript数组方法大全(推荐)

    原网址:http://www.jb51.net/article/87930.htm 数组在笔试中经常会出现的面试题,javascript中的数组与其他语言中的数组有些不同,为了方便之后数组的方法学习, ...

  3. JavaScript 数组方法处理字符串 prototype

    js中数组有许多方法,如join.map,reverse.字符串没有这些方法,可以“借用”数组的方法来处理字符串. <!doctype html> <html lang=" ...

  4. 2018.2.27 JavaScript数组方法应用

    JavaScript数组方法应用 1.找出元素item在给定数组arr中的位置 function indexOf(arr,item){ return arr.indexOf(item); } func ...

  5. JavaScript数组方法详解

    JavaScript数组方法详解 JavaScript中数组的方法种类众多,在ES3-ES7不同版本时期都有新方法:并且数组的方法还有原型方法和从object继承的方法,这里我们只介绍数组在每个版本中 ...

  6. JavaScript数组方法速查,32个数组的常用方法和属性

    JavaScript数组方法速查手册极简版 http://30ke.cn/doc/js-array-method JavaScript数组方法速查手册极简版中共收了32个数组的常用方法和属性,并根据方 ...

  7. JavaScript数组方法大集合

    JavaScript数组方法集合 本文总结一下js数组处理用到的所有的方法.自己做个笔记. 数组方法 concat() 合并两个或多个数组 concat()能合并两个或者多个数组,不会更改当前数组,而 ...

  8. 一站式超全JavaScript数组方法大全

    一站式JavaScript数组方法大全(建议收藏) 方法一览表 详细操作 本人总结了JavaScript中有关数组的几乎所有方法(包含ES6之后新增的),并逐一用代码进行演示使用,希望可以帮助大家! ...

  9. Javascript数组方法(译)

    在JavaScript中,数组可以使用Array构造函数来创建,或使用[]快速创建,这也是首选的方法.数组是继承自Object的原型,并且他对typeof没有特殊的返回值,他只返回'object'. ...

随机推荐

  1. 《转》return *this和 return this有什么区别?

    别跟我说 return *this 表示返回当前对象,return this 表示返回当前对象的地址(指向当前对象的指针). 正确答案为:return *this 返回的是当前对象的克隆或者本身(若返 ...

  2. 【BZOJ 2119】股市的预测(SAM)

    SAM很好用的啊... 传送门 双倍经验:L-Gap Substrings 基本做法类似,这道题的差分改掉,map 改掉就好了QWQ noteskey 反正就是先差分一下,然后把首项丢掉(没有比较的对 ...

  3. Statically Linking freeglut

    It’s possible statically link freeglut into your applications, instead of dynamically linking agains ...

  4. java 系统属性

    java.version  Java 运行时环境版本 java.vendor  Java 运行时环境供应商 java.vendor.url  Java 供应商的 URL java.home  Java ...

  5. Hystrix浅谈

    Hystrix如何使用很多说明,看了很多博客,却发现能说明一些简单概念的文章就没有. 所以本文不太回去说如何使用 Hystrix ,但是会简明的说一下 一些概念 super(Setter.withGr ...

  6. Modbus库开发笔记之十:利用协议栈开发Mosbus RTU Slave应用

    上一节我们使用协议占开发了一个Modbus TCP Server应用.接下来我们使用协议栈在开发一个基于串行链路的Mosbus RTU Slave应用. 根据前面对协议栈的封装,我们需要引用Modbu ...

  7. maven install 报错 No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?

    1.控制台打印信息 [INFO] Scanning for projects... [INFO] [INFO] ---------------------< org.cqupt.mauger:R ...

  8. Oracle 口令文件:即 oracle密码文件

    一:文件路径位置 [oracle@localhost db_1]$ cd $ORACLE_HOME/dbs [oracle@localhost dbs]$ ls dbsorapwPROD1 hc_or ...

  9. Oracle12c Data Guard搭建手册

    Oracle12c Data Guard搭建手册 注:本文来源: 红黑联盟 < Oracle12c Data Guard搭建手册 > Oracle 12c 的DataGuard 是在CDB ...

  10. Confluence 6 管理 Atlassian 提供的 App

    Confluence 用户可以使用桌面应用来编辑一个已经上传到 Confluence 的文件,然后这个文件自动保存回 Confluence. 这个下载和上传的过程是通过 Atlassian Compa ...