什麼是柯里化
在電腦科學中,柯里化(Currying)是一種技術(技巧),能夠把本來接受 n 個引數的函式A,轉換成只接收一個引數的函式B(B中的唯一引數,就是A的多個引數中的 第一個 引數)。
然後新函式B返回的,還是一個函式,記為C(注意原A中返回的不一定是啥)。這個C函式又只能接收一個引數(即為A函式的 第二個 引數)......依次不斷往復,直到返回一個接收A的第 n 個引數的函式F,F中判斷這是最後一個函式了,執行,然後返回最終的值。
很繞口,看個例子:
// 我們想要實現一個把引數相加,返回和的函式
function adder(a, b) {
return a + b;
}
adder(4,6);
// 結果為:10
複製程式碼
當然也可以這樣寫(非柯里化)
function adder() {
var sum = 0;
for(var i=0;i<arguments.length;i++)
sum += arguments[i];
return sum;
}
adder(4,6);
// 10
複製程式碼
用柯里化實現
var adder = function(num) {
var n = num; // 對應的為引數4
return function(y) {
return n + y; // y為6 返回 4+6
}
}
adder(4)(6) // 10
複製程式碼
從上面可以看到,本來adder是傳2個引數adder(4, 6),柯里化的方式後,就成為了adder(4)(6),即每次接受一個引數並放回一個函式,然後鏈式的執行。。。
可以看到,adder函式中有著內部函式,內部函式一直引用著n,這就形成了一個閉包,所以柯里化是閉包的應用之一,面試時直接可以答~~~
進階
從上面可以大概瞭解了,柯里化就是,
把原來的函式 adder(3,5,6,7,8,9) ==> adder(3)(5)(6)(7)(8)(9)()
注意:最後一次呼叫沒有引數(),所以adder中可以通過判斷是否有引數,去判斷我是要繼續相加,還是返回求和值了
或是這樣也可以
adder(3)
adder(5,6,7)
adder(8,9)
adder() // 無引數時返回和
複製程式碼
那麼這麼做的好處是什麼呢?
- 延遲計算,何時想要結果了,直接adder()就行了
- 通常也成為部分求值,給函式分步傳遞引數,逐步縮小函式的適用範圍,逐步求解的過程。
所以我們可以在寫一個函式的時候,用柯里化的思想去寫;但是我們也可以對任何一個原有的函式,將他柯里化了,變成柯里化的思想與形式,看思路~
例:
一個原有的函式
// 不知道這test函式是啥功能,不用管他是幹啥的,但很顯然不是簡單的求和,
// 所以你不能繼續用 迴圈 arguments 了。。。
function test(name, id, num, score, height) {
}
複製程式碼
將他柯里化,就是要將他
test("chen", 45, 789, 284.2, 178) ==> test2("chen")(45)(789)(284.2)(178)()
其中test2就是經過柯里化包裝後的test
包裝的思想是
- test2中建個陣列arr,多次呼叫後,arr就成了 ["chen", 45, 789, 284.2, 178]
- 當沒引數時,執行test.apply(arr) ,,, 想起apply幹啥的不
- 所以,其實就是把一堆引數給存起來先,然後最後再執行一次執行test
通用封裝程式碼如下,一定要搞懂!
實際應用
如果還是沒覺得有啥用,看兩個實際的例子
1. 瀏覽器事件
還記得事件要區分IE和非IE不了
var addEvent = function(el, type, fn, capture) {
if(window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e)
}, capture)
} else {
el.attachEvent('on'+type, function(e) {
fn.call(el, e)
})
}
}
複製程式碼
也就是說,我們會呼叫addEvent,但是每次的話都得執行內部的if...else....
所以可以用柯里化,改成
var curEvent = (function() {
if(window.addEventListener) {
return function(el, sType, fn, capture) { // return funtion
el.addEventListener(sType, function() {
fn.call(el, e)
}, capture)
}
} else {
return function(el, sType, fn) {
el.attachEvent('on'+sType, function(e) {
fn.call(el, e)
})
}
}
})
var addEvent = curEvent(); // addEvent 這回得到的,就是if..else...裡面的那個return 的function,所以只需要curEvent()執行一遍判斷了if..else,其他時候就都不需要判斷了
addEvent(elem)
複製程式碼
2. 函式的bind
我們總會看到,函式可以這樣 test.bind(this),其中bind函式就是柯里化的思想
Funtion.prototype.bind()
var foo = {
x: 888
}
var bar = function(e) {
console.log(this.x, e)
}
Function.prototype.testBind = function(scope) {
var fn = this; // 指向的 bar
return function() {
return fn.apply(scope, [].slice.call(arguments))
}
}
var test = bar.testBind(foo) // 繫結 foo ,延遲執行
console.log(test)
test(2323) // 執行, 結果是 888 2323
複製程式碼
將傳入的第一個引數(foo),當作之後函式的執行上下文,,,其他引數 再傳給 呼叫的方法(函式本身不執行,只進行了bind繫結,以後函式執行的時候,相當於了 延遲執行) , 所以相當於,預先繫結了物件並返回了函式,之後再執行函式,符合柯里化。
3.Redux中的 applyMiddle 中介軟體原理
let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
export default function createLogger({ getState }) {
return (next) => // return function
(action) => {
const console = window.console;
const prevState = getState();
const returnValue = next(action);
const nextState = getState();
const actionType = String(action.type);
const message = `action ${actionType}`;
console.log(`%c prev state`, `color: #9E9E9E`, prevState);
console.log(`%c action`, `color: #03A9F4`, action);
console.log(`%c next state`, `color: #4CAF50`, nextState);
return returnValue;
};
}
複製程式碼
作用總結
- 一個js預先處理的思想;利用函式執行時可以形成一個不銷燬的作用域的原理,把需要預先處理的內容都儲存到這個不銷燬的作用域中,並且返回一個小函式,以後我們執行的都是這個小函式,小函式中把之前預先儲存的值進行相關操作處理。
- 通常也成為部分求值,給函式分步傳遞引數,逐步縮小函式的適用範圍,逐步求解的過程。
- 預處理
- 延遲計算
- 可以傳遞需要的引數,等到何時想要結果,再一併計算
- 引數複用
- 有些引數相同,只需要傳遞一遍即可,不需要每次都傳,太繁瑣。例如 bind
- 動態建立函式。這可以是在部分計算出結果後,在此基礎上動態生成新的函式,處理後面的業務,這樣省略了重複計算。或者可以通過將要傳入呼叫函式的引數子集,部分應用到函式中,從而動態創造出一個新函式,這個新函式儲存了重複傳入的引數(以後不必每次都傳)。例如,瀏覽器新增事件的輔助方法。
最後:若有錯誤之處,還請見諒,提出後會馬上修改~~~
轉載請註明出處,謝謝~~