在ES6語法中,...操做符
有兩種意義:rest
(剩餘語法,rest參數) 和 spread
(展開語法,展開數組/對象),做爲函數、數組、對象的擴展運算符。javascript
從某種意義上說,剩餘語法與展開語法是相反的:
剩餘語法將多個元素收集起來並「凝聚」爲單個元素,而展開語法則是將數組/對象展開爲其中的各個元素。
形式爲(...變量名)
,將一個不定數量的參數表示爲一個數組。用於獲取函數
實參中的多餘參數,組成一個數組,這樣就不須要使用arguments對象了。
語法html
function(a, b, ...theArgs) { // ... }
注意:
函數的length屬性不包括rest參數。java
(function(a,b,...c){}).length //2
一個實例node
function sumOnlyNumbers() { var args = arguments; var numbers = filterNumbers(); return numbers.reduce((sum, element) => sum + element); function filterNumbers() { return Array.prototype.filter.call(args, element => typeof element === 'number' ); } } sumOnlyNumbers(1, 'Hello', 5, false); // => 6
缺點:
首先咱們要將arguments分配給給一個臨時新變量args,這樣才能在內部函數filterNumbers中能夠訪問args新變量,由於 filterNumbers()定義了它本身的arguments 會覆蓋外部的arguments 。這種作法太冗餘了。優化:
使用rest操做符能夠靈活解決這個問題,容許在函數中定義一個rest參數 ...args:es6
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
剩餘參數和 arguments 對象的區別
摘自 arguments 對象不是一個真實的數組,而剩餘參數是真實的 Array實例。因此,
可以在剩餘參數上面直接使用全部的數組方法,好比 sort,map,forEach,pop。
而若是想在arguments對象上使用數組方法,你首先得將它轉換爲真實的數組。數據庫
[].slice.call(arguments)
let [first,...rest]=[1,2,3,4,5]; first //1 rest //[2,3,4,5]
// ES5 a = list[0], rest = list.slice(1) // ES6 [a, ...rest] = list
const [first, ...rest] = []; first // undefined rest // []
let { x, ...y } = { x: 1, a: 2, b: 3 }; x // 1 y // { a: 2, b: 3 }
// 淺拷貝 let obj = { a: { b: 1 } }; let { ...x } = obj; obj.a.b = 2; x.a.b // 2
// ...運算符的解構賦值不能繼承自原型對象的屬性 let o1 = { a: 1 }; let o2 = { b: 2 }; o2.__proto__ = o1; let { ...o3 } = o2; o3 // { b: 2 } o3.a // undefined
const o = Object.create({ x: 1, y: 2 }); o.z = 3; let { x, ...newObj } = o; let { y, z } = newObj; x // 1 y // undefined z // 3
上面代碼中,變量x是單純的解構賦值
,因此能夠讀取對象o繼承的屬性;變量y和z是擴展運算符的解構賦值
,只能讀取對象o自身的屬性,因此變量z能夠賦值成功,變量y取不到值。
ES6 規定,變量聲明語句之中,若是使用解構賦值,擴展運算符後面必須是一個變量名,而不能是一個解構賦值表達式
,因此上面代碼引入了中間變量newObj,若是寫成下面這樣會報錯。數組
let { x, ...{ y, z } } = o; // SyntaxError: ... must be followed by an identifier in declaration contexts
注意:
瀏覽器
只能放在參數最後一位
,不然會報錯。要求等號右邊是一個對象
,若是等號右邊是undefined或null,就會報錯,由於它們沒法轉爲對象。淺拷貝
,即若是一個鍵的值是複合類型的值(數組、對象、函數)、那麼解構賦值拷貝的是這個值的引用,而不是這個值的副本...運算符
的解構賦值,不能複製繼承自原型對象的屬性
擴展某個函數的參數,引入其餘操做。數據結構
function baseFunction({ a, b }) { // ... } function wrapperFunction({ x, y, ...restConfig }) { // 使用 x 和 y 參數進行操做 // 其他參數傳給原始函數 return baseFunction(restConfig); }
spread運算則能夠看做是rest參數的逆運算。app
展開數組做爲參數序列、複製數組、合併數組、代替apply、...+表達式
1.展開數組做爲函數參數(...arr)
, 能夠將數組轉化爲逗號分隔的參數序列
console.log(1,...arr) arr1.push(...arr);
注意:
push方法的參數不能是數組
函數應用實例
JavaScript 的函數只能返回一個值,若是須要返回多個值,只能返回數組或對象。擴展運算符提供瞭解決這個問題的一種變通方法。
var dateFields = readDateFields(database); var d = new Date(...dateFields);
上面代碼從數據庫取出一行數據,經過擴展運算符,直接將其傳入構造函數Date。
2.複製數組
const a2 = a1.concat(); // ES5 const itemsCopy = [...items]; // ES6
注意:
這兩種方法都是淺拷貝,使用的時候須要注意。
3.合併數組
// 合併生成一個新的數組,不影響原來的兩個數組 arr = [4].concat(list) // ES5 arr = [4, ...list] // ES6
// 擴展arr變量,追加arr2 Array.prototype.push.apply(arr, list); // ES5 arr.push(...list); // ES6
若是擴展運算符後面是一個空數組,則不產生任何效果
[...[], 1] // [1]
4.能夠替代apply方法,apply要求將參數合併爲數組,做爲參數傳入
Function.apply(obj,args)方法能接收兩個參數
- obj:這個對象將代替Function類裏this對象
- args:這個是數組,它將做爲參數傳給Function(args-->arguments)
數組沒有max方法。Math.max.apply(null,[]);
Math.max.apply(null,[14,3,7]) // ES5寫法 Math.max(...[14,3,7]) // ES6寫法 // 等同於 Math.max(14, 3, 77);
5.擴展運算符後面還能夠放置表達式。
const arr = [ ...(x > 0 ? ['a'] : []), 'b', ];
複製對象、完整克隆、合併對象、...+表達式、取值函數get
1.拷貝
let z = { a: 3, b: 4 }; let n = { ...z }; // { a: 3, b: 4 }
這等同於使用Object.assign方法。
let aClone = { ...a }; // 等同於, let aClone = Object.assign({}, a);
上面的例子只是拷貝了對象實例的屬性。
2.完整克隆一個對象,拷貝實例屬性+對象原型的屬性
// 寫法一 const clone1 = { __proto__: Object.getPrototypeOf(obj), ...obj }; // 寫法二 const clone2 = Object.assign( Object.create(Object.getPrototypeOf(obj)), obj ); // 寫法三 const clone3 = Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) )
上面代碼中,寫法一的__proto__屬性在非瀏覽器的環境不必定部署,所以推薦使用寫法二和寫法三。
3.合併對象
// 合併 let ab = { ...a, ...b }; // 等同於, let ab = Object.assign({}, a, b);
若是用戶自定義的屬性,放在擴展運算符後面,則擴展運算符內部的同名屬性會被覆蓋掉
let aWithOverrides = { ...a, x: 1, y: 2 }; // 等同於 let aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; // 等同於 let x = 1, y = 2, aWithOverrides = { ...a, x, y }; // 等同於 let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
上面代碼中,a對象的x屬性和y屬性,拷貝到新對象後會被覆蓋掉。這用來修改現有對象部分的屬性就很方便了
let newVersion = { ...previousVersion, name: 'New Name' // Override the name property };
上面代碼中,newVersion對象自定義了name屬性,其餘屬性所有複製自previousVersion對象。
若是把自定義屬性放在擴展運算符前面,就變成了設置新對象的默認屬性值。
let aWithDefaults = { x: 1, y: 2, ...a }; // 等同於 let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a); // 等同於 let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
若是擴展運算符後面是一個空對象,則沒有任何效果。
若是擴展運算符的參數是null或undefined,這兩個值會被忽略,不會報錯
。
{...{}, a: 1} // { a: 1 } let emptyObject = { ...null, ...undefined }; // 不報錯
4.與數組的擴展運算符同樣,對象的擴展運算符後面能夠跟表達式。
const obj = { ...(x > 1 ? {a: 1} : {}), b: 2, };
5.對象中取值函數get
問題
擴展運算符的參數對象之中,若是有取值函數get,這個函數是會執行的
// 並不會拋出錯誤,由於 x 屬性只是被定義,但沒執行 let aWithXGetter = { ...a, get x() { throw new Error('not throw yet'); } }; // 會拋出錯誤,由於 x 屬性被執行了 let runtimeError = { ...a, ...{ get x() { throw new Error('throw now'); } } };
[...'hello'] // [ "h", "e", "l", "l", "o" ]
Unicode 是有兩個字節、四字節之區分。上面的寫法,有一個重要的好處,那就是可以正確識別四個字節的 Unicode 字符。
'x\uD83D\uDE80y' //'x🚀y' 'x\uD83D\uDE80y'.length // 4 [...'x\uD83D\uDE80y'].length // 3
上面代碼的第一種寫法,JavaScript 會將四個字節的 Unicode 字符,識別爲 2 個字符,採用擴展運算符就沒有這個問題。所以,正確返回字符串長度的函數,能夠像下面這樣寫。
function length(str) { return [...str].length; } length('x\uD83D\uDE80y') // 3
凡是涉及到操做四個字節的 Unicode 字符的函數,都有這個問題。所以,最好都用擴展運算符改寫。
let str = 'x\uD83D\uDE80y'; str.split('').reverse().join('') // 'y\uDE80\uD83Dx' [...str].reverse().join('') // 'y\uD83D\uDE80x'
上面代碼中,若是不用擴展運算符,字符串的reverse操做就不正確。reverse,顛倒數組中元素的順序,會改變原來的數組,而不會建立新的數組。
任何 Iterator 接口的對象,均可以用擴展運算符轉爲真正的數組。
let nodeList = document.querySelectorAll('div'); //nodeList 類數組對象 let array = [...nodeList];
這時,...運算符
能夠將其轉爲真正的數組,緣由就在於NodeList對象實現了 Iterator 。
對於那些沒有部署 Iterator 接口的相似數組的對象,...運算符
就沒法將其轉爲真正的數組。
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // TypeError: Cannot spread non-iterable object. let arr = [...arrayLike];
上面代碼中,arrayLike是一個相似數組的對象,可是沒有部署 Iterator 接口,擴展運算符就會報錯。這時,能夠改成使用Array.from方法將arrayLike轉爲真正的數組。
擴展運算符內部調用的是數據結構的 Iterator 接口,所以只要具備 Iterator 接口的對象,均可以使用擴展運算符,好比 Map 結構。
let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3]
Generator 函數運行後,返回一個遍歷器對象,所以也可使用擴展運算符。
const go = function*(){ yield 1; yield 2; yield 3; }; [...go()] // [1, 2, 3]
上面代碼中,變量go是一個 Generator 函數,執行後返回的是一個遍歷器對象,對這個遍歷器對象執行擴展運算符,就會將內部遍歷獲得的值,轉爲一個數組。
若是對沒有 Iterator 接口的對象,使用擴展運算符,將會報錯。
const obj = {a: 1, b: 2}; let arr = [...obj]; // TypeError: Cannot spread non-iterable object