使用一個例子來研究 map 操做符的工做原理。javascript
推薦閱讀本文以前,先瀏覽這篇文章RxJs fromEvent 工做原理分析以瞭解相關知識。html
源代碼:java
import { Component, OnInit, Inject } from '@angular/core'; import { fromEvent, combineLatest } from 'rxjs'; import { mapTo, startWith, scan, tap, map } from 'rxjs/operators'; import { DOCUMENT } from '@angular/common'; @Component({ selector: 'app-combine-latest', templateUrl: './combine-latest.component.html' }) export class CombineLatestComponent implements OnInit { readonly document: Document; constructor( // https://github.com/angular/angular/issues/20351 @Inject(DOCUMENT) document: any) { this.document = document as Document; } redTotal:HTMLElement; blackTotal: HTMLElement; total:HTMLElement; test:HTMLElement; ngOnInit(): void { this.redTotal = this.document.getElementById('red-total'); this.blackTotal = this.document.getElementById('black-total'); this.total = this.document.getElementById('total'); this.test = this.document.getElementById('test'); combineLatest(this.addOneClick$('red'), this.addOneClick$('black')).subscribe(([red, black]: any) => { this.redTotal.innerHTML = red; this.blackTotal.innerHTML = black; this.total.innerHTML = red + black; }); fromEvent(this.test, 'click').pipe(map( event => event.timeStamp)).subscribe((event) => console.log(event)); } addOneClick$ = id => fromEvent(this.document.getElementById(id), 'click').pipe( // map every click to 1 mapTo(1), // keep a running total scan((acc, curr) => acc + curr, 0), startWith(0) ); }
打開頁面,點擊 Test 按鈕,能在 Chrome 控制檯裏看到每次點擊發生時的 timestamp 時間戳:
git
下面介紹 map 操做符是如何起做用的。github
先縷一縷順序:typescript
2.執行 pipe:
app
咱們能夠把 pipe 形象地想象成管道,經過 fromEvent 返回的 Observable 對象,流過一根根管道,最後觸發其訂閱者,執行訂閱者的邏輯。那麼 RxJs 提供的各類 operator,就是安裝在管道里的處理器。函數
map 操做的輸入是咱們定義的映射函數,在 RxJs 上下文裏,稱爲 project:this
map 返回一個新的函數,名爲 mapOperation. 新函數體裏,基於傳入的 project,建立一個新的 MapOperator. 這個 MapOperator,做爲新函數輸入參數 source 的 lift 方法調用的輸入參數。到如今爲止,咱們尚且不知道 source 參數的類型。spa
接下來執行 Observable 的 pipe 方法。
operations 參數是 map operator 返回的新函數,mapOperation:
pipeFromArray 的實現,若是 pipe 輸入只有一個 operator,這種狀況比較簡單,進入第 9 行的 IF 分支,直接將 map 返回的 mapOperation 函數做爲 pipeFromArray 調用的返回結果。
注意到 Observable.js 實現裏,在 pipeFromArray(operations) 返回以後,緊跟了另外一個括號,說明這是另外一個函數調用,輸入參數爲 this,即 Observable 對象自己。
如今進入到 map 操做返回的 新函數 mapOperation 的函數體內部了:
由於此時 button 還沒有點擊,所以 Observable 對象並無 emit 值,只是完成相關的 setup 工做。
這行語句:
return source.lift(new MapOperator(project, thisArg));
只是返回一個新的 Observable 對象,其 source 屬性指向調用 lift 操做的原始 Observable 對象,而 operator 屬性指向 new MapOperator 返回的結果,後者是 project 的 wrapper.
如此一來,調用 subscribe 方法註冊應用程序監聽函數的 Observable 對象,不再是 fromEvent 返回的原始 Observable 對象,而是前者調用了 pipe,接收了 map 指定的 project 以後,由 source.lift( new MapOperator) 返回的新 Observable 對象。
這個新的 Observable 對象,調用 subscribe 方法,執行邏輯和這篇文章RxJs fromEvent 工做原理分析介紹的相比有所差別,複雜度稍稍增長了。
把 Observable 對象 operator 屬性值提取出來:
接下來的 21行代碼執行,和以前沒有 operator 時相比,沒有差別,略過。
前一篇文章進入 ELSE 分支,而本文由於 operator 的存在,進入 22 行的 IF 分支:
首先執行 operator.call 方法:
MapSubscriber 也是 Subscriber 的子類之一,和其父類相比,多了 project 屬性。
再次執行 subscribe:
由於此次傳入的 Observable 是最原始的即 fromEvent 返回的 Observable,所以不存在 operator,因此進入 ELSE 分支執行:
重點分析 this 和 sink:
this 是 fromEvent 返回的原始 Observable,而 sink 是包含了 map operator 以及應用程序定義的訂閱邏輯的 Subscriber:
_trySubscribe 調用 _subscribe:
最終仍舊進入了 fromEvent 的核心邏輯:
這段代碼,定義了 fromEvent,以什麼樣的方式,emit 何種類型的數據。
至此 Observable 相關的 setup 執行完畢。
點擊按鈕,觸發以前經過 addListener 註冊的 handler 函數。fromEvent.js 此處 subscriber 不是原始的 subscriber,而是 MapSubscriber,其 destination 屬性的 _next, 指向了應用程序指定的訂閱處理邏輯。Emit 的數據是 MouseEvent.
MapSubscriber 的特點:在將原始值 MouseEvent 交給應用程序以前,先要執行 project 對其進行處理:
這個 project 的邏輯是,將 MouseEvent 對象映射成 timestamp 時間戳:
將 project 處理結果返回給destination 繼續進行傳遞:
this._next 指向的是應用程序定義的 console.log(event), 在這裏獲得執行:
更多Jerry的原創文章,盡在:"汪子熙":