您需要了解的有关JavaScript数组的知识

我们为您呈现由Thomas Lombart发表的文章的译文,该文章已发布在medium.freecodecamp.org上。 该翻译经作者许可出版。


使用reduce方法缩小数组的示例

让我大胆地声明:循环通常是无用的,并且使代码难以阅读。 对于数组中的迭代,搜索,排序元素和其他类似操作,可以使用以下方法之一。

尽管有效,但是大多数这些方法仍然鲜为人知,并且不太流行。 我会为您辛勤工作,并讨论最有用的。 阅读本文作为JavaScript数组方法的指南。

注意 :在开始之前,您需要了解一件事:我偏向于函数式编程。 为了避免产生副作用,我努力应用不会直接修改原始数组的方法。 我并不是要告诉您根本不要更改数组,但是值得考虑的是某些方法会导致这种情况。 结果,会出现副作用,不必要的更改以及错误。

本文最初发布在thomlom.dev上 ,您可以在其中找到更多的Web开发材料。

基础知识


如果要使用数组,有四种方法值得了解。 这些是mapfilterreducespread算子。 它们是有效和有用的。

地图
您将经常使用map方法。 通常,每次需要更改数组的元素时,请考虑此选项。

它有一个参数-在数组的每个元素上调用的函数,然后返回一个新数组,这样就不会有副作用。

 const numbers = [1, 2, 3, 4] const numbersPlusOne = numbers.map(n => n + 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] } ] const allCoordinates = allActivities.map(activity => activity.coordinates) console.log(allCoordinates) // [[50.123, 3.291], [1.238, 4.292]] 

因此,请记住:当您需要更改数组时,请考虑使用map

过滤器
该方法的名称不言而喻:当您要过滤数组时使用它。

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方法采用值数组,并将它们连接为单个值。 它带有两个参数,一个回调函数(它是reducer )和一个可选的初始值(它是默认情况下数组的第一个元素)。 变速箱本身具有四个参数:

  • 收集变速箱返回值的电池;
  • 数组的当前值;
  • 当前指数;
  • 为其调用reduce方法的数组。

基本上,您将只使用前两个参数-电池和当前值。

但是,让我们不深入理论并考虑应用reduce的最常见示例。

 const numbers = [37, 12, 28, 4, 9] const total = numbers.reduce((total, n) => total + n) console.log(total) // 90 

在第一次迭代中,作为总和的累加器取初始值37。返回值为37 + n,其中n =12。我们得到49。

在第二次迭代期间,累加器为49,返回值为49 + 28 =77。依此类推。

reduce方法的功能如此强大,您可以使用它来构建许多数组方法,例如mapfilter

 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几乎在做同样的事情,我们只在元素上运行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]])) // [1, 2, 3, 4, 5, 6, 7, 8] 

这个例子与map非常相似,除了我们在这里使用递归。 我不会详细介绍递归,因为这超出了本主题的范围,但是如果您想了解更多信息,请转至这个出色的资源

价差报表(ES2015)
我同意,这不是方法。 但是,传播运算符在处理数组时可帮助实现不同的目标。 您可以应用它以将一个数组的值扩展为另一个数组的值,然后进行复制或将多个数组链接在一起。

 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] 

注意 :spread语句会复制原始数组。 但是“表面”是什么意思?

这样的副本将尽可能少地复制原始元素。 如果您有一个包含数字,字符串或布尔值( 原始类型 )的数组,则没有问题,并且这些值实际上是重复的。 但是, 对象数组的情况有所不同:仅复制对原始值的引用 。 因此,如果对包含对象的数组进行浅表复制并在复制的数组中更改对象,则原始对象中的对象也会更改,因为它们具有相同的引用

 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) 。 但是不要认为自己有义务这样做。 您的目标是找出所有事情在幕后运作

有用的方法


在下面,您将找到其他有用的方法,这些方法对于解决问题(例如在数组中查找元素,删除数组的一部分等等)也很有用。

包括(ES2015)
您是否曾经使用indexOf来确定数组中是否存在元素? 一种可怕的检查方法,对吗?

幸运的是, includes方法为我们进行了验证。 设置include的参数,它将在数组中搜索元素。

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

康卡特
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方法。 它使用一个函数作为参数,然后又使用三个参数:当前值,索引和数组。

 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方法类似于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) // { id: '6gbe', name: 'mary' } 

因此,当您要过滤整个数组时,请使用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) // 1 

您可能会认为findIndexindexOf是同一件事。 不完全是 indexOf的第一个参数是原始值(布尔值,数字,字符串,未定义的值或字符),而第一个参数findIndex是回调函数。

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

切片
当需要加入数组或复制数组时,可以参考slice方法。 但是要小心:像散布运算符一样, slice返回浅表副本

 const numbers = [1, 2, 3, 4, 5] const copy = numbers.slice() 

在本文的开头,我提到循环通常是无用的。 让我告诉你如何摆脱它们。

假设您要从API返回一定数量的聊天消息,而您只需要查看其中的五个即可。 以下是两种方法:一种使用循环,另一种使用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 。 与mapfilterfindsome方法将回调函数作为唯一参数,然后如果至少一个元素通过了检查,则返回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 

每一个
此方法类似于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) // false 

平(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)
猜猜这种方法是做什么的? 我敢打赌你会明白一个名字。

首先,它为每个元素运行映射功能,然后一次缩小数组。 就这么简单!

 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方法并将所有句子分成单词,然后缩短数组。

然后,您可以使用reduce函数计算单词的数量(这不适用于flatMap ,我只想向您展示另一个使用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您可以直观地显示活动中的所有参与者。

 const 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 

来自
这是一种静态方法,可从类似数组的对象或可迭代对象(例如字符串)中创建新数组。 使用文档对象模型时,它会派上用场。

 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! 

您是否看到我们使用数组类型而不是数组实例? 这就是为什么将此方法称为静态的原因。

然后,您可以体验节点的乐趣,例如,使用forEach方法为每个节点注册事件侦听器。

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

值得了解的阵列修改


以下是其他标准方法。 它们的区别在于它们修改了原始数组。 所做的更改没有错,但是您在工作时应该考虑这一点。

如果您不想在使用这些方法时修改原始数组,请提前制作表面或完整副本。

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

排序
是的, 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] 

然后如何对数组排序? sort方法具有一个功能- 比较功能 。 它具有两个参数:第一个元素( )和第二个用于比较的元素( b )。 这两个元素之间的比较需要返回一个数字:

  • 如果值是负数-a排在b之前;
  • 如果值是正数,则ba之前排序;
  • 如果值为0,则保持不变。

然后,您可以对数字进行排序。

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

或者,您可以从最新的日期开始对日期进行排序。

 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) // Substracting two dates returns the difference in millisecond between them console.log(posts) // [ { title: 'How to get better at writing CSS', // date: 2018-07-17T00:00:00.000Z }, // { title: 'Create a Discord bot under 15 minutes', // date: 2018-12-26T00:00:00.000Z }, // { title: 'Learn Javascript arrays the functional way', // date: 2019-03-16T10:31:00.208Z } ] 

填满
fill方法使用指定的值修改或填充数组的所有元素,从开始索引到结束。 出色使用fill一个示例是使用初始数据填充新数组。

 // Normally I would have called a function that generates ids and random names but let's not bother with that here. function fakeUser() { return { id: 'fe38', name: 'thomas', } } const posts = Array(3).fill(fakeUser()) console.log(posts) // [{ id: "fe38", name: "thomas" }, { id: "fe38", name: "thomas" }, { id: "fe38", name: "thomas" }] 

倒转
在我看来,该方法的名称充分说明了其本质。

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

流行音乐
此方法从数组中删除最后一个元素并返回它。

 const messages = ['Hello', 'Hey', 'How are you?', "I'm fine"] const lastMessage = messages.pop() console.log(messages) // ['Hello', 'Hey', 'How are you?'] console.log(lastMessage) // I'm fine 

可以替换的方法


在上一节中,您将找到修改原始数组的方法,这些方法很容易找到替代方法。 我并不是说它们需要打折,我只是想向您传达一些方法有副作用,可以替代。


该方法经常使用。 它允许您向该数组添加一个或多个元素,以及在前一个数组的基础上构建一个新数组。

 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) // [2, 3, 4, 5, 6] const todos = ['Write an article', 'Proofreading'] todos.push('Publish the article') console.log(todos) // ['Write an article', 'Proofreading', 'Publish the article'] 

如果您需要在另一个数组的基础上构建一个数组,例如itemsIncremented方法,那么我们已经熟悉了合适的mapfilterreduce 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'] // With splice months.splice(2, 1) // remove one element at index 2 console.log(months) // ['January', 'February', 'April', 'May'] // Without splice const monthsFiltered = months.filter((month, i) => i !== 3) console.log(monthsFiltered) // ['January', 'February', 'April', 'May'] 

您可能会问:如果我需要删除很多元素怎么办? 然后使用slice
 const months = ['January', 'February', 'March', 'April', ' May'] // With splice months.splice(1, 3) // remove thirds element starting at index 1 console.log(months) // ['January', 'February', 'April', 'May'] // Without splice const monthsFiltered = [...months.slice(0, 1), ...months.slice(4)] console.log(monthsFiltered) // ['January', 'February', 'April', 'May'] 

换档
shift方法删除数组的第一个元素并返回它。 要以函数式编程的方式执行此操作,可以使用spread或rest语句。

 const numbers = [1, 2, 3, 4, 5] // With shift const firstNumber = numbers.shift() console.log(firstNumber) // 1 console.log(numbers) // [2, 3, 4, 5] // Without shift const [firstNumber, ...numbersWithoutOne] = numbers console.log(firstNumber) // 1 console.log(numbersWithoutOne) // [2, 3, 4, 5] 

不变
unshift方法允许您将一个或多个元素添加到数组的开头。 像shift一样,您可以使用价差运算符执行此操作。

 const numbers = [3, 4, 5] // With unshift numbers.unshift(1, 2) console.log(numbers) // [1, 2, 3, 4, 5] // Without unshift const newNumbers = [1, 2, ...numbers] console.log(newNumbers) // [1, 2, 3, 4, 5] 

TL; DR


  • 当您要对数组执行某些操作时,请勿使用for循环 ,也不要重新发明轮子,因为上面很可能有一种方法可以满足您的需要。
  • 大多数情况下,您将使用mapfilterreduce方法和散布运算符-这些对于任何开发人员都是重要的工具。
  • 还有许多很高兴知道的数组方法: slicesomeflatMap等。了解它们并在必要时应用。
  • 副作用可能导致不必要的变化。 请记住,某些方法会修改您的原始数组。
  • slice方法和散布运算符制作浅表副本。 结果,对象和子数组将具有相同的链接-这也值得牢记。
  • 修改数组的旧方法可以替换为新方法。 您决定要做什么。

现在,您了解了有关JavaScript数组的所有知识。 如果您喜欢这篇文章 ,请单击“ Pat”按钮(最多50次,如果需要:-),然后共享它。 并随时在评论中分享您的印象!

Source: https://habr.com/ru/post/zh-CN446902/


All Articles