摘要:而數組里則是為每一個值運行一次映射函數,無論這個值何時加入,然后把它返回到里。上一章翻譯連載第章異步的函數式上輕量級函數式編程你不知道的姊妹篇原創新書移動前端高效開發實戰已在亞馬遜京東當當開售。
原文地址:Functional-Light-JS
原文作者:Kyle Simpson-《You-Dont-Know-JS》作者
第 10 章:異步的函數式(下) 響應式函數式編程關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結,是 JavaScript 中最嚴謹的邏輯。經過捶打磨練,成就了本書的中文版。本書包含了函數式編程之精髓,希望可以幫助大家在學習函數式編程的道路上走的更順暢。比心。
譯者團隊(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao
為了理解如何在2個值之間創建和使用惰性的映射,我們需要去抽象我們對列表(數組)的想法。
讓我們來想象一個智能的數組,不只是簡單地獲得值,還是一個懶惰地接受和響應(也就是“反應”)值的數組。考慮下:
var a = new LazyArray(); var b = a.map( function double(v){ return v * 2; } ); setInterval( function everySecond(){ a.push( Math.random() ); }, 1000 );
至此,這段代碼的數組和普通的沒有什么區別。唯一不同的是在我們執行 map(..) 來映射數組 a 生成數組 b 之后,定時器在 a 里面添加隨機的值。
但是這個虛構的 LazyArray 有點不同,它假設了值可以隨時的一個一個添加進去。就像隨時可以 push(..) 你想要的值一樣。可以說 b 就是一個惰性映射 a 最終值的數組。
此外,當 a 或者 b 改變時,我們不需要確切地保存里面的值,這個特殊的數組將會保存它所需的值。所以這些數組不會隨著時間而占用更多的內存,這是 惰性數據結構和懶操作的重要特點。事實上,它看起來不像數組,更像是buffer(緩沖區)。
普通的數組是積極的,所以它會立馬保存所有它的值。"惰性數組" 的值則會延遲保存。
由于我們不一定要知道 a 什么時候添加了新的值,所以另一個關鍵就是我們需要有去監聽 b 并在有新值的時候通知它的能力。我們可以想象下監聽器是這樣的:
b.listen( function onValue(v){ console.log( v ); } );
b 是反應性的,因為它被設置為當 a 有值添加時進行反應。函數式編程操作當中的 map(..) 是把數據源 a 里面的所有值轉移到目標 b 里。每次映射操作都是我們使用同步函數式編程進行單值建模的過程,但是接下來我們將讓這種操作變得可以響應式執行。
注意: 最常用到這些函數式編程的是響應式函數式編程(FRP)。我故意避開這個術語是因為一個有關于 FP + Reactive 是否真的構成 FRP 的辯論。我們不會全面深入了解 FRP 的所有含義,所以我會繼續稱之為響應式函數式編程。或者,如果你不會感覺那么困惑,也可以稱之為事件機制函數式編程。
我們可以認為 a 是生成值的而 b 則是去消費這些值的。所以為了可讀性,我們得重新整理下這段代碼,讓問題歸結于 生產者 和 消費者。
// 生產者: var a = new LazyArray(); setInterval( function everySecond(){ a.push( Math.random() ); }, 1000 ); // ************************** // 消費者: var b = a.map( function double(v){ return v * 2; } ); b.listen( function onValue(v){ console.log( v ); } );
a 是一個行為本質上很像數據流的生產者。我們可以把每個值賦給 a 當作一個事件。map(..) 操作會觸發 b 上面的 listen(..) 事件來消費新的值。
我們分離 生產者 和 消費者 的相關代碼,是因為我們的代碼應該各司其職。這樣的代碼組織可以很大程度上提高代碼的可讀性和維護性。
聲明式的時間我們應該非常謹慎地討論如何介紹時間狀態。具體來說,正如 promise 從單個異步操作中抽離出我們所擔心的時間狀態,響應式函數式編程從一系列的值/操作中抽離(分割)了時間狀態。
從 a (生產者)的角度來說,唯一與時間相關的就是我們手動調用的 setInterval(..) 循環。但它只是為了示范。
想象下 a 可以被綁定上一些其他的事件源,比如說用戶的鼠標點擊事件和鍵盤按鍵事件,服務端來的 websocket 消息等。在這些情況下,a 沒必要關注自己的時間狀態。每當值準備好,它就只是一個與值連接的無時態管道。
從 b (消費者)的角度來說,我們不用知道或者關注 a 里面的值在何時何地來的。事實上,所有的值都已經存在。我們只關注是否無論何時都能取到那些值。或者說,map(..) 的轉換操作是一個無時態(惰性)的建模過程。
時間 與 a 和 b 之間的關系是聲明式的,不是命令式的。
以 operations-over-time 這種方式來組織值可能不是很有效。讓我們來對比下相同的功能如何用命令式來表示:
// 生產者: var a = { onValue(v){ b.onValue( v ); } }; setInterval( function everySecond(){ a.onValue( Math.random() ); }, 1000 ); // ************************** // 消費者: var b = { map(v){ return v * 2; }, onValue(v){ v = this.map( v ); console.log( v ); } };
這似乎很微妙,但這就是存在于命令式版本的代碼和之前聲明式的版本之間一個很重要的不同點,除了 b.onValue(..) 需要自己去調用 this.map(..) 之外。在之前的代碼中, b 從 a 當中去拉取,但是在這個代碼中,a 推送給 b。換句話說,把 b = a.map(..) 替換成 b.onValue(v)。
在上面的命令式代碼中,以消費者的角度來說它并不清楚 v 從哪里來。此外命令式強硬的把代碼 b.onValue(..) 夾雜在生產者 a 的邏輯里,這有點違反了關注點分離原則。這將會讓分離生產者和消費者變得困難。
相比之下,在之前的代碼中,b = a.map(..) 表示了 b 的值來源于 a ,對于如同抽象事件流的數據源 a,我們不需要關心。我們可以 確信 任何來自于 a 到 b 里的值都會通過 map(..) 操作。
映射之外的東西為了方便,我們已經說明了通過隨著時間一次一次的用 map(..) 來綁定 a 和 b 的概念。其實我們許多其他的函數式編程操作也可以做到這種效果。
思考下:
var b = a.filter( function isOdd(v) { return v % 2 == 1; } ); b.listen( function onlyOdds(v){ console.log( "Odd:", v ); } );
這里可以看到 a 的值肯定會通過 isOdd(..) 賦值給 b。
即使是 reduce(..) 也可以持續的運行:
var b = a.reduce( function sum(total,v){ return total + v; } ); b.listen( function runningTotal(v){ console.log( "New current total:", v ); } );
因為我們調用 reduce(..) 是沒有給具體 initialValue 的值,無論是 sum(..) 或者 runningTotal(..) 都會等到有 2 個來自 a 的參數時才會被調用。
這段代碼暗示了在 reduction 里面有一個 內存空間, 每當有新的值進來的時候,sum(..) 才會帶上第一個參數 total 和第二個參數 v被調用。
其他的函數式編程操作會在內部作用域請求一個緩存區,比如說 unique(..) 可以追蹤每一個它訪問過的值。
Observables希望現在你可以察覺到響應式,事件式,類數組結構的數據的重要性,就像我們虛構出來的 LazyArray 一樣。值得高興的是,這類的數據結構已經存在的了,它就叫 observable。
注意: 只是做些假設(希望):接下來的討論只是簡要的介紹 observables。這是一個需要我們花時間去探究的深層次話題。但是如果你理解本文中的輕量級函數式編程,并且知道如何通過函數式編程的原理來構建異步的話,那么接著學習 observables 將會變得得心應手。
現在已經有各種各樣的 Observables 的庫類, 最出名的是 RxJS 和 Most。在寫這篇文章的時候,正好有一個直接向 JS 里添加 observables 的建議,就像 promise。為了演示,我們將用 RxJS 風格的 Observables 來完成下面的例子。
這是我們一個較早的響應式的例子,但是用 Observables 來代替 LazyArray:
// 生產者: var a = new Rx.Subject(); setInterval( function everySecond(){ a.next( Math.random() ); }, 1000 ); // ************************** // 消費者: var b = a.map( function double(v){ return v * 2; } ); b.subscribe( function onValue(v){ console.log( v ); } );
在 RxJS 中,一個 Observer 訂閱一個 Observable。如果你把 Observer 和 Observable 的功能結合到一起,那就會得到一個 Subject。因此,為了保持代碼的簡潔,我們把 a 構建成一個 Subject,所以我們可以調用它的 next(..) 方法來添加值(事件)到他的數據流里。
如果我們要讓 Observer 和 Observable 保持分離:
// 生產者: var a = Rx.Observable.create( function onObserve(observer){ setInterval( function everySecond(){ observer.next( Math.random() ); }, 1000 ); } );
在這個代碼里,a 是 Observable,毫無疑問,observer 就是獨立的 observer,它可以去“觀察”一些事件(比如我們的setInterval(..)循環),然后我們使用它的 next(..) 方法來發送一些事件到 observable a 的流里。
除了 map(..),RxJS 還定義了超過 100 個可以在有新值添加時才觸發的方法。就像數組一樣。每個 Observable 的方法都會返回一個新的 Observable,意味著他們是鏈式的。如果一個方法被調用,則它的返回值應該由輸入的 Observable 去返回,然后觸發到輸出的 Observable里,否則拋棄。
一個鏈式的聲明式 observable 的例子:
var b = a .filter( v => v % 2 == 1 ) // 過濾掉偶數 .distinctUntilChanged() // 過濾連續相同的流 .throttle( 100 ) // 函數節流(合并100毫秒內的流) .map( v = v * 2 ); // 變2倍 b.subscribe( function onValue(v){ console.log( "Next:", v ); } );
注意: 這里的鏈式寫法不是一定要把 observable 賦值給 b 和調用 b.subscribe(..) 分開寫,這樣做只是為了讓每個方法都會得到一個新的返回值。通常,subscribe(..) 方法都會在鏈式寫法的最后被調用。
總結這本書詳細的介紹了各種各樣的函數式編程操作,例如:把單個值(或者說是一個即時列表的值)轉換到另一個值里。
對于那些有時態的操作,所有基礎的函數式編程原理都可以無時態的應用。就像 promise 創建了一個單一的未來值,我們可以創建一個積極的列表的值來代替像惰性的observable(事件)流的值。
數組的 map(..) 方法會用當前數組中的每一個值運行一次映射函數,然后放到返回的數組里。而 observable 數組里則是為每一個值運行一次映射函數,無論這個值何時加入,然后把它返回到 observable 里。
或者說,如果數組對函數式編程操作是一個積極的數據結構,那么 observable 相當于持續惰性的。
【上一章】翻譯連載 | 第 10 章:異步的函數式(上)-《JavaScript輕量級函數式編程》 |《你不知道的JS》姊妹篇
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、當當開售。
iKcamp官網:http://www.ikcamp.com
訪問官網更快閱讀全部免費分享課程:《iKcamp出品|全網最新|微信小程序|基于最新版1.0開發者工具之初中級培訓教程分享》。
包含:文章、視頻、源代碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89012.html
摘要:這就是積極的函數式編程。上一章翻譯連載第章遞歸下輕量級函數式編程你不知道的姊妹篇原創新書移動前端高效開發實戰已在亞馬遜京東當當開售。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總...
摘要:我稱之為輕量級函數式編程。序眾所周知,我是一個函數式編程迷。函數式編程有很多種定義。本書是你開啟函數式編程旅途的絕佳起點。事實上,已經有很多從頭到尾正確的方式介紹函數式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團隊(排名不分先后):阿希、blueken、brucecham、...
摘要:為此決定自研一個富文本編輯器。例如當要轉化的對象有環存在時子節點屬性賦值了父節點的引用,為了關于函數式編程的思考作者李英杰,美團金融前端團隊成員。只有正確使用作用域,才能使用優秀的設計模式,幫助你規避副作用。 JavaScript 專題之惰性函數 JavaScript 專題系列第十五篇,講解惰性函數 需求 我們現在需要寫一個 foo 函數,這個函數返回首次調用時的 Date 對象,注意...
摘要:所以我覺得函數式編程領域更像學者的領域。函數式編程的原則是完善的,經過了深入的研究和審查,并且可以被驗證。函數式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數式編程編程者會認為形式主義本身有助于學習。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液...
摘要:譯者團隊排名不分先后阿希冬青蘿卜萌萌輕量級函數式編程第章融會貫通現在你已經掌握了所有需要掌握的關于輕量級函數式編程的內容。回頭想想我們用到的函數式編程原則。這兩個函數組合成一個映射函數通過,這就是融合見第章。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:...
閱讀 2106·2021-11-24 09:39
閱讀 1495·2019-08-30 15:44
閱讀 1946·2019-08-29 17:06
閱讀 3393·2019-08-29 16:32
閱讀 3543·2019-08-29 16:26
閱讀 2654·2019-08-29 15:35
閱讀 3026·2019-08-29 12:50
閱讀 1636·2019-08-29 11:15