rxjs原理解析

一. rxjs介绍

rxjs是一个函数式编程的工具库,即推崇纯函数的开发理念。纯函数的概念这里不过多展开,读者可以自行查阅。简单理解就是不依赖this,稳定的输入输出。

代码示例如下

javascript 复制代码
import { Observable, map } from 'rxjs'

const observable = new Observable(subscriber => {
  subscriber.next(1)
}).pipe(map(x => x * 2))

observable.subscribe(x => {
  console.log(x)
})

二. rxjs原理

rxjs核心原理是实现了一套类似EventEmitter的事件订阅/派发更新的机制,且结合懒执行/惰性执行的机制优化性能。在下文实现fromEvent这个API时会详细说明。

rxjs有两个核心对象,一个是Observable,用于收集事件监听方法,另一个是Subscriber,用于收集事件派发方法。

2.1 定义Observable对象原型

Observable对象核心属性_subscribe记录事件监听方法,核心方法subscribe创建Subscriber对象实例,收集事件派发方法,执行事件监听处理逻辑。

javascript 复制代码
function Observable(subscribe) {
  if (subscribe) this._subscribe = subscribe
}

Observable.prototype.subscribe = function (observerOrNext, error, complete) {
  const subscriber = new Subscriber(observerOrNext, error, complete)
  const operator = this.operator
  const source = this.source
  subscriber.add(
    operator
      ? operator.call(subscriber, source)
      : this._trySubscribe(subscriber),
  )
}

Observable.prototype._trySubscribe = function (subscriber) {
  try {
    return this._subscribe(subscriber)
  } catch (err) {
    subscriber.error(err)
  }
}

2.2 定义Subscriber对象原型

Subscriber对象的partialObserver属性用于收集派发更新方法,是一个对象,核心属性有nexterrorcomplete,其中next属性是必传的。

EventEmitter类似,提供了类似off方法的逻辑解除事件监听逻辑,主要通过addunsubscribe方法,add方法用于收集解除事件监听方法,unsubscribe方法用于执行解除事件监听方法。

javascript 复制代码
function Subscriber(observerOrNext, error, complete) {
  // 是否终止执行next、error、complete事件
  this.isStopped = false
  this.closed = false
  this._finalizers = []

  if (isFunction(observerOrNext)) {
    this.partialObserver = {
      next: observerOrNext,
      error,
      complete,
    }
  } else {
    this.partialObserver = observerOrNext
  }
}

Subscriber.prototype.next = function (value) {
  if (this.isStopped) return
  if (this.partialObserver.next) this.partialObserver.next(value)
}

Subscriber.prototype.error = function (err) {
  if (this.isStopped) return
  if (this.partialObserver.error) this.partialObserver.error(err)
}

Subscriber.prototype.complete = function () {
  if (this.isStopped) return
  this.isStopped = true
  if (this.partialObserver.complete) this.partialObserver.complete()
}

Subscriber.prototype.add = function (finalizer) {
  if (finalizer) {
    if (this.closed) {
      finalizer()
    } else {
      this._finalizers.push(finalizer)
    }
  }
}

Subscriber.prototype.unsubscribe = function () {
  if (this.closed) return
  this.closed = true
  this.isStopped = true
  this._finalizers.forEach(cb => cb())
}

2.3 实现fromEvent

fromEvent方法作用是添加DOM事件监听。代码逻辑比较简单,核心是学习懒执行的思想。

javascript 复制代码
function isEventTarget(target) {
  return (
    isFunction(target.addEventListener) &&
    isFunction(target.removeEventListener)
  )
}

function isNodeStyleEventEmitter(target) {
  return isFunction(target.addListener) && isFunction(target.removeListener)
}

const eventTargetMethods = ['addEventListener', 'removeEventListener']
const nodeEventEmitterMethods = ['addListener', 'removeListener']

export function fromEvent(target, eventName, options) {
  const listeners = (
    isEventTarget(target)
      ? eventTargetMethods
      : isNodeStyleEventEmitter(target)
        ? nodeEventEmitterMethods
        : []
  ).map(
    method =>
      function (handler) {
        target[method](eventName, handler, options)
      },
  )
  if (!listeners.length) throw new TypeError('Invalid event target')
  const addEventListener = listeners[0]
  const removeEventListener = listeners[1]
  
  return new Observable(subscriber => {
    const handler = function (...args) {
      return subscriber.next(...args)
    }
    addEventListener(handler)
    return () => removeEventListener(handler)
  })
}

结合下面代码示例,解析整体执行流程:

首先调用fromEvent方法时,会创建Observable对象实例,收集添加DOM事件监听方法,需要注意的是此时还没有执行事件绑定逻辑的,当调用observable.subscribe方法时,才执行DOM事件监听逻辑,同时收集事件回调方法。假如这时触发click事件,会执行handler方法,调用subscriber.next方法执行事件回调。

这里懒执行的思想主要体现在DOM事件监听的执行时机不是一开始就执行,而是当添加订阅事件回调时才执行DOM事件监听逻辑。

javascript 复制代码
import { fromEvent } from 'rxjs'

const observable = fromEvent(document, 'click')

observable.subscribe(evt => {
  console.log(evt)
})

2.4 实现函数式编程

rxjs提供了丰富的API,可以通过pipe方法进行API的组装,举个🌰,类似工厂流水线,流水线中包含多道工序,可以根据业务场景自由增删工序。

例如下面这段代码,数据会先经过filter处理,符合条件的数据会流转map处理,最终流转到业务事件回调。

javascript 复制代码
import { filter, map, Observable } from 'rxjs'

const observable = new Observable(subscriber => {
  subscriber.next(1)
}).pipe(
  filter(x => x),
  map(x => x * 2),
)

observable.subscribe(x => {
  console.log(x)
})

2.4.1 实现pipe方法

pipe方法入参operations是数组类型,元素值是API方法,会循环遍历数组元素,创建其对应的Observable对象实例,observable.source指向上一个Observable对象实例,observable.operator指向API的执行方法。

javascript 复制代码
Observable.prototype.lift = function (operator) {
  const observable = new Observable()
  observable.source = this
  observable.operator = operator
  return observable
}

Observable.prototype.pipe = function (...operations) {
  return (function piped(input) {
    return operations.reduce((prev, operation) => operation(prev), input)
  })(this)
}

2.4.2 实现mapfilter

rxjsAPI很多,这里只实现mapfilter,了解其核心原理即可。每个API都对应一个Observable对象实例,会调用其subscribe方法添加事件派发更新回调,当触发顶层Observable对象的_subscribe方法时,则会依次调用API代码逻辑。

javascript 复制代码
function operate(init) {
  return function (source) {
    return source.lift(function (liftedSource) {
      try {
        return init(liftedSource, this)
      } catch (err) {
        this.error(err)
      }
    })
  }
}

function filter(cb, thisArg) {
  return operate(function (source, subscriber) {
    source.subscribe(function (value) {
      if (cb.call(thisArg, value)) subscriber.next(value)
    })
  })
}

function map(cb, thisArg) {
  return operate(function (source, subscriber) {
    source.subscribe(function (value) {
      subscriber.next(cb.call(thisArg, value))
    })
  })
}

三. 总结

rxjs是一个优秀的函数式编程框架,如果业务中有使用函数式编程的诉求,那可以考虑使用rxjs。最重要的是学习rxjs的核心理念,即事件订阅/派发更新的设计模式,配合懒执行机制优化性能。另外就是函数式编程的理念。代码仓库

创作不易,如果文章对你有帮助,那点个小小的赞吧,你的支持是我持续更新的动力!

相关推荐
2501_915106322 小时前
移动端网页调试实战,iOS WebKit Debug Proxy 的应用与替代方案
android·前端·ios·小程序·uni-app·iphone·webkit
柯南二号3 小时前
【大前端】React Native 调用 Android、iOS 原生能力封装
android·前端·react native
睡美人的小仙女1274 小时前
在 Vue 前端(Vue2/Vue3 通用)载入 JSON 格式的动图
前端·javascript·vue.js
yuanyxh4 小时前
React Native 初体验
前端·react native·react.js
大宝贱4 小时前
H5小游戏-超级马里奥
javascript·css·html·h5游戏·超级马里奥
程序视点4 小时前
2025最佳图片无损放大工具推荐:realesrgan-gui评测与下载指南
前端·后端
程序视点5 小时前
2023最新HitPaw免注册版下载:一键去除图片视频水印的终极教程
前端
小只笨笨狗~7 小时前
el-dialog宽度根据内容撑开
前端·vue.js·elementui
weixin_490354347 小时前
Vue设计与实现
前端·javascript·vue.js
GISer_Jing8 小时前
React过渡更新:优化渲染性能的秘密
javascript·react.js·ecmascript