你有一份Rx编程秘籍请签收

1、背景

在学习Rx编程的过程当中,理解Observable这个概念相当重要,常规学习过程当中,一般须要进行屡次“碰壁”才能逐渐“开悟”。这个有点像小时候学骑自行车,必须摔几回才能掌握同样。固然若是有办法能“言传”,则能够少走一些弯路,尽快领悟Rx的精妙。java

2、Observable

Observable从字面翻译来讲叫作“可观察者”,换言之就是某种“数据源”或者“事件源”,这种数据源具备可被观察的能力,这个和你主动去捞数据有本质区别。用一个形象的比喻就是Observable比如是水龙头,你能够去打开水龙头——订阅Observable,而后水——数据就会源源不断流出。这就是响应式编程的核心思想——变主动为被动。不过这个不在本篇文章中详解。编程

(图片来源自网络)缓存

Observable是一种概念,能够经过不一样的方式去具体实现,本文经过高阶函数来实现两个经常使用Observable:fromEvent和Interval。经过讲解对Observable的订阅和取消订阅两个行为来帮助读者真正理解Observable是什么。网络

3、高阶函数

高阶函数的概念来源于函数式编程,简单的定义就是一个函数的入参或者返回值是一个函数的函数。例如:异步

function foo(arg){
    return function(){
        console.log(arg)
    }
}
const bar = foo(“hello world”)
bar()  // hello world
ps:高阶函数能作的事情不少,这里仅仅针对本文须要的情形进行使用。

上面这个foo函数的调用并不会直接打印hello world,而只是把这个hello world给缓存起来。后面咱们根据实际须要调用返回出来的bar函数,而后真正去执行打印hello world的工做。函数式编程

为啥要作这么一步封装呢?实际上这么作的效果就是“延迟”了调用。而一切的精髓就在这个“延迟”两个字里面。咱们其实是对一种行为进行了包装,看上去就像某种一致的东西,比如是快递盒子。函数

(图片来源自网络)学习

里面能够装不一样的东西,但对于物流来讲就是统一的东西。所以,就能够造成对快递盒的统一操做,好比堆叠、运输、存储、甚至是打开盒子这个动做也是一致的。spa

回到前面的例子,调用foo函数,至关于打包了一个快递盒,这个快递盒里面有一个固定的程序,就是当打开这个快递盒(调用bar)时执行一个打印操做。翻译

咱们能够有foo一、foo二、foo3……里面有各类各样的程序,可是这些foos,都有一个共同的操做就是“打开”。(前提是这个foo会返回一个函数,这样才能知足“打开”的操做,即调用返回的函数)。

function foo1(arg){
    return function(){
       console.log(arg+"?")
    }
}
function foo2(arg){
      return function(){
         console.log(arg+"!")
     }
}
const bar1 = foo1(“hello world”)
const bar2 = foo2("yes")
bar1()+bar2() // hello world? yes!

4、快递盒模型

4.1 快递盒模型1:fromEvent

有了上面的基础,下面咱们就来看一下Rx编程中最经常使用的一个Observable—fromEvent(……)。对于Rx编程的初学者,起初很难理解fromEvent(……)和addEventListener(……)有什么区别。

btn.addEventListener("click",callback)
rx.fromEvent(btn,"click").subscribe(callback)

若是直接执行这个代码,确实效果是同样的。那么区别在哪儿呢?最直接的区别是,subscribe函数做用在fromEvent(……)上而不是btn上,而addEventListener是直接做用在btn上的。subscribe函数是某种“打开”操做,而fromEvent(……)则是某种快递盒。

fromEvent其实是对addEventListener的“延迟”调用

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
    }
}
const ob = fromEvent(btn,"click")
ob(console.log)// 至关于 subscribe

哦!fromEvent本质上是高阶函数

至于如何实现subscribe来完成“打开”操做,不在本文讨论范围,在Rx编程中,这个subscribe的动做叫作“订阅”。“订阅”就是全部Observable的统一具有的操做。再次强调:本文中对Observable的“调用”在逻辑上至关于subscribe。

下面再举一个例子,基本可让读者举二反N了。

4.2 快递盒模型2:interval

Rx中有一个interval,它和setInterval有什么区别呢?

估计有人已经开始抢答了,interval就是对setInterval的延迟调用!bingo!

function interval(period){
    let i = 0
    return function(callback){
        setInterval(period,()=>callback(i++))
    }
}
const ob = interval(1000)
ob(console.log)// 至关于 subscribe

从上面两个例子来看,不管是fromEvent(……)仍是Interval(……),虽然内部是彻底不一样的逻辑,可是他们同属于“快递盒”这种东西,咱们把它称之为Observable——可观察者

fromEvent和Interval自己只是制做“快递盒”的模型,只有调用后返回的东西才是“快递盒”,即fromEvent(btn,"click")、interval(1000) 等等...

5、高阶快递盒

有了上面的基础,下面开始进阶:咱们拥有了那么多快递盒,那么就能够对这些快递盒再封装。

在文章开头说了,快递盒统一了一些操做,因此咱们能够把许许多多的快递盒堆叠在一块儿,即组合成一个大的快递盒!这个大的快递盒和小的快递盒同样,具备“打开”操做(即订阅)。当咱们打开这个大的快递盒的时候,会发生什么呢?

能够有不少种不一样的可能性,好比能够逐个打开小的快递盒(concat),或者一次性打开全部小的快递盒(merge),也能够只打开那个最容易打开的快递盒(race)。

下面是一个简化版的merge方法:

function merge(...obs){
    return function(callback){
        obs.forEach(ob=>ob(callback)) // 打开全部快递盒
    }
}

咱们仍是拿以前的fromEvent和interval来举例吧!

使用merge方法对两个Observable进行组合:

const ob1 = fromEvent(btn,'click') // 制做快递盒1
const ob2 = interval(1000) // 制做快递盒2
const ob = merge(ob1,ob2) //制做大快递盒
ob(console.log) // 打开大快递盒

当咱们“打开”(订阅)这个大快递盒ob的时候,其中两个小快递盒也会被“打开”(订阅),任意一个小快递盒里面的逻辑都会被执行,咱们就合并(merge)了两个Observable,变成了一个。

这就是咱们为何要辛辛苦苦把各类异步函数封装成快递盒(Observable)的缘由了——方便对他们进行统一操做!固然仅仅只是“打开”(订阅)这个操做只是最初级的功能,下面开始进阶。

6、销毁快递盒

6.1 销毁快递盒——取消订阅

咱们仍是以fromEvent为例子,以前咱们写了一个简单的高阶函数,做为对addEventListener的封装:

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
    }
}

当咱们调用这个函数的时候,就生成了一个快递盒(fromEvent(btn,'click'))。当咱们调用了这个函数返回的函数的时候,就是打开了快递盒(fromEvent(btn,'click')(console.log))。

那么咱们怎么去销毁这个打开的快递盒呢?

首先咱们须要获得一个已经打开的快递盒,上面的函数调用结果是void,咱们没法作任何操做,因此咱们须要构造出一个打开状态的快递盒。仍是使用高阶函数的思想:在返回的函数里面再返回一个函数,用于销毁操做。

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
        return function(){
            target.removeEventListener(evtName,callback)
        }
    }
}
const ob = fromEvent(btn,'click') // 制做快递盒
const sub = ob(console.log) // 打开快递盒,并获得一个可用于销毁的函数
sub() // 销毁快递盒

同理,对于interval,咱们也能够如法炮制:

function interval(period){
    let i = 0
    return function(callback){
        let id = setInterval(period,()=>callback(i++))
        return function(){
            clearInterval(id)
        }
    }
}
const ob = interval(1000) // 制做快递盒
const sub = ob(console.log) // 打开快递盒
sub() // 销毁快递盒

6.2 销毁高阶快递盒

咱们以merge为例:

function merge(...obs){
    return function(callback){
        const subs = obs.map(ob=>ob(callback)) // 订阅全部并收集全部的销毁函数
        return function(){
            subs.forEach(sub=>sub()) // 遍历销毁函数并执行
        }
    }
}
 
const ob1 = fromEvent(btn,'click') // 制做快递盒1
const ob2 = interval(1000) // 制做快递盒2
const ob = merge(ob1,ob2) //制做大快递盒
const sub = ob(console.log) // 打开大快递盒
sub() // 销毁大快递盒

当咱们销毁大快递盒的时候,就会把里面全部的小快递盒一块儿销毁。

6、补充

到这里咱们已经将Observable的两个重要操做(订阅、取消订阅)讲完了,值得注意的是,取消订阅这个行为并不是是做用于Observable上,而是做用于已经“打开”的快递盒(订阅Observable后返回的东西)之上!

Observable除此之外,还有两个重要操做,即发出事件、完成/异常,(这两个操做属因而由Observable主动发起的回调,和操做的方向是相反的,因此其实不能称之为操做)。

这个两个行为用快递盒就不那么形象了,咱们能够将Observable比作是水龙头,原先的打开快递盒变成拧开水龙头,而咱们传入的回调函数就能够比喻成接水的水杯!因为你们对回调函数已经很是熟悉了,因此本文就再也不赘述了。

7、后记

总结一下咱们学习的内容,咱们经过高阶函数将一些操做进行了“延迟”,并赋予了统一的行为,好比“订阅”就是延迟执行了异步函数,“取消订阅”就是在上面的基础上再“延迟”执行了销毁资源的函数。

这些所谓的“延迟”执行就是Rx编程中幕后最难理解,也是最核心的部分。Rx的本质就是将异步函数封装起来,而后抽象成四大行为:订阅、取消订阅、发出事件、完成/异常。

实际实现Rx库的方法有不少,本文只是利用了高阶函数的思想来帮助你们理解Observable的本质,在官方实现的版本中,Observable这个快递盒并不是是高阶函数,而是一个对象,但本质上是同样的,这里引出了一个话题:函数式编程与面向对象的异同,请听下回分解。

做者:vivo互联网开发团队-Li Yuxiang
相关文章
相关标签/搜索