亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

JavaScript 函數式編程

2019-11-11 06:13:00
字體:
來源:轉載
供稿:網友

javaScript 函數式編程

近年來,函數式編程(Functional PRogramming)已經成為了Javascript社區中炙手可熱的主題之一,無論你是否欣賞這種編程理念,相信你都已經對它有所了解。即使是前幾年函數式編程尚未流行的時候,我已經在很多的大型應用代碼庫中發現了不少對于函數式編程理念的深度實踐。函數式編程即是在軟件開發的工程中避免使用共享狀態(Shared State)、可變狀態(Mutable Data)以及副作用(Side Effects)。函數式編程中整個應用由數據驅動,應用的狀態在不同純函數之間流動。與偏向命令式編程的面向對象編程而言,函數式編程其更偏向于聲明式編程,代碼更加簡潔明了、更可預測,并且可測試性也更好。。函數式編程本質上也是一種編程范式(Programming Paradigm),其代表了一系列用于構建軟件系統的基本定義準則。其他編程范式還包括面向對象編程(Object Oriented Programming)與過程程序設計(Procedural Programming)。

純函數

顧名思義,純函數往往指那些僅根據輸入參數決定輸出并且不會產生任何副作用的函數。純函數最優秀的特性之一在于其結果的可預測性:

var z = 10; function add(x, y) { return x + y; } console.log(add(1, 2)); // prints 3 console.log(add(1, 2)); // still prints 3 console.log(add(1, 2)); // WILL ALWAYS print 3

在add函數中并沒有操作z變量,即沒有讀取z的數值也沒有修改z的值。它僅僅根據參數輸入的x與y變量然后返回二者相加的和。這個add函數就是典型的純函數,而如果在add函數中涉及到了讀取或者修改z變量,那么它就失去了純潔性。我們再來看另一個函數:

function justTen() { return 10; }

對于這樣并沒有任何輸入參數的函數,如果它要保持為純函數,那么該函數的返回值就必須為常量。不過像這種固定返回為常量的函數還不如定義為某個常量呢,就沒必要大材小用用函數了,因此我們可以認為絕大部分的有用的純函數至少允許一個輸入參數。再看看下面這個函數:

function addNoReturn(x, y) { var z = x + y }

注意這個函數并沒有返回任何值,它確實擁有兩個輸入參數x與y,然后將這兩個變量相加賦值給z,因此這樣的函數也可以認為是無意義的。這里我們可以說,絕大部分有用的純函數必須要有返回值。總結而言,純函數應該具有以下幾個特效:

絕大部分純函數應該擁有一或多個參數值。

純函數必須要有返回值。

相同輸入的純函數的返回值必須一致。

純函數不能夠產生任何的副作用。

共享狀態與副作用

共享狀態(Shared State)可以是存在于共享作用域(全局作用域與閉包作用域)或者作為傳遞到不同作用域的對象屬性的任何變量、對象或者內存空間。在面向對象編程中,我們常常是通過添加屬性到其他對象的方式共享某個對象。共享狀態問題在于,如果開發者想要理解某個函數的作用,必須去詳細了解該函數可能對于每個共享變量造成的影響。譬如我們現在需要將客戶端生成的用戶對象保存到服務端,可以利用saveUser()函數向服務端發起請求,將用戶信息編碼傳遞過去并且等待服務端響應。而就在你發起請求的同時,用戶修改了個人頭像,觸發了另一個函數updateAvatar()以及另一次saveUser()請求。正常來說,服務端會先響應第一個請求,并且根據第二個請求中用戶參數的變更對于存儲在內存或者數據庫中的用戶信息作相應的修改。不過某些意外情況下,可能第二個請求會比第一個請求先到達服務端,這樣用戶選定的新的頭像反而會被第一個請求中的舊頭像覆寫。這里存放在服務端的用戶信息就是所謂的共享狀態,而因為多個并發請求導致的數據一致性錯亂也就是所謂的競態條件(Race Condition),也是共享狀態導致的典型問題之一。另一個共享狀態的常見問題在于不同的調用順序可能會觸發未知的錯誤,這是因為對于共享狀態的操作往往是時序依賴的。

const x = { val: 2 }; const x1 = () => x.val += 1; const x2 = () => x.val *= 2; x1(); x2(); console.log(x.val); // 6 const y = { val: 2 }; const y1 = () => y.val += 1; const y2 = () => y.val *= 2; // 交換了函數調用順序 y2(); y1(); // 最后的結果也受到了影響 console.log(y.val); // 5

副作用指那些在函數調用過程中沒有通過返回值表現的任何可觀測的應用狀態變化,常見的副作用包括但不限于:

修改任何外部變量或者外部對象屬性

在控制臺中輸出日志

寫入文件

發起網絡通信

觸發任何外部進程事件

調用任何其他具有副作用的函數

在函數式編程中我們會盡可能地規避副作用,保證程序更易于理解與測試。Haskell或者其他函數式編程語言通常會使用Monads來隔離與封裝副作用。在絕大部分真實的應用場景進行編程開始時,我們不可能保證系統中的全部函數都是純函數,但是我們應該盡可能地增加純函數的數目并且將有副作用的部分與純函數剝離開來,特別是將業務邏輯抽象為純函數,來保證軟件更易于擴展、重構、調試、測試與維護。這也是很多前端框架鼓勵開發者將用戶的狀態管理與組件渲染相隔離,構建松耦合模塊的原因。

不變性

不可變對象(Immutable Object)指那些創建之后無法再被修改的對象,與之相對的可變對象(Mutable Object)指那些創建之后仍然可以被修改的對象。不可變性(Immutability)是函數式編程的核心思想之一,保證了程序運行中數據流的無損性。如果我們忽略或者拋棄了狀態變化的歷史,那么我們很難去捕獲或者復現一些奇怪的小概率問題。使用不可變對象的優勢在于你在程序的任何地方訪問任何的變量,你都只有只讀權限,也就意味著我們不用再擔心意外的非法修改的情況。另一方面,特別是在多線程編程中,每個線程訪問的變量都是常量,因此能從根本上保證線程的安全性??偨Y而言,不可變對象能夠幫助我們構建簡單而更加安全的代碼。在JavaScript中,我們需要搞清楚const與不可變性之間的區別。const聲明的變量名會綁定到某個內存空間而不可以被二次分配,其并沒有創建真正的不可變對象。你可以不修改變量的指向,但是可以修改該對象的某個屬性值,因此const創建的還是可變對象。JavaScript中最方便的創建不可變對象的方法就是調用Object.freeze()函數,其可以創建一層不可變對象:

const a = Object.freeze({ foo: 'Hello', bar: 'world', baz: '!' }); a.foo = 'Goodbye'; // Error: Cannot assign to read only property 'foo' of object Object

不過這種對象并不是徹底的不可變數據,譬如如下的對象就是可變的:

const a = Object.freeze({ foo: { greeting: 'Hello' }, bar: 'world', baz: '!' }); a.foo.greeting = 'Goodbye'; console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`);

如上所見,頂層的基礎類型屬性是不可以改變的,不過如果對象類型的屬性,譬如數組等,仍然是可以變化的。在很多函數式編程語言中,會提供特殊的不可變數據結構Trie Data Structures來實現真正的不可變數據結構,任何層次的屬性都不可以被改變。Tries還可以利用結構共享(Structural Sharing)的方式來在新舊對象之間共享未改變的對象屬性值,從而減少內存占用并且顯著提升某些操作的性能。JavaScript中雖然語言本身并沒有提供給我們這個特性,但是可以通過Immutable.js與Mori這些輔助庫來利用Tries的特性。我個人兩個庫都使用過,不過在大型項目中會更傾向于使用Immutable.js。估計到這邊,很多習慣了命令式編程的同學都會大吼一句:在沒有變量的世界里我又該如何編程呢?不要擔心,現在我們考慮下我們何時需要去修改變量值:譬如修改某個對象的屬性值,或者在循環中修改某個循環計數器的值。而函數式編程中與直接修改原變量值相對應的就是創建原值的一個副本并且將其修改之后賦予給變量。而對于另一個常見的循環場景,譬如我們所熟知的for,while,do,repeat這些關鍵字,我們在函數式編程中可以使用遞歸來實現原本的循環需求:

// 簡單的循環構造 var acc = 0; for (var i = 1; i <= 10; ++i) acc += i; console.log(acc); // prints 55 // 遞歸方式實現 function sumRange(start, end, acc) { if (start > end) return acc; return sumRange(start + 1, end, acc + start) } console.log(sumRange(1, 10, 0)); // prints 55

注意在遞歸中,與變量i相對應的即是start變量,每次將該值加1,并且將acc+start作為當前和值傳遞給下一輪遞歸操作。在遞歸中,并沒有修改任何的舊的變量值,而是根據舊值計算出新值并且進行返回。不過如果真的讓你把所有的迭代全部轉變成遞歸寫法,估計得瘋掉,這個不可避免地會受到JavaScript語言本身的混亂性所影響,并且迭代式的思維也不是那么容易理解的。而在Elm這種專門面向函數式編程的語言中,語法會簡化很多:

sumRange start end acc = if start > end then acc else sumRange (start + 1) end (acc + start)

其每一次的迭代記錄如下:

sumRange 1 10 0 = -- sumRange (1 + 1) 10 (0 + 1) sumRange 2 10 1 = -- sumRange (2 + 1) 10 (1 + 2) sumRange 3 10 3 = -- sumRange (3 + 1) 10 (3 + 3) sumRange 4 10 6 = -- sumRange (4 + 1) 10 (6 + 4) sumRange 5 10 10 = -- sumRange (5 + 1) 10 (10 + 5) sumRange 6 10 15 = -- sumRange (6 + 1) 10 (15 + 6) sumRange 7 10 21 = -- sumRange (7 + 1) 10 (21 + 7) sumRange 8 10 28 = -- sumRange (8 + 1) 10 (28 + 8) sumRange 9 10 36 = -- sumRange (9 + 1) 10 (36 + 9) sumRange 10 10 45 = -- sumRange (10 + 1) 10 (45 + 10) sumRange 11 10 55 = -- 11 > 10 => 55 55

高階函數

函數式編程傾向于重用一系列公共的純函數來處理數據,而面向對象編程則是將方法與數據封裝到對象內。這些被封裝起來的方法復用性不強,只能作用于某些類型的數據,往往只能處理所屬對象的實例這種數據類型。而函數式編程中,任何類型的數據則是被一視同仁,譬如map()函數允許開發者傳入函數參數,保證其能夠作用于對象、字符串、數字,以及任何其他類型。JavaScript中函數同樣是一等公民,即我們可以像其他類型一樣處理函數,將其賦予變量、傳遞給其他函數或者作為函數返回值。而高階函數(Higher Order Function)則是能夠接受函數作為參數,能夠返回某個函數作為返回值的函數。高階函數經常用在如下場景:

利用回調函數、Promise或者Monad來抽象或者隔離動作、作用以及任何的異步控制流

構建能夠作用于泛數據類型的工具函數

函數重用或者創建柯里函數

將輸入的多個函數并且返回這些函數復合而來的復合函數

典型的高階函數的應用就是復合函數,作為開發者,我們天性不希望一遍一遍地重復構建、測試與部分相同的代碼,我們一直在尋找合適的只需要寫一遍代碼的方法以及如何將其重用于其他模塊。代碼重用聽上去非常誘人,不過其在很多情況下是難以實現的。如果你編寫過于偏向具體業務的代碼,那么就會難以重用。而如果你把每一段代碼都編寫的過于泛化,那么你就很難將這些代碼應用于具體的有業務場景,而需要編寫額外的連接代碼。而我們真正追尋的就是在具體與泛化之間尋求一個平衡點,能夠方便地編寫短小精悍而可復用的代碼片,并且能夠將這些小的代碼片快速組合而解決復雜的功能需求。在函數式編程中,函數就是我們能夠面向的最基礎代碼塊,而在函數式編程中,對于基礎塊的組合就是所謂的函數復合(Function Composition)。我們以如下兩個簡單的JavaScript函數為例:

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

如果你習慣了使用ES6,那么可以用Arrow Function重構上述代碼:

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

現在看上去清爽多了吧,下面我們考慮面對一個新的函數需求,我們需要構建一個函數,首先將輸入參數加10然后乘以5,我們可以創建一個新函數如下:

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

盡管上面這個函數也很簡單,我們還是要避免任何函數都從零開始寫,這樣也會讓我們做很多重復性的工作。我們可以基于上文的add10與mult5這兩個函數來構建新的函數:

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

在mult5AfterAdd10函數中,我們已經站在了add10與mult5這兩個函數的基礎上,不過我們可以用更優雅的方式來實現這個需求。在數學中,我們認為f ° g是所謂的Function Composition,因此`f ° g可以認為等價于f(g(x)),我們同樣可以基于這種思想重構上面的mult5AfterAdd10。不過JavaScript中并沒有原生的Function Composition支持,在Elm中我們可以用如下寫法:

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

這里的<<操作符也就指明了在Elm中是如何組合函數的,同時也較為直觀的展示出了數據的流向。首先value會被賦予給add10,然后add10的結果會流向mult5。另一個需要注意的是,(mult5 << add10)中的中括號是為了保證函數組合會在函數調用之前。你也可以組合更多的函數:

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

如果在JavaScript中,你可能需要以如下的遞歸調用來實現該功能:

g(h(s(r(t(x)))))
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩精品在线播放| 亚洲精品videossex少妇| 欧美在线视频在线播放完整版免费观看| 日韩在线观看免费全| 8090成年在线看片午夜| 国产精品视频男人的天堂| 韩国三级电影久久久久久| 久青草国产97香蕉在线视频| 国产精品成人一区二区三区吃奶| 热门国产精品亚洲第一区在线| 日韩精品免费在线视频| 亚洲成色999久久网站| 在线中文字幕日韩| 日韩美女av在线| 色噜噜国产精品视频一区二区| 上原亚衣av一区二区三区| 91在线高清免费观看| 亚洲网站在线播放| 欧美电影免费看| 亚洲一区中文字幕| 久久久国产精品一区| 亚洲一区二区三区成人在线视频精品| 久久综合色88| 福利视频导航一区| 国产精品盗摄久久久| 日韩一区二区三区在线播放| 久久影视电视剧免费网站清宫辞电视| 日韩av网站导航| 日韩视频免费在线| 全色精品综合影院| 欧美日韩中文在线观看| 亚洲色图25p| 日韩在线视频网| 日韩av手机在线观看| 日韩在线国产精品| 日韩国产精品一区| 激情久久av一区av二区av三区| 国产欧美一区二区| 欧美性极品xxxx做受| 欧美午夜久久久| 国产xxx69麻豆国语对白| 欧美日韩亚洲精品内裤| 在线视频国产日韩| 国产美女久久精品| 久久国产精品久久精品| 日韩大陆毛片av| 精品久久久久久久久久ntr影视| 久久久久久国产精品美女| 伊人伊成久久人综合网站| 国色天香2019中文字幕在线观看| 久久精品国产亚洲精品| 亚洲自拍偷拍福利| 中国china体内裑精亚洲片| 91高清视频免费观看| 欧美华人在线视频| 欧美一级电影免费在线观看| 国产精品丝袜久久久久久高清| 久久av在线播放| 欧美激情免费视频| 91久久在线播放| 国产一区二区欧美日韩| 国产精品嫩草视频| 欧美国产一区二区三区| 免费91在线视频| 91精品国产色综合久久不卡98口| 欧洲成人性视频| 久久久综合免费视频| 最新国产精品拍自在线播放| 精品色蜜蜜精品视频在线观看| 久久视频这里只有精品| 欧美激情视频一区二区| 一区二区三区四区精品| 亚洲尤物视频网| 国产精品第8页| 亚洲午夜精品久久久久久久久久久久| 亚洲成色777777女色窝| 国产精品高潮视频| 亚洲国产小视频| 成人精品福利视频| 2020国产精品视频| 国产精品永久免费视频| 国产精品欧美日韩一区二区| 国产精品爱啪在线线免费观看| 日本久久久久久久久| 超碰97人人做人人爱少妇| 另类天堂视频在线观看| 精品久久中文字幕| 久久福利网址导航| 欧美在线视频观看| 亚洲综合在线做性| 国产欧美精品一区二区三区-老狼| 久久中文字幕国产| 欧美激情一区二区久久久| 亚洲精品在线91| 亚洲国产女人aaa毛片在线| 国产不卡av在线免费观看| 97视频在线观看免费| 亚洲精品色婷婷福利天堂| 91国内揄拍国内精品对白| 97在线精品国自产拍中文| 国产97在线亚洲| 日韩一区av在线| 精品女厕一区二区三区| 九色成人免费视频| 色婷婷综合久久久久| 久色乳综合思思在线视频| 日韩精品中文字幕在线观看| 91精品国产九九九久久久亚洲| 欧美成人免费播放| 亚洲成av人片在线观看香蕉| 欧美巨大黑人极品精男| 91免费福利视频| 精品国产区一区二区三区在线观看| 国产日韩欧美在线观看| 久青草国产97香蕉在线视频| 成人高h视频在线| 亚洲а∨天堂久久精品9966| 最新国产精品亚洲| 91成人免费观看网站| 亚洲国产精品va在线观看黑人| 国产精品视频专区| 亚洲人成77777在线观看网| 日韩av观看网址| 国产精品嫩草影院一区二区| 黑人精品xxx一区| 亚洲欧洲日产国产网站| 高清欧美性猛交xxxx黑人猛交| 亚洲男人天堂2019| 久久激情视频免费观看| 国产精品色婷婷视频| 欧美孕妇性xx| 欧美日韩精品在线观看| 亚洲石原莉奈一区二区在线观看| 欧美亚洲激情在线| 亚洲精品成人久久| 国产女同一区二区| 亚洲自拍偷拍区| 国产精品视频地址| 日本中文字幕不卡免费| 久久久亚洲精选| 国产亚洲a∨片在线观看| 亚洲国产精品99久久| 国产精品久久精品| 久久影视电视剧免费网站| 久久福利视频网| 色综合久久天天综线观看| 清纯唯美亚洲激情| 97在线视频精品| 亚洲第一区第一页| 国产丝袜一区视频在线观看| 97av在线视频免费播放| 国产成人精品久久二区二区91| 97免费中文视频在线观看| 亚洲一区二区三区乱码aⅴ蜜桃女| 久久综合久中文字幕青草| 中文字幕亚洲综合久久筱田步美| 欧美精品成人在线| 欧美性xxxxhd| 亚洲精品国产精品国自产观看浪潮| 亚洲日本欧美日韩高观看| 成人黄色免费在线观看| 日韩最新av在线| 久久99久久99精品中文字幕| 97人洗澡人人免费公开视频碰碰碰|