JavaScript中的函數(shù)式編程函數(shù)和組合以及柯里化是怎樣的

JavaScript中的函數(shù)式編程函數(shù)和組合以及柯里化是怎樣的,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

康巴什ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!

面向?qū)ο缶幊毯秃瘮?shù)式編程是兩種非常不同的編程范式,它們有自己的規(guī)則和優(yōu)缺點。

但是,JavaScript  并沒有一直遵循一個規(guī)則,而是正好處于這兩個規(guī)則的中間,它提供了普通OOP語言的一些方面,比如類、對象、繼承等等。但與此同時,它還為你提供了函數(shù)編程的一些概念,比如高階函數(shù)以及組合它們的能力。

高階函數(shù)

我們行人人三個概念中最重要的一個開始:高階函數(shù)。

高階函數(shù)意味著函數(shù)不僅僅是一個可以從代碼中定義和調(diào)用,實際上,你可以將它們用作可分配的實體。如果你使用過一些JavaScript,那么這并不奇怪。將匿名函數(shù)分配給常量,這樣的事情非常常見。

const adder = (a, b) => {   return a + b }

上述邏輯在許多其他語言中是無效的,能夠像分配整數(shù)一樣分配函數(shù)是一個非常有用的工具,實際上,本文涵蓋的大多數(shù)主題都是該函數(shù)的副產(chǎn)品。

高階函數(shù)的好處:封裝行為

有了高階函數(shù),我們不僅可以像上面那樣分配函數(shù),還可以在函數(shù)調(diào)用時將它們作為參數(shù)傳遞。這為創(chuàng)建一常動態(tài)的代碼基打開了大門,在這個代碼基礎(chǔ)上,可以直接將復(fù)雜行為作為參數(shù)傳遞來重用它。

想象一下,在純面向?qū)ο蟮沫h(huán)境中工作,你想擴展類的功能,以完成任務(wù)。在這種情況下,你可能會使用繼承,方法是將該實現(xiàn)邏輯封裝在一個抽象類中,然后將其擴展為一組實現(xiàn)類。這是一種完美的  OOP 行為,并且行之有效,我們:

  • 創(chuàng)建了一個抽象結(jié)構(gòu)來封裝我們的可重用邏輯

  • 創(chuàng)建了二級構(gòu)造

  • 我們重用的原有的類,并擴展了它

現(xiàn)在,我們想要的是重用邏輯,我們可以簡單地將可重用邏輯提取到函數(shù)中,然后將該函數(shù)作為參數(shù)傳遞給任何其他函數(shù),這種方法,可以少省去一些創(chuàng)建“樣板”過程,因為,我們只是在創(chuàng)建函數(shù)。

下面的代碼顯示了如何在 OOP 中重用程序邏輯。

//Encapsulated behavior封裝行為stract class LogFormatter {      format(msg) {     return Date.now() + "::" + msg   }  }  //重用行為 class ConsoleLogger extends LogFormatter {      log(msg) {     console.log(this.format(msg))   }   }  class FileLogger extends LogFormatter {    log(msg) {     writeToFileSync(this.logFile, this.format(msg))   } }

第二個示是將邏輯提取到函數(shù)中,我們可以混合匹配輕松創(chuàng)建所需的內(nèi)容。你可以繼續(xù)添加更多格式和編寫功能,然后只需將它們與一行代碼混合在一起即可:

// 泛型行為抽象 function format(msg) {   return Date.now() + "::" + msg }  function consoleWriter(msg) {   console.log(msg) }  function fileWriter(msg) {   let logFile = "logfile.log"   writeToFileSync(logFile, msg) }  function logger(output, format) {   return msg => {     output(format(msg))   } } // 通過組合函數(shù)來使用它 const consoleLogger = logger(consoleWriter, format) const fileLogger = logger(fileWriter, format)

這兩種方法都有優(yōu)點,而且都非常有效,沒有誰最優(yōu)。這里只是展示這種方法的靈活性,我們有能力通過  行為(即函數(shù))作為參數(shù),就好像它們是基本類型(如整數(shù)或字符串)一樣。

高階函數(shù)的好處:簡潔代碼

對于這個好處,一個很好的例子就是Array方法,例如forEach,map,reduce等等。在非函數(shù)式編程語言(例如C)中,對數(shù)組元素進行迭代并對其進行轉(zhuǎn)換需要使用for循環(huán)或某些其他循環(huán)結(jié)構(gòu)。這就要求我們以指定方式編寫代碼,就是需求描述循環(huán)發(fā)生的過程。

let myArray = [1,2,3,4] let transformedArray = []  for(let i = 0; i < myArray.length; i++) {   transformedArray.push(myArray[i] * 2)  }

上面的代碼主要做了:

  • 聲明一個新變量i,該變量將用作myArray的索引,其值的范圍為0到myArray的長度

  • 對于i的每個值,將myArray的值在i的位置相乘,并將其添加到transformedArray數(shù)組中。

這種方法很有效,而且相對容易理解,然而,這種邏輯的復(fù)雜性會隨著項目的復(fù)雜程度上升而上升,認知負荷也會隨之增加。但是,像下面這種方式就更容易閱讀:

const double = x => x * 2;  let myArray = [1,2,3,4]; let transformedArray = myArray.map(double);

與第一種方式相比,這種方式更容易閱讀,而且由于邏輯隱藏在兩個函數(shù)(map和double)中,因此你不必擔心了解它們的工作原理。你也可以在第一個示例中將乘法邏輯隱藏在函數(shù)內(nèi)部,但是遍歷邏輯必須存在,這就增加了一些不必要的閱讀阻礙。

柯里化

函數(shù)柯里化是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。我們來看個例子:

function adder(a, b) {   return a + b }  // 變成 const add10 = x => adder(a, 10)

現(xiàn)在,如果你要做的就是將10添加到一系列值中,則可以調(diào)用add10而不是每次都使用相同的第二個參數(shù)調(diào)用adder。這個事例看起來比較蠢,但它是體現(xiàn)了  柯里化 的理想。

你可以將柯里化視為函數(shù)式編程的繼承,然后按照這種思路再回到logger的示例,可以得到以下內(nèi)容:

function log(msg, msgPrefix, output) {   output(msgPrefix + msg) }   function consoleOutput(msg) {   console.log(msg) }  function fileOutput(msg) {   let filename = "mylogs.log"   writeFileSync(msg, filename) }  const logger = msg => log(msg, ">>", consoleOutput); const fileLogger = msg => log(msg, "::", fileOutput);

log的函數(shù)需要三個參數(shù),而我們將其引入僅需要一個參數(shù)的專用版本中,因為其他兩個參數(shù)已由我們選擇。

注意,這里將log函數(shù)視為抽象類,只是因為在我的示例中,不想直接使用它,但是這樣做是沒有限制的,因為這只是一個普通的函數(shù)。如果我們使用的是類,則將無法直接實例化它。

組合函數(shù)

函數(shù)組合就是組合兩到多個函數(shù)來生成一個新函數(shù)的過程。將函數(shù)組合在一起,就像將一連串管道扣合在一起,讓數(shù)據(jù)流過一樣。

在計算機科學(xué)中,函數(shù)組合是將簡單函數(shù)組合成更復(fù)雜函數(shù)的一種行為或機制。就像數(shù)學(xué)中通常的函數(shù)組成一樣,每個函數(shù)的結(jié)果作為下一個函數(shù)的參數(shù)傳遞,而最后一個函數(shù)的結(jié)果是整個函數(shù)的結(jié)果。

這是來自維基百科的函數(shù)組合的定義,粗體部分是比較關(guān)鍵的部分。使用柯里化時,就沒有該限制,我們可以輕松使用預(yù)設(shè)的函數(shù)參數(shù)。

代碼重用聽起來很棒,但是實現(xiàn)起來很難。如果代碼業(yè)務(wù)性過于具體,就很難重用它。如時代碼太過通用簡單,又很少人使用。所以我們需要平衡兩者,一種制作更小的、可重用的部件的方法,我們可以將其作為構(gòu)建塊來構(gòu)建更復(fù)雜的功能。

在函數(shù)式編程中,函數(shù)是我們的構(gòu)建塊。每個函數(shù)都有各自的功能,然后我們把需要的功能(函數(shù))組合起來完成我們的需求,這種方式有點像樂高的積木,在編程中我們稱為  組合函數(shù)。

看下以下兩個函數(shù):

var add10 = function(value) {     return value + 10; }; var mult5 = function(value) {     return value * 5; };

上面寫法有點冗長了,我們用箭頭函數(shù)改寫一下:

var add10 = value => value + 10; var mult5 = value => value * 5;

現(xiàn)在我們需要有個函數(shù)將傳入的參數(shù)先加上 10 ,然后在乘以 5, 如下:

現(xiàn)在我們需要有個函數(shù)將傳入的參數(shù)先加上 10 ,然后在乘以 5, 如下:

var mult5AfterAdd10 = value => 5 * (value + 10)

盡管這是一個非常簡單的例子,但仍然不想從頭編寫這個函數(shù)。首先,這里可能會犯一個錯誤,比如忘記括號。第二,我們已經(jīng)有了一個加 10 的函數(shù) add10  和一個乘以 5 的函數(shù) mult5 ,所以這里我們就在寫已經(jīng)重復(fù)的代碼了。

使用函數(shù) add10,mult5 來重構(gòu) mult5AfterAdd10 :

var mult5AfterAdd10 = value => mult5(add10(value));

我們只是使用現(xiàn)有的函數(shù)來創(chuàng)建 mult5AfterAdd10,但是還有更好的方法。

在數(shù)學(xué)中, f &deg; g 是函數(shù)組合,叫作“f 由 g 組合”,或者更常見的是 “f after g”。因此 (f &deg; g)(x) 等效于f(g(x))  表示調(diào)用 g 之后調(diào)用 f。

在我們的例子中,我們有 mult5 &deg; add10 或 “add10 after mult5”,因此我們的函數(shù)的名稱叫做  mult5AfterAdd10。由于Javascript本身不做函數(shù)組合,看看 Elm 是怎么寫的:

add10 value =     value + 10 mult5 value =     value * 5 mult5AfterAdd10 value =     (mult5 << add10) value

在 Elm 中 << 表示使用組合函數(shù),在上例中 value 傳給函數(shù) *** add10 *** 然后將其結(jié)果傳遞給  mult5。還可以這樣組合任意多個函數(shù):

f x =    (g << h << s << r << t) x

這里 x 傳遞給函數(shù) t,函數(shù) t 的結(jié)果傳遞給 r,函數(shù) t 的結(jié)果傳遞給 s,以此類推。在Javascript中做類似的事情,它看起來會像  ***g(h(s(r(t(x)))))***,一個括號噩夢。

常見的函數(shù)式函數(shù)(Functional Function)

函數(shù)式語言中3個常見的函數(shù):Map,Filter,Reduce。

如下JavaScript代碼:

for (var i = 0; i < something.length; ++i) {    // do stuff }

這段代碼存在一個很大的問題,但不是bug。問題在于它有很多重復(fù)代碼(boilerplate  code)。如果你用命令式語言來編程,比如Java,C#,JavaScript,PHP,Python等等,你會發(fā)現(xiàn)這樣的代碼你寫地最多。這就是問題所在。

現(xiàn)在讓我們一步一步的解決問題,最后封裝成一個看不見 for 語法函數(shù):

先用名為 things 的數(shù)組來修改上述代碼:

var things = [1, 2, 3, 4]; for (var i = 0; i < things.length; ++i) {     things[i] = things[i] * 10; // 警告:值被改變! } console.log(things); // [10, 20, 30, 40]

這樣做法很不對,數(shù)值被改變了!

在重新修改一次:

var things = [1, 2, 3, 4]; var newThings = []; for (var i = 0; i < things.length; ++i) {     newThings[i] = things[i] * 10; } console.log(newThings); // [10, 20, 30, 40]

這里沒有修改***things***數(shù)值,但卻卻修改了***newThings***。暫時先不管這個,畢竟我們現(xiàn)在用的是  JavaScript。一旦使用函數(shù)式語言,任何東西都是不可變的。

現(xiàn)在將代碼封裝成一個函數(shù),我們將其命名為 map,因為這個函數(shù)的功能就是將一個數(shù)組的每個值映射(map)到新數(shù)組的一個新值。

var map = (f, array) => {     var newArray = [];     for (var i = 0; i < array.length; ++i) {         newArray[i] = f(array[i]);     }     return newArray; };

函數(shù) f 作為參數(shù)傳入,那么函數(shù) map 可以對 array 數(shù)組的每項進行任意的操作。

現(xiàn)在使用 map 重寫之前的代碼:

var things = [1, 2, 3, 4]; var newThings = map(v => v * 10, things);

這里沒有 for 循環(huán)!而且代碼更具可讀性,也更易分析。

現(xiàn)在讓我們寫另一個常見的函數(shù)來過濾數(shù)組中的元素:

var filter = (pred, array) => {     var newArray = []; for (var i = 0; i < array.length; ++i) {         if (pred(array[i]))             newArray[newArray.length] = array[i];     }     return newArray; };

當某些項需要被保留的時候,斷言函數(shù) pred 返回TRUE,否則返回FALSE。

使用過濾器過濾奇數(shù):

var isOdd = x => x % 2 !== 0; var numbers = [1, 2, 3, 4, 5]; var oddNumbers = filter(isOdd, numbers); console.log(oddNumbers); // [1, 3, 5]

比起用 for 循環(huán)的手動編程,filter  函數(shù)簡單多了。最后一個常見函數(shù)叫reduce。通常這個函數(shù)用來將一個數(shù)列歸約(reduce)成一個數(shù)值,但事實上它能做很多事情。

在函數(shù)式語言中,這個函數(shù)稱為 fold。

var reduce = (f, start, array) => {     var acc = start;     for (var i = 0; i < array.length; ++i)         acc = f(array[i], acc); // f() 有2個參數(shù)     return acc; });

reduce函數(shù)接受一個歸約函數(shù) f,一個初始值 start,以及一個數(shù)組 array。

這三個函數(shù),map,filter,reduce能讓我們繞過for循環(huán)這種重復(fù)的方式,對數(shù)組做一些常見的操作。但在函數(shù)式語言中只有遞歸沒有循環(huán),這三個函數(shù)就更有用了。附帶提一句,在函數(shù)式語言中,遞歸函數(shù)不僅非常有用,還必不可少。

看完上述內(nèi)容,你們掌握JavaScript中的函數(shù)式編程函數(shù)和組合以及柯里化是怎樣的的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

網(wǎng)站標題:JavaScript中的函數(shù)式編程函數(shù)和組合以及柯里化是怎樣的
本文網(wǎng)址:http://muchs.cn/article28/jpshjp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供標簽優(yōu)化、商城網(wǎng)站網(wǎng)站改版、外貿(mào)建站、網(wǎng)站收錄、網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)