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的核心理念,即事件订阅/派发更新的设计模式,配合懒执行机制优化性能。另外就是函数式编程的理念。代码仓库

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

相关推荐
烛阴10 分钟前
JavaScript 函数对象与 NFE:你必须知道的秘密武器!
前端·javascript
px521334412 分钟前
Solder leakage problems and improvement strategies in electronics manufacturing
java·前端·数据库·pcb工艺
eli96014 分钟前
node-ddk,electron 开发组件
前端·javascript·electron·node.js·js
全宝29 分钟前
🔥一个有质感的拟态开关
前端·css·weui
老K(郭云开)34 分钟前
最新版Chrome浏览器加载ActiveX控件技术--allWebPlugin中间件一键部署浏览器扩展
前端·javascript·chrome·中间件·edge
老K(郭云开)35 分钟前
allWebPlugin中间件自动适应Web系统多层iframe嵌套
前端·javascript·chrome·中间件
银之夏雪1 小时前
Vue 3 vs Vue 2:深入解析从性能优化到源码层面的进化
前端·vue.js·性能优化
还是鼠鼠1 小时前
Node.js 的模块作用域和 module 对象详细介绍
前端·javascript·vscode·node.js·web
拉不动的猪1 小时前
刷刷题36(uniapp高级实际项目问题-1)
前端·javascript·面试
-代号95271 小时前
【CSS】一、基础选择器
前端·css