响应式编程就是一种异步数据流和变化传播的编程规范,可以简单的理解为是观察者模式与Rxj的开发模式,在流的操作中通过一些特定的操作符,从旧的流转换为新的流,然后监听变化

一、rxjs的基本使用及基本术语

  • 安装使用

    • 1、传统方式<script src="http://cdn.bootcss.com/rxjs/5.4.0/Rx.js"></script>
    • 2、npm安装包的使用

      import {Observable} from 'rxjs/Observable' // 1. 按需打包,减轻bundle.js大小
      import 'rxjs/add/observable/merge'; // 2. 按需导入函数,如merge
  • 一些专业术语

    • Observable( 可被观察的 ) : 是一个包含来自未来、可以被使用的值( value )或事件( event )的集合
    • Observe( 观察者 ):是一个知道如何监听、处理来自Obervable的值的函数集合
    • Subscription( 订阅 ):代表着Observable的执行动作,我们可以使用它来停止Obervable继续执行
    • Operators( 操作 ):一系列可以操作集合的pure function,像是过滤( filter )、转换( map )等等
    • Subject( ):相当于一个事件发射器,是唯一能够向多个Observer广播值( value )的唯一手段
    • Schedulers( 调度 ):是一个中央调度员,帮助我们控制并发,协调计算( setTimeout、requestAnimationFrame等 )
  • Observable(被观察者)四大核心:创建 、订阅 、 执行 、销毁

    • 前面说的RXJS可以简单理解为javascript里面的观察者与被观察者模式模式
    • 那么RXJS一样的具有创建一个被观察对象,订阅这个观察对象,执行与取消订阅的功能
    • subscribe为观察者订阅一个被观察者
    • unsubscribe取消订阅
  • Subject是一个特殊的Observable可以当一个value成为观察者

//下面是angular中的一个输入框的
public searchText:Subject<string> = new Subject<string>();

二、常见的转换为Rx流的方式

  • 1、fromEvent(dom元素,事件名称)将网页中的DOM事件转换为流式编程

    <input type="button" value="按钮" class="btn" />
    <input type="button" value="按钮" class="btn" />
    <input type="button" value="按钮" class="btn" />
    <input type="button" value="按钮" class="btn" />
    <input type="button" value="按钮" class="btn" />
    var btn = document.querySelectorAll(".btn");
    Rx.Observable.fromEvent(btn, 'click')
       .subscribe(count => console.log('你点击了我'));
  • 2、from(数组)将数组转换为流式编程

    Rx.Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9])
        .filter(result => result % 2 == 0)
        .subscribe(rs => console.log(rs));
  • 3、of(元素1,元素2,元素2)将一组数据转换为流式编程

    Rx.Observable.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
        .map(re => re * 2)
        .subscribe(result => console.log(result));
  • 4、interval表示每隔多少时间发送一个流

    Rx.Observable.interval(1000)
        .map(re => {
            console.log("我是值", re);
            return re;
        })
        .subscribe(x => console.log(x));

三、常见的操作符

  • 1、map将一个旧的流转换为一个新的流

    Rx.Observable.of(1,2,3,4,5,6,7,8,9)
        .map(e => {
            console.log(e);
            return e;
        })
        .subscribe(re => console.info(re));

    处理事件操作的时候

    Rx.Observable.fromEvent(btn, "click")
        .map(ev => ev.target.value)
        .subscribe(re => console.log(re));
  • 2、filter对一个旧的流进行过滤成一个新的流

    Rx.Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9])
        .filter(result => result % 2 == 0)
        .subscribe(rs => console.log(rs));
  • 3、first表示流中的第一个元素

    Rx.Observable.from(["张三", "李四", "王五"])
        .first()
        .subscribe(x => console.log(x));
  • 4、last表示流中的最后一个元素

    Rx.Observable.from(["张三", "李四", "王五"])
        .last()
        .subscribe(x => console.log(x));
  • 5、elementAt获取指定下标的值

    Rx.Observable.from(["张三", "李四"])
        .elementAt(0)
        .subscribe(x => console.log(x));
  • 6、scan操作符和数组中reduce方法的类似, 它需要一个传递给回调函数的参数值。
    回调函数的返回值将成为下一次回调函数运行时要传递的下一个参数值

    Rx.Observable.fromEvent(btn, 'click')
        .scan(count => count + 1, 0)
        .subscribe(count => console.log(`Clicked ${count} times`));
  • 7、throttleTime间隔多少时间再执行

    • 传统的javascript的写法

      var count = 0;
      var rate = 1000;
      var lastClick = Date.now() - rate;
      var button = document.querySelector('button');
      button.addEventListener('click', () => {
          if (Date.now() - lastClick >= rate) {
              console.log(`Clicked ${++count} times`);
              lastClick = Date.now();
          }
      });
    • 使用Rxjs的写法

      Rx.Observable.fromEvent(btn, "click")
              .throttleTime(1000)
              .scan(count => count + 1, 0)
              .subscribe(count => console.log(`Clicked ${count} times`));
  • 8、debounceTime在一定时间内只取最新数据进行发射,其他数据取消发射(常用输入框)

    <input type="text" class="input" />
    <input type="button" value="按钮" class="btn1" />
    var input = document.querySelector(".input");
    Rx.Observable.fromEvent(input, "keyup")
        .debounceTime(500)
        .map(ev => ev.target.value)
        .subscribe(re => console.log(re));
  • 9、delay延迟和debounceTime类似的功能

    Rx.Observable.fromEvent(btn, "click")
        .delay(1000)
        .scan(count => count + 1, 0)
        .subscribe(count => console.log(`Clicked ${count} times`));
  • 10、pluck是从一组数据里面提取想要的(将上面的代码改版下)

    Rx.Observable.fromEvent(input, "keyup")
        .pluck("target", "value")
        .subscribe(re => console.log(re));
  • 11、debounce防抖动,只有当另一个Observable发射值时,才取源Obervable的最新数据进行发射,其他数据取消发射。

    Rx.Observable.interval( 1000 )
        .debounce(( ) => Rx.Observable.fromEvent(document, 'click'))
        .subscribe(x => console.log( x ));
  • 12、distinct去除重复的(类似数组去重复)

    Rx.Observable.from([1, 2, 3, 2, 1, 4, 5, 3, "2", "3"])
        .distinct()
        .subscribe(result => console.log(result));
  • 13、distinctUntilChanged去除连续重复的

    Rx.Observable.from(['A', 'B', 'B', 'A', 'B', 'A', 'A'])
        .distinctUntilChanged()
        .subscribe(result => console.log(result));
  • 14、distinctKeyUntilChanged去除连续中具有相同的key的(用于数组对象中)

    let items = [
        {age: 4,name: 'Foo'}, 
        {age: 7,name: 'Bar'}, 
        {age: 5,name: 'Foo'}, 
        {age: 6,name: 'Foo'}
    ];
    Rx.Observable.of(...items)
        .distinctUntilKeyChanged('name') //去除具有连续相同nam的值
        .subscribe(x => console.log(x))
  • 15、sampleTime每隔多少时间发送一个数据类似setInterval()

    Rx.Observable.interval(1000) //之前介绍过interval()是隔多少时间发射一个Observable
        .sampleTime(2000)        //sampleTime()表示每隔多少时间发射一个流出来
        .subscribe(x => console.log(x));
  • 16、获取事件信息

    Rx.Observable.fromEvent(document, "click")
        .map(ev => {
            console.log(ev);
            return ev.clientX;
        })
        .subscribe(re => console.log(re));
  • 17、combineLatest合并两个流,前提是必须两个都有流,不然是不会发射的

    <input type="text" id="weight" />kg <br/>
    <input type="text" id="height" />cm <br/>
    <div>
        使用combineLatest计算结果:<span id="result"></span>
    </div>
    let weight = document.getElementById("weight");
    let height = document.getElementById("height");
    let result = document.getElementById("result");
    let weight$ = Rx.Observable.fromEvent(weight, "keyup")
                    .pluck("target", "value");
    let height$ = Rx.Observable.fromEvent(height, "keyup")
                    .pluck("target", "value");
    
    let result$ = Rx.Observable.combineLatest(weight$, height$, (w, h) => w / (h * h / 100) / 100);
    
    result$.subscribe(re => result.innerHTML = re);
  • 18、concat链式拼接两个Observable的结果

    Rx.Observable.of( 1, 2, 3 )
        .concat( 'a', 'b', 'c' )
        .subscribe( x => console.log( x ));
  • 19、zip组合多个Observable,并生成一个新的Observable,其值能够通过每个Observable的值,和自定义函数进行定义。

    let age$ = Rx.Observable.of<number>(27, 25, 29);
    let name$ = Rx.Observable.of<string>('Foo', 'Bar', 'Beer');
    let isDev$ = Rx.Observable.of<boolean>(true, true, false);
    
    Rx.Observable
        .zip(age$, name$, isDev$,
             (age: number, name: string, isDev: boolean) => ({ age, name, isDev }))
        .subscribe(x => console.log(x));
    
    // 输出 
    // {age: 27. name: 'Foo', isDev: true }
    // {age: 25. name: 'Bar', isDev: true }
    // {age: 29. name: 'Bear', isDev: false}
  • 20、关于更多的操作符的使用请参考

四、关于subscribe的补充说明

subscribe可以定义三个方法,第一个是输出结果,第二个是错误的,第三个最后都要输出的

五、将普通函数改成Observable的方式

  • 普通函数

    function foo() {
        console.log("hello");
        return 42;
    }
    var x = foo.call();
    console.log(x);
  • Observable的方式

    • 1、next可以多个
    • 2、next类似传统函数的return类似ES6中的Generatoryield
    • 3、使用Observable可以传递多个值
    var observable = Rx.Observable.create(function(observer) {
        console.log("hello");
        observer.next(142);
        observer.next(42);
    });
    observable.subscribe(re => console.log(re));
    observable.unsubscribe(); //取消订阅