Javascript currying柯里化詳解

我是一個前端發表於2019-02-16

面試題:實現add(1)(2)(3) //結果 = 6,題的核心就是問的js的柯里化

先說說什麼是柯里化,看過許多關於柯里化的文章,始終搞不太清楚,例如:柯里化(Currying)是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數且返回結果的新函式的技術。大多數的blog都是這種說法,說實話我是懵逼的。
我的理解是,curry是一個收集引數的方法,收集夠了去執行函式。

實現前我們先列一下要點
1、收集引數(就像面試題多次執行多個引數)是的利用閉包
2、每次執行引數有多有少例如add(1)(2,3)(4)
3、我們需要知道什麼時候引數夠了

        //如題
        //add(1)(2)(3)
        //邏輯應該是這樣add(1)執行收集引數1繼續執行收集引數2依次類推直到收集完畢。
       function curry(fn) {
            let arg = []; //用於收集引數
            //做一個閉包https://segmentfault.com/a/1190000017824877
            return function() {
                //每執行一次收集一次引數,為什麼用concat是因為有時候後是多個引數(2,3)
                arg = arg.concat([...arguments]);
                //直到引數收集完成執行fn
                // 我們需要知道什麼時候收集完了,條件就是curry引數fn的引數個數 fn.length
                //如果收集的引數個數大於等於fn的引數個數執行fn,如果沒有遞迴執行
                if (arg.length >= fn.length) {
                    return fn(...arg)
                }
                // 引數沒有收集完我們需要繼續收集,遞迴
                return arguments.callee
            }
        }

        // 測試一下
        let testAdd = curry(add1)
        // console.log(testAdd(1)(2)(3))
        // console.log(testAdd(1, 2)(3))
        //console.log(testAdd(1)(2, 3))

一不小心寫完了!

不過不能標題黨,說好的詳解,接下來我們解析一下網上大多數柯里化的實現程式碼

function curry(fn) {
  function _c(restNum, argsList) {
    return restNum === 0 ?
      fn.apply(null, argsList) :
      function(x) {
        return _c(restNum - 1, argsList.concat(x));
      };
  }
  return _c(fn.length, []);
}

一眼看不明白沒事,我們多看幾眼。
解析:
1、curry也是接收一個引數(fn)這個是必然
2、返回了一個函式,接收兩個引數,fn.length和一個空陣列這個好解
釋,我寫的簡版也說過了,fn.length是為了判斷引數是否收集夠
了,引數傳一個空陣列其實也是閉包的一種實現,用來收集引數。
3、裡邊是一個三目判斷,看著花裡胡哨的沒那麼複雜,判斷fn的引數個數如果是0,那就沒必要收集了直接執行fn,至於fn.apply(null,argList)我很明白的大聲說出來會用個apply就到處用嗎?在我看來沒有一分錢用,之所以用是因為argList是一個陣列,正好apply正好支援第二個引數是陣列,主要看起來很牛逼的樣子。
4、收集引數,fn引數個數不為零,每次收集fn函式引數的個數減一,直到等於0執行fn,這個就沒有我寫的通用了,我一次傳倆就掛了。

再來一個例子:bind方法實現

Function.prototype.bind = function(context) {
            //返回一個繫結this的函式,我們需要在此儲存this
            let self = this
                // 可以支援柯里化傳參,儲存引數
            let arg = [...arguments].slice(1)
                // 返回一個函式
            return function() {
                //同樣因為支援柯里化形式傳參我們需要再次獲取儲存引數
                let newArg = [...arguments]
                console.log(newArg)
                    // 返回函式繫結this,傳入兩次儲存的引數
                    //考慮返回函式有返回值做了return
                return self.apply(context, arg.concat(newArg))
            }
        }

        // 搞定測試
        let fn = Person.say.bind(Person1)
        fn()
        fn(18)

是的bind方法就是用的柯里化,bind實現詳情請移步:https://segmentfault.com/a/11…

相關文章