成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript專題之函數(shù)柯里化

zhangfaliang / 1628人閱讀

摘要:一個(gè)經(jīng)常會(huì)看到的函數(shù)的實(shí)現(xiàn)為第一版我們可以這樣使用或者或者已經(jīng)有柯里化的感覺(jué)了,但是還沒(méi)有達(dá)到要求,不過(guò)我們可以把這個(gè)函數(shù)用作輔助函數(shù),幫助我們寫真正的函數(shù)。

JavaScript 專題系列第十三篇,講解函數(shù)柯里化以及如何實(shí)現(xiàn)一個(gè) curry 函數(shù)

定義

維基百科中對(duì)柯里化 (Currying) 的定義為:

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.

翻譯成中文:

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

舉個(gè)例子:

function add(a, b) {
    return a + b;
}

// 執(zhí)行 add 函數(shù),一次傳入兩個(gè)參數(shù)即可
add(1, 2) // 3

// 假設(shè)有一個(gè) curry 函數(shù)可以做到柯里化
var addCurry = curry(add);
addCurry(1)(2) // 3
用途

我們會(huì)講到如何寫出這個(gè) curry 函數(shù),并且會(huì)將這個(gè) curry 函數(shù)寫的很強(qiáng)大,但是在編寫之前,我們需要知道柯里化到底有什么用?

舉個(gè)例子:

// 示意而已
function ajax(type, url, data) {
    var xhr = new XMLHttpRequest();
    xhr.open(type, url, true);
    xhr.send(data);
}

// 雖然 ajax 這個(gè)函數(shù)非常通用,但在重復(fù)調(diào)用的時(shí)候參數(shù)冗余
ajax("POST", "www.test.com", "name=kevin")
ajax("POST", "www.test2.com", "name=kevin")
ajax("POST", "www.test3.com", "name=kevin")

// 利用 curry
var ajaxCurry = curry(ajax);

// 以 POST 類型請(qǐng)求數(shù)據(jù)
var post = ajaxCurry("POST");
post("www.test.com", "name=kevin");

// 以 POST 類型請(qǐng)求來(lái)自于 www.test.com 的數(shù)據(jù)
var postFromTest = post("www.test.com");
postFromTest("name=kevin");

想想 jQuery 雖然有 $.ajax 這樣通用的方法,但是也有 $.get 和 $.post 的語(yǔ)法糖。(當(dāng)然 jQuery 底層是否是這樣做的,我就沒(méi)有研究了)。

curry 的這種用途可以理解為:參數(shù)復(fù)用。本質(zhì)上是降低通用性,提高適用性。

可是即便如此,是不是依然感覺(jué)沒(méi)什么用呢?

如果我們僅僅是把參數(shù)一個(gè)一個(gè)傳進(jìn)去,意義可能不大,但是如果我們是把柯里化后的函數(shù)傳給其他函數(shù)比如 map 呢?

舉個(gè)例子:

比如我們有這樣一段數(shù)據(jù):

var person = [{name: "kevin"}, {name: "daisy"}]

如果我們要獲取所有的 name 值,我們可以這樣做:

var name = person.map(function (item) {
    return item.name;
})

不過(guò)如果我們有 curry 函數(shù):

var prop = curry(function (key, obj) {
    return obj[key]
});

var name = person.map(prop("name"))

我們?yōu)榱双@取 name 屬性還要再編寫一個(gè) prop 函數(shù),是不是又麻煩了些?

但是要注意,prop 函數(shù)編寫一次后,以后可以多次使用,實(shí)際上代碼從原本的三行精簡(jiǎn)成了一行,而且你看代碼是不是更加易懂了?

person.map(prop("name")) 就好像直白的告訴你:person 對(duì)象遍歷(map)獲取(prop) name 屬性。

是不是感覺(jué)有點(diǎn)意思了呢?

第一版

未來(lái)我們會(huì)接觸到更多有關(guān)柯里化的應(yīng)用,不過(guò)那是未來(lái)的事情了,現(xiàn)在我們?cè)摼帉戇@個(gè) curry 函數(shù)了。

一個(gè)經(jīng)常會(huì)看到的 curry 函數(shù)的實(shí)現(xiàn)為:

// 第一版
var curry = function (fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};

我們可以這樣使用:

function add(a, b) {
    return a + b;
}

var addCurry = curry(add, 1, 2);
addCurry() // 3
//或者
var addCurry = curry(add, 1);
addCurry(2) // 3
//或者
var addCurry = curry(add);
addCurry(1, 2) // 3

已經(jīng)有柯里化的感覺(jué)了,但是還沒(méi)有達(dá)到要求,不過(guò)我們可以把這個(gè)函數(shù)用作輔助函數(shù),幫助我們寫真正的 curry 函數(shù)。

第二版
// 第二版
function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {

    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}

我們驗(yàn)證下這個(gè)函數(shù):

var fn = curry(function(a, b, c) {
    return [a, b, c];
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

效果已經(jīng)達(dá)到我們的預(yù)期,然而這個(gè) curry 函數(shù)的實(shí)現(xiàn)好難理解吶……

為了讓大家更好的理解這個(gè) curry 函數(shù),我給大家寫個(gè)極簡(jiǎn)版的代碼:

function sub_curry(fn){
    return function(){
        return fn()
    }
}

function curry(fn, length){
    length = length || 4;
    return function(){
        if (length > 1) {
            return curry(sub_curry(fn), --length)
        }
        else {
            return fn()
        }
    }
}

var fn0 = function(){
    console.log(1)
}

var fn1 = curry(fn0)

fn1()()()() // 1

大家先從理解這個(gè) curry 函數(shù)開(kāi)始。

當(dāng)執(zhí)行 fn1() 時(shí),函數(shù)返回:

curry(sub_curry(fn0))
// 相當(dāng)于
curry(function(){
    return fn0()
})

當(dāng)執(zhí)行 fn1()() 時(shí),函數(shù)返回:

curry(sub_curry(function(){
    return fn0()
}))
// 相當(dāng)于
curry(function(){
    return (function(){
        return fn0()
    })()
})
// 相當(dāng)于
curry(function(){
    return fn0()
})

當(dāng)執(zhí)行 fn1()()() 時(shí),函數(shù)返回:

// 跟 fn1()() 的分析過(guò)程一樣
curry(function(){
    return fn0()
})

當(dāng)執(zhí)行 fn1()()()() 時(shí),因?yàn)榇藭r(shí) length > 2 為 false,所以執(zhí)行 fn():

fn()
// 相當(dāng)于
(function(){
    return fn0()
})()
// 相當(dāng)于
fn0()
// 執(zhí)行 fn0 函數(shù),打印 1

再回到真正的 curry 函數(shù),我們以下面的例子為例:

var fn0 = function(a, b, c, d) {
    return [a, b, c, d];
}

var fn1 = curry(fn0);

fn1("a", "b")("c")("d")

當(dāng)執(zhí)行 fn1("a", "b") 時(shí):

fn1("a", "b")
// 相當(dāng)于
curry(fn0)("a", "b")
// 相當(dāng)于
curry(sub_curry(fn0, "a", "b"))
// 相當(dāng)于
// 注意 ... 只是一個(gè)示意,表示該函數(shù)執(zhí)行時(shí)傳入的參數(shù)會(huì)作為 fn0 后面的參數(shù)傳入
curry(function(...){
    return fn0("a", "b", ...)
})

當(dāng)執(zhí)行 fn1("a", "b")("c") 時(shí),函數(shù)返回:

curry(sub_curry(function(...){
    return fn0("a", "b", ...)
}), "c")
// 相當(dāng)于
curry(function(...){
    return (function(...) {return fn0("a", "b", ...)})("c")
})
// 相當(dāng)于
curry(function(...){
     return fn0("a", "b", "c", ...)
})

當(dāng)執(zhí)行 fn1("a", "b")("c")("d") 時(shí),此時(shí) arguments.length < length 為 false ,執(zhí)行 fn(arguments),相當(dāng)于:

(function(...){
    return fn0("a", "b", "c", ...)
})("d")
// 相當(dāng)于
fn0("a", "b", "c", "d")

函數(shù)執(zhí)行結(jié)束。

所以,其實(shí)整段代碼又很好理解:

sub_curry 的作用就是用函數(shù)包裹原函數(shù),然后給原函數(shù)傳入之前的參數(shù),當(dāng)執(zhí)行 fn0(...)(...) 的時(shí)候,執(zhí)行包裹函數(shù),返回原函數(shù),然后再調(diào)用 sub_curry 再包裹原函數(shù),然后將新的參數(shù)混合舊的參數(shù)再傳入原函數(shù),直到函數(shù)參數(shù)的數(shù)目達(dá)到要求為止。

如果要明白 curry 函數(shù)的運(yùn)行原理,大家還是要?jiǎng)邮謱懸槐?,嘗試著分析執(zhí)行步驟。

更易懂的實(shí)現(xiàn)

當(dāng)然了,如果你覺(jué)得還是無(wú)法理解,你可以選擇下面這種實(shí)現(xiàn)方式,可以實(shí)現(xiàn)同樣的效果:

function curry(fn, args) {
    length = fn.length;

    args = args || [];

    return function() {

        var _args = args.slice(0),

            arg, i;

        for (i = 0; i < arguments.length; i++) {

            arg = arguments[i];

            _args.push(arg);

        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}


var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

或許大家覺(jué)得這種方式更好理解,又能實(shí)現(xiàn)一樣的效果,為什么不直接就講這種呢?

因?yàn)橄虢o大家介紹各種實(shí)現(xiàn)的方法嘛,不能因?yàn)殡y以理解就不給大家介紹吶~

第三版

curry 函數(shù)寫到這里其實(shí)已經(jīng)很完善了,但是注意這個(gè)函數(shù)的傳參順序必須是從左到右,根據(jù)形參的順序依次傳入,如果我不想根據(jù)這個(gè)順序傳呢?

我們可以創(chuàng)建一個(gè)占位符,比如這樣:

var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});

fn("a", _, "c")("b") // ["a", "b", "c"]

我們直接看第三版的代碼:

// 第三版
function curry(fn, args, holes) {
    length = fn.length;

    args = args || [];

    holes = holes || [];

    return function() {

        var _args = args.slice(0),
            _holes = holes.slice(0),
            argsLen = args.length,
            holesLen = holes.length,
            arg, i, index = 0;

        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            // 處理類似 fn(1, _, _, 4)(_, 3) 這種情況,index 需要指向 holes 正確的下標(biāo)
            if (arg === _ && holesLen) {
                index++
                if (index > holesLen) {
                    _args.push(arg);
                    _holes.push(argsLen - 1 + index - holesLen)
                }
            }
            // 處理類似 fn(1)(_) 這種情況
            else if (arg === _) {
                _args.push(arg);
                _holes.push(argsLen + i);
            }
            // 處理類似 fn(_, 2)(1) 這種情況
            else if (holesLen) {
                // fn(_, 2)(_, 3)
                if (index >= holesLen) {
                    _args.push(arg);
                }
                // fn(_, 2)(1) 用參數(shù) 1 替換占位符
                else {
                    _args.splice(_holes[index], 1, arg);
                    _holes.splice(index, 1)
                }
            }
            else {
                _args.push(arg);
            }

        }
        if (_holes.length || _args.length < length) {
            return curry.call(this, fn, _args, _holes);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}

var _ = {};

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

// 驗(yàn)證 輸出全部都是 [1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5)
寫在最后

至此,我們已經(jīng)實(shí)現(xiàn)了一個(gè)強(qiáng)大的 curry 函數(shù),可是這個(gè) curry 函數(shù)符合柯里化的定義嗎?柯里化可是將一個(gè)多參數(shù)的函數(shù)轉(zhuǎn)換成多個(gè)單參數(shù)的函數(shù),但是現(xiàn)在我們不僅可以傳入一個(gè)參數(shù),還可以一次傳入兩個(gè)參數(shù),甚至更多參數(shù)……這看起來(lái)更像一個(gè)柯里化 (curry) 和偏函數(shù) (partial application) 的綜合應(yīng)用,可是什么又是偏函數(shù)呢?下篇文章會(huì)講到。

專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。

JavaScript專題系列預(yù)計(jì)寫二十篇左右,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87191.html

相關(guān)文章

  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評(píng)論0 收藏0
  • JavaScript專題函數(shù)

    摘要:專題系列第十四篇,講解偏函數(shù)以及如何實(shí)現(xiàn)一個(gè)函數(shù)定義維基百科中對(duì)偏函數(shù)的定義為翻譯成中文在計(jì)算機(jī)科學(xué)中,局部應(yīng)用是指固定一個(gè)函數(shù)的一些參數(shù),然后產(chǎn)生另一個(gè)更小元的函數(shù)。 JavaScript 專題系列第十四篇,講解偏函數(shù)以及如何實(shí)現(xiàn)一個(gè) partial 函數(shù) 定義 維基百科中對(duì)偏函數(shù) (Partial application) 的定義為: In computer science, pa...

    beita 評(píng)論0 收藏0
  • JavaScript專題系列20篇正式完結(jié)!

    摘要:寫在前面專題系列是我寫的第二個(gè)系列,第一個(gè)系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫在前面 JavaScript 專題系列是我寫的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專題系列共計(jì) 20 篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評(píng)論0 收藏0
  • js 擴(kuò)展 -- currying 柯里函數(shù)

    摘要:里也有柯里化的實(shí)現(xiàn),只是平時(shí)沒(méi)有在意。如果函數(shù)柯里化后雖然生搬硬套,不過(guò)現(xiàn)實(shí)業(yè)務(wù)也會(huì)有類似場(chǎng)景。 柯里化 先解釋下什么是 柯里化 在計(jì)算機(jī)科學(xué)中,柯里化(英語(yǔ):Currying),又譯為卡瑞化或加里化,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。 js 里也有柯里化的實(shí)現(xiàn),只是平時(shí)沒(méi)有在意。先把原文簡(jiǎn)介貼...

    Pocher 評(píng)論0 收藏0
  • JavaScript專題遞歸

    摘要:專題系列第十八篇,講解遞歸和尾遞歸定義程序調(diào)用自身的編程技巧稱為遞歸。然而非尾調(diào)用函數(shù),就會(huì)創(chuàng)建多個(gè)執(zhí)行上下文壓入執(zhí)行上下文棧。所以我們只用把階乘函數(shù)改造成一個(gè)尾遞歸形式,就可以避免創(chuàng)建那么多的執(zhí)行上下文。 JavaScript 專題系列第十八篇,講解遞歸和尾遞歸 定義 程序調(diào)用自身的編程技巧稱為遞歸(recursion)。 階乘 以階乘為例: function factorial(n...

    asoren 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<