我们为您呈现由Thomas Lombart发表的文章的译文,该文章已发布在medium.freecodecamp.org上。 该翻译经作者许可出版。
使用reduce方法缩小数组的示例让我大胆地声明:循环通常是无用的,并且使代码难以阅读。 对于数组中的迭代,搜索,排序元素和其他类似操作,可以使用以下方法之一。
尽管有效,但是大多数这些方法仍然鲜为人知,并且不太流行。 我会为您辛勤工作,并讨论最有用的。 阅读本文作为JavaScript数组方法的指南。
注意 :在开始之前,您需要了解一件事:我偏向于函数式编程。 为了避免产生副作用,我努力应用不会直接修改原始数组的方法。 我并不是要告诉您根本不要更改数组,但是值得考虑的是某些方法会导致这种情况。 结果,会出现副作用,不必要的更改以及错误。
本文最初发布在
thomlom.dev上 ,您可以在其中找到更多的Web开发材料。
基础知识
如果要使用数组,有四种方法值得了解。 这些是
map
,
filter
,
reduce
和
spread
算子。 它们是有效和有用的。
地图您将经常使用
map
方法。 通常,每次需要更改数组的元素时,请考虑此选项。
它有一个参数-在数组的每个元素上调用的函数,然后返回一个
新数组,这样就不会有副作用。
const numbers = [1, 2, 3, 4] const numbersPlusOne = numbers.map(n => n + 1) console.log(numbersPlusOne)
您还可以创建一个仅存储对象的一个特定属性的新数组。
const allActivities = [ { title: 'My activity', coordinates: [50.123, 3.291] }, { title: 'Another activity', coordinates: [1.238, 4.292] } ] const allCoordinates = allActivities.map(activity => activity.coordinates) console.log(allCoordinates)
因此,请记住:当您需要
更改数组时,请考虑使用
map 。
过滤器该方法的名称不言而喻:当您要过滤数组时使用它。
与
map
一样,
filter
将在数组的每个元素上调用的函数作为单个参数。 此函数应返回一个布尔值:
true
如果要将元素保存在数组中;false
如果您不想保存它。
结果,您将拥有要保留的元素的正确新数组。
例如,只能将奇数存储在数组中。
const numbers = [1, 2, 3, 4, 5, 6] const oddNumbers = numbers.filter(n => n % 2 !== 0) console.log(oddNumbers)
您还可以使用过滤器删除数组中的特定元素。
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'))
减少我认为,这种方法最难理解。 但是,一旦掌握它,您将拥有很多机会。
通常,
reduce
方法采用值数组,并将它们连接为单个值。 它带有两个参数,一个回调函数(它是
reducer )和一个可选的初始值(它是默认情况下数组的第一个元素)。 变速箱本身具有四个参数:
- 收集变速箱返回值的电池;
- 数组的当前值;
- 当前指数;
- 为其调用
reduce
方法的数组。
基本上,您将只使用前两个参数-电池和当前值。
但是,让我们不深入理论并考虑应用
reduce
的最常见示例。
const numbers = [37, 12, 28, 4, 9] const total = numbers.reduce((total, n) => total + n) console.log(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))
通常,我们将初始值
[]
分配给
reduce
方法-累加器。 对于
map
我们运行一个函数,该函数使用
传播运算符将其结果添加到电池末尾(我们将在下面讨论,不用担心)。 对于
filter
几乎在做同样的事情,我们只在元素上运行filter函数。 如果为true,则返回
前一个数组。 否则,将元素添加到数组的末尾。
让我们看一个更复杂的示例:将数组
[1, 2, 3, [4, [[[5, [6, 7]]]], 8]]
大大减少为
[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]]))
这个例子与
map
非常相似,除了我们在这里使用递归。 我不会详细介绍递归,因为这超出了本主题的范围,但是如果您想了解更多信息,请转至这个
出色的资源 。
价差报表(ES2015)我同意,这不是方法。 但是,传播运算符在处理数组时可帮助实现不同的目标。 您可以应用它以将一个数组的值扩展为另一个数组的值,然后进行复制或将多个数组链接在一起。
const numbers = [1, 2, 3] const numbersCopy = [...numbers] console.log(numbersCopy)
注意 :spread语句会
复制原始数组。 但是“表面”是什么意思?
这样的副本将尽可能少地复制原始元素。 如果您有一个包含数字,字符串或布尔值(
原始类型 )的数组,则没有问题,并且这些值实际上是重复的。 但是,
对象和
数组的情况有所不同:仅复制对原始值的
引用 。 因此,如果对包含对象的数组进行浅表复制并在复制的数组中更改对象,则原始对象中的对象也会更改,因为它们具有
相同的引用 。
const arr = ['foo', 42, { name: 'Thomas' }] let copy = [...arr] copy[0] = 'bar' console.log(arr)
因此,如果要创建包含一个或多个对象的数组的真实副本,则可以使用lodash函数(如
cloneDeep) 。 但是不要认为自己有义务这样做。 您的目标是
找出所有事情在幕后运作 。
有用的方法
在下面,您将找到其他有用的方法,这些方法对于解决问题(例如在数组中查找元素,删除数组的一部分等等)也很有用。
包括(ES2015)您是否曾经使用
indexOf
来确定数组中是否存在元素? 一种可怕的检查方法,对吗?
幸运的是,
includes
方法为我们进行了验证。 设置include的参数,它将在数组中搜索元素。
const sports = ['football', 'archery', 'judo'] const hasFootball = sports.includes('football') console.log(hasFootball)
康卡特concat方法可用于合并两个或更多数组。
const numbers = [1, 2, 3] const otherNumbers = [4, 5, 6] const numbersConcatenated = numbers.concat(otherNumbers) console.log(numbersConcatenated)
每次如果要对数组的每个元素执行操作,则可以使用
forEach
方法。 它使用一个函数作为参数,然后又使用三个参数:当前值,索引和数组。
const numbers = [1, 2, 3, 4, 5] numbers.forEach(console.log)
indexOf此方法用于返回可以在数组中找到元素的第一个索引。 同样,
indexOf
经常检查数组中元素的存在。 老实说,现在我很少使用它了。
const sports = ['football', 'archery', 'judo'] const judoIndex = sports.indexOf('judo') console.log(judoIndex)
找find
方法类似于
filter
。 您需要为其提供测试数组中每个元素的函数。 但是,只要找到通过测试的项目,
find
停止测试。 无论哪种情况,这
都不是遍历整个数组
的 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)
因此,当您要过滤
整个数组时,请使用
filter
方法;当您确定要在数组中查找
唯一元素时,请使用
find
方法。
findIndex此方法与
find
几乎相同,但是它返回
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)
您可能会认为
findIndex
和
indexOf
是同一件事。 不完全是
indexOf
的第一个参数是原始值(布尔值,数字,字符串,未定义的值或字符),而第一个参数
findIndex
是回调函数。
因此,当您需要在原始值数组中查找元素的索引时,可以使用
indexOf
。 如果您有更复杂的元素(例如对象),请使用
findIndex
。
切片当需要加入数组或复制数组时,可以参考
slice
方法。 但是要小心:像散布运算符一样,
slice
返回
浅表副本 。
const numbers = [1, 2, 3, 4, 5] const copy = numbers.slice()
在本文的开头,我提到循环通常是无用的。 让我告诉你如何摆脱它们。
假设您要从API返回一定数量的聊天消息,而您只需要查看其中的五个即可。 以下是两种方法:一种使用循环,另一种使用
slice
方法。
一些如果要检查数组的
至少一个元素是否通过测试,则可以使用
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)
每一个此方法类似于
some
,除了它检查
每个元素(而
不是一个 )是否符合条件。
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)
平(ES2019)这些是JavaScript世界中的全新方法。 通常,
flat
创建一个新数组,连接嵌套数组的所有元素。 它采用一个参数-一个数字,表示要减少数组维数的数量。
const numbers = [1, 2, [3, 4, [5, [6, 7]], [[[[8]]]]]] const numbersflattenOnce = numbers.flat() console.log(numbersflattenOnce)
flatMap(ES2019)猜猜这种方法是做什么的? 我敢打赌你会明白一个名字。
首先,它为每个元素运行映射功能,然后一次缩小数组。 就这么简单!
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)
在此示例中,数组中有很多句子,并且您希望获得所有单词。 您可以立即使用
flatMap
,而不是使用
map
方法并将所有句子分成单词,然后缩短数组。
然后,您可以使用
reduce
函数计算单词的数量(这不适用于
flatMap
,我只想向您展示另一个使用
reduce
方法的示例)。
const wordsCount = allWords.reduce((count, word) => { count[word] = count[word] ? count[word] + 1 : 1 return count }, {}) console.log(wordsCount)
flatMap
方法也经常在反应式编程中使用。 您可以
在此处查看示例。
参加如果需要基于数组元素创建字符串,则需要使用
join
方法。 它允许您通过连接数组的所有元素(由提供的分隔符分隔)来创建新行。
例如,使用
join
您可以直观地显示活动中的所有参与者。
const participants = ['john', 'mary', 'gary'] const participantsFormatted = participants.join(', ') console.log(participantsFormatted)
这是一个更实际的示例,您可以首先过滤参与者并获取他们的名字。
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)
来自这是一种
静态方法,可从类似数组的对象或可迭代对象(例如字符串)中创建新数组。 使用文档对象模型时,它会派上用场。
const nodes = document.querySelectorAll('.todo-item')
您是否看到我们使用数组类型而不是数组实例? 这就是为什么将此方法称为静态的原因。
然后,您可以体验节点的乐趣,例如,使用
forEach
方法为每个节点注册事件侦听器。
todoItems.forEach(item => { item.addEventListener('click', function() { alert(`You clicked on ${item.innerHTML}`) }) })
值得了解的阵列修改
以下是其他标准方法。 它们的区别在于它们修改了原始数组。 所做的更改没有错,但是您在工作时应该考虑这一点。
如果您不想在使用这些方法时修改原始数组,请提前制作表面或完整副本。
const arr = [1, 2, 3, 4, 5] const copy = [...arr]
排序是的,
sort
修改了原始数组。 实际上,它可以对数组中的元素进行排序。 默认的排序方法将所有元素转换为字符串,然后按字母顺序对其进行排序。
const names = ['john', 'mary', 'gary', 'anna'] names.sort() console.log(names)
注意:例如,如果您从Python语言切换,那么在处理数字数组时使用
sort
方法将无法获得预期的结果。
const numbers = [23, 12, 17, 187, 3, 90] numbers.sort() console.log(numbers)
然后如何对数组排序?
sort
方法具有一个功能-
比较功能 。 它具有两个参数:第一个元素(
)和第二个用于比较的元素(
b
)。 这两个元素之间的比较需要返回一个数字:
- 如果值是负数-a排在
b
之前; - 如果值是正数,则
b
在a
之前排序; - 如果值为0,则保持不变。
然后,您可以对数字进行排序。
const numbers = [23, 12, 17, 187, 3, 90] numbers.sort((a, b) => a - b) console.log(numbers)
或者,您可以从最新的日期开始对日期进行排序。
const posts = [ { title: 'Create a Discord bot under 15 minutes', date: new Date(2018, 11, 26), }, { title: 'How to get better at writing CSS', date: new Date(2018, 06, 17) }, { title: 'JavaScript arrays', date: new Date() }, ] posts.sort((a, b) => a.date - b.date)
填满fill
方法使用指定的值修改或填充数组的所有元素,从开始索引到结束。 出色使用
fill
一个示例是使用初始数据填充新数组。
倒转在我看来,该方法的名称充分说明了其本质。
const numbers = [1, 2, 3, 4, 5] numbers.reverse() console.log(numbers)
流行音乐此方法从数组中删除最后一个元素并返回它。
const messages = ['Hello', 'Hey', 'How are you?', "I'm fine"] const lastMessage = messages.pop() console.log(messages)
可以替换的方法
在上一节中,您将找到修改原始数组的方法,这些方法很容易找到替代方法。 我并不是说它们需要打折,我只是想向您传达一些方法有副作用,可以替代。
推该方法经常使用。 它允许您向该数组添加一个或多个元素,以及在前一个数组的基础上构建一个新数组。
const todoItems = [1, 2, 3, 4, 5] const itemsIncremented = [] for (let i = 0; i < items.length; i++) { itemsIncremented.push(items[i] + 1) } console.log(itemsIncremented)
如果您需要在另一个数组的基础上构建一个数组,例如
itemsIncremented
方法,那么我们已经熟悉了合适的
map
,
filter
或
reduce
itemsIncremented
。 例如,我们可以使用
map
来做到这一点。
const itemsIncremented = todoItems.map(x => x + 1)
而且,如果要在需要添加新元素时使用
push
,则散布运算符非常有用。
const todos = ['Write an article', 'Proofreading'] console.log([...todos, 'Publish the article'])
接头通常可以使用
splice
来清理特定索引处的元素。 您可以使用
filter
方法执行相同的操作。
const months = ['January', 'February', 'March', 'April', ' May']
您可能会问:如果我需要删除很多元素怎么办? 然后使用
slice
。
const months = ['January', 'February', 'March', 'April', ' May']
换档shift
方法删除数组的第一个元素并返回它。 要以函数式编程的方式执行此操作,可以使用spread或rest语句。
const numbers = [1, 2, 3, 4, 5]
不变unshift方法允许您将一个或多个元素添加到数组的开头。 像
shift
一样,您可以使用价差运算符执行此操作。
const numbers = [3, 4, 5]
TL; DR
- 当您要对数组执行某些操作时,请勿使用for循环 ,也不要重新发明轮子,因为上面很可能有一种方法可以满足您的需要。
- 大多数情况下,您将使用
map
, filter
, reduce
方法和散布运算符-这些对于任何开发人员都是重要的工具。 - 还有许多很高兴知道的数组方法:
slice
, some
, flatMap
等。了解它们并在必要时应用。 - 副作用可能导致不必要的变化。 请记住,某些方法会修改您的原始数组。
slice
方法和散布运算符制作浅表副本。 结果,对象和子数组将具有相同的链接-这也值得牢记。- 修改数组的旧方法可以替换为新方法。 您决定要做什么。
现在,您了解了有关JavaScript数组的所有知识。 如果您喜欢
这篇文章 ,请单击“ Pat”按钮(最多50次,如果需要:-),然后共享它。 并随时在评论中分享您的印象!