反科里化的話題來自javascript之父Brendan Eich去年的一段twitter. 近幾天研究了一下,覺得這個東東非常有意思,分享一下。先忘記它的名字,看下它能做什麼.
不要小看這個功能,試想下,我們在寫一個庫的時候,時常會寫這樣的程式碼,拿webQQ的Jx庫舉例。
我們想要的,其實只是藉用Array原型鏈上的一些函數。並沒有必要去顯式的構造一個新的函數來改變它們的參數並且重新運算。
如果用uncurrying的方式顯然更加優雅和美妙,就像這樣:
還能做很多有趣和方便的事情.
甚至還能把call和apply方法都可以用uncurrying,把函數也當作普通資料來使用. 使得javascript中的函數呼叫方式更像它的前生scheme, 當函數名稱本身是個變數的時候, 這種呼叫方法特別方便.
scheme裡面呼叫函數是這樣:
javascript裡可以寫的很接近.
再看看jquery庫,由於jquery對象( 即通過$()創建的對象)是一個對象冒充的偽數組,它有length屬性,並且能夠通過下標找出對應的元素,當需要為jquery物件增加一個成員時, 偽代碼大概是:
如果用uncurrying的話, 就可以
借用了array對象的
借用了array對象的pushpush函數, 讓引擎去自動管理陣列成員與length屬性.
而且可以一次把需要的函數全部借過來, 一勞永逸.一段測試程式碼:
總的來說, 使用uncurrying技術, 可以讓任何物件擁有原生物件的方法. 好了,如果到這裡還是沒有引起你的興趣,那麼你可以去幹點別的了。 接下來一步一步來看看原理以及實現。 在了解反currying化這個奇怪的名字之前,我們得先搞清楚currying。 維基百科上的定義:科里化( currying ); 又稱部分求值,是把接受多個參數的函數變換成接受一個單一參數的函數,並且返回接受餘下的參數並且返回結果的新函數的技術。通俗點講,currying有點類似買房子時分期付款的方式,先給一部分首付( 一部分參數 ), 返回一個存摺( 返回一個函數 ),合適的時候再給餘下的參數並且求值計算。
來看看我們都用過的currying, 我們經常在綁定context 的時候實現一個Function.prototype.bind函數.
高階函數是實現currying的基礎, 所謂高階函數至少滿足這2個特性:1, 函數可以當作參數傳遞,2, 函數可以當作回傳值。Javascript在設計之初,參考了許多scheme語言的特性。而scheme是函數式語言鼻祖lisp的2大方言之一,所以javascript也擁有一些函數式語言的特性,包括高階函數,閉包,lambda表達式等。
當javascript中的函數傳回另一個函數,此時會形成一個閉包,而在閉包中就可以保存第一次運算的參數,我們用這個思想,來寫一個通用的currying函數。
我們約定, 當傳入參數時候, 繼續currying化, 參數為空時才開始求值.
🎜假設在實現一個計算每月花費的函數, 每天結束前我們都要記錄今天花了多少錢, 但我們只關心月底的花費總值, 無需每天計算一次.🎜🎜🎜🎜使用currying函數, 便可以延遲到最後一刻才一起計算, 好處不言而喻, 在很多場合可以避免無謂的計算, 節省性能, 也是實現惰性求值的一種方案.
好了,現在才走進正題,
curring是預先填入一些參數.
反curring就是把原來已經固定的參數或者this上下文等當作參數延遲到未來傳遞.
其實就是搞這樣一個事情,將:
1
obj.foo( arg1 ) //foo本來是只在obj上的函數. 就像push原本只在Array.prototype上
轉化成這樣的形式
1
foo( obj, arg1) / / 跟我們舉的第一個例子一樣.將[].push轉換成push( [] )
就像原本是接在電視插頭上的插座,把它拆下來之後,其實也能用來接冰箱。
Ecma上Array和String的每個原型方法後面都有這麼一段話,例如push:
NOTE The push function is intentionally generic; it does not require that its this value be an Array object.The be transferred to other kinds of objects for use as a method. Whether the concat function can be applied.
Javascript為什麼要這樣設計, 我們先來複習下動態語言中重要的鴨子類型思想.
說個故事:
很久以前有個皇帝喜歡聽鴨子呱呱叫,於是他召集大臣組一個一千隻鴨子的合唱團。大臣把全國的鴨子都抓來了,最後始終還差一隻。有天終於來了一隻自告奮勇的雞,這隻雞說它也會呱呱叫,好吧在這個故事的設定裡,它確實會呱呱叫。 後來故事的發展很明顯,這隻雞混到了鴨子的合唱團。 — 皇帝只是想聽呱呱叫,他才不在乎你是鴨子還是雞呢。
這個就是鴨子類型的概念,在javascript裡面,很多函數都不做物件的類型檢測,而是只關心這些物件能做什麼。
Array建構器和String建構器的prototype上的方法就被刻意設計成了鴨子類型。這些方法不會對this的資料類型做任何校驗。這也就是為什麼arguments可以假扮array呼叫push方法.
看下v8引擎裡面Array.prototype.push的程式碼:
function ArrayPush() {
_thisf var m = %_ArgumentsLength();
for (var i = 0; i
this[i+n] = %_Arguments(i); //屬性拷貝
this.thislength = n + m; //修正length
return this.length;
}
}
可以看到,ArrayPush方法並沒有對this的類型做任何顯示的限制,所以理論上任何物件都可以傳入ArrayPush這個訪客。
我們需要解決的只剩下一個問題, 如何透過一種通用的方式來使得一個物件可以冒充array物件。
真正的實現程式碼其實很簡單:
這段程式碼雖然很簡單, 初次理解的時候還是有點費力. 我們拿push的例子看看它發生了什麼.
var push = Arvar push = Arvar .prototype.push.uncurrying();
push( obj, 'first' );