真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JavaScript中怎么實(shí)現(xiàn)柯里化函數(shù)

本篇文章為大家展示了JavaScript 中怎么實(shí)現(xiàn)柯里化函數(shù),內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

創(chuàng)新互聯(lián)公司主要從事成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)保山,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792

階段1

現(xiàn)在有一個(gè)加法函數(shù):

function add(x, y, z) {    return x + y + z}

調(diào)用方式是 add(1, 2, 3)。

如果執(zhí)行柯里化,變成了 curriedAdd(),從效果來說,大致就是變成 curriedAdd(1)(2)(3) 這樣子。

現(xiàn)在先不看怎么對(duì)原函數(shù)執(zhí)行柯里化,而是根據(jù)這個(gè)調(diào)用方式重新寫一個(gè)函數(shù)。代碼可能是這樣的:

function curriedAdd1(x) {    return function (y) {        return function (z) {            return x + y + z       }    }}

階段2

假如現(xiàn)在想要升級(jí)一下,不止可以接受三個(gè)參數(shù)。可以使用 arguments,或者使用展開運(yùn)算符來處理傳入的參數(shù)。

但是有一個(gè)衍生的問題。因?yàn)橹懊看沃荒軅鬟f一個(gè),總共只能傳遞三個(gè),才保證了調(diào)用三次之后參數(shù)個(gè)數(shù)剛好足夠,函數(shù)才能執(zhí)行。

既然我們打算修改為可以接受任意個(gè)數(shù)的參數(shù),那么就要規(guī)定一個(gè)終點(diǎn)。比如說,可以規(guī)定為當(dāng)不再傳入?yún)?shù)的時(shí)候,就執(zhí)行函數(shù)。

下面是使用 arguments 的實(shí)現(xiàn)。

function getCurriedAdd() {    // 在外部維護(hù)一個(gè)數(shù)組保存?zhèn)鬟f的變量    let args_arr = []    // 返回一個(gè)閉包    let closure = function () {       // 本次調(diào)用傳入的參數(shù)        let args = Array.prototype.slice.call(arguments)        // 如果傳進(jìn)了新的參數(shù)       if (args.length > 0) {            // 保存參數(shù)           args_arr = args_arr.concat(args)            // 再次返回閉包,等待下次調(diào)用            // 也可以 return arguments.callee            return closure          }    // 沒有傳遞參數(shù),執(zhí)行累加    return args_arr.reduce((total, current) => total + current)  }  return closure}curriedAdd = getCurriedAdd()curriedAdd(1)(2)(3)(4)()復(fù)制代碼

階段3

這時(shí)可以發(fā)現(xiàn),上面的整個(gè)函數(shù)里,與函數(shù)具體功能(在這里就是執(zhí)行加法)有關(guān)的,就只是當(dāng)沒有傳遞參數(shù)時(shí)的部分,其他部分都是在實(shí)現(xiàn)怎樣多次接收參數(shù)。

那么,只要讓 getCurriedAdd 接受一個(gè)函數(shù)作為參數(shù),把沒有傳遞參數(shù)時(shí)的那一行代碼替換一下,就可以實(shí)現(xiàn)一個(gè)通用的柯里化函數(shù)了。

把上面的修改一下,實(shí)現(xiàn)一個(gè)通用柯里化函數(shù),并把一個(gè)階乘函數(shù)柯里化:

function currying(fn) {    let args_arr = []    let closure =  function (...args) {        if (args.length > 0) {            args_arr = args_arr.concat(args)            return closure       }        // 沒有新的參數(shù),執(zhí)行函數(shù)        return fn(...args_arr)   }    return closure}function multiply(...args) {   return args.reduce((total, current) => total * current)}curriedMultiply = currying(multiply)console.log(curriedMultiply(2)(3, 4)()

階段4

上面的代碼里,對(duì)于函數(shù)執(zhí)行時(shí)機(jī)的判斷,是根據(jù)是否有參數(shù)傳入。但是更多時(shí)候,更合理的依據(jù)是原函數(shù)可以接受的參數(shù)的總數(shù)。

函數(shù)名的 length 屬性就是該函數(shù)接受的參數(shù)個(gè)數(shù)。比如:

function test1(a, b) {}function test2(...args){}console.log(test1.length) // 2console.log(test2.length) // 0

改寫一下:

function currying(fn) {    let args_arr = [],       max_length = fn.length  let closure = function (...args) {    // 先把參數(shù)加進(jìn)去    args_arr = args_arr.concat(args)    // 如果參數(shù)沒滿,返回閉包等待下一次調(diào)用    if (args_arr.length < max_length) return closure    // 傳遞完成,執(zhí)行    return fn(...args_arr)  }  return closure}function add(x, y, z) {  return x + y + z}curriedAdd = currying(add)console.log(curriedAdd(1, 2)(3))復(fù)制代碼

Lodash 中的柯里化

讓我們先看一下 lodash.js 的文檔,看看一個(gè)真正的 curry 方法到底是做什么的。

var abc = function(a, b, c) { return [a, b, c];};var curried = _.curry(abc);curried(1)(2)(3); // => [1, 2, 3]curried(1, 2)(3); // => [1, 2, 3]curried(1, 2, 3); // => [1, 2, 3]// Curried with placeholders.curried(1)(_, 3)(2); // => [1, 2, 3]

在我理解看來,curry 能夠讓我們:

  1. 在多個(gè)函數(shù)調(diào)用中逐步收集參數(shù),不用在一個(gè)函數(shù)調(diào)用中一次收集。

  2. 當(dāng)收集到足夠的參數(shù)時(shí),返回函數(shù)執(zhí)行結(jié)果。

為了更好的理解它,我在網(wǎng)上找了多個(gè)實(shí)現(xiàn)示例。然而,我希望是有一個(gè)非常簡(jiǎn)單的教程從一個(gè)基本的例子開始,就像下面這個(gè)一樣,而不是直接從最終的實(shí)現(xiàn)開始。

var fn = function() {  console.log(arguments);  return fn.bind(null, ...arguments);  // 如果沒有es6的話我們可以這樣寫:  // return Function.prototype.bind.apply(fn, [null].concat(  //   Array.prototype.slice.call(arguments)  // ));}fb = fn(1); //[1]fb = fb(2); //[1, 2]fb = fb(3); //[1, 2, 3]fb = fb(4); //[1, 2, 3, 4]

理解 fn 函數(shù)是所有的起點(diǎn)。基本上,這個(gè)函數(shù)的作用就是一個(gè)“參數(shù)收集器”。每次調(diào)用該函數(shù)時(shí),它都會(huì)返回一個(gè)自身的綁定函數(shù)(fb),并且將該函數(shù)提供的“參數(shù)”綁定到返回函數(shù)上。該“參數(shù)”將位于之后調(diào)用返回的綁定函數(shù)時(shí)提供的任何參數(shù)之前。因此,每個(gè)調(diào)用中傳的參數(shù)將被逐漸收集到一個(gè)數(shù)組當(dāng)中。

當(dāng)然,就像 curry 函數(shù)一樣,我們不必一直收集下去?,F(xiàn)在我們可以先寫死一個(gè)終止點(diǎn)。

var numOfRequiredArguments = 5;var fn = function() {  if (arguments.length < numOfRequiredArguments) {    return fn.bind(null, ...arguments);  } else {    console.log('we already collect 5 arguments: ', [...arguments]);    return null;  }}

為了讓它表現(xiàn)得和 curry 方法一樣,需要解決兩個(gè)問題:

  1. 我們希望將收集到的參數(shù)傳遞給需要它們的目標(biāo)函數(shù),而不是通過將它們傳遞給 console.log 在最后打印出來。

  2. 變量 numOfRequiredArguments 不應(yīng)該是寫死的,它應(yīng)該是目標(biāo)函數(shù)所期望的參數(shù)個(gè)數(shù)。

幸運(yùn)的是,JavaScript函數(shù)確實(shí)帶有一個(gè)名為 “l(fā)ength” 的屬性,它指定了函數(shù)所期望的參數(shù)個(gè)數(shù)。因此,我們就可以使用這個(gè)屬性來確定所需要的參數(shù)個(gè)數(shù),而不用再寫死了。那么第二個(gè)問題就解決了。

那第一個(gè)問題呢:保持對(duì)目標(biāo)函數(shù)的引用?

網(wǎng)上有幾個(gè)例子可以解決這個(gè)問題。它們之間雖然略有不同,但是有著相同的思路:除去存儲(chǔ)參數(shù)以外,我們還需要在某處存儲(chǔ)對(duì)于目標(biāo)函數(shù)的引用。

這里我把它們分為兩種不同的方法,它們之間或多或少都有相似之處,理解它們能夠幫助我們更好地理解背后的邏輯。順便說一句,這里我將這個(gè)函數(shù)叫做 magician,以代替 curry。

方法1

function magician(targetfn) {  var numOfArgs = targetfn.length;  return function fn() {    if (arguments.length < numOfArgs) {      return fn.bind(null, ...arguments);    } else {      return targetfn.apply(null, arguments);    }  }}

magician 函數(shù)的作用是:它接收目標(biāo)函數(shù)作為參數(shù),然后返回‘參數(shù)收集器’函數(shù),與上例中 fn 函數(shù)作用相同。唯一的不同點(diǎn)在于,當(dāng)收集的參數(shù)數(shù)量與目標(biāo)函數(shù)所必需的參數(shù)數(shù)量相等時(shí),它將把收集到的參數(shù)通過 apply 方法給到該目標(biāo)函數(shù),并返回計(jì)算的結(jié)果。這個(gè)方法通過將其存儲(chǔ)在 magician 創(chuàng)建的閉包當(dāng)中來解決第一個(gè)問題(引用目標(biāo)函數(shù))。

方法2

這個(gè)方法更進(jìn)一步,由于參數(shù)收集器函數(shù)只是一個(gè)普通函數(shù),那為什么不使用 magician 函數(shù)本身作為參數(shù)收集器呢?

function magician (targetfn) {  var numOfArgs = targetfn.length;  if (arguments.length - 1 < numOfArgs) {    return magician.bind(null, ...arguments);  } else {    return targetfn.apply(null, Array.prototype.slice.call(arguments, 1));  }}

注意方法2中的一個(gè)不同。因?yàn)?magician 接收目標(biāo)函數(shù)作為它的第一個(gè)參數(shù),因此收集到的參數(shù)將始終包含該函數(shù)作為 arguments[0]。這就導(dǎo)致,我們?cè)跈z查有效參數(shù)的總數(shù)時(shí),需要減去第一個(gè)參數(shù)。

順便說一句,因?yàn)槟繕?biāo)函數(shù)是遞歸地傳遞給 magician 函數(shù)的,所以我們可以通過傳入第一個(gè)參數(shù)顯式地引用目標(biāo)函數(shù),以代替使用閉包來存儲(chǔ)目標(biāo)函數(shù)的引用。

正如你所見,Eric Elliott 上面使用到的 “curry” 函數(shù)和方法1功能相似,但實(shí)際上它是一個(gè)偏函數(shù)(這又是另外一說了)。

const curry = fn => (…args) => fn.bind(null, …args);

上面是一個(gè) curry 函數(shù),它返回“參數(shù)收集器”,該收集器只收集一次參數(shù),并返回綁定的目標(biāo)函數(shù)。

更進(jìn)一步

上面的‘magician’函數(shù)仍然沒有l(wèi)odash.js中的‘curry’函數(shù)那樣神奇。lodash的curry允許使用‘_’作為輸入?yún)?shù)的占位符。

curried(1)(_, 3)(2); // => [1, 2, 3], 注意占位符 '_'

為了實(shí)現(xiàn)占位符功能,有一個(gè)隱含的需求:我們需要知道哪些參數(shù)被預(yù)設(shè)給了綁定函數(shù),以及哪些是在調(diào)用函數(shù)時(shí)顯示提供的附加參數(shù)(這里我們稱之為added參數(shù))。

這個(gè)功能可以通過創(chuàng)建另外一個(gè)閉包來完成:

function fn2() {  var preset = Array.prototype.slice.call(arguments);  /*    原先是這樣:    return fn.bind(null, ...arguments);  */  return function helper() {    var added = Array.prototype.slice.call(arguments);    return fn2.apply(null, [...preset, ...added]); //簡(jiǎn)單起見,使用es6  }}

上面的 fn2 幾乎和 fn 一樣,功能就像‘參數(shù)收集器’一樣。然而,fn2 不是直接返回綁定函數(shù),而是返回一個(gè)中間輔助函數(shù) helper。helper 函數(shù)是未綁定的,因此它可以用來分離預(yù)設(shè)的參數(shù)和后來提供的參數(shù)。

當(dāng)然,我們需要在組合時(shí)進(jìn)行一些修改,而不是通過 [...preset, ...added] 將預(yù)設(shè)的參數(shù)和后來提供的參數(shù)合并起來。我們需要在preset參數(shù)中找到占位符的位置,并用有效的added參數(shù)替換它。我沒有看lodash是如何實(shí)現(xiàn)它的,但下面是一個(gè)完成類似功能的簡(jiǎn)單實(shí)現(xiàn)。

// 定義占位符var _ = '_';function magician3 (targetfn, ...preset) {  var numOfArgs = targetfn.length;  var nextPos = 0; // 下一個(gè)有效輸入位置的索引,可以是'_',也可以是preset的結(jié)尾  // 查看是否有足夠的有效參數(shù)  if (preset.filter(arg=> arg !== _).length === numOfArgs) {    return targetfn.apply(null, preset);  } else {    // 返回'helper'函數(shù)    return function (...added) {      // 循環(huán)并將added參數(shù)添加到preset參數(shù)      while(added.length > 0) {        var a = added.shift();        // 獲取下一個(gè)占位符的位置,可以是'_'也可以是preset的末尾        while (preset[nextPos] !== _ && nextPos < preset.length) {          nextPos++        }        // 更新preset        preset[nextPos] = a;        nextPos++;      }      // 綁定更新后的preset      return magician3.call(null, targetfn, ...preset);    }  }}

第15到24行是用于將added參數(shù)放入preset數(shù)組中正確位置的邏輯:無論是占位符或是preset的結(jié)尾。該位置被標(biāo)記為 nextPos 并初始化為索引0。

上述內(nèi)容就是JavaScript 中怎么實(shí)現(xiàn)柯里化函數(shù),你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


分享標(biāo)題:JavaScript中怎么實(shí)現(xiàn)柯里化函數(shù)
網(wǎng)頁網(wǎng)址:http://weahome.cn/article/gjschh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部