<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺析 JavaScript 函數(shù)式編程

          共 15297字,需瀏覽 31分鐘

           ·

          2021-04-23 19:19

          前言

          隨著React的流行,函數(shù)式編程在前端領(lǐng)域備受關(guān)注。尤其近幾年,越來越多的類庫偏向于函數(shù)式開發(fā):lodash/fp,Rx.js、Redux的純函數(shù),React16.8推出的hooks,Vue3.0的composition Api...同時(shí)在ES5/ES6標(biāo)準(zhǔn)中也有體現(xiàn),例如:箭頭函數(shù)、迭代器、map、filter、reduce等。

          那么為什么要使用函數(shù)式編程呢?我們通過一個(gè)例子感受一下:在業(yè)務(wù)需求開發(fā)中,我們更多時(shí)候是對(duì)數(shù)據(jù)的處理,例如:將字符串?dāng)?shù)組進(jìn)行分類,轉(zhuǎn)為字符串對(duì)象格式。

          // jsList => jsObj
          const jsList = [
            'es5:forEach',
            'es5:map',
            'es5:filter',
            'es6:find',
            'es6:findIndex',
            'add'
          ]

          const jsObj = {
            es5: ["forEach""map""filter"],
            es6: ["find""findIndex"]
          }

          先通過我們最常用的命令式實(shí)現(xiàn)一遍:

          const jsObj = {}

          for (let i = 0; i < jsList.length; i++) {
            const item = jsList[i];
            const [vesion, apiName] = item.split(":")
              
            if (apiName) {
              if (!jsObj[vesion]) {
                jsObj[vesion] = []
              }

              jsObj[vesion].push(apiName);   
            }
          }

          接下來再看函數(shù)式的實(shí)現(xiàn):

          const jsObj = jsList
            .map(item => item.split(':'))
            .filter(arr => arr.length === 2)
            .reduce((obj, item) => {
              const [version, apiName] = item
              return {
                ...obj,
                [version]: [...(obj[version] || []), apiName]
              }
            }, {})

          兩段代碼對(duì)比下來,會(huì)發(fā)現(xiàn)命令式的實(shí)現(xiàn)過程中會(huì)產(chǎn)生大量的臨時(shí)變量,還參雜大量的邏輯處理,通常只有讀完整段代碼才會(huì)明白具體做了什么。如果后續(xù)需求變更,又會(huì)添加更多的邏輯處理,想想腦殼都痛...

          反觀函數(shù)式的實(shí)現(xiàn):單看每個(gè)函數(shù),就可以知道在做什么,代碼更加語義化,可讀性更高。整個(gè)過程就像一條完整的流水線,數(shù)據(jù)從一個(gè)函數(shù)輸入,處理完成后流入下一個(gè)處理函數(shù)...每個(gè)函數(shù)都是各司其職。

          接下來,讓我們?cè)诟Q探函數(shù)式編程的世界之前,先簡單了解一下上面提到的編程范式。

          編程范式

          編程范式是指軟件工程中的一類典型的編程風(fēng)格,編程范式提供并決定了程序員對(duì)程序的看法。

          例如在面向?qū)ο缶幊讨?,程序員認(rèn)為程序是一系列相互作用的對(duì)象;而在函數(shù)式編程中,程序會(huì)被當(dāng)做一個(gè)無狀態(tài)的函數(shù)計(jì)算的序列。常見的編程范式如下:

          命令式編程

          命令式編程是一種描述電腦所需作出的行為的編程范式,也是目前使用最廣的編程范式,其主要思想就是站在計(jì)算機(jī)的角度思考問題,關(guān)注計(jì)算執(zhí)行步驟,每一步都是指令。(代表:C、C++、Java)

          大部分命令式編程語言都支持四種基本的語句:

          1. 運(yùn)算語句;
          2. 循環(huán)語句(for、while);
          3. 條件分支語句(if else、switch);
          4. 無條件分支語句(return、break、continue)。

          計(jì)算機(jī)執(zhí)行的每一個(gè)步驟都是程序員控制的,所以可以更加精細(xì)嚴(yán)謹(jǐn)?shù)目刂拼a,提高應(yīng)用程序的性能;但是由于存在大量的流程控制語句,在處理多線程、并發(fā)問題時(shí),容易造成邏輯紊亂。

          聲明式編程

          聲明式編程描述的是目標(biāo)的性質(zhì),讓計(jì)算機(jī)明白目標(biāo),而非流程。通過定義具體的規(guī)則,以便系統(tǒng)底層可以自動(dòng)實(shí)現(xiàn)具體功能。(代表:Haskell)

          相較于命令式編程范式,不需要流程控制語言,沒有冗余的操作步驟,使得代碼更加語義化,降低了代碼的復(fù)雜性;但是其底層實(shí)現(xiàn)的邏輯并不可控,不適合做更加精細(xì)的代碼優(yōu)化。

          總結(jié)下來,這兩種編程范式最大的不同就是:

          1. How:命令式編程告訴計(jì)算機(jī)如何 計(jì)算,關(guān)心解決問題的步驟;
          2. What:聲明式編程告訴計(jì)算機(jī)需要計(jì)算什么,關(guān)心解決問題的目標(biāo)。

          函數(shù)式編程

          聲明式編程是一個(gè)大的概念,其下包含一些有名的子編程范式:約束式編程、領(lǐng)域?qū)僬Z言、邏輯式編程、函數(shù)式編程。其中領(lǐng)域?qū)僬Z言(DSL)和函數(shù)式編程(FP)在前端領(lǐng)域的應(yīng)用更加廣泛,接下來開始我們今天的主角--函數(shù)式編程。

          函數(shù)式編程并不是一種工具,而是一種可以適用于任何環(huán)境的編程思想,它是一種以函數(shù)使用為主的軟件開發(fā)風(fēng)格。這與大家都熟悉的面向?qū)ο缶幊痰乃季S方式完全不同,函數(shù)式的目的是通過函數(shù)抽象作用在數(shù)據(jù)流的操作,從而在系統(tǒng)中消除副作用并減少對(duì)狀態(tài)的改變。

          為了充分理解函數(shù)式編程,我們先來看下它有哪些基本概念?

          概念

          函數(shù)是一等公民

          函數(shù)與其他數(shù)據(jù)類型一樣,不僅可以賦值給變量,也可以當(dāng)作參數(shù)傳遞,或者做為函數(shù)的返回值。例如:

          // 做為變量
          fn = () => {}
          // 做為參數(shù)
          function fn1(fn){fn()}
          // 做為函數(shù)返回值
          function fn2(){return () => {} }

          正是函數(shù)是‘一等公民’的前提,函數(shù)式編程才得以實(shí)現(xiàn),而在JavaScript中,閉包和高階函數(shù)成了中堅(jiān)力量。

          純函數(shù)

          純函數(shù)是這樣一種函數(shù),即相同的輸入,永遠(yuǎn)會(huì)得到相同的輸出,而且沒有任何可觀察的副作用。

          提到純函數(shù),熟悉redux的同學(xué)可能再熟悉不過了,在redux中所有的修改都需要使用純函數(shù)。純函數(shù)具有以下特點(diǎn):

          • 無狀態(tài):函數(shù)的輸出僅取決于輸入,而不依賴外部狀態(tài);
          • 無副作用:不會(huì)造成超出其作用域的變化,即不修改函數(shù)參數(shù)或全局變量等。
          function add(obj{
            obj.num += 1
            return obj
          }

          const obj = {num1}
          add(obj)
          console.log(obj)
          // { num: 2 }

          這個(gè)函數(shù)不是純的,因?yàn)閖s對(duì)象傳遞的是引用地址,函數(shù)內(nèi)部的修改會(huì)直接影響外部變量,最后產(chǎn)生了預(yù)料之外的結(jié)果。接下來,我們改成純函數(shù)的寫法:

          function add(obj{
            const _obj = {...obj}
            _obj.num += 1
            return _obj
          }

          const obj = {num1}
          add(obj)
          console.log(obj);
          // { num: 1 }

          通過在函數(shù)內(nèi)部創(chuàng)建新的變量進(jìn)行更改(是不是有想起redux的reducer寫法~~),從而避免產(chǎn)生副作用。純函數(shù)除了無副作用外,還有其他好處:

          1. 可緩存性正是因?yàn)楹瘮?shù)式聲明的無狀態(tài)特點(diǎn),即:相同輸入總能得到相同的輸出。所以我們可以提前緩存函數(shù)的執(zhí)行結(jié)果,實(shí)現(xiàn)更多功能。例如:優(yōu)化斐波拉契數(shù)列的遞歸解法。
          2. 可移植性/自文檔化純函數(shù)的依賴很明確,更易于觀察和理解,配合類型簽名可以使程序更加簡單易讀。
          // get :: a -> a
          const get = function (id) { return id}
          // map :: (a -> b) -> [a] -> [b]
          const map = curry(function (f, res){
              return res.map(f)
          })
          1. 可測(cè)試性純函數(shù)讓測(cè)試更加簡單,只需簡單地給函數(shù)一個(gè)輸入,然后斷言輸出就可以了。
          副作用

          函數(shù)的副作用是指在調(diào)用函數(shù)時(shí),除了返回函數(shù)值外還產(chǎn)生了額外的影響。例如修改上個(gè)例子中的修改參數(shù)或者全局變量。除此之外,以下副作用也都有可能會(huì)發(fā)生:

          • 更改全局變量
          • 處理用戶輸入
          • 屏幕打印或打印log日志
          • DOM查詢以及瀏覽器cookie、localstorage查詢
          • 發(fā)送http請(qǐng)求
          • 拋出異常,未被當(dāng)前函數(shù)捕獲
          • ...

          副作用往往會(huì)影響代碼的可讀性和復(fù)雜性,從而導(dǎo)致意想不到的bug。在實(shí)際開發(fā)中,我們是離不開副作用的,那么在函數(shù)式編程中應(yīng)盡量減少副作用,盡量書寫純函數(shù)。

          引用透明

          如果一個(gè)函數(shù)對(duì)于相同輸出始終產(chǎn)生同一個(gè)輸出結(jié)果,完全不依賴外部環(huán)境的變化,那么就可以說它是引用透明的。

          數(shù)據(jù)不可變

          所有數(shù)據(jù)被創(chuàng)建后不可更改,如果想要修改變量,需要新建一個(gè)新的對(duì)象進(jìn)行修改(例如上面純函數(shù)提到的例子)。

          說完這些概念,我們?cè)賮砜匆幌略诤瘮?shù)式編程中又有哪些常見的操作。

          柯里化(curry)

          把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)的函數(shù),并返回接受剩余參數(shù)而且返回結(jié)果的新函數(shù)。

          F(a,b,c) => F(a)(b)(c)

          接下來我們實(shí)現(xiàn)一版簡單的curry函數(shù)。

          function curry(targetFunc{
            // 獲取目標(biāo)函數(shù)的參數(shù)個(gè)數(shù)
            const argsLen = targetFunc.length
            
            return function func(...rest{
              return rest.length < argsLen ? func.bind(null, ...rest) : targetFunc.apply(null, rest)
            }
          }

          function add(a,b,c,d{
            return a + b + c + d
          }

          console.log(curry(add)(1)(2)(3)(4));
          console.log(curry(add)(12)(3)(4));
          // 10

          仔細(xì)的同學(xué)可能已經(jīng)看出來,上面實(shí)現(xiàn)的curry函數(shù)并不是單純柯里化函數(shù),因?yàn)榭吕锘瘡?qiáng)調(diào)的是生成單元函數(shù),但是單次傳入多個(gè)參數(shù)也可以,更像是柯里化偏函數(shù)的綜合應(yīng)用。那偏函數(shù)又是怎么定義的呢?

          偏函數(shù)(Partial)是指固定一個(gè)函數(shù)的一些參數(shù),然后產(chǎn)生另一個(gè)更小元的函數(shù)。

          偏函數(shù)在創(chuàng)建的時(shí)候還可以傳入預(yù)設(shè)的partials參數(shù),類似bind的使用。通常情況下,我們不會(huì)自己寫curry函數(shù),像Lodash、Ramda這些庫都實(shí)現(xiàn)了curry函數(shù),這些庫實(shí)現(xiàn)的curry函數(shù)和柯里化的定義也是不太一樣的。

          const add = function (a, b, c{return a + b + c}

          const curried = _.curry(add)
          curried(1)(2)(3)
          curried(12)(3)
          curried(123)
          // 還實(shí)現(xiàn)了附加參數(shù)的占位符
          curried(1)(_, 3)(2)

          組合(compose)

          compose在函數(shù)式編程中也是一個(gè)很重要的思想。把復(fù)雜的邏輯拆分成一個(gè)個(gè)簡單任務(wù),最后組合起來完成任務(wù),使得整個(gè)過程的數(shù)據(jù)流更明確、可控、可讀。這也印證了上面我們提到過:函數(shù)式編程像一條流水線,初始數(shù)據(jù)通過多個(gè)函數(shù)依次處理,最后完成整體輸出。

          // 整個(gè)過程處理
          a => fn => b
          // 拆分成多段處理
          a => fn1 => fn2 => fn3 => b 

          接下來,我們實(shí)現(xiàn)一般簡單的compose:

          function compose(...fns{
            return fns.reduce((a,b) => {
              return (...args) => {
                return a(b(...args))
              }
            })
          }

          function fn1(a{
            console.log('fn1: ', a);
            return a+1
          }

          function fn2(a{
            console.log('fn2: ', a);
            return a+1
          }

          function fn3(a{
            console.log('fn3: ', a);
            return a+1
          }

          console.log(compose(fn1, fn2, fn3)(1));
          // fn3:  1
          // fn2:  2
          // fn1:  3
          // 4

          分析上述compose的實(shí)現(xiàn),可以看出fn3是先于fn2執(zhí)行,fn2先于fn1執(zhí)行,也就是說:compose創(chuàng)建了一個(gè)從右向左執(zhí)行的數(shù)據(jù)流。如果要實(shí)現(xiàn)從左到右的數(shù)據(jù)流,可以直接更改compose的部分代碼即可實(shí)現(xiàn):

          • 更換Api接口:把reduce改為reduceRight
          • 交互包裹位置:把a(b(...args))改為b(a(...args))。

          也可以使用Ramda中提供的組合方式:管道(pipe)。

          R.pipe(fn1, fn2, fn3)

          函數(shù)組合不僅讓代碼更富有可讀性,數(shù)據(jù)流的整體流向也更加清晰,程序更加可控。接下來,我們看下函數(shù)式編程在具體業(yè)務(wù)中的實(shí)踐。

          編程實(shí)踐

          數(shù)據(jù)處理

          業(yè)務(wù)開發(fā)過程中,我們更多的時(shí)候是對(duì)接口請(qǐng)求數(shù)據(jù)或表單提交數(shù)據(jù)的處理,尤其是經(jīng)常開發(fā)B端的同學(xué)更是深有體會(huì)。筆者之前就做過針對(duì)大量表單數(shù)據(jù)的處理需求,例如:針對(duì)用戶提交的表單數(shù)據(jù)做一定的處理:1. 清除空格;2. 全部轉(zhuǎn)為大寫。

          首先我們站在函數(shù)式編程的思維上分析一下整個(gè)需求:

          1. 抽象:每個(gè)處理過程都是一個(gè)純函數(shù)
          2. 組合:通過compose組合每一個(gè)處理函數(shù)
          3. 擴(kuò)展:只需刪除或添加對(duì)應(yīng)的處理純函數(shù)即可

          接下來,我們看一下整體的實(shí)現(xiàn):

          // 1. 實(shí)現(xiàn)遍歷函數(shù)
          function traverse (obj, handler{
            if (typeof obj !== 'object'return handler(obj)

            const copy = {}
            Object.keys(obj).forEach(key => {
              copy[key] = traverse(obj[key], handler)
            })

            return copy
          }

          // 2. 實(shí)現(xiàn)具體業(yè)務(wù)處理的純函數(shù)
          function toUpperCase(str{
            return str.toUpperCase() // 轉(zhuǎn)為大寫
          }

          function toTrim(str{
            return str.trim() // 刪除前后空格
          }

          // 3. 通過compose執(zhí)行
          // 用戶提交數(shù)據(jù)如下:
          const obj = {
            info: {
              name' asyncguo '
            },
            address: {
              province'beijing',
              city'beijing',
              area'haidian'
            }
          }
          console.log(traverse(obj, compose(toUpperCase, toTrim)));
          /**
              {
               info: { name: 'ASYNCGUO' },
               address: { province: 'BEIJING', city: 'BEIJING', area: 'HAIDIAN' }
              }
          */

          redux中間件實(shí)現(xiàn)

          說到函數(shù)式在JavaScript中的實(shí)踐,那就不得不聊一下redux。首先我們先實(shí)現(xiàn)一版簡單redux:

          function createStore(reducer{
            let currentState
            let listeners = []

            function getState({
              return currentState
            }

            function dispatch(action{
              currentState = reducer(currentState, action)
              listeners.map(listener => {
                listener()
              })
              return action
            }

            function subscribe(cb{
              listeners.push(cb)
              return () => {}
            }
            
            dispatch({type'ZZZZZZZZZZ'})

            return {
              getState,
              dispatch,
              subscribe
            }
          }

          // 應(yīng)用實(shí)例如下:
          function reducer(state = 0, action{
            switch (action.type) {
              case 'ADD':
                return state + 1
              case 'MINUS':
                return state - 1
              default:
                return state
            }
          }

          const store = createStore(reducer)

          console.log(store);
          store.subscribe(() => {
            console.log('change');
          })
          console.log(store.getState());
          console.log(store.dispatch({type'ADD'}));
          console.log(store.getState());

          首先使用reducer初始化store,后續(xù)事件產(chǎn)生時(shí),通過dispatch更新store狀態(tài),同時(shí)通過getState獲取store的最新狀態(tài)。

          redux規(guī)范了單向數(shù)據(jù)流action只能由dispatch函數(shù)派發(fā),并通過純函數(shù)reducer更新狀態(tài)state,然后繼續(xù)等待下一次的事件。這種單向數(shù)據(jù)流的機(jī)制進(jìn)一步簡化事件管理的復(fù)雜度,并且還可以在事件流程中插入中間件(middleware)。通過中間件,可以實(shí)現(xiàn)日志記錄、thunk、異步處理等一系列擴(kuò)展處理,大大得增強(qiáng)事件處理的靈活性。

          接下來對(duì)上面的redux進(jìn)一步增強(qiáng)優(yōu)化:

          // 擴(kuò)展createStore
          function createStore(reducer, enhancer){
           if (enhancer) {
             return enhancer(createStore)(reducer)
            }
            
            ...
          }
          // 中間件的實(shí)現(xiàn)
          function applyMiddleware(...middlewares{
            return function (createStore{
              return function (reducer{
                const store = createStore(reducer)
                let _dispatch = store.dispatch

                const middlewareApi = {
                  getState: store.getState,
                  dispatchaction => _dispatch(action)
                }

                // 獲取中間件數(shù)組:[mid1, mid2]
                // mid1 = next1 => action1 => {}
                // mid2 = next2 => action2 => {}
                const midChain = middlewares.map(mid => mid(middlewareApi))

                // 通過compose組合中間件:mid1(mid2(mid3())),得到最終的dispatch
                // 1. compse執(zhí)行順序:next2 => next1
                // 2. 最終dispatch:action1 (action1中調(diào)用next時(shí),回到上一個(gè)中間件action2; action2中調(diào)用next時(shí),回到最原始的dispatch)
                
                _dispatch = compose(...midChain)(store.dispatch)

                return {
                  ...store,
                  dispatch: _dispatch
                }
              }
            }
          }

          // 自定義中間件模板
          const middleaware = store => next => action => {
              // ...邏輯處理
              next(action)
          }

          通過compose組合所有的middleware,然后返回包裝過的dispatch。接下來,在每次dispatch時(shí),action會(huì)經(jīng)過全部中間件進(jìn)行一系列操作,最后透傳給純函數(shù)reducer進(jìn)行真正的狀態(tài)更新。任何middleware能夠做到的事情,我們都可以通過手動(dòng)包裝dispatch調(diào)用實(shí)現(xiàn),但是放在同一個(gè)地方統(tǒng)一管理使得整個(gè)項(xiàng)目的擴(kuò)展變得更加容易。

          // 1. 手動(dòng)包裝dispatch調(diào)用,實(shí)現(xiàn)logger功能
          function dispatchWithLog(store, action{
           console.log('dispatching', action)
           store.dispatch(action)
           console.log('next state', store.getState())
          }

          dispatchWithLog(store, {type'ADD'})

          // 2. 中間件方式包裝dispatch調(diào)用
          const store = new Store(reducer, applyMiddleware(thunkMiddleware, loggerMiddleware))

          store.dispatch(() => {
           setTimeout(() => {
             store.dispatch({type'ADD'})
            }, 2000)
          })
            
          // 中間件執(zhí)行過程
          thunk => logger => store.dispatch

          RxJS

          提到Rxjs,更多人想到應(yīng)該是響應(yīng)式編程(Reactive Programming, RP),即使用異步數(shù)據(jù)流進(jìn)行編程。響應(yīng)式編程使用Rx.Observale為異步數(shù)據(jù)提供統(tǒng)一的名為可觀察的流(observeale stream)的概念,可以說響應(yīng)式編程的世界就是的世界。想要提取其值,就必須先訂閱它。例如:

          Rx.observale.of(12345)
           .filter(x => x%2 !== 0)
           .map(x => x * x)
           .subscrible(x => console.log(`ext: ${x}`))

          通過上面的例子,可以發(fā)現(xiàn)響應(yīng)式編程就是讓整個(gè)編程過程流式化,就像一條流水線,同時(shí)以函數(shù)式編程為主,即流水線的每條工序都是無副作用的(純函數(shù))。所以更準(zhǔn)確的說Rxjs應(yīng)該是函數(shù)響應(yīng)式編程(Functional Reactive Programming,F(xiàn)RP),顧名思義,FRP同時(shí)具有函數(shù)式編程和響應(yīng)式編程的特點(diǎn)。(今天主要是講函數(shù)式編程,更多Rxjs部分的內(nèi)容,感興趣的同學(xué)可以自行了解一下。筆者還是很推薦學(xué)習(xí)一下Rxjs在異步數(shù)據(jù)流上的處理~)

          總結(jié)

          函數(shù)式編程是一個(gè)很大的話題,今天我們主要是介紹了一下函數(shù)式編程的基礎(chǔ)概念,當(dāng)然還有更高級(jí)的概念:Functor(函子)、Monad、Application Functor等還沒有提到,真正掌握這些東西還是需要一定練習(xí)積累,感興趣的同學(xué)可以自行了解一下,或者期待筆者后續(xù)的文章。

          對(duì)比面向?qū)ο缶幊?,我們可以總結(jié)一下,函數(shù)式編程的優(yōu)點(diǎn):

          • 代碼更加簡明,流程更可控
          • 流式處理數(shù)據(jù)
          • 降低事件驅(qū)動(dòng)代碼的復(fù)雜性

          當(dāng)然,函數(shù)式編程也存在一定的性能問題,在抽象層次往往因?yàn)檫^度包裝,導(dǎo)致上下文切換的性能開銷;同時(shí)由于數(shù)據(jù)不可變的特點(diǎn),中間變量也會(huì)消耗更多內(nèi)存空間。

          在日常業(yè)務(wù)開發(fā)中,函數(shù)式編程應(yīng)是與面向?qū)ο缶幊桃曰パa(bǔ)的形式存在,根據(jù)具體的需求選擇合適的編程范式。在面對(duì)一種新技術(shù)或新的編程方式時(shí),若其優(yōu)點(diǎn)值得我們學(xué)習(xí)和借鑒時(shí),并不應(yīng)該因?yàn)槟硞€(gè)缺陷就一味的拒絕它,更多時(shí)候是應(yīng)該能夠想到與其互補(bǔ)的更優(yōu)解。不以優(yōu)而喜,不以劣而悲,與君共勉~

          推薦資料

          編程范式(https://zh.wikipedia.org/wiki/%E7%BC%96%E7%A8%8B%E8%8C%83%E5%9E%8B)

          functional light JS(https://frontendmasters.com/courses/functional-javascript-v3/)

          Functional-Light-JS - github(https://github.com/getify/Functional-Light-JS)

          redux-middleware(https://www.redux.org.cn/docs/api/applyMiddleware.html)

          函數(shù)式編程淺析(https://zhuanlan.zhihu.com/p/74777206)

          函數(shù)式編程在Redux/React中的應(yīng)用 (https://tech.meituan.com/2017/10/12/functional-programming-in-redux.html)

          函數(shù)式編程指北(https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch5.html)

          JavaScript函數(shù)式編程指南(https://book.douban.com/subject/30283769/)

          感謝你的閱讀,有任何問題,歡迎評(píng)論區(qū)留言討論,互相學(xué)習(xí)。

          END



          如果覺得這篇文章還不錯(cuò)
          點(diǎn)擊下面卡片關(guān)注我
          來個(gè)【分享、點(diǎn)贊、在看】三連支持一下吧

             “分享、點(diǎn)贊、在看” 支持一波  

          瀏覽 70
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧美中文日韩在线观看 | 学生妹做爱在线播放 | 操骚逼视频| 青草青免费视频 | 久久久久久久久久久久久久国产 |