<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          柯里化與反柯里化

          共 4648字,需瀏覽 10分鐘

           ·

          2022-04-29 05:47

          作者:我是leon

          地址:https://juejin.cn/post/6844903645222273037

          前言

          柯里化,可以理解為提前接收部分參數(shù),延遲執(zhí)行,不立即輸出結(jié)果,而是返回一個(gè)接受剩余參數(shù)的函數(shù)。因?yàn)檫@樣的特性,也被稱為部分計(jì)算函數(shù)。柯里化,是一個(gè)逐步接收參數(shù)的過程。在接下來的剖析中,你會(huì)深刻體會(huì)到這一點(diǎn)。
          反柯里化,是一個(gè)泛型化的過程。它使得被反柯里化的函數(shù),可以接收更多參數(shù)。目的是創(chuàng)建一個(gè)更普適性的函數(shù),可以被不同的對象使用。有鳩占鵲巢的效果。

          一、柯里化

          1.1 例子

          實(shí)現(xiàn)?add(1)(2, 3)(4)() = 10?的效果

          依題意,有兩個(gè)關(guān)鍵點(diǎn)要注意:
          • 傳入?yún)?shù)時(shí),代碼不執(zhí)行輸出結(jié)果,而是先記憶起來

          • 當(dāng)傳入空的參數(shù)時(shí),代表可以進(jìn)行真正的運(yùn)算

          完整代碼如下:

          function currying(fn){  var allArgs = [];
          return function next(){ var args = [].slice.call(arguments);
          if(args.length > 0){ allArgs = allArgs.concat(args); return next; }else{ return fn.apply(null, allArgs); } } }var add = currying(function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum;});

          1.2 記憶傳入?yún)?shù)

          由于是延遲計(jì)算結(jié)果,所以要對參數(shù)進(jìn)行記憶。
          這里的實(shí)現(xiàn)方式是采用閉包。

          function currying(fn){  var allArgs = [];
          return function next(){ var args = [].slice.call(arguments);
          if(args.length > 0){ allArgs = allArgs.concat(args); return next; } } }

          當(dāng)執(zhí)行var add = currying(...)時(shí),add變量已經(jīng)指向了next方法。此時(shí),allArgsnext方法內(nèi)部有引用到,所以不能被GC回收。也就是說,allArgs在該賦值語句執(zhí)行后,一直存在,形成了閉包。
          依靠這個(gè)特性,只要把接收的參數(shù),不斷放入allArgs變量進(jìn)行存儲(chǔ)即可。
          所以,當(dāng)arguments.length > 0時(shí),就可以將接收的新參數(shù),放到allArgs中。
          最后返回next函數(shù)指針,形成鏈?zhǔn)秸{(diào)用。


          1.3 判斷觸發(fā)函數(shù)執(zhí)行條件

          題意是,空參數(shù)時(shí),輸出結(jié)果。所以,只要判斷arguments.length == 0即可執(zhí)行。
          另外,由于計(jì)算結(jié)果的方法,是作為參數(shù)傳入currying函數(shù),所以要利用apply進(jìn)行執(zhí)行。
          綜合上述思考,就可以得到以下完整的柯里化函數(shù)。

          function currying(fn){
          function currying(fn){ var allArgs = []; // 用來接收參數(shù)
          return function next(){ var args = [].slice.call(arguments);
          // 判斷是否執(zhí)行計(jì)算 if(args.length > 0){ allArgs = allArgs.concat(args); // 收集傳入的參數(shù),進(jìn)行緩存 return next; }else{ return fn.apply(null, allArgs); // 符合執(zhí)行條件,執(zhí)行計(jì)算 } } }


          1.4 總結(jié)

          柯里化,在這個(gè)例子中可以看出很明顯的行為規(guī)范:

          • 逐步接收參數(shù),并緩存供后期計(jì)算使用

          • 不立即計(jì)算,延后執(zhí)行

          • 符合計(jì)算的條件,將緩存的參數(shù),統(tǒng)一傳遞給執(zhí)行方法


          1.5 擴(kuò)展

          實(shí)現(xiàn)?add(1)(2, 3)(4)(5) = 15?的效果。
          很多人這里就犯嘀咕了:我怎么知道執(zhí)行的時(shí)機(jī)?
          其實(shí),這里有個(gè)忍者技藝:valueOftoString。
          js在獲取當(dāng)前變量值的時(shí)候,會(huì)根據(jù)語境,隱式調(diào)用valueOftoString方法進(jìn)行獲取需要的值。


          那么,實(shí)現(xiàn)起來就很簡單了。

          function currying(fn){  var allArgs = [];
          function next(){ var args = [].slice.call(arguments); allArgs = allArgs.concat(args); return next; } // 字符類型 next.toString = function(){ return fn.apply(null, allArgs); }; // 數(shù)值類型 next.valueOf = function(){ return fn.apply(null, allArgs); }
          return next;}var add = currying(function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum;});

          二、反柯里化

          2.1 例子

          有以下輕提示類?,F(xiàn)在想要單獨(dú)使用其show方法,輸出新對象obj中的內(nèi)容。

          // 輕提示function Toast(option){  this.prompt = '';}Toast.prototype = {  constructor: Toast,  // 輸出提示  show: function(){  console.log(this.prompt);  }};
          // 新對象var obj = { prompt: '新對象'};

          用反柯里化的方式,可以這么做

          function unCurrying(fn){  return function(){    var args = [].slice.call(arguments);    var that = args.shift();    return fn.apply(that, args);  }}
          var objShow = unCurrying(Toast.prototype.show);
          objShow(obj); // 輸出"新對象"

          2.2 反柯里化的行為

          • 非我之物,為我所用

          • 增加被反柯里化方法接收的參數(shù)


          在上面的例子中,Toast.prototype.show方法,本來是Toast類的私有方法。跟新對象obj沒有半毛錢關(guān)系。
          經(jīng)過反柯里化后,卻可以為obj對象所用。
          為什么能被obj所用,是因?yàn)閮?nèi)部將Toast.prototype.show的上下文重新定義為obj。也就是用apply改變了this指向。
          而實(shí)現(xiàn)這一步驟的過程,就需要增加反柯里化后的objShow方法參數(shù)。


          2.3 另一種反柯里化的實(shí)現(xiàn)

          Function.prototype.unCurrying = function(){  var self = this;  return function(){    return Function.prototype.call.apply(self, arguments);  }}
          // 使用var objShow = Toast.prototype.show.unCurrying();objShow(obj);

          這里的難點(diǎn),在于理解Function.prototype.call.apply(self, arguments);。
          可以分拆為兩步:

          1)?Function.prototype.call.apply(...)的解析

          可以看成是callFunction.apply(...)。這樣,就清晰很多。callFunctionthis指針,被apply修改為self。然后執(zhí)行callFunction?->?callFunction(arguments)

          2)?callFunction(arguments)的解析

          call方法,第一個(gè)參數(shù),是用來指定this的。所以callFunction(arguments)?->?callFunction(arguments[0], arguments[1-n])。
          由此可以得出,反柯里化后,第一個(gè)參數(shù),是用來指定this指向的。


          3)為什么要用apply(self, arguments)?如果使用apply(null, arguments),因?yàn)?/span>null對象沒有call方法,會(huì)報(bào)錯(cuò)。

          三、實(shí)戰(zhàn)

          3.1 判斷變量類型(反柯里化)

          var fn = function(){};var val = 1;
          if(Object.prototype.toString.call(fn) == '[object Function]'){ console.log(`${fn} is function.`);}
          if(Object.prototype.toString.call(val) == '[object Number]'){ console.log(`${val} is number.`);}
          上述代碼,用反柯里化,可以這么寫:
          var fn = function(){};var val = 1;var toString = Object.prototype.toString.unCurrying();
          if(toString(fn) == '[object Function]'){ console.log(`${fn} is function.`);}
          if(toString(val) == '[object Number]'){ console.log(`${val} is number.`);}

          3.2 監(jiān)聽事件(柯里化)

          function nodeListen(node, eventName){  return function(fn){    node.addEventListener(eventName, function(){      fn.apply(this, Array.prototype.slice.call(arguments));    }, false);  }}
          var bodyClickListen = nodeListen(document.body, 'click');bodyClickListen(function(){ console.log('first listen');});
          bodyClickListen(function(){ console.log('second listen');});

          使用柯里化,優(yōu)化監(jiān)聽DOM節(jié)點(diǎn)事件。addEventListener三個(gè)參數(shù)不用每次都寫。

          后記

          其實(shí),反柯里化和泛型方法一樣,只是理念上有一些不同而已。理解這種思維即可


          瀏覽 28
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线欧美日 | 老汉色老汉首页aV亚洲 | 青娱乐91视频 | 免费A级视频 | 亚洲性爱电影在线免费观看 |