RxJs map operator 工作原理分析
使用一個例子來研究 map 操作符的工作原理。
推薦閱讀本文之前,先瀏覽這篇文章RxJs fromEvent 工作原理分析以了解相關知識。
源代碼:
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 1mapTo(1),// keep a running totalscan((acc, curr) => acc + curr, 0),startWith(0)); }打開頁面,點擊 Test 按鈕,能在 Chrome 控制臺里看到每次點擊發生時的 timestamp 時間戳:
下面介紹 map 操作符是如何起作用的。
先縷一縷順序:
首先執行fromEvent,返回一個 Observable 對象。
執行 map 操作符,其結果作為輸入,傳入 pipe
2.執行 pipe:
我們可以把 pipe 形象地想象成管道,通過 fromEvent 返回的 Observable 對象,流過一根根管道,最后觸發其訂閱者,執行訂閱者的邏輯。那么 RxJs 提供的各種 operator,就是安裝在管道里的處理器。
map 操作的輸入是我們定義的映射函數,在 RxJs 上下文里,稱為 project:
map 返回一個新的函數,名為 mapOperation. 新函數體里,基于傳入的 project,創建一個新的 MapOperator. 這個 MapOperator,作為新函數輸入參數 source 的 lift 方法調用的輸入參數。到現在為止,我們尚且不知道 source 參數的類型。
接下來執行 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 何種類型的數據。
- 什么樣的方式?addEventListener,每次 eventTarget 定義的 HTMLElement 發生 click 事件時,emit 數據
- emit 的數據格式為 MouseEvent.
至此 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的原創文章,盡在:“汪子熙”:
總結
以上是生活随笔為你收集整理的RxJs map operator 工作原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lerp 实现匀速运动「建议收藏」
- 下一篇: 什么是 ABAP Field Symbo