理解 JavaScript 扩展运算符 - 从初学者到专家 简介 我们将学习什么 为什么要使用扩展运算符 扩展运算符如何工作? 克隆数组和对象 将类数组对象转换为数组 扩展运算符作为参数 添加新元素 合并数组/对象 结论

2025-06-07

理解 JavaScript 扩展运算符 - 从初学者到专家

介绍

我们将要学习的内容

为什么要使用扩展运算符

扩展运算符如何工作?

克隆数组和对象

将类数组对象转换为数组

扩展运算符作为参数

添加新元素 

合并数组/对象

结论

介绍

扩展运算符“…”最初是在 ES6 中引入的。它很快就成为了最受欢迎的功能之一。尽管它仅适用于数组,但仍有人提议将其功能扩展到对象。这项功能最终在 ES9 中引入。

本教程分为两部分,旨在向您展示为什么应该使用扩展运算符、它如何工作,以及深入了解它的用途,从最基础到最高级。 

以下是本教程内容的简短摘要:

我们将要学习的内容

第 1 部分

  1. 为什么要使用扩展运算符
  2. 克隆数组/对象
  3. 将类数组结构转换为数组
  4. 扩展运算符作为参数
  5. 向数组/对象添加元素
  6. 合并数组/对象

第 2 部分

  1. 解构嵌套元素
  2. 添加条件属性
  3. 短路
  4. 其余参数(…)
  5. 默认解构值
  6. 默认属性

为什么要使用扩展运算符

读完上面的列表后,你可能会想:“但是 JavaScript 有函数可以完成所有这些事情……我为什么要使用扩展运算符?”请允许我向你介绍一下不变性

摘自牛津词典:不变性——随着时间的推移不会改变或无法改变。

在软件开发中,我们使用“不可变”一词来指状态不会随时间改变的值。事实上,我们通常使用的大多数值(原始值,例如字符串、整数等)都是不可变的。 

然而,JavaScript 在处理数组和对象时,有一个特殊的行为;它们实际上是可变的。这可能会成为一个问题。以下是一个例子,说明了原因:

const mySquirtle = {
name: 'Squirtle',
type: 'Water',
hp: 100
};
const anotherSquirtle = mySquirtle;
anotherSquirtle.hp = 0;
console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 }

正如你在前面的代码片段中看到的,我们有一只杰尼龟。由于我们刚刚访问了宝可梦中心,所以杰尼龟的生命值为 100。 

由于我们想要另一只杰尼龟,所以我们声明了变量anotherSquirtle,并将原来的杰尼龟赋值给它的值。经过一场艰苦的战斗,anotherSquirtle被击败了。因此,我们获取了anotherSquirtle的生命值,并将其更改为 0。下一步是检查原来的杰尼龟。我们运行 console.log 并……

等等,什么?我们原本的杰尼龟生命值降到了 0!怎么会这样?我们可怜的杰尼龟怎么了?JavaScript 突变发生了。让我来解释一下发生了什么。 

当我们创建anotherSquirtle 变量,并将原始 Squirtle 赋值给它的值时,我们实际上做的是将一个引用赋值给原始 Squirtle 对象的内存位置。这是因为 JavaScript 数组和对象是引用数据类型。与原始数据类型不同,它们指向实际存储对象/数组的内存地址

为了更容易理解,你可以将引用数据类型想象成指向全局变量的指针。通过改变引用数据类型的值,我们实际上是在改变全局变量的值。 

这意味着,当我们将anotherSquirtle的 hp 值改为 0 时,实际上是将内存中 Squirtle 对象的 hp 值也改为 0。这就是为什么mySquirtle的 hp 值为 0 的原因,因为mySquirtle持有对内存中 Squirtle 对象的引用,而我们通过anotherSquirtle 变量修改了该对象的引用。感谢 JavaScript。

我们该如何解决这个问题?

为了避免变量的意外变异,我们需要做的就是,每当需要复制数组/对象时,创建一个新的数组/对象实例
 
。该怎么做呢? 使用扩展运算符!:)

扩展运算符如何工作?

来自 MDN 文档:扩展语法允许在需要零个或多个参数(对于函数调用)或元素(对于数组文字)的位置扩展可迭代对象(例如数组表达式或字符串),或者在需要零个或多个键值对(对于对象文字)的位置扩展对象表达式

简而言之,扩展运算符“...”将可迭代对象(可迭代对象是可以循环的任何东西,例如字符串,数组,集合……)中包含的项目扩展至接收器(接收器是接收扩展值的东西)。以下是几个使用数组的简单示例,可帮助您更好地理解它:

如你所见,当我们对数组使用展开运算符时,我们会获取数组中包含的每个单独的项。在前面的所有例子中,接收者都是一个函数,即console.log函数。很简单,对吧?

克隆数组和对象

现在我们已经了解了展开运算符的工作原理,我们可以利用它来不可变地复制数组和对象。怎么做呢?通过展开内容,然后使用数组或对象字面量(分别为[]{})来生成数组/对象的新实例。让我们以之前的 Squirtle 示例为例,并通过不可变地克隆 *mySquirtle * 变量来修复它:

通过使用扩展运算符解构mySquirtle变量的内容,并使用对象字面量,我们创建了 Squirtle 对象的新实例。这样,我们就可以防止意外的变量突变。 

要复制数组,我们使用完全相同的语法:

注意:请记住,扩展运算符仅执行拷贝。这意味着,如果你的数组/对象中存储了引用数据类型,当你使用扩展运算符进行复制时,嵌套的数组/对象将包含对原始对象的引用,因此将是可变的

将类数组对象转换为数组

类数组对象与数组非常相似。它们通常具有编号元素和长度属性。然而,它们有一个关键的区别:类数组对象不具备任何数组函数

类数组对象包括大多数 DOM 方法返回的 HTML 节点列表、每个 JS 函数中自动生成的参数变量以及一些其他变量。

与克隆数组的语法相同,我们可以使用扩展运算符将类数组结构转换为数组,这可以替代使用 Array.from。以下是将NodeList转换为数组的示例: 

利用这种技术,我们可以将任何类似数组的结构转换为数组,从而可以访问所有数组函数

扩展运算符作为参数

有些函数接受可变数量的参数。这类函数的一个很好的例子是 Math 集合中的函数。在我们的示例中,我们选择 Math.max() 函数,它接受n 个数字参数,并返回其中最大的一个。假设我们有一个数字数组,想要将其传递给 Math.max() 函数。该怎么做呢? 

我们可以做这样的事情(不要因为下面的代码而恨我):

但是,这样做当然无异于自杀。如果我们有 20 个值怎么办?或者 1000 个呢?我们真的要通过索引访问每个值吗?答案是否定的。我们已经知道,扩展运算符接受一个数组并提取每个单独的值。这正是我们想要的!因此,我们可以这样做:

扩展运算符来救援!

添加新元素 

向数组添加项目

要向数组添加新元素,我们首先展开数组的内容,然后使用数组文字 [] 创建数组的新实例,其中包含原始数组的内容以及我们要添加的值:

如您所见,我们可以添加任意数量的新项目。 

向对象添加属性

使用与数组相同的语法,我们可以在克隆对象时轻松添加新属性。稍微改变一下,下面是向对象添加属性的另一种语法(它也可以用于数组):

如您所见,我们可以在对象文字内部直接声明和初始化新变量,而不必在外部进行。 

合并数组/对象

数组

我们可以像前面的例子一样,通过展开数组并使用数组字面量来合并两个数组。不过,我们不是简单地添加一个新元素,而是添加另一个(展开)数组:

如果我们有一个对象数组,它也可以起作用:

对象

我们可以使用与以前相同的语法将两个(或多个)对象合并为一个对象(您现在可能已经注意到,对于数组和对象,扩展运算符的使用方式非常相似):

结论

在本教程的第一部分中,我们学习了为什么要使用扩展运算符(因为它具有不变性! )、它的工作原理以及该运算符的几种基本用法。在本教程的第二部分中,我们将通过一些高级技巧和用例来加深对该运算符的理解。这是第二部分的链接

非常感谢您的阅读:)如果您有任何疑问或意见,请随时与我联系,这是我的Twitter 页面的链接。

文章来源:https://dev.to/nyagarcia/understanding-the-javascript-spread-operator-from-beginner-to-expert-5bdb
PREV
🎉像专业人士一样监控你的 Javascript 应用程序🧙‍♂️💫
NEXT
使用 Github Actions 将 React App 部署到 Amazon S3