Javascript與柯里化

1、柯里化和柯南的關係是?

回答:若是我說「柯里化 == 柯南」呢?
衆人:博主,r u ok!? 是否是釣魚釣久了腦殼秀逗了哈?柯里化但是函數式編程中的一個技巧,而柯南是到哪兒哪兒死人、10年不老的神話般的存在。八竿子都打不到的,怎會相等呢??編程

回答:諸位,眼睛睜大點,是==, 不是===哦~
衆人:嗯哪,我眼睛已經瞪得燈泡大了,粑粑並無變冰淇淋啊?設計模式

回答:這不就結了嘛。我說的是弱等於==, 又不是強等於===. 你想那,JS的世界裏,0==false. 你看,這阿拉伯屆的數字0,和英美屆的單詞false不就相等了,這兩個傢伙也是八竿子都達不到的哦。
衆人這……好吧,村婦吵架道理多,恕咱們愚鈍,仍是看不出來「柯里化==柯南」,喔~除了那個「柯」字是同樣的,求解釋~~瀏覽器

回答:百科中柯里化解釋爲:「@#¥#¥#%*&……%@#¥」,太術語了,我鏡片看裂才知大概。若非要套用定義(見下面的①標註的引用),我的以爲:「柯里化」就像某些官員的把戲,官員要弄7個老婆,礙於國策(一夫一妻)以及年老弟衰,表面上就1個老婆,實際上剩下的6個暗地裏消化。代碼表示就是:app

 1 var currying = function(fn) {
 2     // fn 指官員消化老婆的手段
 3     var args = [].slice.call(arguments, 1);
 4     // args 指的是那個合法老婆
 5     return function() {
 6         // 已經有的老婆和新搞定的老婆們合成一體,方便控制
 7         var newArgs = args.concat([].slice.call(arguments));
 8         // 這些老婆們用 fn 這個手段消化利用,完成韋小寶前輩的壯舉並返回
 9         return fn.apply(null, newArgs);
10     };
11 };
12 
13 // 下爲官員如何搞定7個老婆的測試
14 // 得到合法老婆
15 var getWife = currying(function() {
16     var allWife = [].slice.call(arguments);
17     // allwife 就是全部的老婆的,包括暗渡陳倉進來的老婆
18     console.log(allWife.join(";"));
19 }, "合法老婆");
20 
21 // 得到其餘6個老婆
22 getWife("大老婆","小老婆","俏老婆","刁蠻老婆","乖老婆","送上門老婆");
23 
24 // 換一批老婆
25 getWife("超越韋小寶的老婆");

 

因而,結果就是:
官員-老婆-柯里化功能結果截圖
衆人:這與「柯南」童鞋有嘛關係?wordpress

① 柯里化(Currying),又稱部分求值(Partial Evaluation),是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。函數式編程

回答:莫急莫急,且聽臣妾一曲。百科上的定義是針對衆多函數式語言而言的,按照Stoyan Stefanov(《JavaScript Pattern》做者)的說法,所謂「柯里化」就是使函數理解並處理部分應用,套用上面官員例子,就是隻要有合法老婆和見不得的情婦就行,數目什麼的隨意。不過,若是這樣理解,老婆的例子就不符合國情,反而更加契合的理解是「柯南」。
衆人:哦?洗耳恭聽!函數

回答:柯南身子雖小,可是裏面住的倒是大柯南,也就是一個function裏面還有個function。不一樣柯南處理不一樣狀況,例如,小柯南能夠和…稍等,他女友叫什麼的忘了,我查查…哦,毛利蘭一塊兒洗澡澡;可是大柯南就不行。小柯南不能當面指正犯人,需藉助小五郎;可是,大柯南就能夠直接質問指出兇手。就相似於,內外function處理不一樣的參數。若是代碼表示就是(小柯南=smallKenan; 大柯南=bigKenan; 小柯南嗑藥會變大柯南):性能

 1 var smallKenan = function(action) {
 2     var bigKenan = function(doing) {
 3         var result = "";
 4         if (action === "take drugs") {
 5             if (doing === "bathWithGirlFriend") {
 6                 result = "尖叫,新一,你這個色狼,而後一巴掌,臉煮熟了~";
 7             } else if (doing === "pointOutKiller") {
 8                 result = "新一,這個案子就交給你的,快點找出誰是兇手吧~";
 9             }
10         } else {
11             if (doing === "bathWithGirlFriend") {
12                 result = "來吧,柯南,一塊兒洗澡吧~";
13             } else if (doing === "pointOutKiller") {
14                 result = "小孩子家,滾一邊去!";
15             }
16         }
17         console.log(result);
18         return arguments.callee; // 等同於return bigKenan
19     };
20     return bigKenan;
21 };
22 
23 // 小柯南吃藥了,而後和毛利蘭洗澡,兇案現場指證犯人;結果是……
24 smallKenan("take drugs")("bathWithGirlFriend")("pointOutKiller");

 

結果以下截圖:
柯南吃藥後洗澡指正凶犯結果學習

「吃藥」、「洗澡」、「指出兇手」就能夠當作三個參數,其中,「吃藥」確實是小柯南使用的,然後面的是「洗澡」、「指出兇手」雖然跟在smallKenan()後面,其實是大柯南使用的。這個就是柯里化,參數部分使用。外部函數處理部分應用,剩下的由外部函數的返回函數處理。測試

因此,我說「柯里化==柯南」不是胡扯吧~~
衆人:咱們擦~~,博主扯淡的功力TM如今更上一層樓啦,說得還真那麼回事!

2、柯里化有什麼做用?

衆人:整這麼多神乎其神的東西,有個毛線用?意淫一些所謂的技術,爲了技術而技術最讓人詬病了,我老老實實if else照樣腳本跑得槓槓的!

回答:唉,你們所言極是。蒼蠅蚊子嗡嗡不停也讓人詬病,可是,也是有其存在的理由的,咱們不妨看看~~
衆人

回答:貌似柯里化有3個常見做用:1. 參數複用2. 提早返回;3. 延遲計算/運行
1. 「參數複用」上面已經展現過了,官員老婆的例子就是,不管哪一個官員,都是須要一個合法老婆;經過柯里化過程,getWife()無需添加這個多餘的「合法老婆」參數。
2. 「提早返回」,很常見的一個例子,兼容現代瀏覽器以及IE瀏覽器的事件添加方法。咱們正常狀況可能會這樣寫:

 1 var addEvent = function(el, type, fn, capture) {
 2     if (window.addEventListener) {
 3         el.addEventListener(type, function(e) {
 4             fn.call(el, e);
 5         }, capture);
 6     } else if (window.attachEvent) {
 7         el.attachEvent("on" + type, function(e) {
 8             fn.call(el, e);
 9         });
10     } 
11 };

 

上面的方法有什麼問題呢?很顯然,咱們每次使用addEvent爲元素添加事件的時候,(eg. IE6/IE7)都會走一遍if...else if ...,其實只要一次斷定就能夠了,怎麼作?–柯里化。改成下面這樣子的代碼:

 1 var addEvent = (function(){
 2     if (window.addEventListener) {
 3         return function(el, sType, fn, capture) {
 4             el.addEventListener(sType, function(e) {
 5                 fn.call(el, e);
 6             }, (capture));
 7         };
 8     } else if (window.attachEvent) {
 9         return function(el, sType, fn, capture) {
10             el.attachEvent("on" + sType, function(e) {
11                 fn.call(el, e);
12             });
13         };
14     }
15 })();

 

初始addEvent的執行其實值實現了部分的應用(只有一次的if...else if...斷定),而剩餘的參數應用都是其返回函數實現的,典型的柯里化。

3. 「延遲計算」,通常而言,延遲計算或運行是沒有必要的,由於一天花10塊錢和月末花300塊錢沒什麼本質區別——只是內心好受點(溫水燉青蛙)。嘛,畢竟只是我的見解,您可能會不這麼認爲。舉個例子,我每週末都要去釣魚,我想知道我12月份4個週末總共釣了幾斤魚,把一些所謂的模式、概念拋開,咱們可能就會下面這樣實現:

 1 var fishWeight = 0;
 2 var addWeight = function(weight) {
 3     fishWeight += weight;
 4 };
 5 
 6 addWeight(2.3);
 7 addWeight(6.5);
 8 addWeight(1.2);
 9 addWeight(2.5);
10 
11 console.log(fishWeight);   // 12.5

 

每次addWeight都會累加魚的總重量。

如果有柯里化實現,則會是下面這樣:

var curryWeight = function(fn) {
    var _fishWeight = [];
    return function() {
        if (arguments.length === 0) {
            return fn.apply(null, _fishWeight);
        } else {
            _fishWeight = _fishWeight.concat([].slice.call(arguments));
        }
    }
};
var fishWeight = 0;
var addWeight = curryWeight(function() {
    var i=0; len = arguments.length;
    for (i; i<len; i+=1) {
        fishWeight += arguments[i];
    }
});

addWeight(2.3);
addWeight(6.5);
addWeight(1.2);
addWeight(2.5);
addWeight();    //  這裏才計算

console.log(fishWeight);    // 12.5

 

衆人:我勒個去,好高的樓啊,相比之下。

回答:確實,柯里化的實現彷佛囉嗦了點。老媽的囉嗦顯然不是用來消耗多餘的口水的。這裏的curryWeight方法囉嗦的意義在於柯里化的複用。比方說,我還想知道平均每次釣貨的重量,則:

 1 var averageWeight = 0;
 2 var addWeight = curryWeight(function() {
 3     var i=0; len = arguments.length;
 4     for (i; i<len; i+=1) {
 5         averageWeight += arguments[i]/len;
 6     }
 7 });
 8 
 9 addWeight(2.3);
10 addWeight(6.5);
11 addWeight(1.2);
12 addWeight(2.5);
13 addWeight();    //  這裏才計算
14 
15 console.log(averageWeight);    // 3.125

 

雖然延遲計算聽上去很高級,可是,恕我愚鈍,我想破了腦殼也沒想出哪一種狀況非要柯里化延遲計算實現才能顯著提升性能。能想到的好處就是參數個數隨意,比方說:

1 addWeight(2.3, 6.5);
2 addWeight(1.2, 2.5);

 

也是能夠的。

補充於翌日:經人提點,發現本身忘了個東西,ES5中的bind方法,用來改變Function執行時候的上下文(函數主體自己不執行,與call/apply直接執行並改變不一樣),本質上就是延遲執行。例如:

1 var obj = {
2     "name": "currying" 
3 },
4 fun = function() {
5     console.log(this.name);
6 }.bind(obj);
7 
8 fun(); // currying

從IE6~8的自定義擴展來看,其實現的機制就是柯里化(不考慮執行時的新增參數):

 1 if (!function() {}.bind) {
 2     Function.prototype.bind = function(context) {
 3         var self = this
 4             , args = Array.prototype.slice.call(arguments);
 5             
 6         return function() {
 7             return self.apply(context, args.slice(1));    
 8         }
 9     };
10 }

關於ES5中bind方法自定義可參見我以前的「ECMAScript 5中bind方法、自定義及小拓展」一文。

3、沒有問答的結束語

最近在看《JavaScript模式》一書,天哪,裏面出現的各類設計模式(如工廠模式、外觀模式、觀察者模式),一雙手都數不過來。並且這些名詞又很抽象,書一合,立刻大眼瞪小眼了。這種感受就像是,房間裏來了10個黑人,每一個黑人都有一個「¥%#…¥」的名字,好不容易勉強記住了,燈一關房間一黑,等再開燈的時候,每一個黑人的名字都變成…..」hello」了

其實這些模式在實際使用的時候,或多或少都使用過,當看到「**模式」概念的時候,咱們就會猛然驚起:「哦,原來這個就叫作‘觀察者模式’等」。如今要討論的問題是,咱們有沒有必要把這些「**模式」都記住呢,都理解其對應的核心呢?這個問題相似於,我能夠看懂NBA的籃球比賽,那我有沒有必要把各個球隊以及球隊的隊員都記住呢?

若是想成爲JS大神,從這個目標來看,這是須要的;比如優秀的籃球解說員必需要知道每一個球隊的名字、球員甚至周邊八卦。可是,現實很重要。若是連JS函數相關的基本東西都駕馭很差,顯然,硬是啃這些似懂非懂的概念只會形成混亂。若是你以爲能夠更近一步,先通透幾個本身習慣的熟悉的使用模式,足夠應付實際項目;其餘一些概念什麼的,更多的只是噱頭,實用性其實並不大。正如本文的柯里化,看上去很高級,彷佛也有點用處,然而JS的靈活性使得不少實現徹底擺脫「柯里化」這個概念的束縛,以更通俗易懂的方式實現。

然而,即便實用性不高,咱們仍是要有所瞭解,由於,你不知道何時會用到它。比方說CSS中的display:table;某些狀況下能夠解決一些棘手問題(secret!).

所以,本文的柯里化至少要知道個大概,若是你囫圇吞棗式的看到此處,只記得「柯里化==柯南」,你能夠再稍微靜點心從新看一下。個人筆頭要比嘴巴犀利的多,由於,文字我能夠靜心琢磨:「如何講述才能提起興趣,才能讓新手也能知其意會其形?」 ,長此以往,就造成了這種菊緊的風格——更適合新手閱讀,同時被「高人」不屑:「寫得太羅嗦啦,直接一句話不就完事啦!」還時不時來一句「鑑定完畢」,恩,挺適合去驗屍

柯里化函數是有一個通用的方法的,在官員那個例子(currying()方法就是)其實已經展現了,不重複展現,至於代碼的含義嘛,知者自知,不囉嗦。

很久沒在結尾扯這麼多亂七八糟的東西了,扯得內心像喝了常潤茶般舒暢。本文內容,邊學習變整理的,JS的概念我接觸的也很少,文中錯誤不免,歡迎指正,歡迎交流,互相學習。

(本篇完)

 

 

【本文轉載自】張鑫旭-鑫空間-鑫生活[http://www.zhangxinxu.com]

相關文章
相關標籤/搜索