-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
957 lines (692 loc) · 132 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" >
<title>MigicQ</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="description" content="慕慕珍珍,曹莅祥,caolixiang,caolixang33,angular2,Angular2,RxJS">
<meta name="keywords" content="慕慕珍珍,caolixiang,caolixiang33,曹莅祥,rxjs,angular,angular2,Angular,Angular2,rxjs教程,rxjs入门教程,rxjs入门,angular2教程,angular2入门教程,angular2入门">
<link rel="alternative" href="/atom.xml" title="MigicQ" type="application/atom+xml">
<link rel="icon" href="http://7xq0ve.com1.z0.glb.clouddn.com/favicon.ico">
<link rel="stylesheet" href="/css/style.css" type="text/css">
</head>
<body>
<div id="container">
<div class="left-col">
<div class="overlay"></div>
<div class="intrude-less">
<header id="header" class="inner">
<a href="/" class="profilepic">
<img lazy-src="http://7xq0ve.com1.z0.glb.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-14%20%E4%B8%8B%E5%8D%8811.46.55.png" class="js-avatar">
</a>
<hgroup>
<h1 class="header-author"><a href="/">慕慕珍珍</a></h1>
</hgroup>
<p class="header-subtitle">The winter is coming.</p>
<div class="switch-btn">
<div class="icon">
<div class="icon-ctn">
<div class="icon-wrap icon-house" data-idx="0">
<div class="birdhouse"></div>
<div class="birdhouse_holes"></div>
</div>
<div class="icon-wrap icon-ribbon hide" data-idx="1">
<div class="ribbon"></div>
</div>
</div>
</div>
<div class="tips-box hide">
<div class="tips-arrow"></div>
<ul class="tips-inner">
<li>菜单</li>
<li>标签</li>
</ul>
</div>
</div>
<div class="switch-area">
<div class="switch-wrap">
<section class="switch-part switch-part1">
<nav class="header-menu">
<ul>
<li><a href="/">主页</a></li>
<li><a href="/archives">所有文章</a></li>
</ul>
</nav>
<nav class="header-nav">
<div class="social">
<a class="weibo" target="_blank" href="http://weibo.com/caolixiang" title="weibo">weibo</a>
<a class="rss" target="_blank" href="/atom.xml" title="rss">rss</a>
<a class="mail" target="_blank" href="mailTo:caolixiang@gmail.com" title="mail">mail</a>
</div>
</nav>
</section>
<section class="switch-part switch-part2">
<div class="widget tagcloud" id="js-tagcloud">
<a href="/tags/Angular2/" style="font-size: 10px;">Angular2</a> <a href="/tags/Angularjs2/" style="font-size: 10px;">Angularjs2</a> <a href="/tags/Reactive/" style="font-size: 15px;">Reactive</a> <a href="/tags/RxJS/" style="font-size: 15px;">RxJS</a> <a href="/tags/javascript/" style="font-size: 20px;">javascript</a>
</div>
</section>
</div>
</div>
</header>
</div>
</div>
<div class="mid-col">
<nav id="mobile-nav">
<div class="overlay">
<div class="slider-trigger"></div>
<h1 class="header-author js-mobile-header hide">慕慕珍珍</h1>
</div>
<div class="intrude-less">
<header id="header" class="inner">
<div class="profilepic">
<img lazy-src="http://7xq0ve.com1.z0.glb.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-14%20%E4%B8%8B%E5%8D%8811.46.55.png" class="js-avatar">
</div>
<hgroup>
<h1 class="header-author">慕慕珍珍</h1>
</hgroup>
<p class="header-subtitle">The winter is coming.</p>
<nav class="header-menu">
<ul>
<li><a href="/">主页</a></li>
<li><a href="/archives">所有文章</a></li>
<div class="clearfix"></div>
</ul>
</nav>
<nav class="header-nav">
<div class="social">
<a class="weibo" target="_blank" href="http://weibo.com/caolixiang" title="weibo">weibo</a>
<a class="rss" target="_blank" href="/atom.xml" title="rss">rss</a>
<a class="mail" target="_blank" href="mailTo:caolixiang@gmail.com" title="mail">mail</a>
</div>
</nav>
</header>
</div>
</nav>
<div class="body-wrap">
<article id="post-rx-observable" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2016/05/04/rx-observable/" class="article-date">
<time datetime="2016-05-04T09:18:13.000Z" itemprop="datePublished">2016-05-04</time>
</a>
</div>
<div class="article-inner">
<input type="hidden" class="isFancy" />
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2016/05/04/rx-observable/">RxJs 核心概念之Observable</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>Observable(<strong>可观察对象</strong>)是基于推送(<strong>Push</strong>)运行时执行(<strong>lazy</strong>)的多值集合。下方表格对Observable进行了定位(<em>为解决基于推送的多值问题</em>):</p>
<table>
<thead>
<tr>
<th>MagicQ</th>
<th>单值</th>
<th>多值</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>拉取(Pull)</strong></td>
<td><a href="https://developer.mozilla.org/en-US/docs/Glossary/Function" target="_blank" rel="external"><code>函数</code></a></td>
<td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols" target="_blank" rel="external">遍历器</a></td>
</tr>
<tr>
<td><strong>推送(Push)</strong></td>
<td><a href="https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Promise" target="_blank" rel="external"><code>Promise</code></a></td>
<td><a href="/../class/es6/Observable.js~Observable.html"><code>Observable</code></a></td>
</tr>
</tbody>
</table>
<p><strong>例</strong>:当<code>observable</code>被订阅后,会立即(<em>同步地</em>)推送<code>1</code>, <code>2</code>, <code>3</code> 三个值;1秒之后,继续推送<code>4</code>这个值,最后结束(<em>推送结束通知</em>): </p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> (<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>);</span><br><span class="line"> observer.next(<span class="number">2</span>);</span><br><span class="line"> observer.next(<span class="number">3</span>);</span><br><span class="line"> setTimeout(() => {</span><br><span class="line"> observer.next(<span class="number">4</span>);</span><br><span class="line"> observer.complete();</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>为得到<code>observable</code>推送的值,我们需要订阅(<em>subscribe</em>)这个Observable:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> (<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>);</span><br><span class="line"> observer.next(<span class="number">2</span>);</span><br><span class="line"> observer.next(<span class="number">3</span>);</span><br><span class="line"> setTimeout(() => {</span><br><span class="line"> observer.next(<span class="number">4</span>);</span><br><span class="line"> observer.complete();</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'just before subscribe'</span>);</span><br><span class="line">observable.subscribe({</span><br><span class="line"> next: x => <span class="built_in">console</span>.log(<span class="string">'got value '</span> + x),</span><br><span class="line"> error: err => <span class="built_in">console</span>.error(<span class="string">'something wrong occurred: '</span> + err),</span><br><span class="line"> complete: () => <span class="built_in">console</span>.log(<span class="string">'done'</span>),</span><br><span class="line">});</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'just after subscribe'</span>);</span><br></pre></td></tr></table></figure></p>
<p>程序执行后,将在控制台输出如下结果:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> just before subscribe got value 1 got value 2 got value 3 just after subscribe got value 4 done</span><br></pre></td></tr></table></figure>
<h2 id="u62C9_u53D6_28Pull_29_V-S-__u63A8_u9001_28Push_29"><a href="#u62C9_u53D6_28Pull_29_V-S-__u63A8_u9001_28Push_29" class="headerlink" title="拉取(Pull) V.S. 推送(Push)"></a>拉取(Pull) V.S. 推送(Push)</h2><p><em>拉取</em>和<em>推送</em>是数据<em>生产者</em>和数据<em>消费者</em>之间通信的两种不同机制。</p>
<p><strong>何为拉取?</strong> 在拉取系统中,总是由消费者决定何时从生产者那里获得数据。生产者对数据传递给消费者的时间毫无感知(<em>被动的生产者,主动的消费者</em>)。</p>
<p>JavaScript函数是典型的拉取系统:函数是数据的生产者,对函数进行调用的代码(消费者)从函数调用后的返回值中拉取<em>单值</em>进行消费。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 函数是数据的生产者</span></span><br><span class="line"><span class="keyword">let</span> getLuckyNumber = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">7</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/* let代码段是数据的消费者,</span><br><span class="line"> * getLuckyNumber对调用时间毫无感知。 </span><br><span class="line"> */</span></span><br><span class="line"><span class="keyword">let</span> luckNumber = getLuckyNumber();</span><br></pre></td></tr></table></figure>
<p>ES2015 引入了的 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*" target="_blank" rel="external">生成器函数 | 遍历器</a> (<code>function*</code>)同样是基于拉取的系统: 调用 <code>iterator.next()</code>的代码段是消费者,它可以从生成器函数中拉取多个值。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">getLessThanTen</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span>(i < <span class="number">11</span>) {</span><br><span class="line"> <span class="keyword">yield</span> i++;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生产者</span></span><br><span class="line"><span class="keyword">let</span> iterator = getLessThanTen();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消费者</span></span><br><span class="line">iterator.next(); <span class="comment">// Object {value: 0, done: false}</span></span><br><span class="line">iterator.next(); <span class="comment">// Object {value: 1, done: false}</span></span><br></pre></td></tr></table></figure>
<table>
<thead>
<tr>
<th>MagicQ</th>
<th>生产者</th>
<th>消费者</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>拉取</strong></td>
<td><strong>被动:</strong> 在被请求时产生数据</td>
<td><strong>主动:</strong> 决定何时请求数据</td>
</tr>
<tr>
<td><strong>推送</strong></td>
<td><strong>主动:</strong> 控制数据的产生逻辑</td>
<td><strong>被动:</strong> 获得数据后进行响应</td>
</tr>
</tbody>
</table>
<p><strong>何为推送?</strong> 在推送系统中生产者决定何时向消费者传递数据,消费者对何时收到数据毫无感知(被动的消费者)。</p>
<p>现代JavaScript中<strong>Promise</strong>是典型的推送系统。作为数据生产者的Promise通过<code>resolve()</code>向数据消费者——回调函数传递数据:与函数不同,Promise决定向回调函数推送值的时间。</p>
<p>RxJS在 JavaScript 中引入了Observable(可观察对象)这个新的推送系统。Observable是多数据值的生产者,向Observer(被动的消费者)推送数据。</p>
<ul>
<li><strong>函数</strong> 调用后同步计算并返回单一值</li>
<li><strong>生成器函数 | 遍历器 </strong> 遍历过程中同步计算并返回0个到无穷多个值</li>
<li><strong>Promise</strong> 异步执行中返回或者不返回单一值</li>
<li><strong>Observable</strong> 同步或者异步计算并返回0个到无穷多个值</li>
</ul>
<h2 id="Observable__u662F_u51FD_u6570_u6982_u5FF5_u7684_u62D3_u5C55"><a href="#Observable__u662F_u51FD_u6570_u6982_u5FF5_u7684_u62D3_u5C55" class="headerlink" title="Observable 是函数概念的拓展"></a>Observable 是函数概念的拓展</h2><p>Observable既不像EventEmitter,也不像是Promise。Observable 中的 Subject 进行多路推送时与 EventEmitter <strong>行为上</strong>有些类似,但是实际上Observable与EventEmitter并不相同。</p>
<p>Observable 更像是一个不需要传入参数的函数,它拓展了函数的概念使得它可以返回多个值。</p>
<p>看看下面的例子:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Hello'</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">42</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = foo.call(); <span class="comment">// same as foo()</span></span><br><span class="line"><span class="built_in">console</span>.log(x);</span><br><span class="line"><span class="keyword">var</span> y = foo.call(); <span class="comment">// same as foo()</span></span><br><span class="line"><span class="built_in">console</span>.log(y);</span><br></pre></td></tr></table></figure>
<p>输出结果如下:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> "Hello" 42 "Hello" 42</span><br></pre></td></tr></table></figure>
<p>通过Observable可以实现同样的行为:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = Rx.Observable.create(<span class="function"><span class="keyword">function</span> (<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Hello'</span>);</span><br><span class="line"> observer.next(<span class="number">42</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">foo.subscribe(<span class="function"><span class="keyword">function</span> (<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line">});</span><br><span class="line">foo.subscribe(<span class="function"><span class="keyword">function</span> (<span class="params">y</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(y);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>输出结果相同:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> "Hello" 42 "Hello" 42</span><br></pre></td></tr></table></figure>
<p>不论Observable还是函数都是在运行时进行求值计算的。如果不调用函数,<code>console.log('Hello')</code>就不会执行;如果如果不<code>subscribe</code>(订阅)Observable,<code>console.log('Hello')</code>也不会执行。此外,<strong>调用</strong>或者<strong>订阅</strong>都是独立的:两次调用产生两个独立的作用域,两次订阅同样会产生两个独立的作用域。EventEmitter总是在同一个作用域中,发射前也不会在意自己是否已经被订阅;Observable不会被共享而产生副作用,并且总是在被订阅时才执行。</p>
<p>订阅Observable与调用函数类似。</p>
<p>一些人认为Observable总是是异步的,这个观点并不正确,如果在控制台log函数中调用函数:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'before'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(foo.call());</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'after'</span>);</span><br></pre></td></tr></table></figure>
<p>显然可以看到以下输出:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> "before" "Hello" 42 "after"</span><br></pre></td></tr></table></figure>
<p>Observable的行为完全一样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'before'</span>);</span><br><span class="line">foo.subscribe(<span class="function"><span class="keyword">function</span> (<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line">});</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'after'</span>);</span><br></pre></td></tr></table></figure>
<p>输出结果为:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> "before" "Hello" 42 "after"</span><br></pre></td></tr></table></figure>
<p>订阅 <code>foo</code>完全是同步的,与函数的调用一样。</p>
<p>Observable可以异步或者同步地产生数据。</p>
<p>那Observable 与函数的不同之处在哪里? <strong>Observable可以在一个时间过程中‘返回’多个值</strong>,而函数却不能。在函数中你不可以这么做:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Hello'</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">42</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">100</span>; <span class="comment">// 这个语句永远不会被执行。</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>虽然函数只能有一个返回值,但是在Observable中你完全可以这么做:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = Rx.Observable.create(<span class="function"><span class="keyword">function</span> (<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Hello'</span>);</span><br><span class="line"> observer.next(<span class="number">42</span>);</span><br><span class="line"> observer.next(<span class="number">100</span>); <span class="comment">// 返回另一个值</span></span><br><span class="line"> observer.next(<span class="number">200</span>); <span class="comment">// 返回另一个值</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'before'</span>);</span><br><span class="line">foo.subscribe(<span class="function"><span class="keyword">function</span> (<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line">});</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'after'</span>);</span><br></pre></td></tr></table></figure>
<p>输出结果如下:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> "before" "Hello" 42 100 200 "after"</span><br></pre></td></tr></table></figure>
<p>你甚至可以异步地返回值:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = Rx.Observable.create(<span class="function"><span class="keyword">function</span> (<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Hello'</span>);</span><br><span class="line"> observer.next(<span class="number">42</span>);</span><br><span class="line"> observer.next(<span class="number">100</span>);</span><br><span class="line"> observer.next(<span class="number">200</span>);</span><br><span class="line"> setTimeout(() => {</span><br><span class="line"> observer.next(<span class="number">300</span>); <span class="comment">// happens asynchronously</span></span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'before'</span>);</span><br><span class="line">foo.subscribe(<span class="function"><span class="keyword">function</span> (<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line">});</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'after'</span>);</span><br></pre></td></tr></table></figure>
<p>输出结果:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"> "before" "Hello" 42 100 200 "after" 300</span><br></pre></td></tr></table></figure>
<p>结论:</p>
<ul>
<li><code>func.call()</code> 意味着“同步地给我一个值”</li>
<li><code>observable.subscribe()</code> 意味着“不管是同步或者异步,给我一些值”</li>
</ul>
<h2 id="Observable__u5256_u6790"><a href="#Observable__u5256_u6790" class="headerlink" title="Observable 剖析"></a>Observable 剖析</h2><p>通过使用 <code>Rx.Observable.create</code> 或者是<em>创建操作符</em>,<strong>创建</strong>一个Observable; Observable 被 Observer(观察者) <strong>订阅</strong>; 在<strong>执行</strong>时 向观察者发送<code>next</code> / <code>error</code> / <code>complete</code> 通知;同时执行过程可以被 <strong>终止</strong>。<br>Observable 类型的实例具备了以上四个方面的特性,与其他类型如:Observer 和 Subscription 紧密相关。</p>
<p>我们重点关注以下四个方面:</p>
<ul>
<li><strong>创建</strong></li>
<li><strong>订阅</strong></li>
<li><strong>执行</strong></li>
<li><strong>终止</strong></li>
</ul>
<h3 id="u521B_u5EFA"><a href="#u521B_u5EFA" class="headerlink" title="创建"></a>创建</h3><p><code>Rx.Observable.create</code> 是 <code>Observable</code> 构造函数的别名,接受一个参数: <code>subscribe</code>函数。</p>
<p>以下例子会创建一个Observable,每一秒钟向其订阅者发射一个<code>'hi'</code> 字符串。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> id = setInterval(() => {</span><br><span class="line"> observer.next(<span class="string">'hi'</span>)</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>除了使用<code>create</code>创建Observable,我们通常还使用<a href="/./overview.html#creation-operators">创建操作符</a>, 如 <code>of</code>,<code>from</code>, <code>interval</code>, 等来创建Observable。</p>
<p>上面例子中,<code>subscribe</code>函数是定义Observable最重要的部分。我们接下来了解订阅的含义。</p>
<h3 id="u8BA2_u9605"><a href="#u8BA2_u9605" class="headerlink" title="订阅"></a>订阅</h3><p>上面例子中的<code>observable</code> 可以以如下方式 <em>订阅</em> :</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">observable.subscribe(x => <span class="built_in">console</span>.log(x));</span><br></pre></td></tr></table></figure>
<p><code>observable.subscribe</code> 和 <code>Observable.create(function subscribe(observer) {...})</code>中的<code>subscribe</code> 同名并非巧合。虽然在Rx中它们不是同一个对象,但是在工程中,我们可以在概念上视两者为等价物。</p>
<p>调用<code>subscribe</code>的观察者并不会共享同一个Observable。观察者调用<code>observable.subscribe</code> 时,<code>Observable.create(function subscribe(observer) {...})</code>中的<code>subscribe</code>会在调用它的观察者作用域中执行。每一次<code>observable.subscribe</code>的调用,都是彼此独立的。</p>
<p>订阅Observable如同调用函数,需要提供相应的回调方法。</p>
<p>订阅机制与处理事件的<code>addEventListener</code> / <code>removeEventListener</code>API完全不同。通过<code>observable.subscribe</code>,观察者并不需要在Observable中进行注册,Observable也不需要维护订阅者的列表。</p>
<p>订阅后便进入了Observable的执行阶段,在执行阶段值和事件将会被传递给观察者供其消费。</p>
<h3 id="u6267_u884C"><a href="#u6267_u884C" class="headerlink" title="执行"></a>执行</h3><p>只有在被订阅之后Observable才会执行,执行的逻辑在<code>Observable.create(function subscribe(observer) {...})</code>中描述,执行后将会在特定时间段内,同步或者异步地成产多个数据值。</p>
<p>Observable在执行过程中,可以推送三种类型的值:</p>
<ul>
<li>“Next” 通知: 实际产生的数据,包括数字、字符串、对象等</li>
<li>“Error” 通知:一个JavaScript错误或者异常</li>
<li>“Complete” 通知:一个不带有值的事件</li>
</ul>
<p>“Next” 通知是最重要和常用的类型:表示事件传递给观察者的数据。错误和完成通知仅会在执行阶段推送其一,并不会同时推送错误和完成通知。</p>
<p>通过所谓的“Observable语法”或者“契约”可以最好地表达这个规则,“Observable语法”借助于正则表达式:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">next*(error|complete)?</span><br></pre></td></tr></table></figure>
<p>在Observable的执行过程中,0个或者多个“Next”通知会被推送。在错误或者完成通知被推送后,Observable不会再推送任何其他通知。</p>
<p>下面代码展示了Observable 在执行过程中推送3个“Next” 通知然后结束:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>);</span><br><span class="line"> observer.next(<span class="number">2</span>);</span><br><span class="line"> observer.next(<span class="number">3</span>);</span><br><span class="line"> observer.complete();</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>Observable 严格遵守 Observable 契约,后面值为<code>4</code>的“Next” 通知永远不会被推送:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>);</span><br><span class="line"> observer.next(<span class="number">2</span>);</span><br><span class="line"> observer.next(<span class="number">3</span>);</span><br><span class="line"> observer.complete();</span><br><span class="line"> observer.next(<span class="number">4</span>); <span class="comment">// 由于违法契约,4不会被推送</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>使用<code>try</code>/<code>catch</code>块包裹 <code>subscribe</code> 代码是一个很赞的想法,如果捕获了异常,可以推送错误通知:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> observer.next(<span class="number">1</span>);</span><br><span class="line"> observer.next(<span class="number">2</span>);</span><br><span class="line"> observer.next(<span class="number">3</span>);</span><br><span class="line"> observer.complete();</span><br><span class="line"> } <span class="keyword">catch</span> (err) {</span><br><span class="line"> observer.error(err); <span class="comment">// 捕获异常后推送错误通知</span></span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h3 id="u7EC8_u6B62"><a href="#u7EC8_u6B62" class="headerlink" title="终止"></a>终止</h3><p>Observable的执行可能是无限的,作为观察者需要主动中断执行:我们需要特定的API去终止执行过程。因为特定的观察者都有特定的执行过程,一旦观察者获得想要的数据后就需要终止执行过程以免带来计算时对内存资源的浪费。</p>
<p>在<code>observable.subscribe</code>被调用时,观察者会与其执行作用域绑定,同时返回一个<code>Subscription</code>类型的对象:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> subscription = observable.subscribe(x => <span class="built_in">console</span>.log(x));</span><br></pre></td></tr></table></figure>
<p>Subscription对象表示执行过程,通过极简的API,你可以终止执行过程。详情请阅读<a href="/./overview.html#subscription"><code>Subscription</code> 相关文档</a>。通过调用<code>subscription.unsubscribe()</code> 你可以终止执行过程:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.from([<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>]);</span><br><span class="line"><span class="keyword">var</span> subscription = observable.subscribe(x => <span class="built_in">console</span>.log(x));</span><br><span class="line"><span class="comment">// Later:</span></span><br><span class="line">subscription.unsubscribe();</span><br></pre></td></tr></table></figure>
<p>在Observable被订阅后,代表执行过程的Subscription 对象将被返回。对其调用<code>unsubscribe()</code>就可以终止执行。</p>
<p>每一个Observable都需要在 <code>create()</code>的创建过程中定义终止的逻辑。在<code>function subscribe()</code>中返回自定义的<code>unsubscribe</code>就可以实现。</p>
<p>下面的例子说明了如何在终止后释放<code>setInterval</code>的句柄:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> observable = Rx.Observable.create(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="comment">// 获得定时函数的句柄</span></span><br><span class="line"> <span class="keyword">var</span> intervalID = setInterval(() => {</span><br><span class="line"> observer.next(<span class="string">'hi'</span>);</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 提供终止方法释放定时函数的句柄</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">unsubscribe</span>(<span class="params"></span>) </span>{</span><br><span class="line"> clearInterval(intervalID);</span><br><span class="line"> };</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>类似于 <code>observable.subscribe</code> 和 <code>Observable.create(function subscribe() {...})</code>的关系,我们在<code>subscribe</code>中返回的 <code>unsubscribe</code> 也与<code>subscription.unsubscribe</code>在概念上等价。事实上,如果我们除去Rx的包装,纯粹的JavaScript代码简单清晰:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> intervalID = setInterval(() => {</span><br><span class="line"> observer.next(<span class="string">'hi'</span>);</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">unsubscribe</span>(<span class="params"></span>) </span>{</span><br><span class="line"> clearInterval(intervalID);</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> unsubscribe = subscribe({next: (x) => <span class="built_in">console</span>.log(x)});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一段时间后:</span></span><br><span class="line">unsubscribe(); <span class="comment">// 终止</span></span><br></pre></td></tr></table></figure>
<p>使用Observable、 Observer 和 Subscription这些概念的原因是,我们可以在Observable 契约之下安全、兼容地调用操作符。</p>
</div>
<div class="article-info article-info-index">
<div class="article-tag tagcloud">
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Reactive/">Reactive</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/RxJS/">RxJS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/javascript/">javascript</a></li></ul>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
<article id="post-rx-api-combine-latest" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2016/01/22/rx-api-combine-latest/" class="article-date">
<time datetime="2016-01-22T06:38:47.000Z" itemprop="datePublished">2016-01-22</time>
</a>
</div>
<div class="article-inner">
<input type="hidden" class="isFancy" />
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2016/01/22/rx-api-combine-latest/">RxJS API解析(四)</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h3 id="Rx*_28Observable-combineLatest_29_u65B9_u6CD5"><a href="#Rx*_28Observable-combineLatest_29_u65B9_u6CD5" class="headerlink" title="Rx*(Observable.combineLatest)方法"></a>Rx*(Observable.combineLatest)方法</h3><h4 id="u65B9_u6CD5_u5B9A_u4E49"><a href="#u65B9_u6CD5_u5B9A_u4E49" class="headerlink" title="方法定义"></a>方法定义</h4><p><code>Rx.Observable.combineLatest(...args, [resultSelector])</code></p>
<h4 id="u4F5C_u7528"><a href="#u4F5C_u7528" class="headerlink" title="作用"></a>作用</h4><p>通过<strong>处理函数</strong>总是将指定的<strong>可观察对象序列</strong>中<strong>最新</strong>发射的值合并为一个<strong>可观察对象</strong>。</p>
<h4 id="u53C2_u6570"><a href="#u53C2_u6570" class="headerlink" title="参数"></a>参数</h4><ol>
<li><code>args</code> <em>(arguments | Array)</em>: 一系列可观察对象或可观察对象的数组。</li>
<li><code>[resultSelector]</code> <em>(<code>Function</code>)</em>: 在所有可观察对象都发射值后调用的<strong>处理函数</strong>。</li>
</ol>
<h4 id="u8FD4_u56DE_u503C"><a href="#u8FD4_u56DE_u503C" class="headerlink" title="返回值"></a>返回值</h4><p><em>(<code>Observable</code>)</em>: 由传入的可观察序列经过<strong>处理函数</strong>合并后的结果组成的可观察序列。</p>
<h4 id="u5B9D_u73E0_u56FE"><a href="#u5B9D_u73E0_u56FE" class="headerlink" title="宝珠图"></a>宝珠图</h4><p><img src="http://reactivex.io/documentation/operators/images/combineLatest.png" alt="combineLatest"></p>
<p><code>Observable.combineLastest()</code>函数,总是合并序列中<strong>最新</strong>发射的值。宝珠图中的颜色球发射<strong>颜色</strong>,空白的图形发射<strong>待染色图形</strong>,处理函数对<strong>待染色对象</strong>进行染色:总是用户最新发射的<strong>颜色</strong>或者对最新发射的<strong>待染色对象</strong>。</p>
<p>假设<strong>颜色</strong>序列仅发射了第一个宝珠<strong>浅紫色</strong>且后续不再发射,那么结果街将会是一个由<strong>浅紫色</strong>组成的<strong>染色后对象</strong>的序列。</p>
<p>使用<a href="http://reactivex.io/documentation/operators/combinelatest.html" target="_blank" rel="external">官方可拖动宝珠图</a>,可以帮助理解,拖动序列中的宝珠,观察输出序列的变化。</p>
<h3 id="u5B9E_u4F8B"><a href="#u5B9E_u4F8B" class="headerlink" title="实例"></a>实例</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> colors = [<span class="string">"紫色"</span>,<span class="string">"黄色"</span>,<span class="string">"蓝色"</span>,<span class="string">"黑色"</span>];</span><br><span class="line"><span class="keyword">var</span> shapes = [<span class="string">"小星星"</span>,<span class="string">"圆形"</span>,<span class="string">"三角形"</span>,<span class="string">"正方形"</span>,<span class="string">"心形"</span>,<span class="string">"五边形"</span>];</span><br><span class="line"><span class="keyword">var</span> source1 = Rx.Observable.interval(<span class="number">3000</span>)</span><br><span class="line"> .map(()=>colors.pop());</span><br><span class="line"><span class="keyword">var</span> source2 = Rx.Observable.interval(<span class="number">2000</span>)</span><br><span class="line"> .map(()=>shapes.pop());</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> combined = Rx.Observable.combineLatest(source1, source2, <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> x + <span class="string">"的"</span> + y;</span><br><span class="line">}).take(<span class="number">8</span>);</span><br><span class="line"></span><br><span class="line">combined.subscribe((shaped)=><span class="built_in">console</span>.log(shaped));</span><br></pre></td></tr></table></figure>
<p>实例模拟第一个<strong>宝珠图</strong>,<a href="http://jsbin.com/joyuze/edit?js,console" target="_blank" rel="external">点击进入可运行实例</a>。其中列1发射颜色值,序列2发射形状。结果输出染色后的形状:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"黑色的五边形"</span></span><br><span class="line"><span class="string">"黑色的心形"</span></span><br><span class="line"><span class="string">"蓝色的心形"</span></span><br><span class="line"><span class="string">"蓝色的正方形"</span></span><br><span class="line"><span class="string">"蓝色的三角形"</span></span><br><span class="line"><span class="string">"黄色的三角形"</span></span><br><span class="line"><span class="string">"黄色的圆形"</span></span><br><span class="line"><span class="string">"紫色的圆形"</span></span><br></pre></td></tr></table></figure></p>
<p>还有一个<strong>非常好的</strong>实例在<a href="http://segmentfault.com/a/1190000004293922" target="_blank" rel="external">前面的文章</a>中,是<code>combineLatest()</code>在缓存数据方面的应用,如果你想深入理解<code>combineLatest()</code>不妨看一下。</p>
<h3 id="u9898_u5916_u8BDD"><a href="#u9898_u5916_u8BDD" class="headerlink" title="题外话"></a>题外话</h3><p>写这个专题的时候,对<code>Rx</code>的抽象能力赞叹不已。</p>
<p>大家通常把编写一个框架的工作称作“造轮子”。<br>“轮子”是一个针对某一类问题的解决方案,通常是由于反复解决某一个工程问题而产生的。某种程度上,轮子可以一劳永逸,同时轮子的使用可以大大地提高生产的效率(试想想你在使用如<code>Rails</code>这类有 <strong>ORM</strong>特性框架时的感受)。</p>
<p><code>Rx</code>似乎从另一个方面而不是实际问题进行抽象——<strong>数学</strong>,是一个函数式编程模式。从数学而不是工程作为起点,创造的工具的威力<em>可能</em>更强大,但是学习成本(使用成本)<em>可能</em>会更高。</p>
<p>任何<strong>程序设计语言</strong>在讲解<strong>递归</strong>特性时,基本都会举<code>汉诺塔</code>、<code>斐波拉契数列</code>的例子。没错,请你对比一下<code>斐波拉契数列</code>和<code>combineLatest()</code>定义的相似之处:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fibo</span><span class="params">(i)</span>:</span> </span><br><span class="line"> <span class="keyword">if</span> i==<span class="number">0</span> <span class="keyword">or</span> i==<span class="number">1</span>: </span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> </span><br><span class="line"> <span class="keyword">else</span>: </span><br><span class="line"> <span class="keyword">return</span> fibo(i-<span class="number">1</span>)+fibo(i-<span class="number">2</span>)</span><br></pre></td></tr></table></figure>
<p>Oops!递归完成后产生值的过程就是<code>combineLatest()</code>的过程。</p>
<p>在学习<code>Rx</code>的操作符时,请反复地理解操作符的作用、限制。最好的理解方法是构建一个场景。</p>
<p>在<code>combineLatest()</code>中,我们不妨将场景限定为拥有<code>两个可观察对象的可观察序列</code>,并且<code>对象A</code>总是较低频率地发射新值,而<code>对象B</code>比较频繁地发射:</p>
<figure class="highlight brainfuck"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">A</span> <span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">*</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">*</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span>></span><br><span class="line"><span class="comment">B</span> <span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">@</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">@</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">@</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">@</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">@</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="comment">@</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span><span class="literal">-</span>></span><br></pre></td></tr></table></figure>
<p>那么<code>对象A</code>在实际中可能是什么?缓存后的<code>http</code>请求后的数据、异步获取的配置文件…<br><code>对象B</code>自然可以是,与服务器的实时同步、用户上传图片的实时上传、用户在列表中执行的翻页操作…</p>
<p><a href="http://segmentfault.com/a/1190000004293922" target="_blank" rel="external">前面的文章</a>中缓存Github用户的就是上面提到的场景。</p>
<p><em>剧终</em></p>
</div>
<div class="article-info article-info-index">
<div class="article-tag tagcloud">
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Reactive/">Reactive</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/RxJS/">RxJS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/javascript/">javascript</a></li></ul>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
<article id="post-angular2-core-concept" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2016/01/19/angular2-core-concept/" class="article-date">
<time datetime="2016-01-19T01:22:30.000Z" itemprop="datePublished">2016-01-19</time>
</a>
</div>
<div class="article-inner">
<input type="hidden" class="isFancy" />
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2016/01/19/angular2-core-concept/">Angular2核心概念</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<blockquote>
<p>原文地址: <a href="http://victorsavkin.com/post/118372404541/the-core-concepts-of-angular-2" target="_blank" rel="external"><em>THE CORE CONCEPTS OF ANGULAR 2</em></a></p>
</blockquote>
<p>这篇博客中,我们将介绍Angular 2的三个核心概念:<strong>组件化</strong>,<strong>依赖注入</strong>,<strong>绑定</strong>。</p>
<blockquote>
<p><em>最后更新:2016-01-04</em></p>
</blockquote>
<h3 id="u514D_u8D23_u58F0_u660E"><a href="#u514D_u8D23_u58F0_u660E" class="headerlink" title="免责声明"></a>免责声明</h3><blockquote>
<p>Angular 2仍然在开发之中,核心概念不会随之变化,但是相关API可能会随着项目的开发进程发生改变:如果发现博文中某些代码片段无法运行了,请告知我,我会进行更新。</p>
</blockquote>
<hr>
<h3 id="u5F00_u59CB_u6784_u5EFAAPP_u5427"><a href="#u5F00_u59CB_u6784_u5EFAAPP_u5427" class="headerlink" title="开始构建APP吧"></a>开始构建APP吧</h3><p>现在我们描述一下将要构建的应用:它包含了一个科技讲座的列表,你可以通过讲师<code>speaker</code>进行筛选,观看讲座或对讲座进行评分,应用Demo如下:</p>
<p><img src="http://40.media.tumblr.com/fd37a5ff9a41cf25ed57236092b708c6/tumblr_nnzn556v7F1qc0howo1_500.png" alt="appWeWillBuild"></p>
<hr>
<h3 id="u7EC4_u4EF6"><a href="#u7EC4_u4EF6" class="headerlink" title="组件"></a>组件</h3><p>你需要定义UI、路由等一系列组件去构建一个Augular 2应用。一个应用中总是存在一个根(主)组件,根组件中包含了其他组件。简而言之,每一个Angular 2应用都有一棵对应的<strong>组件树</strong>。我们应用的组件树看起来是这样的:</p>
<p><img src="http://40.media.tumblr.com/4f7c8180305cd3641ebc52eb1a628ed0/tumblr_nnzn556v7F1qc0howo2_1280.png" alt="ourApp'sComponentsTree"></p>
<p><code>Application</code>是<strong>根组件</strong>;<code>Filters组件</code>包含一个演讲者<code>speaker</code>姓名输入框和筛选按钮;<code>TalkList</code>是你在Demo中看到的讲座列表;<code>TalkCmp</code>是讲座列表中的一个元素(一个讲座)。</p>
<p>为了理解Angular 2中组件的构成,我们先研究一下<code>TalkCmp</code>:</p>
<blockquote>
<p>TalkCmp.ts:</p>
</blockquote>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'talk-cmp'</span>,</span><br><span class="line"> directives: [FormattedRating, WatchButton, RateButton],</span><br><span class="line"> templateUrl: <span class="string">'talk_cmp.html'</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">class</span> TalkCmp {</span><br><span class="line"> @Input() talk: Talk;</span><br><span class="line"> @Output() rate: EventEmitter;</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>talk_cmp.html</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{{talk.title}}</span><br><span class="line">{{talk.speaker}}</span><br><span class="line"><span class="tag"><<span class="title">formatted-rating</span> [<span class="attribute">rating</span>]=<span class="value">"talk.rating"</span>></span><span class="tag"></<span class="title">formatted-rating</span>></span></span><br><span class="line"><span class="tag"><<span class="title">watch-button</span> [<span class="attribute">talk</span>]=<span class="value">"talk"</span>></span><span class="tag"></<span class="title">watch-button</span>></span></span><br><span class="line"><span class="tag"><<span class="title">rate-button</span> [<span class="attribute">talk</span>]=<span class="value">"talk"</span>></span><span class="tag"></<span class="title">rate-button</span>></span></span><br></pre></td></tr></table></figure>
<h4 id="INPUT__26amp_3B_OUTPUT__u5C5E_u6027"><a href="#INPUT__26amp_3B_OUTPUT__u5C5E_u6027" class="headerlink" title="INPUT & OUTPUT 属性"></a>INPUT & OUTPUT 属性</h4><p>每一个组件都拥有<code>input</code>、<code>output</code> 属性,我们可以在组件中通过属性装饰符语法定义这些属性。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">class</span> TalkCmp {</span><br><span class="line"> @Input() talk: Talk;</span><br><span class="line"> @Output() rate: EventEmitter;</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">}</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>通过<code>input</code>属性,数据可以流入到组件中;通过<code>output</code>属性,数据可以从组件中流出。</p>
<p><img src="http://40.media.tumblr.com/8d2360fe8f3f0c66b20cb5dcc45856ce/tumblr_nnzn556v7F1qc0howo3_1280.png" alt="flows in and out"></p>
<p><code>Input</code> 和 <code>output</code> 是组件提供的公共API,在应用中初始化组件时你可以使用它们。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="title">talk-cmp</span> [<span class="attribute">talk</span>]=<span class="value">"someExp"</span> (<span class="attribute">rate</span>)=<span class="value">"eventHandler($event.rating)"</span>></span><span class="tag"></<span class="title">talk-cmp</span>></span></span><br></pre></td></tr></table></figure>
<p>通过属性绑定(使用方括号语法),你可以设置<code>input</code>属性的值;通过事件绑定,(使用圆括号语法),你可以绑定<code>output</code>属性。</p>
<p>每一个组件总是有一个模板与之对应,模板定义了一个组件在页面中的渲染方式。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'talk-cmp'</span>,</span><br><span class="line"> directives: [FormattedRating, WatchButton, RateButton],</span><br><span class="line"> templateUrl: <span class="string">'talk_cmp.html'</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<blockquote>
<p>talk_cmp.html</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{{talk.title}}</span><br><span class="line">{{talk.speaker}}</span><br><span class="line"><span class="tag"><<span class="title">formatted-rating</span> [<span class="attribute">rating</span>]=<span class="value">"talk.rating"</span>></span><span class="tag"></<span class="title">formatted-rating</span>></span></span><br><span class="line"><span class="tag"><<span class="title">watch-button</span> [<span class="attribute">talk</span>]=<span class="value">"talk"</span>></span><span class="tag"></<span class="title">watch-button</span>></span></span><br><span class="line"><span class="tag"><<span class="title">rate-button</span> [<span class="attribute">talk</span>]=<span class="value">"talk"</span>></span><span class="tag"></<span class="title">rate-button</span>></span></span><br></pre></td></tr></table></figure>
<p>为进行渲染,Angular需要事先知道:渲染中可以使用哪些<code>directives</code>?使用什么样的模板?你可以用<code>templateUrl</code>把模板文件定义在外部文件中,或者使用内联的方式像下面这样进行定义:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">@Component({</span><br><span class="line"> selector: 'talk-cmp',</span><br><span class="line"> directives: [FormattedRating, WatchButton, RateButton],</span><br><span class="line"> template: `</span><br><span class="line"> {{talk.title}}</span><br><span class="line"> {{talk.speaker}}</span><br><span class="line"> <formatted-rating [rating]="talk.rating"></formatted-rating></span><br><span class="line"> <watch-button [talk]="talk"></watch-button></span><br><span class="line"> <rate-button [talk]="talk"></rate-button></span><br><span class="line"> `</span><br><span class="line">})</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<h4 id="u751F_u547D_u5468_u671F"><a href="#u751F_u547D_u5468_u671F" class="headerlink" title="生命周期"></a>生命周期</h4><p>Angular 2为组件定义了完整的生命周期,你可以在组件各个生命周期中进行介入。在<code>TalkCmp</code>组件中,我们没有订阅其生命周期中的事件,这并不代表其他组件不能。下面例子中的组件会在<code>input</code>属性变化时收到通知。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">@Component({selector: <span class="string">'cares-about-changes'</span>})</span><br><span class="line"><span class="keyword">class</span> CareAboutChanges {</span><br><span class="line"> @Input() field1;</span><br><span class="line"> @Input() field2;</span><br><span class="line"> onChange(changes) {</span><br><span class="line"> <span class="comment">//..</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="u670D_u52A1_u63D0_u4F9B_u8005"><a href="#u670D_u52A1_u63D0_u4F9B_u8005" class="headerlink" title="服务提供者"></a>服务提供者</h4><p>一个组件可以包含一系列服务提供者,其子组件也可以使用这些服务。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'conf-app'</span>,</span><br><span class="line"> providers: [ConfAppBackend, Logger]</span><br><span class="line">})</span><br><span class="line"><span class="keyword">class</span> TalksApp {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> TalksCmp {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>(backend:ConfAppBackend) </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的例子中,我们在<strong>根组件</strong>中声明了后端服务(即服务器通信)和日志服务,这样在应用中我们都可以使用这些服务。接下来<code>talksCmp</code>组件注入后端服务(因为<code>ConfAppBackend</code>在<strong>根组件</strong>中做过声明)。<br>我们会在本文的第二部分详细地介绍<strong>依赖注入</strong>,这里我们只用了解:通过组件进行<strong>依赖注入</strong>的设置。</p>
<h4 id="u5BBF_u4E3B_u5143_u7D20"><a href="#u5BBF_u4E3B_u5143_u7D20" class="headerlink" title="宿主元素"></a>宿主元素</h4><p>为了将Angular组件渲染成DOM树,需要将Angular组件与一个DOM元素相关联,我们把这样的DOM元素称为:<strong>宿主元素</strong>。</p>
<p>组件可以与宿主元素进行如下方式的交互:</p>
<ul>
<li>监听宿主元素事件</li>
<li>更改宿主元素属性</li>
<li>调用宿主元素方法</li>
</ul>
<p>下面这个组件中,通过<code>HostListener</code>监听宿主元素的输入事件,然后去除输入值两端的空格,并将其存储。Angular 会时时保持DOM元素与存储值的一致性。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@Component({selector: <span class="string">'trimmed-input'</span>})</span><br><span class="line"><span class="keyword">class</span> TrimmedInput {</span><br><span class="line"> @HostBinding() value: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> @HostListener(<span class="string">"input"</span>, <span class="string">"$event.target.value"</span>)</span><br><span class="line"> onChange(updatedValue: <span class="built_in">string</span>) {</span><br><span class="line"> <span class="keyword">this</span>.value = updatedValue.trim();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>请注意</strong>,上面的例子中我对DOM元素进行操作。Angular 2致力于提供更高层面的抽象接口:我们可以将Angular 2应用<strong>映射</strong>到<strong>原生平台</strong>(比如iOS、Android)或者<strong>浏览器中</strong>。</p>
<p>这个理念极为重要,因为:</p>
<ul>
<li>我们可以更为方便地重构应用<code>(与DOM解耦)</code></li>
<li>可以脱离DOM进行大多数单元测试。测试脚本会变得更加利于理解和编写,测试效率也会显著提升</li>
<li>可以在<strong>web worker</strong>中运行Angular 2应用</li>
<li>可以脱离浏览器环境,例如使用<code>NativeScript</code>可以在<code>iOS</code>、<code>Android</code>平台运行Angular 2应用</li>
</ul>
<p>但是有些时候,我们还是需要和DOM直接打交道。Angular 2 提供了这类接口,不过我们希望你能尽可能少地使用它们。 </p>
<h4 id="u7EC4_u4EF6_u662F_u5B9A_u4E49_u5B8C_u5907_u7684"><a href="#u7EC4_u4EF6_u662F_u5B9A_u4E49_u5B8C_u5907_u7684" class="headerlink" title="组件是定义完备的"></a>组件是定义完备的</h4><p>组件由下面这些部分构成:</p>
<ul>
<li>组件知道如何与宿主元素进行交互</li>
<li>组件知道如何对自身进行渲染</li>
<li>组件可以进行<strong>依赖注入</strong>的设置</li>
<li>组件有定义<code>inputs</code>和<code>outputs</code>属性的接口</li>
</ul>
<p>这些部分让Angular 2元素具备了自我完备定义的能力,我们可以独立地初始化一个组件,因为<strong>组件是定义完备的</strong>。</p>
<p>任意组件都可以<code>bootstrap</code>一个应用;任意组件都可以绑定在特定路由之上并渲染。任意组件可以被其他组件直接使用。虽然我们定义的接口更少了,的但是带来了高可复用性。</p>
<h4 id="u90A3_u4E48DIRECTIVES_u5462_3F"><a href="#u90A3_u4E48DIRECTIVES_u5462_3F" class="headerlink" title="那么DIRECTIVES呢?"></a>那么<code>DIRECTIVES</code>呢?</h4><p>如果你熟悉Angular 1,你一定会问:“<code>directives</code>去哪里了?”</p>
<p>其实,<code>directives</code>一直都在。组件是最为重要的<code>directives</code>,但并不是唯一的<code>directives</code>。组件是具有模板的<code>directive</code>,你可以使用装饰器语法来定义没有模板的<code>directive</code>。</p>
<p><img src="http://40.media.tumblr.com/a35e02840c12d0945d16ed0087e23974/tumblr_nnzn556v7F1qc0howo4_400.png" alt="componentsAndDirectives"></p>
<h4 id="u5C0F_u7ED3"><a href="#u5C0F_u7ED3" class="headerlink" title="小结"></a>小结</h4><p>组件是构建 Angular 2应用的基础:</p>
<ul>
<li>它们有定义<code>inputs</code>和<code>outputs</code>属性的接口</li>
<li>有完整的生命周期</li>
<li>是定义完备的</li>
</ul>
<hr>
<h3 id="u4F9D_u8D56_u6CE8_u5165"><a href="#u4F9D_u8D56_u6CE8_u5165" class="headerlink" title="依赖注入"></a>依赖注入</h3><p>现在讨论 Angular 的另一个重要基石——依赖注入。</p>
<p>依赖注入背后的思想很简单:如果一个组件依赖一项服务,那么组件不应该去直接生成这个服务实例。通过在构造方法<code>constructor</code>中注入,框架(指 Angular 2的 <code>DI</code> 框架)会把服务提供给你。<strong>面向接口编程而非实现进行编程</strong>,可以<strong>降低代码的耦合度</strong>,提高<strong>可测试性</strong>(比如<code>mocking</code>数据),带来诸多其他好处。</p>
<p><img src="http://41.media.tumblr.com/c5b2e9096e2b877c856ef549ad211ac0/tumblr_nnzn556v7F1qc0howo5_1280.png" alt="DI"></p>
<p>Angular 2与生俱来拥有一个依赖注入模块(当然该模块可以脱离Angular 2与其他库结合使用)。我们试着从下面的组件了解如何进行依赖注入。这个组件会渲染讲座列表。 </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'talk-list'</span>,</span><br><span class="line"> templateUrl: <span class="string">'talks.html'</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">class</span> TalkList {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>() </span>{</span><br><span class="line"> <span class="comment">//..获取数据</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>talks.html</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="title">h2</span>></span>Talks:<span class="tag"></<span class="title">h2</span>></span></span><br><span class="line"><span class="tag"><<span class="title">div</span> *<span class="attribute">ngFor</span>=<span class="value">"#t of talks"</span>></span></span><br><span class="line"> {{t.name}}</span><br><span class="line"><span class="tag"></<span class="title">div</span>></span></span><br></pre></td></tr></table></figure>
<p>让我们构造一个服务来提供模拟数据:</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> TalksAppBackend {</span><br><span class="line"> fetchTalks() {</span><br><span class="line"> <span class="keyword">return</span> [</span><br><span class="line"> { name: <span class="string">'Are we there yet?'</span> },</span><br><span class="line"> { name: <span class="string">'The value of values'</span> }</span><br><span class="line"> ];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们如何调用这个服务?一种实现是:在我们的组件中创建一个服务对象的实例,并调用实例方法:</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> TalkList {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>() </span>{</span><br><span class="line"> <span class="keyword">var</span> backend = <span class="keyword">new</span> TalksAppBackend();</span><br><span class="line"> <span class="keyword">this</span>.talks = backend.fetchTalks();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>作为应用Demo来说,这么做没有问题。但是真实应用中这的确不是一个很好的解决方案。<code>TalksAppBackend</code>的作用不单单是返回一个讲座对象的数组,它同样需要通过<code>http</code>请求获得数据:在单元测试中,我们理应需要发起<code>http</code>请求。<br>问题在于:我们在<code>TalkList</code>中创建<code>TalksAppBackend</code>的实例造成了代码的耦合(从面向对象<strong>单一职责</strong>的原则看,<code>TalkList</code>不应该关心<code>TalksAppBackend</code>的具体实现)。</p>
<p>通过在构造方法中注入<code>TalksAppBackend</code>可以解决这个问题,注入的服务可以在测试中简单地替换,比如下面这样:</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> TalkList {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>(backend:TalksAppBackend) </span>{</span><br><span class="line"> <span class="keyword">this</span>.talks = backend.fetchTalks();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>代码告知 Angular<code>TalksList</code> 依赖于 <code>TalksAppBackend</code>。我们同样需要告知Angular 如何创建 <code>TalksAppBackend</code>服务。通过在组件中加入<code>providers</code>属性可以完成这个工作。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'talk-list'</span>,</span><br><span class="line"> templateUrl: <span class="string">'talks.html'</span>,</span><br><span class="line"> providers: [TalksAppBackend]</span><br><span class="line">})</span><br><span class="line"><span class="keyword">class</span> TalkList {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>(backend:TalksAppBackend) </span>{</span><br><span class="line"> <span class="keyword">this</span>.talks = backend.fetchTalks();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>TalksAppBackend</code>服务需要在<code>TalkList</code>或者它的祖先组件中进行声明。如果你习惯于 Angular 1 的编程风格,你可以在<strong>根组件</strong>中设置所有的<code>providers</code>。这样,所有的组件都可以直接使用这些服务了。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'talk-app'</span>,</span><br><span class="line"> providers: [TalksAppBackend] <span class="comment">// 在根组件中注册,之后所有应组件都可以直接注入这些服务。</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">class</span> Application {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@Component({</span><br><span class="line"> selector: <span class="string">'talk-list'</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">class</span> TalkList {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>(backend:TalksAppBackend) </span>{</span><br><span class="line"> <span class="keyword">this</span>.talks = backend.fetchTalks();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="u7EDF_u4E00_u7684_u4F9D_u8D56_u6CE8_u5165_u63A5_u53E3"><a href="#u7EDF_u4E00_u7684_u4F9D_u8D56_u6CE8_u5165_u63A5_u53E3" class="headerlink" title="统一的依赖注入接口"></a>统一的依赖注入接口</h4><p> <code>Angular 1</code> 和 <code>Angular 2</code>都有各自的依赖注入模块。在<code>Angular 1</code>中,框架提供了好几种依赖注入接口,有按照位置注入的(如 <code>element</code>),有按照名称注入的,有一点让人困惑。<code>Angular 2</code>提供了单一的依赖注入接口。所有依赖注入都在组件的构造方法中完成。</p>
<p> 比如,下面的组件注入了<code>TalksAppBackend</code>(一般都是单例的),<code>ElementRef</code>(对于每一个组件都不同)。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> TalksList {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>(elRef:ElementRef, backend:TalksAppBackend) </span>{</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们对于全局或者本地的依赖注入都使用同样的接口,即便是在一个组件中注入其他的组件。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Component {</span><br><span class="line"> <span class="constructor"><span class="keyword">constructor</span>(ancestor:AncestorCmp) </span>{</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="u5C0F_u7ED3-1"><a href="#u5C0F_u7ED3-1" class="headerlink" title="小结"></a>小结</h4><p>我们并不会在使用依赖注入后马上受益。但然后随着应用复杂度的增加,使用依赖注入会越来越重要。</p>
<p>依赖注入使得我们面向接口而不是实现进行编程,大大降低了代码的耦合性,提高了可测试性。同时Angular 2提供了统一的依赖注入接口。</p>
<h3 id="u5C5E_u6027_u7ED1_u5B9A"><a href="#u5C5E_u6027_u7ED1_u5B9A" class="headerlink" title="属性绑定"></a>属性绑定</h3><p>Angular通过属性绑定自动同步组件树、模型和组件树对应的DOM结构,为了理解其重要性,我们再回顾一下第一节中的应用。</p>
<p><img src="http://40.media.tumblr.com/fd37a5ff9a41cf25ed57236092b708c6/tumblr_nnzn556v7F1qc0howo1_500.png" alt="application"></p>
<p>我们知道,用户在输入演讲者<code>speaker</code>后,会产生一颗组件树,以及对应的模型。假设模型是简单的<code>Javascript</code>对象,并且看起来是这个样子:</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> filters: {</span><br><span class="line"> speaker: <span class="string">"Rich Hickey"</span>,</span><br><span class="line"> }</span><br><span class="line"> talks: [</span><br><span class="line"> {</span><br><span class="line"> title: <span class="string">"Are we there yet?"</span>,</span><br><span class="line"> speaker: <span class="string">"Rich Hickey"</span>,</span><br><span class="line"> yourRating: <span class="literal">null</span>,</span><br><span class="line"> avgRating: <span class="number">9.0</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们试着来改变模型。假设我看了这个演讲<code>Are we there yet?</code>并且觉得很赞,给了9.9的评分,模型会变为下面的结构。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> filters: {</span><br><span class="line"> speaker: <span class="string">"Rich Hickey"</span>,</span><br><span class="line"> }</span><br><span class="line"> talks: [</span><br><span class="line"> {</span><br><span class="line"> title: <span class="string">"Are we there yet?"</span>,</span><br><span class="line"> speaker: <span class="string">"Rich Hickey"</span>,</span><br><span class="line"> yourRating: <span class="number">9.9</span>,</span><br><span class="line"> avgRating: <span class="number">9.0</span>?</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果我需要找到依赖于这个值(我的评分)的所有部分并且更新它们,过程繁琐且易出错。我们希望应用能够自动地映射值的改变,而属性绑定正好能够赋予我们。</p>
<p>在<code>虚拟机</code>每一循环周期的末尾,Angular会检查组件树中的每一个组件,更确切地说,它将会检查每一个绑定的属性(所有<code>[]</code>和<code>{}</code>),并且更新这些组件,在更新组件的同时,Angular也会依照组件树更新对应的DOM结构。</p>
<p><em>仅仅使用了属性绑定的<code>input</code>属性可以被更新。</em></p>
<h4 id="ZONES"><a href="#ZONES" class="headerlink" title="ZONES"></a>ZONES</h4><p>Angular 1中我们需要通过<code>scope.$apply</code>告知框架进行脏值检查。Angular 2 通过使用 Zone.js 进行脏值检查,这意味着在使用第三方库时,你再也不需要使用<code>scope.$apply</code>进行脏值检查了,全部交给框架就好了。</p>
<h4 id="u5C0F_u7ED3-2"><a href="#u5C0F_u7ED3-2" class="headerlink" title="小结"></a>小结</h4><p>Angular 2 通过属性绑定,同步组件树、模型和组件树对应的DOM结构。<br>Angular 2 使用 Zone.js 来获知进行同步的时间点。</p>
<h3 id="u56DE_u987E"><a href="#u56DE_u987E" class="headerlink" title="回顾"></a>回顾</h3><p><code>Directives</code>特别是组件,是 Angular 2 中最重要的部分:它们是构建 Angular 2 应用的基础。组件时定义完备的,可以通过接口定义组件的<code>inputs</code>、<code>outputs</code>属性。组件通过私有API,来在组件各个生命周期产生<strong>钩子</strong>。组件可以与宿主元素进行交互。当组件需要依赖其他服务或组件时,Angular 提供了依赖注入。组件树,是 Angular 2的核心,使用属性绑定和<code>Zone.js</code>使得Angular 2的开发更加便捷。</p>
<p>在开始 Angular 2 之前请先充分地理解上面这些概念。当然实际工程中需要的知识远不止这些,这也是为什么我们在 Angular 2 核心的基础上又构建了一系列模块的原因,它们使得开发体验更加愉悦,这些模块包括:</p>
<ul>
<li>处理表单和输入的模块</li>
<li><code>http</code>模块</li>
<li>强大的路由模块</li>
<li>对动画的支持的模块</li>
<li>基于<code>material</code>设计风格的UI组件</li>
<li>用以进行单元测试、端对端和性能测试的工具集</li>
</ul>
<p>还有许多特性尚在开发之中,目前为止进展顺利。</p>
<h3 id="u62D3_u5C55_u8D44_u6599"><a href="#u62D3_u5C55_u8D44_u6599" class="headerlink" title="拓展资料"></a>拓展资料</h3><p><a href="https://angular.io/" target="_blank" rel="external">angular.io</a> 中有快速教程,手把手指南和API文档。<br><a href="https://www.youtube.com/watch?v=3IqtmUscE_U" target="_blank" rel="external">Brian Ford对Zone的介绍</a>: 如果你想深入了解<code>Zone.js</code>可以去看这个视频(需要翻墙%>_<%)。<br><a href="http://victorsavkin.com/post/110170125256/change-detection-in-angular-2" target="_blank" rel="external">Angular 2中的脏值检查</a>:深度解析了神奇的属性绑定背后的脏值检查。</p>
</div>
<div class="article-info article-info-index">
<div class="article-tag tagcloud">
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Angular2/">Angular2</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Angularjs2/">Angularjs2</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/javascript/">javascript</a></li></ul>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
<article id="post-rx-api-catch" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2016/01/17/rx-api-catch/" class="article-date">
<time datetime="2016-01-17T06:03:07.000Z" itemprop="datePublished">2016-01-17</time>
</a>
</div>
<div class="article-inner">
<input type="hidden" class="isFancy" />
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2016/01/17/rx-api-catch/">RxJS API解析(三)</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h3 id="Rx*__28Observable-catch_29_u65B9_u6CD5"><a href="#Rx*__28Observable-catch_29_u65B9_u6CD5" class="headerlink" title="Rx* (Observable.catch)方法"></a>Rx* (Observable.catch)方法</h3><h4 id="u65B9_u6CD5_u5B9A_u4E49"><a href="#u65B9_u6CD5_u5B9A_u4E49" class="headerlink" title="方法定义"></a>方法定义</h4><p> <code>Rx.Observable.catch(...args)</code></p>
<h4 id="u4F5C_u7528"><a href="#u4F5C_u7528" class="headerlink" title="作用"></a>作用</h4><p>序列中可观察对象因为异常而被终止后,<strong>继续</strong>订阅序列中的其他可观察对象。</p>
<h4 id="u53C2_u6570"><a href="#u53C2_u6570" class="headerlink" title="参数"></a>参数</h4><p><code>args</code> <em>(<code>Array</code> | <code>arguments</code>)</em>: 可观察对象序列。</p>
<h4 id="u8FD4_u56DE_u503C"><a href="#u8FD4_u56DE_u503C" class="headerlink" title="返回值"></a>返回值</h4><p><em>(<code>Observable</code>)</em>: 可观察对象序列中能够正确终止,不抛出异常的第一个可观察对象。</p>
<h4 id="u5B9D_u73E0_u56FE"><a href="#u5B9D_u73E0_u56FE" class="headerlink" title="宝珠图"></a>宝珠图</h4><p><img src="http://reactivex.io/documentation/operators/images/Catch.png" alt="catch"></p>
<h3 id="u5B9E_u4F8B"><a href="#u5B9E_u4F8B" class="headerlink" title="实例"></a>实例</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obs1 = Rx.Observable.throw(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'error'</span>));</span><br><span class="line"><span class="keyword">var</span> obs2 = Rx.Observable.return(<span class="number">42</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> source = Rx.Observable.catch(obs1, obs2);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> subscription = source.subscribe(</span><br><span class="line"> x => <span class="built_in">console</span>.log(<span class="string">`onNext: <span class="subst">${x}</span>`</span>),</span><br><span class="line"> e => <span class="built_in">console</span>.log(<span class="string">`onError: <span class="subst">${e}</span>`</span>),</span><br><span class="line"> () => <span class="built_in">console</span>.log(<span class="string">'onCompleted'</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// onNext: 42</span></span><br></pre></td></tr></table></figure>
<p>在订阅时, <code>obs1</code>抛出错误后,程序继续执行,转而输出没有异常的<code>obs2</code>,并输出<code>obs2</code>发射的值<code>42</code>。点击进入<a href="http://jsbin.com/vehili/1/edit?js,console,output" target="_blank" rel="external">在线演示</a>。</p>
<h3 id="u9898_u5916_u8BDD"><a href="#u9898_u5916_u8BDD" class="headerlink" title="题外话"></a>题外话</h3><p><strong>服务可用性</strong>是指,服务提供者需要保证服务在任何时间、情况下正确地提供。比如联网的银行系统,用户在各个ATM终端进行提取现金等操作后,数据都会被及时同步和备份。当不可抗因素发生时,数据可以被尽快的通过备份恢复。通常这些解决方案被称为<strong>灾备处理</strong>。</p>
<p>使用云服务,例如<strong>Ucloud</strong>的<code>Redis</code>服务,可以在同一个服务上看到两个不同地址访问地址,文档描述如下:</p>
<blockquote>
<p>每个云内存存储实例都会提供两个IP进行访问。</p>
<p>这两个IP都可以对云内存存储实例进行访问,分布在不同的接入服务上,其作用在于,当其中一个IP无法正常访问时,仍有另一个IP可用,不会完全中止服务。</p>
<p>因此,应用程序可以增加一个容灾切换的逻辑处理:将访问的IP列表设置好,默认访问其中的一个IP,当该IP无法访问时,自动切换到另一个IP继续业务。</p>
</blockquote>
<p>文档中提到了增强<strong>服务可用性</strong>的线索:<strong>总是提供一组相同的服务而不是一个服务</strong>,或者至少是相似的服务,服务调用后可以完成<strong>相同</strong>的业务逻辑。<br>这个策略也是<strong>负载均衡</strong>的基础,可以缓解单个服务提供者的压力,从用户角度看,又感知不到服务的差异性:比如 <em>多个HTTP服务</em> 、<em>读写分离的数据库</em>。</p>
<p>文末,举一个实例:假设你需要做一个APP,APP中用户在通过手机验证码验证后,才能登录账户。</p>
<p><img src="http://image.woshipm.com/wp-files/2015/04/2.png" alt="verifyMobile"></p>
<p>许多第三方服务提供商,都提供手机验证服务,比如<em>LeanCloud</em>,调用者像服务提供方发送<code>POST</code>请求,请求的<code>body</code>为用户手机号码。然后服务提供者,会将<strong>验证码</strong>发送到用户手机。用户在收到<strong>验证码</strong>后,通过表单,输入<strong>验证码</strong>,提交后,调用者再次向服务提供商发起<code>POST</code>请求,请求的<code>body</code>为用户输入的验证码然后等待服务提供商响应。</p>
<p>当然,某些情况下,服务提供商可能自己挂了,或者是不支持向某个号码所属的运营商提供服务;还有些情况下,用户的号码可能在某个服务提供商的黑名单中。比如:你的一个用户是 <em>经常写竞品分析的<strong>产品经理</strong></em> ,可能也许大概你的号码就在某个服务提供商的黑名单中。</p>
<p>我们往往要同时接入多个服务提供商的短信验证服务,保证用户能够正常通过我们的注册(登录)流程:</p>
<p>回到<code>catch()</code>函数,结合定义我们可以把一个提供商作为<strong>主要服务提供者</strong>,如果其不能提供服务(调用失败),我们可以选择第二家作为候选:</p>
<figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var service1 = Observable.create(<span class="string">"服务提供商#1"</span>);</span><br><span class="line">var service2 = Observable.create(<span class="string">"服务提供商#1"</span>);</span><br><span class="line"></span><br><span class="line">Observable.<span class="keyword">catch</span>(service1, service2).subscribe({</span><br><span class="line"> ()=><span class="built_in">console</span>.log(<span class="string">'succeed'</span>),</span><br><span class="line"> <span class="function"><span class="params">()</span>=></span><span class="built_in">console</span>.log(<span class="string">'所有验证服务均不可用'</span>)</span><br><span class="line"> ()=><span class="built_in">console</span>.log(<span class="string">'completed'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>这样,用户能够收到验证码并成功验证的几率大大增加。</p>
<p><em>剧终</em></p>
</div>
<div class="article-info article-info-index">
<div class="article-tag tagcloud">
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Reactive/">Reactive</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/RxJS/">RxJS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/javascript/">javascript</a></li></ul>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
<article id="post-rx-api-case" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2016/01/14/rx-api-case/" class="article-date">
<time datetime="2016-01-14T14:45:36.000Z" itemprop="datePublished">2016-01-14</time>
</a>
</div>
<div class="article-inner">
<input type="hidden" class="isFancy" />
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2016/01/14/rx-api-case/">RxJS API解析(二)</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h3 id="Rx*__28Observable-case_29_u65B9_u6CD5"><a href="#Rx*__28Observable-case_29_u65B9_u6CD5" class="headerlink" title="Rx* (Observable.case)方法"></a>Rx* (Observable.case)方法</h3><h4 id="u65B9_u6CD5_u5B9A_u4E49"><a href="#u65B9_u6CD5_u5B9A_u4E49" class="headerlink" title="方法定义"></a>方法定义</h4><p>[<code>Rx.Observable.case(selector, sources, [elseSource|scheduler])</code>]</p>
<h4 id="u4F5C_u7528"><a href="#u4F5C_u7528" class="headerlink" title="作用"></a>作用</h4><p>选择序列中特定可观察对象进行订阅,在特定可观察对象不存在的情况下,返回传入的默认可观察对象。</p>
<h4 id="u53C2_u6570"><a href="#u53C2_u6570" class="headerlink" title="参数"></a>参数</h4><ol>
<li><code>selector</code> <em>(<code>Function</code>)</em>: 返回<strong>键</strong>的字符串的函数,<strong>键</strong>用以与<code>sources</code>中的键名进行比较。</li>
<li><code>sources</code> <em>(<code>Object</code>)</em>: 一个包含可观察对象的Javascript对象。</li>
<li><code>[elseSource|scheduler]</code> <em>(<code>Observable</code> | <code>Scheduler</code>)</em>:当<code>selector</code>无法匹配<code>sources</code>时,该对象被默认返回。 如果没有明确指定,将返回附加了指定<code>scheduler</code>的<code>Rx.Observabe.empty</code> 对象。</li>
</ol>
<h4 id="u8FD4_u56DE_u503C"><a href="#u8FD4_u56DE_u503C" class="headerlink" title="返回值"></a>返回值</h4><p><em>(<code>Observable</code>)</em>: 返回值为经过选择后的<code>Observable</code>(可观察对象)。</p>
<h4 id="u5B9D_u73E0_u56FE"><a href="#u5B9D_u73E0_u56FE" class="headerlink" title="宝珠图"></a>宝珠图</h4><p><img src="http://7xq0ve.com1.z0.glb.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-15%20%E4%B8%8A%E5%8D%8811.45.33.png" alt="Observable.case"></p>
<h3 id="u5B9E_u4F8B"><a href="#u5B9E_u4F8B" class="headerlink" title="实例"></a>实例</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sources = {</span><br><span class="line"> hello: Rx.Observable.just(<span class="string">'clx'</span>),</span><br><span class="line"> world: Rx.Observable.just(<span class="string">'wxq'</span>)</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> subscription = Rx.Observable.case(()=><span class="string">"hello"</span>, sources, Rx.Observable.empty())</span><br><span class="line"></span><br><span class="line">subscription.subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x)</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>实例中,匿名函数<code>()=>"hello"</code>指定需要在sources中返回的可观察对象的键名为<code>"hello"</code>,命令行最终输出<code>"clx"</code>,点击进入<a href="https://jsbin.com/kodije/edit?js,console,output" target="_blank" rel="external"><code>case()</code>实例</a>。</p>
<h3 id="u9898_u5916_u8BDD"><a href="#u9898_u5916_u8BDD" class="headerlink" title="题外话"></a>题外话</h3><p>键值对,可以对值进行命名。通过键值对可以构造命名的<code>observable</code>可观察对象。</p>
<p>键值对是<strong>Javascript</strong>对象的组成部分,键名可以方便进行查找和比较操作。</p>
<p>两个典型的使用场景中,数据都是用键值对表示的:通过<code>Ajax</code>请求获得的数据,就是一个键值对(<code>JSON'</code>对象);许多配置文件也是键值对写入并持久化的,比如数据库、缓存的配置。</p>
<p>典型的<code>Ajax</code>请求结果<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> message: <span class="string">"ok"</span>,</span><br><span class="line"> nu: <span class="string">"350430378480"</span>,</span><br><span class="line"> companytype: <span class="string">"huitongkuaidi"</span>,</span><br><span class="line"> ischeck: <span class="string">"1"</span>,</span><br><span class="line"> com: <span class="string">"huitongkuaidi"</span>,</span><br><span class="line"> updatetime: <span class="string">"2016-01-15 10:58:19"</span>,</span><br><span class="line"> status: <span class="string">"200"</span>,</span><br><span class="line"> condition: <span class="string">"F00"</span>,</span><br><span class="line"> codenumber: <span class="string">"350430378480"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p><em>Laravel</em> 的数据库配置<br><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'mysql'</span> => [</span><br><span class="line"> <span class="string">'read'</span> => [</span><br><span class="line"> <span class="string">'host'</span> => <span class="string">'127.0.0.1'</span>,</span><br><span class="line"> ],</span><br><span class="line"> <span class="string">'write'</span> => [</span><br><span class="line"> <span class="string">'host'</span> => <span class="string">'127.0.0.1'</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">'driver'</span> => <span class="string">'mysql'</span>,</span><br><span class="line"> <span class="string">'database'</span> => <span class="string">'homestead'</span>,</span><br><span class="line"> <span class="string">'username'</span> => env(<span class="string">'DB_USERNAME'</span>, <span class="string">'root'</span>),</span><br><span class="line"> <span class="string">'password'</span> => env(<span class="string">'DB_PASSWORD'</span>, <span class="string">''</span>),</span><br><span class="line"> <span class="string">'charset'</span> => <span class="string">'utf8'</span>,</span><br><span class="line"> <span class="string">'collation'</span> => <span class="string">'utf8_general_ci'</span>,</span><br><span class="line"> <span class="string">'prefix'</span> => <span class="string">''</span>,</span><br><span class="line"> <span class="string">'strict'</span> => <span class="keyword">false</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure></p>
<p>试想,我们从不同的其他服务器获取配置文件,那么整个获取配置的过程是异步的。</p>
<p>我们将获取的结果封装成可观察对象,再命名为如<code>database</code>这样名称的键值对,使用<code>case()</code>方法便可以在可观察对象发射时,执行相应的初始化操作。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> config = {</span><br><span class="line"> <span class="string">"database"</span>: Observable.return(<span class="string">"数据库配置"</span>),</span><br><span class="line"> <span class="string">"cache"</span>: Observable.return(<span class="string">"缓存配置"</span>),</span><br><span class="line"> <span class="string">"picCDN"</span>: Observable.return(<span class="string">"图片CDN配置,比如七牛"</span>)</span><br><span class="line">};</span><br><span class="line">Observable.case(()=><span class="string">'database'</span>, config, Observable.empty())</span><br><span class="line"> .subscribe((databaseConfig) => {</span><br><span class="line"> <span class="comment">// 连接数据库</span></span><br><span class="line"> })</span><br><span class="line">Observable.case(()=><span class="string">'picCDN'</span>, config, Observable.empty())</span><br><span class="line"> .subscribe((pciCDNConfig) => {</span><br><span class="line"> <span class="comment">// 初始化图片CDN</span></span><br><span class="line"> })</span><br></pre></td></tr></table></figure>
<p>把上面的例子分开写也没有什么问题:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Observable.return(<span class="string">"数据库配置"</span>)</span><br><span class="line"> .subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">databaseConfig</span>) </span>{</span><br><span class="line"> <span class="comment">// 链接数据库</span></span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line">Observable.return(<span class="string">"图片CDN配置,比如七牛"</span>)</span><br><span class="line"> .subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">picCDNConfig</span>) </span>{</span><br><span class="line"> <span class="comment">// 初始化图片CDN</span></span><br><span class="line"> })</span><br></pre></td></tr></table></figure>
<p>我们为何要多此一举去使用<code>case()</code>呢?从<strong>结构化</strong>去考虑,将所有从远程获取配置的过程封装成<code>config</code>对象更有实际意义,也更便于代码的维护和管理。</p>
<p>我们再看一个例子作为结束:例子是针对表单进行校验,校验用户的<code>手机号</code>和<code>邮箱</code>是否和服务器记录重复,将所有校验封装在<code>validate</code>对象中结构更为合理:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> validate = {</span><br><span class="line"> <span class="string">"mobile"</span>: Observable.return(<span class="string">'123-566-789-01'</span>),</span><br><span class="line"> <span class="string">"email"</span>: Observable.return(<span class="string">'JonSnow@company.com'</span>)</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> emptyObserable = Observable.empty();</span><br><span class="line">validate.case(()=><span class="string">'mobile'</span>, validate, empty)</span><br><span class="line"> .subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">mobile</span>)</span>{</span><br><span class="line"> <span class="comment">// 验证手机号码是否重复</span></span><br><span class="line"> })</span><br><span class="line">validate.case(()=><span class="string">'email'</span>, validate, empty)</span><br><span class="line"> .subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">email</span>)</span>{</span><br><span class="line"> <span class="comment">// 验证用户邮箱是否重复</span></span><br><span class="line"> })</span><br></pre></td></tr></table></figure>
<p><em>剧终</em></p>
</div>
<div class="article-info article-info-index">
<div class="article-tag tagcloud">
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Reactive/">Reactive</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/RxJS/">RxJS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/javascript/">javascript</a></li></ul>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
<article id="post-rx-api-amb" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2016/01/13/rx-api-amb/" class="article-date">
<time datetime="2016-01-13T12:20:15.000Z" itemprop="datePublished">2016-01-13</time>
</a>
</div>
<div class="article-inner">
<input type="hidden" class="isFancy" />
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2016/01/13/rx-api-amb/">RxJS API解析(一)</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h3 id="u8D77_u56E0"><a href="#u8D77_u56E0" class="headerlink" title="起因"></a>起因</h3><p>在<strong>SegmentFault</strong>里发布过一篇<strong>RxJS</strong>的简明教程,很多人反馈对这个主题很是很感兴趣,详见<a href="http://segmentfault.com/a/1190000004293922" target="_blank" rel="external">RxJS简明教程</a>。</p>
<p>Rx<em> 是一种编程的思维,而不是一个特定的框架或库。**</em>RxJS<em>*是Rx</em>基于Javascript语言栈的实现。<br>我决定,今后写一系列“深入浅出”的文章来介绍 Rx*。我选择RxJS作为base,所有的代码实例都会基于RxJS,这一系列文章主要会涉及以下几个方面:</p>
<blockquote>
<ul>
<li>我对Rx的理解,和使用中的感悟,不会拘泥于前端或是服务端。</li>
<li>对Rx*标准:对象、方法(API)的阐述,这部分相当于对API文档的翻译。</li>
</ul>
</blockquote>
<p>这个系列,坚持原创和对国外优秀材料的翻译。当然这是个浩大的工程,希望我可以坚持完成。</p>
<hr>
<h3 id="Rx*__28Observable-amb__26amp_3B_Observable_23amb_29"><a href="#Rx*__28Observable-amb__26amp_3B_Observable_23amb_29" class="headerlink" title="Rx* (Observable.amb & Observable#amb)"></a>Rx* (Observable.amb & Observable#amb)</h3><p><em>注:Object.method为对象方法,Object#method为实例方法</em></p>
<h4 id="u65B9_u6CD5_u5B9A_u4E49"><a href="#u65B9_u6CD5_u5B9A_u4E49" class="headerlink" title="方法定义"></a>方法定义</h4><p>[<code>Rx.Observable.amb(...args)</code>]</p>
<h4 id="u4F5C_u7528"><a href="#u4F5C_u7528" class="headerlink" title="作用"></a>作用</h4><p>从一系列流中,订阅最先发射的值的可观察对象并忽略其他的可观察对象。</p>
<h4 id="u53C2_u6570"><a href="#u53C2_u6570" class="headerlink" title="参数"></a>参数</h4><p><code>args</code> <em>(Array|arguments)</em>:方法参数为多个可观察对象(流),或者是Promise对象,对象间存在竞争关系。</p>
<h4 id="u8FD4_u56DE_u503C"><a href="#u8FD4_u56DE_u503C" class="headerlink" title="返回值"></a>返回值</h4><p><em>(<code>Observable</code>)</em> :方法返回<em>呈竞争态</em>的多个可观察对象中,首先发射的可观察对象。</p>
<h4 id="u603B_u7ED3"><a href="#u603B_u7ED3" class="headerlink" title="总结"></a>总结</h4><p>简单的说,<code>amb()</code>像一个多路电闸,一次仅能构建一条通路:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">| | | | | | | |</span><br><span class="line">A B C D E F G H</span><br><span class="line">| | | | | | | |</span><br><span class="line"> \</span><br><span class="line"> \ 开关臂</span><br><span class="line"> \ </span><br><span class="line"> |</span><br><span class="line"> 主线</span><br><span class="line"> |</span><br></pre></td></tr></table></figure>
<p>函数需要做出 <em>选择</em> ,选择的依据就是哪一个可观察对象(流)先发射了值。选择后,仅有“联通”的可观察对象会被观察到。还是用 <em>电路</em> 做比喻,其中“ * ”表示电子:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">*</span><br><span class="line">| | | | | | |</span><br><span class="line"> *</span><br><span class="line">| | | | | | |</span><br><span class="line">A B C D E F G</span><br><span class="line">| | | | | | |</span><br><span class="line"> *</span><br><span class="line">| | | | | | |</span><br><span class="line"> *</span><br><span class="line">| | | | | | |</span><br><span class="line"> *</span><br></pre></td></tr></table></figure>
<p>可以看到,E支流的电子先到达了末端,所以E路被接通。从外部看,所有订阅者仅能观测到这个联通了E支流。</p>
<p>Rx官方喜欢使用<strong>珠宝图</strong>来解释各个操作符(函数)的作用,<a href="http://reactivex.io/documentation/operators/amb.html" target="_blank" rel="external">珠宝图表示<code>amb()</code></a>。</p>
<p>介绍一下牛逼的 <em>珠宝图</em> :<br><img src="http://7xq0ve.com1.z0.glb.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-14%20%E4%B8%8B%E5%8D%885.59.10.png" alt="amb珠宝图"><br>从左到右的箭头,代表时间轴。<code>|</code>代表可观察对象(流)发出了完成信号。<br>轴上的每一个<strong>珠宝</strong>代表流发射的<strong>值</strong>;</p>
<p>下方<code>amd</code>那个层是处理操作符,本图意味着所有操作符以上的流,都会经过操作符的处理(操作符以上的流为操作符的操作数);</p>
<p>最下方,是操作符处理后的输出<strong>结果</strong>。<br><code>y = f(x)</code>,其中<code>x</code>表示输入流,<code>f()</code>是操作符,<code>y</code>是最后的输出流。</p>
<p>观察上面的珠宝图,<code>1, 2, 3</code>这条时间轴上的可观察对象发射了值<code>1</code>,所以<code>amb()</code>选择了它作为最终输出的可观察对象。接下来如果它被订阅,订阅者会依次收到<code>1</code>,<code>2</code> 和 <code>3</code>。</p>
<p>当然,<strong>珠宝图不是静态的摆设</strong> !<strong>珠宝图不是静态的摆设</strong> !<strong>珠宝图不是静态的摆设</strong>!<br>我们可以拖动上面的每一个珠宝,来改变流中<strong>可观察对象</strong>的<strong>发射顺序</strong>:</p>
<p>我们拖动第一个时间轴——<code>20, 40, 60</code>上的<strong>可观察对象</strong>,把<code>20</code>这个珠宝拖到所有的珠宝前面(让其最先<strong>发射</strong>)。<br>依照<code>amb()</code>操作符的定义,我们可以推断,输出会变为<code>20, 40, 60</code>。截图验证一下:</p>
<p><img src="http://7xq0ve.com1.z0.glb.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-14%20%E4%B8%8B%E5%8D%886.11.52.png" alt="amb珠宝图拖动"></p>
<p>当一个流被联通后,其他的流肿么办?先记住结论:<strong>未被选择的流将被调用dispose方法</strong>,也就是说,他们被终止了。</p>
<h3 id="u5B9E_u4F8B"><a href="#u5B9E_u4F8B" class="headerlink" title="实例"></a>实例</h3><p><em>HTML</em><br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="title">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="title">input</span> <span class="attribute">id</span>=<span class="value">"input1"</span> <span class="attribute">type</span>=<span class="value">"text"</span>></span></span><br><span class="line"> <span class="tag"><<span class="title">input</span> <span class="attribute">id</span>=<span class="value">"input2"</span> <span class="attribute">type</span>=<span class="value">"text"</span>></span></span><br><span class="line"><span class="tag"></<span class="title">body</span>></span></span><br></pre></td></tr></table></figure></p>
<p><em>JavaScript</em><br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">input1 = $(<span class="string">'#input1'</span>);</span><br><span class="line">input2 = $(<span class="string">'#input2'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> source = Rx.Observable.amb(</span><br><span class="line"> Rx.Observable.fromEvent(input1, <span class="string">'click'</span>)</span><br><span class="line"> .map(()=><span class="string">'one'</span>),</span><br><span class="line"> Rx.Observable.fromEvent(input2, <span class="string">'click'</span>)</span><br><span class="line"> .map(()=><span class="string">'two'</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure></p>
<p>上面例子中,<code>amb()</code>中传入了两个点击事件流。事件流1,会在点击后发射字符串<code>one</code>;事件流2,会在点击后发射字符串<code>two</code>;</p>
<p>初始情况下,产生事件流1之后,事件流2不会再被输出;反之亦然,我们可以订阅<code>amb()</code>产生的结果流:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">var</span> subscription = source.subscribe(</span><br><span class="line"> <span class="function"><span class="keyword">function</span> (<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line"> },</span><br><span class="line"> <span class="function"><span class="keyword">function</span> (<span class="params">err</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Error: '</span> + err); </span><br><span class="line"> },</span><br><span class="line"> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Completed'</span>); </span><br><span class="line"> });</span><br></pre></td></tr></table></figure>
<p>具体可演示实例,可以进入<a href="http://jsbin.com/pitayi/edit?js,console,output" target="_blank" rel="external"><code>amb()操作符演示</code></a>。订阅结果会在控制台中输出。<br>当然,你可以在<strong>充分</strong>理解了<code>amb()</code>的原理之后修改可演示实例,验证自己的掌握程度。</p>
<h3 id="u9898_u5916_u8BDD"><a href="#u9898_u5916_u8BDD" class="headerlink" title="题外话"></a>题外话</h3><p>上文提到过 <em>Rx</em> 是一种<strong>编程模式</strong>,几乎各个平台、语言栈都有实现。我们试着探讨下<code>amb()</code>更宽泛地应用:</p>
<p><strong>秒杀系统</strong> :秒杀是一个高并发的场景,出现“多卖”是常态,“多卖”是由于秒杀商品的库存同步问题引起的。参与秒杀的用户<strong>呈竞争态</strong>,将请求分组后(比如100个一组),通过<code>amd()</code>可以甄选出具有购买资格的用户:因为秒杀的产品逻辑是:谁手快,谁买到。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Observable.amb(</span><br><span class="line"> 用户A的拍下请求,</span><br><span class="line"> 用户B的拍下请求,</span><br><span class="line"> 用户C的拍下请求,</span><br><span class="line"> ...</span><br><span class="line">).subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">user</span>) </span>{</span><br><span class="line"> 执行购买逻辑,创建订单,打开支付工具</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p><strong>移动电话</strong>:假设同一时间多个人呼叫你,你接通了最先到达的来电,这段时间内你就只能和他(她、它)通话了,其余呼叫者将会接收到忙音(对不起,你所呼叫的用户正在通话中,请稍后再拨)。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Observable.amb(</span><br><span class="line"> A来电,</span><br><span class="line"> B来电,</span><br><span class="line"> C来电,</span><br><span class="line"> ...</span><br><span class="line">).subscribe(<span class="function"><span class="keyword">function</span>(<span class="params">call</span>) </span>{</span><br><span class="line"> 通话吧啦吧啦</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p><em>剧终</em></p>
</div>
<div class="article-info article-info-index">
<div class="article-tag tagcloud">
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Reactive/">Reactive</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/RxJS/">RxJS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/javascript/">javascript</a></li></ul>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
</div>
<footer id="footer">
<div class="outer">
<div id="footer-info">
<div class="footer-left">
© 2016 慕慕珍珍
</div>
<div class="footer-right">
<a href="http://hexo.io/" target="_blank">Hexo</a> Theme <a href="https://github.com/litten/hexo-theme-yilia" target="_blank">Yilia</a> by Litten
</div>
</div>
</div>
</footer>
</div>
<link rel="stylesheet" href="/fancybox/jquery.fancybox.css" type="text/css">
<script>
var yiliaConfig = {
fancybox: true,
mathjax: true,
animate: true,
isHome: true,
isPost: false,
isArchive: false,
isTag: false,
isCategory: false,
open_in_new: false
}
</script>
<script src="http://7.url.cn/edu/jslib/comb/require-2.1.6,jquery-1.9.1.min.js" type="text/javascript"></script>
<script src="/js/main.js" type="text/javascript"></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
processEscapes: true,
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
}
});
MathJax.Hub.Queue(function() {
var all = MathJax.Hub.getAllJax(), i;
for(i=0; i < all.length; i += 1) {
all[i].SourceElement().parentNode.className += ' has-jax';
}
});
</script>
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
</div>
</body>
</html>