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

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

 更新時(shí)間:2021年09月15日 14:23:53   作者:云中橋  
隨著主流JavaScript中函數(shù)式編程的迅速發(fā)展, 函數(shù)柯里化在許多應(yīng)用程序中已經(jīng)變得很普遍。 了解它們是什么,它們?nèi)绾喂ぷ饕约叭绾纬浞掷盟鼈兎浅V匾?。本篇文章小編九向大家詳?xì)介紹JavaScript函數(shù)柯里化,需要的小伙伴可以參考下面文字內(nèi)容

一、什么是柯里化( curry)

在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中,柯里化是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。

舉例來(lái)說(shuō),一個(gè)接收3個(gè)參數(shù)的普通函數(shù),在進(jìn)行柯里化后, 柯里化版本的函數(shù)接收一個(gè)參數(shù)并返回接收下一個(gè)參數(shù)的函數(shù), 該函數(shù)返回一個(gè)接收第三個(gè)參數(shù)的函數(shù)。 最后一個(gè)函數(shù)在接收第三個(gè)參數(shù)后, 將之前接收到的三個(gè)參數(shù)應(yīng)用于原普通函數(shù)中,并返回最終結(jié)果。

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

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

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

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

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

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

而對(duì)于Javascript語(yǔ)言來(lái)說(shuō),我們通常說(shuō)的柯里化函數(shù)的概念,與數(shù)學(xué)和計(jì)算機(jī)科學(xué)中的柯里化的概念并不完全一樣。

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

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

來(lái)看這個(gè)例子:

//普通函數(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

對(duì)于已經(jīng)柯里化后的 _fn 函數(shù)來(lái)說(shuō),當(dāng)接收的參數(shù)數(shù)量與原函數(shù)的形參數(shù)量相同時(shí),執(zhí)行原函數(shù); 當(dāng)接收的參數(shù)數(shù)量小于原函數(shù)的形參數(shù)量時(shí),返回一個(gè)函數(shù)用于接收剩余的參數(shù),直至接收的參數(shù)數(shù)量與形參數(shù)量一致,執(zhí)行原函數(shù)。

當(dāng)我們知道柯里化是什么了的時(shí)候,我們來(lái)看看柯里化到底有什么用?

二、柯里化的用途

柯里化實(shí)際是把簡(jiǎn)答的問(wèn)題復(fù)雜化了,但是復(fù)雜化的同時(shí),我們?cè)谑褂煤瘮?shù)時(shí)擁有了更加多的自由度。 而這里對(duì)于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在。 柯里化本質(zhì)上是降低通用性,提高適用性。來(lái)看一個(gè)例子:

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

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

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

上面這段代碼,乍一看沒(méi)什么問(wèn)題,可以滿足我們所有通過(guò)正則檢驗(yàn)的需求。 但是我們考慮這樣一個(gè)問(wèn)題,如果我們需要校驗(yàn)多個(gè)電話號(hào)碼或者校驗(yàn)多個(gè)郵箱呢?

我們可能會(huì)這樣做:

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

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

我們每次進(jìn)行校驗(yàn)的時(shí)候都需要輸入一串正則,再校驗(yàn)同一類型的數(shù)據(jù)時(shí),相同的正則我們需要寫(xiě)多次, 這就導(dǎo)致我們?cè)谑褂玫臅r(shí)候效率低下,并且由于 checkByRegExp 函數(shù)本身是一個(gè)工具函數(shù)并沒(méi)有任何意義, 一段時(shí)間后我們重新來(lái)看這些代碼時(shí),如果沒(méi)有注釋,我們必須通過(guò)檢查正則的內(nèi)容, 我們才能知道我們校驗(yàn)的是電話號(hào)碼還是郵箱,還是別的什么。

此時(shí),我們可以借助柯里化對(duì) checkByRegExp 函數(shù)進(jìn)行封裝,以簡(jiǎn)化代碼書(shū)寫(xiě),提高代碼可讀性。

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

checkCellPhone('18642838455'); // 校驗(yàn)電話號(hào)碼
checkCellPhone('13109840560'); // 校驗(yàn)電話號(hào)碼
checkCellPhone('13204061212'); // 校驗(yàn)電話號(hào)碼

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

再來(lái)看看通過(guò)柯里化封裝后,我們的代碼是不是變得又簡(jiǎn)潔又直觀了呢。

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

我們?cè)賮?lái)看一個(gè)例子

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

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

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

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

那么我們?nèi)绾斡每吕锘乃季S來(lái)實(shí)現(xiàn)呢

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

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

我們可以換個(gè)思路,prop 函數(shù)實(shí)現(xiàn)一次后,以后是可以多次使用的,所以我們?cè)诳紤]代碼復(fù)雜程度的時(shí)候,是可以將 prop 函數(shù)的實(shí)現(xiàn)去掉的。

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

這么看來(lái),通過(guò)柯里化的方式,我們的代碼是不是變得更精簡(jiǎn)了,并且可讀性更高了呢。

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

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

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

我們已經(jīng)知道了,當(dāng)柯里化函數(shù)接收到足夠參數(shù)后,就會(huì)執(zhí)行原函數(shù),那么我們?nèi)绾稳ゴ_定何時(shí)達(dá)到足夠的參數(shù)呢?

我們有兩種思路:

  1. 通過(guò)函數(shù)的 length 屬性,獲取函數(shù)的形參個(gè)數(shù),形參的個(gè)數(shù)就是所需的參數(shù)個(gè)數(shù)
  2. 在調(diào)用柯里化工具函數(shù)時(shí),手動(dòng)指定所需的參數(shù)個(gè)數(shù)

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

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

/**
 * 中轉(zhuǎn)函數(shù)
 * @param fn    待柯里化的原函數(shù)
 * @param len   所需的參數(shù)個(gè)數(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)
        }
    }
}

我們來(lái)驗(yàn)證一下:

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

我們常用的工具庫(kù) lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通過(guò)占位符的方式來(lái)改變傳入?yún)?shù)的順序。

比如說(shuō),我們傳入一個(gè)占位符,本次調(diào)用傳遞的參數(shù)略過(guò)占位符, 占位符所在的位置由下次調(diào)用的參數(shù)來(lái)填充,比如這樣:

直接看一下官網(wǎng)的例子:

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

對(duì)于 lodash curry 函數(shù)來(lái)說(shuō),curry 函數(shù)掛載在 lodash 對(duì)象上,所以將 lodash 對(duì)象當(dāng)做默認(rèn)占位符來(lái)使用。

而我們的自己實(shí)現(xiàn)的 curry 函數(shù),本身并沒(méi)有掛載在任何對(duì)象上,所以將 curry 函數(shù)當(dāng)做默認(rèn)占位符

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

直接上代碼:

/**
 * @param  fn           待柯里化的函數(shù)
 * @param  length       需要的參數(shù)個(gè)數(shù),默認(rèn)為函數(shù)的形參個(gè)數(shù)
 * @param  holder       占位符,默認(rèn)當(dāng)前柯里化函數(shù)
 * @return {Function}   柯里化后的函數(shù)
 */
function curry(fn,length = fn.length,holder = curry){
    return _curry.call(this,fn,length,holder,[],[])
}
/**
 * 中轉(zhuǎn)函數(shù)
 * @param fn            柯里化的原函數(shù)
 * @param length        原函數(shù)需要的參數(shù)個(gè)數(shù)
 * @param holder        接收的占位符
 * @param args          已接收的參數(shù)列表
 * @param holders       已接收的占位符位置列表
 * @return {Function}   繼續(xù)柯里化的函數(shù) 或 最終結(jié)果
 */
function _curry(fn,length,holder,args,holders){
    return function(..._args){
        //將參數(shù)復(fù)制一份,避免多次操作同一函數(shù)導(dǎo)致參數(shù)混亂
        let params = args.slice();
        //將占位符位置列表復(fù)制一份,新增加的占位符增加至此
        let _holders = holders.slice();
        //循環(huán)入?yún)?,追加參?shù) 或 替換占位符
        _args.forEach((arg,i)=>{
            //真實(shí)參數(shù) 之前存在占位符 將占位符替換為真實(shí)參數(shù)
            if (arg !== holder && holders.length) {
                let index = holders.shift();
                _holders.splice(_holders.indexOf(index),1);
                params[index] = arg;
            }
            //真實(shí)參數(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)
        }
    }
}

驗(yàn)證一下:

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

let _ = {}; // 定義占位符
let _fn = curry(fn,5,_);  // 將函數(shù)柯里化,指定所需的參數(shù)個(gè)數(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

我們已經(jīng)完整實(shí)現(xiàn)了一個(gè) curry 函數(shù)~~

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

相關(guān)文章

最新評(píng)論