<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          JavaScript函數柯里化

          時間:?2017-11-24閱讀:?720標簽:?柯里化作者:?j2ue

          什么是柯里化?

          官方的說法

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

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


          方便的理解

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

          如果我們需要實現一個求三個數之和的函數:

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

          用ES6的箭頭函數,我們可以將上面的add實現成這樣:

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

          好像使用箭頭函數更清晰了許多。


          偏函數?

          來看這個函數:

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

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

          • 在調用ajax()函數時,傳入全局URL常量。
          • 創建一個已經預設URL實參的函數引用。

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

          對于第一種做法,我們可能產生如下調用方式:

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

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

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

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

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

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

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

          partial()函數接收fn參數,來表示被我們偏應用實參(partially apply)的函數。接著,fn形參之后,presetArgs數組收集了后面傳入的實參,保存起來稍后使用。

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

          使用箭頭函數,則更為簡潔:

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

          使用偏函數的這種模式,我們重構之前的代碼:

          function ajax(url, data, callback) {
            // ..
          }
          
          var ajaxTest1 = partial(ajax, 'http://www.test.com/test1');
          var ajaxTest2 = partial(ajax, 'http://www.test.com/test1');

          再次思考beginTest()函數,我們使用partial()來重構它應該怎么做呢?

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


          一次傳一個

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

          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.

          假設我們已經創建了一個柯里化版本的ajax()函數curriedAjax():

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

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

          var ajaxTest1 = curriedAjax('http://www.test.com/test1');
          
          var beginTest = ajaxTest1({
            data: GLOBAL_TEST_1,
          });
          
          var ajaxCallback = beginTest(function callback(data) {
            // dosomething
          });


          實現柯里化

          那么,我們如何來實現一個自動的柯里化的函數呢?

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

          調用上述currying()函數:

          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); // 傳入了參數,不真正求值
          cost(200); // 傳入了參數,不真正求值
          cost(300); // 傳入了參數,不真正求值
          
          console.log(cost()); // 求值并且輸出600

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

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

          因此把上述柯里化函數更改如下:

          var currying = function(fn) {
            var args = Array.prototype.slice.call(arguments, 1);
          
            return function() {
              if (arguments.length === 0) {
                return fn.apply(this, args); // 沒傳參數時,調用這個函數
              } else {
                [].push.apply(args, arguments); // 傳入了參數,把參數保存下來
                return arguments.callee; // 返回這個函數的引用
              }
            }
          }

          使用實例:

          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); // 傳入了參數,不真正求值
          cost(300); // 傳入了參數,不真正求值
          
          console.log(cost()); // 求值并且輸出600

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

          借鑒自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);
              }
            };
          }

          在上述函數中,我們在currying的返回函數中,每次把arguments.length和fn.length作比較,一旦arguments.length達到了fn.length的數量,我們就去調用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方法的實現

          使用柯里化,能夠很方便地借用call()或者apply()實現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);
            }
          }

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

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

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

          這是基于MVC的JavaScript Web富應用開發的bind()方法實現:

          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)

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

          下面是簡單的uncurrying的實現方式:

          function uncurrying(fn) {
            return function(...args) {
              var ret = fn;
          
              for (let i = 0; i < args.length; i++) {
                ret = ret(args[i]); // 反復調用currying版本的函數
              }
          
              return ret; // 返回結果
            };
          }

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

          var currying = function(fn) {
            var args = Array.prototype.slice.call(arguments, 1);
          
            return function() {
              if (arguments.length === 0) {
                return fn.apply(this, args); // 沒傳參數時,調用這個函數
              } else {
                [].push.apply(args, arguments); // 傳入了參數,把參數保存下來
                return arguments.callee; // 返回這個函數的引用
              }
            }
          }
          
          function uncurrying(fn) {
            return function(...args) {
              var ret = fn;
          
              for (let i = 0; i < args.length; i++) {
                ret = ret(args[i]); // 反復調用currying版本的函數
              }
          
              return ret; // 返回結果
            };
          }
          
          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


          柯里化或偏函數有什么用?

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

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

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

          • 延遲計算
          • 參數復用
          • 動態生成函數

          原文來源:https://segmentfault.com/a/1190000012145489

          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/167

          理解Javascript的柯里化

          函數式編程是一種如今比較流行的編程范式,它主張將函數作為參數進行傳遞,然后返回一個沒有副作用的函數,說白了,就是希望一個函數只做一件事情。這種編程思想涵蓋了三個重要的概念:

          Js之函數柯里化

          最近在社區閱讀技術博客的時候偶然間看到了函數柯里化幾個字,還有要求手寫js函數柯里化,心想是柯里化是什么高級的東西?沒聽說過啊?就帶著問題出發,專門去學習了一下,做了一些整理。

          javascript實現函數柯里化與反柯里化

          把接收多個參數的函數變換成接收一個單一參數(最初函數的第一個參數)的函數,并返回接受剩余的參數而且返回結果的新函數的技術。其由數學家Haskell Brooks Curry提出,并以curry命名。

          Js柯里化

          柯里化(Currying),又稱部分求值(Partial Evaluation),是把接收多個參數的函數變成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受剩余的參數而且返回結果的新函數的技術。

          js高級函數技巧-函數柯里化

          我們經常說在Javascript語言中,函數是“一等公民”,它們本質上是十分簡單和過程化的。可以利用函數,進行一些簡單的數據處理,return 結果,或者有一些額外的功能,需要通過使用閉包來實現,最后經常會return 匿名函數。

          js函數柯里化的延伸

          如果我們需要設計一個函數來計算每個月的開銷,在每天結束之前,我們需要記錄當天花費了多少。在月底的時候計算出這個月一共開銷了多少? 可以看出在性能上和方式二差不多,但是這樣做更加通俗易懂了,當然在實際開發中,我們一般會封裝為對象。

          柯里化與反柯里化

          柯里化,是一個逐步接收參數的過程。在接下來的剖析中,你會深刻體會到這一點。 反柯里化,是一個泛型化的過程。它使得被反柯里化的函數,可以接收更多參數。目的是創建一個更普適性的函數,可以被不同的對象使用。

          javascript偏函數應用與柯里化的區別

          很多剛剛了解函數式編程的人會對偏函數應用(partial application)和柯里化(currying)之間的區別感到困惑。實際上,直到現在也很少在 JavaScript 中看到柯里化的實際使用,許多叫curry()的工具函數并不是柯里化函數。它們其實是偏函數!

          fly63.com版權所有,內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网