... 运算符,是
ES6
里一个新引入的运算法,也叫
展开/收集
运算符,我们每天都要和它打交道。
这篇文章,我就带你系统的回顾下这个运算符,介绍一些
基础
和
进阶
的用法。
基础篇
先看一下官方描述:
Spread syntax allows an
iterable
, such as an
array expression
or
string
, to be expanded in places where 0 or more
arguments
or
elements
are expected or an
object expression
to be expanded in places where 0 or more key-value pairs (for object literals) are expected.
简而言之就是,
...
运算符可以展开一个
可迭代
对象重的所有项。
可迭代的对象一般是指可以被循环的,包括:
string
,
array
,
set
等等。
下面我们来看几个基础的例子来加深理解。
基础用法
基础用法 1: 展开
const a = [2, 3, 4] const b = [1, ...a, 5] b; // [1, 2, 3, 4, 5]
基础用法 2: 收集
function foo(a, b, ...c) { console.log(a, b, c) }foo(1, 2, 3, 4, 5); // 1, 2, [3, 4, 5]
如果没有命名参数的话,
...
就会收集所有的参数:
function foo(...args) { console.log(args) }foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
关于这个
收集
的用法,官方描述:
“A function’s
last parameter
can be prefixed with ... which will cause all remaining (user supplied) arguments to be placed within a "standard" javascript array.
Only the last parameter can be a rest parameter
.”
这个运算符一定是在最后一个参数的位置,也很好理解,就是“收集前面剩下的参数”。
Remember that the rest parameter
must be the last parameter
, or an
error
will occur.
如果不在最后一位,会报错。
不得不感叹,这个运算符设计的真的是妙,
可展开
,
可收集
,
收放自如
,当真好用。
基础用法 3: 把 类数组 转换为 数组
先回顾下什么是
类数组
吧.
类数组和数组非常接近,都可以拥有一系列元素,也有
length
属性,最大的不同是:
类数组不具备数组的一系列方法。
举个例子:
const nodeList = document.getElementsByClassName("test");const array = [...nodeList];console.log(nodeList); //Result: HTMLCollection [ div.test, div.test ]console.log(array); //Result: Array [ div.test, div.test ]
使用
...
就可以实现类数组到数组的转换,转换之后,就可以使用数组的各种方法了。
你还记得在这个操作符出来之前是如何转换的吗?
这个问题还是头条的一个前端面试题。
看例子:
// ES5 时代function bar() { var args = Array.prototype.slice.call(arguments); // 调用push 加几个元素 args.push(1, 2, 3); // 把args 作为参数传递给foo foo.apply(null, args)}// ES6 时代function foo(...args) { // 搜集参数到 args args.push(4, 5, 6) console.log(...args) // 展开args}bar(0); // 0 1 2 3 4 5 6
基础用法 4: 增加元素或属性
1: 为数组新增成员
const pokemon = ['KK', 'Peter'];const charmander = '郑伊健';const pokedex = [...pokemon, charmander];console.log(pokedex); //Result: [ 'KK', 'Peter', '郑伊健' ]
2: 为对象新增属性
const basicSquirtle = { name: 'Squirtle', type: 'Water' };const fullSquirtle = { ...basicSquirtle, species: 'Tiny Turtle', evolution: 'Wartortle'};console.log(fullSquirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle', evolution: 'Wartortle' }
基础用法 5:
合并数组/对象
合并数组:
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' } ]
合并对象
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' }
以上是一些基础费用法
下面介绍一些... 操作符的进阶用法。
进阶篇
1. 复制具有嵌套结构的数据/对象
先看一个例子:
const pokemon = { name: 'Squirtle', type: 'Water', abilities: ['Torrent', 'Rain Dish']};const squirtleClone = { ...pokemon };pokemon.name = 'Charmander';pokemon.abilities.push('Surf');console.log(squirtleClone); //Result: { name: 'Squirtle', type: 'Water', abilities: [ 'Torrent', 'Rain Dish', 'Surf' ] }
当我们修改
原对象
的
name
属性时,我们的
克隆对象
的
name
属性没有受影响,这是符合我们预期的。
但是当修改
原对象
的
abilities
属性时,我们的克隆对象也被修改了。
原因也很简单,因为复制过来的
abilities
是一个引用类型,原数据改了,用到他的地方也会跟着改。
知道原因,再解决就很简单了,两种方式:
1、复制引用类型的数据
const pokemon = { name: 'Squirtle', type: 'Water', abilities: ['Torrent', 'Rain Dish']};const squirtleClone = { ...pokemon, abilities: [...pokemon.abilities] };pokemon.name = 'Charmander';pokemon.abilities.push('Surf');console.log(squirtleClone);//Result: { name: 'Squirtle', type: 'Water', abilities: [ 'Torrent', 'Rain Dish' ] }
这样就 OK 了
2、深克隆
在这里就不多解释了。
2. 增加条件属性
顾名思义,就是需要根据条件添加的属性。
看个例子:
const pokemon = { name: 'Squirtle', type: 'Water'};const abilities = ['Torrent', 'Rain dish'];const fullPokemon = abilities ? { ...pokemon, abilities } : pokemon;console.log(fullPokemon);
3. 短路
const pokemon = { name: 'Squirtle', type: 'Water'};const abilities = ['Torrent', 'Rain dish'];const fullPokemon = { ...pokemon, ...(abilities && { abilities })};console.log(fullPokemon);
如果
abilities
为
true
,就相当于是
const fullPokemon = { ...pokemon, ...{ abilities }}
这也是一个很有用的技巧。
4. 默认结构和添加默认属性
默认结构:
我们知道,当结构一个对象的时候,如果这个对象里没有某个属性,解出来是
undefined
, 我们可以添加默认值来解决:
const pokemon = { id: 1, name: 'Squirtle'};const { type, name } = pokemon;console.log(name); //Result: Squirtleconsole.log(type); //Result: undefined//Assigning default value to the type variableconst { type = 'Water', name } = pokemon;console.log(type); //Result: Water
添加默认属性
有时候从我们会遇到这样的情况,一个对象,大部分属性是相似的,只有小部分是不不同的,这时候我们就可以设置一个基础对象,具备基础属性,其他的对象可以通过扩展这个对象来得到。
看例子:
const pokemon = { name: 'Squirtle', type: 'Water'};// 给abilities默认赋值const { abilities = [], ...rest } = pokemon;const fullSquirtle = { ...rest, abilities };console.log(rest); //Result: { name: 'Squirtle', type: 'Water' }console.log({ fullSquirtle }); //Result: { name: 'Squirtle', type: 'Water', abilities: [] }
这里就是通过展开
rest
, 合并
abilities
得到完全体的数据。
如果有批量的数据需要处理,这种方法也非常方便:
const pokemon = [ { name: 'Charmander', type: 'Fire' }, { name: 'Squirtle', type: 'Water', abilities: ['Torrent', 'Rain Dish'] }, { name: 'Bulbasur', type: 'Plant' }];function setDefaultAbilities(object) { const { abilities = [], ...rest } = object; return { ...rest, abilities };}// Applying the setDefaultAbilities function to all the pokemon in the array:const normalizedPokemon = pokemon.map(pokemon => setDefaultAbilities(pokemon));console.log(normalizedPokemon);//Result: [ { name: 'Charmander', type: 'Fire', abilities: [] }, { name: 'Squirtle', type: 'Water', abilities: [ 'Torrent', 'Rain Dish' ] }, { name: 'Bulbasur', type: 'Plant', abilities: [] } ]
这样迭代一遍,所有的对象就都具备
abilities
属性了。
总结
...
运算符非常灵活,收放自如,非常强大,希望我们都能很好的掌握这个工具。
内容就这么多,希望对大家有所帮助,如有纰漏,欢迎指正。