91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript 中怎么實現柯里化函數

發布時間:2021-07-14 16:21:25 來源:億速云 閱讀:142 作者:Leah 欄目:web開發

本篇文章為大家展示了JavaScript 中怎么實現柯里化函數,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

階段1

現在有一個加法函數:

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

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

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

現在先不看怎么對原函數執行柯里化,而是根據這個調用方式重新寫一個函數。代碼可能是這樣的:

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

階段2

假如現在想要升級一下,不止可以接受三個參數。可以使用 arguments,或者使用展開運算符來處理傳入的參數。

但是有一個衍生的問題。因為之前每次只能傳遞一個,總共只能傳遞三個,才保證了調用三次之后參數個數剛好足夠,函數才能執行。

既然我們打算修改為可以接受任意個數的參數,那么就要規定一個終點。比如說,可以規定為當不再傳入參數的時候,就執行函數。

下面是使用 arguments 的實現。

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

階段3

這時可以發現,上面的整個函數里,與函數具體功能(在這里就是執行加法)有關的,就只是當沒有傳遞參數時的部分,其他部分都是在實現怎樣多次接收參數。

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

把上面的修改一下,實現一個通用柯里化函數,并把一個階乘函數柯里化:

function currying(fn) {    let args_arr = []    let closure =  function (...args) {        if (args.length > 0) {            args_arr = args_arr.concat(args)            return closure       }        // 沒有新的參數,執行函數        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

上面的代碼里,對于函數執行時機的判斷,是根據是否有參數傳入。但是更多時候,更合理的依據是原函數可以接受的參數的總數。

函數名的 length 屬性就是該函數接受的參數個數。比如:

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) {    // 先把參數加進去    args_arr = args_arr.concat(args)    // 如果參數沒滿,返回閉包等待下一次調用    if (args_arr.length < max_length) return closure    // 傳遞完成,執行    return fn(...args_arr)  }  return closure}function add(x, y, z) {  return x + y + z}curriedAdd = currying(add)console.log(curriedAdd(1, 2)(3))復制代碼

Lodash 中的柯里化

讓我們先看一下 lodash.js 的文檔,看看一個真正的 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. 在多個函數調用中逐步收集參數,不用在一個函數調用中一次收集。

  2. 當收集到足夠的參數時,返回函數執行結果。

為了更好的理解它,我在網上找了多個實現示例。然而,我希望是有一個非常簡單的教程從一個基本的例子開始,就像下面這個一樣,而不是直接從最終的實現開始。

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 函數是所有的起點。基本上,這個函數的作用就是一個“參數收集器”。每次調用該函數時,它都會返回一個自身的綁定函數(fb),并且將該函數提供的“參數”綁定到返回函數上。該“參數”將位于之后調用返回的綁定函數時提供的任何參數之前。因此,每個調用中傳的參數將被逐漸收集到一個數組當中。

當然,就像 curry 函數一樣,我們不必一直收集下去。現在我們可以先寫死一個終止點。

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;  }}

為了讓它表現得和 curry 方法一樣,需要解決兩個問題:

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

  2. 變量 numOfRequiredArguments 不應該是寫死的,它應該是目標函數所期望的參數個數。

幸運的是,JavaScript函數確實帶有一個名為 “length” 的屬性,它指定了函數所期望的參數個數。因此,我們就可以使用這個屬性來確定所需要的參數個數,而不用再寫死了。那么第二個問題就解決了。

那第一個問題呢:保持對目標函數的引用?

網上有幾個例子可以解決這個問題。它們之間雖然略有不同,但是有著相同的思路:除去存儲參數以外,我們還需要在某處存儲對于目標函數的引用。

這里我把它們分為兩種不同的方法,它們之間或多或少都有相似之處,理解它們能夠幫助我們更好地理解背后的邏輯。順便說一句,這里我將這個函數叫做 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 函數的作用是:它接收目標函數作為參數,然后返回‘參數收集器’函數,與上例中 fn 函數作用相同。唯一的不同點在于,當收集的參數數量與目標函數所必需的參數數量相等時,它將把收集到的參數通過 apply 方法給到該目標函數,并返回計算的結果。這個方法通過將其存儲在 magician 創建的閉包當中來解決第一個問題(引用目標函數)。

方法2

這個方法更進一步,由于參數收集器函數只是一個普通函數,那為什么不使用 magician 函數本身作為參數收集器呢?

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中的一個不同。因為 magician 接收目標函數作為它的第一個參數,因此收集到的參數將始終包含該函數作為 arguments[0]。這就導致,我們在檢查有效參數的總數時,需要減去第一個參數。

順便說一句,因為目標函數是遞歸地傳遞給 magician 函數的,所以我們可以通過傳入第一個參數顯式地引用目標函數,以代替使用閉包來存儲目標函數的引用。

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

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

上面是一個 curry 函數,它返回“參數收集器”,該收集器只收集一次參數,并返回綁定的目標函數。

更進一步

上面的‘magician’函數仍然沒有lodash.js中的‘curry’函數那樣神奇。lodash的curry允許使用‘_’作為輸入參數的占位符。

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

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

這個功能可以通過創建另外一個閉包來完成:

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]); //簡單起見,使用es6  }}

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

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

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

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

上述內容就是JavaScript 中怎么實現柯里化函數,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

金山区| 祥云县| 思南县| 阿巴嘎旗| 临桂县| 湟源县| 海阳市| 邛崃市| 兴文县| 荣成市| 顺昌县| 红桥区| 紫阳县| 明溪县| 读书| 通城县| 滨海县| 资讯| 离岛区| 睢宁县| 丽江市| 彰化县| 罗平县| 禄劝| 建昌县| 社旗县| 东乌| 抚松县| 吴桥县| 绥化市| 山阴县| 杂多县| 东乡族自治县| 永安市| 武平县| 孟州市| 安顺市| 永善县| 海阳市| 蛟河市| 洛扎县|