... 运算符, 是 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 [ p.test, p.test ]
- console.log(array); //Result: Array [ p.test, p.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 是一个引用类型, 原数据改了, 用到他的地方也会跟着改.
知道原因, 再解决就很简单了, 两种方式:
复制引用类型的数据
- 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: 增加条件属性
顾名思义, 就是需要根据条件添加的属性.
看个例子:
- 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 }
- }
这也是一个很有用的技巧.
3: 默认结构和添加默认属性
默认解构:
我们知道, 当结构一个对象的时候, 如果这个对象里没有某个属性, 解出来是 undefined, 我们可以添加默认值来解决:
- const pokemon = {
- id: 1,
- name: 'Squirtle'
- };
- const { type, name } = pokemon;
- console.log(name); //Result: Squirtle
- console.log(type); //Result: undefined
- //Assigning default value to the type variable
- const { 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 属性了.
总结
... 运算符非常灵活, 收放自如, 非常强大, 希望我们都能很好的掌握这个工具.
内容就这么多, 希望对大家有所帮助, 如有纰漏, 欢迎指正.
来源: http://www.css88.com/web/javascript/14920.html