如何在JavaScript中使用扩展运算符(...)?

177 阅读6分钟

如何在JavaScript中使用扩展运算符(...)?

ES6在JavaScript中引入了许多新的功能,其中有传播操作符(...) ,它可以将一个可迭代的对象扩展成一个单独元素的列表。

如果你还不清楚,不要担心,在接下来的章节中,当我们真正学会用真实的场景来使用它时,我们会详细介绍。

复制数组或对象

看看下面这个脚本,你能说出输出是什么吗?

const listA = [1, 2, 3]
const listB = listA
listB.push(4)
console.log('listA:', listA)
console.log('listB:', listB)

这个例子的输出是这样的。

"listA:" [1, 2, 3, 4]
"listB:" [1, 2, 3, 4]

等等!什么?为什么listA 改变了它的值,而我们明明只改变了listB 。嗯,原因很简单,当我们做赋值的时候。

const listB = listA

Javascript给listB 分配了一个对listA 的引用,所以本质上listAlistB 指向内存中的同一个列表。

那么,我怎样才能创建一个副本呢?这就是传播操作者进入画面的地方。让我们再看一下使用传播操作符的同一个例子。

const listC = [1, 2, 3]
const listD = [...listC]
listD.push(4)
console.log('listC:', listC)
console.log('listD:', listD)

以及输出结果。

"listC:" [1, 2, 3]
"listD:" [1, 2, 3, 4]

在这种情况下,通过使用传播操作符,我们在内存中创建了一个新的数组副本,所以当我们更新listD ,我们不会以任何方式影响listC

同样的,我们可以用这个技术来复制对象,然而,有一个问题。

const article = {
  title: 'How to Use the Spread Operator (...) in JavaScript',
  claps: 1000000,
  author: {
    name: 'Juan',
    publication: 'LiveCodeStream'
  }
}

const articleCopy = { ...article }


articleCopy.title = 'Strange behaviours with spread operator and deep copy';
articleCopy.author.name = 'JC';

console.log('Original title:', article.title);
console.log('Original authors:', article.author.name)

console.log('Copy title:', articleCopy.title)
console.log('Copy authors:', articleCopy.author.name)

在我们解释这里发生了什么之前,让我们看看输出。

Original title:	How to Use the Spread Operator (...) in JavaScript	
Original author:	JC	
Copy title:	Strange behaviours with spread operator and deep copy	
Copy author:	JC

同样,什么?现在我们已经使用了传播操作符,我们在内存中得到了原始对象的副本,然而,有些属性是以值复制的,有些是以引用的形式复制的,如作者的情况(注意标题只在副本中改变了,但author ,原始和副本都被改变了)。

这里发生的事情是,传播操作者不会做深度复制,但它会把原始对象/列表中的每个元素,映射到内存中的一个新位置。然而,如果其中一个元素碰巧是对另一个对象的引用,它将简单地将引用复制到内存中,但它不会改变它所引用的内容。

合并数组或对象

传播操作符在复制对象时非常有用,但我们也可以用它来将多个对象或列表合并成一个对象。

让我们看一个合并列表和一个合并对象的例子。

const list1 = [1, 2, 3]
const list2 = [4, 5]

const mergedList = [...list1, ...list2]

console.log('Merged List: ', mergedList)
const obj1 = {a: 1, b: 2}
const obj2 = {c: 3}

const mergedObj = {...obj1, ...obj2}

console.log('Merged Object: ', mergedObj)

按照上面的考虑,这和预期的工作一样。下面是输出结果。

Merged List: 	[1,2,3,4,5]	
Merged Object: 	{"a":1,"b":2,"c":3}

然而,事情会因为JavaScript而变得有点奇怪。

const weird1 = {...obj1, ...list2}
console.log('Merged list as object', weird1)

在这种情况下,我们把我们的obj1list2 合并成一个对象,你知道结果是什么吗?

Merged list as object	{"0":4,"1":5,"a":1,"b":2}

它出乎意料地成功了!看起来有点奇怪,但你可以很容易地预测到结果会是什么。

反过来也会成功吗?将一个对象合并成一个列表?

const weird2 = [...obj1, ...list1]
console.log('Merged list as object', weird2)

有什么猜测吗?

object is not iterable (cannot read property Symbol(Symbol.iterator))

也许和你想的不一样,但不可能合并一个对象,或者说不可能把一个对象复制到一个列表中。原因是你不能对一个对象进行迭代。如果你把对象实现为一个可迭代的对象,那么就有可能做到这一点。

传递参数

你有没有试过在一个数组上找到最大(或最小)的值?你对这个问题的第一个回答可能是使用Math.max 函数,然而,这是不可能的,我们需要做别的事情。为什么呢?

Math.max 因为其他类似的函数,顾名思义就是希望有多个参数。如果我们试图把一个数组作为一个值来传递,那么这个值就不会像预期的那样是一个数字,而函数将返回 。为了解决这个问题,我们可以使用扩散运算符,将数组转换为参数列表,如下所示。NaN

console.log('Math.max(1, 2, 3)', Math.max(1, 2, 3))
console.log('Math.max([1, 2, 3])', Math.max([1, 2, 3]))
console.log('Math.max(...[1, 2, 3])', Math.max(...[1, 2, 3]))

以及输出。

Math.max(1, 2, 3)	3	
Math.max([1, 2, 3])	null	
Math.max(...[1, 2, 3])	3	

很好!但我怎样才能自己创建一个这样的函数呢?让我们看一个例子。

function test(param1, ...args) {
  console.log('  -> param1', param1)
  console.log('  -> args', args)
}

console.log('test(1):')
test(1)

console.log('test(1, "a", "b", "c"):')
test(1, 'a', 'b', 'c')

输出结果。

test(1):	
  -> param1	1	
  -> args	[]	
test(1, "a", "b", "c"):	
  -> param1	1	
  -> args	["a","b","c"]

使用spread作为函数声明的一部分是将参数转换为数组的一个好方法。接下来有更多关于这个话题的内容。

销毁数组或对象

我们看到了如何复制和合并对象,但是....,我们可以 "取消合并 "对象吗?这叫做析构......是的!让我们看一看。

console.log('first', first)
console.log('second', second)
console.log('rest', rest)

输出。

first	1	
second	2	
rest	[3,4,5]	

同样地,我们也可以对对象做同样的处理。

const article = {
  title: 'Cool article',
  claps: 10000000,
  author: {
    name: 'Juan'
  }
}

const { title, claps, author: { name }} = article

console.log('title', title)
console.log('claps', claps)
console.log('authors name', name)

输出。

title	Cool article	
claps	10000000	
author name	Juan

对于对象会发生一些有趣的事情,我们甚至可以检索到嵌套的属性。这种技术在React组件和使用Redux或类似的东西时被广泛使用。

现在我们了解了这个功能,你可能对前面的函数声明的例子中到底发生了什么有了更好的认识。

节点列表到数组

就像它听起来那样简单,我们可以做一些事情。

[...document.querySelectorAll('div')]

将字符串转换为字符

字符串是一个可迭代的东西,我们可以用它们来扩展为一个字符列表,如下所示。

const name = 'Juan'
const chars = [...name];

删除重复的内容

我们可以使用展开运算符来只获得唯一的值吗?是的,但不是直接的......我们需要利用其他的东西与展开运算符结合起来,Sets

const list = [1, 3, 1, 3, 3, 2]
const uniqueList = [...new Set(list)]

uniqueList ,现在的价值是。

[1, 3, 2]

结论

传播操作符(...)对于在Javascript中处理数组和对象非常有用。在使用React等框架以及开发还原器时,你会经常看到它们。如果你用Javascript工作,这绝对是一个需要学习和掌握的重要功能。