
『ES6腳丫系列』擴(kuò)展運(yùn)算符spread和rest參數(shù)
學(xué)習(xí)就好比是座大山,人們沿著不同的路登山,分享著自己看到的風(fēng)景。你不一定能看到別人看到的風(fēng)景,體會到別人的心情。只有自己去登山,才能看到不一樣的風(fēng)景,體會才更加深刻。
擴(kuò)展運(yùn)算符(...)
概念
【01】又叫做展開運(yùn)算符。
【02】spread 運(yùn)算符和 rest 參數(shù)相反。
語法
三個(gè)點(diǎn): ...
將一個(gè)數(shù)組或一個(gè)可迭代的對象,在一次調(diào)用中,將它們的內(nèi)容分隔為單個(gè)單個(gè)的成員參與運(yùn)算。
如果是數(shù)組:
等同于將一個(gè)數(shù)組去掉外層的方括號,然后整體放在原先的位置一樣。
逗號分隔的參數(shù)列表。
如果是字符串:
等同于,變?yōu)閱蝹€(gè)字符單個(gè)字符用逗號分隔的參數(shù)列表。
let res = [..."hel"];//["h","e","l"];
如果是可迭代對象:(只有部署了iterator的對象才是可迭代的)
把屬性變?yōu)橛枚禾柗指舻膮?shù)列表。
例子:
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]
只想要一個(gè)數(shù)組呢?
var middle = [3, 4];
var arr = [1, 2, ...middle, 5, 6];
console.log(arr);// [1, 2, 3, 4, 5, 6]
用途:
【01】復(fù)制數(shù)組
slice()是JS數(shù)組的一個(gè)方法,它可以復(fù)制數(shù)組,類似的,可以使用擴(kuò)展運(yùn)算符來復(fù)制數(shù)組:
arr2并不等于arr。因?yàn)椴皇窍嗟炔僮?,它們引用的地址不一樣?/p>
var arr = ['a', 'b', 'c'];
var arr2 = [...arr];
console.log(arr2);// ['a', 'b', 'c']
【02】連接數(shù)組
可以使用擴(kuò)展運(yùn)算符替代concat()來連接數(shù)組。
首先,我們來看看concat()方法是如何實(shí)現(xiàn)的。
var arr = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];
arr1 = arr.concat(arr2);
console.log(arr);// ['a', 'b', 'c', 'd', 'e', 'f']
使用擴(kuò)展運(yùn)算符:
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函數(shù)時(shí)結(jié)合擴(kuò)展運(yùn)算符。
例子。
Math.max()會返回一堆數(shù)字中最大的數(shù)。
Math.max();// -Infinity
Math.max(1, 2, 3);// 3
Math.max(100, 3, 4);// 100
如果不使用擴(kuò)展運(yùn)算符,最簡單的方式是使用.apply(),將一個(gè)數(shù)組作為參數(shù)傳入Math.max()
var arr = [2, 4, 8, 6, 0];
function max(arr) {
return Math.max.apply(null, arr);
}
console.log(max(arr));// 8
這樣做很麻煩。
現(xiàn)在來看一下如何使用擴(kuò)展運(yùn)算符來得到相同的結(jié)果的。只需要兩行代碼:
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】字符串轉(zhuǎn)換數(shù)組
使用擴(kuò)展運(yùn)算符將字符串轉(zhuǎn)換成數(shù)組。
var str = "hello";
var chars = [...str];
console.log(chars); // ['h', 'e',' l',' l', 'o']
【05】能夠把可迭代對象(NodeList, arguments等等)轉(zhuǎn)化為真正的數(shù)組。
// Convert NodeList to Array
let divsArray = [...document.querySelectorAll('div')];
// Convert Arguments to Array
let argsArray = [...arguments];
下面的例子使得一個(gè)類數(shù)組對象符合迭代協(xié)議,并利用spread運(yùn)算符將其轉(zhuǎn)變?yōu)橐粋€(gè)數(shù)組:
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參數(shù)。用三個(gè)點(diǎn)表示。
【02】rest 參數(shù)意味著把剩下的東西包裝成一個(gè)數(shù)組。
【03】用在函數(shù)參數(shù)和解構(gòu)數(shù)組中。
它將一個(gè)逗號分隔的參數(shù)列表轉(zhuǎn)換成一個(gè)數(shù)組。
吃碼小妖:類似打包和解壓的既視感?
如果在函數(shù)形參中使用了rest參數(shù)(打包),那么在函數(shù)中使用spread使用它(解壓),等于原封不動(dòng)的使用實(shí)參了。
【04】當(dāng)三個(gè)點(diǎn)出現(xiàn)在函數(shù)參數(shù)時(shí),它意味著將調(diào)用函數(shù)時(shí)的參數(shù)列表變?yōu)橐粋€(gè)數(shù)組。該參數(shù)名就是這個(gè)數(shù)組。
如果函數(shù)參數(shù)本身就是一個(gè)數(shù)組,那么rest參數(shù)運(yùn)算符等于把函數(shù)形參變?yōu)橐粋€(gè)二維數(shù)組了。
例子:
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】如果是多個(gè)形參,那么rest參數(shù)需要是參數(shù)列表中的最后一個(gè)參數(shù)。寫成逗號分隔的參數(shù)列表。
arguments對象不具有這種選擇性并且始終包含所有的參數(shù)值。
例子:
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】使用不定數(shù)量的函數(shù)參數(shù)。
在ES5中,當(dāng)我們需要處理一個(gè)未知數(shù)量的參數(shù)的函數(shù)時(shí),可以使用arguments變量。
function sum () {
console.log(arguments)
}
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);//55

計(jì)算這個(gè)參數(shù)總和的一種方法是將其轉(zhuǎn)換成具有 Array.prototype.slice.call(arguments) 的數(shù)組,然后用數(shù)組方法循環(huán)遍歷每個(gè)數(shù)字,如 forEach 或 reduce。
我相信你可以自己實(shí)現(xiàn) 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 參數(shù),可以將所有逗號分隔的參數(shù)直接打包到數(shù)組中。
// 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)
}
【】在數(shù)組解構(gòu)中遇到三個(gè)點(diǎn)。它會將剩余的元素打包變?yōu)橐粋€(gè)數(shù)組。
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 的分?jǐn)?shù),我們可以通過將剩余的分?jǐn)?shù)打包成一個(gè)數(shù)組。
let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...restOfScores] = scores
console.log(restOfScores) // [90, 97, 95]
用途2:在數(shù)組中插入不定數(shù)量的元素。
例如,.push(item1, ..., itemN)會把元素一個(gè)接一個(gè)的插入數(shù)組:不得不循環(huán)每個(gè)元素將其作為參數(shù)。但這并不總是很方便:有時(shí)需要把一整個(gè)數(shù)組的元素push到目標(biāo)數(shù)組。
在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']
【】如何更好的把數(shù)組中的元素作為參數(shù)填充到函數(shù)調(diào)用中。
ES5在函數(shù)對象上提供了 .apply()來解決這個(gè)問題。不幸的是這項(xiàng)技術(shù)有3個(gè)問題:
- 它需要手工的指定函數(shù)調(diào)用的上下文。
- 它不能使用在構(gòu)造器函數(shù)調(diào)用中。
- 人們傾向于一個(gè)更短的解決方案。
例子:
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"]
在復(fù)雜情景中的函數(shù)體內(nèi)操作arguments對象是很麻煩的。
為了在filterNumbers()訪問sumOnlyNumbers()的arguments,你不得不創(chuàng)建一個(gè)臨時(shí)變量args。這是因?yàn)閒ilterNumbers()會定義它自己的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運(yùn)算符可以優(yōu)雅的解決這個(gè)問題。它允許你在函數(shù)聲明時(shí)定義一個(gè)rest參數(shù)...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
函數(shù)聲明function sumOnlyNumbers(...args)表明args以數(shù)組的形式接受調(diào)用參數(shù)。
【】箭頭函數(shù)并不定義自己的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運(yùn)算符可以在構(gòu)造器調(diào)用中使用數(shù)組元素作為參數(shù),而這并不能通過直接使用.apply()做到。
讓我們看一個(gè)例子:
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'