国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript 函數(shù)式編程技巧 - 柯里化

edgardeng / 809人閱讀

摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。在一些函數(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ù)用

    提前返回

    延遲計算/運行

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

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

一個通用實現(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  // 第一遍運行l(wèi)ength是函數(shù)fn一共需要的參數(shù)個數(shù),以后是剩余所需要的參數(shù)個數(shù)
  return function(...rest) {
    return rest.length >= length    // 檢查是否傳入了fn所需足夠的參數(shù)
        ");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 歲了, 我喜歡吃 冬瓜

如此實現(xiàn)一個高階的柯里化函數(shù),使得柯里化一個函數(shù)的時候可以不用嵌套的currying,當(dāng)然是因為把嵌套的地方放到了curryingHelper里面進(jìn)行了...-。-

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

盡管柯里化函數(shù)已經(jīng)很牛了,但是它也讓你必須花費點小心思在你所定義函數(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) {              // 是否還有沒有填補的hole
        holeLength--
        _args.splice(_holes.shift(), 0, arg)           // 在參數(shù)列表指定hole的地方插入當(dāng)前參數(shù)
      } else {
        _args.push(arg)            // 不需要填補hole,直接添加到參數(shù)列表里面
      }
    }
    
    return _args.length >= length                          // 遞歸的進(jìn)行柯里化
        ");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í)行,來實現(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ù)組的每一項平方
map(square, [6, 7, 8, 9, 10]);
map(dubble, [1, 2, 3, 4, 5]);        // 數(shù)組的每一項加倍
map(dubble, [6, 7, 8, 9, 10]);

同一規(guī)則重復(fù)使用,帶來代碼的重復(fù)性,因此可以使用上面的通用柯里化實現(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()               // 最后計算輸出: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()               // 最后計算輸出: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ù), 這符合柯里化特點。

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ù)綁定到實參傳入的上下文,延遲執(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è)計

    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://specialneedsforspecialkids.com/yun/6771.html

相關(guān)文章

  • JavaScript函數(shù)編程,真香之組合(一)

    摘要:組合的概念是非常直觀的,并不是函數(shù)式編程獨有的,在我們生活中或者前端開發(fā)中處處可見。其實我們函數(shù)式編程里面的組合也是類似,函數(shù)組合就是一種將已被分解的簡單任務(wù)組織成復(fù)雜的整體過程。在函數(shù)式編程的世界中,有這樣一種很流行的編程風(fēng)格。 JavaScript函數(shù)式編程,真香之認(rèn)識函數(shù)式編程(一) 該系列文章不是針對前端新手,需要有一定的編程經(jīng)驗,而且了解 JavaScript 里面作用域,閉...

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

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

    zhjx922 評論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.16 - 淺入淺出 JavaScript 函數(shù)編程

    摘要:函數(shù)式編程,一看這個詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...

    csRyan 評論0 收藏0
  • 高級函數(shù)技巧-函數(shù)柯里

    摘要:如果你對函數(shù)式編程有一定了解,函數(shù)柯里化是不可或缺的,利用函數(shù)柯里化,可以在開發(fā)中非常優(yōu)雅的處理復(fù)雜邏輯。同樣先看簡單版本的方法,以方法為例,代碼來自高級程序設(shè)計加強(qiáng)版實現(xiàn)上面函數(shù),可以換成任何其他函數(shù),經(jīng)過函數(shù)處理,都可以轉(zhuǎn)成柯里化函數(shù)。 我們經(jīng)常說在Javascript語言中,函數(shù)是一等公民,它們本質(zhì)上是十分簡單和過程化的。可以利用函數(shù),進(jìn)行一些簡單的數(shù)據(jù)處理,return 結(jié)果,...

    shixinzhang 評論0 收藏0
  • JavaScript函數(shù)編程(一)

    摘要:所以下面介紹一些函數(shù)式編程的知識和概念。函數(shù)式編程的一個明顯的好處就是這種聲明式的代碼,對于無副作用的純函數(shù),我們完全可以不考慮函數(shù)內(nèi)部是如何實現(xiàn)的,專注于編寫業(yè)務(wù)代碼。我會在下一篇文章中介紹函數(shù)式編程的更加高階一些的知識,例如等等概念。 一、引言 說到函數(shù)式編程,大家可能第一印象都是學(xué)院派的那些晦澀難懂的代碼,充滿了一大堆抽象的不知所云的符號,似乎只有大學(xué)里的計算機(jī)教授才會使用這些東...

    Shihira 評論0 收藏0

發(fā)表評論

0條評論

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