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

資訊專欄INFORMATION COLUMN

JavaScript函數(shù)柯里化

tommego / 804人閱讀

摘要:函數(shù)柯里化的對偶是,一種使用匿名單參數(shù)函數(shù)來實現(xiàn)多參數(shù)函數(shù)的方法。這是基于的富應(yīng)用開發(fā)的方法實現(xiàn)反柯里化可能遇到這種情況拿到一個柯里化后的函數(shù),卻想要它柯里化之前的版本,這本質(zhì)上就是想將類似的函數(shù)變回類似的函數(shù)。

什么是柯里化? 官方的說法

在計算機科學中,柯里化(英語:Currying),又譯為卡瑞化加里化,是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。這個技術(shù)由克里斯托弗·斯特雷奇以邏輯學家哈斯凱爾·加里命名的,盡管它是Moses Sch?nfinkel戈特洛布·弗雷格發(fā)明的。

在直覺上,柯里化聲稱如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個函數(shù)。
在理論計算機科學中,柯里化提供了在簡單的理論模型中,比如:只接受一個單一參數(shù)的lambda演算中,研究帶有多個參數(shù)的函數(shù)的方式。
函數(shù)柯里化的對偶是Uncurrying,一種使用匿名單參數(shù)函數(shù)來實現(xiàn)多參數(shù)函數(shù)的方法。

方便的理解

Currying概念其實很簡單,只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)。

如果我們需要實現(xiàn)一個求三個數(shù)之和的函數(shù):

function add(x, y, z) {
  return x + y + z;
}
console.log(add(1, 2, 3)); // 6
var add = function(x) {
  return function(y) {
    return function(z) {
      return x + y + z;
    }
  }
}

var addOne = add(1);
var addOneAndTwo = addOne(2);
var addOneAndTwoAndThree = addOneAndTwo(3);

console.log(addOneAndTwoAndThree);

這里我們定義了一個add函數(shù),它接受一個參數(shù)并返回一個新的函數(shù)。調(diào)用add之后,返回的函數(shù)就通過閉包的方式記住了add的第一個參數(shù)。一次性地調(diào)用它實在是有點繁瑣,好在我們可以使用一個特殊的curry幫助函數(shù)(helper function)使這類函數(shù)的定義和調(diào)用更加容易。

ES6的箭頭函數(shù),我們可以將上面的add實現(xiàn)成這樣:

const add = x => y => z => x + y + z;

好像使用箭頭函數(shù)更清晰了許多。

偏函數(shù)?

來看這個函數(shù):

function ajax(url, data, callback) {
  // ..
}

有這樣的一個場景:我們需要對多個不同的接口發(fā)起HTTP請求,有下列兩種做法:

在調(diào)用ajax()函數(shù)時,傳入全局URL常量。

創(chuàng)建一個已經(jīng)預(yù)設(shè)URL實參的函數(shù)引用。

下面我們創(chuàng)建一個新函數(shù),其內(nèi)部仍然發(fā)起ajax()請求,此外在等待接收另外兩個實參的同時,我們手動將ajax()第一個實參設(shè)置成你關(guān)心的API地址。

對于第一種做法,我們可能產(chǎn)生如下調(diào)用方式:

function ajaxTest1(data, callback) {
  ajax("http://www.test.com/test1", data, callback);
}

function ajaxTest2(data, callback) {
  ajax("http://www.test.com/test2", data, callback);
}

對于這兩個類似的函數(shù),我們還可以提取出如下的模式:

function beginTest(callback) {
  ajaxTest1({
    data: GLOBAL_TEST_1,
  }, callback);
}

相信您已經(jīng)看到了這樣的模式:我們在函數(shù)調(diào)用現(xiàn)場(function call-site),將實參應(yīng)用(apply) 于形參。如你所見,我們一開始僅應(yīng)用了部分實參 —— 具體是將實參應(yīng)用到URL形參 —— 剩下的實參稍后再應(yīng)用。

上述概念即為偏函數(shù)的定義,偏函數(shù)一個減少函數(shù)參數(shù)個數(shù)的過程;這里的參數(shù)個數(shù)指的是希望傳入的形參的數(shù)量。我們通過ajaxTest1()把原函數(shù)ajax()的參數(shù)個數(shù)從3個減少到了2個。

我們這樣定義一個partial()函數(shù):

function partial(fn, ...presetArgs) {
  return function partiallyApplied(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  }
}

partial()函數(shù)接收fn參數(shù),來表示被我們偏應(yīng)用實參(partially apply)的函數(shù)。接著,fn形參之后,presetArgs數(shù)組收集了后面?zhèn)魅氲膶崊?,保存起來稍后使用?/p>

我們創(chuàng)建并return了一個新的內(nèi)部函數(shù)(為了清晰明了,我們把它命名為partiallyApplied(..)),該函數(shù)中,laterArgs數(shù)組收集了全部實參。

使用箭頭函數(shù),則更為簡潔:

var partial =
  (fn, ...presetArgs) =>
    (...laterArgs) =>
      fn(...presetArgs, ...laterArgs);

使用偏函數(shù)的這種模式,我們重構(gòu)之前的代碼:

function ajax(url, data, callback) {
  // ..
}

var ajaxTest1 = partial(ajax, "http://www.test.com/test1");
var ajaxTest2 = partial(ajax, "http://www.test.com/test1");

再次思考beginTest()函數(shù),我們使用partial()來重構(gòu)它應(yīng)該怎么做呢?

function ajax(url, data, callback) {
  // ..
}

// 版本1
var beginTest = partial(ajax, "http://www.test.com/test1", {
  data: GLOBAL_TEST_1,
});

// 版本2
var ajaxTest1 = partial(ajax, "http://www.test.com/test1");
var beginTest = partial(ajaxTest1, {
  data: GLOBAL_TEST_1,
});
一次傳一個

相信你已經(jīng)在上述例子中看到了版本2比起版本1的優(yōu)勢所在了,沒錯,柯里化就是:將一個帶有多個參數(shù)的函數(shù)轉(zhuǎn)換為一次一個的函數(shù)的過程。每次調(diào)用函數(shù)時,它只接受一個參數(shù),并返回一個函數(shù),直到傳遞所有參數(shù)為止。

The process of converting a function that takes multiple arguments into a function that takes them one at a time.

Each time the function is called it only accepts one argument and returns a function that takes one argument until all arguments are passed.

假設(shè)我們已經(jīng)創(chuàng)建了一個柯里化版本的ajax()函數(shù)curriedAjax()

curriedAjax("http://www.test.com/test1")
  ({
    data: GLOBAL_TEST_1,
  })
  (function callback(data) {
    // dosomething
  });

我們將三次調(diào)用分別拆解開來,這也許有助于我們理解整個過程:

var ajaxTest1 = curriedAjax("http://www.test.com/test1");

var beginTest = ajaxTest1({
  data: GLOBAL_TEST_1,
});

var ajaxCallback = beginTest(function callback(data) {
  // dosomething
});
實現(xiàn)柯里化

那么,我們?nèi)绾蝸韺崿F(xiàn)一個自動的柯里化的函數(shù)呢?

var currying = function(fn) {
  var args = [];

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args); // 沒傳參數(shù)時,調(diào)用這個函數(shù)
    } else {
      [].push.apply(args, arguments); // 傳入了參數(shù),把參數(shù)保存下來
      return arguments.callee; // 返回這個函數(shù)的引用
    }
  }
}

調(diào)用上述currying()函數(shù):

var cost = (function() {
  var money = 0;
  return function() {
    for (var i = 0; i < arguments.length; i++) {
      money += arguments[i];
    }
    return money;
  }
})();

var cost = currying(cost);

cost(100); // 傳入了參數(shù),不真正求值
cost(200); // 傳入了參數(shù),不真正求值
cost(300); // 傳入了參數(shù),不真正求值

console.log(cost()); // 求值并且輸出600

上述函數(shù)是我之前的JavaScript設(shè)計模式與開發(fā)實踐讀書筆記之閉包與高階函數(shù)所寫的currying版本,現(xiàn)在仔細思考后發(fā)現(xiàn)仍舊有一些問題。

我們在使用柯里化時,要注意同時為函數(shù)預(yù)傳的參數(shù)的情況。

因此把上述柯里化函數(shù)更改如下:

var currying = function(fn) {
  var args = Array.prototype.slice.call(arguments, 1);

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args); // 沒傳參數(shù)時,調(diào)用這個函數(shù)
    } else {
      [].push.apply(args, arguments); // 傳入了參數(shù),把參數(shù)保存下來
      return arguments.callee; // 返回這個函數(shù)的引用
    }
  }
}

使用實例:

var cost = (function() {
  var money = 0;
  return function() {
    for (var i = 0; i < arguments.length; i++) {
      money += arguments[i];
    }
    return money;
  }
})();

var cost = currying(cost, 100);
cost(200); // 傳入了參數(shù),不真正求值
cost(300); // 傳入了參數(shù),不真正求值

console.log(cost()); // 求值并且輸出600

你可能會覺得每次都要在最后調(diào)用一下不帶參數(shù)的cost()函數(shù)比較麻煩,并且在cost()函數(shù)都要使用arguments參數(shù)不符合你的預(yù)期。我們知道函數(shù)都有一個length屬性,表明函數(shù)期望接受的參數(shù)個數(shù)。因此我們可以充分利用預(yù)傳參數(shù)的這個特點。

借鑒自mqyqingfeng:

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);
    }
  };
}

在上述函數(shù)中,我們在currying的返回函數(shù)中,每次把arguments.lengthfn.length作比較,一旦arguments.length達到了fn.length的數(shù)量,我們就去調(diào)用fn(return fn.apply(this, arguments);)

驗證:

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"]
bind方法的實現(xiàn)

使用柯里化,能夠很方便地借用call()或者apply()實現(xiàn)bind()方法的polyfill

Function.prototype.bind = Function.prototype.bind || function(context) {
  var me = this;
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return me.apply(contenxt, finalArgs);
  }
}

上述函數(shù)有的問題在于不能兼容構(gòu)造函數(shù)。我們通過判斷this指向的對象的原型屬性,來判斷這個函數(shù)是否通過new作為構(gòu)造函數(shù)調(diào)用,來使得上述bind方法兼容構(gòu)造函數(shù)。

Function.prototype.bind() by MDN如下說到:

綁定函數(shù)適用于用new操作符 new 去構(gòu)造一個由目標函數(shù)創(chuàng)建的新的實例。當一個綁定函數(shù)是用來構(gòu)建一個值的,原來提供的 this 就會被忽略。然而, 原先提供的那些參數(shù)仍然會被前置到構(gòu)造函數(shù)調(diào)用的前面。

這是基于MVC的JavaScript Web富應(yīng)用開發(fā)的bind()方法實現(xiàn):

Function.prototype.bind = function(oThis) {
  if (typeof this !== "function") {
    throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
  }

  var aArgs = Array.prototype.slice.call(arguments, 1),
    fToBind = this,
    fNOP = function() {},
    fBound = function() {
      return fToBind.apply(
        this instanceof fNOP && oThis ? this : oThis || window,
        aArgs.concat(Array.prototype.slice.call(arguments))
      );
    };

  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();

  return fBound;
};
反柯里化(uncurrying)

可能遇到這種情況:拿到一個柯里化后的函數(shù),卻想要它柯里化之前的版本,這本質(zhì)上就是想將類似f(1)(2)(3)的函數(shù)變回類似g(1,2,3)的函數(shù)。

下面是簡單的uncurrying的實現(xiàn)方式:

function uncurrying(fn) {
  return function(...args) {
    var ret = fn;

    for (let i = 0; i < args.length; i++) {
      ret = ret(args[i]); // 反復調(diào)用currying版本的函數(shù)
    }

    return ret; // 返回結(jié)果
  };
}

注意,不要以為uncurrying后的函數(shù)和currying之前的函數(shù)一模一樣,它們只是行為類似!

var currying = function(fn) {
  var args = Array.prototype.slice.call(arguments, 1);

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args); // 沒傳參數(shù)時,調(diào)用這個函數(shù)
    } else {
      [].push.apply(args, arguments); // 傳入了參數(shù),把參數(shù)保存下來
      return arguments.callee; // 返回這個函數(shù)的引用
    }
  }
}

function uncurrying(fn) {
  return function(...args) {
    var ret = fn;

    for (let i = 0; i < args.length; i++) {
      ret = ret(args[i]); // 反復調(diào)用currying版本的函數(shù)
    }

    return ret; // 返回結(jié)果
  };
}

var cost = (function() {
  var money = 0;
  return function() {
    for (var i = 0; i < arguments.length; i++) {
      money += arguments[i];
    }
    return money;
  }
})();

var curryingCost = currying(cost);
var uncurryingCost = uncurrying(curryingCost);
console.log(uncurryingCost(100, 200, 300)()); // 600
柯里化或偏函數(shù)有什么用?

無論是柯里化還是偏應(yīng)用,我們都能進行部分傳值,而傳統(tǒng)函數(shù)調(diào)用則需要預(yù)先確定所有實參。如果你在代碼某一處只獲取了部分實參,然后在另一處確定另一部分實參,這個時候柯里化和偏應(yīng)用就能派上用場。

另一個最能體現(xiàn)柯里化應(yīng)用的的是,當函數(shù)只有一個形參時,我們能夠比較容易地組合它們(單一職責原則(Single responsibility principle))。因此,如果一個函數(shù)最終需要三個實參,那么它被柯里化以后會變成需要三次調(diào)用,每次調(diào)用需要一個實參的函數(shù)。當我們組合函數(shù)時,這種單元函數(shù)的形式會讓我們處理起來更簡單。

歸納下來,主要為以下常見的三個用途:

延遲計算

參數(shù)復用

動態(tài)生成函數(shù)

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

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

相關(guān)文章

  • 掌握JavaScript函數(shù)柯里

    摘要:原文鏈接和都支持函數(shù)的柯里化函數(shù)的柯里化還與的函數(shù)編程有很大的聯(lián)系如果你感興趣的話可以在這些方面多下功夫了解相信收獲一定很多看本篇文章需要知道的一些知識點函數(shù)部分的閉包高階函數(shù)不完全函數(shù)文章后面有對這些知識的簡單解釋大家可以看看什么是柯里化 原文鏈接 Haskell和scala都支持函數(shù)的柯里化,JavaScript函數(shù)的柯里化還與JavaScript的函數(shù)編程有很大的聯(lián)系,如果你感興...

    DTeam 評論0 收藏0
  • JavaScript 函數(shù)式編程技巧 - 柯里

    摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。在一些函數(shù)式編程語言中,會定義一個特殊的占位變量。個人理解不知道對不對延遲執(zhí)行柯里化的另一個應(yīng)用場景是延遲執(zhí)行。不斷的柯里化,累積傳入的參數(shù),最后執(zhí)行。作為函數(shù)式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~ 1. 簡介 柯里化(Currying)...

    edgardeng 評論0 收藏0
  • JavaScript 函數(shù)式編程技巧 - 反柯里

    摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。而反柯里化,從字面講,意義和用法跟函數(shù)柯里化相比正好相反,擴大適用范圍,創(chuàng)建一個應(yīng)用范圍更廣的函數(shù)。作為函數(shù)式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 可以對照另外一篇介紹 JS 柯里化 的文章一起看~ 1. 簡介 柯里化,是固定部分參數(shù),返回一個接受剩余參數(shù)的函數(shù),也稱為部分計算函數(shù),目的是為了縮...

    zhjx922 評論0 收藏0
  • JavaScript函數(shù)式編程,真香之組合(一)

    摘要:組合的概念是非常直觀的,并不是函數(shù)式編程獨有的,在我們生活中或者前端開發(fā)中處處可見。其實我們函數(shù)式編程里面的組合也是類似,函數(shù)組合就是一種將已被分解的簡單任務(wù)組織成復雜的整體過程。在函數(shù)式編程的世界中,有這樣一種很流行的編程風格。 JavaScript函數(shù)式編程,真香之認識函數(shù)式編程(一) 該系列文章不是針對前端新手,需要有一定的編程經(jīng)驗,而且了解 JavaScript 里面作用域,閉...

    mengbo 評論0 收藏0
  • 高級函數(shù)技巧-函數(shù)柯里

    摘要:如果你對函數(shù)式編程有一定了解,函數(shù)柯里化是不可或缺的,利用函數(shù)柯里化,可以在開發(fā)中非常優(yōu)雅的處理復雜邏輯。同樣先看簡單版本的方法,以方法為例,代碼來自高級程序設(shè)計加強版實現(xiàn)上面函數(shù),可以換成任何其他函數(shù),經(jīng)過函數(shù)處理,都可以轉(zhuǎn)成柯里化函數(shù)。 我們經(jīng)常說在Javascript語言中,函數(shù)是一等公民,它們本質(zhì)上是十分簡單和過程化的。可以利用函數(shù),進行一些簡單的數(shù)據(jù)處理,return 結(jié)果,...

    shixinzhang 評論0 收藏0
  • Javascript中的柯里

    摘要:三使用場景場景性能優(yōu)化可以將一些模板代碼通過柯里化的形式預(yù)先定義好,例如這段代碼的作用就是根據(jù)瀏覽器的類型決定事件添加的方式。場景擴展能力中的方法,就是通過柯里化實現(xiàn)的四總結(jié)通過本文的介紹,相信你對柯里化已經(jīng)有一個全新的認識了。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 柯里化...

    HtmlCssJs 評論0 收藏0

發(fā)表評論

0條評論

tommego

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<