柯里化與反柯里化

来源:https://www.cnblogs.com/walls/archive/2018/07/24/9357818.html
-Advertisement-
Play Games

前言 柯里化,可以理解為 提前接收部分參數,延遲執行,不立即輸出結果,而是返回一個接受剩餘參數的函數 。因為這樣的特性,也被稱為部分計算函數。柯里化,是一個逐步接收參數的過程。在接下來的剖析中,你會深刻體會到這一點。 反柯里化,是一個 泛型化 的過程。它使得被反柯里化的函數,可以 接收更多參數 。目 ...


前言

柯里化,可以理解為提前接收部分參數,延遲執行,不立即輸出結果,而是返回一個接受剩餘參數的函數。因為這樣的特性,也被稱為部分計算函數。柯里化,是一個逐步接收參數的過程。在接下來的剖析中,你會深刻體會到這一點。

反柯里化,是一個泛型化的過程。它使得被反柯里化的函數,可以接收更多參數。目的是創建一個更普適性的函數,可以被不同的對象使用。有鳩占鵲巢的效果。

一、柯里化

1.1 例子

實現 add(1)(2, 3)(4)() = 10 的效果

依題意,有兩個關鍵點要註意:

  • 傳入參數時,代碼不執行輸出結果,而是先記憶起來
  • 當傳入空的參數時,代表可以進行真正的運算

完整代碼如下:

function currying(fn){
    var allArgs = [];

    return function next(){
        var args = [].slice.call(arguments);

        if(args.length > 0){
            allArgs = allArgs.concat(args);
            return next;
        }else{
            return fn.apply(null, allArgs);
        }
    } 
}
var add = currying(function(){
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
});

1.2 記憶傳入參數

由於是延遲計算結果,所以要對參數進行記憶。
這裡的實現方式是採用閉包。

function currying(fn){
    var allArgs = [];

    return function next(){
        var args = [].slice.call(arguments);

        if(args.length > 0){
            allArgs = allArgs.concat(args);
            return next;
        }
    } 
}

當執行var add = currying(...)時,add變數已經指向了next方法。此時,allArgsnext方法內部有引用到,所以不能被GC回收。也就是說,allArgs在該賦值語句執行後,一直存在,形成了閉包。
依靠這個特性,只要把接收的參數,不斷放入allArgs變數進行存儲即可。
所以,當arguments.length > 0時,就可以將接收的新參數,放到allArgs中。
最後返回next函數指針,形成鏈式調用。

1.3 判斷觸發函數執行條件

題意是,空參數時,輸出結果。所以,只要判斷arguments.length == 0即可執行。
另外,由於計算結果的方法,是作為參數傳入currying函數,所以要利用apply進行執行。
綜合上述思考,就可以得到以下完整的柯里化函數。

function currying(fn){
    var allArgs = []; // 用來接收參數

    return function next(){
        var args = [].slice.call(arguments);

        // 判斷是否執行計算
        if(args.length > 0){
            allArgs = allArgs.concat(args); // 收集傳入的參數,進行緩存
            return next;
        }else{
            return fn.apply(null, allArgs); // 符合執行條件,執行計算
        }
    } 
}

1.4 總結

柯里化,在這個例子中可以看出很明顯的行為規範:

  • 逐步接收參數,並緩存供後期計算使用
  • 不立即計算,延後執行
  • 符合計算的條件,將緩存的參數,統一傳遞給執行方法

1.5 擴展

實現 add(1)(2, 3)(4)(5) = 15 的效果。
很多人這裡就犯嘀咕了:我怎麼知道執行的時機?
其實,這裡有個忍者技藝:valueOftoString
js在獲取當前變數值的時候,會根據語境,隱式調用valueOftoString方法進行獲取需要的值。
那麼,實現起來就很簡單了。

function currying(fn){
    var allArgs = [];

    function next(){
        var args = [].slice.call(arguments);
        allArgs = allArgs.concat(args);
        return next;
    }
    // 字元類型
    next.toString = function(){
        return fn.apply(null, allArgs);
    };
    // 數值類型
    next.valueOf = function(){
        return fn.apply(null, allArgs);
    }

    return next;
}
var add = currying(function(){
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
});

二、反柯里化

2.1 例子

有以下輕提示類。現在想要單獨使用其show方法,輸出新對象obj中的內容。

// 輕提示
function Toast(option){
  this.prompt = '';
}
Toast.prototype = {
  constructor: Toast,
  // 輸出提示
  show: function(){
    console.log(this.prompt);
  }
};

// 新對象
var obj = {
    prompt: '新對象'
};

用反柯里化的方式,可以這麼做

function unCurrying(fn){
    return function(){
        var args = [].slice.call(arguments);
        var that = args.shift();
        return fn.apply(that, args);
    }
}

var objShow = unCurrying(Toast.prototype.show);

objShow(obj); // 輸出"新對象"

2.2 反柯里化的行為

  • 非我之物,為我所用
  • 增加被反柯里化方法接收的參數

在上面的例子中,Toast.prototype.show方法,本來是Toast類的私有方法。跟新對象obj沒有半毛錢關係。
經過反柯里化後,卻可以為obj對象所用。
為什麼能被obj所用,是因為內部將Toast.prototype.show的上下文重新定義為obj。也就是用apply改變了this指向。
而實現這一步驟的過程,就需要增加反柯里化後的objShow方法參數。

2.3 另一種反柯里化的實現

Function.prototype.unCurrying = function(){
    var self = this;
    return function(){
        return Function.prototype.call.apply(self, arguments);
    }
}

// 使用
var objShow = Toast.prototype.show.unCurrying();
objShow(obj);

這裡的難點,在於理解Function.prototype.call.apply(self, arguments);
可以分拆為兩步:

1) Function.prototype.call.apply(...)的解析

可以看成是callFunction.apply(...)。這樣,就清晰很多。
callFunctionthis指針,被apply修改為self
然後執行callFunction -> callFunction(arguments)

2) callFunction(arguments)的解析

call方法,第一個參數,是用來指定this的。所以callFunction(arguments) -> callFunction(arguments[0], arguments[1-n])
由此可以得出,反柯里化後,第一個參數,是用來指定this指向的。

3)為什麼要用apply(self, arguments)
如果使用apply(null, arguments),因為null對象沒有call方法,會報錯。

三、實戰

3.1 判斷變數類型(反柯里化)

var fn = function(){};
var val = 1;

if(Object.prototype.toString.call(fn) == '[object Function]'){
    console.log(`${fn} is function.`);
}

if(Object.prototype.toString.call(val) == '[object Number]'){
    console.log(`${val} is number.`);
}

上述代碼,用反柯里化,可以這麼寫:

var fn = function(){};
var val = 1;
var toString = Object.prototype.toString.unCurrying();

if(toString(fn) == '[object Function]'){
    console.log(`${fn} is function.`);
}

if(toString(val) == '[object Number]'){
    console.log(`${val} is number.`);
}

3.2 監聽事件(柯里化)


function nodeListen(node, eventName){
    return function(fn){
        node.addEventListener(eventName, function(){
            fn.apply(this, Array.prototype.slice.call(arguments));
        }, false);
    }
}

var bodyClickListen = nodeListen(document.body, 'click');
bodyClickListen(function(){
    console.log('first listen');
});

bodyClickListen(function(){
    console.log('second listen');
});

使用柯里化,優化監聽DOM節點事件。addEventListener三個參數不用每次都寫。

後記

其實,反柯里化和泛型方法一樣,只是理念上有一些不同而已。理解這種思維即可。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Android四大組件 1、Activity a、Activity是指與用戶交互的界面。 b、一個Activity通常就是一個單獨的屏幕(視窗)。 c、Activity之間通過Intent傳遞數據。 2、service a、service在後臺運行,沒有圖形界面。 b、service的啟動方式有兩種 ...
  • 概述 有的時候需要在現有的項目上面開發一個新的項目,如果新建工程的話,就比較麻煩了,所以一般是直接現有的工程上面直接修改名字步驟如下: 1.修改工程名字 在這裡修改完之後,會彈出一個對話框,點擊Rename 2.修改Scheme名稱 點擊Xcode上面的工具欄Product Secheme Edit ...
  • 前言 在保證代碼沒有功能問題,完成業務開發之餘,有追求的程式員還要追求代碼的規範、可維護性。 今天,以“成為優秀的程式員”為目標的拭心將和大家一起精益求精,學習使用 Lint 優化我們的代碼。 什麼是 Lint Lint 是Android Studio 提供的 代碼掃描分析工具,它可以幫助我們發現代 ...
  • 導入框架CaptiveNetwork 獲取當前連接的wifi信息 ...
  • 在Android自定義一個類繼承集成Application後,併在AndroidManifest.xml裡面配置了application的name屬性為該類名稱後報錯: Unable to instantiate activity ComponentInfo ClassNotFoundExcepti ...
  • 說起二維碼掃描,估計很多人用的是 "zxing" 吧。 然而 zxing 雖然好用,但是卻有一些坑。 這邊分析一下自己實際項目遇到的一個坑。 什麼坑呢? 下麵舉個慄子你就懂了。 這邊生成二維碼使用的是網路上的一個網站 "聯圖" 以百度為例,正常情況生成的二維碼如下: 這種情況下用 zxing 分分鐘 ...
  • 一,效果圖。 二,代碼。 ...
  • getParamValue("id"); //http://localhost:2426/TransactionNotes.aspx?id=100 //返回值是100; // 根據參數名稱獲取參數值 function getParamValue(name) { var paramsArray = g... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...