文章摘要: RxJS 是基於觀察者模式和迭代器模式以函數語言程式設計思維來實現的讓一個或多個觀察者物件監聽一個主題物件
ReactiveX combines the Observer pattern
with the Iterator pattern
and functional programming
with collections to fill the need for an ideal way of managing sequences of events. ReactiveX將觀察者模式、迭代器模式和函式程式設計與集合結合起來,以滿足管理事件序列的理想方式的需要。
根據官方定義,RxJS 是基於觀察者模式和迭代器模式以函數語言程式設計思維來實現的,那麼我們先了解一下這幾個概念。
函數語言程式設計(Functional Programming)
什麼是函數語言程式設計 ?
Functional Programming
是一種程式設計正規化( programming paradigm
),就像 Object-oriented Programming(OOP)
一樣,就是一種寫程式的方法論,這些方法論告訴我們如何思考及解決問題。
函數語言程式設計關心資料的對映,指令式程式設計關心解決問題的步驟.
這裏的對映就是數學上函式的概念——一種東西和另一種東西之間的對應關係, 簡單說 Functional Programming
核心思想就是做運算處理,並用function 來思考問題.
函數語言程式設計基本要素
函式為一等公民(First Class)
所謂一等公民是指跟其它物件具有同等的地位,也就是說函式能夠被賦值給變數,也能夠被當作引數傳入另一個函式,也可當作一個函式的返回值。
// 函式能夠被賦值給變數 var hello = function() {} // 函式當作引數傳入另一個函式 fetch('www.google.com') .then(function(response) {}) // 匿名 function 被傳入 then() // 當作一個函式的返回值 var a = function(a) { return function(b) { return a + b; }; }
Expression, no Statement
Functional Programming
都是表示式( Expression
)不會是語句(Statement)。 基本區分表示式與語句:
function
有時候表示式也可能同時是合法的陳述式,這裏只講基本的判斷方法。如果想更深入瞭解其中的差異,可以看這篇文章 Expressions versus statements in JavaScript
純函式(Pure Function)
純函式是這樣一種函式,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用( side effect
)
舉個簡單的例子, slice
和 splice
:
var arr = [1, 2, 3, 4, 5]; arr.slice(0, 3); // [1, 2, 3] arr.slice(0, 3); // [1, 2, 3] arr.slice(0, 3); // [1, 2, 3]
這裏可以看到 slice
不管執行幾次,返回值都是相同的,並且除了返回一個值之外並沒有做任何事,所以 slice
就是一個 pure function
。
var arr = [1, 2, 3, 4, 5]; arr.splice(0, 3); // [1, 2, 3] arr.splice(0, 3); // [4, 5] arr.slice(0, 3); // []
這裏我們換成用 splice
,因為 splice
每執行一次就會影響 arr
的值,導致每次結果都不同,這就很明顯不是一個 pure function
。
什麼是副作用( side effect
)
副作用指一個 function
做了跟本身運算返回值沒有關係的事,比如說修改某個全域變數,或是修改傳入引數的值,甚至是執行 console.log
都算是副作用。
Functional Programming
強調沒有副作用,也就是 function
要保持純粹,只做運算並返回一個值,沒有其他額外的行為。
這裏列舉幾個常見的副作用:
- 更改檔案系統
- 傳送一個 http 請求
- 可變資料(random)
- 列印/log
- 獲取使用者輸入
- DOM 查詢
概括來講,只要是跟函式外部環境發生的互動就都是副作用——這一點可能會讓你懷疑無副作用程式設計的可行性。函數語言程式設計的哲學就是假定副作用是造成不正當行為的主要原因, 這並不是說,要禁止使用一切副作用,而是說,要讓它們在可控的範圍內發生。
函數語言程式設計優勢
Pure function
觀察者模式(Observer Pattern)
觀察者模式,即釋出-訂閱模式,它定義了一個一對多的依賴關係,讓一個或多個觀察者物件監聽一個主題物件。這樣一來,當被觀察者狀態發生改變時,需要通知相應的觀察者,使這些觀察者物件能夠自動更新。
關鍵要素
主題
主題是觀察者觀察的物件,一個主題必須具備下面三個特徵。
- 持有監聽的觀察者的引用
- 支援增加和刪除觀察者
- 主題狀態改變,主動通知觀察者
觀察者
當主題發生變化,收到通知後進行具體的處理
這裏舉一個例子來說明,牛奶送奶站就是主題,訂奶客戶為監聽者,客戶從送奶站訂閱牛奶後,會每天收到牛奶。如果客戶不想訂閱了,可以取消,以後就不會收到牛奶。
根據上面的說明,我們可以簡單實現一個被觀察者:
class Subject { constructor() { this.observerCollection = []; } registerObserver(observer){ if(typeof observer === 'function') { this.observerCollection.push(observer) } else { throw new Error('observer must be function') } } unRegisterObserver(observer){ this.observerCollection.splice(this.observer.findIndex(observer), 1) } notifyObservers(message){ this.observerCollection.forEach(observer => { observer.notify(message); }) } }
鬆耦合
- 觀察者增加或刪除無需修改主題的程式碼,只需呼叫主題對應的增加或者刪除的方法即可。
- 主題只負責通知觀察者,但無需瞭解觀察者如何處理通知。舉個例子,送奶站只負責送遞牛奶,不關心客戶是喝掉還是洗臉。
- 觀察者只需等待主題通知,無需觀察主題相關的細節。還是那個例子,客戶只需關心送奶站送到牛奶,不關心牛奶由哪個快遞人員,使用何種交通工具送達。
迭代器模式(Iterator Pattern)
迭代器模式(Iterator)提供了一種方法順序訪問一個集合物件中各個元素,而又不暴露該物件的內部表示,迭代器模式可以把迭代的過程從業務邏輯中分離出來,在使用迭代器模式之後,即使不關心物件的內部構造,也可以按順序訪問其中的每個元素。
Iterator
的遍歷過程是這樣的:
-
建立一個指標物件,指向當前數據結構的起始位置。也就是說,遍歷器物件本質上,就是一個指標物件。
-
第一次呼叫指標物件的
next
方法,可以將指標指向數據結構的第一個成員。 -
第二次呼叫指標物件的
next
方法,指標就指向數據結構的第二個成員。 -
不斷呼叫指標物件的
next
方法,直到它指向數據結構的結束位置。
ES6是這樣建立迭代器的:
var arr = [1, 2, 3]; var iterator = arr[Symbol.iterator](); iterator.next(); // { value: 1, done: false } iterator.next(); // { value: 2, done: false } iterator.next(); // { value: 3, done: false } iterator.next(); // { value: undefined, done: true }
下一篇開始介紹Observable。