Javascript Rest 參數(shù)與 Spread 語法

2023-02-17 10:50 更新

在 JavaScript 中,很多內(nèi)建函數(shù)都支持傳入任意數(shù)量的參數(shù)。

例如:

  • ?Math.max(arg1, arg2, ..., argN)? —— 返回參數(shù)中的最大值。
  • ?Object.assign(dest, src1, ..., srcN)? —— 依次將屬性從 ?src1..N? 復(fù)制到 ?dest?。
  • ……等。

在本章中,我們將學(xué)習(xí)如何編寫支持傳入任意數(shù)量參數(shù)的函數(shù),以及如何將數(shù)組作為參數(shù)傳遞給這類函數(shù)。

Rest 參數(shù) ...

在 JavaScript 中,無論函數(shù)是如何定義的,你都可以在調(diào)用它時傳入任意數(shù)量的參數(shù)。

例如:

function sum(a, b) {
  return a + b;
}

alert( sum(1, 2, 3, 4, 5) );

雖然這里這個函數(shù)不會因為傳入過多的參數(shù)而報錯。但是,當(dāng)然,只有前兩個參數(shù)被求和了。

我們可以在函數(shù)定義中聲明一個數(shù)組來收集參數(shù)。語法是這樣的:...變量名,這將會聲明一個數(shù)組并指定其名稱,其中存有剩余的參數(shù)。這三個點的語義就是“收集剩余的參數(shù)并存進指定數(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

我們也可以選擇將第一個參數(shù)獲取為變量,并將剩余的參數(shù)收集起來。

下面的例子把前兩個參數(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ù)會收集剩余的所有參數(shù),因此下面這種用法沒有意義,并且會導(dǎo)致錯誤:

function f(arg1, ...rest, arg2) { // arg2 在 ...rest 后面?!
  // error
}

...rest 必須寫在參數(shù)列表最后。

“arguments” 變量

有一個名為 arguments 的特殊類數(shù)組對象可以在函數(shù)中被訪問,該對象以參數(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(沒有第二個參數(shù))
showName("Ilya");

在過去,JavaScript 中不支持 rest 參數(shù)語法,而使用 arguments 是獲取函數(shù)所有參數(shù)的唯一方法?,F(xiàn)在它仍然有效,我們可以在一些老代碼里找到它。

但缺點是,盡管 arguments 是一個類數(shù)組,也是可迭代對象,但它終究不是數(shù)組。它不支持數(shù)組方法,因此我們不能調(diào)用 arguments.map(...) 等方法。

此外,它始終包含所有參數(shù),我們不能像使用 rest 參數(shù)那樣只截取參數(shù)的一部分。

因此,當(dāng)我們需要這些功能時,最好使用 rest 參數(shù)。

箭頭函數(shù)沒有 ?"arguments"?

如果我們在箭頭函數(shù)中訪問 arguments,訪問到的 arguments 并不屬于箭頭函數(shù),而是屬于箭頭函數(shù)外部的“普通”函數(shù)。

舉個例子:

function f() {
  let showArg = () => alert(arguments[0]);
  showArg();
}

f(1); // 1

我們已經(jīng)知道,箭頭函數(shù)沒有自身的 this。現(xiàn)在我們知道了它們也沒有特殊的 arguments 對象。

Spread 語法

我們剛剛看到了如何從參數(shù)列表中獲取數(shù)組。

有時候我們也需要做與之相反的事。

例如,內(nèi)建函數(shù) Math.max 會返回參數(shù)中最大的值:

alert( Math.max(3, 5, 1) ); // 5

如果我們有一個數(shù)組 [3, 5, 1],我們該如何用它調(diào)用 Math.max 呢?

直接“原樣”傳入這個數(shù)組是不會奏效的,因為 Math.max 期望的是列表形式的數(shù)值型參數(shù),而不是一個數(shù)組:

let arr = [3, 5, 1];

alert( Math.max(arr) ); // NaN

毫無疑問,我們不能手動地去一一設(shè)置參數(shù) Math.max(arg[0], arg[1], arg[2]),因為我們不確定這兒有多少個。在代碼執(zhí)行時,參數(shù)數(shù)組中可能有很多個元素,也可能一個都沒有。而且,這樣的代碼也很不優(yōu)雅。

Spread 語法 可以解決這個問題!它看起來和 rest 參數(shù)很像,也使用 ...,但是二者的用途完全相反。

當(dāng)在函數(shù)調(diào)用中使用 ...arr 時,它會把可迭代對象 arr “展開”到參數(shù)列表中。

以 Math.max 為例:

let arr = [3, 5, 1];

alert( Math.max(...arr) ); // 5(spread 語法把數(shù)組轉(zhuǎn)換為參數(shù)列表)

我們還可以通過這種方式傳入多個可迭代對象:

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 語法,其實我們可以用 spread 語法這樣操作任何可迭代對象。

例如,在這兒我們使用 spread 語法將字符串轉(zhuǎn)換為字符數(shù)組:

let str = "Hello";

alert( [...str] ); // H,e,l,l,o

Spread 語法內(nèi)部使用了迭代器來收集元素,與 for..of 的方式相同。

因此,對于一個字符串,for..of 會逐個返回該字符串中的字符,...str 也同理會得到 "H","e","l","l","o" 這樣的結(jié)果。隨后,字符列表被傳遞給數(shù)組初始化器 [...str]

對于這個特定任務(wù),我們還可以使用 Array.from 來實現(xiàn),因為該方法會將一個可迭代對象(如字符串)轉(zhuǎn)換為數(shù)組:

let str = "Hello";

// Array.from 將可迭代對象轉(zhuǎn)換為數(shù)組
alert( Array.from(str) ); // H,e,l,l,o

運行結(jié)果與 [...str] 相同。

不過 Array.from(obj) 和 [...obj] 存在一個細微的差別:

  • ?Array.from? 適用于類數(shù)組對象也適用于可迭代對象。
  • Spread 語法只適用于可迭代對象。

因此,對于將一些“東西”轉(zhuǎn)換為數(shù)組的任務(wù),?Array.from? 往往更通用。

復(fù)制 array/object

還記得我們 之前講過的 Object.assign() 嗎?

使用 spread 語法也可以做同樣的事情(譯注:也就是進行淺拷貝)。

let arr = [1, 2, 3];

let arrCopy = [...arr]; // 將數(shù)組 spread 到參數(shù)列表中
                        // 然后將結(jié)果放到一個新數(shù)組

// 兩個數(shù)組中的內(nèi)容相同嗎?
alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true

// 兩個數(shù)組相等嗎?
alert(arr === arrCopy); // false(它們的引用是不同的)

// 修改我們初始的數(shù)組不會修改副本:
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3

并且,也可以通過相同的方式來復(fù)制一個對象:

let obj = { a: 1, b: 2, c: 3 };

let objCopy = { ...obj }; // 將對象 spread 到參數(shù)列表中
                          // 然后將結(jié)果返回到一個新對象

// 兩個對象中的內(nèi)容相同嗎?
alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true

// 兩個對象相等嗎?
alert(obj === objCopy); // false (not same reference)

// 修改我們初始的對象不會修改副本:
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)我們在代碼中看到 ?"..."? 時,它要么是 rest 參數(shù),要么是 spread 語法。

有一個簡單的方法可以區(qū)分它們:

  • 若 ?...? 出現(xiàn)在函數(shù)參數(shù)列表的最后,那么它就是 rest 參數(shù),它會把參數(shù)列表中剩余的參數(shù)收集到一個數(shù)組中。
  • 若 ?...? 出現(xiàn)在函數(shù)調(diào)用或類似的表達式中,那它就是 spread 語法,它會把一個數(shù)組展開為列表。

使用場景:

  • Rest 參數(shù)用于創(chuàng)建可接受任意數(shù)量參數(shù)的函數(shù)。
  • Spread 語法用于將數(shù)組傳遞給通常需要含有許多參數(shù)的函數(shù)。

我們可以使用這兩種語法輕松地互相轉(zhuǎn)換列表與參數(shù)數(shù)組。

舊式的 ?arguments?(類數(shù)組且可迭代的對象)也依然能夠幫助我們獲取函數(shù)調(diào)用中的所有參數(shù)。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號