1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        JS 函數(shù)式編程應(yīng)該掌握哪些基礎(chǔ)知識?

        共 7175字,需瀏覽 15分鐘

         ·

        2021-10-02 09:44

        大廠技術(shù)  高級前端  Node進(jìn)階

        點擊上方 程序員成長指北,關(guān)注公眾號

        回復(fù)1,加入高級Node交流群

        一、引言

        函數(shù)式編程的歷史已經(jīng)很悠久了,但是最近幾年卻頻繁的出現(xiàn)在大眾的視野,很多不支持函數(shù)式編程的語言也在積極加入閉包,匿名函數(shù)等非常典型的函數(shù)式編程特性。大量的前端框架也標(biāo)榜自己使用了函數(shù)式編程的特性,好像一旦跟函數(shù)式編程沾邊,就很高大上一樣,而且還有一些專門針對函數(shù)式編程的框架和庫,比如:RxJS、cycleJS、ramdaJS、lodashJS、underscoreJS 等。函數(shù)式編程變得越來越流行,掌握這種編程范式對書寫高質(zhì)量和易于維護(hù)的代碼都大有好處,所以我們有必要掌握它。

        二、什么是函數(shù)式編程

        維基百科定義:函數(shù)式編程(英語:functional programming),又稱泛函編程,是一種編程范式,它將電腦運算視為數(shù)學(xué)上的函數(shù)計算,并且避免使用程序狀態(tài)以及易變對象。

        三、純函數(shù)(函數(shù)式編程的基石,無副作用的函數(shù))

        在初中數(shù)學(xué)里,函數(shù) f 的定義是:對于輸入 x 產(chǎn)生一個唯一輸出 y=f(x)。這便是純函數(shù)。它符合兩個條件:1.此函數(shù)在相同的輸入值時,總是產(chǎn)生相同的輸出。函數(shù)的輸出和當(dāng)前運行環(huán)境的上下文狀態(tài)無關(guān)。2.此函數(shù)運行過程不影響運行環(huán)境,也就是無副作用(如觸發(fā)事件、發(fā)起 http 請求、打印/log 等)。簡單來說,也就是當(dāng)一個函數(shù)的輸出不受外部環(huán)境影響,同時也不影響外部環(huán)境時,該函數(shù)就是純函數(shù),也就是它只關(guān)注邏輯運算和數(shù)學(xué)運算,同一個輸入總得到同一個輸出。javascript 內(nèi)置函數(shù)有不少純函數(shù),也有不少非純函數(shù)。

        純函數(shù):Array.prototype.sliceArray.prototype.mapString.prototype.toUpperCase

        非純函數(shù):Math.randomDate.nowArray.ptototype.splice

        這里我們以 slice 和 splice 方法舉例:

        var xs = [1,2,3,4,5];
        // 純的
        xs.slice(0,3);
        //=> [1,2,3]
        xs.slice(0,3);
        //=> [1,2,3]
        xs.slice(0,3);
        //=> [1,2,3]

        // 不純的
        xs.splice(0,3);
        //=> [1,2,3]
        xs.splice(0,3);
        //=> [4,5]
        xs.splice(0,3);
        //=> []

        我們看到調(diào)用數(shù)組的 slice 方法每次返回的結(jié)果完全相同,同時 xs 不會被改變,而調(diào)用 splice 方法每次返回值都不一樣,同時 xs 變得面目全非。這就是我們強調(diào)使用純函數(shù)的原因,因為純函數(shù)相對于非純函數(shù)來說,在可緩存性、可移植性、可測試性以及并行計算方面都有著巨大的優(yōu)勢。這里我們以可緩存性舉例:

        var squareNumber  = memoize(function(x)return x*x; });
        squareNumber(4);
        //=> 16
        squareNumber(4); // 從緩存中讀取輸入值為 4 的結(jié)果
        //=> 16

        那我們?nèi)绾伟岩粋€非純函數(shù)變純呢?比如下面這個函數(shù):

        var minimum = 21;
        var checkAge = function(age{
          return age >= minimum;
        };

        這個函數(shù)的返回值依賴于可變變量 minimum 的值,它依賴于系統(tǒng)狀態(tài)。在大型系統(tǒng)中,這種對于外部狀態(tài)的依賴是造成系統(tǒng)復(fù)雜性大大提高的主要原因。

        var checkAge = function(age{
          var minimum = 21;
          return age >= minimum;
        };

        通過改造,我們把 checkAge 變成了一個純函數(shù),它不依賴于系統(tǒng)狀態(tài),但是 minimum 是通過硬編碼的方式定義的,這限制了函數(shù)的擴展性,我們可以在后面的柯里化中看到如何優(yōu)雅的使用函數(shù)式解決這個問題。所以把一個函數(shù)變純的基本手段是不要依賴系統(tǒng)狀態(tài)。

        四、函數(shù)柯里化

        curry 的概念很簡單:將一個低階函數(shù)轉(zhuǎn)換為高階函數(shù)的過程就叫柯里化。用一個形象的比喻就是:比如對于加法操作:var add = (x, y) => x + y,我們可以這樣柯里化:

        //es5寫法
        var add = function(x{
          return function(y{
            return x + y;
          };
        };

        //es6寫法
        var add = x => (y => x + y);

        //試試看
        var increment = add(1);
        var addTen = add(10);

        increment(2);  // 3

        addTen(2);  // 12

        對于加法這種極其簡單的函數(shù)來說,柯里化并沒有什么用。還記得上面的 checkAge 函數(shù)嗎?我們可以這樣柯里化它:

        var checkage = min => (age => age > min);
        var checkage18 = checkage(18);
        checkage18(20);
        // =>true

        這表明函數(shù)柯里化是一種“預(yù)加載”函數(shù)的能力,通過傳遞一到兩個參數(shù)調(diào)用函數(shù),就能得到一個記住了這些參數(shù)的新函數(shù)。從某種意義上來講,這是一種對參數(shù)的緩存,是一種非常高效的編寫函數(shù)的方法:

        var curry = require('lodash').curry;

        //柯里化兩個純函數(shù)
        var match = curry((what, str) => str.match(what));
        var filter = curry((f, ary) => ary.filter(f));

        //判斷字符串里有沒有空格
        var hasSpaces = match(/\s+/g);

        hasSpaces("hello world");  // [ ' ' ]
        hasSpaces("spaceless");  // null

        var findSpaces = filter(hasSpaces);

        findSpaces(["tori_spelling""tori amos"]);  // ["tori amos"]

        五、函數(shù)組合

        假設(shè)我們需要對一個字符串做一些列操作,如下,為了方便舉例,我們只對一個字符串做兩種操作,我們定義了一個新函數(shù) shout,先調(diào)用 toUpperCase,然后把返回值傳給 exclaim 函數(shù),這樣做有什么不好呢?不優(yōu)雅,如果做得事情一多,嵌套的函數(shù)會非常深,而且代碼是由內(nèi)往外執(zhí)行,不直觀,我們希望代碼從右往左執(zhí)行,這個時候我們就得使用組合。

        var toUpperCase = function(xreturn x.toUpperCase(); };
        var exclaim = function(xreturn x + '!'; };

        var shout = function(x){
          return exclaim(toUpperCase(x));
        };

        shout("send in the clowns");
        //=> "SEND IN THE CLOWNS!"

        使用組合,我們可以這樣定義我們的 shout 函數(shù):

        //定義compose
        var compose = (...args) => x => args.reduceRight((value, item) => item(value), x);

        var toUpperCase = function(xreturn x.toUpperCase(); };
        var exclaim = function(xreturn x + '!'; };

        var shout = compose(exclaim, toUpperCase);

        shout("send in the clowns");
        //=> "SEND IN THE CLOWNS!"

        代碼從右往左執(zhí)行,非常清晰明了,一目了然。我們定義的 compose 像 N 面膠一樣,可以將任意多個純函數(shù)結(jié)合到一起。這種靈活的組合可以讓我們像拼積木一樣來組合函數(shù)式的代碼:

        var head = function(xreturn x[0]; };
        var reverse = reduce(function(acc, x)return [x].concat(acc); }, []);
        var last = compose(head, reverse);

        last(['jumpkick''roundhouse''uppercut']);
        //=> 'uppercut'

        六、聲明式和命令式代碼

        命令式代碼:命令“機器”如何去做事情(how),這樣不管你想要的是什么(what),它都會按照你的命令實現(xiàn)。聲明式代碼:告訴“機器”你想要的是什么(what),讓機器想出如何去做(how)。與命令式不同,聲明式意味著我們要寫表達(dá)式,而不是一步一步的指示。以 SQL 為例,它就沒有“先做這個,再做那個”的命令,有的只是一個指明我們想要從數(shù)據(jù)庫取什么數(shù)據(jù)的表達(dá)式。至于如何取數(shù)據(jù)則是由它自己決定的。以后數(shù)據(jù)庫升級也好,SQL 引擎優(yōu)化也好,根本不需要更改查詢語句。這是因為,有多種方式解析一個表達(dá)式并得到相同的結(jié)果。這里為了方便理解,我們來看一個例子:

        // 命令式
        var makes = [];
        for (var i = 0; i < cars.length; i++) {
          makes.push(cars[i].make);
        }

        // 聲明式
        var makes = cars.map(function(car)return car.make; });

        命令式的循環(huán)要求你必須先實例化一個數(shù)組,而且執(zhí)行完這個實例化語句之后,解釋器才繼續(xù)執(zhí)行后面的代碼。然后再直接迭代 cars 列表,手動增加計數(shù)器,就像你開了一輛零部件全部暴露在外的汽車一樣。這不是優(yōu)雅的程序員應(yīng)該做的。聲明式的寫法是一個表達(dá)式,如何進(jìn)行計數(shù)器迭代,返回的數(shù)組如何收集,這些細(xì)節(jié)都隱藏了起來。它指明的是做什么,而不是怎么做。除了更加清晰和簡潔之外,map 函數(shù)還可以進(jìn)一步獨立優(yōu)化,甚至用解釋器內(nèi)置的速度極快的 map 函數(shù),這么一來我們主要的業(yè)務(wù)代碼就無須改動了。函數(shù)式編程的一個明顯的好處就是這種聲明式的代碼,對于無副作用的純函數(shù),我們完全可以不考慮函數(shù)內(nèi)部是如何實現(xiàn)的,專注于編寫業(yè)務(wù)代碼。優(yōu)化代碼時,目光只需要集中在這些穩(wěn)定堅固的函數(shù)內(nèi)部即可。相反,不純的不函數(shù)式的代碼會產(chǎn)生副作用或者依賴外部系統(tǒng)環(huán)境,使用它們的時候總是要考慮這些不干凈的副作用。在復(fù)雜的系統(tǒng)中,這對于程序員的心智來說是極大的負(fù)擔(dān)。

        七、Point Free

        pointfree 模式指的是,永遠(yuǎn)不必說出你的數(shù)據(jù)。它的意思是說,函數(shù)無須提及將要操作的數(shù)據(jù)是什么樣的。一等公民的函數(shù)、柯里化(curry)以及組合協(xié)作起來非常有助于實現(xiàn)這種模式。

        // 非 pointfree,因為提到了數(shù)據(jù):word
        var snakeCase = function (word{
          return word.toLowerCase().replace(/\s+/ig'_');
        };

        // pointfree
        var snakeCase = compose(replace(/\s+/ig'_'), toLowerCase);

        這種風(fēng)格能夠幫助我們減少不必要的命名,讓代碼保持簡潔和通用。當(dāng)然,為了在一些函數(shù)中寫出 Point Free 的風(fēng)格,在代碼的其它地方必然是不那么 Point Free 的,這個地方需要自己取舍。

        八、示例應(yīng)用

        擁有了以上的知識,我們是時候該寫一個示例應(yīng)用了。這里我們使用了 ramda ,沒有用 lodash 或者其他類庫。ramda 提供了 compose、curry 等很多函數(shù)。我們的應(yīng)用將做四件事:1.根據(jù)特定搜索關(guān)鍵字構(gòu)造 url
        2.向 flickr 發(fā)送 api 請求
        3.把返回的 json 轉(zhuǎn)為 html 圖片
        4.把圖片放到屏幕上上面提到了兩個不純的動作,即從 flickr 的 api 獲取數(shù)據(jù)和在屏幕上放置圖片這兩件事。我們先來定義這兩個動作,這樣就能隔離它們了。這里我們只是簡單包裝了一下 jQuery 的 getJSON 函數(shù),把它變?yōu)橐粋€ curry 函數(shù),還有就是把參數(shù)位置也調(diào)換了下,我們把它們放在 Impure 命名空間下以用來隔離,這樣我們就知道它們都是危險函數(shù)。運用函數(shù)柯里化和函數(shù)組合的技巧,我們就可以創(chuàng)建一個函數(shù)式的實際應(yīng)用了:預(yù)覽地址:demo[1]看看,多么美妙的聲明式規(guī)范啊,只說做什么,不說怎么做?,F(xiàn)在我們可以把每一行代碼都視作一個等式,變量名所代表的屬性就是等式的含義。

        九、總結(jié)

        我們已經(jīng)見識到如何在一個小而不失真實的應(yīng)用中運用新技能了,但是異常處理以及代碼分支呢?如何讓整個應(yīng)用都是函數(shù)式的,而不僅僅是把破壞性的函數(shù)放到命名空間下?如何讓應(yīng)用更安全更富有表現(xiàn)力?我會在下一篇文章中介紹函數(shù)式編程的更加高階一些的知識,例如 Functor、Monad、Applicative 等概念。

        參考資料
        [1]

        預(yù)覽地址:demo: https://code.h5jun.com/vixe/1/edit?html,js,output

        ?
        Node 社群


        我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


           “分享、點贊、在看” 支持一波??

        瀏覽 35
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            麻豆成人毛片 | 老外把我添高潮了视频 | 亚洲综合婷婷 | 色五月亚洲开心网 | 91老师国产黑色丝袜在线 | 久久成人秘 18免费观看 | 黄上黄在线观看 | 久草中文在线视频 | 手机在线观看av 豆花视频色 | 成人涩涩无遮挡 视频 |