前端進階之認識與手寫compose方法
阿新 • • 發佈:2020-12-11
![](https://imgkr2.cn-bj.ufileos.com/b7e1c8ed-39e6-4260-b4f8-87b1941c7403.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=jJuXUGNwnmbNAWcc6QRnIJydLE4%253D&Expires=1607605254)
[toc]
## 前言:為什麼要學習這個方法
遇到這個方法主要是最近在閱讀redux,koa 原理 等多次遇到這個方法,為了更好地理解框架原理,於是深入學習了一下compose的實現。
然後也發現這屬於函數語言程式設計的東西,發現函數語言程式設計是進擊前端進階的必經之路,因為像其中的純函式的概念在redux的reducer中也展示得淋漓盡致,而保留函式計算結果的思想無論是在vue,還是react等其他框架也多處見到。
所以建議有時間可以去看下函式試程式設計。
接下來,就讓我們學習下其中的compose函式吧!
## compose簡介
compose就是執行一系列的任務(函式),比如有以下任務佇列
```js
let tasks = [step1, step2, step3, step4]
```
每一個step都是一個步驟,按照步驟一步一步的執行到結尾,這就是一個**compose**
compose在函數語言程式設計中是一個很重要的工具函式,在這裡實現的compose有三點說明
- 第一個函式是多元的(接受多個引數),後面的函式都是單元的(接受一個引數)
- 執行順序的自右向左的
- 所有函式的執行都是同步的
還是用一個例子來說,比如有以下幾個函式
```js
let init = (...args) => args.reduce((ele1, ele2) => ele1 + ele2, 0)
let step2 = (val) => val + 2
let step3 = (val) => val + 3
let step4 = (val) => val + 4
```
這幾個函式組成一個任務佇列
```js
steps = [step4, step3, step2, init]
```
使用compose組合這個佇列並執行
```js
let composeFunc = compose(...steps)
console.log(composeFunc(1, 2, 3))
```
執行過程
```
6 -> 6 + 2 = 8 -> 8 + 3 = 11 -> 11 + 4 = 15
```
所以流程就是從init自右到左依次執行,下一個任務的引數是上一個任務的返回結果,並且任務都是同步的,這樣就能保證任務可以按照有序的方向和有序的時間執行。
## compose的實現
好了,我們現在已經知道compose是什麼東西了,現在就來實現它吧!
### 最容易理解的實現方式
思路就是使用遞迴的過程思想,不斷的檢測佇列中是否還有任務,如果有任務就執行,並把執行結果往後傳遞,這裡是一個區域性的思維,無法預知任務何時結束。直觀上最容易理解。
```js
const compose = function(...funcs) {
let length = funcs.length
let count = length - 1
let result
return function f1 (...arg1) {
result = funcs[count].apply(this, arg1)
if (count <= 0) {
count = length - 1
return result
}
count--
return f1.call(null, result)
}
}
```
刪繁就簡來看下,去掉args1引數
```js
const compose = function(...funcs) {
let length = funcs.length
let count = length - 1
let result
return function f1 () {
result = funcs[count]()
if (count <= 0) {
count = length - 1
return result
}
count--
return f1(result)
}
}
```
這就好看很多,我們假設有三個方法,aa,bb,cc
```js
function aa() {
console.log(11);
}
function bb() {
console.log(22);
}
function cc() {
console.log(33);
return 33
}
```
然後傳入compose
```
compose(aa,bb,cc)
```
此時count = 2,則下面其實是執行cc
```
result = funcs[count]()
```
然後count--。再遞迴執行f1,則下面其實就是執行bb
```
result = funcs[count]()
```
這樣,就實現了 從funcs數組裡從右往左依次拿方法出來呼叫,再把返回值傳遞給下一個。
後面的步驟同理。
這其實是一種**面向過程**的思想
### 手寫javascript中reduce方法
為什麼要手寫?其實你要是能夠很熟練的使用reduce,我覺得不必手寫reduce,只是我覺得熟悉一下reduce內部的實現可以更好地理解後面的內容,況且 也不會太難呀!
```js
function reduce(arr, cb, initialValue){
var num = initValue == undefined? num = arr[0]: initValue;
var i = initValue == undefined? 1: 0
for (i; i< arr.length; i++){
num = cb(num,arr[i],i)
}'
return num
}
```
如程式碼所示,就是先判斷有沒有傳入初始值,有的話,下面的迴圈直接 從i = 0開始,否則i=1開始。
如果沒有傳入初始值,num就取 陣列的第一個元素。這也是說明了為什麼傳入初始值,i就=1,因為第一個都被取出來了,就不能再取一次啦啦啦!
下面使用我們寫的reduce方法
```
function fn(result, currentValue, index){
return result + currentValue
}
var arr = [2,3,4,5]
var b = reduce(arr, fn,10)
var c = reduce(arr, fn)
console.log(b) // 24
```
好了 ,沒毛病,既然我們瞭解了reduce原理,就看看下面的**redux中compose的實現**吧
### redux中compose的實現
```js
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
debugger
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
```
很簡短,非常的巧妙,但是不是很不好理解。不過沒關係。
依舊通過例子來講解。
```js
function aa() {
console.log(11);
}
function bb() {
console.log(22);
}
function cc() {
console.log(33);
}
```
假設只有這三個方法,我們怎樣才能先執行cc再執行bb,再執行aa呢?
沒錯,可以直接寫
```
aa(bb(cc()))
```
就是這樣,非常巧妙,不僅完成了執行順序,還實現了前一個方法執行返回的結果傳遞給了下一個即將要執行的方法。
而下面這段程式碼所做的就是將funcs陣列`[aa,bb,cc]`,轉化成`aa(bb(cc()))`
```js
funcs.reduce((a, b) => (...args) => a(b(...args)))
```
怎麼辦到的?
看看下面的解釋:
reduce內部第一次執行返回的結果是 一個方法
```js
(...args) => aa(bb(...args))
```
我們現在把這個方法簡化成dd,即
```js
dd = (...args) => aa(bb(...args))
```
reduce內部第二次執行的時候,此時的a 是 上一次返回的dd方法,b是cc
所以執行結果是
```js
(...args) => dd(cc(...args))
```
而`dd(cc(...args))`不就是先執行cc再執行dd嗎?而dd就是執行bb再執行aa。
我的天,!這不是俄羅斯套娃嗎!沒錯 redux中的compose的實現原理就是套娃哈哈哈!
## 參考文章
https://segmentfault.com/a/1190000011447164
## 最後
文章首發於公眾號《前端陽光》,歡迎加入技術交