溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript中的函數(shù)柯理化是什么意思

發(fā)布時(shí)間:2020-12-21 09:24:12 來源:億速云 閱讀:121 作者:小新 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)JavaScript中的函數(shù)柯理化是什么意思,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

什么是函數(shù)柯里化?

什么是函數(shù)柯里化?先看看維基百科如何解釋:

在計(jì)算機(jī)科學(xué)中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。

這個(gè)技術(shù)由克里斯托弗·斯特雷奇以邏輯學(xué)家哈斯凱爾·加里命名的,盡管它是Moses Sch?nfinkel和戈特洛布·弗雷格發(fā)明的。

在直覺上,柯里化聲稱“如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個(gè)函數(shù)”。所以對(duì)于有兩個(gè)變量的函y^x,如果固定了y=2,則得到有一個(gè)變量的函數(shù)2^x。

Currying的概念其實(shí)并不復(fù)雜,用通俗易懂的話說:只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。

如果文字解釋還是有一點(diǎn)抽象,我們就拿add函數(shù),來做一個(gè)簡(jiǎn)單的函數(shù)柯里化的實(shí)現(xiàn)。

// 普通的add函數(shù)
function add(x, y) {
    return x + y
}

// add函數(shù)柯里化后
var curryingAdd = function(x) {
  return function(y) {
    return x + y;
  };
};

// 函數(shù)復(fù)用
var increment = curryingAdd(1);
var addTen = curryingAdd(10);

increment(2);
// 3
addTen(2);
// 12

實(shí)際上就是把a(bǔ)dd函數(shù)的x,y兩個(gè)參數(shù)變成了先用一個(gè)函數(shù)接收x然后返回一個(gè)函數(shù)去處理y參數(shù)?,F(xiàn)在思路應(yīng)該就比較清晰了,就是只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。

為什么要函數(shù)柯里化?

看完上面的關(guān)于add函數(shù)的柯里化,問題來了,費(fèi)這么大勁封裝一層,到底有什么用處呢?

一、參數(shù)復(fù)用

其實(shí)剛剛第一個(gè)add函數(shù)的柯里化例子中已經(jīng)涉及到了函數(shù)柯里化所帶來的函數(shù)復(fù)用的便捷,我們通過add函數(shù)柯里化,很快捷地實(shí)現(xiàn)了increment函數(shù)和addTen函數(shù),再來看個(gè)例子:

// 正常正則驗(yàn)證字符串 reg.test(txt)

// 函數(shù)封裝后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

上面的示例是一個(gè)正則的校驗(yàn),正常來說直接調(diào)用check函數(shù)就可以了,但是如果我有很多地方都要校驗(yàn)是否有數(shù)字,其實(shí)就是需要將第一個(gè)參數(shù)reg進(jìn)行復(fù)用,這樣別的地方就能夠直接調(diào)用hasNumber,hasLetter等函數(shù),讓參數(shù)能夠復(fù)用,調(diào)用起來也更方便。

二、提前確認(rèn)
var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

var on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

換一種寫法可能比較好理解一點(diǎn),上面就是把isSupport這個(gè)參數(shù)給先確定下來了

var on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}

我們?cè)谧鲰?xiàng)目的過程中,封裝一些dom操作可以說再常見不過,上面第一種寫法也是比較常見,但是我們看看第二種寫法,它相對(duì)一第一種寫法就是自執(zhí)行然后返回一個(gè)新的函數(shù),這樣其實(shí)就是提前確定了會(huì)走哪一個(gè)方法,避免每次都進(jìn)行判斷。

三、延遲計(jì)算/運(yùn)行
Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)

    return function() {
        return _this.apply(context, args)
    }
}

像我們js中經(jīng)常使用的bind,實(shí)現(xiàn)的機(jī)制就是Currying.

如何實(shí)現(xiàn)函數(shù)柯里化?

通用的封裝方法:

// 初步封裝
var currying = function(fn) {
    // args 獲取第一個(gè)方法內(nèi)的全部參數(shù)
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // 將后面方法里的全部參數(shù)和args進(jìn)行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合并后的參數(shù)通過apply作為fn的參數(shù)并執(zhí)行
        return fn.apply(this, newArgs)
    }
}

這邊首先是初步封裝,通過閉包把初步參數(shù)給保存下來,然后通過獲取剩下的arguments進(jìn)行拼接,最后執(zhí)行需要currying的函數(shù)。

但是上面的函數(shù)還是有些缺陷,這樣返回的話其實(shí)只能多擴(kuò)展一個(gè)參數(shù),currying(a)(b)(c)這樣的話,貌似就不支持了(不支持多參數(shù)調(diào)用),一般這種情況都會(huì)想到使用遞歸再進(jìn)行封裝一層。

// 支持多參數(shù)傳遞
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果參數(shù)個(gè)數(shù)小于最初的fn.length,則遞歸調(diào)用,繼續(xù)收集參數(shù)
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 參數(shù)收集完畢,則執(zhí)行fn
        return fn.apply(this, _args);
    }
}

這邊其實(shí)是在初步的基礎(chǔ)上,加上了遞歸的調(diào)用,只要參數(shù)個(gè)數(shù)小于最初的fn.length,就會(huì)繼續(xù)執(zhí)行遞歸。

函數(shù)柯里化的性能怎么樣?

關(guān)于Currying的性能,我們應(yīng)該知道下面幾點(diǎn):

  • 存取arguments對(duì)象通常要比存取命名參數(shù)要慢一點(diǎn)

  • 一些老版本的瀏覽器在arguments.length的實(shí)現(xiàn)上是相當(dāng)慢的

  • 使用fn.apply( … ) 和 fn.call( … )通常比直接調(diào)用fn( … ) 稍微慢點(diǎn)

  • 創(chuàng)建大量嵌套作用域和閉包函數(shù)會(huì)帶來花銷,無論是在內(nèi)存還是速度上

其實(shí)在大部分應(yīng)用中,主要的性能瓶頸是在操作DOM節(jié)點(diǎn)上,這js的性能損耗基本是可以忽略不計(jì)的,所以curry是可以直接放心的使用。

柯里化面試題

// 實(shí)現(xiàn)一個(gè)add方法,使計(jì)算結(jié)果能夠滿足如下預(yù)期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次執(zhí)行時(shí),定義一個(gè)數(shù)組專門用來存儲(chǔ)所有的參數(shù)
    var _args = Array.prototype.slice.call(arguments);

    // 在內(nèi)部聲明一個(gè)函數(shù),利用閉包的特性保存_args并收集所有的參數(shù)值
    var _adder = function() {
        _args.push(…arguments);
        return _adder;
    };

    // 利用toString隱式轉(zhuǎn)換的特性,當(dāng)最后執(zhí)行時(shí)隱式轉(zhuǎn)換,并計(jì)算最終的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

總結(jié)

通過簡(jiǎn)單地傳遞幾個(gè)參數(shù),就能動(dòng)態(tài)創(chuàng)建實(shí)用的新函數(shù);而且還能帶來一個(gè)額外好處,那就是保留了數(shù)學(xué)的函數(shù)定義,盡管參數(shù)不止一個(gè)。

Currying函數(shù)用起來非常得心應(yīng)手,每天使用它對(duì)我來說簡(jiǎn)直就是一種享受。它堪稱手頭必備工具,能夠讓函數(shù)式編程不那么繁瑣和沉悶。

關(guān)于JavaScript中的函數(shù)柯理化是什么意思就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI