點燈坊

失くすものさえない今が強くなるチャンスよ

將 Arguments 轉成 Array

Sam Xiao's Avatar 2020-11-26

ECMAScript Function 的 arguments 是 Array-like Object,僅有部分 Array 功能,實務上我們會希望把 arguments 當成真正 Array 操作。

Version

ECMAScript 2015

Array-Like Object

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

}

f(1, 2, 3) // ?

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

  • arguments 只有 index、calleelengthSymbol 4 個 property

  • 其 prototype 為普通 Object 並非 Array,所以沒有 Array.prototype 該有的 method

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

array000

Array.prototype.slice()

function f() {
  let args = Array.prototype.slice.call(arguments)
  return args.reduce((ac, x) => ac + x, 0)
}

f(1, 2, 3) // ?

Array.prototype.slice() 為 Array 自帶的 method,可複製出新 Array,因為內部使用 this 實作,因此可使用 call()arguments 傳入取代內部 this,如此就可使用 Array.prototype.reduce()

array001

[].slice()

function f() {
  let args = [].slice.call(arguments)
  return args.reduce((ac, x) => ac + x, 0)
}

f(1, 2, 3) // ?

[] 為 Empty Array,因此自帶 slice(),再以 call() 傳入 arguments 取代 this,這種寫法更簡短。

array002

Array.prototype.reduce()

function f() {
  return Array.prototype.reduce.call(arguments, (ac, x) => ac + x, 0)
}

f(1, 2, 3) // ?

既然目的是要使用 Array.prototype.reduce(),亦可直接對 reduce()call() 傳入 arguments

array003

[].reduce()

function f() {
  return [].reduce.call(arguments, (ac, x) => ac + x, 0)
}

f(1, 2, 3) // ?

同理亦可使用 [].reduce() 取代 Array.prototype.reduce()

array004

Array.from()

function f() {
  let args = Array.from(arguments)
  return args.reduce((ac, x) => ac + x)
}

f(1, 2, 3) // ?

ES6 提供了 Array.from() 可將 Array-liked Object 轉成真正 Array,如此可不必使用 Array.prototype.slice() 配合 call()

array005

Array Spread

function f() {
  let args = Array(...arguments)
  return args.reduce((ac, x) => ac + x, 0)
}

f(1, 2, 3) // ?

ES6 的 Array Spread 搭配 Array Constructor Function 亦可將 Array-liked Object 轉成真正 Array。

array006

Function Pipeline

import { pipe, sum } from 'ramda'
import { from } from 'wink-fp'

function f() {
  return pipe(
    from,
    sum
  )(arguments)
}

f(1, 2, 3) // ?

若要以 Function Pipeline 風格:

  • 使用 from() 將 Array-liked Object 轉成真正 Array
  • 使用 sum() 計算總和
  • 最後使用 pipe() 組合 from()sum()

array007

Arrow Function

let f = (...args) => args.reduce((ac, x) => ac + x) 

f(1, 2, 3) // ?

Arrow function 不再支援 arguments,必須以 ... rest parameter 取代。

array008

sum()

import { sum } from 'ramda'

let f = (...args) => sum(args)

f(1, 2, 3) // ?

亦可直接以 sum() 取代 reduce()

array009

Point-free

import { unapply, sum } from 'ramda'

let f = unapply(sum)

f(1, 2, 3) // ?

sum() 必須接受 Array,可使用 unapply() 使 sum() 能接受正常 argument,如此 f() 則 Point-free。

array010

Conclusion

  • ES5 時代,Array.prototype.slice.call(arguments)[].slice.call(arguments) 都算標準做法,主要是借用 slice()arguments 轉成新的 array
  • 也可以直接 借用 Array.prototype 的 method,根本不透過 slice()
  • ES6 時代使用 Array.from() 與 Array Spread 也都是不錯方式
  • arguments 在 arrow function 則無法使用 arguments,必須改用 rest parameter 取代
  • 若要求 Point-free,可直接對 sum() 加以 unapply()

Reference

MDN, The arguments object