<s id="0oyqk"></s>
  • <option id="0oyqk"><optgroup id="0oyqk"></optgroup></option>
  • <source id="0oyqk"><bdo id="0oyqk"></bdo></source>
  • javascript實現函數柯里化與反柯里化過程解析

     更新時間:2019-10-08 14:01:52   作者:佚名   我要評論(0)

    函數柯里化(黑人問號臉)???Currying(黑人問號臉)???妥妥的中式翻譯既視感;下面來一起看看究竟什么是函數柯里化:
    維基百科的解釋是:把接收多個參

    函數柯里化(黑人問號臉)???Currying(黑人問號臉)???妥妥的中式翻譯既視感;下面來一起看看究竟什么是函數柯里化:

    維基百科的解釋是:把接收多個參數的函數變換成接收一個單一參數(最初函數的第一個參數)的函數,并返回接受剩余的參數而且返回結果的新函數的技術。其由數學家Haskell Brooks Curry提出,并以curry命名。

    概念往往都是干澀且難懂的,讓我們用人話來解釋就是:如果我們不確定這個函數有多少個參數,我們可以先給它傳入一個參數,然后通過JS閉包(如若不懂JS閉包,請先學習閉包知識點再來學習本篇博文https://www.jb51.net/article/171398.htm)來進行返回一個函數,內部函數接收除開第一個參數外的其余參數進行操作并輸出,這個就是函數的柯里化;

    舉個小例子:

    場景(需求):

    眾所周知程序員每天加班的時間還是比較多的,如果我們需要計算一個程序員每天的加班時間,那么我們的第一反應應該是這樣;

    var overtime=0;
    function time(x){
      return overtime+=x;
    }
    
    time(1); //1
    time(2); //3
    time(3); //6

    上面的代碼固然沒有問題,可是需要每天調用都算加一下當天的時間,很麻煩,并且每調用一次函數都要進行一定的操作,如果數據量巨大,有可能會有影響性能的風險,那么有沒有可以偷懶又能解決問題的辦法呢?有的!

    function time(x){
     return function(y){
        return x+y;
      }   
    }
    
    var times=time(0);
    times(3);

    但是上面代碼依然存在問題,在實際開發中很多時候我們的參數是不確定的,上面代碼雖然簡單的實現了柯里化的基本操作,但是對于參數不確定的情況是處理不了的;所以存在著函數參數的局限性;不過我們從上面的代碼中基本可以知道函數柯里化是個啥意思了;就是一個函數調用的時候只允許傳入一個參數,然后通過閉包返回內部函數去處理和接收剩余參數,返回的函數通過閉包的方式記住了time的第一個參數;

    我們再來把代碼改造一下:

    // 首先定義一個變量接收函數
    var overtime = (function() {
    //定義一個數組用來接收參數
     var args = [];
    //這里運用閉包,調用外部函數返回一個內部函數
     return function() {
      //arguments是瀏覽器內置對象,專門用來接收參數
      //如果參數的長度為0即沒有參數的時候
      if(arguments.length === 0) {
        //定義變量用來累加
       var time = 0;
        //循環累加,用i和args的長度進行比較
       for (var i = 0, l = args.length; i < l; i++) {
        //進行累加操作  等價于time=time+args[i]
        time += args[i];
       }
        // 返回累加的結果
       return time;
        //如果arguments對象參數長度不為零,即有參數的時候
      }else {
        //定義的空數組添加arguments參數作為數組項,第一個參數古args作為改變this指向,第二個參數arguments把剩余參數作為數組形式添加至空數組中
       [].push.apply(args, arguments);
      }
     }
    })();
    
    overtime(3.5);  // 第一天
    overtime(4.5);  // 第二天
    overtime(2.1);  // 第三天
    //...
    
    console.log( overtime() );  // 10.1

    代碼經過我們的改造已經實現了功能,但是這不是一個函數柯里化的完整實現,那么我們要怎么完整實現呢?下面我們來介紹一種通用的實現方式:

    通用的實現方式:

    //定義方法currying,先傳入一個參數
    var currying=function(fn){
      //定義空數組裝arguments對象的剩余參數
     var args=[];
      //利用閉包返回一個函數處理剩余參數
     return function (){
        //如果arguments的參數長度為0,即沒有剩余參數
      if(arguments.length===0){
        //執行上面方法
        //這里的this指向下面的s,類似于s(),代表參數長度為0的時候直接調用函數
       return fn.apply(this,args)
      }
      console.log(arguments)
      //如果arguments的參數長度不為0,即還有剩余參數
      //在數組的原型對象上添加數組,apply用來更改this的指向為args
      //將[].slice.call(arguments)的數組添加到原型數組上
      //這里的[].slice.call(arguments)===Array.prototype.slice.call(arguments)實質上就是將arguments對象轉成數組并具有slice功能
    
      Array.prototype.push.apply(args,[].slice.call(arguments))
      //args.push([].slice.call(arguments))
      console.log(args)
      //這里返回的arguments.callee是返回的閉包函數,callee是arguments對象里面的一個屬性,用于返回正被執行的function對象
      return arguments.callee
     }
    }
      //這里調用currying方法并傳入add函數,結果會返回閉包內部函數
     var s=currying(add);
      //調用閉包內部函數,當有參數的時候會將參數逐步添加到args數組中,待沒有參數傳入的時候直接調用
      //調用的時候支持鏈式操作
     s(1)(2)(3)();
    //也可以一次性傳入多個參數
      s(1,2,3);
     console.log(s());

    JS函數柯里化的優點:

    1.可以延遲計算,即如果調用柯里化函數傳入參數是不調用的,會將參數添加到數組中存儲,等到沒有參數傳入的時候進行調用;

    2.參數復用,當在多次調用同一個函數,并且傳遞的參數絕大多數是相同的,那么該函數可能是一個很好的柯里化候選。

    世間萬物相對,有因必有果,當然了,有柯里化必然有反柯里化;

    反柯里化(uncurrying)

    從字面意思上來講就是跟柯里化的意思相反;其實真正的反柯里化的作用是擴大適用范圍,就是說當我們調用某個方法的時候,不需要考慮這個對象自身在設計的過程中有沒有這個方法,只要這個方法適用于它,我們就可以使用;(這里引用的是動態語言中的鴨子類型的思想)

    在學習JS反柯里化之前,我們先學習一下動態語言的鴨子類型思想,以助于我們更好的理解:

    動態語言鴨子類型思想(維基百科解釋):

    在程序設計中,鴨子類型(duck typing)是動態類型的一種風格。

    在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的接口,而是由當前方法和屬性的集合決定。

    這個概念的名字來源于由 James Whitcomb Riley 提出的鴨子測試,“鴨子測試”可以這樣表述:

    當看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。

    理論上的解釋往往干澀難懂,換成人話來說就是:你是你媽媽的兒子/女兒,不管你是否優秀,是否漂亮,只要你是你媽親生的,那么你就是你媽的兒子/女兒;換成鴨子類型就是,只要你會呱呱叫,走起來像鴨子,只要你擁有的行為像鴨子,不管你是不是鴨子,那么你就可以被稱為鴨子;

    在Javascript中有很多鴨子類型的引用,比如我們在對一個變量進行賦值的時候,顯然是不需要考慮變量的類型的,正是因為如此,Javascript才更加的靈活,所以Javascript是一門典型的動態類型語言;

    我們來看一下反柯里化中是怎么引用鴨子類型的:

    //函數原型對象上添加uncurring方法
    Function.prototype.uncurring = function() {
    //改變this的指向  
    //這里的this指向是Array.prototype.push
     var self = this;
      //這里的閉包用來返回內部函數的執行
     return function() {
      //創建一個變量,在數組的原型對象上添加shift上面刪除第一個參數
      //改變數組this的指向為arguments
      var obj = Array.prototype.shift.call(arguments);
      //最后返回執行并給方法改變指向為obj也就是arguments
      // 并傳入arguments作為參數
      return self.apply(obj, arguments);
     };
    };
    
    //數組原型對象上添加uncurrying方法
    var push = Array.prototype.push.uncurring();
    
    //測試一下
    //匿名函數自執行
    (function() {
      //這里的push就是一個函數方法了
      //相當于傳入參數arguments和4兩個參數,但是在上面shift方法中刪除第一個參數,這里的arguments參數被截取了,所以最后實際上只傳入了4
     push(arguments, 4);
     console.log(arguments); //[1, 2, 3, 4]
    //匿名函數自調用并帶入參數1,2,3
    })(1, 2, 3)

    到這里大家可以想一想arguments是一個接收參數的對象,里面是沒有push方法的,那么arguments為什么能調用push方法呢?

    這是因為代碼var push = Array.prototype.push.uncurring();在數組的原型對象的push方法上添加了uncurring方法,然后在執行匿名函數的方法push(arguments, 4);時候實質上是在調用上面的方法在Function的原型對象上添加uncurring方法并返回一個閉包內部函數執行,在執行的過程中因為Array原型對象上的shift方法會把 push(arguments, 4);中的arguments截取,所以其實方法的實際調用是push(4),所以最終的結果才是[1,2,3,4]

    在《JavaScript設計模式與開發實踐》一書中,JS函數的反柯里化的案例是這樣寫的:

    //定義一個對象
    var obj = {
      "length":1,
      "0":1
    }
    //在Function原型對象定義方法uncurrying
    Function.prototype.uncurrying = function() {
      //this指向Array.prototype.push
      var self = this;
      //閉包返回一個內部函數
      return function() {
      // 這里可以拆開理解
      //首先執行apply return 
      //Function.prototype.call(Array.prototype.push[obj,2])
      //然后Array.prototype.push.call(obj,2)
      //call改變指向 obj.push(2)
      //所以最后結果就是 {0: 1, 1: 2, length: 2}
        return Function.prototype.call.apply(self, arguments);
    }
    }
    
    //在
    var push = Array.prototype.push.uncurrying()
    
    push(obj, 2) 
    console.log(obj);
    //{0: 1, 1: 2, length: 2}

    上面的方式不好理解?沒關系,咱們來個好理解的:

    Function.prototype.unCurrying = function () {
      var self = this;
      return function () {
        //[].slice.call(arguments,1)===Array.prototype.push.slice.call(arguments,1)===arguments.slice(1)
    return self.apply(arguments[0], [].slice.call(arguments, 1));
      };
    };
    var push = Array.prototype.push.uncurrying()
    console.log(push);
    push(obj,2) //{0: 1, 1: 2, length: 2}
    console.log(obj);

    分析一下:

    1、首先在Function原型對象上添加uncurrying方法,這樣所有的Function都可以借用;

    2、返回一個閉包內部函數

    3、閉包函數返回的結果中返回的是調用方法,self指向Array.prototype.push,apply方法中第一個參數是更改指向,對應下面push(obj,2)相當于更改指向為obj.push(2)

    4、apply方法中第二個參數的call方法是更改指向為arguments,并且arguments中能使用slice方法,等于arguments.slice(1)

    以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

    您可能感興趣的文章:

    • JavaScript偏函數與柯里化實例詳解
    • JS中精巧的自動柯里化實現方法
    • 淺談JS中的反柯里化( uncurrying)
    • 詳解JS中的柯里化(currying)
    • Javascript閉包與函數柯里化淺析
    • 深入剖析JavaScript中的函數currying柯里化
    • JavaScript函數柯里化詳解
    • 深入解析JavaScript中函數的Currying柯里化

    相關文章

    • javascript實現函數柯里化與反柯里化過程解析

      javascript實現函數柯里化與反柯里化過程解析

      函數柯里化(黑人問號臉)???Currying(黑人問號臉)???妥妥的中式翻譯既視感;下面來一起看看究竟什么是函數柯里化: 維基百科的解釋是:把接收多個參
      2019-10-08
    • Idea2019創建Spingboot Web項目的方法步驟

      Idea2019創建Spingboot Web項目的方法步驟

      1、Idea及Java版本:Idea2019.1 + jdk1.8 2、File > Peoject 3、Spring Initializr: 4、之后會自動下載需要的相關文件 5、src > main > com.xx
      2019-10-08
    • Android 獲取 usb 權限的兩種方法

      Android 獲取 usb 權限的兩種方法

      前言: 最近工作上遇到幾個USB模塊在android平臺上適配使用的情況,所以要用到USB權限獲取問題 ##USB權限獲取有以下2種方式: 一、直接在AndroidManifest.xml
      2019-10-08
    • 通過正則表達式使用ajax檢驗注冊信息功能

      通過正則表達式使用ajax檢驗注冊信息功能

      本期博客內容應該不算多,我們此次的目的是通過正則表達式并利用ajax可以實現動態交互的特點,檢驗注冊的用戶名以及密碼是否合法。 Entity層 該層主要包含一個
      2019-10-08
    • python實現的按要求生成手機號功能示例

      python實現的按要求生成手機號功能示例

      本文實例講述了python實現的按要求生成手機號功能。分享給大家供大家參考,具體如下: 看到一個生成手機號的代碼,于是自己優化了一下,可以支持按要求生成手
      2019-10-08
    • 微信小程序返回箭頭跳轉到指定頁面實例解析

      微信小程序返回箭頭跳轉到指定頁面實例解析

      這篇文章主要介紹了微信小程序返回箭頭跳轉到指定頁面實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以
      2019-10-08
    • vue.js中ref及$refs的使用方法解析

      vue.js中ref及$refs的使用方法解析

      關于ref和$refs的用法及講解,vue.js中文社區( https://cn.vuejs.org/v2/api/#ref )是這么講解的: ref 被用來給元素或子組件注冊引用信息,引用信息將會注
      2019-10-08
    • python3.7 openpyxl 刪除指定一列或者一行的代碼

      python3.7 openpyxl 刪除指定一列或者一行的代碼

      python3.7 openpyxl 刪除指定一列或者一行 # encoding:utf-8 import pandas as pd import openpyxl xl = pd.read_excel(r"E:\55\CRM經營分析表-10001741-15
      2019-10-08
    • 簡單實現節流函數和防抖函數過程解析

      簡單實現節流函數和防抖函數過程解析

      在日常開發中有很多場景我們都需要用到節流函數和防抖函數,比如:實現輸入框的模糊查詢因為需要輪詢ajax,影響瀏覽器性能,所以需要用到節流函數;實現手機號
      2019-10-08
    • jQuery實現提交表單時不提交隱藏div中input的方法

      jQuery實現提交表單時不提交隱藏div中input的方法

      本文實例講述了jQuery實現提交表單時不提交隱藏div中input的方法。分享給大家供大家參考,具體如下: 今天做一個功能 想在下拉列表中根據下拉的選項隱藏對應的
      2019-10-08

    最新評論

    种子磁力搜索器