ES6 進(jìn)階:你不知道的 Rest 參數(shù)與 Spread 語法細(xì)節(jié)

Rest 參數(shù)與 Spread 語法
在 JavaScript 中,很多內(nèi)建函數(shù)都支持傳入任意數(shù)量的參數(shù)。
例如:
Math.max(arg1, arg2, ..., argN)—— 返回入?yún)⒅械淖畲笾怠?/section>Object.assign(dest, src1, ..., srcN)—— 依次將屬性從src1..N復(fù)制到dest。……等。
在本文中,我們將學(xué)習(xí)如何編程實(shí)現(xiàn)支持函數(shù)可傳入任意數(shù)量的參數(shù)。以及,如何將數(shù)組作為參數(shù)傳遞給這類函數(shù)。
Rest 參數(shù) ...
在 JavaScript 中,無論函數(shù)是如何定義的,你都可以使用任意數(shù)量的參數(shù)調(diào)用函數(shù)。
例如:
function?sum(a,?b)?{
??return?a?+?b;
}
alert(?sum(1,?2,?3,?4,?5)?);
雖然這里不會(huì)因?yàn)閭魅搿斑^多”的參數(shù)而報(bào)錯(cuò)。但是當(dāng)然,在結(jié)果中只有前兩個(gè)參數(shù)被計(jì)算進(jìn)去了。
Rest 參數(shù)可以通過使用三個(gè)點(diǎn) ... 并在后面跟著包含剩余參數(shù)的數(shù)組名稱,來將它們包含在函數(shù)定義中。這些點(diǎn)的字面意思是“將剩余參數(shù)收集到一個(gè)數(shù)組中”。
例如,我們需要把所有的參數(shù)都放到數(shù)組 args 中:
function?sumAll(...args)?{?//?數(shù)組名為?args
??let?sum?=?0;
??for?(let?arg?of?args)?sum?+=?arg;
??return?sum;
}
alert(?sumAll(1)?);?//?1
alert(?sumAll(1,?2)?);?//?3
alert(?sumAll(1,?2,?3)?);?//?6
我們也可以選擇獲取第一個(gè)參數(shù)作為變量,并將剩余的參數(shù)收集起來。
下面的例子把前兩個(gè)參數(shù)定義為變量,并把剩余的參數(shù)收集到 titles 數(shù)組中:
function?showName(firstName,?lastName,?...titles)?{
??alert(?firstName?+?'?'?+?lastName?);?//?Julius?Caesar
??//?剩余的參數(shù)被放入?titles?數(shù)組中
??//?i.e.?titles?=?["Consul",?"Imperator"]
??alert(?titles[0]?);?//?Consul
??alert(?titles[1]?);?//?Imperator
??alert(?titles.length?);?//?2
}
showName("Julius",?"Caesar",?"Consul",?"Imperator");
Rest 參數(shù)必須放到參數(shù)列表的末尾
Rest 參數(shù)會(huì)收集剩余的所有參數(shù),因此下面這種用法沒有意義,并且會(huì)導(dǎo)致錯(cuò)誤:
function?f(arg1,?...rest,?arg2)?{?// arg2 在 ...rest 后面?!
??//?error
}
...rest必須處在最后。
"arguments" 變量
有一個(gè)名為 arguments 的特殊的類數(shù)組對象,該對象按參數(shù)索引包含所有參數(shù)。
例如:
function?showName()?{
??alert(?arguments.length?);
??alert(?arguments[0]?);
??alert(?arguments[1]?);
??//?它是可遍歷的
??//?for(let?arg?of?arguments)?alert(arg);
}
//?依次顯示:2,Julius,Caesar
showName("Julius",?"Caesar");
//?依次顯示:1,Ilya,undefined(沒有第二個(gè)參數(shù))
showName("Ilya");
在過去,JavaScript 中沒有 rest 參數(shù),而使用 arguments 是獲取函數(shù)所有參數(shù)的唯一方法。現(xiàn)在它仍然有效,我們可以在一些老代碼里找到它。
但缺點(diǎn)是,盡管 arguments 是一個(gè)類數(shù)組,也是可迭代對象,但它終究不是數(shù)組。它不支持?jǐn)?shù)組方法,因此我們不能調(diào)用 arguments.map(...) 等方法。
此外,它始終包含所有參數(shù),我們不能像使用 rest 參數(shù)那樣只截取入?yún)⒌囊徊糠帧?/p>
因此,當(dāng)我們需要這些功能時(shí),最好使用 rest 參數(shù)。
箭頭函數(shù)是沒有 "arguments"
如果我們在箭頭函數(shù)中訪問
arguments,訪問到的arguments并不屬于箭頭函數(shù),而是屬于箭頭函數(shù)外部的“普通”函數(shù)。舉個(gè)例子:
function?f()?{
??let?showArg?=?()?=>?alert(arguments[0]);
??showArg();
}
f(1);?//?1
我們已經(jīng)知道,箭頭函數(shù)沒有自身的?`this`?,F(xiàn)在我們知道了它們也沒有特殊的?`arguments`?對象。
Spread 語法
我們剛剛看到了如何從參數(shù)列表中獲取數(shù)組。
不過有時(shí)候我們也需要做與之相反的事兒。
例如,內(nèi)建函數(shù) Math.max[1] 會(huì)返回參數(shù)中最大的值:
alert(?Math.max(3,?5,?1)?);?//?5
假如我們有一個(gè)數(shù)組 [3, 5, 1],我們該如何用它調(diào)用 Math.max 呢?
直接把數(shù)組“原樣”傳入是不會(huì)奏效的,因?yàn)?Math.max 希望你傳入一個(gè)列表形式的數(shù)值型參數(shù),而不是一個(gè)數(shù)組:
let?arr?=?[3,?5,?1];
alert(?Math.max(arr)?);?//?NaN
毫無疑問,我們不可能手動(dòng)地去一一設(shè)置參數(shù) Math.max(arg[0], arg[1], arg[2]),因?yàn)槲覀儾淮_定這兒有多少個(gè)。在腳本執(zhí)行時(shí),可能參數(shù)數(shù)組中有很多個(gè)元素,也可能一個(gè)都沒有。并且這樣設(shè)置的代碼也很丑。
Spread 語法 來幫助你了!它看起來和 rest 參數(shù)很像,也使用 ...,但是二者的用途完全相反。
當(dāng)在函數(shù)調(diào)用中使用 ...arr 時(shí),它會(huì)把可迭代對象 arr “展開”到參數(shù)列表中。
以 Math.max 為例:
let?arr?=?[3,?5,?1];
alert(?Math.max(...arr)?);?//?5(spread?語法把數(shù)組轉(zhuǎn)換為參數(shù)列表)
我們還可以通過這種方式傳遞多個(gè)可迭代對象:
let?arr1?=?[1,?-2,?3,?4];
let?arr2?=?[8,?3,?-8,?1];
alert(?Math.max(...arr1,?...arr2)?);?//?8
我們甚至還可以將 spread 語法與常規(guī)值結(jié)合使用:
let?arr1?=?[1,?-2,?3,?4];
let?arr2?=?[8,?3,?-8,?1];
alert(?Math.max(1,?...arr1,?2,?...arr2,?25)?);?//?25
并且,我們還可以使用 spread 語法來合并數(shù)組:
let?arr?=?[3,?5,?1];
let?arr2?=?[8,?9,?15];
let?merged?=?[0,?...arr,?2,?...arr2];
alert(merged);?//?0,3,5,1,2,8,9,15(0,然后是?arr,然后是?2,然后是?arr2)
在上面的示例中,我們使用數(shù)組展示了 spread 語法,其實(shí)任何可迭代對象都可以。
例如,在這兒我們使用 spread 語法將字符串轉(zhuǎn)換為字符數(shù)組:
let?str?=?"Hello";
alert(?[...str]?);?//?H,e,l,l,o
Spread 語法內(nèi)部使用了迭代器來收集元素,與 for..of 的方式相同。
因此,對于一個(gè)字符串,for..of 會(huì)逐個(gè)返回該字符串中的字符,...str 也同理會(huì)得到 "H","e","l","l","o" 這樣的結(jié)果。隨后,字符列表被傳遞給數(shù)組初始化器 [...str]。
對于這個(gè)特定任務(wù),我們還可以使用 Array.from 來實(shí)現(xiàn),因?yàn)樵摲椒〞?huì)將一個(gè)可迭代對象(如字符串)轉(zhuǎn)換為數(shù)組:
let?str?=?"Hello";
//?Array.from?將可迭代對象轉(zhuǎn)換為數(shù)組
alert(?Array.from(str)?);?//?H,e,l,l,o
運(yùn)行結(jié)果與 [...str] 相同。
不過 Array.from(obj) 和 [...obj] 存在一個(gè)細(xì)微的差別:
Array.from適用于類數(shù)組對象也適用于可迭代對象。Spread 語法只適用于可迭代對象。
因此,對于將一些“東西”轉(zhuǎn)換為數(shù)組的任務(wù),Array.from 往往更通用。
獲取一個(gè) array/object 的副本
還記得我們 之前講過的[2] Object.assign() 嗎?
使用 spread 語法也可以做同樣的事情。
let?arr?=?[1,?2,?3];
let?arrCopy?=?[...arr];?//?將數(shù)組?spread?到參數(shù)列表中
????????????????????????//?然后將結(jié)果放到一個(gè)新數(shù)組
//?兩個(gè)數(shù)組中的內(nèi)容相同嗎?
alert(JSON.stringify(arr)?===?JSON.stringify(arrCopy));?//?true
//?兩個(gè)數(shù)組相等嗎?
alert(arr?===?arrCopy);?//?false(它們的引用是不同的)
//?修改我們初始的數(shù)組不會(huì)修改副本:
arr.push(4);
alert(arr);?//?1,?2,?3,?4
alert(arrCopy);?//?1,?2,?3
并且,也可以通過相同的方式來復(fù)制一個(gè)對象:
let?obj?=?{?a:?1,?b:?2,?c:?3?};
let?objCopy?=?{?...obj?};?//?將對象?spread?到參數(shù)列表中
??????????????????????????//?然后將結(jié)果返回到一個(gè)新對象
//?兩個(gè)對象中的內(nèi)容相同嗎?
alert(JSON.stringify(obj)?===?JSON.stringify(objCopy));?//?true
//?兩個(gè)對象相等嗎?
alert(obj?===?objCopy);?//?false?(not?same?reference)
//?修改我們初始的對象不會(huì)修改副本:
obj.d?=?4;
alert(JSON.stringify(obj));?//?{"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy));?//?{"a":1,"b":2,"c":3}
這種方式比使用 let arrCopy = Object.assign([], arr); 來復(fù)制數(shù)組,或使用 let objCopy = Object.assign({}, obj); 來復(fù)制對象寫起來要短得多。因此,只要情況允許,我們更喜歡使用它。
總結(jié)
當(dāng)我們在代碼中看到 "..." 時(shí),它要么是 rest 參數(shù),要么就是 spread 語法。
有一個(gè)簡單的方法可以區(qū)分它們:
若 ...出現(xiàn)在函數(shù)參數(shù)列表的最后,那么它就是 rest 參數(shù),它會(huì)把參數(shù)列表中剩余的參數(shù)收集到一個(gè)數(shù)組中。若 ...出現(xiàn)在函數(shù)調(diào)用或類似的表達(dá)式中,那它就是 spread 語法,它會(huì)把一個(gè)數(shù)組展開為列表。
使用場景:
Rest 參數(shù)用于創(chuàng)建可接受任意數(shù)量參數(shù)的函數(shù)。 Spread 語法用于將數(shù)組傳遞給通常需要含有許多參數(shù)的列表的函數(shù)。
它們倆的出現(xiàn)幫助我們輕松地在列表和參數(shù)數(shù)組之間來回轉(zhuǎn)換。
“舊式”的 arguments(類數(shù)組且可迭代的對象)也依然能夠幫助我們獲取函數(shù)調(diào)用中的所有參數(shù)。
現(xiàn)代 JavaScript 教程:開源的現(xiàn)代 JavaScript 從入門到進(jìn)階的優(yōu)質(zhì)教程。React 官方文檔推薦,與 MDN 并列的 JavaScript 學(xué)習(xí)教程[3]。
在線免費(fèi)閱讀:https://zh.javascript.info
參考資料
Math.max: https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Math/max
[2]之前講過的: https://zh.javascript.info/object-copy#ke-long-yu-he-bing-objectassign
[3]React 官方文檔推薦,與 MDN 并列的 JavaScript 學(xué)習(xí)教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources
看完三件事
如果你覺得本文對你有幫助,我想請你幫個(gè)忙:
轉(zhuǎn)發(fā)本文,點(diǎn)贊或者點(diǎn)個(gè)「在看」,是對我最大的認(rèn)可和支持; 關(guān)注公眾號(hào)「全棧修煉」,訂閱更多精彩內(nèi)容,獲取更多學(xué)習(xí)資料;
?
