发布于 2026-01-06 0 阅读
0

数组方法和可迭代对象 - 提升你的 JavaScript 技能 DEV 全球展示挑战赛,由 Mux 呈现:展示你的项目!

数组方法和可迭代对象 - 提升你的 JavaScript 水平

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

原文发布于michaelzanggl.com。订阅我的电子报,不错过任何新内容。

今天我想介绍一些数组方法,帮助你提升 JavaScript 水平。

对于所有示例,我们假设有以下变量声明

let users = [
  {id: 1, name: 'Michael', active: true, group: 1 }, 
  {id: 2, name: 'Lukas', active: false, group: 2 }
]
Enter fullscreen mode Exit fullscreen mode

本文将向您展示如何实现这一点。

const activeUsernames = []

users.forEach(user => {
  if (user.active) {
    activeUsernames.push(user.name)
  }
})
Enter fullscreen mode Exit fullscreen mode

进入这个

const activeUsernames = users
  .filter(user => user.active)
  .map(user => user.name)
Enter fullscreen mode Exit fullscreen mode

以及更多。

在代码改进方面,我们希望重点关注以下四个目标。

  • 避免使用临时变量
  • 避免条件
  • 能够分步骤思考你的代码。
  • 意图

我们将重点介绍 Array 原型中最重要的一些方法(省略基本的数组操作,如、 或pushpop希望您能找到一些场景,在这些场景中,您可以应用这些方法而不是以下常用的方法。spliceconcat

for 循环

for (let i = 0; i < users.length; i++) {
    //
}
Enter fullscreen mode Exit fullscreen mode

Array.prototype.forEach

users.forEach(function(user) {
    //
}
Enter fullscreen mode Exit fullscreen mode

ES6 for 循环

for (const user of users) {
    //
}
Enter fullscreen mode Exit fullscreen mode

开始之前还有一件事!

如果您不熟悉 ES6 箭头函数,例如:

users.map(user => user.name)
Enter fullscreen mode Exit fullscreen mode

我建议您先看一下这些。
总而言之,以上内容非常相似,而且在这种情况下,与……相同。

users.map(function(user) {
   return user.name
})
Enter fullscreen mode Exit fullscreen mode

数组.原型.过滤器

假设我们想要找到所有活跃用户。我们在文章引言部分已经简要讨论过这个问题。

const activeUsers = []

users.forEach(user => {
  if (user.active) {
    activeUsers.push(user)
  }
})
Enter fullscreen mode Exit fullscreen mode

回顾我们之前设定的四个目标,很明显,这至少违反了其中两个。
它既包含临时变量,也包含条件语句

我们来看看如何才能让这件事变得更容易。

const activeUsers = users.filter(user => user.active)
Enter fullscreen mode Exit fullscreen mode

Array.prototype.filter工作原理是:它接受一个函数作为参数(使其成为高阶函数),并返回所有通过测试的用户。在本例中,返回所有活跃用户。

我认为可以肯定地说,我们也已经表明了我们的意图。forEach它可以表示任何意思,例如保存到数据库等等,而它的作用正如filter其名称所示。

当然,你也可以将其应用于filter简单的数组。
以下示例将返回所有以字母 a 开头的动物。

['ape', 'ant', 'giraffe'].filter(animal => animal.startsWith('a'))
Enter fullscreen mode Exit fullscreen mode

我经常看到的另一个用例是从数组中删除元素。假设我们要删除 id 为 1 的用户。我们可以这样做:

users = users.filter(user => user.id !== 1)
Enter fullscreen mode Exit fullscreen mode

过滤器还有以下用途:

const result = [true, 1, 0, false, '', 'hi'].filter(Boolean) 
result //? [true, 1, 'hi']
Enter fullscreen mode Exit fullscreen mode

这实际上会从数组中移除所有假值。这里没有什么神奇之处。`is`Boolean是一个接受参数并测试其真假的函数。例如,`is`Boolean('')返回 false,而 ` Boolean('hi')is` 返回 true。我们只需将该函数传递给filter方法,它就充当了我们的测试。

数组.原型.映射

我们经常会遇到这样的情况:有一个数组,我们需要对其中的每个元素进行转换。与其遍历数组,不如直接使用 Map 函数。Map
函数会返回一个长度相同的数组,至于每次迭代返回什么值,则取决于你。

让我们创建一个数组来保存所有用户的用户名。

传统循环

const usernames = []

users.forEach(user => {
  usernames.push(user.name)
})
Enter fullscreen mode Exit fullscreen mode

绘制地图

const usernames = users.map(user => user.name)
Enter fullscreen mode Exit fullscreen mode

我们避免使用临时变量同时揭示意图。

连锁

这些高阶函数的妙处在于它们可以链式调用。map例如,`map` 函数可以遍历数组并返回一个新数组,filter`filter` 函数可以过滤数组并返回一个新数组。看出其中的规律了吗?有了这种特性,编写类似下面的代码不仅可行,而且非常易读。

const activeUsernames = users
  .filter(user => user.active)
  .map(user => user.name)
Enter fullscreen mode Exit fullscreen mode

至此,我们的最终目标就完成了to think in steps。与其在脑海中完整地思考整个逻辑,不如一步一步地来。想想我们一开始举的例子。

const activeUsernames = []

users.forEach(user => {
  if (user.active) {
    activeUsernames.push(user.name)
  }
})
Enter fullscreen mode Exit fullscreen mode

当你第一次读到这段话时,你脑海中的理解过程大概会是这样的:

  • 初始化一个空数组
  • 遍历所有用户
    • 如果用户处于活动状态
      • 从数组开头向数组中添加新元素。
        • 但只有用户名
  • 重复

重构后的方法看起来更像这样

  • 获取所有活跃用户
  • 创建一个大小相同的新数组
    • 仅包含他们的用户名

这样更容易思考和推理。


还有许多其他有趣的方法。让我们再来看看。

Array.prototype.find

同样地,filter返回一个包含所有通过测试的项的数组,find返回第一个通过测试的项。

// returns user with id 1
users.find(user => user.id === 1)
Enter fullscreen mode Exit fullscreen mode

Array.prototype.findIndex工作原理相同,但它返回的是索引而不是项目本身。

对于不需要深度检查的数组,无需额外函数带来的开销,只需分别使用 ` get`includes和 ` indexOfget` 即可。

['a', 'b', 'c'].includes('b') //? true
['a', 'b', 'c'].indexOf('a') //? 0
['a', 'b', 'c'].includes('d') //? false
['a', 'b', 'c'].indexOf('d') //? -1
Enter fullscreen mode Exit fullscreen mode

Array.prototype.some

如果至少有一个测试通过,则返回 true。我们可以用它来检查数组中是否至少有一个用户处于活跃状态。

传统解决方案使用 for 循环

let activeUserExists = false
for (let i = 0; i < users.length; i++) {
  if (users[i].active) {
    activeUserExists = true
    break
  }
}
Enter fullscreen mode Exit fullscreen mode

使用的解决方案some

users.some(user => user.active)
Enter fullscreen mode Exit fullscreen mode

Array.prototype.every

如果所有项目都通过测试,则返回 true。我们可以使用此方法检查所有用户是否都处于活跃状态。

传统解决方案使用 for 循环

let allUsersAreActive = true
for (let i = 0; i < users.length; i++) {
  if (!users[i].active) {
    allUsersAreActive = false
    break
  }
}
Enter fullscreen mode Exit fullscreen mode

使用的解决方案every

users.every(user => user.active)
Enter fullscreen mode Exit fullscreen mode

Array.prototype.reduce

如果以上函数都无法帮到你,那就试试 reduce 函数吧!它本质上就是将数组简化成你想要的任何形式。我们来看一个非常简单的数字示例。我们想要计算数组中所有数字的总和。在传统的 forEach 循环中,代码会是这样的:

const numbers = [5, 4, 1]
let sum = 0
numbers.forEach(number => sum += number)
sum //? 10
Enter fullscreen mode Exit fullscreen mode

但是 reduce 函数可以帮我们省去一些样板代码。

const numbers = [5, 2, 1, 2]
numbers.reduce((result, number) => result + number, 0) //? 10
Enter fullscreen mode Exit fullscreen mode

reduce该函数接受两个参数:一个函数和一个起始值。在本例中,起始值为零。如果我们传递 2 而不是 0,最终结果将为 12。

因此,在下面的例子中

const numbers = [1, 2, 3]
numbers.reduce((result, number) => {
    console.log(result, number)
    return result + number
}, 0)
Enter fullscreen mode Exit fullscreen mode

日志将显示:

  • 0,1
  • 1、2
  • 3,3

最终结果是最后两个数字 3 和 3 之和,即 6。

当然,我们也可以将对象数组简化为,比如说哈希表。

按键分组后group,生成的 hashMap 应该如下所示

const users = {
  1: [
    { id: 1, name: 'Michael' },
  ],
  2: [
    { id: 2, name: 'Lukas' },
  ],
}
Enter fullscreen mode Exit fullscreen mode

我们可以通过以下代码实现这一点。

users.reduce((result, user) => {
  const { group, ...userData } = user
  result[group] = result[group] || []
  result[group].push(userData)

  return result
}, {})
Enter fullscreen mode Exit fullscreen mode
  • const { group, ...userData } = usergroup从用户处获取密钥,并将剩余的值放入其中userData
  • result[group] = result[group] || []如果组尚不存在,我们会对其进行初始化
  • 我们进入userData了新群体
  • 我们返回下一次迭代的新结果

将这些知识应用于其他可迭代对象和类数组对象

你还记得以前发生过这件事吗?

for 循环:适用于类似数组的对象

for (let i = 0; i < users.length; i++) {
    //
}
Enter fullscreen mode Exit fullscreen mode

数组原型上的 Array.prototype.forEach 方法

users.forEach(function(user) {
    //
}
Enter fullscreen mode Exit fullscreen mode

ES6 for 循环:适用于可迭代对象

for (const user of users) {
    //
}
Enter fullscreen mode Exit fullscreen mode

forEach你有没有意识到这两个循环的语法有多么不同for

为什么?因为这两个for循环并非只能处理数组。事实上,它们根本不知道数组是什么。

我相信你在计算机科学课上肯定学过这种类型的代码。

const someString = 'Hello World';
for (let i=0; i < someString.length; i++) {
    console.log(someString[i]);
}
Enter fullscreen mode Exit fullscreen mode

即使字符串不是数组,我们也可以对其进行遍历。

这种for循环适用于任何“类似数组的对象”,即具有 length 属性和索引元素的对象。

循环for of可以这样使用。

const someString = 'Hello World';
for (const char of someString) {
    console.log(char);
}
Enter fullscreen mode Exit fullscreen mode

for of循环适用于任何可迭代对象。

要检查某个对象是否可迭代,可以使用这行相当简洁的代码Symbol.iterator in Object('pretty much any iterable')

操作 DOM 时也是如此。如果您现在打开开发者工具并在控制台中执行以下表达式,就会看到一个醒目的红色错误信息。

document.querySelectorAll('div').filter(el => el.classList.contains('text-center'))
Enter fullscreen mode Exit fullscreen mode

遗憾的是,filter可迭代的 DOM 集合中不存在这种方法,因为它们不是数组,因此不共享数组原型中的方法。需要证据吗?

(document.querySelectorAll('div') instanceof Array) //? false
Enter fullscreen mode Exit fullscreen mode

但它是一个类似数组的对象。

> document.querySelectorAll('.contentinfo')

    NodeList [div#license.contentinfo]
        0: div#license.contentinfo
        length: 1
        __proto__: NodeList
Enter fullscreen mode Exit fullscreen mode

并且也是可迭代的

Symbol.iterator in Object(document.querySelectorAll('div')) //? true
Enter fullscreen mode Exit fullscreen mode

如果我们想将新学的数组知识应用于可迭代的 DOM 集合,我们首先必须将它们转换为合适的数组。

有两种方法可以做到。

const array = Array.from(document.querySelectorAll('div'))
Enter fullscreen mode Exit fullscreen mode

或者

const array = [...document.querySelectorAll('div')]
Enter fullscreen mode Exit fullscreen mode

我个人更喜欢第一种方法,因为它更易于阅读。

结论

我们学习了数组对象最重要的几个方法,并了解了可迭代对象。回顾我们最初设定的目标,我认为可以肯定地说,我们至少已经实现了这些目标。

  • 分步骤思考
  • 避免使用临时变量
  • 避免条件

但我并不完全满意reveal intent

尽管

const usernames = users.map(user => user.name)
Enter fullscreen mode Exit fullscreen mode

肯定比……更易读。

const usernames = []

users.forEach(user => {
  usernames.push(user.name)
})
Enter fullscreen mode Exit fullscreen mode

不会

const usernames = users.pluck('name')
Enter fullscreen mode Exit fullscreen mode

可以更友善一些吗?

下一篇文章我们将探讨数组的子类化,以便实现类似的功能。这也将是使用 Node.js 进行单元测试的绝佳切入点,敬请期待。

PS:如果您是 Laravel 的粉丝,请看看Laravel Collections


如果这篇文章对您有帮助,我这里还有很多关于简化写作软件的技巧

文章来源:https://dev.to/michi/array-methods-and-iterables---stepping-up-your-javascript-game-15bo