多種讓 Arguments 當 Array 使用方式

ECMAScript function 的 arguments 是 Array-like object,僅有部分 array 功能,但有時候我們會希望把 arguments 當成 array 操作,此時就必須借用 array 的 method 讓 arguments 使用,或者藉由 arguments 產生新的 array。

Version


ECMAScript 5
ECMAScript 2015

Array-Like Object


length property,也可以如同 array 以 index 操作的 object,但沒有 Array.prototype 該有的 method

也就是當初 ECMAScript 的設計,只希望 arguments 提供 lengthindex 讓你 for loop 而已。

1
2
3
4
5
function foo() {
console.log(arguments);
}

foo(1, 2, 3);

foo() 並沒有宣告任何 parameter,但 foo() 有 3 個 argument 傳入。

arguments000

  1. arguments 只有 index、calleelengthSymbol 4 個 property
  2. 其 prototype 為普通 object,並非 array,所以沒有 Array.prototype 該有的 method

但由於 arguments 太像 array,因此直覺想使用 Array.prototype 下的 method 操作 arguments,此時我們就必須 借用 Array.prototype 下的 method,或者藉由 arguments 產生真正的 array。

For Loop


我們希望實作 sum(),無論提供幾個參數,都能相加得到結果。

1
2
3
4
5
6
7
8
9
10
function sum() {
let result = 0;

for(let i = 0; i < arguments.length; i++)
result += arguments[i];

return result;
}

console.log(1, 2, 3);

若使用 length[],則可使用 for loop 實踐任何功能,這也是 arguments 設計為 Array-Like Object 的初衷。

arguments006

Array.prototype.slice()


1
2
3
4
5
6
function sum() {
return Array.prototype.slice.call(arguments)
.reduce((acc, elm) => acc + elm, 0);
}

console.log(sum(1, 2, 3));

既然 arguments 很像 array,直覺就會想用 Array.prototype 下的眾多 method 解決問題。

先借用 Array.prototype.slice(),將 arguments 轉成正常 array,如此就能使用 Array.prototype 下的眾多 method。

1
Array.prototype.slice.call(arguments)

arguments 傳入 function 自帶的 call(),這樣就相當於有了 arguments.slice() 可用,回傳了一個真正的 array,因此就能使用 Array.prototype.reduce() 了。

使用 prototype + this,是 ECMAScript 借 Method 的慣用手法

arguments001

1
2
3
4
5
6
function sum() {
return [].slice.call(arguments)
.reduce((acc, elm) => acc + elm, 0);
}

console.log(sum(1, 2, 3));

[] 為 empty array,當使用 slice() 時,找不到會自動去 prototypeslice(),再以 call() 借用 slice() 成為 arguments 的 method,這種寫法更簡短,也是可行的。

arguments002

Array Generics


1
2
3
4
5
6
function sum() {
return Array.slice(arguments)
.reduce((acc, elm) => acc + elm, 0);
}

console.log(sum(1, 2, 3));

Array Generics 寫法讓你不用寫 Array.prototype.slice.call(),直接將 arguments 傳入 Array.slice() 即可,本質仍然是讓 arguments 借用 Array.prototype.slice()

這種寫法曾經是 ECMAScript 標準,但目前已經屬於 deprecated,Firefox 仍然可以使用,但 Chrome 已經無法執行,實務上不建議使用。

arguments007

  • 只能在 FireFox 執行,Chrome 與 Babel 皆無法執行

Array.prototype.reduce()


1
2
3
4
5
6
function sum() {
return Array.prototype.reduce
.call(arguments, (acc, elm) => acc + elm, 0);
}

console.log(sum(1, 2, 3));

既然目的是要使用 Array.prototype.reduce(),為什麼不直接 借用 reduce() 呢 ?

arguments004

Array.from()


1
2
3
4
5
6
function sum() {
return Array.from(arguments)
.reduce((acc, elm) => acc + elm);
}

console.log(sum(1, 2, 3));

ECMAScript 2015 提供了 Array.from(),將 arguments 轉成新的 array,讓我們不必再借用 Array.prototype.slice()

arguments003

Array.from() 是 static method,與 Array.slice() 的 Array Generics 不一樣,也因為 static method 與 Array Generics 太像了,所以 ECMAScript 新的標準才會將 Array Generics 列入 deprecated

Array Spread


1
2
3
4
5
6
function sum() {
return new Array(...arguments)
.reduce((acc, elm) => acc + elm, 0);
}

console.log(sum(1, 2, 3));

ECMAScript 2015 的 Array Spread 搭配 Array Constructor Function 也是不錯的寫法。

arguments005

Conclusion


  • ES5 時代,Array.prototype.slice.call(arguments)[].slice.call(arguments) 都算標準做法,主要是借用 slice()arguments 轉成新的 array
  • Array Generics 算 deprecated 寫法,實務上不建議使用
  • 也可以直接 借用 Array.prototype 的 method,根本不透過 slice()
  • ECMAScript 2015 時代,使用 Array.from() 與 Array Spread 也都是不錯的方式
  • 以個人觀點,ES5 偏好 Array.prototype.slice.call(arguments),能從 prototype 能明顯看出借用 method;而 ECMAScript 2015 則偏好 Array.from(),主要是語意清楚

Reference


MDN, The arguments object

2018-11-07