如何在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 的引用,所以本质上listA 和listB 指向内存中的同一个列表。
那么,我怎样才能创建一个副本呢?这就是传播操作者进入画面的地方。让我们再看一下使用传播操作符的同一个例子。
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)
在这种情况下,我们把我们的obj1 和list2 合并成一个对象,你知道结果是什么吗?
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工作,这绝对是一个需要学习和掌握的重要功能。