【SSD系列】都說柯里化,反柯里化 , 不會不知道吧

這是我參與8月更文挑戰的第4天,活動詳情查看:8月更文挑戰git

前言

柯里化偏函數都是函數式編程裏面重要的概念,咱們今天來來點不同的 反柯里化github

不過既然是反柯里化,就先了解一下其姊妹 柯里化和偏函數npm

柯里化 和偏函數

1.1 柯里化

維基百科上說道:編程

柯里化,英語:Currying,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。數組

這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,儘管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。markdown

仍是看個例子吧,比較直觀:app

function sum(a, b, c) {
  return a + b + c;
}

function currySum(a){
    return function (b){
        return function (c){
            return a + b + c;
        }
    }
}
sum(1,2,3) // 6
currySum(1)(2)(3) // 6

複製代碼

簡單點說就兩點:函數式編程

  1. 多段傳參函數

  2. 返回的函數,知足條件就是執行函數,返回結果oop

1.1.1 難點

通用的柯里化函數難點就是在於如何知道函數參數的長度, 經常使用的手段是:

  1. Function.prototype.length屬性獲取參數長度

    缺點嘛,rest參數是不計算在長度裏面的

    下面的代碼,獲取的length是2就是最好的事實。

    function log(msg1, msg2, ...other){
        console.log(msg1, msg2, ...other);
    }
    
    console.log(log.length); // 2
    複製代碼
  2. 柯里化的時候,顯示的傳入參數的長度

  3. 固然能夠綜合二者,一個函數默認值就ok了

    function curry(fn, length = fn.length){
        // ....
    }
    複製代碼

1.1.2 佔位符


柯里化後來多個一個佔位符的概念,啥意思,就是,這個參數我先不傳,後面再傳入。

看個lodash官方的例子。

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

至於實現嘛,高級版本lodash.curry,平民版本 JavaScript專題之函數柯里化

1.2 偏函數

和柯里化有類似之處,簡單理解參數兩次傳遞

  1. 第一次固定部分參數
  2. 第二次傳入剩餘參數

看個underscore官方的例子,這個例子也 出現了佔位符,不過不影響理解。

var subtract = function(a, b) { return b - a; };
sub5 = _.partial(subtract, 5);
sub5(20);
=> 15

// Using a placeholder
subFrom20 = _.partial(subtract, _, 20);
subFrom20(5);
=> 15
複製代碼

至於實現, 高級版本 lodash.partial 以及underscore.partial, 貧民版本JavaScript專題之偏函數

到這裏,偏函數的功能和bind有類似之處。

不過,這些都不是今天的重點,今天的重點是反柯里化。

反柯里化

2.1 概念

反柯里化是一種拿來主義,把別人的東西拿過來用。

先看個例子: 咱們經常使用來判斷數據類型的Object.prototype.toString

function unCurry(fn) {
    return function (context) {
        return fn.apply(context, Array.prototype.slice.call(arguments, 1));
    }
}

// 不使用反柯里化
Object.prototype.toString.call({});  // [object Object]

// 反柯里化
const toString = unCurry(Object.prototype.toString);
toString({});        // [object Object]
toString(() => { }); // [object Function]
toString(1);         // [object Number]
複製代碼

2.2 實現

2.2.1 基礎版本:簡單好理解

function unCurry(fn) {
    return function (context) {
        return fn.apply(context, Array.prototype.slice.call(arguments, 1));
    }
}
複製代碼

2.2.2 原型版本: 入侵 + 便利

Function.prototype.unCurry = function(){
    var self = this;
    return function(){
        return Function.prototype.call.apply(self, arguments);
    }
}
複製代碼

這個版本入侵了原型,我不喜歡。 便利性倒仍是有的。
理解上的難點兩個

  1. self = this
    self等於函數自己,這裏就是暫存函數

  2. Function.prototype.call.apply(self, arguments)

提及來費事,看下面的轉換吧。

Function.prototype.call.apply(self, arguments)
==>
Function.prototype.call.bind(self)(arguments)
==>
self.call(arguments)
==>
self.call(arguments[0], arguments[1-n]) // arguments[0]就是self函數的上下文了
複製代碼

使用的話,也會略有變化

2.2.3 原型版本2

這個我不作解讀,你們自行理解一波

Function.prototype.unCurry = function () {
    return this.call.bind(this);
};

複製代碼

藉助ES6, 下面的代碼是否是也能夠呢?

Function.prototype.unCurry = function () {
    return (...args) => this.call(...args)
};
複製代碼

當前還有各類好玩的寫法,更多詳情能夠參考# 柯里化&&反柯里化

2.3 使用場景

反柯里化是一種思路,其實現確定是能夠被其餘方案替代的,可是多一種思路,就多一種手段

2.3.1 判斷數據類型

上面的demo已經演示過了。

2.3.2 數組push(高級編程中的例子)

const push = unCurry(Array.prototype.push);
const arr = [1, 2, 3];
push(arr, 4, 5, 6);
console.log(arr);

複製代碼

2.3.3 複製數組

const clone = unCurry(Array.prototype.slice);

var a = [1,2,3];

var b = clone(a);

console.log("a==b", a === b);  // a==b false
console.log(a, b);  // [ 1, 2, 3 ] [ 1, 2, 3 ]

複製代碼

2.3.4 發送事件

const dispatch = unCurry(EventTarget.prototype.dispatchEvent);

    window.addEventListener("event-x", (ev) => {
        console.log("event-x", ev.detail);  // event-x ok
    })

    dispatch(window, new CustomEvent("event-x", { detail: "ok" }));

複製代碼

詳解JS函數柯里化

柯里化與反柯里化

JavaScript專題之偏函數

相關文章
相關標籤/搜索