函數(shù)柯里化 add(1)(2)

第一次看到柯里化這個(gè)詞的時(shí)候,還是在看一篇算法相關(guān)的博客提到把函數(shù)柯里化,那時(shí)一看這個(gè)詞就感覺(jué)很高端,實(shí)際上當(dāng)你了解了后才發(fā)現(xiàn)其實(shí)就是高階函數(shù)的一個(gè)特殊用法。

果然是不管作用怎么樣都要有個(gè)高端的名字才有用。

首先看看柯里化到底是什么?
維基百科上說(shuō)道:柯里化,英語(yǔ):Currying(果然是滿滿的英譯中的既視感),是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。

看這個(gè)解釋有一點(diǎn)抽象,我們就拿被做了無(wú)數(shù)次示例的add函數(shù),來(lái)做一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。

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

// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3

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

但是問(wèn)題來(lái)了費(fèi)這么大勁封裝一層,到底有什么用處呢?沒(méi)有好處想讓我們程序員多干事情是不可能滴,這輩子都不可能.

來(lái)列一列Currying有哪些好處呢?

1. 參數(shù)復(fù)用
// 正常正則驗(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),正常來(lái)說(shuō)直接調(diào)用check函數(shù)就可以了,但是如果我有很多地方都要校驗(yàn)是否有數(shù)字,其實(shí)就是需要將第一個(gè)參數(shù)reg進(jìn)行復(fù)用,這樣別的地方就能夠直接調(diào)用hasNumber,hasLetter等函數(shù),讓參數(shù)能夠復(fù)用,調(diào)用起來(lái)也更方便。

  1. 提前確認(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);
            }
        };
    }
})();

//換一種寫(xiě)法可能比較好理解一點(diǎn),上面就是把isSupport這個(gè)參數(shù)給先確定下來(lái)了
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)目的過(guò)程中,封裝一些dom操作可以說(shuō)再常見(jiàn)不過(guò),上面第一種寫(xiě)法也是比較常見(jiàn),但是我們看看第二種寫(xiě)法,它相對(duì)一第一種寫(xiě)法就是自執(zhí)行然后返回一個(gè)新的函數(shù),這樣其實(shí)就是提前確定了會(huì)走哪一個(gè)方法,避免每次都進(jìn)行判斷。

  1. 延遲運(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.

說(shuō)了這幾點(diǎn)好處之后,發(fā)現(xiàn)還有個(gè)問(wèn)題,難道每次使用Currying都要對(duì)底層函數(shù)去做修改,

有沒(méi)有什么通用的封裝方法?

// 初步封裝
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ù)通過(guò)apply作為fn的參數(shù)并執(zhí)行
        return fn.apply(this, newArgs)
    }
}

這邊首先是初步封裝,通過(guò)閉包把初步參數(shù)給保存下來(lái),然后通過(guò)獲取剩下的arguments進(jìn)行拼接,最后執(zhí)行需要currying的函數(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í)行遞歸。

好處說(shuō)完了,通用方法也有了,讓我們來(lái)關(guān)注下curry的性能
curry的一些性能問(wèn)題你只要知道下面四點(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ì)帶來(lái)花銷(xiāo),無(wú)論是在內(nèi)存還是速度上
其實(shí)在大部分應(yīng)用中,主要的性能瓶頸是在操作DOM節(jié)點(diǎn)上,這js的性能損耗基本是可以忽略不計(jì)的,所以curry是可以直接放心的使用。

最后再擴(kuò)展一道經(jīng)典面試題

// 實(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ù)組專門(mén)用來(lái)存儲(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第一次看到柯里化這個(gè)詞的時(shí)候,還是在看一篇算法相關(guān)的博客提到把函數(shù)柯里化,那時(shí)一看這個(gè)詞就感覺(jué)很高端,實(shí)際上當(dāng)你了...
    flowsands閱讀 233,568評(píng)論 40 282
  • 首先看看柯里化到底是什么? 維基百科上說(shuō)道:柯里化,英語(yǔ):Currying(果然是滿滿的英譯中的既視感),是把接受...
    指尖跳動(dòng)閱讀 287評(píng)論 0 1
  • 這篇文章是在我學(xué)習(xí)call,bind,apply用法時(shí)候看到別人文章里面有提及這個(gè)詞,所以就簡(jiǎn)單學(xué)習(xí)了。 定義 維...
    鄭饞師閱讀 297評(píng)論 0 0
  • 一、什么是Currying 定義:柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)...
    劉越姐姐啊閱讀 1,039評(píng)論 0 0
  • 簡(jiǎn)介 首先,柯里化(Currying)是什么呢? 簡(jiǎn)單說(shuō),假如有一個(gè)函數(shù),接受多個(gè)參數(shù),那么一般來(lái)說(shuō)就是一次性傳入...
    骨灰設(shè)計(jì)師小X閱讀 447評(píng)論 0 1

友情鏈接更多精彩內(nèi)容