『ES6脚丫系列』扩展运算符spread和rest参数
学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。
扩展运算符(...)
概念
【01】又叫做展开运算符。
【02】spread 运算符和 rest 参数相反。
语法
三个点: ...
将一个数组或一个可迭代的对象,在一次调用中,将它们的内容分隔为单个单个的成员参与运算。
如果是数组:
等同于将一个数组去掉外层的方括号,然后整体放在原先的位置一样。
逗号分隔的参数列表。
如果是字符串:
等同于,变为单个字符单个字符用逗号分隔的参数列表。
let res = [..."hel"];//["h","e","l"];
如果是可迭代对象:(只有部署了iterator的对象才是可迭代的)
把属性变为用逗号分隔的参数列表。
例子:
let array = ['one', 'two', 'three']
// These two are exactly the same
console.log(...array) // one two three
console.log('one', 'two', 'three') // one two three
例子:
var middle = [3, 4];
var arr = [1, 2, middle, 5, 6];
console.log(arr);// [1, 2, [3, 4], 5, 6]
只想要一个数组呢?
var middle = [3, 4];
var arr = [1, 2, ...middle, 5, 6];
console.log(arr);// [1, 2, 3, 4, 5, 6]
用途:
【01】复制数组
slice()是JS数组的一个方法,它可以复制数组,类似的,可以使用扩展运算符来复制数组:
arr2并不等于arr。因为不是相等操作,它们引用的地址不一样。
var arr = ['a', 'b', 'c'];
var arr2 = [...arr];
console.log(arr2);// ['a', 'b', 'c']
【02】连接数组
可以使用扩展运算符替代concat()来连接数组。
首先,我们来看看concat()方法是如何实现的。
var arr = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];
arr1 = arr.concat(arr2);
console.log(arr);// ['a', 'b', 'c', 'd', 'e', 'f']
使用扩展运算符:
var arr = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];
arr = [...arr, ...arr2];
console.log(arr);// ['a', 'b', 'c', 'd', 'e', 'f']
【03】Math
可以在使用math函数时结合扩展运算符。
例子。
Math.max()会返回一堆数字中最大的数。
Math.max();// -Infinity
Math.max(1, 2, 3);// 3
Math.max(100, 3, 4);// 100
如果不使用扩展运算符,最简单的方式是使用.apply(),将一个数组作为参数传入Math.max()
var arr = [2, 4, 8, 6, 0];
function max(arr) {
return Math.max.apply(null, arr);
}
console.log(max(arr));// 8
这样做很麻烦。
现在来看一下如何使用扩展运算符来得到相同的结果的。只需要两行代码:
var arr = [2, 4, 8, 6, 0];
var max = Math.max(...arr);
console.log(max);// 8
let numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
【04】字符串转换数组
使用扩展运算符将字符串转换成数组。
var str = "hello";
var chars = [...str];
console.log(chars); // ['h', 'e',' l',' l', 'o']
【05】能够把可迭代对象(NodeList, arguments等等)转化为真正的数组。
// Convert NodeList to Array
let divsArray = [...document.querySelectorAll('div')];
// Convert Arguments to Array
let argsArray = [...arguments];
下面的例子使得一个类数组对象符合迭代协议,并利用spread运算符将其转变为一个数组:
function iterator() {
var index = 0;
return {
next: () => ({ // Conform to Iterator protocol
done : index >= this.length,
value: this[index++]
})
};
}
var arrayLike = {
0: 'Cat',
1: 'Bird',
length: 2
};
arrayLike[Symbol.iterator] = iterator; //Conform to Iterable Protocol
var array = [...arrayLike];
console.log(array); // => ['Cat', 'Bird']
【02】散布操作符 (…)
概念
【01】rest参数。用三个点表示。
【02】rest 参数意味着把剩下的东西包装成一个数组。
【03】用在函数参数和解构数组中。
它将一个逗号分隔的参数列表转换成一个数组。
吃码小妖:类似打包和解压的既视感?
如果在函数形参中使用了rest参数(打包),那么在函数中使用spread使用它(解压),等于原封不动的使用实参了。
【04】当三个点出现在函数参数时,它意味着将调用函数时的参数列表变为一个数组。该参数名就是这个数组。
如果函数参数本身就是一个数组,那么rest参数运算符等于把函数形参变为一个二维数组了。
例子:
function a(...args){}
a(1,2,3,4,5);
等同于
function a(){
var args = [arguments[0],arguments[1],...,arguments[N]];
};
a(1,2,3,4,5);
【05】如果是多个形参,那么rest参数需要是参数列表中的最后一个参数。写成逗号分隔的参数列表。
arguments对象不具有这种选择性并且始终包含所有的参数值。
例子:
function filter(type, ...items) {
return items.filter(item => typeof item === type);
}
filter('boolean', true, 0, false); // => [true, false]
filter('number', false, 4, 'Welcome', 7); // => [4, 7]
用途
【01】使用不定数量的函数参数。
在ES5中,当我们需要处理一个未知数量的参数的函数时,可以使用arguments变量。
function sum () {
console.log(arguments)
}
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);//55
计算这个参数总和的一种方法是将其转换成具有 Array.prototype.slice.call(arguments) 的数组,然后用数组方法循环遍历每个数字,如 forEach 或 reduce。
我相信你可以自己实现 forEach ,所以这里是 reduce 的例子:
// ES5 way
function sum () {
let argsArray = Array.prototype.slice.call(arguments);
return argsArray.reduce(function(sum, current) {
return sum + current;
}, 0)
}
使用 ES6 rest 参数,可以将所有逗号分隔的参数直接打包到数组中。
// ES6 way
const sum = (...args) => args.reduce((sum, current) => sum + current, 0)
// ES6 way if we didn't shortcut it with so many arrow functions
function sum (...args) {
return args.reduce((sum, current) => sum + current, 0)
}
【】在数组解构中遇到三个点。它会将剩余的元素打包变为一个数组。
let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third] = scores
console.log(first) // 98
console.log(second) // 95
console.log(third) // 93
如果我们想要 rest 的分数,我们可以通过将剩余的分数打包成一个数组。
let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...restOfScores] = scores
console.log(restOfScores) // [90, 97, 95]
用途2:在数组中插入不定数量的元素。
例如,.push(item1, ..., itemN)会把元素一个接一个的插入数组:不得不循环每个元素将其作为参数。但这并不总是很方便:有时需要把一整个数组的元素push到目标数组。
在ES5中这可以通过.apply()做到:用一种不友好且繁琐的方式。让我们看看:
var fruits = ['banana'];
var moreFruits = ['apple', 'orange'];
Array.prototype.push.apply(fruits, moreFruits);
console.log(fruits); // => ['banana', 'apple', 'orange']
ES6:let res = fruits.push(...moreFruits);console.log(res);// ['banana', 'apple', 'orange']
【】如何更好的把数组中的元素作为参数填充到函数调用中。
ES5在函数对象上提供了 .apply()来解决这个问题。不幸的是这项技术有3个问题:
- 它需要手工的指定函数调用的上下文。
- 它不能使用在构造器函数调用中。
- 人们倾向于一个更短的解决方案。
例子:
let countries = ['Moldova', 'Ukraine'];
countries.push.apply(countries, ['USA', 'Japan']);
console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']
例子:
function spreadReporter(...values) {
let object = [...values];
return object[0].length;
}
var items = ['one', 'two', 'three'];
console.log(spreadReporter(items)); // 3
例子:
let dairy = [];
let store = {
add: function(category, ...items) {
category.push(...items);
}
};
store.add(dairy, 'milk', 'sour cream');
store.add(dairy, 'ice cream', 'yogurt', 'cheese');
console.log(dairy);
// outputs ["milk", "sour cream", "ice cream", "yogurt", "cheese"]
在复杂情景中的函数体内操作arguments对象是很麻烦的。
为了在filterNumbers()访问sumOnlyNumbers()的arguments,你不得不创建一个临时变量args。这是因为filterNumbers()会定义它自己的arguments从而覆盖了外层的arguments。
这种方式可以工作,但是太繁琐了。
function sumOnlyNumbers() { function filterNumbers() {
return Array.prototype.filter.call(args, element => typeof element === 'number');
}
var args = arguments;
var numbers = filterNumbers();
return numbers.reduce((sum, element) => sum + element);
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6
rest运算符可以优雅的解决这个问题。它允许你在函数声明时定义一个rest参数...args:
function sumOnlyNumbers(...args) {
var numbers = filterNumbers();
return numbers.reduce((sum, element) => sum + element);
function filterNumbers() {
return args.filter(element => typeof element === 'number');
}
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6
函数声明function sumOnlyNumbers(...args)表明args以数组的形式接受调用参数。
【】箭头函数并不定义自己的arguments而是会访问外层作用域中的arguments对象。
例子:
(function() {
let outerArguments = arguments;
const concat = (...items) => {
console.log(arguments === outerArguments); // => true
return items.reduce((result, item) => result + item, '');
};
concat(1, 5, 'nine'); // => '15nine'
})();
Spread运算符可以在构造器调用中使用数组元素作为参数,而这并不能通过直接使用.apply()做到。
让我们看一个例子:
class King {
constructor(name, country) {
this.name = name;
this.country = country;
}
getDescription() {
return `${this.name} leads ${this.country}`;
}
}
var details = ['Alexander the Great', 'Greece'];
var Alexander = new King(...details);
Alexander.getDescription(); // => 'Alexander the Great leads Greece'