欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端JavaScript徹底弄懂函數(shù)柯里化curry

 更新時間:2021年09月15日 14:23:53   作者:云中橋  
隨著主流JavaScript中函數(shù)式編程的迅速發(fā)展, 函數(shù)柯里化在許多應用程序中已經變得很普遍。 了解它們是什么,它們如何工作以及如何充分利用它們非常重要。本篇文章小編九向大家詳細介紹JavaScript函數(shù)柯里化,需要的小伙伴可以參考下面文字內容

一、什么是柯里化( curry)

在數(shù)學和計算機科學中,柯里化是一種將使用多個參數(shù)的一個函數(shù)轉換成一系列使用一個參數(shù)的函數(shù)的技術。

舉例來說,一個接收3個參數(shù)的普通函數(shù),在進行柯里化后, 柯里化版本的函數(shù)接收一個參數(shù)并返回接收下一個參數(shù)的函數(shù), 該函數(shù)返回一個接收第三個參數(shù)的函數(shù)。 最后一個函數(shù)在接收第三個參數(shù)后, 將之前接收到的三個參數(shù)應用于原普通函數(shù)中,并返回最終結果。

數(shù)學和計算科學中的柯里化:

// 數(shù)學和計算科學中的柯里化:

//一個接收三個參數(shù)的普通函數(shù)
function sum(a,b,c) {
    console.log(a+b+c)
}

//用于將普通函數(shù)轉化為柯里化版本的工具函數(shù)
function curry(fn) {
  //...內部實現(xiàn)省略,返回一個新函數(shù)
}

//獲取一個柯里化后的函數(shù)
let _sum = curry(sum);

//返回一個接收第二個參數(shù)的函數(shù)
let A = _sum(1);
//返回一個接收第三個參數(shù)的函數(shù)
let B = A(2);
//接收到最后一個參數(shù),將之前所有的參數(shù)應用到原函數(shù)中,并運行
B(3)    // print : 6

而對于Javascript語言來說,我們通常說的柯里化函數(shù)的概念,與數(shù)學和計算機科學中的柯里化的概念并不完全一樣。

在數(shù)學和計算機科學中的柯里化函數(shù),一次只能傳遞一個參數(shù);

而我們Javascript實際應用中的柯里化函數(shù),可以傳遞一個或多個參數(shù)。

來看這個例子:

//普通函數(shù)
function fn(a,b,c,d,e) {
  console.log(a,b,c,d,e)
}
//生成的柯里化函數(shù)
let _fn = curry(fn);

_fn(1,2,3,4,5);     // print: 1,2,3,4,5
_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5
_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

對于已經柯里化后的 _fn 函數(shù)來說,當接收的參數(shù)數(shù)量與原函數(shù)的形參數(shù)量相同時,執(zhí)行原函數(shù); 當接收的參數(shù)數(shù)量小于原函數(shù)的形參數(shù)量時,返回一個函數(shù)用于接收剩余的參數(shù),直至接收的參數(shù)數(shù)量與形參數(shù)量一致,執(zhí)行原函數(shù)。

當我們知道柯里化是什么了的時候,我們來看看柯里化到底有什么用?

二、柯里化的用途

柯里化實際是把簡答的問題復雜化了,但是復雜化的同時,我們在使用函數(shù)時擁有了更加多的自由度。 而這里對于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在。 柯里化本質上是降低通用性,提高適用性。來看一個例子:

我們工作中會遇到各種需要通過正則檢驗的需求,比如校驗電話號碼、校驗郵箱、校驗身份證號、校驗密碼等, 這時我們會封裝一個通用函數(shù) checkByRegExp ,接收兩個參數(shù),校驗的正則對象和待校驗的字符串

function checkByRegExp(regExp,string) {
    return regExp.test(string);  
}

checkByRegExp(/^1\d{10}$/, '18642838455'); // 校驗電話號碼
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校驗郵箱

上面這段代碼,乍一看沒什么問題,可以滿足我們所有通過正則檢驗的需求。 但是我們考慮這樣一個問題,如果我們需要校驗多個電話號碼或者校驗多個郵箱呢?

我們可能會這樣做:

checkByRegExp(/^1\d{10}$/, '18642838455'); // 校驗電話號碼
checkByRegExp(/^1\d{10}$/, '13109840560'); // 校驗電話號碼
checkByRegExp(/^1\d{10}$/, '13204061212'); // 校驗電話號碼

checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校驗郵箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@qq.com'); // 校驗郵箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@gmail.com'); // 校驗郵箱

我們每次進行校驗的時候都需要輸入一串正則,再校驗同一類型的數(shù)據(jù)時,相同的正則我們需要寫多次, 這就導致我們在使用的時候效率低下,并且由于 checkByRegExp 函數(shù)本身是一個工具函數(shù)并沒有任何意義, 一段時間后我們重新來看這些代碼時,如果沒有注釋,我們必須通過檢查正則的內容, 我們才能知道我們校驗的是電話號碼還是郵箱,還是別的什么。

此時,我們可以借助柯里化對 checkByRegExp 函數(shù)進行封裝,以簡化代碼書寫,提高代碼可讀性。

//進行柯里化
let _check = curry(checkByRegExp);
//生成工具函數(shù),驗證電話號碼
let checkCellPhone = _check(/^1\d{10}$/);
//生成工具函數(shù),驗證郵箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

checkCellPhone('18642838455'); // 校驗電話號碼
checkCellPhone('13109840560'); // 校驗電話號碼
checkCellPhone('13204061212'); // 校驗電話號碼

checkEmail('test@163.com'); // 校驗郵箱
checkEmail('test@qq.com'); // 校驗郵箱
checkEmail('test@gmail.com'); // 校驗郵箱

再來看看通過柯里化封裝后,我們的代碼是不是變得又簡潔又直觀了呢。

經過柯里化后,我們生成了兩個函數(shù) checkCellPhone 和 checkEmail, checkCellPhone 函數(shù)只能驗證傳入的字符串是否是電話號碼, checkEmail 函數(shù)只能驗證傳入的字符串是否是郵箱, 它們與 原函數(shù) checkByRegExp 相比,從功能上通用性降低了,但適用性提升了。 柯里化的這種用途可以被理解為:參數(shù)復用

我們再來看一個例子

假定我們有這樣一段數(shù)據(jù):

let list = [
    {
        name:'lucy'
    },
    {
        name:'jack'
    }
]

我們需要獲取數(shù)據(jù)中的所有 name 屬性的值,常規(guī)思路下,我們會這樣實現(xiàn):

let names = list.map(function(item) {
  return item.name;
})

那么我們如何用柯里化的思維來實現(xiàn)呢

let prop = curry(function(key,obj) {
    return obj[key];
})
let names = list.map(prop('name'))

看到這里,可能會有疑問,這么簡單的例子,僅僅只是為了獲取 name 的屬性值,為何還要實現(xiàn)一個 prop 函數(shù)呢,這樣太麻煩了吧。

我們可以換個思路,prop 函數(shù)實現(xiàn)一次后,以后是可以多次使用的,所以我們在考慮代碼復雜程度的時候,是可以將 prop 函數(shù)的實現(xiàn)去掉的。

我們實際的代碼可以理解為只有一行 let names = list.map(prop('name'))

這么看來,通過柯里化的方式,我們的代碼是不是變得更精簡了,并且可讀性更高了呢。

三、如何封裝柯里化工具函數(shù)

接下來,我們來思考如何實現(xiàn) curry 函數(shù)。

回想之前我們對于柯里化的定義,接收一部分參數(shù),返回一個函數(shù)接收剩余參數(shù),接收足夠參數(shù)后,執(zhí)行原函數(shù)。

我們已經知道了,當柯里化函數(shù)接收到足夠參數(shù)后,就會執(zhí)行原函數(shù),那么我們如何去確定何時達到足夠的參數(shù)呢?

我們有兩種思路:

  1. 通過函數(shù)的 length 屬性,獲取函數(shù)的形參個數(shù),形參的個數(shù)就是所需的參數(shù)個數(shù)
  2. 在調用柯里化工具函數(shù)時,手動指定所需的參數(shù)個數(shù)

我們將這兩點結合以下,實現(xiàn)一個簡單 curry 函數(shù):

/**
 * 將函數(shù)柯里化
 * @param fn    待柯里化的原函數(shù)
 * @param len   所需的參數(shù)個數(shù),默認為原函數(shù)的形參個數(shù)
 */
function curry(fn,len = fn.length) {
    return _curry.call(this,fn,len)
}

/**
 * 中轉函數(shù)
 * @param fn    待柯里化的原函數(shù)
 * @param len   所需的參數(shù)個數(shù)
 * @param args  已接收的參數(shù)列表
 */
function _curry(fn,len,...args) {
    return function (...params) {
        let _args = [...args,...params];
        if(_args.length >= len){
            return fn.apply(this,_args);
        }else{
            return _curry.call(this,fn,len,..._args)
        }
    }
}

我們來驗證一下:

let _fn = curry(function(a,b,c,d,e){
    console.log(a,b,c,d,e)
});

_fn(1,2,3,4,5);     // print: 1,2,3,4,5
_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5
_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

我們常用的工具庫 lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通過占位符的方式來改變傳入參數(shù)的順序。

比如說,我們傳入一個占位符,本次調用傳遞的參數(shù)略過占位符, 占位符所在的位置由下次調用的參數(shù)來填充,比如這樣:

直接看一下官網的例子:

接下來我們來思考,如何實現(xiàn)占位符的功能。

對于 lodash curry 函數(shù)來說,curry 函數(shù)掛載在 lodash 對象上,所以將 lodash 對象當做默認占位符來使用。

而我們的自己實現(xiàn)的 curry 函數(shù),本身并沒有掛載在任何對象上,所以將 curry 函數(shù)當做默認占位符

使用占位符,目的是改變參數(shù)傳遞的順序,所以在 curry 函數(shù)實現(xiàn)中,每次需要記錄是否使用了占位符,并且記錄占位符所代表的參數(shù)位置。

直接上代碼:

/**
 * @param  fn           待柯里化的函數(shù)
 * @param  length       需要的參數(shù)個數(shù),默認為函數(shù)的形參個數(shù)
 * @param  holder       占位符,默認當前柯里化函數(shù)
 * @return {Function}   柯里化后的函數(shù)
 */
function curry(fn,length = fn.length,holder = curry){
    return _curry.call(this,fn,length,holder,[],[])
}
/**
 * 中轉函數(shù)
 * @param fn            柯里化的原函數(shù)
 * @param length        原函數(shù)需要的參數(shù)個數(shù)
 * @param holder        接收的占位符
 * @param args          已接收的參數(shù)列表
 * @param holders       已接收的占位符位置列表
 * @return {Function}   繼續(xù)柯里化的函數(shù) 或 最終結果
 */
function _curry(fn,length,holder,args,holders){
    return function(..._args){
        //將參數(shù)復制一份,避免多次操作同一函數(shù)導致參數(shù)混亂
        let params = args.slice();
        //將占位符位置列表復制一份,新增加的占位符增加至此
        let _holders = holders.slice();
        //循環(huán)入參,追加參數(shù) 或 替換占位符
        _args.forEach((arg,i)=>{
            //真實參數(shù) 之前存在占位符 將占位符替換為真實參數(shù)
            if (arg !== holder && holders.length) {
                let index = holders.shift();
                _holders.splice(_holders.indexOf(index),1);
                params[index] = arg;
            }
            //真實參數(shù) 之前不存在占位符 將參數(shù)追加到參數(shù)列表中
            else if(arg !== holder && !holders.length){
                params.push(arg);
            }
            //傳入的是占位符,之前不存在占位符 記錄占位符的位置
            else if(arg === holder && !holders.length){
                params.push(arg);
                _holders.push(params.length - 1);
            }
            //傳入的是占位符,之前存在占位符 刪除原占位符位置
            else if(arg === holder && holders.length){
                holders.shift();
            }
        });
        // params 中前 length 條記錄中不包含占位符,執(zhí)行函數(shù)
        if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
            return fn.apply(this,params);
        }else{
            return _curry.call(this,fn,length,holder,params,_holders)
        }
    }
}

驗證一下:

let fn = function(a, b, c, d, e) {
    console.log([a, b, c, d, e]);
}

let _ = {}; // 定義占位符
let _fn = curry(fn,5,_);  // 將函數(shù)柯里化,指定所需的參數(shù)個數(shù),指定所需的占位符

_fn(1, 2, 3, 4, 5);                 // print: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1);              // print: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2);              // print: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5);         // print: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5);        // print: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5);        // print: 1,2,3,4,5

我們已經完整實現(xiàn)了一個 curry 函數(shù)~~

到此這篇關于前端JavaScript徹底弄懂函數(shù)柯里化的文章就介紹到這了,更多相關JavaScript函數(shù)柯里化內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論