開始之前
在之前讀redux
原始碼時,遇到了關於Symbol.observable
的使用,發現從沒有看到過這個特性,在國內的技術論壇上逛了許久發現提及此的文章甚少,恰巧今天在摸魚時發現了一篇聊ECMAScript
中新提案observables
的文章,故翻譯出來加深印象~
ECMAScript 中的 Observables 提案
ps: 在下文中將以 ES 代替 ECMAScript
本文介紹的是當前還在ES提案階段中的observables特性,在下文中通過本文,我們將帶你瞭解該提案,該提案的api,以及一些使用案例
在寫下這篇文章的時候,javascript
的Observables(觀察)
正在通過RXJs、Bacon.js
等各種類庫逐漸普及。Jafar Husain,這位長久以來主張函數語言程式設計的Netfix
的技術leader(同時也是TC39的委員)也提出了將observables
整合到我們js core
中的議案,並且已經通過了stage1(徵求意見階段)
且已經確定該提案即將進入stage2(草案階段)
。
Observable
和 observer
的api
在當前提案中,Observable
是一個內建的被用來處理事件流的類,Obsservalbe
的建構函式可以接受一個定義事件流的回撥函式。在接下來的例子中,我們的observable
將只返回值為1或者2的事件流。observer.next
方法是用來在observalbe
流中新增事件的:
new Observable(observer => {
observer.next(1);
observer.next(2);
})
複製程式碼
我們也可以使用observer.error
來記錄在流處理時遇到的錯誤:
new Observable(observer => {
observer.error(new Error(`Failed to stream events`))
})
複製程式碼
我們還可以使用observer.complete
來在流處理完結的時候發出訊號:
new Observable(observer => {
observer.next(1)
observer.next(2)
observer.complete()
})
複製程式碼
傳遞給我們Observable
建構函式的這個回撥函式會返回一個清理我們Observable例項的方法,它可以執行清理事件監聽,定時任務等等類似的清理任務。舉個例子,當然這個例子就要比上面這些有趣的多了,他追蹤了使用者在移動滑鼠時,游標相對於頁面的位置,並同時產生了描述當前游標座標的事件流:
function mouseTracking () {
return new Observable(observer => {
const handler = ({ pageX, pageY }) => {
observer.next({ x: pageX, y: pageY })
}
document.body.addEventListener(`mousemove`, handler)
return () => {
document.body.removeEventListener(`mousemove`, handler)
}
})
}
複製程式碼
為了訂閱一個Observable
的事件流,我們會使用Observable
例項上的subscribe
方法,這樣做會呼叫我們之前例項化Observable
時傳入的回撥函式,繫結事件的監聽,並且啟動整個事件流。這樣做之後我們就能在移動滑鼠的時候在事件流裡捕獲到它啦:
mouseTracking().subscribe({
next({ x, y }) { console.log(`New position: ${ x }, ${ y }`) },
error(err) { console.log(`Error: ${ err }`) },
complete() { console.log(`Done!`) }
})
複製程式碼
訂閱物件上的 unsubscribe
每次訂閱我們都會生成一個訂閱物件Subscription
,這個訂閱物件上會有一個unsubscribe
方法讓我們用來取消訂閱,執行清理方法(我猜大家應該都還記著之前提到的清理函式吧~),當我們不再需要關注觀察流裡的事件的時候,just unsubscribe it,讓我們將其解放吧。
const subscription = mouseTracking().subscribe({
next({ x, y }) { console.log(`New position: ${ x }, ${ y }`) },
error(err) { console.log(`Error: ${ err }`) },
complete() { console.log(`Done!`) }
})
subscription.unsubscribe()
複製程式碼
Observable.of
Observable.of(...items)
是一個簡單有效的能幫助我們從提供的items
中建立Observable
的方法,在使用了Observable.of
方法之後,items
生成的Observable
例項會在呼叫subscribe
的同時生成事件流,返回items
中的value
:
Observable.of(1, 2, 3, 4).subscribe({
next(item) { console.log(item) }
})
// <- 1
// <- 2
// <- 3
// <- 4
複製程式碼
我們甚至可以認為,Observable.of
可以理解為跟以下接受一個入參,然後回傳事件流的簡單例子一樣:
Observable.of = (...items) => {
return new Observable(observer => {
items.forEach(item => {
observer.next(item)
})
observer.complete()
})
}
複製程式碼
Observable.from
Observable.from
靜態方法接受一個型別為物件的入參,如果這個物件中有鍵值為Symbol.observable
的方法,那麼就會返回這個方法的返回值。
Observable
.from({
[Symbol.observable]() { return Observable.of(1, 2, 3) }
})
.subscribe({
next(item) { console.log(item) }
})
// <- 1
// <- 2
// <- 3
複製程式碼
當然如果這個傳入的物件沒有實現Symbol.observable
,那麼我們就假定其傳入的是一個可迭代的元素,Observable.from
在這個時候的作用就是將迭代元素遍歷並生成一個Observable
例項,依次將被遍歷的結果放入事件流中:
Observable
.from([1, 2, 3])
.subscribe({
next(item) { console.log(item) }
})
// <- 1
// <- 2
// <- 3
複製程式碼
在這種情況下,我們的Observable.from
實現的功能和Observable.of
是類似的,據此我們甚至可以這樣去理解Observable.from
的實現:
Observable.from = value => {
if (typeof value[Symbol.observable] === `function`) {
return value[Symbol.observable]()
}
return Observable.of(Array.from(value))
}
複製程式碼
結語
雖然現在這個提案還在襁褓之中,但是我相信遲早有一天,其會成為javascript
的函數語言程式設計的基石。到那天,我相信它還會具有類似filter
和map
的能力去處理我們的事件流,讓我們在龐大的事件流能夠僅僅注重我們需要關注的部分就夠了
與此同時,我們的程式碼格式和開發模式也能在其幫助下變得更加的自然和規範,當然你也可以提前使用我們在github上的的polyfill
去提前體驗它,但是請切記在瀏覽器環境下刪除掉你的export
關鍵字。
多個嘴
作為一個英語不是很好的碼農,翻譯本文還是有點磕磕碰碰,但是總算還是勉勉強強搞定了,希望能夠幫助大家多瞭解一下這個特性,如有錯誤麻煩諸位指出一下,最後國際慣例,感謝各位的閱讀~