Rxjs 01-认识Rxjs

2,603 阅读7分钟

ReactiveX combines the Observer pattern with the Iterator pattern and functional programming with collections to fill the need for an ideal way of managing sequences of events. ReactiveX将观察者模式、迭代器模式和函数编程与集合结合起来,以满足管理事件序列的理想方式的需要。

根据官方定义,RxJS 是基于观察者模式和迭代器模式以函数式编程思维来实现的,那么我们先了解一下这几个概念。

函数式编程(Functional Programming)

什么是函数式编程 ?

Functional Programming 是一种编程范式(programming paradigm),就像Object-oriented Programming(OOP)一样,就是一种写程式的方法论,这些方法论告诉我们如何思考及解决问题。

函数式编程关心数据的映射,命令式编程关心解决问题的步骤.

这里的映射就是数学上函数的概念——一种东西和另一种东西之间的对应关系, 简单说Functional Programming 核心思想就是做运算处理,并用function 来思考问题.

函数式编程基本要素

函式为一等公民(First Class)

所谓一等公民是指跟其它对象具有同等的地位,也就是说函数能够被赋值给变量,也能够被当作参数传入另一个函数,也可当作一个函数的返回值。

// 函数能够被赋值给变量
var hello = function() {}

// 函数当作参数传入另一个函数
fetch('www.google.com')
.then(function(response) {}) // 匿名 function 被傳入 then()

// 当作一个函数的返回值
var a = function(a) {
	return function(b) {
	  return a + b;
	}; 
}

Expression, no Statement

Functional Programming都是表达式(Expression)不会是语句(Statement)。 基本区分表达式与语句:

  • 表达式是一个运算过程,一定会有返回值,例如执行一个function, 声明一个变量。
  • 语句则是表现某个行为,例如赋值给一个变量, for循环,if判断

有时候表达式也可能同时是合法的语句,这里只讲基本的判断方法。如果想更深入了解其中的差异,可以看这篇文章Expressions versus statements in JavaScript

纯函数(Pure Function)

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用(side effect)

举个简单的例子,slicesplice

var arr = [1, 2, 3, 4, 5];

arr.slice(0, 3); // [1, 2, 3]

arr.slice(0, 3); // [1, 2, 3]

arr.slice(0, 3); // [1, 2, 3]

这里可以看到slice不管执行几次,返回值都是相同的,并且除了返回一个值之外并没有做任何事,所以slice就是一个pure function

var arr = [1, 2, 3, 4, 5];

arr.splice(0, 3); // [1, 2, 3]

arr.splice(0, 3); // [4, 5]

arr.slice(0, 3); // []

这里我们换成用splice,因为splice每执行一次就会影响arr的值,导致每次结果都不同,这就很明显不是一个pure function

什么是副作用(side effect)

副作用指一个function做了跟本身运算返回值没有关系的事,比如说修改某个全域变数,或是修改传入参数的值,甚至是执行console.log都算是副作用。

Functional Programming 强调没有副作用,也就是function 要保持纯粹,只做运算并返回一个值,没有其他额外的行为。

这里列举几个常见的副作用:

  • 更改文件系统
  • 发送一个 http 请求
  • 可变数据(random)
  • 打印/log
  • 获取用户输入
  • DOM 查询

概括来讲,只要是跟函数外部环境发生的交互就都是副作用——这一点可能会让你怀疑无副作用编程的可行性。函数式编程的哲学就是假定副作用是造成不正当行为的主要原因, 这并不是说,要禁止使用一切副作用,而是说,要让它们在可控的范围内发生。

函数式编程优势

  • 可读性高: 通过一系列的函数封装过程,代码变得非常的简洁且可读性极高
  • 可维护性高: 因为Pure function等特性,执行结果不依赖外部状态,且不会对外部环境有任何操作,这使得单元测试和调试都更容易
  • 易于并行处理: 由于不共享外部状态,不会造成资源争用(Race condition),也就不需要用锁来保护可变状态,也就不会出现死锁,这样可以更好地并发起来。

观察者模式(Observer Pattern)

观察者模式,即发布-订阅模式,它定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。

关键要素

主题

主题是观察者观察的对象,一个主题必须具备下面三个特征。

  • 持有监听的观察者的引用
  • 支持增加和删除观察者
  • 主题状态改变,主动通知观察者

观察者

当主题发生变化,收到通知后进行具体的处理

这里举一个例子来说明,牛奶送奶站就是主题,订奶客户为监听者,客户从送奶站订阅牛奶后,会每天收到牛奶。如果客户不想订阅了,可以取消,以后就不会收到牛奶。

根据上面的说明,我们可以简单实现一个被观察者:

class Subject {
  constructor() {
    this.observerCollection = [];
  }

  registerObserver(observer){
    this.observerCollection.push(observer)
  }
  unRegisterObserver(observer){
    this.observerCollection.splice(this.observer.findIndex(observer), 1)
  }

  notifyObservers(message){
    this.observerCollection.forEach(observer => {
      observer.notify(message);
    })
  }
}

松耦合

  • 观察者增加或删除无需修改主题的代码,只需调用主题对应的增加或者删除的方法即可。
  • 主题只负责通知观察者,但无需了解观察者如何处理通知。举个例子,送奶站只负责送递牛奶,不关心客户是喝掉还是洗脸。
  • 观察者只需等待主题通知,无需观察主题相关的细节。还是那个例子,客户只需关心送奶站送到牛奶,不关心牛奶由哪个快递人员,使用何种交通工具送达。

迭代器模式(Iterator Pattern)

迭代器模式(Iterator)提供了一种方法顺序访问一个集合对象中各个元素,而又不暴露该对象的内部表示,迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

Iterator 的遍历过程是这样的:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

可参考ES6系列--7. 可迭代协议和迭代器协议中关于迭代器的介绍。

JavaScript 中像 Array、Set、Map 等都属于内置的可迭代类型, 可以通过 iterator方法来获取一个迭代对象,调用迭代对象的 next 方法将获取一个元素对象,如下示例:

var arr = [1, 2, 3];

var iterator = arr[Symbol.iterator]();

iterator.next();
// { value: 1, done: false }
iterator.next();
// { value: 2, done: false }
iterator.next();
// { value: 3, done: false }
iterator.next();
// { value: undefined, done: true }

遍历迭代器可以使用下面的方法。

while(true) {
  let result;
  try {
    result = iterator.next();
  } catch (error) {
    handleError(error); // 错误处理
  }
  if (result.done) {
    handleCompleted(); // 已完成之后的处理
  }
  doSomething(result.value);
}

上面的代码主要对应三种情况:

  • 获取下一个值(next):调用next方法可以将元素一个个的返回,这样就支持了返回多次值。
  • 已完成(complete):当没有更多值时,next返回元素中的donetrue
  • 错误处理(error):当 next 方法执行时报错,则会抛出 error 事件,所以可以用 try catch 包裹 next 方法处理可能出现的错误。

下一篇开始介绍Observable 和 observer。