... 运算符, 是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, 等等。
下面我们来看几个基础的例子来加深理解。
const a = [2, 3, 4]
const b = [1, ...a, 5]
b; // [1, 2, 3, 4, 5]
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.
如果不在最后一位, 会报错。
不得不感叹, 这个运算符设计的真的是妙, 可展开, 可收集, 收放自如, 当真好用。
先回顾下什么是类数组吧.
类数组和数组非常接近, 都可以拥有一系列元素, 也有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
const pokemon = ['KK', 'Peter'];
const charmander = '郑伊健';
const pokedex = [...pokemon, charmander];
console.log(pokedex);
//Result: [ 'KK', 'Peter', '郑伊健' ]
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' }
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' }
以上是一些基础费用法。下面介绍一些... 操作符的进阶用法。
先看一个例子:
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了
在这里就不多解释了。
顾名思义, 就是需要根据条件添加的属性。
看个例子:
const pokemon = {
name: 'Squirtle',
type: 'Water'
};
const abilities = ['Torrent', 'Rain dish'];
const fullPokemon = abilities ? { ...pokemon, abilities } : pokemon;
console.log(fullPokemon);
也可以简化一下:
const fullPokemon = abilities && { ...pokemon, abilities };
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 }
}
这也是一个很有用的技巧。
我们知道, 当结构一个对象的时候, 如果这个对象里没有某个属性, 解出来是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 属性了。
... 运算符非常灵活, 收放自如,非常强大, 希望我们都能很好的掌握这个工具。
觉得内容有帮助可以关注下我的公众号 「 前端e进阶 」,一起学习成长
JavaScript是一门了不起的语言。我喜欢它的灵活性:只需以你喜欢的方式做事:更改变量类型,动态的向对象添加方法或属性,对不同的变量类型使用运算符等等。然而动态是要付出代价的,开发人员需要知道怎样处理对于不同操作符的类型转换
TypeScript 2.1 增加了对 对象扩展运算和 rest 属性提案的支持,该提案在 ES2018 中标准化。可以以类型安全的方式使用 rest 和 spread 属性。对象 rest 属性假设已经定义了一个具有三个属性的简单字面量对象
我们可以使用展开操作符复制数组,不过要注意的是这是一个浅拷贝。这样我们就可以复制一个基本的数组,注意,它不适用于多级数组或带有日期或函数的数组。
JS 里的操作符大家每天都在使用,还有一些 ES2020、ES2021 新加的实用操作符,这些共同构成了 JS 灵活的语法生态
你有没有花一个下午的时间浏览过 Mozilla 文档?如果你有,你会很清楚网上有很多关于 JavaScript 的信息。这使得人们很容易忽略一些不同寻常的 JavaScript 操作符。
扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。array.push(...items)和add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。
JavaScript使用符号三个点(...)作为剩余运算符和展开运算符,不过这两个运算符是有区别的。最主要的区别就是,剩余运算符将用户提供的某些特定值的其余部分放入JavaScript数组中
首先,如果是嵌套写法的话,简单的嵌套还好,但是当嵌套变得很深的时候就有点难以阅读了。嵌套的执行流程是从右到左移动的,而不是我们正常阅读代码从左到右的方向
很多人都对双竖杠||非常熟悉,因为这个经常在项目中经常会用到。单竖杠|,却很少在项目开发中使用到。|是位运算符,||是逻辑运算符。平常,经常使用以下这个几个方法对数字进行处理。
Spread 和 Rest 是 ES6 Javascript 提供的两个功能,分别主要用于解构和函数参数处理。Spread 从可迭代对象(如数组、字符串或对象)中获取元素并将它们分散到各个部分。这就像将一副纸牌铺在桌子上一样。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!