理解 JavaScript 扩展运算符 - 从初学者到专家
介绍
我们将要学习的内容
为什么要使用扩展运算符
扩展运算符如何工作?
克隆数组和对象
将类数组对象转换为数组
扩展运算符作为参数
添加新元素
合并数组/对象
结论
介绍
扩展运算符“…”最初是在 ES6 中引入的。它很快就成为了最受欢迎的功能之一。尽管它仅适用于数组,但仍有人提议将其功能扩展到对象。这项功能最终在 ES9 中引入。
本教程分为两部分,旨在向您展示为什么应该使用扩展运算符、它如何工作,以及深入了解它的用途,从最基础到最高级。
以下是本教程内容的简短摘要:
我们将要学习的内容
第 1 部分
- 为什么要使用扩展运算符
- 克隆数组/对象
- 将类数组结构转换为数组
- 扩展运算符作为参数
- 向数组/对象添加元素
- 合并数组/对象
第 2 部分
- 解构嵌套元素
- 添加条件属性
- 短路
- 其余参数(…)
- 默认解构值
- 默认属性
为什么要使用扩展运算符
读完上面的列表后,你可能会想:“但是 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