從Function.length 與 Argument.length 區別談到如何傳遞任意個數參數

以前電面有問到:「你知道一個函數的length是什麼嗎?」前端

由於沒看過,也沒碰到過使用場景,我沒答出來。後來查了下發現是指函數的參數個數,因而也就做罷了。不過今天恰巧碰到了使用場景,且發現以前的理解也有誤,因而就寫一篇短文分享一下。數組

今天在嘗試 render props 的各類擴展玩法,寫了個簡單的表格數據 CRUD 操做 Demo:CodeSandboxbabel

寫Demo通常從全部操做都同步開始,最後再全改爲異步的狀況。因此須要寫一個將全部同步函數(數據加載以及增刪改)都轉換成異步函數的工具函數。我一開始寫得以下:dom

const delay = ms => new Promise(_ => setTimeout(_, ms));
// 打算從今開始儘可能使用 async/await
const withRequest = func => async args => {
  await delay(1000);
  if (Math.random() > 0.3) {
    func(...args);
  } else {
    message.info("操做失敗!");
  }
};
// 等一秒以後 30% 失敗,70% 執行操做
複製代碼

這是個所謂的 Curried(庫裏?柯里?咖喱?)函數,用於批量改造函數的函數,接受func爲參數,返回改造好的func。明眼人應該已經發現錯在哪裏了,不過我沒有,因而走了一堆彎路,卻收穫很多。異步

用此函數包裹了個人一堆測試方法:async

add = (a,b) => a + b
  square = a => a * a
  loadData = () => this.setState({ ... })
  
  loadData = withRequest(this.loadData);
  add = withRequest(this.add);
  square = withRequest(this.square);
複製代碼

立馬報錯跪了,因而我知道在沒有參數的 loadData 函數那裏跪了,並開始了個人求知之旅。函數

問題:

如何將任意個參數從上級函數傳遞給下級函數?工具

笨辦法解決:分狀況討論

分狀況討論的關鍵是:如何知道函數有幾個參數呢?毫無疑問我想到了fn.length, 因而寫下:post

const len = func.length
if(len === 0){
  func()
} else if(len === 1) {
  func(args)
} else {
  func(...args)
}
複製代碼

這對了嗎?答案是不對。 fn.length的定義是:函數的形參個數。也就是函數定義時的參數個數,而不是函數實際接受的參數個數。好比測試

const add = (a,b) => a + b
add(1,2,3,4,5)  // 3
add.length  // 2
複製代碼

而問題的狀況,咱們須要判斷的是函數接受的參數個數。這時候有一個方便的內置變量:arguments

function func1(a, b, c) {
  console.log(arguments[0]); // 1
  console.log(arguments[1]); // 2
  console.log(arguments[2]); // 3
}

func1(1, 2, 3);
複製代碼

arguments 即爲函數接收到的全部參數組成的(類)數組。那麼用 arguments.length 替換全部 func.length 是否就對了呢?仍是不對,arguments 有它的侷限性:

const func1 = (a, b, c) => {
  console.log(arguments[0]); // 1
  console.log(arguments[1]); // 2
  console.log(arguments[2]); // 3
}

func1(1, 2, 3);
// error: arguments is not defined
複製代碼

箭頭函數沒有arguments。 同時注意到如今前端代碼的箭頭函數會通過 babel 轉譯,產生的結果是 arguments 雖然不會 undefined,但會有各類怪異賦值。總之在箭頭函數裏別使用。

真正的解決辦法

剩餘參數 (Rest parameters)

const withRequest = func => async (...args) => {
  await delay(1000);
  if (Math.random() > 0.3) {
    func(...args);
  } else {
    message.info("操做失敗!");
  }
};
複製代碼

這段代碼裏出現了兩個 ...args, 前者是剩餘參數,後者是數組展開。二者一個是收束,一個是展開。功能相反。

function fun1(...args) {
  console.log(args.length);
}
 
fun1();  // 0
fun1(5); // 1
fun1(5, 6, 7); // 2
複製代碼

剩餘參數語法將「剩餘」的參數收束到一個數組中, 注意和 arguments 不同, 剩餘參數是一個真正的數組。繞了一大圈,實際上是忘記寫了三個點。不過也算真正理解了:

  1. Function.length
  2. arguments
  3. rest & spread

最後,讓咱們用剩餘參數挑戰一個實用函數吧:

問題

寫一個callAll函數,它接收任意數量的函數和任意數量的參數,若是做爲參數的函數存在就用全部的參數調用那個函數。

const add = (a,b) => {console.log(a + b)}
const minus = (a,b) => {console.log(a - b)}
callAll(add, minus)(2,1) 
// 3
// 1
複製代碼

答案以下:

// 剩餘參數是一個真正的數組,可使用任何數組方法
const callAll = (...fns) => (...args) => fns.forEach( fn => fn && fn(...args))
複製代碼
相關文章
相關標籤/搜索