本文是Rxjs 响应式编程-第三章: 构建并发程序这篇文章的学习笔记。
示例代码托管在:
更多博文:《大史住在大前端》原创博文目录
一. 划重点
尽量避免外部状态
在基本的函数式编程中,纯函数可以保障构建出的数据管道得到确切的可预测的结果,响应式编程中有着同样的要求,博文中的示例可以很清楚地看到,当依赖于外部状态时,多个订阅者在观察同一个流时就容易互相影响而引发混乱。
当不同的流之间出现共享的外部依赖时,一般的实现思路有两种:
将这个外部状态独立生成一个可观察对象,然后根据实际逻辑需求使用正确的流合并方法将其合并。
将这个外部状态独立生成一个可观察对象,然后使用Subject来将其和其他逻辑流联系起来。
管道的执行效率
在上一节中通过compose运算符组合纯函数就可以看到,容器相关的方法几乎全都是高阶函数,这样的做法就使得管道在构建过程中并不不会被启用,而是缓存组合在了一起(从上一篇的IO容器的示例中就可以看到延缓执行的形式),当它被订阅时才会真正启动。
Subject类
Subject同时具备Observable和observer的功能,可订阅消息,也可产生数据,一般作为流和观察者的代理来使用,可以用来实现流的解耦。为了实现更精细的订阅控制,Subject还提供了以下几种方法。
AsyncSubject
AsyncSubject观察的序列完成后它才会发出最后一个值,并永远缓存这个值,之后订阅这个AsyncSubject的观察者都会立刻得到这个值。
BehaviorSubject
Observer在订阅BehaviorSubject时,它接收最后发出的值,然后接收后续发出的值,一般要求提供一个初始值,观察者接收到的消息就是距离订阅时间最近的那个数据以及流后续产生的数据。
ReplaySubject
ReplaySubject会缓存它监听的流发出的值,然后将其发送给任何较晚的Observer,它可以通过在构造函数中传入参数来实现缓冲时间长度的设定。
二. 从理论到实践原文中提供了一个非常详细的打飞机游戏的代码,但我仍然建议你在熟悉了其基本原理和思路后自己将它实现出来,然后去和原文中的代码作对比,好搞清楚哪些东西是真的理解了,哪些只是你以为自己理解了,接着找一些很明显的优化点,继续使用响应式编程的思维模式来试着实现它们,起初不知道从何下手是非常正常的(当然也可能是笔者的自我安慰),但这对于培养响应式编程思维习惯大有裨益。笔者在自己的实现中又加入了右键切换飞船类型的功能,必须得说开发游戏的确比写业务逻辑要有意思。
由于没有精确计算雪碧图的坐标,所以在碰撞检测时会有一些偏差。
三. 问题及反思
关于canvas的尺寸问题
建议通过以下方式来设置:
<!--推荐方式1--> <canvas></canvas> //推荐方式2 canvas = document.getElementById('canvas'); canvas.height = 300; canvas.width = 300;需要避免的几种方式(都是只改变画板尺寸,不改变画布尺寸,会造成绘图被拉伸):
//1.CSS设置 #mycanvas{ height:300px; width:300px; } //2.DOM元素API设置 canvas = document.getElementById('canvas'); canvas.style.height = 300; canvas.style.width= 300; //3.Jquery设置 $('#mycanvas').width(300);同时需要注意canvas的宽高不支持百分比设定。
Rx.Observable.combineLatest以后整体的流不自动触发了
combineLatest这个运算符需要等所有的流都emit一次数据以后才会开始emit数据,因为它需要为整合在一起的每一个流保持一个最新值。所以自动启动的方法也很简单,为那些不容易触发首次数据的流添加一个初始值就可以了,就像笔者在上述实现右键来更换飞船外观时所实现的那样,使用startWith运算符提供一个初始值后,在鼠标移动时combineLatest生成的飞船流就会开始生产数据了。另外一点需要注意的就是combineLatest结合在一起后,其中任何一个流产生数据都会导致合成后的流产生数据,由于图例数据的坐标是在绘制函数中实现的,所以被动的触发可能会打乱原有流的预期频率,使得一些舞台元素的位置或形状变化更快,这种情况可以使用sample( )运算符对合并后的流进行取样操作来限制数据触发频率。
一段越来越快的流