编程技术文章分享与教程

网站首页 > 技术文章 正文

大救星:延展操作符,是如何运作的?

hmc789 2024-11-23 16:25:35 技术文章 2 ℃

全文共5530字,预计学习时长11分钟



延展操作符首次于ES6中引入,并很快成为最受欢迎的功能之一。尽管事实上延展操作符只适用于数组,但仍有建议提出可以将其功能扩展到对象。最终ES9中引入了此功能。

本教程将说明为什么应该使用扩展运算符,以及它如何运作。



目录


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 }


从上述代码中可以看到,我们有一个变量Squirtle(杰尼龟)。因为刚刚访问了神奇宝贝中心,这只杰尼龟的HP值为100。

由于还想要另一只杰尼龟,因此声明变量为anotherSquirtle,将初始Squirtle指定为它的值。一场苦战后,另一只杰尼龟被击败了。因此,访问另一只杰尼龟的HP值并将其更改为0。下一步,检查初始Squirtle,输入console.log和...

等等,什么?初始Squirtle的HP降至0。这怎么可能?可怜的杰尼龟遭遇了什么?原来是发生了JavaScript变异。接下来将为你解释其中缘由。

当创建anotherSquirtle变量并将初始Squirtle指定为其值时,实际是给初始Squirtle对象的内存位置分配了一个引用。这是因为JavaScript数组和对象是引用数据类型。与基本数据类型不同,引用数据类型指向存储实际对象/数组的内存地址。

为了便于理解,可以将引用数据类型想象为全局变量的指针。更改引用数据类型的值实际上是在更改全局变量的值。

这意味着当将anotherSquirtle的HP值更改为0时,实际是将存储在内存中的Squirtle对象的HP值更改为0。这就是为什么mySquirtle的HP值为0 - 因为mySquirtle是对存储在内存中的对象的引用,可以通过anotherSquirtle变量被改变。谢谢JavaScript。

如何解决这个问题?

为了避免变量的变异,需要在要复制数组/对象时,创建数组/对象实例。如何实现这一操作?

使用延展操作符。


延展操作符如何运作


从MDN文档中可以查到:展开语法(spread syntax),可以在函数调用或数组构造时,将数组表达式或string等iterable在语法层面展开,还可以在构造字面量对象时,将对象表达式按键-值方式展开。

简而言之,延展操作符......延展iterable中的项(iterable指receiver中任何可循环的项,如字符串,数组,集等)。(receiver用于接收展开值。)为便于理解,以下是数组的简单示例:

const numbers = [1, 2, 3];
console.log(...numbers); //Result: 1 2 3
const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
console.log(...pokemon); //Squirtle Bulbasur Charmander
const pokedex = [
 { name: 'Squirtle', type: 'Water' },
 { name: 'Bulbasur', type: 'Plant' },
 { name: 'Charmander', type: 'Fire' }
];
console.log(...pokedex); //{ name: 'Squirtle', type: 'Water' } { name: 'Bulbasur', type: 'Plant' } { name: 'Charmander', type: 'Fire' }
import pandas as pd


数组中使用延展操作符的三个示例


如上所示,当在数组上使用延展操作符时,可以获取数组中所含的每个单独的项。在上述所有示例中,receiver都是一个函数,即console.log函数。够简单吧?


克隆数组/对象


现在已经知道了延展操作符的工作原理,可以利用它复制数组和对象而不改变其值。怎么做呢?延展内容然后使用数组[]或对象文字{}来生成数组/对象实例。

仍然以上文的杰尼龟为例,通过克隆mySquirtle变量解决上文中的问题:

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

使用延展操作符复制对象


通过使用解延展操作符解构mySquirtle变量内容并使用对象字面量,创建了Squirtle对象的新实例。这样,就防止变量突然变异。

使用相同的语法复制数组:

const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
const pokedex = [...pokemon];
pokedex.push('Cyndaquil');
console.log(pokemon); //[ 'Squirtle', 'Bulbasur', 'Charmander' ]
console.log(pokedex); //[ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]


使用延展操作符复制数组

注意:延展操作符只执行浅拷贝。这意味着若在数组/对象中存储了引用数据类型,则在使用延展操作符进行复制时,嵌套数组/对象将包含对原始的引用,因此其数值将是可变的。


将类数组对象转换为数组


类数组对象与数组非常相似。它们通常都有编号元素和长度属性。但是,两者有一个至关重要的区别:类数组对象没有任何数组函数。

类数组对象包含主要由DOM方法返回的HTML节点列表,和每个JS函数和少部分其他函数自动生成的参数变量。

使用与克隆数组相同的语法,可以使用延展操作符将类数组结构转换为数组,这可以代替使用Array.from的方法。以下是将nodeList转换为数组的示例:

const nodeList = document.getElementsByClassName("pokemon");
const array = [...nodeList];
 
console.log(nodeList); //Result: HTMLCollection [ div.pokemon, div.pokemon ]
console.log(array); //Result: Array [ div.pokemon, div.pokemon ]


将nodelist转换为数组

使用这种技术,可以将任何类数组结构转换为数组,从而访问所有数组函数。


延展操作符用作参数


某些函数接受可变数量的参数。其中一个典型列子就是Math集合中的函数。以Math.max()函数为例。它接受n个数字参数,并返回最大的参数。假设需要将一个数字数组传递给Math.max()函数。该怎么做呢?

可以这样做:

const numbers = [1, 4, 5];
const max = Math.max(numbers[0], numbers[1], numbers[2]);
console.log(max); //Result: 5


但是,这样做无疑是自寻死路。若是有20个值怎么办?1000个值呢?真的要通过索引访问每个值吗?当然不是。我们可以通过使用延展操作符提取数组中每个单独的值,如下所示:

const numbers = [1, 4, 5, 6, 9, 2, 3, 4, 5, 6];
const max = Math.max(...numbers);
console.log(max); //Result: 9


大救星:延展操作符。


添加新元素


将项添加到数组

向数组添加新元素,首先需要延展数组的内容并使用数字字面量[]创建数组实例,需要包含原始数组的内容以及要添加的值:

const pokemon = ['Squirtle', 'Bulbasur'];
const charmander = 'Charmander';
const cyndaquil = 'Cyndaquil';
const pokedex = [...pokemon, charmander, cyndaquil];
console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]


使用延展操作符将项添加到数组中


如你所见,可以任意添加新项。

向对象添加属性

通过使用与数组相同的语法,可以在克隆对象时轻松添加新属性。稍微转变一下,就有一个不同的语法来向对象添加属性(也可以用于数组):

const basicSquirtle = { name: 'Squirtle', type: 'Water' };
const fullSquirtle = {
 ...basicSquirtle,
 species: 'Tiny Turtle Pokemon',
 evolution: 'Wartortle'
};
console.log(fullSquirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }


如你所见,可以在对象字面量中而不是在外部直接声明和初始化新变量。


合并数组/对象


数组

如上述例子所示,可以通过延展数组并使用数组字面量来合并两个数组。但是,这一部分要讲的不是简单地添加新元素,而是添加另一个(延展)数组:

const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
const morePokemon = ['Totodile', 'Chikorita', 'Cyndaquil'];
const pokedex = [...pokemon, ...morePokemon];
console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Totodile', 'Chikorita', 'Cyndaquil' ]

使用延展操作符合并数组

这也适用于数组对象:

const pokemon = [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }];const morePokemon = [{ name: 'Charmander', type: 'Fire' }]; const pokedex = [...pokemon, ...morePokemon]; console.log(pokedex); //Result: [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]Merging two arrays of objects with the spread operator


对象

可以使用与之前相同的语法将两个(或更多)对象合并到一个对象中(你可能已经留意到,扩展运算符在数组和对象中的使用方式非常相似):

const baseSquirtle = {
 name: 'Squirtle',
 type: 'Water'
};
const squirtleDetails = {
 species: 'Tiny Turtle Pokemon',
 evolution: 'Wartortle'
};
const squirtle = { ...baseSquirtle, ...squirtleDetails };
console.log(squirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }


使用延展操作符合并对象

本教程说明了为什么应该使用扩展运算符(重点强调不变性!),它是如何工作的以及几个基本用法。


留言 点赞 关注

我们一起分享AI学习与发展的干货

如需转载,请后台留言,遵守转载规范

Tags:

标签列表
最新留言