作為函數語言程式設計語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。
可以對照另外一篇介紹 JS 柯里化 的文章一起看~
1. 簡介
柯里化,是固定部分引數,返回一個接受剩餘引數的函式,也稱為部分計算函式,目的是為了縮小適用範圍,建立一個針對性更強的函式。核心思想是把多引數傳入的函式拆成單引數(或部分)函式,內部再返回撥用下一個單引數(或部分)函式,依次處理剩餘的引數。
而反柯里化,從字面講,意義和用法跟函式柯里化相比正好相反,擴大適用範圍,建立一個應用範圍更廣的函式。使本來只有特定物件才適用的方法,擴充套件到更多的物件。
2. 實現
先來看看反柯里化的通用實現吧~
Function.prototype.unCurrying = function() {
const self = this
return function(...rest) {
return Function.prototype.call.apply(self, rest)
}
}
複製程式碼
解釋下:
- 為Function原型新增
uncurrying
方法,並在執行的時候儲存執行unCurrying
的方法到self - 借用apply把要借用的函式作為this環境賦給call,並傳入之後的形參作為引數執行
還有一個實現:
Function.prototype.unCurrying = function() {
return this.call.bind(this)
}
複製程式碼
如果你覺得把函式放在Function.prototype上不太好,也可以這樣:
function unCurrying(fn) {
return function(tar, ...argu) {
return fn.apply(tar, argu)
}
}
複製程式碼
3. 使用
3.1 簡單使用
用unCurrying
通用實現簡單的實用一下試試:
Function.prototype.unCurrying = function() {
const self = this // 這裡的self就是Array.prototype.push方法
return function(...rest) { // rest為傳入的兩層引數[[1,2,3],4]
return Function.prototype.call.apply(self, rest)
}
}
const push = Array.prototype.push.unCurrying()
~function(...rest) { // rest:[1,2,3]
push(rest, 4)
console.log(rest) // [1, 2, 3, 4]
}(1, 2, 3)
複製程式碼
3.2 借用其他方法
反柯里化其實反映的是一種思想,即擴大方法的適用範圍,仍然呼叫剛剛的通用unCurrying
方法借用push方法:
const push = Array.prototype.push.unCurrying()
const obj = { a: '嘻嘻' }
push(obj, '呵呵', '哈哈', '嘿嘿')
console.log(obj) // { '0': '呵呵', '1': '哈哈', '2': '嘿嘿', a: '嘻嘻', length: 3 }
複製程式碼
相當於obj.push(...)
,obj不僅多了類似於陣列一樣以數字作為索引的屬性,還多了個類似於陣列的length屬性,讓引擎自動管理陣列成員和length屬性;(文後有V8引擎實現push方法的原始碼)
這樣一個陣列的push方法就被借用出來,可以應用於任何其他物件了。
只要是方法,unCurrying
就可以借用,call
方法也可以:
var call = Function.prototype.call.unCurrying();
function $(id) {
return this.getElementById(id);
}
call($, document, 'demo') // #demo 元素
複製程式碼
相當於document.$('demo')
,成功的借用了call方法,當然可以把document改成你希望作為this繫結到$的任何物件,比如{ getElementById:T=>console.log(T+'呃') } // demo呃
3.3 借用自己
unCurrying
本身也是方法,也可以借用自己...-。-
const unCurrying = Function.prototype.unCurrying.unCurrying()
const map = unCurrying(Array.prototype.map)
map({ 0: 4, 1: 'a', 2: null, length: 3 }, n => n + n) // [8, "aa", 0]
複製程式碼
神奇吧~
4. 總結
簡單說,函式柯里化就是對高階函式的降階處理,縮小適用範圍,建立一個針對性更強的函式。舉栗子:
function(arg1,arg2) // => function(arg1)(arg2)
function(arg1,arg2,arg3) // => function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4) // => function(arg1)(arg2)(arg3)(arg4)
function(arg1,arg2,…,argn) // => function(arg1)(arg2)…(argn)
複製程式碼
而反柯里化就是反過來,增加適用範圍,讓方法使用場景更大。使用unCurrying
, 可以把原生方法借出來,讓任何物件擁有原生物件的方法。舉個栗子:
obj.func(arg1, arg2) // => func(obj, arg1, arg2)
複製程式碼
也可以這樣理解: 柯里化是在運算前提前傳參,可以傳遞多個引數; 反柯里化是延遲傳參,在運算時把原來已經固定的引數或者this上下文等當作引數延遲到未來傳遞。
附:
V8引擎中Array.prototype.push方法原始碼實現:
function ArrayPush() {
var n = TO_UINT32(this.length);
var m = %_ArgumentsLength();
for (var i = 0; i < m; i++) {
this[i + n] = %_Arguments(i); // 屬性拷貝
this.length = n + m; // 修正length
return this.length;
}
}
複製程式碼
網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~
參考:
PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~