成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JS中的柯里化

Hancock_Xu / 2161人閱讀

摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。個人理解不知道對不對延遲執(zhí)行柯里化的另一個應(yīng)用場景是延遲執(zhí)行。不斷的柯里化,累積傳入的參數(shù),最后執(zhí)行。

作為函數(shù)式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。

這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~

1. 簡介

柯里化(Currying),又稱部分求值(Partial Evaluation),是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。

核心思想是把多參數(shù)傳入的函數(shù)拆成單參數(shù)(或部分)函數(shù),內(nèi)部再返回調(diào)用下一個單參數(shù)(或部分)函數(shù),依次處理剩余的參數(shù)。

按照Stoyan Stefanov --《JavaScript Pattern》作者 的說法,所謂“柯里化”就是使函數(shù)理解并處理部分應(yīng)用

柯里化有3個常見作用:

參數(shù)復(fù)用

提前返回

延遲計(jì)算/運(yùn)行

talk is cheap,看看怎么實(shí)現(xiàn)吧~

2. 實(shí)現(xiàn) 2.1 通用實(shí)現(xiàn)

一個通用實(shí)現(xiàn):

function currying(fn, ...rest1) {
  return function(...rest2) {
    return fn.apply(null, rest1.concat(rest2))
  }
}

注意這里concat接受非數(shù)組元素參數(shù)將被當(dāng)做調(diào)用者的一個元素傳入

用它將一個sayHello函數(shù)柯里化試試:

function sayHello(name, age, fruit) {
  console.log(console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`))
}

const curryingShowMsg1 = currying(sayHello, "小明")
curryingShowMsg1(22, "蘋果")            // 我叫 小明,我 22 歲了, 我喜歡吃 蘋果

const curryingShowMsg2 = currying(sayHello, "小衰", 20)
curryingShowMsg2("西瓜")               // 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜

嘻嘻,感覺還行~

2.2 高階柯里化函數(shù)

以上柯里化函數(shù)已經(jīng)能解決一般需求了,但是如果要多層的柯里化總不能不斷地進(jìn)行currying函數(shù)的嵌套吧,我們希望經(jīng)過柯里化之后的函數(shù)每次只傳遞一個或者多個參數(shù),那該怎么做呢:

function curryingHelper(fn, len) {
  const length = len || fn.length  // 第一遍運(yùn)行l(wèi)ength是函數(shù)fn一共需要的參數(shù)個數(shù),以后是剩余所需要的參數(shù)個數(shù)
  return function(...rest) {
    return rest.length >= length    // 檢查是否傳入了fn所需足夠的參數(shù)
        ? fn.apply(this, rest)
        : curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length)        // 在通用currying函數(shù)基礎(chǔ)上
  }
}

function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`) }    

const betterShowMsg = curryingHelper(sayHello)
betterShowMsg("小衰", 20, "西瓜")      // 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜
betterShowMsg("小豬")(25, "南瓜")      // 我叫 小豬,我 25 歲了, 我喜歡吃 南瓜
betterShowMsg("小明", 22)("倭瓜")      // 我叫 小明,我 22 歲了, 我喜歡吃 倭瓜
betterShowMsg("小拽")(28)("冬瓜")      // 我叫 小拽,我 28 歲了, 我喜歡吃 冬瓜

如此實(shí)現(xiàn)一個高階的柯里化函數(shù),使得柯里化一個函數(shù)的時候可以不用嵌套的currying,當(dāng)然是因?yàn)榘亚短椎牡胤椒诺搅薱urryingHelper里面進(jìn)行了...-。-

2.3 瘋狂柯里化函數(shù)

盡管柯里化函數(shù)已經(jīng)很牛了,但是它也讓你必須花費(fèi)點(diǎn)小心思在你所定義函數(shù)的參數(shù)順序上。在一些函數(shù)式編程語言中,會定義一個特殊的“占位變量”。通常會指定下劃線來干這事,如果作為一個函數(shù)的參數(shù)被傳入,就表明這個是可以“跳過的”,是尚待指定的參數(shù)。比如:

var sendAjax = function (url, data, options) { /* ... */ }
var sendPost = function (url, data) {                    // 當(dāng)然可以這樣
    return sendAjax(url, data, { type: "POST", contentType: "application/json" })
}
// 也可以使用下劃線來指定未確定的參數(shù)
var sendPost = sendAjax( _ , _ , { type: "POST", contentType: "application/json" })        

JS不具備這樣的原生支持,可以使用一個全局占位符變量const _ = { }并且通過===來判斷是否是占位符,當(dāng)然你如果使用了lodash的話可以使用別的符號代替。那么可以這樣改造柯里化函數(shù):

const _ = {}
function crazyCurryingHelper(fn, length, args, holes) {
  length = length || fn.length    // 第一遍是fn所需的參數(shù)個數(shù),以后是
  args = args || []
  holes = holes || []
  
  return function(...rest) {
    let _args = args.slice(),
        _holes = holes.slice(),
        argLength = _args.length,        // 存儲接收到的args和holes的長度
        holeLength = _holes.length,
        arg, i = 0
    for (; i < rest.length; i++) {
      arg = rest[i]
      if (arg === _ && holeLength) {
        holeLength--                      // 循環(huán)_holes的位置
        _holes.push(_holes.shift())      // _holes最后一個移到第一個
      } else if (arg === _) {
        _holes.push(argLength + i)          // 存儲_hole就是_的位置
      } else if (holeLength) {              // 是否還有沒有填補(bǔ)的hole
        holeLength--
        _args.splice(_holes.shift(), 0, arg)           // 在參數(shù)列表指定hole的地方插入當(dāng)前參數(shù)
      } else {
        _args.push(arg)            // 不需要填補(bǔ)hole,直接添加到參數(shù)列表里面
      }
    }
    
    return _args.length >= length                          // 遞歸的進(jìn)行柯里化
        ? fn.apply(this, _args)
        : crazyCurryingHelper.call(this, fn, length, _args, _holes)
  }
}

function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`) }

const betterShowMsg = crazyCurryingHelper(sayHello)
betterShowMsg(_, 20)("小衰", _, "西瓜")          // 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜
betterShowMsg(_, _, "南瓜")("小豬")(25)          // 我叫 小豬,我 25 歲了, 我喜歡吃 南瓜
betterShowMsg("小明")(_, 22)(_, _, "倭瓜")          // 我叫 小明,我 22 歲了, 我喜歡吃 倭瓜
betterShowMsg("小拽")(28)("冬瓜")          // 我叫 小拽,我 28 歲了, 我喜歡吃 冬瓜

牛B閃閃

3. 柯里化的常見用法 3.1 參數(shù)復(fù)用

通過柯里化方法,緩存參數(shù)到閉包內(nèi)部參數(shù),然后在函數(shù)內(nèi)部將緩存的參數(shù)與傳入的參數(shù)組合后apply/bind/call給函數(shù)執(zhí)行,來實(shí)現(xiàn)參數(shù)的復(fù)用,降低適用范圍,提高適用性。

參看以下栗子,官員無論添加后續(xù)老婆,都能和合法老婆組合,通過柯里化方法,getWife方法就無需添加多余的合法老婆...

var currying = function(fn) {
  var args = [].slice.call(arguments, 1)      // fn 指官員消化老婆的手段,args 指的是那個合法老婆
  return function(...rest) {
    var newArgs = args.concat(...rest)        // 已經(jīng)有的老婆和新搞定的老婆們合成一體,方便控制
    return fn.apply(null, newArgs)        // 這些老婆們用 fn 這個手段消化利用,完成韋小寶前輩的壯舉并返回
  }
}

var getWife = currying(function() {
  console.log([...arguments].join(";"))          // allwife 就是所有的老婆的,包括暗渡陳倉進(jìn)來的老婆
}, "合法老婆")

getWife("老婆1", "老婆2", "老婆3")      // 合法老婆;老婆1;老婆2;老婆3
getWife("超越韋小寶的老婆")             // 合法老婆;超越韋小寶的老婆
getWife("超級老婆")                    // 合法老婆;超級老婆
3.2 提高適用性

通用函數(shù)解決了兼容性問題,但同時也會再來,使用的不便利性,不同的應(yīng)用場景往,要傳遞很多參數(shù),以達(dá)到解決特定問題的目的。有時候應(yīng)用中,同一種規(guī)則可能會反復(fù)使用,這就可能會造成代碼的重復(fù)性。

// 未柯里化前
function square(i) { return i * i; }
function dubble(i) { return i * 2; }
function map(handler, list) { return list.map(handler); }

map(square, [1, 2, 3, 4, 5]);        // 數(shù)組的每一項(xiàng)平方
map(square, [6, 7, 8, 9, 10]);
map(dubble, [1, 2, 3, 4, 5]);        // 數(shù)組的每一項(xiàng)加倍
map(dubble, [6, 7, 8, 9, 10]);

同一規(guī)則重復(fù)使用,帶來代碼的重復(fù)性,因此可以使用上面的通用柯里化實(shí)現(xiàn)改造一下:

// 柯里化后
function square(i) { return i * i; }
function dubble(i) { return i * 2; }
function map(handler, ...list) { return list.map(handler); }

var mapSQ = currying(map, square);
mapSQ([1, 2, 3, 4, 5]);
mapSQ([6, 7, 8, 9, 10]);

var mapDB = currying(map, dubble);
mapDB([1, 2, 3, 4, 5]);
mapDB([6, 7, 8, 9, 10]);

可以看到這里柯里化方法的使用和偏函數(shù)比較類似,順便回顧一下偏函數(shù)~

偏函數(shù)是創(chuàng)建一個調(diào)用另外一個部分(參數(shù)或變量已預(yù)制的函數(shù))的函數(shù),函數(shù)可以根據(jù)傳入的參數(shù)來生成一個真正執(zhí)行的函數(shù)。比如:

const isType = function(type) {
  return function(obj) {
    return Object.prototype.toString.call(obj) === `[object ${type}]`
  }
}
const isString = isType("String")
const isFunction = isType("Function")

這樣就用偏函數(shù)快速創(chuàng)建了一組判斷對象類型的方法~

偏函數(shù)固定了函數(shù)的某個部分,通過傳入的參數(shù)或者方法返回一個新的函數(shù)來接受剩余的參數(shù),數(shù)量可能是一個也可能是多個
柯里化是把一個有n個參數(shù)的函數(shù)變成n個只有1個參數(shù)的函數(shù),例如:add = (x, y, z) => x + y + zcurryAdd = x => y => z => x + y + z
當(dāng)偏函數(shù)接受一個參數(shù)并且返回了一個只接受一個參數(shù)的函數(shù),與兩個接受一個參數(shù)的函數(shù)curry()()的柯里化函數(shù),這時候兩個概念類似。(個人理解不知道對不對)

3.3 延遲執(zhí)行

柯里化的另一個應(yīng)用場景是延遲執(zhí)行。不斷的柯里化,累積傳入的參數(shù),最后執(zhí)行。例如累加:

const curryAdd = function(...rest) {
  const _args = rest
  return function cb(...rest) {
    if (rest.length === 0) {
      return _args.reduce((sum, single) => sum += single)
    } else {
      _args.push(...rest)
      return cb
    }
  }
}()                        // 為了保存添加的數(shù),這里要返回一個閉包
curryAdd(1)
curryAdd(2)
curryAdd(3)
curryAdd(4)
curryAdd()               // 最后計(jì)算輸出:10

更通用的寫法,將處理函數(shù)提取出來:

const curry = function(fn) {
  const _args = []
  return function cb(...rest) {
    if (rest.length === 0) {
      return fn.apply(this, _args)
    }
    _args.push(...rest)
    return cb
  }
}

const curryAdd = curry((...T) => 
  T.reduce((sum, single) => sum += single)
)
curryAdd(1)
curryAdd(2)
curryAdd(3)
curryAdd(4)
curryAdd()               // 最后計(jì)算輸出:10
4. Function.prototype.bind 方法也是柯里化應(yīng)用

與 call/apply 方法直接執(zhí)行不同,bind 方法將第一個參數(shù)設(shè)置為函數(shù)執(zhí)行的上下文,其他參數(shù)依次傳遞給調(diào)用方法(函數(shù)的主體本身不執(zhí)行,可以看成是延遲執(zhí)行),并動態(tài)創(chuàng)建返回一個新的函數(shù), 這符合柯里化特點(diǎn)。

var foo = {x: 888};
var bar = function () {
    console.log(this.x);
}.bind(foo);              // 綁定
bar();                    // 888

下面是一個 bind 函數(shù)的模擬,testBind 創(chuàng)建并返回新的函數(shù),在新的函數(shù)中將真正要執(zhí)行業(yè)務(wù)的函數(shù)綁定到實(shí)參傳入的上下文,延遲執(zhí)行了。

Function.prototype.testBind = function(scope) {
  return () => this.apply(scope)
}
var foo = { x: 888 }
var bar = function() {
  console.log(this.x)
}.testBind(foo)              // 綁定
bar()                    // 888

網(wǎng)上的帖子大多深淺不一,甚至有些前后矛盾,在下的文章都是學(xué)習(xí)過程中的總結(jié),如果發(fā)現(xiàn)錯誤,歡迎留言指出~

參考:
JS高級程序設(shè)計(jì)
JS中的柯里化(currying)
前端開發(fā)者進(jìn)階之函數(shù)柯里化Currying
淺析 JavaScript 中的 函數(shù) currying 柯里化
掌握J(rèn)avaScript函數(shù)的柯里化
函數(shù)式JavaScript(4):函數(shù)柯里化

PS:歡迎大家關(guān)注我的公眾號【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備注加群,我拉你入群~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90778.html

相關(guān)文章

  • JS中的柯里 及 精巧的自動柯里實(shí)現(xiàn)

    摘要:笑中自動柯里化的精巧實(shí)現(xiàn)柯里化是函數(shù)式編程中很重要的一環(huán),很多函數(shù)式語言都會默認(rèn)將函數(shù)自動柯里化。 什么是柯里化? 在計(jì)算機(jī)科學(xué)中,柯里化(Currying)是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。這個技術(shù)由 Christopher Strachey 以邏輯學(xué)家 Haskell Curry 命名的,盡管...

    moven_j 評論0 收藏0
  • 關(guān)于js中的柯里(Currying)與反柯里(Uncurrying)

    摘要:今天了解到一個新名詞柯里化,研究一番后總結(jié)如下一柯里化定義把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)最初函數(shù)的第一個參數(shù)的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。如果使用反柯里化,則可以這樣寫震驚某前端只會,竟月入百萬。。。 今天了解到一個新名詞:柯里化,研究一番后總結(jié)如下: 一· 柯里化 定義 把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并...

    Rocko 評論0 收藏0
  • JavaScript 函數(shù)式編程技巧 - 反柯里

    摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。而反柯里化,從字面講,意義和用法跟函數(shù)柯里化相比正好相反,擴(kuò)大適用范圍,創(chuàng)建一個應(yīng)用范圍更廣的函數(shù)。作為函數(shù)式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 可以對照另外一篇介紹 JS 柯里化 的文章一起看~ 1. 簡介 柯里化,是固定部分參數(shù),返回一個接受剩余參數(shù)的函數(shù),也稱為部分計(jì)算函數(shù),目的是為了縮...

    zhjx922 評論0 收藏0
  • JS進(jìn)階篇--JS中的柯里(currying)

    摘要:何為化柯里化化來源與數(shù)學(xué)家的名字編程語言也是以他的名字命名。因此柯里化的過程是逐步傳參,逐步縮小函數(shù)的適用范圍,逐步求解的過程。當(dāng)在多次調(diào)用同一個函數(shù),并且傳遞的參數(shù)絕大多數(shù)是相同的,那么該函數(shù)可能是一個很好的柯里化候選。 何為Curry化/柯里化? curry化來源與數(shù)學(xué)家 Haskell Curry的名字 (編程語言 Haskell也是以他的名字命名)。 柯里化通常也稱部分求值,其...

    young.li 評論0 收藏0
  • 掌握J(rèn)avaScript函數(shù)的柯里

    摘要:原文鏈接和都支持函數(shù)的柯里化函數(shù)的柯里化還與的函數(shù)編程有很大的聯(lián)系如果你感興趣的話可以在這些方面多下功夫了解相信收獲一定很多看本篇文章需要知道的一些知識點(diǎn)函數(shù)部分的閉包高階函數(shù)不完全函數(shù)文章后面有對這些知識的簡單解釋大家可以看看什么是柯里化 原文鏈接 Haskell和scala都支持函數(shù)的柯里化,JavaScript函數(shù)的柯里化還與JavaScript的函數(shù)編程有很大的聯(lián)系,如果你感興...

    DTeam 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<