很長時間沒有更新,一方面是工做比較忙,另外一方面本身也處於一個學習的過程。接下來應該會逐漸恢復到穩定更新的狀態,分享一些有趣的知識點以及我我的的思考。感興趣的朋友能夠關注下呀!javascript
若是有不對的地方麻煩你們斧正,我會及時更新,感謝。前端
博客地址 🍹🍰 fe-codevue
bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被 bind 的第一個參數指定,其他的參數將做爲新函數的參數供調用時使用。 —— MDNjava
bind 方法想必你們都很熟悉,一般用來作 this 的綁定,它會返回一個新函數。可是還有一點,咱們每每會忽略掉:其他的參數將做爲新函數的參數供調用時使用。node
栗子:git
function add(a, b) {
return a + b;
}
const addBind = add.bind(null, 1);
addBind(2); // 3
複製代碼
顯而易見,bind 調用時傳入的參數會在 addBind 調用時和新參數一塊兒傳入。github
那這是怎麼作到的呢?咱們簡單看下 bind 的 Polyfill 版本,網絡上相似的實現也有不少。面試
Function.prototype.mybind = function(context, ...args) {
const f = this;
const fpro = function() {};
const fBound = function(..._arg) {
// fpro.prototype.isPrototypeOf(this) 判斷是不是 new 調用的 fBound
return f.apply(fpro.prototype.isPrototypeOf(this)
? this
: context,
[...args, ..._arg]);
};
if (this.prototype) {
fpro.prototype = this.prototype;
}
fBound.prototype = new fpro();
return fBound;
};
複製代碼
能夠看到,bind 實際返回的是相似於 fBound 的函數,咱們簡化一下看看。mongodb
// 刪減部分代碼
// f
// args
const fBound = function(..._arg) {
return f.apply(null, [...args, ..._arg]);
};
複製代碼
其實就是利用閉包保存了上文的參數,最後再一塊兒傳給目標函數使用。這樣若是不考慮 this,不使用 bind,咱們能夠直接把 add 函數改爲這樣:編程
function add(a) {
return function (b) {
return a + b;
}
}
add(1)(2); // 2
複製代碼
是否是很熟悉,一道很常見的面試題,這就是咱們要說的柯里化。
在計算機科學中,柯里化(英語:Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。—— 維基百科
curry 實際上是函數式編程中很是重要的一個思想,經過簡單的局部調用,讓函數具備預加載能力以及減小樣板代碼。
正由於咱們有相似於add(1)(2)
這樣的需求,可是每次手動實現又很繁瑣,因此可使用一個特殊的 curry 函數,來幫助咱們達到需求(在 lodash 等一些函數庫中均有相似實現,感興趣的同窗能夠去看看源碼)。
function curry(fn) {
const len = fn.length;
return function bindfn() {
if(arguments.length < len) {
return bindfn.bind(null, ...arguments);
// 關鍵:保存參數,並在調用時和後面的參數一塊兒傳入 bindfn
} else {
return fn.call(null, ...arguments);
}
}
}
複製代碼
這個實現方案正是利用咱們上面提到的 bind 的特性,因此咱們再要實現 add 需求的時候就能夠這樣作:
// 普通寫法
function add(a, b) {
return a + b
}
const curryAdd = curry(add);
curryAdd(1)(2); // 3
複製代碼
不使用 bind 咱們要怎麼實現一個 curry 呢?
function _curry(fn) {
const len = fn.length;
function warp (..._arguments) {
let _arg = _arguments;
// 容許一次傳完全部參數,雖然這和不用 curry 同樣 add(1, 2);
if (_arg.length >= len) {
return fn(..._arg);
}
function fnc(...args) {
_arg = [..._arg, ...args];
if (_arg.length < len) { // 參數不夠,繼續返回函數
return fnc;
} else { // 參數足夠,執行原函數
return fn(..._arg);
}
}
return fnc;
}
return warp;
}
複製代碼
可是需求每每是多變的,以上兩種方案,咱們都是用形參和實參的個數來判斷的函數是否須要執行,可是若是這種需求呢?
add(1)(2)() // 3
、add(1)(2)(3)() // 6
,參數不肯定且只有手動調用()
,纔會執行函數。很容易會想到須要改判斷函數返回的判斷條件。
function _curry1(fn) {
function warp (..._arguments) {
let _arg = _arguments;
function fnc(...args) {
_arg = [..._arg, ...args];
if (args.length > 0) { // 經過判斷當前是否傳入了參數,來決定函數返回
return fnc;
} else {
return fn(..._arg);
}
}
return fnc;
}
return warp;
}
複製代碼
固然,這種 add 函數自己也得作調整,用來知足任意參數累加的需求。
function addInfinity(...args) {
return args.reduce((pre, cur) => pre + cur);
}
複製代碼
前面囉嗦這麼多固然不是爲了水這一篇文章,並且add(1)(2)
這種東西除了能應付面試還能作什麼?咱們真正須要的,仍是實際應用到業務中。
相信你們都寫過或者看過相似的代碼。
function request(url, params) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(url), 1000);
})
}
//
function getData(params) {
return request('/api/getData', params);
}
function setData(params) {
return request('/api/setData', params);
}
//...
複製代碼
這就很是適合用 curry 來作處理,減小樣板代碼。
const _curryRequest = _curry(request);
const getData = _curryRequest('/api/getData'); // 默認入參
const setData = _curryRequest('/api/setData');
複製代碼
數組的 map 函數你們都用過,簡單看一個場景。
[1,2,3].map(x => 2 * x);
複製代碼
若是但願這個是一個通用型的功能應該怎麼處理呢?很容易會想到這麼寫。
function map(fn, arr) {
return arr.map(fn);
}
function multiply2(x) {
return 2 * x;
}
// map(multiply2, arr);
複製代碼
好像看起來很不錯,簡潔易用。那換成 curry 咱們來看下。
const mapMultiply2 = curry(map, multiply2);
// 可能有同窗會發現,以前寫的 curry 函數並不支持這種寫法。沒錯,不過稍微處理一下就好,我這裏就不作處理了,你們能夠本身試試。
// mapMultiply2(arr);
複製代碼
能夠看見,curry 更利於咱們去抽取函數,組合函數,讓咱們把更多的精力放在 multiply2 上。
到這裏,這篇文章就結束了。相信你們對 curry 也有了一些瞭解,好比,經過局部調用,讓函數具備預加載能力以及減小樣板代碼。固然,關於柯里化的東西其實還不少,我這裏講的比較淺顯,之後有機會再分享這方面的東西。
qq前端交流羣:960807765,歡迎各類技術交流,期待你的加入;
微信羣:有須要的同窗能夠加我好友(q1324210213),我拉你入羣。
若是你看到了這裏,且本文對你有一點幫助的話,但願你能夠動動小手支持一下做者,感謝🍻。文中若有不對之處,也歡迎你們指出,共勉。好了,又耽誤你們的時間了,感謝閱讀,下次再見!
往期文章:
感興趣的同窗能夠關注下個人公衆號 前端發動機,好玩又有料。