开始之前
在之前读 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 关键字.
多个嘴
作为一个英语不是很好的码农, 翻译本文还是有点磕磕碰碰, 但是总算还是勉勉强强搞定了, 希望能够帮助大家多了解一下这个特性, 如有错误麻烦诸位指出一下, 最后国际惯例, 感谢各位的阅读~
来源: https://juejin.im/post/5bee325de51d456d2066461d