[深入05] 柯里化 偏函數(shù) 函數(shù)記憶 尾遞歸

導(dǎo)航

[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉(zhuǎn)換 和 運算符
[深入07] 瀏覽器緩存機(jī)制(http緩存機(jī)制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫Promise
[深入20] 手寫函數(shù)

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex
[源碼-vue01] data響應(yīng)式 和 初始化渲染

前置知識

函數(shù)的參數(shù)

  • length:函數(shù)的legth屬性棠隐,返回函數(shù)預(yù)期的參數(shù)個數(shù)(形參)
  • arguments:arguments對象赊瞬,包含了程序運行時的所有參數(shù)(實參)

類似數(shù)組的對象轉(zhuǎn)換成數(shù)組

  • [].slice.call(類似數(shù)組的對象)
  • [].slice.apply(類似數(shù)組的對象)
  • Array.prototype.slice.call(類似數(shù)組的對象, x) // x是綁定this后傳入slice函數(shù)的參數(shù)
  • Array.from()

偏函數(shù)和柯里化的概念

  • 柯里化 curry:
    • 將接收多個參數(shù)的函數(shù)诈胜,轉(zhuǎn)換成接收一個單一參數(shù)的函數(shù),并返回接收余下參數(shù)顾瞻,并返回最終結(jié)果的新函數(shù)
    • <font color=red>即當(dāng)參數(shù)小于預(yù)期參數(shù)時,返回一個可以接收剩余參數(shù)的函數(shù)德绿,參數(shù)大于等于預(yù)期參數(shù)時荷荤,返回最終結(jié)果</font>
  • 偏函數(shù) partial application:
    • 是固定一個或多個參數(shù)退渗,產(chǎn)生另一個較小元的函數(shù) n元函數(shù) => 轉(zhuǎn)換成n-x元函數(shù)

柯里化 curry

  • 柯里化函數(shù),他接收函數(shù)A作為參數(shù)蕴纳,運行后能夠返回一個新的函數(shù)氓辣,并且這個新的函數(shù)能夠處理函數(shù)A的剩余參數(shù)

1. 柯里化階段一

  • 需求: 將add(1,2,3)轉(zhuǎn)化成curryAdd(1)(2)(3)
  • <font color=red>缺點:只能處理3個參數(shù)的情況,不能處理任意多個參數(shù)的情況袱蚓,毫無擴(kuò)展性</font>

需求: 將add(1,2,3)轉(zhuǎn)化成curryAdd(1)(2)(3)
缺點:只能處理3個參數(shù)的情況钞啸,不能處理任意多個參數(shù)的情況,毫無擴(kuò)展性

function curryAdd(a) {
  return function(b) {
    return function(c) {
      return a+b+c
    }
  }
}
const res = curryAdd(1)(2)(3)
console.log(res, 'res1')

2. 柯里化階段二

  • 需求:處理任意多個參數(shù)相加
  • 缺點:
    • 1. 處理相加邏輯的代碼喇潘,只是在沒有參數(shù)時才會執(zhí)行体斩,其他部分都在處理怎么收集所有參數(shù),會多一次沒有參數(shù)的調(diào)用
      • 更合理的方式是通過判斷函數(shù)可以接收參數(shù)的總和颖低,來判斷是否參數(shù)收集完畢
    • 2. 相加邏輯可以單獨抽離

function curryAdd() {
  let params_arr = [] // 用于收集所有實參
  function closure() {
    const args = Array.prototype.slice.call(arguments) // 每次調(diào)用閉包函數(shù)傳入的實參絮吵,可以是多個
    if (args.length) {
      params_arr = params_arr.concat(args)
      // concat返回一個拼接過后的新數(shù)組,不改變原數(shù)組
      return closure
      // 如果還有參數(shù)忱屑,則繼續(xù)返回閉包函數(shù)蹬敲,則繼續(xù)繼續(xù)傳參調(diào)用
    }
    return params_arr.reduce((total, current) => total + current)
    // 如果沒有再傳入?yún)?shù),則相加所有傳入的參數(shù)莺戒,缺點是要多一次沒有參數(shù)的調(diào)用
  }
  return closure // 第一次調(diào)用curryAdd返回的閉包
}
const fn = curryAdd()
const res = fn(1,2)(3)(4)()
console.log(res, 'res'); // 10

3. 柯里化階段三

function add(a,b,c,d,e) {
  return Array.prototype.slice.call(arguments).reduce((total, current) => total + current)
  // 注意:這里拿到的是實參的實際個數(shù)伴嗡,即實參可能大于形參,當(dāng)實參 (大于等于) 形參時从铲,執(zhí)行相加
}
function curryAdd(fn) {
  let paramsArr = []
  const paramsMaxLength = fn.length // function.length返回函數(shù)的形參個數(shù)瘪校,預(yù)期的參數(shù)個數(shù)為最大參數(shù)個數(shù),即相加執(zhí)行條件
  function closure() {
    const args = Array.prototype.slice.call(arguments)
    paramsArr = paramsArr.concat(args)
    if (paramsArr.length < paramsMaxLength) {
      return closure
    }
    // 當(dāng)參數(shù)個數(shù) 大于等于 最大的期望個數(shù)名段,即形參的個數(shù)時阱扬,執(zhí)行相加函數(shù)
    return fn.apply(this, paramsArr)
  }
  return closure
}
const fn = curryAdd(add)
const res = fn(1,2,3)(4)(5,6)
console.log(res, 'res');

4.柯里化變通版

  • 上面版本的缺點:上面的版本需要知道add的參數(shù)length

function add() {
  return Array.from(arguments).reduce((total, current) => total + current)
}
function currentAdd(fn) {
  let paramsArr = []
  function closure() { // 該閉包函數(shù)只負(fù)責(zé)收集參數(shù),處理相加可以在閉包上掛載新的方法getSum
    const args = Array.from(arguments)
    paramsArr = paramsArr.concat(args)
    return closure
  }
  closure.getSum = function() {
    return fn.apply(this, paramsArr) // getSum負(fù)責(zé)計算伸辟,利用了閉包中的變量paramsArr
  }
  return closure
}
const fn = currentAdd(add)
const resAdd = fn(1)(2,3)
const res = resAdd.getSum(); // 該方法的缺點就是需要單獨再調(diào)用getSum函數(shù)
console.log(res, 'res')

偏函數(shù) partial

  • 將一個或者多個參數(shù)麻惶,固定到一個函數(shù)上,并產(chǎn)生返回一個更小元的函數(shù)
function add (a, b) {
  return a + b
}
function partial (fn) {...}

const addPartial = partial(add, 1)  // ------------------ 實現(xiàn)固定一部分參數(shù)1
const res = addPartial(2) // 3 -------------------------- 只傳一部分參數(shù) 2

偏函數(shù)實現(xiàn)方式1

  • <font color=red>通過bind方法實現(xiàn)</font>
  • bind方法綁定this指向信夫,同時也可以傳入fn的部分和全部參數(shù)窃蹋,并返回一個新函數(shù),新函數(shù)可以傳入?yún)?shù)作為fn的剩余參數(shù)

function add(a,b,c,d) {
  return a+b+c+d
}
function partail() {
  const params = Array.prototype.slice.call(arguments)
  const fn = params.shift() // 刪除數(shù)組第一個元素忙迁,返回該元素脐彩,改變原數(shù)組
  return fn.bind(this, ...params)
  // 該params執(zhí)行shift后已經(jīng)改變\
  // params數(shù)組展開后的所有成員,都會作為fn的參數(shù)
  // 并且bind返回的新函數(shù)還可以傳參
}

const fn = partail(add, 1, 2) // 固定了 1姊扔,2兩個參數(shù)
const res = fn(3,4) // 除了固定的參數(shù)惠奸,剩下的參數(shù)在這里傳入
console.log(res, 'res')


偏函數(shù)實現(xiàn)方式2


function add(a,b,c,d) {
  return Array.from(arguments).reduce((total, current) => total + current)
  // 相加實參
  // 因為實參可能大于形參
}
function partialAdd(fn) {
  let paramsFixed = Array.from(arguments).slice(1)
  // 除去fn的剩余參數(shù)
  // 注意:該方法和curry很相似,current第一調(diào)用是不需要傳fn參數(shù)的恰梢,聲明的是空數(shù)組佛南,而在partial中需要傳固定的參數(shù)
  const paramsMaxLength = fn.length // 形參個數(shù)
  function closure() {
    const args = Array.from(arguments)
    paramsFixed = paramsFixed.concat(args)
    if (paramsFixed.length < paramsMaxLength) {
      return closure
    }
    return fn.apply(this, paramsFixed) // 大于等于時
  }
  return closure
}
const fn = partialAdd(add, 2)
const res = fn(3)(4)(5)
console.log(res, 'res') // 14

函數(shù)記憶

  • 函數(shù)記憶:指將上次的(計算結(jié)果)緩存起來梗掰,當(dāng)下次調(diào)用時,如果遇到相同的(參數(shù))嗅回,就直接返回(緩存中的數(shù)據(jù))
  • 實現(xiàn)原理:將參數(shù)和對應(yīng)的結(jié)果保存在對象中及穗,再次調(diào)用時,判斷對象key是否存在绵载,存在返回緩存的值
    • 注意:函數(shù)是需要返回值的
function memorize(fn) {
  const cache = {}
  return function() {
    const key = Array.prototype.join.call(arguments, ',')
    if (key in cache) {
      return cache[key]
    }
    return cache[key] = fn.apply(this, arguments)
  }
}

我的簡書:http://www.reibang.com/p/eb583d76452f

尾調(diào)用

尾調(diào)用: 函數(shù)執(zhí)行的最后一個步驟埂陆,是返回另一個函數(shù)的調(diào)用,叫尾調(diào)用
優(yōu)點:   
1. 尾調(diào)用娃豹,當(dāng)里層函數(shù)被調(diào)用時焚虱,外層函數(shù)已經(jīng)執(zhí)行完,出棧了懂版,不會造成內(nèi)存泄漏
2. 在遞歸中鹃栽,尾調(diào)用使得棧中只有一個函數(shù)在運行,不會造成性能問題


f(x) {
  return g(x)
}
// 尾調(diào)用躯畴,因為返回g(x)調(diào)用的時候民鼓,f(x)已經(jīng)執(zhí)行完


f(x) {
  return g(x) + 1
}
// 非尾調(diào)用,因為返回 g(x) 調(diào)用時蓬抄,f(x)并未執(zhí)行完丰嘉,當(dāng)g(x)執(zhí)行完后,還有執(zhí)行 g(x)+1倡鲸,f(x)才執(zhí)行完
// 函數(shù)只有執(zhí)行完后才會出棧(執(zhí)行上下文調(diào)用棧)


const a = x => x ? f() : g();
// f()和g()都是尾調(diào)用

const a = () => f() || g()
// f()非尾調(diào)用供嚎,還要接著判斷

const a = () => f() && g();
// f()非尾調(diào)用

尾遞歸

遞歸  -- 尾遞歸和尾調(diào)用

1. 構(gòu)成遞歸的條件
  - 邊界條件
  - 遞歸前進(jìn)段
  - 遞歸返回段
  - 當(dāng)邊界條件不滿足時黄娘,遞歸前進(jìn)
  - 當(dāng)邊界條件滿足時峭状,遞歸返回

2. 
Recursive:遞歸
factorial:階乘

3. 尾調(diào)用和非尾調(diào)用
  - 尾調(diào)用和非尾調(diào)用的區(qū)別是 執(zhí)行上下文棧不一樣
  - 為調(diào)用:調(diào)用在函數(shù)結(jié)尾處
  - 尾調(diào)用的執(zhí)行上下文棧,外層函數(shù)執(zhí)行完就出棧逼争,不會一層一層嵌套优床,不造成內(nèi)存溢出
  - 尾調(diào)用自身就叫尾遞歸
// 尾調(diào)用
// 因為調(diào)用g(x)時,f(x)已經(jīng)執(zhí)行完了誓焦,就會出棧胆敞,不會壓棧,不會造成內(nèi)存溢出
function f(x){
    return g(x);
}
// 非尾調(diào)用
// 因為調(diào)用g(x)時杂伟,f(x)并未執(zhí)行完移层,g(x)+1需要g(x)函數(shù)執(zhí)行完,才會相加赫粥,返回后f(x)才會執(zhí)行完
function f(x){
    return g(x) + 1;
}



------------------------------------------------------------------------------------

+++(例1)階乘
     // recursive遞歸
    function factorial (n) {
      if (n < 2) return n
      return n * factorial(n-1)
    }
    const res = factorial(3)
    // 1. 3 => 3 * factorial(2) => 3 * 2 * factorial(1) => 3 * 2 * 1  
(分析)
    1. 每次返回一個遞歸的函數(shù)观话,都會創(chuàng)建一個閉包
    2. 所以維護(hù)這么多執(zhí)行上下文棧,開銷大越平,用以造成內(nèi)存泄漏
    3. 優(yōu)化方法:尾調(diào)用


+++(例1升級)階乘優(yōu)化
    function factorial(n, res) {
      if (n === 1) {
        return res
      }
      return factorial(n-1, n * res)
    }
(分析)
    第一次:factorial(3, 4* 1)
    第二次:factorial(2, 3* 4)
    第三次:factorial(1, 2* 12)
    第四次:24


+++(例1再升級)階乘優(yōu)化频蛔,多傳了一個參數(shù)灵迫,可以用函數(shù)柯里化或者偏函數(shù)來實現(xiàn)

    function factorial(res, n) {
      if (n === 1) return res;
      return factorial(n * res, n-1)
    }

    function curring (fn) {
      let par_arr = Array.prototype.slice.call(arguments, 1)
      const closure = function () {
        par_arr = par_arr.concat(Array.prototype.slice.call(arguments))
        console.log(par_arr, 'par_arr')
        if (par_arr.length < fn.length) {
          return closure
        }
        return fn.apply(null, par_arr)
      }
      return closure
    }
    const curringFactorial =  curring(factorial, 1)
    const res = curringFactorial(4)
    console.log(res)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晦溪,隨后出現(xiàn)的幾起案子瀑粥,更是在濱河造成了極大的恐慌,老刑警劉巖三圆,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狞换,死亡現(xiàn)場離奇詭異,居然都是意外死亡舟肉,警方通過查閱死者的電腦和手機(jī)哀澈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來度气,“玉大人割按,你說我怎么就攤上這事×准” “怎么了适荣?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長院领。 經(jīng)常有香客問我弛矛,道長,這世上最難降的妖魔是什么比然? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任丈氓,我火速辦了婚禮,結(jié)果婚禮上强法,老公的妹妹穿的比我還像新娘万俗。我一直安慰自己,他們只是感情好饮怯,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布闰歪。 她就那樣靜靜地躺著,像睡著了一般蓖墅。 火紅的嫁衣襯著肌膚如雪库倘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天论矾,我揣著相機(jī)與錄音教翩,去河邊找鬼。 笑死贪壳,一個胖子當(dāng)著我的面吹牛饱亿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼路捧,長吁一口氣:“原來是場噩夢啊……” “哼关霸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杰扫,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤队寇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后章姓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佳遣,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年凡伊,在試婚紗的時候發(fā)現(xiàn)自己被綠了零渐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡系忙,死狀恐怖诵盼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情银还,我是刑警寧澤风宁,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蛹疯,受9級特大地震影響戒财,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捺弦,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一饮寞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧列吼,春花似錦幽崩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凑耻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柠贤,已是汗流浹背香浩。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留臼勉,地道東北人邻吭。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像宴霸,于是被迫代替她去往敵國和親囱晴。 傳聞我的和親對象是個殘疾皇子膏蚓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容