李隐从仓库拿到的东西JS那儿去修理了 需要注意哪些东西

豆丁微信公众号
君,已阅读到文档的结尾了呢~~
新手买本要注意 购买笔记本电脑时的常见问题解答
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
新手买本要注意 购买笔记本电脑时的常见问题解答
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='http://www.docin.com/DocinViewer--144.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口后使用快捷导航没有帐号?
查看: 2668|回复: 17
后天去二手JS那买A6Je,高人乱进
该用户从未签到
后天就去买了,型号是A6Q22JE-DR,对这款机子有个大概了解,但细节方面不清楚,请高人指点下有没有什么需要注意的,严重感谢~~!!
1、这款机子的缺点?(去了才好耍屠龙刀~~~~~)
2、有什么细节方面的需要注意?(本人菜鸟,从没玩过本本)
3、如何知道JS是否拆机或修理过啊?(技术性问题,请详细说明,谁叫我菜啊)
4、内存硬盘等等的具体测机参数?(测机的时候好对比对比,不知道拿到修理货)
5、屏幕是哪家的出的?(担心同上)
6、其他的我不知道了,大家看看还有没有什么需要注意的?
[ 本帖最后由 forzadoria 于
03:32 PM 编辑 ]
该用户从未签到
自己顶下 高人快来
该用户从未签到
这款机器还是很不错的 A6系列应该说比较重吧,我是A6Q42CJC&&使用还行 就是比较重 2.8KG&&当然不全是 外壳也行,A6基本没有外壳的问题
验机就要你好好看看了 由于你买的是2手,看看后盖的螺丝有没有拧过的痕迹
内存用cpu-Z就行了&&硬盘用hdtune
屏幕可以用everest来看是哪家出的 也可以看参数
软件你下到U盘里带着去
你去下载吧 先研究下怎么使用
下载地址:和
该用户从未签到
回复 #3 伊不能静 的帖子
谢谢斑竹大大&&软件我都准备好了&&就怕百密一疏啊 毕竟我没用过本 所以害怕在细节方面中招
该用户从未签到
看看后盖的螺丝有没有拧过的痕迹
这个,问一下,下内存和硬盘的时候,是要拧螺丝的吗?
该用户从未签到
软件准备好了啊 呵呵
二手的仔细看看硬盘有无坏道,顺便看下使用时间, 可用hdtune 当然这个检测只是参考 不放心可以在DOS下用MHDD、效率源等软件详细检测硬盘有无坏道
键盘挨个按下 看看 有无不良的
屏幕仔细看有无暗点、亮点、彩点
最重要的一点还有在华硕官网有个注册 也要过来 详细看看
该用户从未签到
原帖由 forzadoria 于
07:54 AM 发表
看看后盖的螺丝有没有拧过的痕迹
这个,问一下,下内存和硬盘的时候,是要拧螺丝的吗? 恩 肯定的了 如果他说是换硬盘了或者内存 我建议你当场拆机看看是不是真如其说,只要不动那些易碎贴就没事 不要害怕打开后盖。如果他给你加了内存,多测试下内存的兼容性。
该用户从未签到
最重要的一点还有在华硕官网有个注册 也要过来 详细看看
官网注册什么啊? 不懂,去哪看啊? 谢谢斑竹大大,给个地址嘛
该用户从未签到
原帖由 伊不能静 于
03:59 PM 发表
恩 肯定的了 如果他说是换硬盘了或者内存 我建议你当场拆机看看是不是真如其说,只要不动那些易碎贴就没事 不要害怕打开后盖。如果他给你加了内存,多测试下内存的兼容性。
如果是没拆机的话,易碎贴都在的是吧?
新本的内存和硬盘盖的螺丝也是没易碎贴的吗?
谢谢谢谢你的回复
该用户从未签到
原帖由 forzadoria 于
07:59 AM 发表
最重要的一点还有在华硕官网有个注册 也要过来 详细看看
官网注册什么啊? 不懂,去哪看啊? 谢谢斑竹大大,给个地址嘛
看看我给你节的图&&你到官网 依照图就能看见了
1.GIF (6.68 KB, 下载次数: 8)
16:08 上传
2.GIF (26.69 KB, 下载次数: 10)
16:08 上传
该用户从未签到
原帖由 forzadoria 于
08:02 AM 发表
如果是没拆机的话,易碎贴都在的是吧?
新本的内存和硬盘盖的螺丝也是没易碎贴的吗?
谢谢谢谢你的回复 内存和硬盘处是没易碎贴的 自己是可以更换的 这点放心
该用户从未签到
该用户从未签到
去官网注册了有什么用
(上面不好意思,多按了下)
该用户从未签到
注册了 电池的质保从半年延长到1年 既然你买他的机&&当然这些注册信息你也要知道的嘛
该用户从未签到
这个我到不知道
我机器都买了一年多了
不知现在注册还可以吗
注册了除了电池,还有什么好处吗
该用户从未签到
原帖由 xiaochun0210 于
08:59 AM 发表
这个我到不知道
我机器都买了一年多了
不知现在注册还可以吗
注册了除了电池,还有什么好处吗
麻烦细说 1年多了 电池已经过保 注册不注册无所谓了
该用户从未签到
还好没什么影响
我电池只用过一次
感觉少用电池就是最好的保养方法了
该用户从未签到
我的第一款本本就是这款,现在还用着。除了重点目前还没有发现什么缺点,最关键的就是要原配的,
17年8月精华宗师
17年8月精华大师
关注本友会
本友会微信公众号
VR微信公众号
benyouhui2012
Powered by&p&前一段时间,我写了两篇文章,一篇是对目前前端主流视图框架的思考:&a href=&http://zhuanlan.zhihu.com/p/& class=&internal&&对当前单页应用的技术栈思考 - 民工叔叔家的田 - 知乎专栏&/a&,一篇是深入使用RxJS控制复杂业务逻辑的:&a href=&http://zhuanlan.zhihu.com/p/& class=&internal&&流动的数据——使用 RxJS 构造复杂单页应用的数据逻辑 - 民工叔叔家的田 - 知乎专栏&/a&,在这两篇中,我分别提到:&/p&&ul&&li&期望在复杂业务逻辑方面使用RxJS,更好地进行抽象,但是视图上使用轻量MVVM以达到快速开发的目的。&/li&&li&目前VueJS中,如果要结合RxJS,可能需要手动订阅和取消订阅,写起来还是没有CycleJS方便。&/li&&/ul&&p&最近,VueJS社区升级了vue-rx这个库,实现了比较方便地把VueJS和RxJS结合的能力。&/p&&p&我们来详细了解一下。&/p&&h2&在视图上绑定Observable&/h2&&p&VueJS本身不是基于RxJS这一套理念构建的,如果不借助任何辅助的东西,可能我们会需要干这么一些事情:&/p&&ul&&li&手动订阅某些Observable,在observer里面,把数据设置到Vue的data上&/li&&li&在视图销毁的时候,手动取消订阅&/li&&/ul&&p&在业务开发中,我们最常用的是绑定简单的Observable,在vue-rx中,这个需求被很轻松地满足了。&/p&&p&与早期版本不同,vue-rx 2.0在Vue实例上添加了一个subscriptions属性,里面放置各种待绑定的Observable,用的时候类似data。&/p&&p&比如,我们可以这么用它:&/p&&p&rx-simple.vue&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template&
&h4&Single Value&/h4&
&div&{{single$}}&/div&
&h4&Array&/h4&
&li v-for=&item of arr0$&&{{item}}&/li&
&li v-for=&item of arr1$&&{{item}}&/li&
&h4&Interval&/h4&
&div&{{interval$}}&/div&
&h4&High-order&/h4&
&div&{{high$}}&/div&
&/template&
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/of'
import 'rxjs/add/observable/from'
import 'rxjs/add/operator/toArray'
import 'rxjs/add/observable/interval'
import 'rxjs/add/observable/range'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/mergeAll'
const single$ = Observable.of(Math.PI)
const arr0$ = Observable.of([1, 1, 2, 3, 5, 8, 13])
const arr1$ = Observable.from([1, 1, 2, 3, 5, 8, 13]).toArray()
const interval$ = Observable.interval(1000)
const high$ = Observable.range(1, 5)
.map(item =& Observable.interval(item * 1000))
.mergeAll()
export default {
name: 'rx-simple',
subscriptions: {
interval$,
&/code&&/pre&&/div&&p&这个demo里面,演示了四种不同的Rx数据形态。其中,single$和interval$虽然创建方式不同,但实际上用的时候是一样的,因为,对它们的订阅,都是取其最后一个值,这两者的区别只是,一个不变了,一个持续变,但界面展示的始终是最后那个值。&/p&&p&关于数组,初学者需要稍微注意一下,从同样的数组,分别通过Observable.of和Observable.from出来的形态是大为不同的:&/p&&ul&&li&of创建的这个,里面只有一个值,这个值是个数组,所以,订阅它,会得到一个数组&/li&&li&from创建的这个,里面有若干个值,每个值是由数组中的元素创建的,订阅它,会一次性得到多个值,但展示的时候只会有最后一个,因为前面的都被覆盖掉了&/li&&/ul&&p&那么,这个high$代表什么呢?&/p&&ul&&li&range操作,创建了一个流,里面有多个简单数字&/li&&li&map操作,把这个流升级为二阶,流里面每个元素又是一个流&/li&&li&mergeAll操作,把其中的每个流合并,降阶为一阶流,流里面每个元素是个简单数字&/li&&/ul&&p&如果说不mergeAll,直接订阅map出来的那个二阶流,结果是不对的,vue-rx只支持一阶订阅绑定,不支持把高阶流直接绑定,如果有业务需要,应当自行降阶,通过各种flat、concat、merge操作,变成一阶流再进行绑定。&/p&&h2&将Vue $watcher转换为Observable&/h2&&p&上面我们述及的,都是从Observable的数据到Vue的ReactiveSetter和Getter中,这条路径的操作已经很简便了,我们只需把Observable放在vue实例的subscriptions里面,就能直接绑定到视图。&/p&&p&但是,反过来还有一条线,我们可能会需要根据某个数据的变化,让这个数据进入一个数据流,然后进行后续运算。&/p&&p&例如:有一个num属性,挂在data上,还有一个数据num1,表达:始终比num大1这么一件事。&/p&&p&当然,我们是可以直接利用computed property去做这件事的,为了使得我们这个例子更有说服力,给它这个加一计算添加一个延时3秒,强行变成异步:始终在num属性确定之后,等3秒,把自己变成比num大1的数字。&/p&&p&这样,computed property就写不出来了,我们可能就要手动去$watch这个num,然后在回调方法中,去延时加一,然后回来赋值给num1。&/p&&p&在vur-rx中,提供了一个从$watch创建Observable的方法,叫做$watchAsObservable,我们来看看怎么用:&/p&&p&rx-watcher.vue&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&template&
&h4&Watch&/h4&
&button v-on:click=&num++&&add&/button&
source: {{num}}
-& result: {{num$}}
&/template&
import 'rxjs/add/operator/pluck'
import 'rxjs/add/operator/startWith'
import 'rxjs/add/operator/delay'
export default {
name: 'rx-watch',
subscriptions() {
num$: this.$watchAsObservable('num')
.pluck('newValue')
.startWith(this.num)
.map(a =& a + 1)
.delay(3000)
&/code&&/pre&&/div&&p&这个例子里面的num$经过这么几步:&/p&&ul&&li&this.$watchAsObservable('num'),把num属性的变动,映射到一个数据流上&/li&&li&这个数据流的结果是一个对象,里面有newValue和oldValue属性,我们通常情况下,要的都是newValue,所以用pluck把它挑出来&/li&&li&注意,这个检测的只是后续变动,对于已经存在的值,是$watch不到的,所以,用startWith,把当前值放进去&/li&&li&然后是常规的rx运算了&/li&&/ul&&p&那么,这件事的原理是什么呢?&/p&&p&我们知道,Vue实例中,data上的属性都会存在ReactiveSetter,所以它被赋值的时候,就会触发这个setter,所以,$watchAsObservable的内部只需根据数据变动,生成一个Observable就可以了。&/p&&p&$watchAsObservable的方法签名如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&$watchAsObservable(expOrFn, [options])
&/code&&/pre&&/div&&p&这个options,跟vue的$watch方法的options一样。&/p&&p&有时候,我们会有这样的情况:在组件实例化的时候,数据流由于缺少某些条件,可能还没法创建。&/p&&p&比如说,某个组件,依赖于路由上面的某个参数,这时候,可能你不知道怎么去初始化绑定。&/p&&p&其实,产生这样的想法,本身就错了,因为没有用Rx的理念去思考问题。想一下下面这句话:&/p&&p&数据流的定义,与初始条件是否具备无关。&/p&&p&初始条件其实也只是整个数据流管道中的一节,如果初始不确定的话,我们只要给它留一个数据入口就好了,后续的流转定义可以全部写得出来。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const taskId$ = new Subject()
const task$ = taskId$
.distinctUntilChanged()
.switchMap(id =& this.getInitialData(id))
&/code&&/pre&&/div&&p&然后,在路由变更等事件里,往这个taskId$里面next当前的id就可以了。通过这种方式,我们就可以把task$直接绑定到界面上。&/p&&p&或者,taskId$也可以通过在路由上面的watch转化而成,只是不能直接用$watchAsObservable,可以考虑改进一下这种情况。&/p&&p&这样可以实现组件canReuse的情况下,改动路由参数,触发当前页面的数据刷新,实现视图的更轻量级的刷新。&/p&&h2&将DOM事件转化为Observable&/h2&&p&使用RxJS可以直接把DOM事件转化为Observable,vue-rx也提供了一个类似的方法来做这个事,不过我没理解这两个东西有什么差异?具体参见官方示例吧。&/p&&h2&构建优化&/h2&&p&关注vue-rx的readme,可以发现,目前推荐使用绑定的方式是这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import Vue from 'vue'
import Rx from 'rxjs/Rx'
import VueRx from 'vue-rx'
Vue.use(VueRx, Rx)
&/code&&/pre&&/div&&p&但这样会有一个问题,import的是rxjs/Rx,我们看到,这个文件里把所有可以被挂接到Rx对象上的东西都import进来了,这会导致构建的时候没法tree-shaking,用不到的那些操作符也被构建进来了,一个简单的demo,可能构建结果也有200多k,这还是太大了。&/p&&p&我们查看一下vue-rx的源码,发现传入的这个Rx是怎么使用的呢?&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var obs$ = Rx.Observable.create(function (observer) {
// Returns function which disconnects the $watch expression
var disposable
if (Rx.Subscription) { // Rx5
disposable = new Rx.Subscription(unwatch)
} else { // Rx4
disposable = Rx.Disposable.create(unwatch)
&/code&&/pre&&/div&&p&这里,其实只是要使用Observable和Subscription这两个东西,所以我们可以改成这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import Vue from 'vue'
import { Observable } from 'rxjs/Observable'
import { Subscription } from 'rxjs/Subscription'
import VueRx from 'vue-rx'
Vue.use(VueRx, { Observable, Subscription })
&/code&&/pre&&/div&&p&再试试,构建大小只有不到100k了,而且是可以正常运行的。如果用的是Rx 4,需要传入的就是Disposable而不是Subscription。&/p&&p&另外,如果我们使用了$watchAsObservable,还会需要引入另外一个东西:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import 'rxjs/add/operator/publish'
&/code&&/pre&&/div&&p&这是因为在$watchAsObservable里面,为了共享Observable,把它pubish之后refCount了,所以要引入,用不到这个方法的话,可以不引。&/p&&p&如果使用了$fromDOMEvent,还需要引入这个:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import 'rxjs/add/observable/empty'
&/code&&/pre&&/div&&p&因为$fromDOMEvent里面的这段:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if (typeof window === 'undefined') {
return Rx.Observable.empty()
&/code&&/pre&&/div&&h2&小结&/h2&&p&有了这个库之后,我们就可以比较优雅地结合VueJS和RxJS了。之前,两者之间结合的麻烦点主要在于:&/p&&p&在RxJS体系中,数据的进、出这两头是有些繁琐的。&/p&&p&所以,CycleJS采用了比较极端的做法,把DOM体系也包括进去了,这样,编写代码的时候,数据就没有进出的成本,但这么做,其实是牺牲了一些视图层的编写效率。&/p&&p&而Angular2中,用的是async这个pipe来解决这问题,这也是一种比较方便的办法,在绑定Observable这一点上,跟有了vue-rx之后的Vue是差不多简便的。&/p&&p&React体系里面也有对RxJS的适配,而且还有跟Redux,Mobx对接的适配,感兴趣的可以自行关注。&/p&&p&从个人角度出发,vue-rx这次的升级很好地满足了我对复杂应用开发的需求了。&/p&&p&本文示例代码参见:&a href=&https://link.zhihu.com/?target=https%3A//github.com/xufei/vue-rx-demo& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&&/p&
前一段时间,我写了两篇文章,一篇是对目前前端主流视图框架的思考:,一篇是深入使用RxJS控制复杂业务逻辑的:,…
&figure&&img src=&https://pic3.zhimg.com/v2-7caa8d867ed04c0df7de82d_b.jpg& data-rawwidth=&1072& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1072& data-original=&https://pic3.zhimg.com/v2-7caa8d867ed04c0df7de82d_r.jpg&&&/figure&&p&今天,我来为大家解读一幅来自 &a href=&http://link.zhihu.com/?target=http%3A//TurnOff.us& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&TurnOff.us&/span&&span class=&invisible&&&/span&&/a& 的漫画 “&a href=&http://link.zhihu.com/?target=http%3A//turnoff.us/geek/inside-the-linux-kernel/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&InSide The Linux Kernel&/a&” 。 &a href=&http://link.zhihu.com/?target=http%3A//turnoff.us/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TurnOff.us&/a& 是一个极客漫画网站,作者Daniel Stori 画了一些非常有趣的关于编程语言、Web、云计算、Linux 相关的漫画。今天解读的便是其中的一篇。&/p&&p&在开始,我们先来看看这幅漫画的全貌!(如题图)&/p&&p&这幅漫画是以一个房子的侧方剖面图来绘画的。使用这样的一个房子来代表 Linux 内核。&/p&&h3&地基&/h3&&p&作为一个房子,最重要的莫过于其地基,在这个图片里,我们也从最下面的地基开始看起:&/p&&figure&&img src=&http://pic2.zhimg.com/v2-8e1c8c95fb79b5a2aca07b_b.jpg& data-rawwidth=&1156& data-rawheight=&451& class=&origin_image zh-lightbox-thumb& width=&1156& data-original=&http://pic2.zhimg.com/v2-8e1c8c95fb79b5a2aca07b_r.jpg&&&/figure&&p&&em&&u&地基&/u&&/em&&/p&&p&地基(底层)由一排排的文件柜组成,井然有序,文件柜里放置着“文件”——电脑中的文件。左上角,有一只胸前挂着 421 号牌的小企鹅,它表示着 PID(进程 ID Process ID) 为 421 的进程,它正在查看文件柜中的文件,这代表系统中正有一个进程在访问文件系统。在右下角有一只小狗,它是看门狗(watchdog) ,这代表对文件系统的监控。&figure&&img src=&http://pic2.zhimg.com/v2-5cbcf9e0a54c48282f1d_b.jpg& data-rawwidth=&1862& data-rawheight=&904& class=&origin_image zh-lightbox-thumb& width=&1862& data-original=&http://pic2.zhimg.com/v2-5cbcf9e0a54c48282f1d_r.jpg&&&/figure&&/p&&p&&em&&u&一层(地面层)&/u&&/em&&/p&&h3&一层(地面层)&/h3&&p&看完了地基,接下来我们来看地基上面的一层,都有哪些东西。&/p&&figure&&img src=&http://pic2.zhimg.com/v2-7cc81a2c7e660deb368c2d_b.jpg& data-rawwidth=&598& data-rawheight=&407& class=&origin_image zh-lightbox-thumb& width=&598& data-original=&http://pic2.zhimg.com/v2-7cc81a2c7e660deb368c2d_r.jpg&&&/figure&&p&&em&&u&进程表&/u&&/em&&/p&&p&在这一层,最引人瞩目的莫过于中间的一块垫子,众多小企鹅在围着着桌子坐着。这个垫子的区域代表进程表。&/p&&p&左上角有一个小企鹅,站着,仿佛在说些什么这显然是一位家长式的人物,不过看起来周围坐的那些小企鹅不是很听话——你看有好多走神、自顾自聊天的——“喂喂,说你呢,哇塞娃(171),转过身来”。它代表着 Linux 内核中的初始化(init)进程,也就是我们常说的 PID 为 1 的进程。桌子上坐的小企鹅都在等待状态wait中,等待工作任务。&/p&&figure&&img src=&http://pic4.zhimg.com/v2-6de2261bc6dbb56a7b1d7_b.jpg& data-rawwidth=&1051& data-rawheight=&232& class=&origin_image zh-lightbox-thumb& width=&1051& data-original=&http://pic4.zhimg.com/v2-6de2261bc6dbb56a7b1d7_r.jpg&&&/figure&&p&&em&&u&看门狗&/u&&/em&&/p&&p&瞧瞧,垫子(进程表)两旁有两只小狗,它会监控小企鹅的状态(监控进程),当小企鹅们不听话时,它就会汪汪地叫喊起来。&/p&&figure&&img src=&http://pic2.zhimg.com/v2-cf6d64581fba4bd42dede222d81739c5_b.jpg& data-rawwidth=&550& data-rawheight=&241& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&http://pic2.zhimg.com/v2-cf6d64581fba4bd42dede222d81739c5_r.jpg&&&/figure&&p&&em&&u&http 进程&/u&&/em&&/p&&p&在这层的左侧,有一只号牌为 1341 的小企鹅,守在门口,门上写着 80,说明这个 PID 为 1341 的小企鹅负责接待 80 端口,也就是我们常说的 HTTP (网站)的端口。小企鹅头上有一片羽毛,这片羽毛大有来历,它是著名的 HTTP 服务器 Apache 的 Logo。喏,就是这只:&/p&&figure&&img src=&http://pic3.zhimg.com/v2-335edadebdeec029bd606d6_b.jpg& data-rawwidth=&369& data-rawheight=&137& class=&content_image& width=&369&&&/figure&&p&&em&&u&apache logo&/u&&/em&&/p&&p&向右看,我们可以看到这里仍有一扇门,门上写着 21,但是,看起来这扇门似乎年久失修,上面的门牌号都歪了,门口也没人守着。看起来这个 21 端口的 FTP 协议有点老旧了,目前用的人也比以前少了,以至于这里都没人接待了。&/p&&figure&&img src=&http://pic3.zhimg.com/v2-078db8b993bea161dfa5ee6e55e45062_b.jpg& data-rawwidth=&541& data-rawheight=&202& class=&origin_image zh-lightbox-thumb& width=&541& data-original=&http://pic3.zhimg.com/v2-078db8b993bea161dfa5ee6e55e45062_r.jpg&&&/figure&&p&&em&&u&无人看守的 FTP 进程&/u&&/em&&/p&&p&而在最右侧的一个门牌号 22 的们的待遇就大为不同,居然有一只带着墨镜的小企鹅在守着,看起来好酷啊,它是黑衣人叔叔吗?为什么要这么酷的一个企鹅呢,因为 22 端口是 SSH 端口,是一个非常重要的远程连接端口,通常通过这个端口进行远程管理,所以对这个端口进来的人要仔细审查。&/p&&figure&&img src=&http://pic2.zhimg.com/v2-1b44ccdb574d_b.jpg& data-rawwidth=&484& data-rawheight=&360& class=&origin_image zh-lightbox-thumb& width=&484& data-original=&http://pic2.zhimg.com/v2-1b44ccdb574d_r.jpg&&&/figure&&br&&p&&em&&u&SSH 守护进程&/u&&/em&&/p&&p&它的身上写着 52,说明它是第 52 个小企鹅。&/p&&figure&&img src=&http://pic3.zhimg.com/v2-2e07ee2eb69b389d5ad6ea_b.jpg& data-rawwidth=&356& data-rawheight=&218& class=&content_image& width=&356&&&/figure&&p&&em&&u&到达文件系统&/u&&/em&&/p&&p&在图片的左上角,有一个向下台阶。这个台阶是底层(地基)的文件系统中的,进程们可以通过这个台阶,到文件系统中去读取文件,进行操作。&/p&&figure&&img src=&http://pic4.zhimg.com/v2-86accdcd85eb766cf98923_b.jpg& data-rawwidth=&382& data-rawheight=&296& class=&content_image& width=&382&&&/figure&&p&&em&&u&Cron 任务&/u&&/em&&/p&&p&在这一层中,有一个身上写着 217 的小企鹅,他正满头大汗地看着自己的手表。这只小企鹅就是定时任务(Crontab),他会时刻关注时间,查看是否要去做某个工作。&/p&&figure&&img src=&http://pic3.zhimg.com/v2-70ac221baccc40c1b7cc916_b.jpg& data-rawwidth=&703& data-rawheight=&237& class=&origin_image zh-lightbox-thumb& width=&703& data-original=&http://pic3.zhimg.com/v2-70ac221baccc40c1b7cc916_r.jpg&&&/figure&&p&&em&&u&管道&/u&&/em&&/p&&p&在图片的中部,有两个小企鹅扛着管道(PipeLine)在行走,一只小企鹅可以把自己手上的东西通过这个管道,传递给后面的小企鹅。不过怎么看起来前面这种(男?)企鹅累得满头大汗,而后面那只(女?)企鹅似乎游刃有余——喂喂,前面那个,裤子快掉了~&/p&&figure&&img src=&http://pic1.zhimg.com/v2-a7f1fe6fa7e30aaaf35398_b.jpg& data-rawwidth=&409& data-rawheight=&216& class=&content_image& width=&409&&&/figure&&p&&em&&u&Wine&/u&&/em&&/p&&p&在这一层还有另外的一个小企鹅,它手上拿着一杯红酒,身上写着 411,看起来有点不胜酒力。它就是红酒(Wine)小企鹅,它可以干(执行)一些来自 Windows 的任务。&/p&&h3&跃层&/h3&&p&在一层之上,还有一个跃层,这里有很多不同的屏幕,每个屏幕上写着 TTY(这就是对外的终端)。比如说最左边 tty4 上输入了“fre”——这是想输入“freshmeat...”么 :d ;它旁边的 tty2 和 tty3 就正常多了,看起来是比较正常的命令;tty7 显示的图形界面嗳,对,图形界面(X Window)一般就在 7 号终端;tty5 和 tty6 是空的,这表示这两个终端没人用。等等,tty1 呢?&/p&&figure&&img src=&http://pic1.zhimg.com/v2-a9feedfce0c_b.jpg& data-rawwidth=&1182& data-rawheight=&653& class=&origin_image zh-lightbox-thumb& width=&1182& data-original=&http://pic1.zhimg.com/v2-a9feedfce0c_r.jpg&&&/figure&&p&&em&&u&跃层&/u&&/em&&/p&&p&tty(终端)是对外沟通的渠道之一,但是,不是每一个进程都需要 tty,某些进程可以直接通过其他途径(比如端口)来和外部进行通信,对外提供服务的,所以,这一层不是完整的一层,只是个跃层。&/p&&p&好了,我们有落下什么吗?&/p&&figure&&img src=&http://pic1.zhimg.com/v2-aa60f6acab5cd183586ecc_b.jpg& data-rawwidth=&484& data-rawheight=&169& class=&origin_image zh-lightbox-thumb& width=&484& data-original=&http://pic1.zhimg.com/v2-aa60f6acab5cd183586ecc_r.jpg&&&/figure&&p&&em&&u&小丑?&/u&&/em&&/p&&p&这小丑是谁啊?&/p&&p&啊哈,我也不知道,或许是病毒?你说呢?&/p&
今天,我来为大家解读一幅来自
的漫画 “” 。
是一个极客漫画网站,作者Daniel Stori 画了一些非常有趣的关于编程语言、Web、云计算、Linux 相关的漫画。今天解读的便是其中的一篇。在开始,我们…
&figure&&img src=&https://pic2.zhimg.com/v2-6f165df3050cd1dfa8330f8_b.jpg& data-rawwidth=&468& data-rawheight=&366& class=&origin_image zh-lightbox-thumb& width=&468& data-original=&https://pic2.zhimg.com/v2-6f165df3050cd1dfa8330f8_r.jpg&&&/figure&&blockquote&感谢&a href=&https://www.zhihu.com/people/drakeleung& class=&internal&&追客&/a&,前端魔法师的投稿;本文译自 &a href=&http://link.zhihu.com/?target=https%3A//gist.github.com/staltz/868e7e9bc2a7b8c1f754& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The introduction to Reactive Programming you've been missing&/a&。&/blockquote&&p&想必你对 &em&Reactive Programming&/em& 这个新东西很好奇吧,尤其是他的衍生,比如:&em&Rx&/em&,&em&Bacon.js&/em&,&em&RAC&/em& 等等。&/p&&p&讲真,如果没有好资料的话,学习 &em&Reactive Programming&/em& 是一件很艰难的事情。还记得刚开始学习的时候,我不停地找教程,后来找到了一个很容易上手的实战指南,但是它仅仅涉及了表面的东西,并没有告诉我如何围绕 &em&Reactive Programming&/em& 来构建整个应用的架构。另外,官方的文档对我的帮助也不是很大,尤其是我想理解某个函数的时候。看看下面的例子你就知道:&/p&&blockquote&&p&Rx.Observable.prototype.flatMapLatest(selector, [thisArg])&/p&&br&&p&Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element’s index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.&/p&&/blockquote&&p&此时我的内心是崩溃的。&figure&&img src=&http://pic1.zhimg.com/v2-6f165df3050cd1dfa8330f8_b.png& data-rawwidth=&468& data-rawheight=&366& class=&origin_image zh-lightbox-thumb& width=&468& data-original=&http://pic1.zhimg.com/v2-6f165df3050cd1dfa8330f8_r.png&&&/figure&&/p&&p&我曾经还阅读过两本书,一本讲得很抽象,而另外一本则是教你如何使用 Reactive 相关的库。最后,我用了最笨的方法来学习:边用边学,把他运用到公司一个实际的项目当中,在遇到问题的时候得到了我同事们的&a href=&http://link.zhihu.com/?target=http%3A//blog.futurice.com/top-7-tips-for-rxjava-on-android& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&帮助&/a&。&/p&&p&在我学习的过程中,最艰难的部分是如何 thinking in Reactive。这需要我们摆脱 imperative and stateful 风格的编程习惯,然后强迫你大脑去思考如何用另外一种方式来解决同样的问题。我并没有找到任何关于这个的教程。所以,我觉得要有一个实战的教程告诉我们如何 thinking in Reactive,这样我们才能着手学习 &em&Reactive Programming&/em&。然后,阅读官方文档就事半功倍了。因此,我希望这篇教程能帮助到你。&/p&&h2&什么是 Reactive Programming&/h2&&p&对于什么是 &em&Reactive Programming&/em&,你会在网上看到很多不好的解释或者定义。Wikipedia 一如既往地万金油和偏理论化。&a href=&http://link.zhihu.com/?target=http%3A//stackoverflow.com/questions/1028250/what-is-functional-reactive-programming& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Stackoverflow&/a&的这个答案又太规范化,不适合初学者。而,&a href=&http://link.zhihu.com/?target=http%3A//www.reactivemanifesto.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reactive Manifesto&/a& 看起来像是用来忽悠产品经理。另外,微软的&a href=&http://link.zhihu.com/?target=https%3A//rx.codeplex.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&解释&/a& Rx = Observables + LINQ + Schedulers 又太 Microsoftish ,看到就觉得好难的样子。其实,像 &em&reactive&/em& 和 &em&propagation of change&/em& 等等这些词条和我们平常在 MV* 或者某些编程语言里看到的没有什么不同,都是解决同样的问题。view 要实时响应 model ,也就是当 model 改变时,view 也要做出相应的变化。&/p&&p&我们还是废话少说。&figure&&img src=&http://pic3.zhimg.com/v2-680af37cf5a6c2_b.png& data-rawwidth=&539& data-rawheight=&406& class=&origin_image zh-lightbox-thumb& width=&539& data-original=&http://pic3.zhimg.com/v2-680af37cf5a6c2_r.png&&&/figure&&/p&&h4&Reactive Programming 其实就是处理异步数据流&/h4&&p&也就是说,他并不是什么新东西。&em&Event buses&/em& 或者 &em&click events&/em& 这些不就是异步事件流(Async event streams)吗?你可以监听他们,然后做出相应的副作用(side effects)。&em&Reactive&/em& 其实就是一个 idea ,推而广之的话,不仅仅是 click 或者 hover 事件能够创建 data stream,所有东西都可以当作一个 stream :比如变量,用户的输入,属性,缓存,数据结构等等。不妨想象一下,你的 twitter feed 其实就是一个 data stream ,同样地 click 事件也是。你可以监听他们,然后做出响应。&/p&&p&在此基础上,你可以使用很多非常棒的函数,比如可以 combine ,create,filter 各种各样的 stream ,因为 Rx 借鉴了函数式编程。一个 stream 可以当作另一个 stream 的输入(input)。甚至多个 stream 也能当作另外一个 stream 的输入。而且,你可以合并(merge)两个 stream 。你也可以把一个 stream 里你只感兴趣的事件 filter 到另外一个 stream 。你还可以把一个 stream 中的数据映射(map)到另外一个 stream 中。&/p&&p&如果 stream 对于 &em&Reactive&/em& 这么重要的话,就让我们来研究研究他。首先,从我们最熟悉的例子开始:「点击一个按钮」 。&figure&&img src=&http://pic4.zhimg.com/v2-a54c1dfb377ea7e9ecb55c7_b.png& data-rawwidth=&512& data-rawheight=&261& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&http://pic4.zhimg.com/v2-a54c1dfb377ea7e9ecb55c7_r.png&&&/figure&&/p&&p&stream 是一序列按时间排序的 正在发生的事件(A stream is a sequence of ongoing events ordered in time)。他可以 emit 三种不同的东西:值(value),错误(error),或者一个 completed 的标志。举个例子,当点击窗口的关闭按钮时,对应的 completed 事件就会触发。&/p&&p&我们只能异步地捕获已经 emit 的事件:当一个值 emit 的时候就调用一个事先定义好的回调函数,同样地,当 error 或者 completed 时调用其对应的回调函数。有时候,你可以不用管后面两个函数,如果只关注值的话。监听 stream 也就是所谓的 subscribing ;回调函数就是所谓的 observers ;而 stream 也就是所谓的 subject (observable)。以上其实就是&a href=&http://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Observer_pattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&观察者设计模式&/a&(Observer Desgin Pattern)。&/p&&p&另外,我们也可以使用 ASCII 来描绘我们的 stream 示例图。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&--a---b-c---d---X---|-&
a, b, c, d are emmited valus
X is an error
| is the 'completed' signal
---& is the timeline
&/code&&/pre&&/div&&p&想必你对上面的东西都很熟悉吧,那么为了让你不感到无聊,让我们来弄点新东西:把一个原始的 click event stream 转换成一个新的 click event stream 。&/p&&p&首先,让我们创建一个 counter stream ,他表示某个按钮被点击了多少次。在常见的 Reactive library 里面,每个 stream 都有很多函数。比如 map,filter,scan 等等。当你调用其中某个时,比如 clickStream.map(f) ,他会返回一个基于 clickStream 的新的 stream 。他并不改变原来的 clickStream ,这就是所谓的 immutability(不变性),他和 &em&Reactive stream&/em& 总是形影不离。这样,我们可以链式地调用 stream 的函数像这样 clickStream.map(f).scan(g):&/p&&br&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&
clickStream: ---c----c--c----c------c--&
vvvvv map&span class=&o&&(&/span&c becomes 1&span class=&o&&)&/span& vvvv
---1----1--1----1------1--&
vvvvvvvvv scan&span class=&o&&(&/span&+&span class=&o&&)&/span& vvvvvvvvv
counterStream: ---1----2--3----4------5--&
&/code&&/pre&&/div&&p&map(f) 函数会根据你传进来的函数 f,替换掉 clickStream 每个 emit 的值,到新的 stream 中。在我们的例子中,我们把每个 click 映射成数字 1。scan(g) 会累加 stream 的过去的所有的值(例子中的 g 其实就是一个简单的 add 函数)。接着,无论 click 事件什么时候发生,counterStream 都会 emit 鼠标点击过的总次数。&/p&&p&为了展示 Reactive 的真正实力,我们不妨假设你有一个「double click」event stream 。为了让他更加有趣一些,我们想要的新的 stream 可以是 「triple clicks」或者直接「multiple clicks」。那么,现在请深呼吸一下,想象一下你用传统的 imperative and stateful 编程风格来实现这个效果。我敢打赌,这一定是一件很令人讨厌的事情,并且你还需要定义一些变量去保存状态,以及解决鼠标连续点击的时间间隔问题。&/p&&p&没错,用 Reactive 的话实现的话,是很简单的。实际上,关于逻辑的代码只有 &a href=&http://link.zhihu.com/?target=http%3A//jsfiddle.net/staltz/4gGgs/27/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&4 行&/a&。但是,我们暂时先不看代码。Thinking in diagrams (画图思考) 是理解和构建 stream 的最好方法,无论你是初学者还是老手。&figure&&img src=&http://pic2.zhimg.com/v2-1642eb54bcb1c4de4e1fdc1eeb794789_b.png& data-rawwidth=&512& data-rawheight=&719& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&http://pic2.zhimg.com/v2-1642eb54bcb1c4de4e1fdc1eeb794789_r.png&&&/figure&&/p&&p&上图中,灰色的矩形是把一个 stream 转换成另一个 stream 的函数。我们会每隔 250ms 把所有 click stream 都缓冲在一个数组里面,这是 buffer(stream.throttle(250ms)) 所要做的事情(如果你现在不了解细节的话不要在意,因为我们现在只是初探一下 &em&Reactive&/em& 而已)。于是,我们得到的是一个包含多个数组的 stream,接着调用 map() 函数,把每个数组都映射成一个整数(数组的长度)。随后,我们调用 filter(x &= 2) 来过滤掉那些长度为 1 的数组。综上,我们只需要3次操作就能得到我们想要的 stream 。最后,我们调用 subscribe() 来监听,响应我们想要做的事情。&/p&&p&我希望你能够欣赏这种很优美的方法。上面的例子其实只是冰山一角:你可以在不同类型的 stream 中调用相同的 operator (例如,map,filter 等等)。此外,还有很多有用的函数供你使用。&/p&&h2&Why Reactive Programming(RP)&/h2&&p&Reactive Programming 提高了你代码的抽象级别,因此你可以专注写业务逻辑(business logic),而不是不停地去折腾一大堆的实现细节,所以 RP 的代码看起来简洁很多。&/p&&p&RP 的优势在现代的 webapp 和 mobile app 中更加明显,因为他们需要和众多的 UI 事件(与数据事件相关)进行高度的交互。十年前,和 web 页面交互仅仅只是提交一个表单给后台,然后返回重新渲染好页面给前端。而如今的应用就需要更加实时(real-time)了:修改一个单独的表单域就会自动保存到后台,比如给某些内容的「点赞」就能够实时地反映给当前在线的其他用户。&/p&&p&为了提高用户体验,现代的应用都需要大量的实时的事件。我们需要工具来正确地解决这些问题,而 Reactive Programming 正是我们想要的答案。&/p&&h2&实战 Thinking in RP&/h2&&p&让我们回到现实世界吧,用一个真实的例子来说明如何一步一步地 thinking in RP 。不是伪代码,没有讲一半不讲另一半的概念性的东西。在教程的最后,我们的代码不仅可以跑起来,还知道每一步为什么要这样做。&/p&&p&我选择 JavaScript 和 &a href=&http://link.zhihu.com/?target=https%3A//github.com/Reactive-Extensions/RxJS& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&RxJS&/a& 作为我们的工具,是因为:JavaScript 是如今最流行的语言,虽然 &a href=&http://link.zhihu.com/?target=http%3A//www.reactivex.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rx* library family&/a& 已经被大量应用到需要的语言和平台中(.NET,Java,Scala,Clojure,JavaScript,Ruby,Phtyhon,C++,Object-C/Cocoa,Groovy 等等)。无论你选择哪个,你都可以从这篇教程中学到东西。&/p&&h2&实现一个「 Who to follow 」&/h2&&p&Twitter 有一个推荐关注用户的 UI 组件:&figure&&img src=&http://pic3.zhimg.com/v2-0d490fbb90b14db5a85a9f1e03d36cea_b.png& data-rawwidth=&400& data-rawheight=&380& class=&content_image& width=&400&&&/figure&&/p&&p&下面,我们就来实现他的主要功能 :&/p&&ul&&li&在 App 启动时,从 API 中加载用户数据,并显示 3 个推荐&/li&&li&点击「刷新」按钮时,重新加载另外 3 个推荐用户&/li&&li&点击每行(每个推荐)的「 x 」(关闭按钮)时,移除掉当前的推荐,加载新的&/li&&li&每行都有用户的头像和主页的链接&/li&&/ul&&p&我们先不理其他比较小的功能。由于 Twitter 关闭了公用 API ,所以我们就转用 &a href=&http://link.zhihu.com/?target=https%3A//developer.github.com/v3/users/%23get-all-users& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GitHub 获取用户的 API&/a& 。&/p&&p&如果你想先看看最后的效果是怎样的,你可以点击这里&a href=&http://link.zhihu.com/?target=http%3A//jsfiddle.net/staltz/8jFJH/48/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&查看完整的代码&/a&。&/p&&h2&请求和响应(Request & Response)&/h2&&p&你怎么用 Rx 解决 API 请求和响应的问题?首先记住,(most) everything is a stream ,这是 施展 Rx 魔法的咒语。现在我们先实现最简单的功能:「在 App 启动时,从 API 中加载用户数据,并显示 3 个推荐」。这里没有什么特别的,就和往常一样:(1)发请求,(2)获取后台的响应,(3)渲染响应。接下来,我们把请求看作一个 stream 。虽然这看起来有点 overkill,但是我们需要从基本的东西开始,不是吗?&/p&&p&App 启动时我们只需要发一个请求,因此我们可以把他看作一个 data stream ,他只 emit 一个值。(以后我们会有多个请求,但现在我们只有一个)。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&--a------|-&
where a is the string 'https://api.github.com/users'
&/code&&/pre&&/div&&p&这就是我们想要发请求的 URL stream 。无论该请求事件何时发生,他都会告诉我们两件事:when and what 。「 when 」是说当事件 emit 时,请求才被执行。而「 what 」则表示请求的就是 emit 的值,即是这个 URL 字符串 。&/p&&p&在 Rx* 中创建只有单独一个值的 stream 是很简单的。stream 的官方术语是「 Observable 」,因为他可以被观察(observe)。但是我发现这是一个很蠢的名字,所以我通常都叫他「 stream 」。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var requestStream = Rx.Observable.just('https://api.github.com/users');
&/code&&/pre&&/div&&p&但现在,这只是一个 string stream ,并没有其他的操作,所以我们要想办法在他 emit 值的时候干些事情。这个时候就需要 subscribe (订阅) 他。&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&requestStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// execute the request&/span&
&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&responseData&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// ...&/span&
&span class=&p&&});&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&注意到现在我们用 &a href=&http://link.zhihu.com/?target=http%3A//devdocs.io/jquery/jquery.getjson& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jQuery Ajax&/a& 回调函数来处理请求后的异步操作。但你不是说 Rx 就是用来处理异步数据流的吗!难道这个请求的响应不能是一个包含数据,并且会在未来某个时间点到达的 stream ?理论上看起来是行的,让我们试试吧。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&requestStream.subscribe(function(requestUrl) {
// execute the request
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUrl)
.done(function(response) { observer.onNext(response); })
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
responseStream.subscribe(function(response) {
// do something with the response
&/code&&/pre&&/div&&p&Rx.Observable.create() 可以自定义我们自己的 stream,通过定义一个 observer(onNext(), onError)。不难发现,上面我们的工作其实就是封装一个 jQuery Ajax Promise 而已。慢着,这也就是说,Promise 是一个 Observable(stream) ?&/p&&p&Yes. 是的!(这都被你发现了!!)&/p&&p&Observable 其实就是 Promise++ 。在 Rx 中,你可以很简单地把一个 Promise 转换成一个 Observable,只需要:var stream = Rx.Observable.fromPromise(promise) ,接下来我们会使用他。Observable 和 Promise++ 的唯一区别是前者不兼容 &a href=&http://link.zhihu.com/?target=http%3A//promises-aplus.github.io/promises-spec/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Promise/A+&/a& ,但是理论上来讲是没有冲突的。Promise 其实就是只有单独一个值 的 Observable ,但后者更胜一筹的是允许多个返回值(多次 emit)。&/p&&p&这其实是一件很棒的事情,Promise 能做的事情,Observable 也能做。Promise 不能做的事情,Observable 还是能做。因此,如果你是 Promise 粉丝的话,那么你也应该尝试一下 Rx 的 Observable 。&/p&&p&回到我们的例子中,你会看到,我们的 subscribe() 函数嵌套着另一个 subscribe() ,这很快就会形成 &em&callback hell&/em& 。并且,responseStream 的创建依赖于 requestStream 。如果你在前面有仔细阅读的话,我们说过 Rx 可以很简单地让不同 stream 之间变换和创建,现在我们要把他应用到我们的例子中。&/p&&p&你首先要了解的最基本的函数是 map(f) ,他会把 stream A 的每个值,传到 f() ,然后产生新的值传给 stream B 。那么,应用到我们例子的话:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&responseMetastream&/span& &span class=&o&&=&/span& &span class=&nx&&requestStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromPromise&/span&&span class=&p&&(&/span&&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&以上,我们创建了一个叫「 metastream 」的怪兽:stream 嵌套着 stream (a stream of streams)。不用紧张,metastream 其实不过是一个 emit value 为 stream 的 stream 。你可以把他想象成一个&a href=&http://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Pointer_%28computer_programming%29& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&指针&/a&:每个 emit 的值就是一个指向另一个 stream 的指针。&figure&&img src=&http://pic2.zhimg.com/v2-00d076b1a8b5c99e25dd6f70691cc7ad_b.png& data-rawwidth=&512& data-rawheight=&342& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&http://pic2.zhimg.com/v2-00d076b1a8b5c99e25dd6f70691cc7ad_r.png&&&/figure&&/p&&p&显然,返回一个 metastream 对我们一点用都没有,我们只想要一个 emit value 为 JSON 对象(而不是一个包含 JSON 对象的「 Promise 」)的 stream 。现在,来认识一下我们的新朋友 &a href=&http://link.zhihu.com/?target=https%3A//github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md%23rxobservableprototypeflatmapselector-resultselector& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Mr.flatMap&/a& :他是特殊的 map,可以 flatten 上面讲到的 「 metastream 」。他通过 emit 主干(trunk stream) 的值,间接 emit 了分支(branch stream)的值。需要注意的是 flatMap 并不是一个「 fix 」,而 metastreams 更不是一个「 bug 」,他们都各自的用途。&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&responseStream&/span& &span class=&o&&=&/span& &span class=&nx&&requestStream&/span&
&span class=&p&&.&/span&&span class=&nx&&flatMap&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromPromise&/span&&span class=&p&&(&/span&&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&figure&&img src=&http://pic2.zhimg.com/v2-c5fd0e_b.png& data-rawwidth=&512& data-rawheight=&319& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&http://pic2.zhimg.com/v2-c5fd0e_r.png&&&/figure&&p&漂亮~ 现在我们的 response stream 是基于 request stream 而创建的。request stream 每次 emit 一个值,在 response stream 都会有相对应的值。就像这样:&/p&&br&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&requestStream:
--a-----b--c------------&span class=&p&&|&/span&-&
responseStream: -----A--------B-----C---&span class=&p&&|&/span&-&
&span class=&o&&(&/span&lowercase is a request, uppercase is its response&span class=&o&&)&/span&
&/code&&/pre&&/div&&p&终于,我们搞定了 response stream ,那么就可以渲染我们得到的数据了:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&responseStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// render `response` to the DOM however you wish&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&整理我们以上的所有代码,有:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'https://api.github.com/users'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&responseStream&/span& &span class=&o&&=&/span& &span class=&nx&&requestStream&/span&
&span class=&p&&.&/span&&span class=&nx&&flatMap&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromPromise&/span&&span class=&p&&(&/span&&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&span class=&nx&&responseStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// render `response` to the DOM however you wish&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&h2&刷新按钮&/h2&&p&我还没告诉你 GitHub 这个 API 返回的 JSON 对象包含了 100 用户。他只允许我们设置 page offset 而不能设置 page size ,但是我们只需要 3 个所以浪费了剩下的 97 个。我们可以暂时先不管这个,因为后面我们会讲到如何缓存 API 返回的响应。&/p&&p&每次点击刷新按钮的时候,request stream 都应该 emit 一个新的 URL ,这样我们才能得到新的 response 。那么,我们现在需要两样东西:点击刷新按钮的 refresh click stream (咒语:anything can be a stream ),以及依赖于 refresh click stream 的 request stream 。幸运的是,RxJS 可以很简单地创建监听事件的 Observables 。&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&refreshButton&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&querySelector&/span&&span class=&p&&(&/span&&span class=&s1&&'.refresh'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&refreshClickStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromEvent&/span&&span class=&p&&(&/span&&span class=&nx&&refreshButton&/span&&span class=&p&&,&/span& &span class=&s1&&'click'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&显然,refreshClickStream 并没有包含任何的 API URL ,所以我们需要把它们映射(map)到一个真正的 URL :&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'https://api.github.com/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&因为我没做自动化测试,所以之前的功能在加了新功能之后跑不起来了:在 App 启动时并没有发送我们的请求,只有在点击刷新按钮的时候发送。但是,这两个情景我都想实现。&/p&&p&根据我们现在的知识,可以分别为每个情景定义一个 stream :&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestOnRefreshStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'https://api.github.com/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&span class=&kd&&var&/span& &span class=&nx&&startupRequestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'https://api.github.com/users'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&可以把两个 stream 合并成一个吗?答案是 merge() 。用图来解释的话:&/p&&br&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&stream A: ---a--------e-----o-----&
stream B: -----B---C-----D--------&
vvvvvvvvv merge vvvvvvvvv
---a-B---C--e--D--o-----&
&/code&&/pre&&/div&&p&因此我们现在可以:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomO
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
var requestStream = Rx.Observable.merge(
requestOnRefreshStream, startupRequestStream
&/code&&/pre&&/div&&p&然而我们还有另外一种更加简洁的写法,&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'https://api.github.com/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&merge&/span&&span class=&p&&(&/span&&span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'https://api.github.com/users'&/span&&span class=&p&&));&/span&
&/code&&/pre&&/div&&p&甚至还可以更加简短和可读:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'https://api.github.com/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&startWith&/span&&span class=&p&&(&/span&&span class=&s1&&'https://api.github.com/users'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&&a href=&http://link.zhihu.com/?target=https%3A//github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md%23rxobservableprototypestartwithscheduler-args& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&startWith&/a& 顾名思义,不管 input stream 是怎样的,output stream 的开头都会有一个值 x ,因为我们设置了 startWith(x) 。但是我没有遵循 &a href=&http://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Don%27t_repeat_yourself& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&DRY&/a&(Dont Repeat Youself) ,因为我重复写了 API 两次。如果要 fix 这个问题的话,我们可以为 refreshClickStream 设置 startWith ,他「模拟」了在应用启动时点击了刷新按钮:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&&span class=&p&&.&/span&&span class=&nx&&startWith&/span&&span class=&p&&(&/span&&span class=&s1&&'startup click'&/span&&span class=&p&&)&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'https://api.github.com/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&太棒了!你可以看到我们只多加了 startWith() ,和「因为我没有做自动化测试,所以我弄坏了…」那个时候的代码比较的话。&/p&&h2&「三个关注用户推荐」UI&/h2&&p&在此之前,我们只在 responseStream 的 subscribe 函数里面渲染我们的「用户推荐 UI 」。但现在我们有了「刷新按钮」,就产生了一个新的问题:当你点击了刷新按钮,当前的三个用户推荐不会被清除掉,而当一个新的 response 到达时,新的推荐会紧跟在之前的推荐后面渲染。因此,如果我们点击了刷新按钮的话,我们需要移除掉当前的推荐。&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&refreshClickStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&c1&&// clear the 3 suggestion DOM elements&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&很显然,这个的做法是不对的,而且很糟糕,因为我们现在有 两个 subscriber 是可以修改「推荐界面」的 DOM 结构(另一个是之前的 responseStream.subscribe()),并且这一点也不 &a href=&http://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Separation_of_concerns& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Separation of concerns&/a&。还记得 Reactive 的咒语?&figure&&img src=&http://pic3.zhimg.com/v2-8fe48eb71aa5f92aa6be3d22d135aa32_b.jpg& data-rawwidth=&422& data-rawheight=&390& class=&origin_image zh-lightbox-thumb& width=&422& data-original=&http://pic3.zhimg.com/v2-8fe48eb71aa5f92aa6be3d22d135aa32_r.jpg&&&/figure&&/p&&p&因此,我们可以把「推荐」也看作一个 stream ,他 emit 的值是一个包含推荐数据的 JSON 对象。我们可以分别为3个推荐写一个 stream 。下面是「推荐用户一」的 stream :&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&suggestion1Stream&/span& &span class=&o&&=&/span& &span class=&nx&&responseStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&listUsers&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// get one random user from the list&/span&
&span class=&k&&return&/span& &span class=&nx&&listUsers&/span&&span class=&p&&[&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&nx&&listUsers&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&)];&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&剩下的两个 suggestion2Stream 和 suggestion3Stream 都可以简单地从 suggestion1Stream中复制过来。注意到,这一点也不 DRY ,但我不打算重构他,因为我想让我们的例子简单一些,并且也是一个好机会让你思考如何才能做到 DRY 。&/p&&p&那么,我们现在就不用在 responseStream 的 subscribe 里面渲染「推荐界面」,而是:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&suggestion1Stream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&suggestion&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// render the 1st suggestion to the DOM&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&回到前面所说的「点击刷新按钮,移除掉当前的推荐」(即是本部分的开头),现在我们可以把「刷新按钮点击」映射为一个 null 的推荐数据,然后把他加进 suggestion1Stream 里面,就像这样:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&suggestion1Stream&/span& &span class=&o&&=&/span& &span class=&nx&&responseStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&listUsers&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// get one random user from the list&/span&
&span class=&k&&return&/span& &span class=&nx&&listUsers&/span&&span class=&p&&[&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&nx&&listUsers&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&)];&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&merge&/span&&span class=&p&&(&/span&
&span class=&nx&&refreshClickStream&/span&&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(){&/span& &span class=&k&&return&/span& &span class=&kc&&null&/span&&span class=&p&&;&/span& &span class=&p&&})&/span&
&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&当渲染的时候,我们可以把 null 解读为「没有数据」,所以就隐藏了他的 UI 元素。&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&suggestion1Stream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&suggestion&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&suggestion&/span& &span class=&o&&===&/span& &span class=&kc&&null&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// hide the first suggestion DOM element&/span&
&span class=&p&&}&/span&
&span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&c1&&// show the first suggestion DOM element&/span&
&span class=&c1&&// and render the data&/span&
&span class=&p&&}&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&来看看我们现在所有的 stream 图示:&/p&&br&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&refreshClickStream: ----------o--------o----&
requestStream: -r--------r--------r----&
responseStream: ----R---------R------R--&
suggestion1Stream: ----s-----N---s----N-s--&
suggestion2Stream: ----q-----N---q----N-q--&
suggestion3Stream: ----t-----N---t----N-t--&
&/code&&/pre&&/div&&p&上面的 N 表示 null 。&/p&&p&我们还可以在启动时渲染一个空的推荐,需要在 suggestion stream 上添加一个 startWith(null):&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&suggestion1Stream&/span& &span class=&o&&=&/span& &span class=&nx&&responseStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&listUsers&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// get one random user from the list&/span&
&span class=&k&&return&/span& &span class=&nx&&listUsers&/span&&span class=&p&&[&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&nx&&listUsers&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&)];&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&merge&/span&&span class=&p&&(&/span&
&span class=&nx&&refreshClickStream&/span&&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(){&/span& &span class=&k&&return&/span& &span class=&kc&&null&/span&&span class=&p&&;&/span& &span class=&p&&})&/span&
&span class=&p&&)&/span&
&span class=&p&&.&/span&&span class=&nx&&startWith&/span&&span class=&p&&(&/span&&span class=&kc&&null&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&结果就是这样:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&refreshClickStream&/span&&span class=&o&&:&/span& &span class=&o&&----------&/span&&span class=&nx&&o&/span&&span class=&o&&---------&/span&&span class=&nx&&o&/span&&span class=&o&&----&&/span&
&span class=&nx&&requestStream&/span&&span class=&o&&:&/span& &span class=&o&&-&/span&&span class=&nx&&r&/span&&span class=&o&&--------&/span&&span class=&nx&&r&/span&&span class=&o&&---------&/span&&span class=&nx&&r&/span&&span class=&o&&----&&/span&
&span class=&nx&&responseStream&/span&&span class=&o&&:&/span& &span class=&o&&----&/span&&span class=&nx&&R&/span&&span class=&o&&----------&/span&&span class=&nx&&R&/span&&span class=&o&&------&/span&&span class=&nx&&R&/span&&span class=&o&&--&&/span&
&span class=&nx&&suggestion1Stream&/span&&span class=&o&&:&/span& &span class=&o&&-&/span&&span class=&nx&&N&/span&&span class=&o&&--&/span&&span class=&nx&&s&/span&&span class=&o&&-----&/span&&span class=&nx&&N&/span&&span class=&o&&----&/span&&span class=&nx&&s&/span&&span class=&o&&----&/span&&span class=&nx&&N&/span&&span class=&o&&-&/span&&span class=&nx&&s&/span&&span class=&o&&--&&/span&
&span class=&nx&&suggestion2Stream&/span&&span class=&o&&:&/span& &span class=&o&&-&/span&&span class=&nx&&N&/span&&span class=&o&&--&/span&&span class=&nx&&q&/span&&span class=&o&&-----&/span&&span class=&nx&&N&/span&&span class=&o&&----&/span&&span class=&nx&&q&/span&&span class=&o&&----&/span&&span class=&nx&&N&/span&&span class=&o&&-&/span&&span class=&nx&&q&/span&&span class=&o&&--&&/span&
&span class=&nx&&suggestion3Stream&/span&&span class=&o&&:&/span& &span class=&o&&-&/span&&span class=&nx&&N&/span&&span class=&o&&--&/span&&span class=&nx&&t&/span&&span class=&o&&-----&/span&&span class=&nx&&N&/span&&span class=&o&&----&/span&&span class=&nx&&t&/span&&span class=&o&&----&/span&&span class=&nx&&N&/span&&span class=&o&&-&/span&&span class=&nx&&t&/span&&span class=&o&&--&&/span&
&/code&&/pre&&/div&&h2&关闭一个推荐 & 缓存 response&/h2&&p&我们还需要实现一个功能:每个推荐都应该有一个「x」按钮去关闭它,然后加载一个新的推荐。一开始我们的想法可能会这样:当点击关闭按钮时,发一个新请求就可以啦:&/p&&br&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&close1Button&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&querySelector&/span&&span class=&p&&(&/span&&span class=&s1&&'.close1'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&close1ClickStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromEvent&/span&&span class=&p&&(&/span&&span class=&nx&&close1Button&/span&&span class=&p&&,&/span& &span class=&s1&&'click'&/span&&span class=&p&&);&/span&
&span class=&c1&&// and the same for close2Button and close3Button&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&&span class=&p&&.&/span&&span class=&nx&&startWith&/span&&span class=&p&&(&/span&&span class=&s1&&'startup click'&/span&&span class=&p&&)&/span&
&span class=&p&&.&/span&&span class=&nx&&merge&/span&&span class=&p&&(&/span&&span class=&nx&&close1ClickStream&/span&&span class=&p&&)&/span& &span class=&c1&&// we added this&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'https://api.github.com/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&但这样做是不行的,因为他会刷新所有的推荐而不是我们点击的那个。其实有很多种方法可以解决这个问题,但是为了有趣一些,我们决定重用之前的 response stream 。还记得 API 返回的 page size 是 100 个用户,但我们只用了 3 个,因此我们还有新的可用的数据,不需要再请求一遍。&/p&&p&再说一遍,让我们 think in streams 。当「 close1 」的 click 事件触发后,我们想要的做的是:在 responseStream 最近(the most recently)emit 的值里面,随机一个出来:&/p&&br&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&
requestStream: --r---------------&
responseStream: ------R-----------&
close1ClickStream: ------------c-----&
suggestion1Stream: ------s-----s-----&
&/code&&/pre&&/div&&p&在 Rx* 中,有一个叫 &a href=&http://link.zhihu.com/?target=https%3A//github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md%23rxobservableprototypecombinelatestargs-resultselector& c}

我要回帖

更多关于 李隐从仓库拿到的东西 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信