Rxjs 01-認識Rxjs

Escape Plan發表於2018-07-01

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, 宣告一個變數。
  • 語句則是表現某個行為,例如賦值給一個變數, for迴圈,if判斷

有時候表示式也可能同時是合法的語句,這裡只講基本的判斷方法。如果想更深入瞭解其中的差異,可以看這篇文章Expressions versus statements in JavaScript

純函式(Pure Function)

純函式是這樣一種函式,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用(side effect)

舉個簡單的例子,slicesplice

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等特性,執行結果不依賴外部狀態,且不會對外部環境有任何操作,這使得單元測試和除錯都更容易
  • 易於並行處理: 由於不共享外部狀態,不會造成資源爭用(Race condition),也就不需要用鎖來保護可變狀態,也就不會出現死鎖,這樣可以更好地併發起來。

觀察者模式(Observer Pattern)

觀察者模式,即釋出-訂閱模式,它定義了一個一對多的依賴關係,讓一個或多個觀察者物件監聽一個主題物件。這樣一來,當被觀察者狀態發生改變時,需要通知相應的觀察者,使這些觀察者物件能夠自動更新。

關鍵要素

主題

主題是觀察者觀察的物件,一個主題必須具備下面三個特徵。

  • 持有監聽的觀察者的引用
  • 支援增加和刪除觀察者
  • 主題狀態改變,主動通知觀察者

觀察者

當主題發生變化,收到通知後進行具體的處理

Rxjs 01-認識Rxjs

這裡舉一個例子來說明,牛奶送奶站就是主題,訂奶客戶為監聽者,客戶從送奶站訂閱牛奶後,會每天收到牛奶。如果客戶不想訂閱了,可以取消,以後就不會收到牛奶。

根據上面的說明,我們可以簡單實現一個被觀察者:

class Subject {
  constructor() {
    this.observerCollection = [];
  }

  registerObserver(observer){
    this.observerCollection.push(observer)
  }
  unRegisterObserver(observer){
    this.observerCollection.splice(this.observer.findIndex(observer), 1)
  }

  notifyObservers(message){
    this.observerCollection.forEach(observer => {
      observer.notify(message);
    })
  }
}
複製程式碼

鬆耦合

  • 觀察者增加或刪除無需修改主題的程式碼,只需呼叫主題對應的增加或者刪除的方法即可。
  • 主題只負責通知觀察者,但無需瞭解觀察者如何處理通知。舉個例子,送奶站只負責送遞牛奶,不關心客戶是喝掉還是洗臉。
  • 觀察者只需等待主題通知,無需觀察主題相關的細節。還是那個例子,客戶只需關心送奶站送到牛奶,不關心牛奶由哪個快遞人員,使用何種交通工具送達。

迭代器模式(Iterator Pattern)

迭代器模式(Iterator)提供了一種方法順序訪問一個集合物件中各個元素,而又不暴露該物件的內部表示,迭代器模式可以把迭代的過程從業務邏輯中分離出來,在使用迭代器模式之後,即使不關心物件的內部構造,也可以按順序訪問其中的每個元素。

Iterator 的遍歷過程是這樣的:

  1. 建立一個指標物件,指向當前資料結構的起始位置。也就是說,遍歷器物件本質上,就是一個指標物件。

  2. 第一次呼叫指標物件的next方法,可以將指標指向資料結構的第一個成員。

  3. 第二次呼叫指標物件的next方法,指標就指向資料結構的第二個成員。

  4. 不斷呼叫指標物件的next方法,直到它指向資料結構的結束位置。

可參考ES6系列--7. 可迭代協議和迭代器協議中關於迭代器的介紹。

JavaScript 中像 Array、Set、Map 等都屬於內建的可迭代型別, 可以通過 iterator方法來獲取一個迭代物件,呼叫迭代物件的 next 方法將獲取一個元素物件,如下示例:

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 }

複製程式碼

遍歷迭代器可以使用下面的方法。

while(true) {
  let result;
  try {
    result = iterator.next();
  } catch (error) {
    handleError(error); // 錯誤處理
  }
  if (result.done) {
    handleCompleted(); // 已完成之後的處理
  }
  doSomething(result.value);
}
複製程式碼

上面的程式碼主要對應三種情況:

  • 獲取下一個值(next):呼叫next方法可以將元素一個個的返回,這樣就支援了返回多次值。
  • 已完成(complete):當沒有更多值時,next返回元素中的donetrue
  • 錯誤處理(error):當 next 方法執行時報錯,則會丟擲 error 事件,所以可以用 try catch 包裹 next 方法處理可能出現的錯誤。

下一篇開始介紹Observable 和 observer。

相關文章