-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1614 lines (863 loc) · 423 KB
/
atom.xml
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
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Ice Panpan's blog</title>
<subtitle>Tan Yibing's blog</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://tanyibing.com/"/>
<updated>2018-12-22T12:10:04.867Z</updated>
<id>http://tanyibing.com/</id>
<author>
<name>Ice Panpan(Tan Yibing)</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Angular中拦截器的真相和HttpClient内部机制</title>
<link href="http://tanyibing.com/2018/12/22/Angular%E4%B8%AD%E6%8B%A6%E6%88%AA%E5%99%A8%E7%9A%84%E7%9C%9F%E7%9B%B8%E5%92%8CHttpClient%E5%86%85%E9%83%A8%E6%9C%BA%E5%88%B6/"/>
<id>http://tanyibing.com/2018/12/22/Angular中拦截器的真相和HttpClient内部机制/</id>
<published>2018-12-22T12:02:26.000Z</published>
<updated>2018-12-22T12:10:04.867Z</updated>
<content type="html"><![CDATA[<h1 id="Angular-中拦截器的真相和-HttpClient-内部机制"><a href="#Angular-中拦截器的真相和-HttpClient-内部机制" class="headerlink" title="Angular 中拦截器的真相和 HttpClient 内部机制"></a>Angular 中拦截器的真相和 HttpClient 内部机制</h1><blockquote><p>原文:<a href="https://blog.angularindepth.com/insiders-guide-into-interceptors-and-httpclient-mechanics-in-angular-103fbdb397bf" target="_blank" rel="noopener">Insider’s guide into interceptors and HttpClient mechanics in Angular</a><br><br>作者:<strong><a href="http://twitter.com/maxim_koretskyi" target="_blank" rel="noopener">Max Koretskyi</a></strong><br><br>原技术博文由 <code>Max Koretskyi</code> 撰写发布,他目前于 <a href="https://angular-grid.ag-grid.com/?utm_source=medium&utm_medium=blog&utm_campaign=angularcustom" target="_blank" rel="noopener">ag-Grid</a> 担任开发大使(Developer Advocate)<br><br><em>译者按:开发大使负责确保其所在的公司认真听取社区的声音并向社区传达他们的行动及目标,其作为社区和公司之间的纽带存在。</em><br><br>译者:<strong><a href="https://github.com/TanYiBing" target="_blank" rel="noopener">Ice Panpan</a></strong>;校对者:<strong><a href="https://github.com/dreamdevil00" target="_blank" rel="noopener">dreamdevil00</a></strong></p></blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs-17/1.png" alt="async or ngdocheck" title=""> </div> <div class="image-caption">async or ngdocheck</div> </figure><p>您可能知道 Angular 在4.3版本中新引入了<a href="https://blog.angularindepth.com/the-new-angular-httpclient-api-9e5c85fe3361" target="_blank" rel="noopener">强大的 HttpClient</a>。它的一个主要功能是请求拦截(request interception)—— 声明位于应用程序和后端之间的拦截器的能力。<a href="https://angular.cn/guide/http" target="_blank" rel="noopener">拦截器的文档</a>写的很好,展示了如何编写并注册一个拦截器。在这篇文章中,我将深入研究 <code>HttpClient</code> 服务的内部机制,特别是拦截器。我相信这些知识对于深入使用该功能是必要的。阅读完本文后,您将能够轻松了解像<a href="https://angular.cn/guide/http#caching" target="_blank" rel="noopener">缓存</a>之类工具的工作流程,并能够轻松自如地实现复杂的请求/响应操作方案。</p><p>首先,我们将使用文档中描述的方法来注册两个拦截器,以便为请求添加自定义的请求头。然后我们将实现自定义的中间件链,而不是使用 Angular 定义的机制。最后,我们将了解 <code>HttpClient</code> 的请求方法如何构建 <code>HttpEvents</code> 类型的 observable 流并满足<a href="https://angular.cn/guide/http#immutability" target="_blank" rel="noopener">不可变(immutability)性的需求</a>。</p><p>与我的大部分文章一样,我们将通过操作实例来学习更多内容。</p><h2 id="应用示例"><a href="#应用示例" class="headerlink" title="应用示例"></a>应用示例</h2><p>首先,让我们实现两个简单的拦截器,每个拦截器使用文档中描述的方法向传出的请求添加请求头。对于每个拦截器,我们声明一个实现了 <code>intercept</code> 方法的类。在此方法中,我们通过添加 <code>Custom-Header-1</code> 和 <code>Custom-Header-2</code> 的请求头信息来修改请求:</p><figure class="highlight ts"><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="meta">@Injectable</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> I1 <span class="keyword">implements</span> HttpInterceptor {</span><br><span class="line"> intercept(req: HttpRequest<<span class="built_in">any</span>>, next: HttpHandler): Observable<HttpEvent<<span class="built_in">any</span>>> {</span><br><span class="line"> <span class="keyword">const</span> modified = req.clone({setHeaders: {<span class="string">'Custom-Header-1'</span>: <span class="string">'1'</span>}});</span><br><span class="line"> <span class="keyword">return</span> next.handle(modified);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Injectable</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> I2 <span class="keyword">implements</span> HttpInterceptor {</span><br><span class="line"> intercept(req: HttpRequest<<span class="built_in">any</span>>, next: HttpHandler): Observable<HttpEvent<<span class="built_in">any</span>>> {</span><br><span class="line"> <span class="keyword">const</span> modified = req.clone({setHeaders: {<span class="string">'Custom-Header-2'</span>: <span class="string">'2'</span>}});</span><br><span class="line"> <span class="keyword">return</span> next.handle(modified);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>正如您所看到的,每个拦截器都将下一个处理程序作为第二个参数。我们需要通过调用该函数来将控制权传递给中间件链中的下一个拦截器。我们会很快发现调用 <code>next.handle</code> 时发生了什么以及为什么有的时候你不需要调用该函数。此外,如果你一直想知道为什么需要对请求调用 <code>clone()</code> 方法,你很快就会得到答案。</p><p>拦截器实现之后,我们需要使用 <code>HTTP_INTERCEPTORS</code> 令牌注册它们:</p><figure class="highlight ts"><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><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@NgModule</span>({</span><br><span class="line"> imports: [BrowserModule, HttpClientModule],</span><br><span class="line"> declarations: [AppComponent],</span><br><span class="line"> providers: [</span><br><span class="line"> {</span><br><span class="line"> provide: HTTP_INTERCEPTORS,</span><br><span class="line"> useClass: I1,</span><br><span class="line"> multi: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> provide: HTTP_INTERCEPTORS,</span><br><span class="line"> useClass: I2,</span><br><span class="line"> multi: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> bootstrap: [AppComponent]</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> AppModule {}</span><br></pre></td></tr></table></figure><p>紧接着执行一个简单的请求来检查我们自定义的请求头是否添加成功:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'my-app'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <div><h3>Response</h3>{{response|async|json}}</div></span></span><br><span class="line"><span class="string"> <button (click)="request()">Make request</button>`</span></span><br><span class="line"> ,</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> AppComponent {</span><br><span class="line"> response: Observable<<span class="built_in">any</span>>;</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> http: HttpClient</span>) {}</span><br><span class="line"></span><br><span class="line"> request() {</span><br><span class="line"> <span class="keyword">const</span> url = <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>;</span><br><span class="line"> <span class="keyword">this</span>.response = <span class="keyword">this</span>.http.get(url, {observe: <span class="string">'response'</span>});</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果我们已经正确完成了所有操作,当我们检查 <code>Network</code> 选项卡时,我们可以看到我们自定义的请求头发送到服务器:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs-17/console_1.png" alt="console_1" title=""> </div> <div class="image-caption">console_1</div> </figure><p>这很容易吧。你可以在 <a href="https://stackblitz.com/edit/angular-interceptors" target="_blank" rel="noopener">stackblitz</a> 上找到这个基础示例。现在是时候研究更有趣的东西了。</p><h2 id="实现自定义的中间件链"><a href="#实现自定义的中间件链" class="headerlink" title="实现自定义的中间件链"></a>实现自定义的中间件链</h2><p>我们的任务是在不使用 <code>HttpClient</code> 提供的方法的情况下手动将拦截器集成到处理请求的逻辑中。同时,我们将构建一个处理程序链,就像 Angular 内部完成的一样。</p><h3 id="处理请求"><a href="#处理请求" class="headerlink" title="处理请求"></a>处理请求</h3><p>在现代浏览器中,AJAX 功能是使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" target="_blank" rel="noopener">XmlHttpRequest</a> 或 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" target="_blank" rel="noopener">Fetch API</a> 实现的。此外,还有经常使用的会导致<a href="https://blog.angularindepth.com/do-you-still-think-that-ngzone-zone-js-is-required-for-change-detection-in-angular-16f7a575afef" target="_blank" rel="noopener">与变更检测相关的意外结果</a>的 <a href="http://schock.net/articles/2013/02/05/how-jsonp-really-works-examples/" target="_blank" rel="noopener"><code>JSONP</code></a> 技术。Angular 需要一个使用上述方法之一的服务来向服务器发出请求。这种服务在 <code>HttpClient</code> 文档上被称为 <strong>后端(backend)</strong>,例如:</p><blockquote><p><em>In an interceptor, next always represents the next interceptor in the chain, if any, or the final backend if there are no more interceptors</em></p><p><em>在拦截器中,<code>next</code> 始终表示链中的下一个拦截器(如果有的话),如果没有更多拦截器的话则表示最终后端</em></p></blockquote><p>在 Angular 提供的 <code>HttpClient</code> 模块中,这种服务有两种实现方法——使用 XmlHttpRequest API 实现的<a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/xhr.ts#L69" target="_blank" rel="noopener">HttpXhrBackend</a> 和使用 JSONP 技术实现的 <a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/jsonp.ts#L49" target="_blank" rel="noopener">JsonpClientBackend</a>。<code>HttpClient</code> 中默认使用 <code>HttpXhrBackend</code>。</p><p>Angular 定义了一个名为 HTTP(request)handler 的抽象概念,负责处理请求。处理请求的中间件链由 HTTP handlers 组成,这些处理程序将请求传递给链中的下一个处理程序,直到其中一个处理程序返回一个 observable 流。处理程序的接口由抽象类 <a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/backend.ts#L25" target="_blank" rel="noopener">HttpHandler</a> 定义:</p><figure class="highlight ts"><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="keyword">export</span> <span class="keyword">abstract</span> <span class="keyword">class</span> HttpHandler {</span><br><span class="line"> <span class="keyword">abstract</span> handle(req: HttpRequest<<span class="built_in">any</span>>): Observable<HttpEvent<<span class="built_in">any</span>>>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于 backend 服务(如 <a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/xhr.ts#L69" target="_blank" rel="noopener">HttpXhrBackend</a> )可以通过 发出网络请求来处理请求,所以它是 HTTP handler 的一个例子。通过和 backend 服务通信来处理请求是最常见的处理形式,但却不是唯一的处理方式。另一种常见的请求处理示例是从本地缓存中为请求提供服务,而不是发送请求给服务器。因此,任何可以处理请求的服务都应该实现 <code>handle</code> 方法,该方法根据函数签名返回一个 HTTP events 类型的 observable,如 <code>HttpProgressEvent</code>,<code>HttpHeaderResponse</code> 或<code>HttpResponse</code>。因此,如果我们想提供一些自定义请求处理逻辑,我们需要创建一个实现了 <code>HttpHandler</code> 接口的服务。</p><h3 id="使用-backend-作为-HTTP-handler"><a href="#使用-backend-作为-HTTP-handler" class="headerlink" title="使用 backend 作为 HTTP handler"></a>使用 backend 作为 HTTP handler</h3><p><code>HttpClient</code> 服务在 DI 容器中的 HttpHandler 令牌下注入了一个全局的 HTTP handler 。然后通过调用它的 <code>handle</code> 方法来发出请求:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> HttpClient {</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> handler: HttpHandler</span>) {}</span><br><span class="line"> </span><br><span class="line"> request(...): Observable<<span class="built_in">any</span>> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">const</span> events$: Observable<HttpEvent<<span class="built_in">any</span>>> = </span><br><span class="line"> of(req).pipe(concatMap(<span class="function">(<span class="params">req: HttpRequest<<span class="built_in">any</span>></span>) =></span> <span class="keyword">this</span>.handler.handle(req)));</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>默认情况下,全局 HTTP handler 是 <a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/xhr.ts#L69" target="_blank" rel="noopener">HttpXhrBackend</a> backend。它被注册在注入器中的 <code>HttpBackend</code> 令牌下。</p><figure class="highlight ts"><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="meta">@NgModule</span>({</span><br><span class="line"> providers: [</span><br><span class="line"> HttpXhrBackend,</span><br><span class="line"> { provide: HttpBackend, useExisting: HttpXhrBackend } </span><br><span class="line"> ]</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> HttpClientModule {}</span><br></pre></td></tr></table></figure><p>正如你可能猜到的那样 <code>HttpXhrBackend</code> 实现了 <code>HttpHandler</code> 接口:</p><figure class="highlight ts"><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">export</span> <span class="keyword">abstract</span> <span class="keyword">class</span> HttpHandler {</span><br><span class="line"> <span class="keyword">abstract</span> handle(req: HttpRequest<<span class="built_in">any</span>>): Observable<HttpEvent<<span class="built_in">any</span>>>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">abstract</span> <span class="keyword">class</span> HttpBackend <span class="keyword">implements</span> HttpHandler {</span><br><span class="line"> <span class="keyword">abstract</span> handle(req: HttpRequest<<span class="built_in">any</span>>): Observable<HttpEvent<<span class="built_in">any</span>>>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> HttpXhrBackend <span class="keyword">implements</span> HttpBackend {</span><br><span class="line"> handle(req: HttpRequest<<span class="built_in">any</span>>): Observable<HttpEvent<<span class="built_in">any</span>>> {}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于默认的 XHR backend 是在 <code>HttpBackend</code> 令牌下注册的,我们可以自己注入并替换 <code>HttpClient</code> 用于发出请求的用法。我们替换掉下面这个使用 <code>HttpClient</code> 的版本:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> AppComponent {</span><br><span class="line"> response: Observable<<span class="built_in">any</span>>;</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> http: HttpClient</span>) {}</span><br><span class="line"></span><br><span class="line"> request() {</span><br><span class="line"> <span class="keyword">const</span> url = <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>;</span><br><span class="line"> <span class="keyword">this</span>.response = <span class="keyword">this</span>.http.get(url, {observe: <span class="string">'body'</span>});</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>让我们直接使用默认的 XHR backend,如下所示:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> AppComponent {</span><br><span class="line"> response: Observable<<span class="built_in">any</span>>;</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> backend: HttpXhrBackend</span>) {}</span><br><span class="line"></span><br><span class="line"> request() {</span><br><span class="line"> <span class="keyword">const</span> req = <span class="keyword">new</span> HttpRequest(<span class="string">'GET'</span>, <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>);</span><br><span class="line"> <span class="keyword">this</span>.response = <span class="keyword">this</span>.backend.handle(req);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://stackblitz.com/edit/angular-http-backend" target="_blank" rel="noopener">这是示例</a>。在示例中需要注意一些事项。首先,我们需要手动构建 <code>HttpRequest</code>。其次,由于 backend 处理程序返回 HTTP events 流,你将在屏幕上看到不同的对象一闪而过,最终将呈现整个 http 响应对象。</p><h3 id="添加拦截器"><a href="#添加拦截器" class="headerlink" title="添加拦截器"></a>添加拦截器</h3><p>我们已经设法直接使用 backend,但由于我们没有运行拦截器,所以请求头尚未添加到请求中。一个拦截器包含处理请求的逻辑,但它要与 <code>HttpClient</code> 一起使用,需要将其封装到实现了 <code>HttpHandler</code> 接口的服务中。我们可以通过执行一个拦截器并将链中的下一个处理程序的引用传递给此拦截器的方式来实现此服务。这样拦截器就可以触发下一个处理程序,后者通常是 backend。为此,每个自定义的处理程序将保存链中下一个处理程序的引用,并将其与请求一起传递给下一个拦截器。下面就是我们想要的东西:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs-17/console_2.png" alt="console_2" title=""> </div> <div class="image-caption">console_2</div> </figure><p>在 Angular 中已经存在这种封装处理程序的方法了并被称为 <code>HttpInterceptorHandler</code>。让我们用它来封装我们的一个拦截器吧。但是不幸的是,Angular 没有将其导出为公共 API,因此我们只能从<a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/interceptor.ts#L52" target="_blank" rel="noopener">源代码</a>中复制基本实现:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> HttpInterceptorHandler <span class="keyword">implements</span> HttpHandler {</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> next: HttpHandler, <span class="keyword">private</span> interceptor: HttpInterceptor</span>) {}</span><br><span class="line"></span><br><span class="line"> handle(req: HttpRequest<<span class="built_in">any</span>>): Observable<HttpEvent<<span class="built_in">any</span>>> {</span><br><span class="line"> <span class="comment">// execute an interceptor and pass the reference to the next handler</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.interceptor.intercept(req, <span class="keyword">this</span>.next);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>并像这样使用它来封装我们的第一个拦截器:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> AppComponent {</span><br><span class="line"> response: Observable<<span class="built_in">any</span>>;</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> backend: HttpXhrBackend</span>) {}</span><br><span class="line"></span><br><span class="line"> request() {</span><br><span class="line"> <span class="keyword">const</span> req = <span class="keyword">new</span> HttpRequest(<span class="string">'GET'</span>, <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>);</span><br><span class="line"> <span class="keyword">const</span> handler = <span class="keyword">new</span> HttpInterceptorHandler(<span class="keyword">this</span>.backend, <span class="keyword">new</span> I1());</span><br><span class="line"> <span class="keyword">this</span>.response = handler.handle(req);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在,一旦我们发出请求,我们就可以看到 <code>Custom-Header-1</code> 已添加到请求中。<a href="https://stackblitz.com/edit/angular-http-backend-with-one-interceptor?file=app%2Finterceptor-handler.ts" target="_blank" rel="noopener">这是示例</a>。通过上面的实现,我们将一个拦截器和引用了下一个处理程序的 XHR backend 封装进了 <code>HttpInterceptorHandler</code>。现在,这就是这是一条处理程序链。</p><p>让我们通过封装第二个拦截器来将另一个处理程序添加到链中:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> AppComponent {</span><br><span class="line"> response: Observable<<span class="built_in">any</span>>;</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> backend: HttpXhrBackend</span>) {}</span><br><span class="line"></span><br><span class="line"> request() {</span><br><span class="line"> <span class="keyword">const</span> req = <span class="keyword">new</span> HttpRequest(<span class="string">'GET'</span>, <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>);</span><br><span class="line"> <span class="keyword">const</span> i1Handler = <span class="keyword">new</span> HttpInterceptorHandler(<span class="keyword">this</span>.backend, <span class="keyword">new</span> I1());</span><br><span class="line"> <span class="keyword">const</span> i2Handler = <span class="keyword">new</span> HttpInterceptorHandler(i1Handler, <span class="keyword">new</span> I2());</span><br><span class="line"> <span class="keyword">this</span>.response = i2Handler.handle(req);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://stackblitz.com/edit/angular-http-backend-with-one-interceptor" target="_blank" rel="noopener">在这可以看到演示</a>,现在一切正常,就像我们在最开始的示例中使用 <code>HttpClient</code> 的那样。我们刚刚所做的就是构建了处理程序的中间件链,其中每个处理程序执行一个拦截器并将下一个处理程序的引用传递给它。这是链的图表:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs-17/console_3.png" alt="console_3" title=""> </div> <div class="image-caption">console_3</div> </figure><p>当我们在拦截器中执行 <code>next.handle(modified)</code> 语句时,我们将控制权传递给链中的下一个处理程序:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> I1 <span class="keyword">implements</span> HttpInterceptor {</span><br><span class="line"> intercept(req: HttpRequest<<span class="built_in">any</span>>, next: HttpHandler): Observable<HttpEvent<<span class="built_in">any</span>>> {</span><br><span class="line"> <span class="keyword">const</span> modified = req.clone({setHeaders: {<span class="string">'Custom-Header-1'</span>: <span class="string">'1'</span>}});</span><br><span class="line"> <span class="comment">// passing control to the handler in the chain</span></span><br><span class="line"> <span class="keyword">return</span> next.handle(modified);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最终,控制权将被传递到最后一个 backend 处理程序,该处理程序将对服务器执行请求。</p><h3 id="自动封装拦截器"><a href="#自动封装拦截器" class="headerlink" title="自动封装拦截器"></a>自动封装拦截器</h3><p>我们可以通过使用 <code>HTTP_INTERCEPTORS</code> 令牌注入所有的拦截器,然后使用 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight" target="_blank" rel="noopener">reduceRight</a> 将它们链接起来的方式自动构建拦截器链,而不是逐个地手动将拦截器链接起来构成拦截器链。我们这样做:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> AppComponent {</span><br><span class="line"> response: Observable<<span class="built_in">any</span>>;</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params"> <span class="keyword">private</span> backend: HttpBackend, </span></span><br><span class="line"> @Inject(HTTP_INTERCEPTORS) private interceptors: HttpInterceptor[]) {}</span><br><span class="line"></span><br><span class="line"> request() {</span><br><span class="line"> <span class="keyword">const</span> req = <span class="keyword">new</span> HttpRequest(<span class="string">'GET'</span>, <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>);</span><br><span class="line"> <span class="keyword">const</span> i2Handler = <span class="keyword">this</span>.interceptors.reduceRight(</span><br><span class="line"> (next, interceptor) => <span class="keyword">new</span> HttpInterceptorHandler(next, interceptor), <span class="keyword">this</span>.backend);</span><br><span class="line"> <span class="keyword">this</span>.response = i2Handler.handle(req);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们需要在这里使用 <code>reduceRight</code> 来从最后注册的拦截器开始构建一个链。使用上面的代码,我们会获得与手动构建的处理程序链相同的链。通过 <code>reduceRight</code> 返回的值是对链中第一个处理程序的引用。</p><p>实际上,上述我写的代码在 Angular 中是使用 <code>interceptingHandler</code> 函数来实现的。原话是这么说的:</p><blockquote><p><em>Constructs an <code>HttpHandler</code> that applies a bunch of <code>HttpInterceptor</code>s to a request before passing it to the given <code>HttpBackend</code>.<br>Meant to be used as a factory function within <code>HttpClientModule</code>.</em></p><p><em>构造一个 HttpHandler,在将请求传递给给定的 HttpBackend 之前,将一系列 HttpInterceptor 应用于请求。<br>可以在 HttpClientModule 中用作工厂函数。</em></p></blockquote><p>(下面顺便贴一下源码:)</p><figure class="highlight ts"><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">export</span> <span class="function"><span class="keyword">function</span> <span class="title">interceptingHandler</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> backend: HttpBackend, interceptors: HttpInterceptor[] | <span class="literal">null</span> = []</span>): <span class="title">HttpHandler</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!interceptors) {</span><br><span class="line"> <span class="keyword">return</span> backend;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> interceptors.reduceRight(</span><br><span class="line"> (next, interceptor) => <span class="keyword">new</span> HttpInterceptorHandler(next, interceptor), backend);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在我们知道是如何构造一条处理函数链的了。在 HTTP handler 中需要注意的最后一点是, <code>interceptingHandler</code> 默认为 <code>HttpHandler</code>:</p><figure class="highlight ts"><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="meta">@NgModule</span>({</span><br><span class="line"> providers: [</span><br><span class="line"> {</span><br><span class="line"> provide: HttpHandler,</span><br><span class="line"> useFactory: interceptingHandler,</span><br><span class="line"> deps: [HttpBackend, [<span class="meta">@Optional</span>(), <span class="meta">@Inject</span>(HTTP_INTERCEPTORS)]],</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> HttpClientModule {}</span><br></pre></td></tr></table></figure><p>因此,执行此函数的结果是链中第一个处理程序的引用被注入 <code>HttpClient</code> 服务并被使用。</p><h2 id="构建处理链的-observable-流"><a href="#构建处理链的-observable-流" class="headerlink" title="构建处理链的 observable 流"></a>构建处理链的 observable 流</h2><p>好的,现在我们知道我们有一堆处理程序,每个处理程序执行一个关联的拦截器并调用链中的下一个处理程序。调用此链返回的值是一个 <code>HttpEvents</code> 类型的 observable 流。这个流通常(但不总是)由最后一个处理程序生成,这跟 backend 的具体实现有关。其他的处理程序通常只返回该流。下面是大多数拦截器最后的语句:</p><figure class="highlight ts"><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">intercept(req: HttpRequest<<span class="built_in">any</span>>, next: HttpHandler): Observable<HttpEvent<<span class="built_in">any</span>>> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> next.handle(authReq);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以我们可以这样来展示逻辑:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs-17/console_4.png" alt="console_4" title=""> </div> <div class="image-caption">console_4</div> </figure><p>但是因为任何拦截器都可以返回一个 <code>HttpEvents</code> 类型的 observable 流,所以你有很多定制机会。例如,你可以实现自己的 backend 并将其注册为拦截器。或者实现一个<a href="https://angular.cn/guide/http#caching" target="_blank" rel="noopener">缓存机制</a>,如果找到了缓存就立即返回, 而不用交给下个处理程序处理:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs-17/console_5.png" alt="console_5" title=""> </div> <div class="image-caption">console_5</div> </figure><p>此外,由于每个拦截器都可以访问下一个拦截器(通过调用 <code>next.handler()</code>)返回的 observable 流,所以我们可以通过 RxJs 操作符添加自定义的逻辑来修改返回的流。</p><h2 id="构建-HttpClient-的-observable-流"><a href="#构建-HttpClient-的-observable-流" class="headerlink" title="构建 HttpClient 的 observable 流"></a>构建 HttpClient 的 observable 流</h2><p>如果您仔细阅读了前面的部分,那么您现在可能想知道处理链创建的 HTTP events 流是否与调用 <code>HttpClient</code> 方法,如 <code>get</code> 或者 <code>post</code> 所返回的流完全相同。咦…不是!实现的过程更有意思。</p><p><code>HttpClient</code> 通过使用 RxJS 的创建操作符 <code>of</code> 来将请求对象变为 observable 流,并在调用 <code>HttpClient</code> 的 HTTP <code>request</code> 方法时返回它。<strong>处理程序链作为此流的一部分被同步处理,并且使用 <code>concatMap</code> 操作符压平链返回的 observable</strong>。<a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/client.ts#L386" target="_blank" rel="noopener">实现的关键点</a>就在 <code>request</code> 方法,因为所有的 API 方法像 <code>get</code>,<code>post</code> 或 <code>delete</code>只是包装了 <code>request</code> 方法:</p><figure class="highlight ts"><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="keyword">const</span> events$: Observable<HttpEvent<<span class="built_in">any</span>>> = of(req).pipe(</span><br><span class="line"> concatMap(<span class="function">(<span class="params">req: HttpRequest<<span class="built_in">any</span>></span>) =></span> <span class="keyword">this</span>.handler.handle(req))</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>在上面的代码片段中,我用 <code>pipe</code> 替换了旧技术 <code>call</code>。如果您仍然对 <code>concatMap</code> 如何工作感到困惑,你可以阅读<a href="https://blog.angularindepth.com/learn-to-combine-rxjs-sequences-with-super-intuitive-interactive-diagrams-20fce8e6511" target="_blank" rel="noopener">学习将 RxJs 序列与超级直观的交互式图表相结合</a>。有趣的是,处理程序链在以 <code>of</code> 开头的 observable 流中执行是有原因的,这里有一个解释:</p><blockquote><p><em>Start with an Observable.of() the initial request, and run the handler (which includes all interceptors) inside a concatMap(). This way, the handler runs inside an Observable chain, which causes interceptors to be re-run on every subscription (this also makes retries re-run the handler, including interceptors).</em></p><p><em>通过 Observable.of() 初始请求,并在 concatMap() 中运行处理程序(包括所有拦截器)。这样,处理程序就在一个 Observable 链中运行,这会使得拦截器会在每个订阅上重新运行(这样重试的时候也会重新运行处理程序,包括拦截器)。</em></p></blockquote><h3 id="处理-‘observe’-请求选项"><a href="#处理-‘observe’-请求选项" class="headerlink" title="处理 ‘observe’ 请求选项"></a>处理 ‘observe’ 请求选项</h3><p>通过 <code>HttpClient</code> 创建的初始 observable 流,发出了所有的 HTTP events,如 <code>HttpProgressEvent</code>,<code>HttpHeaderResponse</code> 或 <code>HttpResponse</code>。但是从文档中我们知道我们可以通过设置 observe 选项来指定我们感兴趣的事件:</p><figure class="highlight ts"><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">request() {</span><br><span class="line"> <span class="keyword">const</span> url = <span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>;</span><br><span class="line"> <span class="keyword">this</span>.response = <span class="keyword">this</span>.http.get(url, {observe: <span class="string">'body'</span>});</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用 <code>{observe: 'body'}</code> 后,从 <code>get</code> 方法返回的 observable 流只会发出响应中 <code>body</code> 部分的内容。 <code>observe</code> 的其他选项还有 <code>events</code> 和 <code>response</code> 并且 <code>response</code> 是默认选项。在探索处理程序链的实现的一开始,我就指出过调用处理程序链返回的流会发出<strong>所有</strong> HTTP events。根据 <code>observe</code> 的参数过滤这些 events 是 <code>HttpClient</code> 的责任。</p><p>这意味着我在上一节中演示 <code>HttpClient</code> 返回流的实现需要稍微调整一下。我们需要做的是过滤这些 events 并根据 <code>observe</code> 参数值将它们映射到不同的值。接下来简单实现下:</p><figure class="highlight ts"><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><br><span class="line"><span class="keyword">const</span> events$: Observable<HttpEvent<<span class="built_in">any</span>>> = of(req).pipe(...)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (options.observe === <span class="string">'events'</span>) {</span><br><span class="line"> <span class="keyword">return</span> events$;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> res$: Observable<HttpResponse<<span class="built_in">any</span>>> =</span><br><span class="line"> events$.pipe(filter(<span class="function">(<span class="params">event: HttpEvent<<span class="built_in">any</span>></span>) =></span> event <span class="keyword">instanceof</span> HttpResponse));</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (options.observe === <span class="string">'response'</span>) {</span><br><span class="line"> <span class="keyword">return</span> res$;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (options.observe === <span class="string">'body'</span>) {</span><br><span class="line"> <span class="keyword">return</span> res$.pipe(map(<span class="function">(<span class="params">res: HttpResponse<<span class="built_in">any</span>></span>) =></span> res.body));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://github.com/angular/angular/blob/6353b77f891d4a74953b23afcf5dd6f64db09a09/packages/common/http/src/client.ts#L403" target="_blank" rel="noopener">在这里</a>,您可以找到源码。</p><h2 id="不可变性的需要"><a href="#不可变性的需要" class="headerlink" title="不可变性的需要"></a>不可变性的需要</h2><p><a href="https://angular.cn/guide/http#immutability" target="_blank" rel="noopener">文档上关于不变性</a>的一个有趣的段落是这样的:</p><blockquote><p><em>Interceptors exist to examine and mutate outgoing requests and incoming responses. However, it may be surprising to learn that the HttpRequest and HttpResponse classes are largely immutable. This is for a reason: because the app may retry requests, the interceptor chain may process an individual request multiple times. If requests were mutable, a retried request would be different than the original request. Immutability ensures the interceptors see the same request for each try.</em></p><p><em>虽然拦截器有能力改变请求和响应,但 HttpRequest 和 HttpResponse 实例的属性却是只读(readonly)的,因此,它们在很大意义上说是不可变对象。有充足的理由把它们做成不可变对象:应用可能会重试发送很多次请求之后才能成功,这就意味着这个拦截器链表可能会多次重复处理同一个请求。 如果拦截器可以修改原始的请求对象,那么重试阶段的操作就会从修改过的请求开始,而不是原始请求。 而这种不可变性,可以确保这些拦截器在每次重试时看到的都是同样的原始请求。</em></p></blockquote><p>让我详细说明一下。当您调用 <code>HttpClient</code> 的任何 HTTP 请求方法时,就会创建请求对象。正如我在前面部分中解释的那样,此请求用于生成一个 <code>events$</code> 的 observable 序列,并且在订阅时,它会在处理程序链中被传递。但是 <code>events$</code> 流可能会被重试,这意味着在序列之外创建的原始请求对象可能再次触发序列多次。但拦截器应始终以原始请求开始。如果请求是可变的,并且可以在拦截器运行期间进行修改,则此条件不适用于下一次拦截器运行。由于同一请求对象的引用将多次用于开始 observable 序列,请求及其所有组成部分,如 <code>HttpHeaders</code> 和 <code>HttpParams</code> 应该是不可变的。</p>]]></content>
<summary type="html">
<h1 id="Angular-中拦截器的真相和-HttpClient-内部机制"><a href="#Angular-中拦截器的真相和-HttpClient-内部机制" class="headerlink" title="Angular 中拦截器的真相和 HttpClient
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
</entry>
<entry>
<title>[翻译]OnPush组件中NgDoCheck和AsyncPipe的区别</title>
<link href="http://tanyibing.com/2018/12/14/%E7%BF%BB%E8%AF%91-OnPush%E7%BB%84%E4%BB%B6%E4%B8%ADNgDoCheck%E5%92%8CAsyncPipe%E7%9A%84%E5%8C%BA%E5%88%AB/"/>
<id>http://tanyibing.com/2018/12/14/翻译-OnPush组件中NgDoCheck和AsyncPipe的区别/</id>
<published>2018-12-14T13:59:55.000Z</published>
<updated>2018-12-14T14:02:36.941Z</updated>
<content type="html"><![CDATA[<h1 id="翻译-OnPush-组件中-NgDoCheck-和-AsyncPipe-的区别"><a href="#翻译-OnPush-组件中-NgDoCheck-和-AsyncPipe-的区别" class="headerlink" title="[翻译] OnPush 组件中 NgDoCheck 和 AsyncPipe 的区别"></a>[翻译] OnPush 组件中 NgDoCheck 和 AsyncPipe 的区别</h1><blockquote><p>原文:<a href="https://blog.angularindepth.com/the-difference-between-ngdocheck-and-asyncpipe-in-onpush-components-4918ec4b29d4" target="_blank" rel="noopener">The difference between NgDoCheck and AsyncPipe in OnPush components</a><br>作者:<strong><a href="http://twitter.com/maxim_koretskyi" target="_blank" rel="noopener">Max Koretskyi</a></strong><br>原技术博文由 <code>Max Koretskyi</code> 撰写发布,他目前于 <a href="https://angular-grid.ag-grid.com/?utm_source=medium&utm_medium=blog&utm_campaign=angularcustom" target="_blank" rel="noopener">ag-Grid</a> 担任开发者职位</p></blockquote><blockquote><p>译者:<strong><a href="https://github.com/TanYiBing" target="_blank" rel="noopener">Ice Panpan</a></strong></p></blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/1.jpeg" alt="async or ngdocheck" title=""> </div> <div class="image-caption">async or ngdocheck</div> </figure><p>这篇文章是对<a href="https://twitter.com/shai_reznik/status/1054868497363283968" target="_blank" rel="noopener">Shai这条推特</a>的回应。他询问使用 <code>NgDoCheck</code> 生命周期钩子来手动比较值而不是使用 <code>asyncPipe</code> 是否有意义。这是一个非常好的问题,需要对引擎的工作原理有很多了解:变化检测(change detection),管道(pipe)和生命周期钩子(lifecycle hooks)。那就是我探索的入口😎。</p><p>在本文中,我将向您展示如何手动处理变更检测。这些技术使您可以更好地掌控 Angular 的输入绑定(input bindings)的自动执行和异步值检查(async values checks)。掌握了这些知识之后,我还将与您分享我对这些解决方案的性能影响的看法。让我们开始吧!</p><h2 id="OnPush-组件"><a href="#OnPush-组件" class="headerlink" title="OnPush 组件"></a>OnPush 组件</h2><p>在 Angular 中,我们有一种非常常见的优化技术,需要将 <code>ChangeDetectionStrategy.OnPush</code> 添加到组件中。假设我们有如下两个简单的组件:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'a-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am A component</span></span></span><br><span class="line"><span class="string"> <b-comp></b-comp></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> AComponent {}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`<span>I am B component</span>`</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {}</span><br></pre></td></tr></table></figure><p>这样设置之后, Angular 每次都会对 <code>A</code> 和 <code>B</code> 两个组件运行变更检测。如果我们现在为 <code>B</code> 组件添加上 <code>OnPush</code> 策略:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`<span>I am B component</span>`</span>,</span><br><span class="line"> changeDetection: ChangeDetectionStrategy.OnPush</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {}</span><br></pre></td></tr></table></figure><p><strong>只有在输入绑定的值发生变化时</strong> Angular 才会对 <code>B</code> 运行变更检测。由于它现在没有任何绑定,因此该组件只会在初始化的时候检查一次。</p><h2 id="手动触发变更检测"><a href="#手动触发变更检测" class="headerlink" title="手动触发变更检测"></a>手动触发变更检测</h2><p>有没有办法强制对 <code>B</code> 组件进行变更检测?是的,我们可以注入 <code>changeDetectorRef</code> 并使用它的方法 <code>markForCheck</code> 来指示 Angular 需要检查该组件。并且由于 <a href="https://blog.angularindepth.com/if-you-think-ngdocheck-means-your-component-is-being-checked-read-this-article-36ce63a3f3e5" target="_blank" rel="noopener"><em>NgDoCheck 钩子仍然会被 B 组件触发</em></a>,所以我们应该在 <em>NgDoCheck</em> 中调用 <em>markForCheck</em>:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`<span>I am B component</span>`</span>,</span><br><span class="line"> changeDetection: ChangeDetectionStrategy.OnPush</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> cd: ChangeDetectorRef</span>) {}</span><br><span class="line"></span><br><span class="line"> ngDoCheck() {</span><br><span class="line"> <span class="keyword">this</span>.cd.markForCheck();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在,当 Angular 检查父组件 <code>A</code> 时,将始终检查 <code>B</code> 组件。现在让我们看看我们可以在哪里使用它。</p><h2 id="输入绑定"><a href="#输入绑定" class="headerlink" title="输入绑定"></a>输入绑定</h2><p>我之前说过,Angular 只在 <code>OnPush</code> 组件的绑定发生变化时运行的变化检测。所以让我们看一下输入绑定的例子。假设我们有一个通过输入绑定从父组件传递下来的对象:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am B component</span></span></span><br><span class="line"><span class="string"> <span>User name: {{user.name}}</span></span></span><br><span class="line"><span class="string"> `</span>,</span><br><span class="line"> changeDetection: ChangeDetectionStrategy.OnPush</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {</span><br><span class="line"> <span class="meta">@Input</span>() user;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在父组件 <code>A</code> 中,我们定义了一个对象,并实现了在单击按钮时来更新对象名称的 <em>changeName</em> 方法:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'a-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am A component</span></span></span><br><span class="line"><span class="string"> <button (click)="changeName()">Trigger change detection</button></span></span><br><span class="line"><span class="string"> <b-comp [user]="user"></b-comp></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> AComponent {</span><br><span class="line"> user = {name: <span class="string">'A'</span>};</span><br><span class="line"></span><br><span class="line"> changeName() {</span><br><span class="line"> <span class="keyword">this</span>.user.name = <span class="string">'B'</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果您现在<a href="https://stackblitz.com/edit/angular-kq26qe" target="_blank" rel="noopener">运行此示例</a>,则在第一次变更检测后,您将看到用户名称被打印出来:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">User name: A</span><br></pre></td></tr></table></figure><p>但是当我们点击按钮并回调中更改名称时:</p><figure class="highlight ts"><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">changeName() {</span><br><span class="line"> <span class="keyword">this</span>.user.name = <span class="string">'B'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>该名称并没有在屏幕上更新</strong>,这是因为 Angular 对输入参数执行浅比较,并且对 user 对象的引用没有改变。那我们怎么解决这个问题呢?</p><p>好吧,我们可以在检测到差异时手动检查名称并触发变更检测:</p><figure class="highlight ts"><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><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am B component</span></span></span><br><span class="line"><span class="string"> <span>User name: {{user.name}}</span></span></span><br><span class="line"><span class="string"> `</span>,</span><br><span class="line"> changeDetection: ChangeDetectionStrategy.OnPush</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {</span><br><span class="line"> <span class="meta">@Input</span>() user;</span><br><span class="line"> previousName = <span class="string">''</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> cd: ChangeDetectorRef</span>) {}</span><br><span class="line"></span><br><span class="line"> ngDoCheck() {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.previousName !== <span class="keyword">this</span>.user.name) {</span><br><span class="line"> <span class="keyword">this</span>.previousName = <span class="keyword">this</span>.user.name;</span><br><span class="line"> <span class="keyword">this</span>.cd.markForCheck();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果您现在<a href="https://stackblitz.com/edit/angular-8dkwct" target="_blank" rel="noopener">运行此代码</a>,你将在屏幕上看到更新的名称。</p><h2 id="异步更新"><a href="#异步更新" class="headerlink" title="异步更新"></a>异步更新</h2><p>现在,让我们的例子更复杂一点。我们将介绍一种基于 RxJs 的服务,它可以异步发出更新。这类似于 NgRx 的体系结构。我将使用一个 <code>BehaviorSubject</code> 作为值的来源,因为我们需要在这个流的最开始设置初始值:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'a-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am A component</span></span></span><br><span class="line"><span class="string"> <button (click)="changeName()">Trigger change detection</button></span></span><br><span class="line"><span class="string"> <b-comp [user]="user"></b-comp></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> AComponent {</span><br><span class="line"> stream = <span class="keyword">new</span> BehaviorSubject({name: <span class="string">'A'</span>});</span><br><span class="line"> user = <span class="keyword">this</span>.stream.asObservable();</span><br><span class="line"></span><br><span class="line"> changeName() {</span><br><span class="line"> <span class="keyword">this</span>.stream.next({name: <span class="string">'B'</span>});</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以我们需要在子组件中订阅这个流并从中获取到 <code>user</code> 对象。我们需要订阅流并检查值是否更新。这样做的常用方法是使用 <a href="https://angular.io/api/common/AsyncPipe" target="_blank" rel="noopener">AsyncPipe</a>。</p><h2 id="AsyncPipe"><a href="#AsyncPipe" class="headerlink" title="AsyncPipe"></a>AsyncPipe</h2><p>所以这里是子组件 <code>B</code> 的实现:</p><figure class="highlight ts"><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="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am B component</span></span></span><br><span class="line"><span class="string"> <span>User name: {{(user | async).name}}</span></span></span><br><span class="line"><span class="string"> `</span>,</span><br><span class="line"> changeDetection: ChangeDetectionStrategy.OnPush</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {</span><br><span class="line"> <span class="meta">@Input</span>() user;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://stackblitz.com/edit/angular-q8n3qj" target="_blank" rel="noopener">这是演示</a>。但是,还有另一种不使用管道的方法吗?</p><h2 id="手动检查并且变更检测"><a href="#手动检查并且变更检测" class="headerlink" title="手动检查并且变更检测"></a>手动检查并且变更检测</h2><p>是的,我们可以手动检查值并在需要时触发变更检测。正如开头的例子一样,我们可以使用 <code>NgDoCheck</code> 生命周期钩子:</p><figure class="highlight ts"><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><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'b-comp'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <span>I am B component</span></span></span><br><span class="line"><span class="string"> <span>User name: {{user.name}}</span></span></span><br><span class="line"><span class="string"> `</span>,</span><br><span class="line"> changeDetection: ChangeDetectionStrategy.OnPush</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> BComponent {</span><br><span class="line"> <span class="meta">@Input</span>(<span class="string">'user'</span>) user$;</span><br><span class="line"> user;</span><br><span class="line"> previousName = <span class="string">''</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> cd: ChangeDetectorRef</span>) {}</span><br><span class="line"></span><br><span class="line"> ngOnInit() {</span><br><span class="line"> <span class="keyword">this</span>.user$.subscribe(<span class="function">(<span class="params">user</span>) =></span> {</span><br><span class="line"> <span class="keyword">this</span>.user = user;</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ngDoCheck() {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.previousName !== <span class="keyword">this</span>.user.name) {</span><br><span class="line"> <span class="keyword">this</span>.previousName = <span class="keyword">this</span>.user.name;</span><br><span class="line"> <span class="keyword">this</span>.cd.markForCheck();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你可以<a href="https://stackblitz.com/edit/angular-4xuug1" target="_blank" rel="noopener">在这查看</a>。</p><p>我们希望把值的比较与更新逻辑从 <code>NgDoCheck</code> 中移至订阅的回调函数,因为我们是从那里获取到新值的:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> BComponent {</span><br><span class="line"> <span class="meta">@Input</span>(<span class="string">'user'</span>) user$;</span><br><span class="line"> user = {name: <span class="literal">null</span>};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> cd: ChangeDetectorRef</span>) {}</span><br><span class="line"></span><br><span class="line"> ngOnInit() {</span><br><span class="line"> <span class="keyword">this</span>.user$.subscribe(<span class="function">(<span class="params">user</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.user.name !== user.name) {</span><br><span class="line"> <span class="keyword">this</span>.cd.markForCheck();</span><br><span class="line"> <span class="keyword">this</span>.user = user;</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><a href="https://stackblitz.com/edit/angular-lvtfve" target="_blank" rel="noopener">例子在这</a>。</p><p>有趣的是,这其实正是 <a href="https://github.com/maximusk/angular/blob/725bae1921cfcdcf5a5b0c35252c632198d1a7a4/packages/common/src/pipes/async_pipe.ts#L139" target="_blank" rel="noopener">AsyncPipe 背后的工作原理</a>:</p><figure class="highlight ts"><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><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Pipe</span>({name: <span class="string">'async'</span>, pure: <span class="literal">false</span>})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> AsyncPipe <span class="keyword">implements</span> OnDestroy, PipeTransform {</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> _ref: ChangeDetectorRef</span>) {}</span><br><span class="line"></span><br><span class="line"> transform(obj: ...): <span class="built_in">any</span> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">this</span>._subscribe(obj);</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>._latestValue === <span class="keyword">this</span>._latestReturnedValue) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>._latestReturnedValue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>._latestReturnedValue = <span class="keyword">this</span>._latestValue;</span><br><span class="line"> <span class="keyword">return</span> WrappedValue.wrap(<span class="keyword">this</span>._latestValue);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> _subscribe(obj): <span class="built_in">void</span> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">this</span>._strategy.createSubscription(</span><br><span class="line"> obj, <span class="function">(<span class="params">value: <span class="built_in">Object</span></span>) =></span> <span class="keyword">this</span>._updateLatestValue(obj, value));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> _updateLatestValue(<span class="keyword">async</span>: <span class="built_in">any</span>, value: <span class="built_in">Object</span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">async</span> === <span class="keyword">this</span>._obj) {</span><br><span class="line"> <span class="keyword">this</span>._latestValue = value;</span><br><span class="line"> <span class="keyword">this</span>._ref.markForCheck();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="那么那种解决方案更快?"><a href="#那么那种解决方案更快?" class="headerlink" title="那么那种解决方案更快?"></a>那么那种解决方案更快?</h2><p>现在我们知道如何使用手动进行变更检测而不是使用 AsyncPipe,让我们回答下最一开始的问题。那种方法更快?</p><p>嗯…这取决于你如何比较它们,但在其他条件相同的情况下,手动方法会更快。尽管我不认为两者会有明显区别。以下是为什么手动方法可以更快的几个例子。</p><p>就内存而言,您不需要创建 Pipe 类的实例。就编译时间而言,编译器不必花时间解析管道特定语法并生成管道特定输出。就运行时间而言,节省了异步管道为组件进行变更检测所调用的函数的时间。这个例子演示了当代码中包含 pipe 时 <a href="https://blog.angularindepth.com/the-mechanics-of-dom-updates-in-angular-3b2970d5c03d" target="_blank" rel="noopener">updateRenderer</a> 所生成的代码:</p><figure class="highlight ts"><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="params">_ck, _v</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> _co = _v.component;</span><br><span class="line"> <span class="keyword">var</span> currVal_0 = jit_unwrapValue_7(_v, <span class="number">3</span>, <span class="number">0</span>, asyncpipe.transform(_co.user)).name;</span><br><span class="line"> _ck(_v, <span class="number">3</span>, <span class="number">0</span>, currVal_0);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如您所见,异步管道的代码调用管道实例上的 <code>transform</code> 方法以获取新值。管道将返回从订阅中收到的最新值。</p><p>将其与为手动方法生成的普通代码进行比较:</p><figure class="highlight ts"><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="params">_ck,_v</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> _co = _v.component;</span><br><span class="line"> <span class="keyword">var</span> currVal_0 = _co.user.name;</span><br><span class="line"> _ck(_v,<span class="number">3</span>,<span class="number">0</span>,currVal_0);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这就是 Angular 在检查 <code>B</code> 组件时调用的方法。</p><h2 id="一些更有趣的事情"><a href="#一些更有趣的事情" class="headerlink" title="一些更有趣的事情"></a>一些更有趣的事情</h2><p>与执行浅比较的输入绑定不同,<strong>异步管道的实现根本不执行比较</strong>(感谢 <a href="https://medium.com/@sharlatenok" target="_blank" rel="noopener">Olena Horal</a> 注意到这一点)。它将每个新发射的值认为是更新,即使它与先前发射的值一样。下面的代码是父组件 <code>A</code> 的实现,它每次都发射出相同的对象。尽管如此,Angular 仍然会对 <code>B</code> 组件进行变更检测:</p><figure class="highlight ts"><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">export</span> <span class="keyword">class</span> AComponent {</span><br><span class="line"> o = {name: <span class="string">'A'</span>};</span><br><span class="line"> user = <span class="keyword">new</span> BehaviorSubject(<span class="keyword">this</span>.o);</span><br><span class="line"></span><br><span class="line"> changeName() {</span><br><span class="line"> <span class="keyword">this</span>.user.next(<span class="keyword">this</span>.o);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这意味着每次发出新值时,使用异步管道的组件都会被标记以进行检查。并且 Angular 将在下次运行变更检测时检查该组件,即使该值未更改。</p><p>这是应用于什么情况呢?嗯…在我们的例子中,我们只关注 <code>user</code> 对象的 <code>name</code> 属性,因为我们需要在模板中使用它。我们并不关心整个对象以及对象的引用可能会改变的事实。如果 name 没有发生改变,我们不需要重新渲染组件。但你无法用异步管道来避免这种情况。</p><p><code>NgDoCheck</code> 并不是没有问题:)由于仅在检查父组件时触发钩子,如果其中一个父组件使用 <code>OnPush</code> 策略并且在变更检测期间未检查,则不会触发该钩子。因此,当您通过服务收到新值时,不能依赖它来触发变更检测。在这种情况下,我在订阅回调中调用 <code>markForCheck</code> 方法是正确的解决方案。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>基本上,手动比较可以让您更好地控制检查。您可以定义何时需要检查组件。这与许多其他工具相同 - 手动控制为您提供了更大的灵活性,但您必须知道自己在做什么。为了获得这些知识,我鼓励您投入时间和精力学习和阅读<a href="https://blog.angularindepth.com/level-up-your-reverse-engineering-skills-8f910ae10630" target="_blank" rel="noopener">更多文章</a>。</p><p>你不用担心 <code>NgDoCheck</code> 生命周期钩子被调用的频率,或者它会比管道的 <code>transform</code> 方法更频繁地被调用。首先,我上面已经展示了解决方案,当使用异步流时,你应该在订阅的回调中而非在该钩子函数中手动执行变更检测。其次,只有在父组件被检测后才会调用该钩子函数。如果父组件没有被检查,则不会调用该钩子。对于管道而言,由于流中的浅比较和更改引用的原因,管道的 <code>transform</code> 方法被调用的次数只会和手动方法相同甚至更多。</p><h2 id="想要了解更过关于-Angular-中-change-detection-的相关知识?"><a href="#想要了解更过关于-Angular-中-change-detection-的相关知识?" class="headerlink" title="想要了解更过关于 Angular 中 change detection 的相关知识?"></a>想要了解更过关于 Angular 中 change detection 的相关知识?</h2><p>从这5篇文章入手会让你成为 Angular 变更检测机制的专家。如果你想要牢固掌握 Angular 中变更检测机制,那么这一系列的文章是必读的。每一篇文章都会基于前一篇文章中所解释的相关信息,既包含高层次的概述又囊括了具体的实现细节,并且都附有相关源代码。</p>]]></content>
<summary type="html">
<h1 id="翻译-OnPush-组件中-NgDoCheck-和-AsyncPipe-的区别"><a href="#翻译-OnPush-组件中-NgDoCheck-和-AsyncPipe-的区别" class="headerlink" title="[翻译] OnPush 组件
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
</entry>
<entry>
<title>【翻译】调试Rxjs(二):日志记录</title>
<link href="http://tanyibing.com/2018/11/25/Rxjs%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E6%97%A5%E5%BF%97%E8%AE%B0%E5%BD%95/"/>
<id>http://tanyibing.com/2018/11/25/Rxjs(二):日志记录/</id>
<published>2018-11-25T15:32:52.000Z</published>
<updated>2018-11-25T15:41:38.794Z</updated>
<content type="html"><![CDATA[<h1 id="翻译-调试-Rxjs(二):日志记录"><a href="#翻译-调试-Rxjs(二):日志记录" class="headerlink" title="[翻译] 调试 Rxjs(二):日志记录"></a>[翻译] 调试 Rxjs(二):日志记录</h1><blockquote><p>原文:<a href="https://blog.angularindepth.com/debugging-rxjs-part-2-logging-56904459f144" target="_blank" rel="noopener">Debugging RxJS, Part 2: Logging</a></p><p>译者:<a href="https://github.com/TanYiBing" target="_blank" rel="noopener">Ice Panpan</a>;校验者:暂无</p></blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/17/banner_01.jpeg?raw=true" alt="banner" title=""> </div> <div class="image-caption">banner</div> </figure><p>日志记录并不是一件让人兴奋的事。</p><p>然而,这是获得足够的信息来推理问题最直接的方法,而不需要去猜测。它通常是调试 <code>RxJS</code> 代码的首选方法。这是这个系列文章的第二篇,专注于使用日志记录来解决实际问题。在第一篇<a href="./16.[翻译]-调试-Rxjs(一):工具.md">调试 Rxjs(一):工具</a>中,主要介绍的是 <a href="https://github.com/cartant/rxjs-spy" target="_blank" rel="noopener"><code>rxjs-spy</code></a>。在本文中,我将展示如何使用 <code>rxjs-spy</code> 以最小的影响来获取详细并有针对性的信息。</p><p>让我们看一个使用 <code>rxjs</code> 和 <code>rxjs-spy</code> UMD捆绑的简单案例:</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">RxSpy.spy();</span><br><span class="line">RxSpy.log(<span class="regexp">/user-.+/</span>);</span><br><span class="line">RxSpy.log(<span class="string">'users'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> names = [<span class="string">'benlesh'</span>, <span class="string">'kwonoj'</span>, <span class="string">'staltz'</span>];</span><br><span class="line"><span class="keyword">const</span> users = Rx.Observable.forkJoin(...names.map(<span class="function"><span class="params">name</span> =></span></span><br><span class="line"> Rx.Observable</span><br><span class="line"> .ajax</span><br><span class="line"> .getJSON(<span class="string">`https://api.github.com/users/<span class="subst">${name}</span>`</span>)</span><br><span class="line"> .tag(<span class="string">`user-<span class="subst">${name}</span>`</span>)</span><br><span class="line">))</span><br><span class="line">.tag(<span class="string">'users'</span>);</span><br><span class="line"></span><br><span class="line">users.subscribe();</span><br></pre></td></tr></table></figure><p>该示例使用 <code>forkJoin</code> 组合了一个用来发射出GitHub用户数组的 <code>Observable</code>。</p><p><code>rxjs-spy</code> 使用 <code>tag</code> 操作符来标记 <code>Observable</code>,并且仅仅通过字符串来给 <code>Observable</code> 注释。这个示例在组合 <code>Observable</code> 之前,首先启用监听功能,并配置了哪些 <code>Observable</code> 要被记录——匹配 <code>/user-.+/</code> 的正则表达式或者带有 <code>users</code> 标签的那些 <code>Observable</code>。</p><p>这个示例的控制台输出如下:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/17/console_01.png?raw=true" alt="Console_01" title=""> </div> <div class="image-caption">Console_01</div> </figure><p>除了 <code>Observable</code> 的 <code>next</code> 和 <code>complete</code> 的通知之外,记录的输出还包括订阅和取消订阅的通知。它显示了发生的一切:</p><ol><li>对组合的 <code>Observable</code> 的订阅影响的每一个用户API请求的 <code>Observable</code> 的并行订阅;</li><li>请求以任意顺序完成;</li><li><code>Observable</code> 全部完成;</li><li>并且在全部完成后取消对组合 <code>Observable</code> 的订阅。</li></ol><p>每个记录的通知还包括有关接受通知的订阅者的信息——包括订阅者具有的订阅量以及 <code>subscribe</code> 调用的堆栈痕迹:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/17/console_02.png?raw=true" alt="Console_02" title=""> </div> <div class="image-caption">Console_02</div> </figure><p>堆栈痕迹指的是 <code>subscribe</code> 调用的根——即影响订阅者对 <code>Observable</code> 订阅的显式调用。因此,用户请求的 <code>Observable</code> 的堆栈痕迹也参考了 <code>medium.js</code> 中的 <code>subscribe</code> 调用:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/17/console_03.png?raw=true" alt="Console_03" title=""> </div> <div class="image-caption">Console_03</div> </figure><p>当我调试时,我发现知道调用 <code>subscribe</code> 的实际的根位置比知道组合 <code>Observable</code> 中某个 <code>subscribe</code> 的位置更有用。</p><p>现在让我们看一个现实中实际的问题。</p><p>在编写 <code>redux-observable epics</code> 或 <code>ngrx effects</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">import</span> { Observable } <span class="keyword">from</span> <span class="string">'rxjs/Observable'</span>;</span><br><span class="line"><span class="keyword">import</span> { ajax } <span class="keyword">from</span> <span class="string">'rxjs/observable/dom/ajax'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> getRepos = <span class="function"><span class="params">action$</span> =></span></span><br><span class="line"> action$.ofType(<span class="string">'REPOS_REQUEST'</span>)</span><br><span class="line"> .map(<span class="function"><span class="params">action</span> =></span> action.payload.user)</span><br><span class="line"> .switchMap(<span class="function"><span class="params">user</span> =></span> ajax.getJSON(<span class="string">`https://api.notgithub.com/users/<span class="subst">${user}</span>/repos`</span>))</span><br><span class="line"> .map(<span class="function"><span class="params">repos</span> =></span> { <span class="attr">type</span>: <span class="string">'REPOS_RESPONSE'</span>, <span class="attr">payload</span>: { repos } })</span><br><span class="line"> .catch(<span class="function"><span class="params">error</span> =></span> Observable.of({ <span class="attr">type</span>: <span class="string">'REPOS_ERROR'</span> }))</span><br><span class="line"> .tag(<span class="string">'getRepos'</span>);</span><br></pre></td></tr></table></figure><p>乍一看,这看上去还不错。大部分时间它都可以正常工作,它是那种可以偷偷混过单元测试的bug。</p><p>问题是,它有时会停止工作,特别是在一个错误的动作发生之后。</p><p>记录显示正在发生的事:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/17/console_04.png?raw=true" alt="Console_04" title=""> </div> <div class="image-caption">Console_04</div> </figure><p>在错误的动作被发射出去之后,看到 <code>redux-observalbe</code>基础结构从epic中解除订阅的 <code>Observable</code> 完成了。该<a href="http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-catch" target="_blank" rel="noopener">文档</a>的 <code>catch</code> 解释了为什么出现这种情况。</p><blockquote><p>无论 <code>selector</code> 返回了什么样的 <code>Observable</code>,都将连在 <code>Observable</code> 链后面。</p></blockquote><p>在这个epic中,<code>catch</code> 完成后返回的 <code>Observable</code> 也看到了epic的完成。</p><p>解决的方案是将 <code>map</code> 和 <code>catch</code> 移到 <code>switchMap</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><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { Observable } <span class="keyword">from</span> <span class="string">'rxjs/Observable'</span>;</span><br><span class="line"><span class="keyword">import</span> { ajax } <span class="keyword">from</span> <span class="string">'rxjs/observable/dom/ajax'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> getRepos = <span class="function"><span class="params">action$</span> =></span></span><br><span class="line"> action$.ofType(<span class="string">'REPOS_REQUEST'</span>)</span><br><span class="line"> .map(<span class="function"><span class="params">action</span> =></span> action.payload.user)</span><br><span class="line"> .switchMap(<span class="function"><span class="params">user</span> =></span> ajax</span><br><span class="line"> .getJSON(<span class="string">`https://api.notgithub.com/users/<span class="subst">${user}</span>/repos`</span>)</span><br><span class="line"> .map(<span class="function"><span class="params">repos</span> =></span> { <span class="attr">type</span>: <span class="string">'REPOS_RESPONSE'</span>, <span class="attr">payload</span>: { repos } })</span><br><span class="line"> .catch(<span class="function"><span class="params">error</span> =></span> Observable.of({ <span class="attr">type</span>: <span class="string">'REPOS_ERROR'</span> }))</span><br><span class="line"> )</span><br><span class="line"> .tag(<span class="string">'getRepos'</span>);</span><br></pre></td></tr></table></figure><p>这个epic将不再完成,并继续发送错误动作:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/17/console_05.png?raw=true" alt="Console_05" title=""> </div> <div class="image-caption">Console_05</div> </figure><p>在这两个示例中,需要对正在调试的代码进行的唯一修改是添加了一些标记注释。</p><p>注释的影响很小,一旦添加,我倾向于将它们留在代码中。标签运算符可以独立于诊断 <code>rxjs-spy</code> 使用——使用<code>rxjs-spy/add/operator/tag</code> 或直接导入 <code>rxjs-spy/operator/tag</code>,因此保留标记的开销很小。</p><p>可以使用正则表达式配置记录器,这可以产生许多可能的标记方法。例如,使用复合标记,例如 <code>github/users</code> 和 <code>github/repos</code> 将允许您为所有被标记后存储在存储库里的 <code>github Observable</code> 启用日志记录。</p><p>记录并不令人兴奋,但可以从记录的输出中收集的信息通常可以节省大量时间。采用灵活的标记方法可以进一步减少处理与日志记录相关的代码所花费的时间。</p>]]></content>
<summary type="html">
<h1 id="翻译-调试-Rxjs(二):日志记录"><a href="#翻译-调试-Rxjs(二):日志记录" class="headerlink" title="[翻译] 调试 Rxjs(二):日志记录"></a>[翻译] 调试 Rxjs(二):日志记录</h1><bloc
</summary>
<category term="Rxjs" scheme="http://tanyibing.com/categories/Rxjs/"/>
<category term="Rxjs" scheme="http://tanyibing.com/tags/Rxjs/"/>
</entry>
<entry>
<title>【翻译】调试Rxjs(一):工具</title>
<link href="http://tanyibing.com/2018/11/19/%E3%80%90%E7%BF%BB%E8%AF%91%E3%80%91%E8%B0%83%E8%AF%95Rxjs%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%B7%A5%E5%85%B7/"/>
<id>http://tanyibing.com/2018/11/19/【翻译】调试Rxjs(一):工具/</id>
<published>2018-11-19T12:34:46.000Z</published>
<updated>2018-11-19T12:58:01.293Z</updated>
<content type="html"><![CDATA[<h1 id="翻译-调试Rxjs(一):工具"><a href="#翻译-调试Rxjs(一):工具" class="headerlink" title="[翻译] 调试Rxjs(一):工具"></a>[翻译] 调试Rxjs(一):工具</h1><blockquote><p>原文:<a href="https://blog.angularindepth.com/debugging-rxjs-4f0340286dd3" target="_blank" rel="noopener">Debugging RxJS, Part 1: Tooling</a></p></blockquote><p>我是一个 <code>Rxjs</code> 的信仰者,我在我所有的项目中都使用 <code>Rxjs</code>。有了 <code>Rxjs</code>,我发现很多曾经觉得乏味的事都变得痛快。但是有一件事不是这样:调试。</p><p><code>Rxjs</code> 中异步的本质在组合之后让调试变得更具挑战性:没有太多的状态(state)供你检查,并且调用堆栈(call stack)也帮不了太大的忙。我过去使用的方法是在整个代码多处添加 <code>do</code> 操作符并且记录,以此来检查那些组合的 <code>Observable</code> 产生的值。这并不是我想要的方法,因为:</p><ol><li>当我在调试时修改代码,我不得不进行更多的日志记录工作;</li><li>当调试结束之后,我必须删除日志记录或者注释掉它;</li><li>当在一个正常的组合Observable中存在‘拍扁’的操作时,如何进行日志记录需要格外的小心。</li><li>就算是专门有的 <code>log</code> 操作符,结果也不会很理想。</li></ol><p>最近,我留了一些时间来为 <code>Rxjs</code> 构建一个调试工具,我觉得这个工具必须具备以下的功能:</p><ol><li>应该尽可能的不显眼;</li><li>不需要通过修改代码来进行调试;</li><li>在调试结束后,不需要删除或注释掉调试的代码;</li><li>应该可以轻松的启用和禁用日志记录;</li><li>它应该提供与浏览器控制台的一些集成————用于打开/关闭调试用能和检查状态等。</li></ol><p>如果想要更完美,还要一些东西:</p><ol><li>它应该支持暂停 <code>Observable</code>;</li><li>它应该支持修改 <code>Observable</code> 或者它们发出的值;</li><li>它应该支持控制台意外的日志记录机制;</li><li>它应该是可扩展的;</li><li>它应该在某种程度上可以捕获可视化订阅依赖关系所需的数据。</li></ol><p>考虑到这些功能,我建立了 <code>rxjs-spy</code>。</p><h2 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h2><p><code>rxjs-spy</code> 引入了 <code>tag</code> 操作符,将字符串标记与 <code>Observable</code> 相关联。这个操作符不会以任何方式更改 <code>Observable</code> 的行为或值。</p><p><code>tag</code> 操作符可以被单独导入: <code>import "rxjs-spy/add/operator/tag"</code>。并且其他的 <code>rxjs-spy</code> 方法可以在生产环境下省略,所以唯一的开销就是字符串注释。</p><p>大多数工具的方法接受匹配器,以确定它们将应用于哪些标记的 <code>Observable</code> 。匹配器可以是传递标签本身的简单字符串,正则表达式或谓词。</p><p>通过调用 <code>spy</code> 来配置工具时,它会修改 <code>Observable.prototype.subscribe</code> 来监听所有的 <code>subscriptions</code>, <code>notifications</code> 和 <code>unsubscriptions</code>。这也就是意味着,只有已经被订阅的 <code>Observable</code> 才会被监听。</p><p><code>rxjs-spy</code> 公开了一个旨在从代码中调用的模块API和一个用于在浏览器控制台中交互使用的控制台API。大多数时候,我早早地在应用程序启动代码里条用模块API的方法,并使用控制台API进行剩余的调试。</p><h2 id="控制台API功能"><a href="#控制台API功能" class="headerlink" title="控制台API功能"></a>控制台API功能</h2><p>在调试时,我通常使用浏览器的控制台来检查和操作标记的 <code>Observables</code>。控制台API功能最容易通过示例解释:</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><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { Observable } <span class="keyword">from</span> <span class="string">"rxjs/Observable"</span>;</span><br><span class="line"><span class="keyword">import</span> { spy } <span class="keyword">from</span> <span class="string">"rxjs-spy"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"rxjs/add/observable/interval"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"rxjs/add/operator/map"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"rxjs/add/operator/mapTo"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"rxjs-spy/add/operator/tag"</span>;</span><br><span class="line"></span><br><span class="line">spy();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> interval = <span class="keyword">new</span> Observable</span><br><span class="line"> .interval(<span class="number">2000</span>)</span><br><span class="line"> .tag(<span class="string">"interval"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> people = interval</span><br><span class="line"> .map(<span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> names = [<span class="string">"alice"</span>, <span class="string">"bob"</span>];</span><br><span class="line"> <span class="keyword">return</span> names[value % names.length];</span><br><span class="line"> })</span><br><span class="line"> .tag(<span class="string">"people"</span>)</span><br><span class="line"> .subscribe();</span><br></pre></td></tr></table></figure><p><code>rxjs-spy</code> 中的控制台API通过 <code>rxSpy</code> 在全局暴露。</p><p>调用 <code>rxSpy.show()</code> 将显示所有已经被标记的 <code>Observable</code> 的列表,指示其状态(<code>incomplete</code>,<code>complete</code> 或者 <code>errored</code>),订阅者(<code>subscribers</code>)的数量和最近发出的值(如果已经发出一个值)。控制台输出将如下所示:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/1.png" alt="Console_1" title=""> </div> <div class="image-caption">Console_1</div> </figure><p>要显示特定标记的 <code>Observable</code> 的信息,可以将标记名称或者正则表达式传递给 <code>show</code>:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/2.png" alt="Console_2" title=""> </div> <div class="image-caption">Console_2</div> </figure><p>可以通过调用 <code>rxSpy.log</code> 来显示被标记的 <code>Observable</code> 的日志信息:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/3.png" alt="Console_3" title=""> </div> <div class="image-caption">Console_3</div> </figure><p><code>log</code> 不带参数调用将会显示所有标记的 <code>Observable</code> 的日志记录。</p><p>模块API中的大多数方法都返回一个撤销功能,可以调用该功能来撤销方法调用。在控制台中,管理起来很繁琐,所以还有另一种选择。</p><p>调用 <code>rxSpy.undo()</code> 将显示已经调用的方法的列表:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/4.png" alt="Console_4" title=""> </div> <div class="image-caption">Console_4</div> </figure><p>调用 <code>rxSpy.undo</code> 并传递与方法调用关联的数字将看到该调用的撤销函数被执行。例如,调用 <code>rxSpy.undo(3)</code> 将看到被标记为 <code>interval</code> 的 <code>Observable</code> 的记录被撤销之后的结果:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/5.png" alt="Console_5" title=""> </div> <div class="image-caption">Console_5</div> </figure><p>有时,在调试时修改 <code>Observable</code> 或其值时,这个方法就很有用。控制台API包含一种 <code>let</code> 方法,其功能与 <code>RxJS</code> 中的 <code>let</code> 操作符大致相同。它的实现方式是通过调用 <code>let</code> 方法对标记的 <code>Observable</code> 的当前和未来的订阅者产生影响。例如。以下调用将看到 <code>people</code> <code>Observable</code> 发射 <code>mallory</code> 而不是 <code>alice</code> 或 <code>bob</code>:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/6.png" alt="Console_6" title=""> </div> <div class="image-caption">Console_6</div> </figure><p>与 <code>log</code> 方法一样,<code>let</code> 可以撤消对该方法的调用:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/7.png" alt="Console_7" title=""> </div> <div class="image-caption">Console_7</div> </figure><p>能够在调试时暂停一个 <code>Observable</code> 对我来说几乎是不可或缺的。调用 <code>rxSpy.pause</code> 将暂停一个标记的 <code>Observable</code>,并返回一个可用于控制和检查 <code>Observable</code> 的通知(<code>notifications</code>)的 <code>deck</code>:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/8.png" alt="Console_8" title=""> </div> <div class="image-caption">Console_8</div> </figure><p>在该 <code>deck</code> 上调用 <code>log</code> 将显示 <code>Observable</code> 是否暂停,并显示被暂停的通知(<code>notifications</code>)。(通知是 <code>Notification</code> 使用 <code>materialize</code> 操作符获得的rxjs实例)</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/9.png" alt="Console_9" title=""> </div> <div class="image-caption">Console_9</div> </figure><p>在 <code>deck</code> 上调用 <code>step</code> 将发出一个被暂停住的通知(<code>notifications</code>):</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/10.png" alt="Console_10" title=""> </div> <div class="image-caption">Console_10</div> </figure><p>调用 <code>resume</code> 将发出所有被暂停的通知(<code>notifications</code>),并将恢复 <code>Observable</code>:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/11.png" alt="Console_11" title=""> </div> <div class="image-caption">Console_11</div> </figure><p>调用 <code>pause</code> 将看到 <code>Observable</code> 重新进入暂停状态:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/12.png" alt="Console_12" title=""> </div> <div class="image-caption">Console_12</div> </figure><p>很容易忘记将返回的 <code>deck</code> 分配给变量,因此控制台API包含一个 <code>deck</code> 方法,和 <code>undo</code> 方法行为相似。调用它将显示 <code>pause</code> 调用的列表:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/13.png" alt="Console_13" title=""> </div> <div class="image-caption">Console_13</div> </figure><p>调用它并传递与调用相关联的数字将返回相对应的 <code>deck</code> :</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/14.png" alt="Console_14" title=""> </div> <div class="image-caption">Console_14</div> </figure><p>像 <code>log</code> 和 <code>let</code> 的调用一样,<code>pause</code> 的调用也可以撤销。撤销 <code>pause</code> 的调用将看到标记的 <code>Observable</code> 恢复正常:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/rxjs/15.png" alt="Console_15" title=""> </div> <div class="image-caption">Console_15</div> </figure><p>希望以上的例子可以对 <code>rxjs-spy</code> 的控制台API进行一个概述。<code>Debugging RxJS</code> 的后续部分将重点介绍 <code>rxjs-spy</code> 的具体功能以及如何使用它们来解决实际的调试问题。</p><p>对我来说,<code>rxjs-spy</code> 肯定让调试Rxjs不再那么繁琐。</p><h2 id="更多信息"><a href="#更多信息" class="headerlink" title="更多信息"></a>更多信息</h2><p><code>rxjs-spy</code> 的代码可以在 <a href="https://github.com/cartant/rxjs-spy" target="_blank" rel="noopener">GitHub</a>上找到,并且有一个<a href="https://cartant.github.io/rxjs-spy/" target="_blank" rel="noopener">在线的控制台API示例</a>。</p><p>该包可以通过<a href="https://www.npmjs.com/package/rxjs-spy" target="_blank" rel="noopener">NPM</a>进行安装。</p><p>有关本系列的一下篇文章,正在翻译中,敬请期待。。。</p>]]></content>
<summary type="html">
<h1 id="翻译-调试Rxjs(一):工具"><a href="#翻译-调试Rxjs(一):工具" class="headerlink" title="[翻译] 调试Rxjs(一):工具"></a>[翻译] 调试Rxjs(一):工具</h1><blockquote>
<p>原
</summary>
<category term="Rxjs" scheme="http://tanyibing.com/categories/Rxjs/"/>
<category term="Rxjs" scheme="http://tanyibing.com/tags/Rxjs/"/>
</entry>
<entry>
<title>api接口跨域问题</title>
<link href="http://tanyibing.com/2018/11/08/api%E6%8E%A5%E5%8F%A3%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98/"/>
<id>http://tanyibing.com/2018/11/08/api接口跨域问题/</id>
<published>2018-11-08T06:55:20.000Z</published>
<updated>2018-11-18T04:37:35.609Z</updated>
<content type="html"><![CDATA[<p>今天在自己的项目中想要提供一些api,暴露出来想要提供数据,下面是我的api接口,很简单:</p><pre><code>const router = require('koa-router')();router.get('/', async (ctx) => { ctx.body = 'api';});router.get('/catelist', async (ctx) => { let catelist = await DB.find('articlecate', {}); ctx.body = { result: catelist }});module.exports = router.routes();</code></pre><p>我们一般调用会怎么写呢?如下:</p><pre><code>$(function(){ $('#button').click(function(){ $.getJSON('http://localhost:8000/api/catelist', function(data){ onsole.log(data); }) })})</code></pre><p>我们通过点击一个按钮来接收数据。但是一旦我们<code>跨域</code>之后这样子就没办法了。啥叫跨域呢,就是当我们的域名、端口、协议中只要有一个不一致,就请求不到数据啦,那一般的解决方法怎么整呢?我们可以用<code>jsonp</code>。</p><p><code>jsonp</code>是个什么原理呢?就是利用<code>script src</code>可以跨域的特性来实现。拢共分两步:</p><ol><li>本地写一个回调函数</li><li>在远程执行这个回调函数,把远程的数据传到本地</li></ol><p>下面给个例子:</p><pre><code>function xxxx(data){ console.log(data);}</code></pre><p>咋们先写个函数,然后用个script来远程调用下:</p><pre><code><script src="http://localhost:8000/api/catelist?callback=xxxx"></script></code></pre><p>没错,咋们在后面加上<code>callback</code>就行啦。再看一个jquery版本的:</p><pre><code>$(function(){ var url='http://localhost:8000/api/catelist'; $.ajax({ url:url, dataType:'jsonp', /*定义jsonp请求*/ data:'', /*get传值*/ jsonp:'callback', /*回调函数的参数*/ success:function(data) { console.log(data); }, timeout:3000 /*超时时间*/ });})</code></pre><p><strong><code>但是这样做有一个前提,那就是后台要允许jsonp,但这样的话会极度不安全,怎么解决呢,我们一般是加上数字签名来保证安全。</code></strong></p>]]></content>
<summary type="html">
<p>今天在自己的项目中想要提供一些api,暴露出来想要提供数据,下面是我的api接口,很简单:</p>
<pre><code>const router = require(&apos;koa-router&apos;)();
router.get(&apos;/&apos;,
</summary>
<category term="Ajax" scheme="http://tanyibing.com/categories/Ajax/"/>
<category term="Node" scheme="http://tanyibing.com/tags/Node/"/>
<category term="Ajax" scheme="http://tanyibing.com/tags/Ajax/"/>
</entry>
<entry>
<title>帝国CMS使用总结</title>
<link href="http://tanyibing.com/2018/11/07/%E5%B8%9D%E5%9B%BDCMS%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/"/>
<id>http://tanyibing.com/2018/11/07/帝国CMS使用总结/</id>
<published>2018-11-07T08:53:15.000Z</published>
<updated>2018-11-18T04:37:35.626Z</updated>
<content type="html"><![CDATA[<p>公司的后台是使用的帝国CMS,想要干好活,总得会用啊。好吧,摸摸索索的用了一段时间,也草草的总结一下吧。</p><ol><li>首先后台啥最重要,肯定是数据,所以系统模型也可以叫数据模型也就显得很重要,里面的字段要考虑的周到一些,以便复用,否则改起来就很蛋疼了。</li><li>帝国CMS中还有两个灵魂般的东西:<code>“万能标签(ecmsinfo)”</code>、<code>“灵动标签(e:loop)”</code>,这两玩意儿其中之一只要会了,模版都是一把唆。明天我得找个时间好好看一下这个武功秘籍。</li><li>以上两个基本是最重要的东东了,数据加模版,站就起来了。但因为不是自己写的后台系统,所以有些东西还是要去改代码的,比如分页这个玩意,你说说你要在前端实现分页你该咋整,又不能去查数据库,所以就还是要找到相应的代码,改一改样式啥的,建站的话基本就只需要这些知识吧,像类似采集啥玩意儿的目前为止还没有遇到过,所以不管了。</li></ol>]]></content>
<summary type="html">
<p>公司的后台是使用的帝国CMS,想要干好活,总得会用啊。好吧,摸摸索索的用了一段时间,也草草的总结一下吧。</p>
<ol>
<li>首先后台啥最重要,肯定是数据,所以系统模型也可以叫数据模型也就显得很重要,里面的字段要考虑的周到一些,以便复用,否则改起来就很蛋疼了。</li
</summary>
<category term="CMS" scheme="http://tanyibing.com/categories/CMS/"/>
<category term="CMS" scheme="http://tanyibing.com/tags/CMS/"/>
</entry>
<entry>
<title>bootstrap使用总结</title>
<link href="http://tanyibing.com/2018/11/06/bootstrap%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/"/>
<id>http://tanyibing.com/2018/11/06/bootstrap使用总结/</id>
<published>2018-11-06T13:05:57.000Z</published>
<updated>2018-11-18T04:37:35.615Z</updated>
<content type="html"><![CDATA[<p>好久咩有写博客了,因为最近在撸很多页面,使用的是<code>Bootstrap</code>,因为设计没有给我两套设计图,所以只能使用这个框架来兼容移动端。现在也可以总结下我使用过程中掌握了啥新知识:</p><ol><li>因为我用的是3这个大版本,所以设备还是分为四种:xs(768)、sm(768-992)、md(992-1200)、lg(1200)。不用说了,这是栅格系统的核心,通过媒体查询来将一行分为12列的。每个col都会带有左右两边15px的padding。</li><li><code>container</code>和<code>container-fluid</code>的区别是container-fluid是撑满整个屏幕的宽度,但是他两都有左右两边各15px的padding,为的是防止文字显示触摸到屏幕边界,如果不想要这个padding,可以在<code>container</code>和<code>container-fluid</code>中加上<code>row</code>,因为row的左右margin都是-15px,正好抵消padding。关于这个padding和margin其实可以很巧妙的使用,如果我们想在column中嵌套column,先把要被嵌套的column放到row中,再把row放到作为容器的column中,而不需要在放置一个container。这也是因为<code>Bootstrap</code>在一开始便说了:<blockquote><p>你的内容应当放置于“列(column)”内,并且,只有“列(column)”可以作为行(row)”的直接子元素。</p><p>“行(row)”必须包含在 .container (固定宽度)或 .container-fluid (100% 宽度)中,以便为其赋予合适的排列(aligment)和内补(padding)。</p></blockquote></li><li>还有一点是container和container-fluid是不能嵌套的,这一点我发现公司之前的项目中很多地方都没有注意。</li><li>我们还可以通过<code>col-xxx-offset-xx</code>、<code>col-xxx-pull-xx</code>、<code>col-xxx-pull-xx</code>来实现跳过xx列、向右推xx列、向左推xx列的效果。</li><li>当我的有先样式在移动端实在是不好展现的时候,这时我们就需要两个不一样的结构,一个在大屏上展示,一个在小屏上展示。这就需要用到<code>visibile-xs-xxx</code>、<code>hide-xs-xxx</code>、<code>visibile-xs-block/inline/block-inline</code>这些来控制展示。</li><li>还有一点是form表单一定要添加<code>label</code>然后可以通过设置<code>.sr-only</code>来隐藏,这是为了给盲人更好的体验,同时也是seo的一种技巧。</li></ol><p>以上是一些零零散散的感悟。我在工作的时候发现,有些人不能拎清<code>自适应</code>和<code>响应式</code>的区别,嘴里老说着‘自适应、自适应’,却只拿出一套设计图。其实响应式才是一套设计适应所有端,而自适用是需要多套设计图的。</p><p>在开发过程中发现有的地方的结构在移动端就是不好看,就需要改变结构,一旦地方多了的话其实不如设计两套。</p><p>还有一点就是bootstrap的定制,定制比较方便,可以节省很多代码,其实还有更好的方法就是修改源码,这样的话我们的自由度更高,可以更加深入的定制。</p>]]></content>
<summary type="html">
<p>好久咩有写博客了,因为最近在撸很多页面,使用的是<code>Bootstrap</code>,因为设计没有给我两套设计图,所以只能使用这个框架来兼容移动端。现在也可以总结下我使用过程中掌握了啥新知识:</p>
<ol>
<li>因为我用的是3这个大版本,所以设备还是分为四种
</summary>
<category term="Bootstrap" scheme="http://tanyibing.com/categories/Bootstrap/"/>
<category term="Bootstrap" scheme="http://tanyibing.com/tags/Bootstrap/"/>
</entry>
<entry>
<title>angular写一个联动表单控件</title>
<link href="http://tanyibing.com/2018/10/18/angular%E5%86%99%E4%B8%80%E4%B8%AA%E8%81%94%E5%8A%A8%E8%A1%A8%E5%8D%95%E6%8E%A7%E4%BB%B6/"/>
<id>http://tanyibing.com/2018/10/18/angular写一个联动表单控件/</id>
<published>2018-10-18T11:48:48.000Z</published>
<updated>2018-10-30T13:53:26.281Z</updated>
<content type="html"><![CDATA[<p>首先展示下这个表单的样式:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/form/1.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>这就是我们需要设计成的表单样式,从图中我们可以看出这个表单可以分成三个部分:</p><ol><li>证件输入部分</li><li>出生日期部分</li><li>地址部分</li></ol><p>我们不应该将这些部分都写在一起,因为这样我们的表单将会特别复杂,因为我们将所有的逻辑都写在了一起,而且也不适合复用。所有我们将这三个部分写成三个控件,下面就分别给出模版:</p><ol><li><p>证件输入部分模版:</p><pre><code><div> <mat-form-field> <mat-select placeholder="证件类型" (ngModelChange)="onIdTypeChange($event.value)" [(ngModel)]="identity.identityType"> <mat-option *ngFor="let type of identityTypes" [value]="type.value"> {{type.label}} </mat-option> </mat-select> </mat-form-field></div><div class="id-input"> <mat-form-field> <input matInput type="text" placeholder="证件号码" (change)="onIdNoChange($event.target.value)" [(ngModel)]="identity.identityNo"> <mat-error>证件号码输入有误</mat-error> </mat-form-field></div></code></pre></li><li><p>出生日期部分模版:</p><pre><code><div [formGroup]="form"> <div> <mat-form-field> <input matInput [matDatepicker]="birthPicker" type="text" placeholder="出生日期" formControlName="birthday"> <mat-datepicker-toggle matSuffix [for]="birthPicker"></mat-datepicker-toggle> <mat-datepicker #dueDatePicker></mat-datepicker> <mat-error>日期不正确</mat-error> </mat-form-field> <mat-datepicker touchUi="true" #birthPicker></mat-datepicker> </div> <ng-container formGroupName="age"> <div> <mat-form-field> <input matInput type="number" placeholder="年龄" formControlName="ageNum"> </mat-form-field> </div> <div> <mat-button-toggle-group formControlName="ageUnit" [(ngModel)]="selectedUnit"> <mat-button-toggle *ngFor="let unit of ageUnits" [value]="unit.value"> {{ unit.label }} </mat-button-toggle> </mat-button-toggle-group> </div> <mat-error class="mat-body-2" *ngIf="form.get('age').hasError('ageInvalid')">年龄或单位不正确</mat-error> </ng-container></div></code></pre></li></ol><ol start="3"><li><p>地址部分模版:</p><pre><code><div class="address-group"> <mat-form-field> <mat-select placeholder="请选择省份" [(ngModel)]="_address.province" (ngModelChange)="onProvinceChange()"> <mat-option *ngFor="let p of provinces$ | async" [value]="p"> {{ p }} </mat-option> </mat-select> </mat-form-field> <mat-form-field> <mat-select placeholder="请选择城市" [(ngModel)]="_address.city" (ngModelChange)="onCityChange()"> <mat-option *ngFor="let c of cities$ | async" [value]="c"> {{ c }} </mat-option> </mat-select> </mat-form-field> <mat-form-field> <mat-select placeholder="请选择区县" [(ngModel)]="_address.district" (ngModelChange)="onDistrictChange()"> <mat-option *ngFor="let d of districts$ | async" [value]="d"> {{ d }} </mat-option> </mat-select> </mat-form-field> <div class="street"> <mat-form-field> <input matInput placeholder="街道地址" [(ngModel)]="_address.street" (ngModelChange)="onStreetChange()"> </mat-form-field> </div></div></code></pre></li></ol><p>我使用的是<code>material</code>主题,模版看起来可能有点复杂。接下来我们分别看下每个模块需要实现的效果:</p><ol><li><p>证件部分就是正常的输入,但是后面要实现一个联动的效果,因为省份证包含了很多信息。</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/form/shenfen.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote></li><li><p>年龄部分本身就需要一个联动,效果如下:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/form/age.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote></li><li><p>地区部分需要需要根据其他选择项进行筛选:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/form/area.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote></li><li><p>最后我们需要有一个根据身份证信息,得到我们地址和生日的联动,最终的效果如下:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/form/form.gif" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote></li></ol><p>效果我们有已经看到了,接下来就要对各个控件进行编写了:</p><ol><li><p>证件部分:</p><pre><code>import { Component, OnInit, forwardRef, OnDestroy, ChangeDetectionStrategy } from '@angular/core';import { NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor, FormControl } from '@angular/forms';import { Subscription, Subject, Observable, combineLatest } from 'rxjs';import { IdentityType, Identity } from '../../domain';@Component({ selector: 'app-identity-input', templateUrl: './identity-input.component.html', styleUrls: ['./identity-input.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => IdentityInputComponent), multi: true, }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => IdentityInputComponent), multi: true, } ], changeDetection: ChangeDetectionStrategy.OnPush,})export class IdentityInputComponent implements ControlValueAccessor, OnInit, OnDestroy { identityTypes: { value: IdentityType, label: string }[] = [ { value: IdentityType.IdCard, label: '身份证' }, { value: IdentityType.Insurance, label: '医保' }, { value: IdentityType.Passport, label: '护照' }, { value: IdentityType.Military, label: '军官证' }, { value: IdentityType.Other, label: '其它' } ]; identity: Identity = { identityType: null, identityNo: null }; private _idType = new Subject<IdentityType>(); private _idNo = new Subject<string>(); private _sub: Subscription; private propagateChange = (_: any) => { }; constructor() { } ngOnInit() { const val$ = combineLatest(this.idType, this.idNo, (_type, _no) => { return { identityType: _type, identityNo: _no }; }); this._sub = val$.subscribe(v => { this.identity = v; this.propagateChange(v); }); } ngOnDestroy(): void { if (this._sub) { this._sub.unsubscribe(); } } writeValue(obj: any): void { } registerOnChange(fn: any): void { this.propagateChange = fn; } registerOnTouched(fn: any): void { } setDisabledState?(isDisabled: boolean): void { } // 验证表单,验证结果正确返回 null 否则返回一个验证结果对象 validate(c: FormControl): { [key: string]: any } { if (!c.value) { return null; } switch (c.value.identityType) { case IdentityType.IdCard: { return this.validateIdCard(c); } case IdentityType.Passport: { return this.validatePassport(c); } case IdentityType.Military: { return this.validateMilitary(c); } case IdentityType.Insurance: default: { return null; } } } onIdTypeChange(idType) { this._idType.next(idType); } onIdNoChange(idNo) { this._idNo.next(idNo); } private get idType(): Observable<IdentityType> { return this._idType.asObservable(); } private get idNo(): Observable<string> { return this._idNo.asObservable(); } private validateIdCard(c: FormControl): { [key: string]: any } { const val = c.value.identityNo; if (val.length !== 18) { return { idNotValid: true }; } const pattern = /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}[x0-9]$/; return pattern.test(val) ? null : { idNotValid: true }; } private validatePassport(c: FormControl): { [key: string]: any } { const value = c.value.identityNo; if (value.length !== 9) { return { idNotValid: true }; } const pattern = /^[GgEe]\d{8}$/; return pattern.test(value) ? null : { idNotValid: true }; } private validateMilitary(c: FormControl): { [key: string]: any } { const value = c.value.identityNo; const pattern = /[\u4e00-\u9fa5](字第)(\d{4,8})(号?)$/; return pattern.test(value) ? null : { idNotValid: true }; }}</code></pre></li><li><p>年龄部分:</p><pre><code>import { Component, forwardRef, OnInit, OnDestroy, Input } from '@angular/core';import { FormGroup, NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor, FormControl, FormBuilder } from '@angular/forms';import { map, merge, filter, startWith, debounceTime, distinctUntilChanged } from 'rxjs/operators';import { Observable, Subscription, combineLatest } from 'rxjs';import { subDays, subMonths, subYears, differenceInDays, differenceInMonths, differenceInYears, isBefore, parse, format} from 'date-fns';import { isValidDate } from '../../utils/date.util';export enum AgeUnit { Year = 0, Month, Day}export interface Age { age: number; unit: AgeUnit;}@Component({ selector: 'app-age-input', templateUrl: './age-input.component.html', styleUrls: ['./age-input.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AgeInputComponent), multi: true // 允许令牌多对一 }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => AgeInputComponent), multi: true } ]})export class AgeInputComponent implements ControlValueAccessor, OnInit, OnDestroy { @Input() daysTop = 90; @Input() daysBottom = 0; @Input() monthsTop = 24; @Input() monthsBottom = 1; @Input() yearsTop = 150; @Input() yearsBottom = 1; @Input() format = 'YYYY-MM-DD'; @Input() debounceTime = 300; selectedUnit = AgeUnit.Year; ageUnits = [ { value: AgeUnit.Year, label: '岁' }, { value: AgeUnit.Month, label: '月' }, { value: AgeUnit.Day, label: '日' } ]; form: FormGroup; sub: Subscription; private propagateChange = (_: any) => { }; constructor(private fb: FormBuilder) { } ngOnInit(): void { this.form = this.fb.group({ birthday: ['', this.validateDate], age: this.fb.group({ ageNum: [''], ageUnit: [AgeUnit.Year] }, { validator: this.validateAge('ageNum', 'ageUnit') }) }); const birthday = this.form.get('birthday'); const ageNum = this.form.get('age').get('ageNum'); const ageUnit = this.form.get('age').get('ageUnit'); const birthday$ = birthday.valueChanges.pipe( map(d => { return { date: d, from: 'birthday' }; }), debounceTime(this.debounceTime), distinctUntilChanged(), filter(_ => birthday.valid) ); const ageNum$ = ageNum.valueChanges.pipe( startWith(ageNum.value), debounceTime(this.debounceTime), distinctUntilChanged(), ); const ageUnit$ = ageUnit.valueChanges.pipe( startWith(ageUnit.value), debounceTime(this.debounceTime), distinctUntilChanged() ); // rxjs6版本中方法被改造了 const age$ = combineLatest(ageNum$, ageUnit$).pipe( map(([_n, _u]) => this.toDate({ age: _n, unit: _u })), map(d => { return { date: d, from: 'age' }; }), filter(_ => this.form.get('age').valid) ); const merged$ = Observable.prototype.pipe( merge(birthday$, age$), filter(_ => this.form.valid) ); this.sub = merged$.subscribe(d => { const age = this.toAge(d.date); if (d.from === 'birthday') { if (age.age !== ageNum.value) { ageNum.patchValue(age.age, { emitEvent: false }); } if (age.unit !== ageUnit.value) { this.selectedUnit = age.unit; ageUnit.patchValue(age.age, { emitEvent: false }); } this.propagateChange(d.date); } else { const ageToCompare = this.toAge(this.form.get('birthday').value); if (age.age !== ageToCompare.age || age.unit !== ageToCompare.unit) { birthday.patchValue(d.date, { emitEvent: false }); this.propagateChange(d.date); } } }); } ngOnDestroy(): void { if (this.sub) { this.sub.unsubscribe(); } } writeValue(obj: any): void { if (obj) { const date = format(obj, this.format); this.form.get('birthday').patchValue(date); const age = this.toAge(date); this.form.get('age').get('ageNum').patchValue(age.age); this.form.get('age').get('ageUnit').patchValue(age.unit); } } registerOnChange(fn: any): void { this.propagateChange = fn; } registerOnTouched(fn: any): void { } setDisabledState?(isDisabled: boolean): void { }</code></pre></li></ol><pre><code> toAge(dateStr: string): Age { const date = parse(dateStr); const now = Date.now(); return isBefore(subDays(now, this.daysTop), date) ? { age: differenceInDays(now, date), unit: AgeUnit.Day } : isBefore(subMonths(now, this.monthsTop), date) ? { age: differenceInMonths(now, date), unit: AgeUnit.Month } : { age: differenceInYears(now, date), unit: AgeUnit.Year }; } toDate(age: Age): string { const now = Date.now(); switch (age.unit) { case AgeUnit.Year: { return format(subYears(now, age.age), this.format); } case AgeUnit.Month: { return format(subMonths(now, age.age), this.format); } case AgeUnit.Day: { return format(subDays(now, age.age), this.format); } default: { return null; } } } validate(c: FormControl): { [key: string]: any } { const val = c.value; if (!val) { return null; } if (isValidDate(val)) { return null; } return { dateOfBirthInvalid: true }; } // 表单验证器 validateAge(ageNumkey: string, ageUnitKey: string) { return (group: FormGroup): { [key: string]: any } => { const ageNum = group.controls[ageNumkey]; const ageUnit = group.controls[ageUnitKey]; let result = false; const ageNumVal = ageNum.value; switch (ageUnit.value) { case AgeUnit.Year: { result = ageNumVal >= this.yearsBottom && ageNumVal < this.yearsTop; break; } case AgeUnit.Month: { result = ageNumVal >= this.monthsBottom && ageNumVal < this.monthsTop; break; } case AgeUnit.Day: { result = ageNumVal >= this.daysBottom && ageNumVal < this.daysTop; break; } default: { break; } } return result ? null : { ageInvalid: true }; }; } validateDate(c: FormControl): { [key: string]: any } { const val = c.value; return isValidDate(val) ? null : { birthdayInvalid: true }; }}</code></pre><ol start="3"><li><p>地区部分:</p><pre><code>import { Component, OnInit, forwardRef, ChangeDetectionStrategy, OnDestroy } from '@angular/core';import { NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor, FormControl } from '@angular/forms';import { Subscription, Subject, combineLatest, Observable, of } from 'rxjs';import { Address } from '../../domain/user.model';import { startWith, map } from 'rxjs/operators';import { getProvinces, getCitiesByProvince, getAreasByCity } from '../../utils/area.util';@Component({ selector: 'app-area-list', templateUrl: './area-list.component.html', styleUrls: ['./area-list.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AreaListComponent), multi: true, }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => AreaListComponent), multi: true, }, ], changeDetection: ChangeDetectionStrategy.OnPush,})export class AreaListComponent implements ControlValueAccessor, OnInit, OnDestroy { _address: Address = { province: '', city: '', district: '', street: '' }; _province = new Subject(); _city = new Subject(); _district = new Subject(); _street = new Subject(); provinces$: Observable<string[]>; cities$: Observable<string[]>; districts$: Observable<string[]>; private _sub: Subscription; private propagateChange = (_: any) => { }; constructor() { } ngOnInit() { const province$ = this._province.asObservable().pipe(startWith('')); const city$ = this._city.asObservable().pipe(startWith('')); const district$ = this._district.asObservable().pipe(startWith('')); const street$ = this._street.asObservable().pipe(startWith('')); const val$ = combineLatest([province$, city$, district$, street$], (_p, _c, _d, _s) => { return { province: _p, city: _c, district: _d, street: _s }; }); this._sub = val$.subscribe(v => { this.propagateChange(v); }); this.provinces$ = of(getProvinces()); // 根据省份的选择得到城市列表 this.cities$ = province$.pipe( map((p: string) => getCitiesByProvince(p)) ); // 根据省份和城市的选择得到地区列表 this.districts$ = combineLatest(province$, city$, (p, c) => ({ province: p, city: c })).pipe( map(a => getAreasByCity(a.province, a.city)) ); } ngOnDestroy(): void { if (this._sub) { this._sub.unsubscribe(); } } // 设置初始值 writeValue(obj: Address): void { if (obj) { this._address = obj; if (this._address.province) { this._province.next(this._address.province); } if (this._address.city) { this._city.next(this._address.city); } if (this._address.district) { this._district.next(this._address.district); } if (this._address.street) { this._street.next(this._address.street); } } } registerOnChange(fn: any): void { this.propagateChange = fn; } registerOnTouched(fn: any): void { } setDisabledState?(isDisabled: boolean): void { } // 验证表单,验证结果正确返回 null 否则返回一个验证结果对象 validate(c: FormControl): { [key: string]: any } { const val = c.value; if (!val) { return null; } if (val.province && val.city && val.district && val.street && val.street.length >= 4) { return null; } return { addressNotValid: true }; } onProvinceChange() { this._province.next(this._address.province); } onCityChange() { this._city.next(this._address.city); } onDistrictChange() { this._district.next(this._address.district); } onStreetChange() { this._street.next(this._address.street); }}</code></pre></li></ol><p>当然了,这些不是全部的代码,有些重复的功能我写成了单独的ts文件,有兴趣的可以去我的<a href="https://github.com/TanYiBing/taskmgr" target="_blank" rel="noopener">github</a>上看。</p><p>这样我们只是完成了各个自定义控件,这样的好处是我们的控件可以重复使用,而且各部分逻辑都在各自的部分,所以最后我们需要一个整体来包含这些控件。</p><pre><code><form [formGroup]="form" (ngSubmit)="onSubmit(form, $event)"> <mat-card> <mat-tab-group> <mat-tab label="帐号信息"> <mat-card-header> <mat-card-title>注册</mat-card-title> </mat-card-header> <mat-card-content> <mat-form-field class="full-width"> <input matInput placeholder="您的email*" type="email" formControlName="email"> </mat-form-field> <mat-form-field class="full-width"> <input matInput placeholder="您的姓名*" type="text" formControlName="name"> </mat-form-field> <mat-form-field class="full-width"> <input matInput placeholder="输入您的密码*" type="password" formControlName="password"> </mat-form-field> <mat-form-field class="full-width"> <input matInput placeholder="重复输入您的密码*" type="password" formControlName="repeat"> </mat-form-field> <app-image-list-select [useSvgIcon]="true" [cols]="6" [title]="'选择头像:'" [items]="items" formControlName="avatar"> </app-image-list-select> <button mat-raised-button color="primary" type="submit">注册</button> </mat-card-content> <mat-card-actions class="text-right"> <p>已有账户?<a href="">登录</a></p> <p>忘记密码?<a href="">找回</a></p> </mat-card-actions> </mat-tab> <mat-tab label="个人信息"> <div class="full-width control-padding"> <app-identity-input formControlName="identity" class="full-width control-padding"></app-identity-input> </div> <div class="full-width control-padding"> <app-age-input formControlName="dateOfBirth"></app-age-input> </div> <div class="full-width control-padding"> <app-area-list formControlName="address"></app-area-list> </div> </mat-tab> </mat-tab-group> </mat-card></form></code></pre><p>这个部分的处理逻辑如下:</p><pre><code>import { Component, OnInit, OnDestroy } from '@angular/core';import { FormBuilder, Validators, FormGroup } from '@angular/forms';import { debounceTime, filter } from 'rxjs/operators';import { Subscription } from 'rxjs';import { extractInfo, isValidAddr, getAddrByCode } from '../../utils/identity.util';import { isValidDate } from '../../utils/date.util';@Component({ selector: 'app-register', templateUrl: './register.component.html', styleUrls: ['./register.component.scss']})export class RegisterComponent implements OnInit, OnDestroy { items: string[]; form: FormGroup; sub: Subscription; private readonly avatarName = 'avatars'; constructor(private fb: FormBuilder) { } ngOnInit() { const img = `${this.avatarName}:svg-${(Math.random() * 16).toFixed()}`; const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; this.items = nums.map(val => `avatars:svg-${val}`); this.form = this.fb.group({ email: ['wang@163.com', Validators.compose([Validators.email, Validators.required])], name: [''], password: [''], repeat: [''], avatar: [img], dateOfBirth: ['1990-01-01'], address: [''], identity: [''] }); const id$ = this.form.get('identity').valueChanges.pipe( debounceTime(300), filter(_ => this.form.get('identity').valid) ); this.sub = id$.subscribe(id => { const info = extractInfo(id.identityNo); if (isValidAddr(info.addrCode)) { const addr = getAddrByCode(info.addrCode); this.form.get('address').patchValue(addr); } if (isValidDate(info.dateOfBirth)) { this.form.get('dateOfBirth').patchValue(info.dateOfBirth); } }); } ngOnDestroy(): void { if (this.sub) { this.sub.unsubscribe(); } } onSubmit({ value, valid }, ev: Event) { ev.preventDefault(); if (!valid) { return; } console.log(value); }}</code></pre><p>这样就形成了一个完整的自定义表单控件,而且里面部分还可以拿出来单独使用。</p>]]></content>
<summary type="html">
<p>首先展示下这个表单的样式:</p>
<blockquote>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="o
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
<category term="Material" scheme="http://tanyibing.com/tags/Material/"/>
</entry>
<entry>
<title>rxjs中的combineLatest</title>
<link href="http://tanyibing.com/2018/10/17/rxjs%E4%B8%AD%E7%9A%84combineLatest/"/>
<id>http://tanyibing.com/2018/10/17/rxjs中的combineLatest/</id>
<published>2018-10-17T03:43:35.000Z</published>
<updated>2018-10-30T13:53:26.464Z</updated>
<content type="html"><![CDATA[<p>在Rxjs第六版之前,我们使用<code>combineLatest</code>这个<code>operator</code>时的方式如下:</p><pre><code>const age$ = Observable .combineLatest(ageNum$, ageUnit$, (_num, _unit) => this.toDate({age: _num, unit: _unit})) .map(d => ({date: d, from: 'age'})) .filter(_ => this.form.get('age').valid);</code></pre><p>但是,在Rxjs第六版中<code>combineLatest</code>这个<code>operator</code>被遗弃了,而是被改成一个function,然后配合<code>pipe()</code>使用,改造后的方法如下:</p><pre><code>const age$ = combineLatest(ageNum$, ageUnit$).pipe( map(([_n, _u]) => this.toDate({ age: _n, unit: _u })), map(d => { return { date: d, from: 'age' }; }), filter(_ => this.form.get('age').valid));</code></pre><p>这之间发生了明显的变化,首先调用的方式就不同,版本六之前是调用<code>Observable.combineLatest</code>,而在版本六之后直接就可以使用<code>combineLatest()</code>;还有一个不同就是参数不同了,原先的参数最后可以接受一个project,实现对元素的操作,但是第六版之后,我们需要pipe出来之后,使用map来对元素进行操作,<strong>而且元素是在一个数组里面的</strong>,这归根结底是因为返回的类型不一样了,operator返回的就是一个operator,而第六版之后返回的是一个<code>Observable</code>对象,我们可以继续对其进行操作。</p>]]></content>
<summary type="html">
<p>在Rxjs第六版之前,我们使用<code>combineLatest</code>这个<code>operator</code>时的方式如下:</p>
<pre><code>const age$ = Observable
.combineLatest(ageNum$,
</summary>
<category term="Rxjs" scheme="http://tanyibing.com/categories/Rxjs/"/>
<category term="Rxjs" scheme="http://tanyibing.com/tags/Rxjs/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
</entry>
<entry>
<title>使用js实现图片的循环播放</title>
<link href="http://tanyibing.com/2018/10/14/%E4%BD%BF%E7%94%A8js%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E7%9A%84%E5%BE%AA%E7%8E%AF%E6%92%AD%E6%94%BE/"/>
<id>http://tanyibing.com/2018/10/14/使用js实现图片的循环播放/</id>
<published>2018-10-14T05:38:01.000Z</published>
<updated>2018-10-30T13:53:26.467Z</updated>
<content type="html"><![CDATA[<p>今天想要实现很多图片的循环播放,我们首先要把所有的图片放到一行上面,具体怎么写css在这就不啰嗦了,最后的html结构如下:</p><pre><code><div class="container"> <ul class="first"> <li> <img src="./images/1.jpg" alt=""> </li> <li> <img src="./images/2.jpg" alt=""> </li> <li> <img src="./images/3.jpg" alt=""> </li> </ul> <ul class="second"> <li> <img src="./images/1.jpg" alt=""> </li> <li> <img src="./images/2.jpg" alt=""> </li> <li> <img src="./images/3.jpg" alt=""> </li> </ul></div></code></pre><p>这里我设置了两个相同的图片ul是为了更好的实现无缝播放,紧接着js代码如下:</p><pre><code>window.onload = function () { var container = document.querySelector('.container'); var first = document.querySelector('.first'); var second = document.querySelector('.second'); // 获得一个ul图片循环需要的长度 var length = ul1.offsetWidth; var x = 0; // 当一个循环结束时,将x重置为0,第二个ul其实就是起一个弥补上一个的空白的作用。 var fun = function () { first.style.left = x/100 + 'rem'; second.style.left = (x + length)/100 + 'rem'; x--; if ((x + length) == 0) { x = 0; } } var loop = setInterval(fun, 10); // 鼠标移上去暂停滚动 container.onmouseover = function () { clearInterval(loop); } // 鼠标移开继续滚动 container.onmouseout = function () { loop = setInterval(fun, 10); } } </code></pre><p>注释里面解释啦,很简单。 </p>]]></content>
<summary type="html">
<p>今天想要实现很多图片的循环播放,我们首先要把所有的图片放到一行上面,具体怎么写css在这就不啰嗦了,最后的html结构如下:</p>
<pre><code>&lt;div class=&quot;container&quot;&gt;
&lt;ul class=&q
</summary>
<category term="JavaScript" scheme="http://tanyibing.com/categories/JavaScript/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
</entry>
<entry>
<title>angular中select的change事件</title>
<link href="http://tanyibing.com/2018/10/11/angular%E4%B8%ADselect%E7%9A%84change%E4%BA%8B%E4%BB%B6/"/>
<id>http://tanyibing.com/2018/10/11/angular中select的change事件/</id>
<published>2018-10-11T14:53:39.000Z</published>
<updated>2018-10-30T13:53:26.273Z</updated>
<content type="html"><![CDATA[<p>今天在使用<code>material</code>中的select组件的时候发现,我的select上面的<code>change</code>事件通过下面的方法并不能接收数据,模板如下:</p><pre><code><mat-form-field> <mat-select placeholder="请选择省份" [(ngModel)]="_address.province" (change)="onProvinceChange()"> <mat-option *ngFor="let p of provinces$ | async" [value]="p"> {{ p }} </mat-option> </mat-select></mat-form-field></code></pre><p>我想通过select的变化来控制其他数据的显示,但是change方法好像监听不到改变一样,最后通过度娘,找到了方法,将change事件改成<code>ngModelChange</code>就可以了:</p><pre><code><mat-form-field> <mat-select placeholder="请选择省份" [(ngModel)]="_address.province" (ngModelChange)="onProvinceChange()"> <mat-option *ngFor="let p of provinces$ | async" [value]="p"> {{ p }} </mat-option> </mat-select></mat-form-field></code></pre><p>这样就可以获得事件了。</p>]]></content>
<summary type="html">
<p>今天在使用<code>material</code>中的select组件的时候发现,我的select上面的<code>change</code>事件通过下面的方法并不能接收数据,模板如下:</p>
<pre><code>&lt;mat-form-field&gt;
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
<category term="Material" scheme="http://tanyibing.com/tags/Material/"/>
</entry>
<entry>
<title>设置input中placeholder的字体颜色</title>
<link href="http://tanyibing.com/2018/10/10/%E8%AE%BE%E7%BD%AEinput%E4%B8%ADplaceholder%E7%9A%84%E5%AD%97%E4%BD%93%E9%A2%9C%E8%89%B2/"/>
<id>http://tanyibing.com/2018/10/10/设置input中placeholder的字体颜色/</id>
<published>2018-10-10T14:22:46.000Z</published>
<updated>2018-10-30T13:53:26.476Z</updated>
<content type="html"><![CDATA[<p>在一个有背景的div中创建一个form之后,我发现input的背景默认是白色的,这样的话就无法看到背景图,所以我将input的背景调成透明,并加上边框来凸显出input的存在:</p><pre><code>input { background: transparent; border: #ffffff solid 1px;}</code></pre><p>这样我们的input框就呈现透明的状态。但是我发现input中的placeholder的颜色是黑色的,在我的背景下面不是特别清晰,所以我们需腰改变placeholder的字体颜色:</p><pre><code>::-webkit-input-placeholder{/*Webkit browsers*/ color:#999; font-size:16px;}:-moz-placeholder{/*Mozilla Firefox 4 to 8*/ color:#999; font-size:16px;}::moz-placeholder{/*Mozilla Firefox 19+*/ color:#999; font-size:16px;}:-ms-input-placeholder{/*Internet Explorer 10+*/ color:#999; font-size:16px;}</code></pre><p>我们只要在:前面加上input或者textarea就可以改变placeholder的字体。它的兼容性如下:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/html/1.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote>]]></content>
<summary type="html">
<p>在一个有背景的div中创建一个form之后,我发现input的背景默认是白色的,这样的话就无法看到背景图,所以我将input的背景调成透明,并加上边框来凸显出input的存在:</p>
<pre><code>input {
background: transpare
</summary>
<category term="CSS" scheme="http://tanyibing.com/categories/CSS/"/>
<category term="HTML" scheme="http://tanyibing.com/tags/HTML/"/>
<category term="CSS" scheme="http://tanyibing.com/tags/CSS/"/>
</entry>
<entry>
<title>在VScode中使用Git</title>
<link href="http://tanyibing.com/2018/10/09/%E5%9C%A8VScode%E4%B8%AD%E4%BD%BF%E7%94%A8Git/"/>
<id>http://tanyibing.com/2018/10/09/在VScode中使用Git/</id>
<published>2018-10-09T13:31:16.000Z</published>
<updated>2018-10-30T13:53:26.472Z</updated>
<content type="html"><![CDATA[<p>在VsCode中使用Git来将我的修改保存到远程仓库的操作:</p><ol><li>首先我们在Github上创建一个仓库,否则我们的项目没办法保存到远程仓库。</li><li>接下来我一般是在本地创建一个目录,并且用VsCode打开目录。</li><li>然后我们开始将本地目录和远程仓库关联起来,首先在VsCode中运行<code>git init</code>命令来将文件夹初始化成Git仓库。</li><li>然后我们运行<code>git remote add origin XXX(你的github仓库地址)</code>,这样就完成了关联。</li><li>接着点击图中的master按钮,就可以看到我们的远程仓库,选择它,并在目录下进行工作。><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/git/1.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></li><li>我们也许需要定义一个<code>.gitignore</code>文件来忽略掉一些你不想展示给别人看的,或者不重要的文件。</li><li>别忘在工作区工作之后要提交更改,并且同步到远程仓库。</li></ol>]]></content>
<summary type="html">
<p>在VsCode中使用Git来将我的修改保存到远程仓库的操作:</p>
<ol>
<li>首先我们在Github上创建一个仓库,否则我们的项目没办法保存到远程仓库。</li>
<li>接下来我一般是在本地创建一个目录,并且用VsCode打开目录。</li>
<li>然后我们开
</summary>
<category term="VsCode" scheme="http://tanyibing.com/categories/VsCode/"/>
<category term="VsCode" scheme="http://tanyibing.com/tags/VsCode/"/>
<category term="Git" scheme="http://tanyibing.com/tags/Git/"/>
</entry>
<entry>
<title>MongoDB怎么导入数据</title>
<link href="http://tanyibing.com/2018/10/08/MongoDB%E6%80%8E%E4%B9%88%E5%AF%BC%E5%85%A5%E6%95%B0%E6%8D%AE/"/>
<id>http://tanyibing.com/2018/10/08/MongoDB怎么导入数据/</id>
<published>2018-10-08T14:44:24.000Z</published>
<updated>2018-10-30T13:53:26.264Z</updated>
<content type="html"><![CDATA[<p>使用<code>node</code>的过程中使用了<code>MongoDB</code>,在MongoDB中怎么直接将数据导入数据库呢?我们只需要一条命令就行了。</p><blockquote><p> mongorestore -h dbhost -d dbname path</p></blockquote><p>其中<code>dbhost</code>代表你的host,一般在自己的电脑上的话是<code>127.0.0.1</code>,<code>dbname</code>代表要导入的数据库,可以是不存在的,默认会创建一个新的数据库,<code>path</code>就是你的数据库数据存放的位置。<strong>路径中最好不要有中文,以防导入失败。这行命令不需要进入到数据库里面再执行,直接在cmd中运行就可以了。</strong></p><p>想要导出的话也很简单,只要执行下面的命令:</p><blockquote><p> mongodump -h dbhost -d dbname -o dbdirectory</p></blockquote><p><code>-o dbdirectory</code>是指导出到哪个目录。但是导出之前我们需要打开我们的数据路服务:</p><blockquote><p> mongod –dbpath yourpath</p></blockquote><p>再执行上面的导出命令就OK啦。</p>]]></content>
<summary type="html">
<p>使用<code>node</code>的过程中使用了<code>MongoDB</code>,在MongoDB中怎么直接将数据导入数据库呢?我们只需要一条命令就行了。</p>
<blockquote>
<p> mongorestore -h dbhost -d dbname
</summary>
<category term="MongoDB" scheme="http://tanyibing.com/categories/MongoDB/"/>
<category term="MongoDB" scheme="http://tanyibing.com/tags/MongoDB/"/>
</entry>
<entry>
<title>ES6中的export & import</title>
<link href="http://tanyibing.com/2018/10/07/ES6%E4%B8%AD%E7%9A%84export-import/"/>
<id>http://tanyibing.com/2018/10/07/ES6中的export-import/</id>
<published>2018-10-07T15:44:24.000Z</published>
<updated>2018-10-30T13:53:26.257Z</updated>
<content type="html"><![CDATA[<p>上次说了一下<a href="http://tanyibing.com/2018/09/19/exports%E5%92%8Cmodule-exports%E7%9A%84%E5%8C%BA%E5%88%AB/">exports和module.exports的区别</a>,但是只讲了那一种状态,所以今天特意再来看看ES6中module的内容。</p><p>在ES6中,专门实现了模块化的功能。想要更仔细的阅读可以看一看阮一峰的《ESMAScript6入门》中的<a href="http://es6.ruanyifeng.com/#docs/module" target="_blank" rel="noopener">Module的语法</a>。里面是这么说的:</p><blockquote><p>在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。</p></blockquote><p>由此可见ES6中的模块化功能还是很强大的。</p><h3 id="export"><a href="#export" class="headerlink" title="export"></a>export</h3><p>先看几种export的写法:</p><pre><code>// 第一种 输出两个变量export var firstName = 'Michael';export var lastName = 'Jackson';// 第二种 和第一种一样,相同情况应该优先使用第二种。var firstName = 'Michael';var lastName = 'Jackson';export {firstName, lastName, year};// 第三种 输出函数或类export function multiply(x, y) { return x * y;};</code></pre><p>上面的三种情况在注释里解释了,就按这种模式写。但是要注意的是不要写成下面的样子;</p><pre><code>// 报错export 1;// 报错var m = 1;export m;</code></pre><p>这样写是直接输出,而不是通过接口,所以会报错。应该写成下面这样:</p><pre><code>// 写法一export var m = 1;// 写法二var m = 1;export {m};// 写法三var n = 1;export {n as m};</code></pre><p>同样的,<code>function</code>和<code>class</code>也要按这种接口的方式去写:</p><pre><code>// 报错function f() {}export f;// 正确export function f() {};// 正确function f() {}export {f};</code></pre><h3 id="import"><a href="#import" class="headerlink" title="import"></a>import</h3><p>看几个import的写法:</p><pre><code>// 普通写法import {firstName, lastName, year} from './profile.js';// 重命名写法import { lastName as surname } from './profile.js';// 整体加载写法import * as name from './profile.js';</code></pre><p>这就是import的几种写法,不要妖来妖去的,例如使用表达式和变量、修改接口啊什么的操作,即使可以操作,也是极不推荐的,防止出现错误后找不到问题的根源。</p><h3 id="export-default"><a href="#export-default" class="headerlink" title="export default"></a>export default</h3><p><code>export default</code>是一种默认输出的方式,他的形式如下:</p><pre><code>// 输出export default function () { console.log('foo');}// 输入import customName from './export-default';customName();</code></pre><p>可以发现,<strong>默认输出之后,引入时被当成匿名函数,我们可以给他起任意的名字。<code>export default</code>可以用在<code>非匿名函数</code>前面,但是在外部还是会被当成匿名函数来加载。还可以发现的是,使用默认输出之后,在<code>import</code>时不再需要大括号了,这是因为<code>export default</code>只能使用一次,所以在引入时可以不加大括号。</strong></p><p>正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。</p><pre><code>// 正确export var a = 1;// 正确var a = 1;export default a;// 错误export default var a = 1;// 正确export default 42;// 报错export 42;</code></pre><h3 id="复合写法"><a href="#复合写法" class="headerlink" title="复合写法"></a>复合写法</h3><p>如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。例如下面:</p><pre><code>export { foo, bar } from 'my_module';// 可以简单理解为import { foo, bar } from 'my_module';export { foo, bar };</code></pre><p>上面代码中,export和import语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。</p>]]></content>
<summary type="html">
<p>上次说了一下<a href="http://tanyibing.com/2018/09/19/exports%E5%92%8Cmodule-exports%E7%9A%84%E5%8C%BA%E5%88%AB/">exports和module.exports的区别</a>,
</summary>
<category term="JavaScript" scheme="http://tanyibing.com/categories/JavaScript/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
</entry>
<entry>
<title>发发牢骚</title>
<link href="http://tanyibing.com/2018/10/02/%E5%8F%91%E5%8F%91%E7%89%A2%E9%AA%9A/"/>
<id>http://tanyibing.com/2018/10/02/发发牢骚/</id>
<published>2018-10-02T14:39:47.000Z</published>
<updated>2018-10-30T13:53:26.471Z</updated>
<content type="html"><![CDATA[<p>今天在家吃饭,大家都聚在一起,喝了很多酒,也聊了很多家庭的问题。从小和家长之间的交流就很少,真正认真的交流就更加是屈指可数。更多的也是在争吵之中吐露心声,但也被冲动的情绪掩盖了。长大之后也多多少少思考过这些事情。过去这么久了,爷爷奶奶也转变了很多,我父亲对我的态度也有很大的改变。其实他们只是不会表达,不善于交流,但他们也是真心希望孩子们过得好。反思反思自己,还有很多地方没做好。以后在外工作要经常给家里带电话,回家也应该买些水果给爷爷奶奶、外公外婆。因为留给他们的时间不多了,能和我们在一起的日子更少。要多抽空回家陪陪他们,让他们不要太寂寞。不要等到最后子欲养而亲不待~~~</p>]]></content>
<summary type="html">
<p>今天在家吃饭,大家都聚在一起,喝了很多酒,也聊了很多家庭的问题。从小和家长之间的交流就很少,真正认真的交流就更加是屈指可数。更多的也是在争吵之中吐露心声,但也被冲动的情绪掩盖了。长大之后也多多少少思考过这些事情。过去这么久了,爷爷奶奶也转变了很多,我父亲对我的态度也有很大的
</summary>
<category term="balabala" scheme="http://tanyibing.com/categories/balabala/"/>
<category term="balabala" scheme="http://tanyibing.com/tags/balabala/"/>
</entry>
<entry>
<title>使用Rx.js和json-server模拟随机获取内容</title>
<link href="http://tanyibing.com/2018/09/29/%E4%BD%BF%E7%94%A8Rx-js%E5%92%8Cjson-server%E6%A8%A1%E6%8B%9F%E9%9A%8F%E6%9C%BA%E8%8E%B7%E5%8F%96%E5%86%85%E5%AE%B9/"/>
<id>http://tanyibing.com/2018/09/29/使用Rx-js和json-server模拟随机获取内容/</id>
<published>2018-09-29T09:03:56.000Z</published>
<updated>2018-10-30T13:53:26.466Z</updated>
<content type="html"><![CDATA[<p>今天使用angular中的<code>rx.js</code>来将我们的数据转换成<code>Observable</code>,毕竟angular中到处都是<code>Observable</code>可观察对象这种方式,因为使用起来很方便。</p><p>我们没有真的后台怎么模拟接口呢,我直接使用了<code>json-server</code>,这个使用起来很方便,只需要写好一个模拟接口的<code>json</code>文件,然后执行命令就行了。首先我们安装:</p><pre><code>cnpm install --save-dev json-server</code></pre><p>然后我们写一个json文件,紧接着执行一条命令:</p><pre><code>json-server ./mock/data.json</code></pre><p>这样我就能在<code>localhost:3000</code>下面通过http请求到我的json文件啦。</p><p>紧接着我们把获取json文件功能封装成angular中的一个服务:</p><pre><code>import { Injectable, Inject } from '@angular/core';import { Quote } from '../domain/quote.model';import { Observable } from 'rxjs';import { HttpClient } from '@angular/common/http';import { map } from 'rxjs/operators';@Injectable()export class QuoteService { constructor( private Http: HttpClient, @Inject('BASE_CONFIG') private config ) {} getQuote(): Observable<Quote> { const uri = `${this.config.uri}/quotes/${Math.floor(Math.random() * 10)}`; return this.Http.get(uri).pipe(map(res => res as Quote)); }}</code></pre><p>这样我们就注册了一个我们自己的服务,而且我们同过随机请求uri可以实现随机获取内容中的一天。而且,我们最后在返回的时候,返回的是<code>Observable</code>类型的内容。在需要使用这些内容的组件内,我们只要注册这个服务就可以了:</p><pre><code>constructor(private quoteService$: QuoteService) { this.quoteService$ .getQuote() .subscribe(q => this.quote = q);}</code></pre><p>我们在组件中注册这个服务之后,通过订阅的方式获取到了数据,这样是不是很简单。</p><p><strong>我们使用<code>quoteService$</code>这样的写法是因为,这是一个流!</strong></p>]]></content>
<summary type="html">
<p>今天使用angular中的<code>rx.js</code>来将我们的数据转换成<code>Observable</code>,毕竟angular中到处都是<code>Observable</code>可观察对象这种方式,因为使用起来很方便。</p>
<p>我们没有真的后
</summary>
<category term="Rxjs" scheme="http://tanyibing.com/categories/Rxjs/"/>
<category term="Rxjs" scheme="http://tanyibing.com/tags/Rxjs/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
</entry>
<entry>
<title>angular中自定义表单控件</title>
<link href="http://tanyibing.com/2018/09/28/angular%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A8%E5%8D%95%E6%8E%A7%E4%BB%B6/"/>
<id>http://tanyibing.com/2018/09/28/angular中自定义表单控件/</id>
<published>2018-09-28T15:51:23.000Z</published>
<updated>2018-10-30T13:53:26.280Z</updated>
<content type="html"><![CDATA[<p>angular中有两种表单,一种是模版驱动的表单,还有一种是响应式的表单。如果我们的表单比较简单的话我们可以使用模版驱动型表单,但是一旦我么的表单复杂起来,那么响应式表单是我们的首选。</p><p>但是一旦我们的表单过于复杂之后,我们就需要将很多的逻辑写在一起,对于我们来说是件很糟糕的事情,所以,我们需要<code>把复杂问题简单化</code>,因此,我们可以自定义表单控件。</p><p>我需要实现一个如下的头像选择效果:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/8.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>如果光是添加头像我们可以直接写,没必要写成控件,但是,我在之后的项目中可能也想给我其他表单添加图片也实现这样的功能,那我就需要封装一下,方便以后复用。</p><p>我在此就写一些删减后的代码,理清思路。首先我们给出我们自定义表单控件的模板:</p><pre><code><div> <mat-icon *ngIf="useSvgIcon else imgSelect"></mat-icon> <ng-template #imgSelect> <img [src]="selected" alt="image selected"> </ng-template></div><div class="scroll-container"> <mat-grid-list> <mat-grid-tile *ngFor="let item of items"> <div class="image-container"> <mat-icon [svgIcon]="item" *ngIf="useSvgIcon else imgItem"></mat-icon> <ng-template #imgItem> <img [src]="item" alt="image item"> </ng-template> </mat-grid-tile></div></code></pre><p>第一个div是我们展示选中的,下面的是展示所有头像,使用的是material中的<code>gridlist</code>。</p><p>我们想要自定义表单控件,最重要的就是实现<code>ControlValueAccessor</code>接口,例子如下:</p><pre><code>import { Component, forwardRef } from '@angular/core';import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';@Component({ selector: 'app-image-list-select', templateUrl: './image-list-select.component.html', styleUrls: ['./image-list-select.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ImageListSelectComponent), multi: true // 允许令牌多对一 } ]})export class ImageListSelectComponent implements ControlValueAccessor { constructor() { } // 这里是做一个空函数体,真正使用的方法在 registerOnChange 中 // 由框架注册,然后我们使用它把变化发回表单 // 注意,和 EventEmitter 尽管很像,但发送回的对象不同 private propagateChange = (_: any) => {}; // 写入控件值 writeValue(obj: any): void {} // 当表单控件值改变时,函数 fn 会被调用 // 这也是我们把变化 emit 回表单的机制 registerOnChange(fn: any): void { this.propagateChange = fn; } // 这里没有使用,用于注册 touched 状态 registerOnTouched(fn: any): void {} setDisabledState?(isDisabled: boolean): void {}}</code></pre><p>要实现<code>ControlValueAccessor</code>接口,我们就需要实现上面四中方法,在<code>writeValue</code>,我们就能获取到控件中值的值,然后我们通过<code>private propagateChange = (_: any) => {};</code>这个类似于<code>EventEmitter</code>的函数将值暴露出去。差不多就行了。</p><p>想要了解这个项目的可以去我的github上看我的项目:<a href="https://github.com/TanYiBing/taskmgr" target="_blank" rel="noopener">这是地址</a></p>]]></content>
<summary type="html">
<p>angular中有两种表单,一种是模版驱动的表单,还有一种是响应式的表单。如果我们的表单比较简单的话我们可以使用模版驱动型表单,但是一旦我么的表单复杂起来,那么响应式表单是我们的首选。</p>
<p>但是一旦我们的表单过于复杂之后,我们就需要将很多的逻辑写在一起,对于我们来
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
<category term="Material" scheme="http://tanyibing.com/tags/Material/"/>
</entry>
<entry>
<title>angular中写自己的指令</title>
<link href="http://tanyibing.com/2018/09/27/angular%E4%B8%AD%E5%86%99%E8%87%AA%E5%B7%B1%E7%9A%84%E6%8C%87%E4%BB%A4/"/>
<id>http://tanyibing.com/2018/09/27/angular中写自己的指令/</id>
<published>2018-09-27T15:23:39.000Z</published>
<updated>2018-10-30T13:53:26.274Z</updated>
<content type="html"><![CDATA[<p>今天我们想实现一个组件拖拽的效果,以方便以后可以通过拖拽来切换我们组件的位置。所以,我们需要设计自己的指令<code>Directive</code>。</p><p>在angular中有三种指令:</p><ol><li>组件 — 拥有模板的指令</li><li>结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令</li><li>属性型指令 — 改变元素、组件或其它指令的外观和行为的指令</li></ol><p>组件是这三种指令中最常用的,而<strong>结构型的指令会改变 DOM 结构,建议是不使用,但真要使用之前得好好思考下,否则可能会带来一些不好的结果</strong>。我们今天就使用属性型的指令来实现我们想要的东西吧。</p><p>直接上代码吧:</p><pre><code>import { Directive, Input, HostListener, ElementRef, Renderer2 } from '@angular/core';import { DragDropService } from '../drag-drop.service';@Directive({ selector: '[app-draggable]'})export class DragDirective { private _isDraggable = false; @Input() draggedClass: string; @Input('app-draggable') set isDraggable(val: boolean) { this._isDraggable = val; this.rd.setAttribute(this.el.nativeElement, 'draggable', `${val}`); } get isDraggable() { return this._isDraggable; } constructor( private el: ElementRef, private rd: Renderer2 ) { } @HostListener('dragstart', ['$event']) onDragStart(ev: Event) { if (this.el.nativeElement === ev.target) { this.rd.addClass(this.el.nativeElement, this.draggedClass); } } @HostListener('dragend', ['$event']) onDragEnd(ev: Event) { if (this.el.nativeElement === ev.target) { this.rd.removeClass(this.el.nativeElement, this.draggedClass); } }}</code></pre><p>这是我们的 <code>dragDective</code>,当然代码没有写全,我们只是列一个思路,首先我们可以使用angular-cli直接生成一个指令模版:</p><pre><code>ng g d xxx //xxx代表指令的名称</code></pre><p>生成之后直接就有了<code>selector</code>,里面就是我们指令的名称,然后我们通过类似于.net的语法来获取下这个指令是<code>true</code>还是<code>false</code>:</p><pre><code>@Input('app-draggable') set isDraggable(val: boolean) { this._isDraggable = val; this.rd.setAttribute(this.el.nativeElement, 'draggable', `${val}`); } get isDraggable() { return this._isDraggable; }</code></pre><p>这样我们就可以在我们的组件模板中加上这个指令了:</p><pre><code><app-task-list class="list-container" app-droppable [app-draggable]="true"//这就是指令的位置 (dropped)="handleMove($event,list)" *ngFor="let list of lists"></code></pre><p>如此一来可以说是完成了添加,但是我们的指令啥都没有做,所以也就不会有效果,我们可以发现的是,我们的指令代码中出现很多<code>@HostListener</code>,没错,这就是我们监听鼠标事件的方法,通过响应事件来进行一系列样式的添加:</p><pre><code>@HostListener('dragstart', ['$event']) onDragStart(ev: Event) { if (this.el.nativeElement === ev.target) { this.rd.addClass(this.el.nativeElement, this.draggedClass); } } @HostListener('dragend', ['$event']) onDragEnd(ev: Event) { if (this.el.nativeElement === ev.target) { this.rd.removeClass(this.el.nativeElement, this.draggedClass); } }</code></pre><p>那这个其中的<code>draggedClass</code>就是我们自己定义的样式,我们的鼠标进行拖拽的时候就能显示我们的样式。</p><p>以上只是项目中的一小块代码,感兴趣的可以去我的Github上看看哦:<a href="https://github.com/TanYiBing/taskmgr" target="_blank" rel="noopener">这是地址</a></p>]]></content>
<summary type="html">
<p>今天我们想实现一个组件拖拽的效果,以方便以后可以通过拖拽来切换我们组件的位置。所以,我们需要设计自己的指令<code>Directive</code>。</p>
<p>在angular中有三种指令:</p>
<ol>
<li>组件 — 拥有模板的指令</li>
<li>结构
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
</entry>
<entry>
<title>material中怎么让主题覆盖dialog这一类组件</title>
<link href="http://tanyibing.com/2018/09/26/material%E4%B8%AD%E6%80%8E%E4%B9%88%E8%AE%A9%E4%B8%BB%E9%A2%98%E8%A6%86%E7%9B%96dialog%E8%BF%99%E4%B8%80%E7%B1%BB%E7%BB%84%E4%BB%B6/"/>
<id>http://tanyibing.com/2018/09/26/material中怎么让主题覆盖dialog这一类组件/</id>
<published>2018-09-26T14:13:15.000Z</published>
<updated>2018-10-30T13:53:26.460Z</updated>
<content type="html"><![CDATA[<p>在<code>material</code>中很大一个特色就是我们可以根据自己的需求自定义自己想要的主题,我就大概讲讲怎么自定义吧,因为我的重点是怎么让主题覆盖一些组件。</p><p>怎么自定义主题呢?我自己写了一个<code>theme.scss</code>文件,代码如下:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/3.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>首先导入 <code>mat-core()</code> sass 混合。这包括多个组件使用的所有常见样式。这应该只在你的应用程序中包含一次。如果这个mixin包含多次,你的应用程序将最终得到这些常用样式的多个副本。将主题数据结构定义为多个调色板的组合。可以使用 mat-light-theme 方法或 mat-dark-theme 方法创建此对象。然后将方法的输出传递给 angular-material-theme 混合,它将输出主题的所有对应样式。</p><p>然后我们需要在<code>style.scss</code>全局样式下引入这个文件,这样就可以自由使用了。</p><p>但是我们发现,例如dialog这些组件无法渲染自定义的主题,我们该怎么解决呢?其实也很简单,我们只需要在最外层的容器上加上一句话:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/4.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>这样之后就大功告成了,我们原先的是背景有黑夜主题,但是dialog没有主题,然后就成功的给dialog也加上了主题。</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/6.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>上面是没有主题的样子,背景是黑的,dialog是白的,不协调。接下来看看完成之后的;</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/5.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>但是这样做我们在项目一开始就定死了我们只能用黑夜模式,我们需要在点击一个按钮的时候切换颜色怎么办呢,好说,我们把它放到跟组件里面的一个按钮事件里不就行了,代码如下:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/7.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>这下是真的彻底完成了哦!</p>]]></content>
<summary type="html">
<p>在<code>material</code>中很大一个特色就是我们可以根据自己的需求自定义自己想要的主题,我就大概讲讲怎么自定义吧,因为我的重点是怎么让主题覆盖一些组件。</p>
<p>怎么自定义主题呢?我自己写了一个<code>theme.scss</code>文件,代码
</summary>
<category term="Material" scheme="http://tanyibing.com/categories/Material/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
<category term="Material" scheme="http://tanyibing.com/tags/Material/"/>
</entry>
<entry>
<title>material中怎么使用自己下载的svg图标</title>
<link href="http://tanyibing.com/2018/09/21/material%E4%B8%AD%E6%80%8E%E4%B9%88%E4%BD%BF%E7%94%A8%E8%87%AA%E5%B7%B1%E4%B8%8B%E8%BD%BD%E7%9A%84svg%E5%9B%BE%E6%A0%87/"/>
<id>http://tanyibing.com/2018/09/21/material中怎么使用自己下载的svg图标/</id>
<published>2018-09-21T06:37:43.000Z</published>
<updated>2018-10-30T13:53:26.459Z</updated>
<content type="html"><![CDATA[<p><code>material</code>是谷歌开发的很好看的一个框架,我就在我最近的项目中使用了这个框架,喜欢的童鞋可以去他们的官网看看:<a href="https://material.io/" target="_blank" rel="noopener">material.io</a></p><p>开发过程中我发现他们的图标库咩有阿里的那么丰富,于是想用自己下载的,那我该怎么操作呢?,下面介绍一下流程。</p><p>为了方便,我没有在需要的模块中反复的添加,而是创建了一个<code>svg.util.ts</code>,并选择在<code>core module</code>中一次性加载,省掉很多事。首先我需要在<code>svg.util.ts</code>中引入必须的模块:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/1.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>这两个模块是核心模块,一个负责注册新加入的svg图标,一个是为了防止XSS漏洞的将URL设为信任对象。</p><p>接下来就将我们的图标都注册进去:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/material/2.png" alt="" title=""> </div> <div class="image-caption"></div> </figure> </blockquote><p>这样子差不多快可以了,<strong>但我们还需要在<code>core module</code>中引入HttpClient模块(这一步必须要,否则会报错)</strong>,因为需要依赖它,最后在<code>core module</code>的构造函数中调用就行了。</p><p>然后我们就能光明正大的使用啦:</p><pre><code><mat-icon svgIcon='注册的名称'></mat-icon></code></pre>]]></content>
<summary type="html">
<p><code>material</code>是谷歌开发的很好看的一个框架,我就在我最近的项目中使用了这个框架,喜欢的童鞋可以去他们的官网看看:<a href="https://material.io/" target="_blank" rel="noopener">mater
</summary>
<category term="Material" scheme="http://tanyibing.com/categories/Material/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
<category term="Material" scheme="http://tanyibing.com/tags/Material/"/>
</entry>
<entry>
<title>尝鲜Ionic4中点击跳转新页面</title>
<link href="http://tanyibing.com/2018/09/20/%E5%B0%9D%E9%B2%9CIonic4%E4%B8%AD%E7%82%B9%E5%87%BB%E8%B7%B3%E8%BD%AC%E6%96%B0%E9%A1%B5%E9%9D%A2/"/>
<id>http://tanyibing.com/2018/09/20/尝鲜Ionic4中点击跳转新页面/</id>
<published>2018-09-20T03:15:48.000Z</published>
<updated>2018-10-30T13:53:26.474Z</updated>
<content type="html"><![CDATA[<p>Ionic已经出了第四版了,不过还在测试阶段,那我们先来玩玩吧。</p><p>首先我们用命令行新建一个页面<code>news</code>,我们发现,这一版中,新建的page被直接加入到了路由当中:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/ionic/1.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>也就是说我们可以直接使用路由跳转,接下来我们在home页面中加一个按钮,并且给它个点击事件:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/ionic/2.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>此时我们到<code>home.page.ts</code>中去定义这个<code>goNews()</code>方法。我们手下需要在构造函数里面注册<code>NavController</code>,但是我接下来发现,这个类的<code>push</code>方法没定义,那我们去看下源码:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/ionic/3.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>果然是没有push方法了,但是有个<code>navigateForward</code>方法好像有那么点意思,试试吧:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/ionic/4.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>果然如此,没问题,现在我们就能够跳转啦。</p><p><strong>官网文档现在还不是写的很清楚,想要正常使用还是要查查源码。</strong></p>]]></content>
<summary type="html">
<p>Ionic已经出了第四版了,不过还在测试阶段,那我们先来玩玩吧。</p>
<p>首先我们用命令行新建一个页面<code>news</code>,我们发现,这一版中,新建的page被直接加入到了路由当中:</p>
<blockquote>
<figure class="ima
</summary>
<category term="Ionic4" scheme="http://tanyibing.com/categories/Ionic4/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
<category term="Ionic4" scheme="http://tanyibing.com/tags/Ionic4/"/>
</entry>
<entry>
<title>exports和module.exports的区别</title>
<link href="http://tanyibing.com/2018/09/19/exports%E5%92%8Cmodule-exports%E7%9A%84%E5%8C%BA%E5%88%AB/"/>
<id>http://tanyibing.com/2018/09/19/exports和module-exports的区别/</id>
<published>2018-09-19T01:35:45.000Z</published>
<updated>2018-10-30T13:53:26.288Z</updated>
<content type="html"><![CDATA[<h1 id="exports和moduls-exports的区别"><a href="#exports和moduls-exports的区别" class="headerlink" title="exports和moduls.exports的区别"></a>exports和moduls.exports的区别</h1><p>每一个node.js执行文件,都会自动创建一个<code>module对象</code>,同时,module对象会创建一个叫<code>exports</code>的属性,初始化的值是{}:</p><pre><code>module.exports = {};</code></pre><p>这时我们使用exports看看:</p><pre><code>math.jsexports.add=function(arg1,arg2){ return arg1+arg2;};exports.minus=function(arg1,arg2){ return arg1-arg2;};console.log(module);test.jsvar math=require('./math.js');console.log('math:'+math);console.log(math.add(3,4));console.log(math.minus(5,3));</code></pre><p>控制台中信息:<br>module:</p><pre><code>{ id: '/home/chander-zhang/NodeJS/test/math.js', exports: { add: [Function], minus: [Function] }, parent: { ... }, filename: '/home/chander-zhang/NodeJS/test/math.js', loaded: false, children: [], paths: [ '/home/chander-zhang/NodeJS/test/node_modules', '/home/chander-zhang/NodeJS/node_modules', '/home/chander-zhang/node_modules', '/home/node_modules', '/node_modules' ] }</code></pre><p>math: </p><pre><code>[object Object]</code></pre><p>这时的math是个对象,当我们使用module.exports时会怎么样:</p><pre><code>math.jsexports.add=function(arg1,arg2){ return arg1+arg2;};minus=function(arg1,arg2){ return arg1-arg2;};module.exports=minus;console.log(module);test.jsvar math=require('./math.js');console.log('math:'+math);//console.log(math.add(3,4));console.log(math.minus(5,3));//相当于执行了 minus(5,3)</code></pre><p>控制台中打印出:、<br>module:</p><pre><code>{ id: '/home/chander-zhang/NodeJS/test/math.js', exports: [Function], parent: { ... }, filename: '/home/chander-zhang/NodeJS/test/math.js', loaded: false, children: [], paths: [ '/home/chander-zhang/NodeJS/test/node_modules', '/home/chander-zhang/NodeJS/node_modules', '/home/chander-zhang/node_modules', '/home/node_modules', '/node_modules' ] }</code></pre><p>math: </p><pre><code>function (arg1,arg2){ return arg1-arg2;}</code></pre><p>如果使用math.add()或math.minus()将会出错,因为此时math是一个函数. 即便module中出现了exports.add,由于为module.exports赋了值,也不能使用math.add().<br><strong>因为exports是指向module.exports的引用,而require()返回的是module.exports而不是exports,当我们自己定义module.exports时就会覆盖自动生成的moudle.exports。</strong></p>]]></content>
<summary type="html">
<h1 id="exports和moduls-exports的区别"><a href="#exports和moduls-exports的区别" class="headerlink" title="exports和moduls.exports的区别"></a>exports和mod
</summary>
<category term="Node" scheme="http://tanyibing.com/categories/Node/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
<category term="Node" scheme="http://tanyibing.com/tags/Node/"/>
</entry>
<entry>
<title>javascript异步(四)</title>
<link href="http://tanyibing.com/2018/09/18/javascript%E5%BC%82%E6%AD%A5%EF%BC%88%E5%9B%9B%EF%BC%89/"/>
<id>http://tanyibing.com/2018/09/18/javascript异步(四)/</id>
<published>2018-09-18T02:56:41.000Z</published>
<updated>2018-10-30T13:53:26.457Z</updated>
<content type="html"><![CDATA[<h1 id="JavaScript异步(四)————async-amp-await"><a href="#JavaScript异步(四)————async-amp-await" class="headerlink" title="JavaScript异步(四)————async & await"></a>JavaScript异步(四)————async & await</h1><p>前面已经介绍了<code>Generator</code>,我的唯一感觉就是————学习成本真特喵的高,我们今天还要看一个异步的终极解决方案————<code>async-await</code>,这就是ES7自己参照Generator封装的,其实更像是Generator的语法糖。</p><h2 id="ES7中的async-await"><a href="#ES7中的async-await" class="headerlink" title="ES7中的async-await"></a>ES7中的async-await</h2><h3 id="Generator和async-await对比"><a href="#Generator和async-await对比" class="headerlink" title="Generator和async-await对比"></a>Generator和async-await对比</h3><p>我们先来一段Generator的异步处理代码:</p><pre><code>co(function* () { const r1 = yield readFilePromise('some1.json') console.log(r1) // 打印第 1 个文件内容 const r2 = yield readFilePromise('some2.json') console.log(r2) // 打印第 2 个文件内容})</code></pre><p>接着看看使用async-await,对比一下;</p><pre><code>const readFilePromise = Q.denodeify(fs.readFile)// 定义 async 函数const readFileAsync = async function () { const f1 = await readFilePromise('data1.json') const f2 = await readFilePromise('data2.json') console.log('data1.json', f1.toString()) console.log('data2.json', f2.toString()) return 'done' // 先忽略,后面会讲到}// 执行const result = readFileAsync()</code></pre><p>我们可以比较看出,async function代替了function* ,await代替了yield,而且使用async-await不在用co这个第三方库了,直接执行即可。</p><h3 id="使用async-await的差异"><a href="#使用async-await的差异" class="headerlink" title="使用async-await的差异"></a>使用async-await的差异</h3><p>第一,await后面不能再跟thunk函数,而必须跟一个Promise对象(因此,Promise才是异步的终极解决方案和未来)。跟其他类型的数据也OK,但是会直接同步执行,而不是异步。</p><p>第二,执行const result = readFileAsync()返回的是个Promise对象,而且上面代码中的return ‘done’会直接被下面的then函数接收到</p><pre><code>result.then(data => { console.log(data) // done})</code></pre><p>第三,从代码的易读性来将,async-await更加易读简介,也更加符合代码的语意。而且还不用引用第三方库,也无需学习Generator那一堆东西,使用成本非常低。</p><p>因此,如果 ES7 正式发布了之后,强烈推荐使用async-await。但是现在尚未正式发布,从稳定性考虑,还是Generator更好一些。</p><h2 id="异步操作代码的演变历程"><a href="#异步操作代码的演变历程" class="headerlink" title="异步操作代码的演变历程"></a>异步操作代码的演变历程</h2><p>最后再来感受下从<code>callback</code>到<code>async-await</code>的历程吧。</p><h3 id="callback方式"><a href="#callback方式" class="headerlink" title="callback方式"></a>callback方式</h3><pre><code>fs.readFile('some1.json', (err, data) => { fs.readFile('some2.json', (err, data) => { fs.readFile('some3.json', (err, data) => { fs.readFile('some4.json', (err, data) => { }) }) })})</code></pre><h3 id="Promise方式"><a href="#Promise方式" class="headerlink" title="Promise方式"></a>Promise方式</h3><pre><code>readFilePromise('some1.json').then(data => { return readFilePromise('some2.json')}).then(data => { return readFilePromise('some3.json')}).then(data => { return readFilePromise('some4.json')})</code></pre><h3 id="Generator方式"><a href="#Generator方式" class="headerlink" title="Generator方式"></a>Generator方式</h3><pre><code>co(function* () { const r1 = yield readFilePromise('some1.json') const r2 = yield readFilePromise('some2.json') const r3 = yield readFilePromise('some3.json') const r4 = yield readFilePromise('some4.json')})</code></pre><h3 id="async-await方式"><a href="#async-await方式" class="headerlink" title="async-await方式"></a>async-await方式</h3><pre><code>const readFileAsync = async function () { const f1 = await readFilePromise('data1.json') const f2 = await readFilePromise('data2.json') const f3 = await readFilePromise('data3.json') const f4 = await readFilePromise('data4.json')}</code></pre><p>到这基本就完了,如果有看到好文章以后再补充,js异步探索的任务算是结束了。</p>]]></content>
<summary type="html">
<h1 id="JavaScript异步(四)————async-amp-await"><a href="#JavaScript异步(四)————async-amp-await" class="headerlink" title="JavaScript异步(四)————async
</summary>
<category term="JavaScript" scheme="http://tanyibing.com/categories/JavaScript/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
</entry>
<entry>
<title>javascript异步(三)</title>
<link href="http://tanyibing.com/2018/09/18/javascript%E5%BC%82%E6%AD%A5%EF%BC%88%E4%B8%89%EF%BC%89/"/>
<id>http://tanyibing.com/2018/09/18/javascript异步(三)/</id>
<published>2018-09-18T01:18:08.000Z</published>
<updated>2018-10-30T13:53:26.455Z</updated>
<content type="html"><![CDATA[<h1 id="JavaScript异步(三)————Generator"><a href="#JavaScript异步(三)————Generator" class="headerlink" title="JavaScript异步(三)————Generator"></a>JavaScript异步(三)————Generator</h1><p>上次已经介绍过一点<code>Generator</code>对象的知识了,这次就继续说说Generator的应用吧。</p><h2 id="Generator的应用"><a href="#Generator的应用" class="headerlink" title="Generator的应用"></a>Generator的应用</h2><h3 id="使用next和yield传递参数"><a href="#使用next和yield传递参数" class="headerlink" title="使用next和yield传递参数"></a>使用next和yield传递参数</h3><p>我们已经知道,<code>yield</code>具有返回数据的功能,如下代码。yield后面的数据被返回,存放到返回结果中的value属性中。这算是一个方向的参数传递。</p><pre><code>function* G() { yield 100}const g = G()console.log( g.next() ) // {value: 100, done: false}</code></pre><p>还有另一个方向的参数传递,就是next向yield传递,如下:</p><pre><code>function* G() { const a = yield 100 console.log('a', a) // a aaa const b = yield 200 console.log('b', b) // b bbb const c = yield 300 console.log('c', c) // c ccc}const g = G()g.next() // value: 100, done: falseg.next('aaa') // value: 200, done: falseg.next('bbb') // value: 300, done: falseg.next('ccc') // value: undefined, done: true</code></pre><p>我们看看上面代码的执行过程:</p><ul><li>执行第一个g.next()时,未传递任何参数,返回的{value: 100, done: false},这个应该没有疑问</li><li>执行第二个g.next(‘aaa’)时,传递的参数是’aaa’,这个’aaa’就会被赋值到G内部的a标量中,然后执行console.log(‘a’, a)打印出来,最后返回{value: 200, done: false}</li><li>执行第三个、第四个时,道理都是完全一样的,大家自己捋一捋。</li></ul><p>有一个要点需要注意,就g.next(‘aaa’)是将’aaa’传递给上一个已经执行完了的yield语句前面的变量,而不是即将执行的yield前面的变量。</p><h3 id="for…of的应用"><a href="#for…of的应用" class="headerlink" title="for…of的应用"></a>for…of的应用</h3><p>for…of是Iterator对象的一个经典操作,我们使用一个斐波那契数列来看一看:</p><pre><code>function* fibonacci() { let [prev, curr] = [0, 1] for (;;) { [prev, curr] = [curr, prev + curr] // 将中间值通过 yield 返回,并且保留函数执行的状态,因此可以非常简单的实现 fibonacci yield curr }}for (let n of fibonacci()) { if (n > 1000) { break } console.log(n)}</code></pre><p>这样我们就能找到1000里面的斐波那契数列了。</p><h3 id="yield-嵌套Generator"><a href="#yield-嵌套Generator" class="headerlink" title="yield*嵌套Generator"></a>yield*嵌套Generator</h3><p>如果我们有两个Generator,我们想在第一个中包含第二个:</p><pre><code>function* G1() { yield 'a' yield* G2() // 使用 yield* 执行 G2() yield 'b'}function* G2() { yield 'x' yield 'y'}for (let item of G1()) { console.log(item)}</code></pre><p>之前学过的yield后面会接一个普通的 JS 对象,而yield<em> 后面会接一个Generator,而且会把它其中的yield按照规则来一步一步执行。如果有多个Generator串联使用的话(例如Koa源码中),用yield</em>来操作非常方便。</p><h3 id="Generator中的this"><a href="#Generator中的this" class="headerlink" title="Generator中的this"></a>Generator中的this</h3><p>对于以下这种写法,大家可能会和构造函数创建对象的写法产生混淆,这里一定要注意 —— Generator 不是函数,更不是构造函数</p><pre><code>function* G() {}const g = G()</code></pre><p>而以下这种写法,更加不会成功。只有构造函数才会这么用,构造函数返回的是this,而Generator返回的是一个Iterator对象。完全是两码事,千万不要搞混了。</p><pre><code>function* G() { this.a = 10}const g = G()console.log(g.a) // 报错</code></pre><h2 id="Thunk函数"><a href="#Thunk函数" class="headerlink" title="Thunk函数"></a>Thunk函数</h2><p>为什么要说说Thunk函数呢,因为它和Generator处理异步操作还是有关系的,我们先看看。</p><h3 id="普通异步函数"><a href="#普通异步函数" class="headerlink" title="普通异步函数"></a>普通异步函数</h3><pre><code>fs.readFile('data1.json', 'utf-8', (err, data) => { // 获取文件内容})</code></pre><p>这个普通的node读取文件的函数传递了三个函数,接下来我们进行一点改造。</p><h3 id="封装成Thunk函数"><a href="#封装成Thunk函数" class="headerlink" title="封装成Thunk函数"></a>封装成Thunk函数</h3><pre><code>const thunk = function (fileName, codeType) { // 返回一个只接受 callback 参数的函数 return function (callback) { fs.readFile(fileName, codeType, callback) }}const readFileThunk = thunk('data1.json', 'utf-8')readFileThunk((err, data) => { // 获取文件内容})</code></pre><p>从上面的Thunk函数可以看出,执行const readFileThunk = thunk(‘data1.json’, ‘utf-8’)返回的其实是一个函数,readFileThunk这个函数,只接受一个参数,而且这个参数是一个callback函数。</p><h3 id="使用thunkify库"><a href="#使用thunkify库" class="headerlink" title="使用thunkify库"></a>使用thunkify库</h3><p>上面的代码封装使我们自己做的,但我们不需要每遇到一个情况就自己做,我们可以直接使用第三方的thunkify就可以了。</p><pre><code>onst thunk = thunkify(fs.readFile)const readFileThunk = thunk('data1.json', 'utf-8')readFileThunk((err, data) => { // 获取文件内容})</code></pre><h2 id="Generator异步操作"><a href="#Generator异步操作" class="headerlink" title="Generator异步操作"></a>Generator异步操作</h2><p>上次我们只是大概的讲了讲,接下来我们详细看看Generator是如何进行异步操作的</p><h3 id="Generator中使用Thunk"><a href="#Generator中使用Thunk" class="headerlink" title="Generator中使用Thunk"></a>Generator中使用Thunk</h3><p>直接看代码吧</p><pre><code>const readFileThunk = thunkify(fs.readFile)const gen = function* () { const r1 = yield readFileThunk('data1.json') console.log(r1) const r2 = yield readFileThunk('data2.json') console.log(r2)}</code></pre><h3 id="挨个读取两个文件的内容"><a href="#挨个读取两个文件的内容" class="headerlink" title="挨个读取两个文件的内容"></a>挨个读取两个文件的内容</h3><p>接着上面的代码继续写:</p><pre><code>const g = gen()// 试着打印 g.next() 这里一定要明白 value 是一个 thunk函数 ,否则下面的代码你都看不懂// console.log( g.next() ) // g.next() 返回 {{ value: thunk函数, done: false }} // 下一行中,g.next().value 是一个 thunk 函数,它需要一个 callback 函数作为参数传递进去g.next().value((err, data1) => { // 这里的 data1 获取的就是第一个文件的内容。下一行中,g.next(data1) 可以将数据传递给上面的 r1 变量,此前已经讲过这种参数传递的形式 // 下一行中,g.next(data1).value 又是一个 thunk 函数,它又需要一个 callback 函数作为参数传递进去 g.next(data1).value((err, data2) => { // 这里的 data2 获取的是第二个文件的内容,通过 g.next(data2) 将数据传递个上面的 r2 变量 g.next(data2) })})</code></pre><p>仔细看望上面的注释,也许会有中恍然大悟的感觉,原来是这样子把异步写成同步的感觉。</p><h3 id="自驱动流程"><a href="#自驱动流程" class="headerlink" title="自驱动流程"></a>自驱动流程</h3><p>接下来我们做一个自驱动的流程,定义好Generator的代码之后,就让他自动执行:</p><pre><code>// 自动流程管理的函数function run(generator) { const g = generator() function next(err, data) { const result = g.next(data) // 返回 { value: thunk函数, done: ... } if (result.done) { // result.done 表示是否结束,如果结束了那就 return 作罢 return } result.value(next) // result.value 是一个 thunk 函数,需要一个 callback 函数作为参数,而 next 就是一个 callback 形式的函数 } next() // 手动执行以启动第一次 next}// 定义 Generatorconst readFileThunk = thunkify(fs.readFile)const gen = function* () { const r1 = yield readFileThunk('data1.json') console.log(r1.toString()) const r2 = yield readFileThunk('data2.json') console.log(r2.toString())}// 启动执行run(gen)</code></pre><p>我们简单分析下:</p><ul><li>最后一行run(gen)之后,进入run函数内部执行</li><li>先const g = generator()创建Generator实例,然后定义一个next方法,并且立即执行next()</li><li>注意这个next函数的参数是err, data两个,和我们fs.readFile用到的callback函数形式完全一样</li><li>第一次执行next时,会执行const result = g.next(data),而g.next(data)返回的是{ value: thunk函数, done: … },value是一个thunk函数,done表示是否结束</li><li>如果done: true,那就直接return了,否则继续进行</li><li>result.value是一个thunk函数,需要接受一个callback函数作为参数传递进去,因此正好把next给传递进去,让next一直被执行下去</li></ul><h3 id="co库"><a href="#co库" class="headerlink" title="co库"></a>co库</h3><p>这个流程我们也可以使用第三方的库co,用Generator的工程师肯定都要用co,两者天生一对。</p><pre><code>// 定义 Generatorconst readFileThunk = thunkify(fs.readFile) const gen = function* () { const r1 = yield readFileThunk('data1.json') console.log(r1.toString()) const r2 = yield readFileThunk('data2.json') console.log(r2.toString())}const c = co(gen)</code></pre><p>而且const c = co(gen)返回的是一个Promise对象,可以接着这么写</p><pre><code>c.then(data => { console.log('结束')})</code></pre><h2 id="Koa中使用Generator"><a href="#Koa中使用Generator" class="headerlink" title="Koa中使用Generator"></a>Koa中使用Generator</h2><p>Koa第一版中大量使用了Generator,接下来我们去看看怎么使用的。</p><h3 id="Koa中如何使用Generator"><a href="#Koa中如何使用Generator" class="headerlink" title="Koa中如何使用Generator"></a>Koa中如何使用Generator</h3><p>koa 是一个 web 框架,处理 http 请求,但是这里我们不去管它如何处理 http 请求,而是直接关注它使用Genertor的部分————中间件。<br>例如,我们现在要用 3 个Generator输出12345,我们如下代码这么写。</p><pre><code>let info = ''function* g1() { info += '1' // 拼接 1 yield* g2() // 拼接 234 info += '5' // 拼接 5}function* g2() { info += '2' // 拼接 2 yield* g3() // 拼接 3 info += '4' // 拼接 4}function* g3() { info += '3' // 拼接 3}var g = g1()g.next()console.log(info) // 12345</code></pre><p>但是如果用 koa 的 中间件 的思路来做,就需要如下这么写。</p><pre><code>app.use(function *(next){ this.body = '1'; yield next; this.body += '5'; console.log(this.body);});app.use(function *(next){ this.body += '2'; yield next; this.body += '4';});app.use(function *(next){ this.body += '3';});</code></pre><p>我们需要注意几点:</p><ul><li>app.use()中传入的每一个Generator就是一个 中间件,中间件按照传入的顺序排列,顺序不能乱</li><li>每个中间件内部,next表示下一个中间件。yield next就是先将程序暂停,先去执行下一个中间件,等next被执行完之后,再回过头来执行当前代码的下一行。因此,koa 的中间件执行顺序是一种洋葱圈模型,不过这里看不懂也没问题。</li><li>每个中间件内部,this可以共享变量。即第一个中间件改变了this的属性,在第二个中间件中可以看到效果。</li></ul><h3 id="Koa的这种机制如何实现的"><a href="#Koa的这种机制如何实现的" class="headerlink" title="Koa的这种机制如何实现的"></a>Koa的这种机制如何实现的</h3><p>我们封住个简单的Koa:</p><pre><code>class MyKoa extends Object { constructor(props) { super(props); // 存储所有的中间件 this.middlewares = [] } // 注入中间件 use (generator) { this.middlewares.push(generator) } // 执行中间件 listen () { this._run() } _run () { const ctx = this const middlewares = ctx.middlewares co(function* () { let prev = null let i = middlewares.length //从最后一个中间件到第一个中间件的顺序开始遍历 while (i--) { // ctx 作为函数执行时的 this 才能保证多个中间件中数据的共享 //prev 将前面一个中间件传递给当前中间件,才使得中间件里面的 next 指向下一个中间件 prev = middlewares[i].call(ctx, prev); } //执行第一个中间件 yield prev; }) }}</code></pre><p>如何使用呢:</p><pre><code>var app = new MyKoa();app.use(function *(next){ this.body = '1'; yield next; this.body += '5'; console.log(this.body); // 12345});app.use(function *(next){ this.body += '2'; yield next; this.body += '4';});app.use(function *(next){ this.body += '3';});app.listen();</code></pre>]]></content>
<summary type="html">
<h1 id="JavaScript异步(三)————Generator"><a href="#JavaScript异步(三)————Generator" class="headerlink" title="JavaScript异步(三)————Generator"></a>Ja
</summary>
<category term="JavaScript" scheme="http://tanyibing.com/categories/JavaScript/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
</entry>
<entry>
<title>javascript异步(二)</title>
<link href="http://tanyibing.com/2018/09/17/javascript%E5%BC%82%E6%AD%A5%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<id>http://tanyibing.com/2018/09/17/javascript异步(二)/</id>
<published>2018-09-17T06:40:16.000Z</published>
<updated>2018-10-30T13:53:26.456Z</updated>
<content type="html"><![CDATA[<h1 id="JavaScript异步(二)————Promise"><a href="#JavaScript异步(二)————Promise" class="headerlink" title="JavaScript异步(二)————Promise"></a>JavaScript异步(二)————Promise</h1><h2 id="ES6中的Promise"><a href="#ES6中的Promise" class="headerlink" title="ES6中的Promise"></a>ES6中的Promise</h2><h3 id="写一个传统的异步操作"><a href="#写一个传统的异步操作" class="headerlink" title="写一个传统的异步操作"></a>写一个传统的异步操作</h3><p>我们先写一段异步的代码,然后用promise来封装一下:</p><pre><code>var wait = function () { var task = function () { console.log('执行完成') } setTimeout(task, 2000)}wait()</code></pre><h3 id="用Promise进行封装"><a href="#用Promise进行封装" class="headerlink" title="用Promise进行封装"></a>用Promise进行封装</h3><pre><code>const wait = function () { // 定义一个 promise 对象 const promise = new Promise((resolve, reject) => { // 将之前的异步操作,包括到这个 new Promise 函数之内 const task = function () { console.log('执行完成') resolve() // callback 中去执行 resolve 或者 reject } setTimeout(task, 2000) }) // 返回 promise 对象 return promise}</code></pre><p>我们首先将之前传统的异步方法用<code>new Promise((resolve, reject) => {...})</code>包装起来,然后return就行了。异步操作的内部,在callback中执行<code>resolve()</code>(表明成功了,失败的话执行reject)。</p><p>我们接下来怎么处理返回的数据呢:</p><pre><code>const w = wait()w.then(() => { console.log('ok 1')}, () => { console.log('err 1')}).then(() => { console.log('ok 2')}, () => { console.log('err 2')})</code></pre><p>我们调用<code>then</code>方法,这个方法接收两个参数,第一个在成功时(触发resolve)执行,第二个在失败时(触发reject)时执行。而且,then还可以进行链式操作。</p><p>以上就是ES6中Promise的基本使用方法,接下来看一些Promise的常见用法吧。</p><h2 id="Promise在ES6中的常见用法"><a href="#Promise在ES6中的常见用法" class="headerlink" title="Promise在ES6中的常见用法"></a>Promise在ES6中的常见用法</h2><p>为了方便使用,我们首先封装个Promise,为后面使用:</p><pre><code>const fs = require('fs')const path = require('path') // 后面获取文件路径时候会用到const readFilePromise = function (fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, (err, data) => { if (err) { reject(err) // 注意,这里执行 reject 是传递了参数,后面会有地方接收到这个参数 } else { resolve(data.toString()) // 注意,这里执行 resolve 时传递了参数,后面会有地方接收到这个参数 } }) })}</code></pre><h3 id="参数传递"><a href="#参数传递" class="headerlink" title="参数传递"></a>参数传递</h3><p>我们要使用上面封装的readFilePromise读取一个 json 文件../data/data2.json,这个文件内容非常简单:{“a”:100, “b”:200}</p><p>先将文件内容打印出来,代码如下。大家需要注意,readFilePromise函数中,执行<code>resolve(data.toString())</code>传递的参数内容,会被下面代码中的data参数所接收到。</p><pre><code>const fullFileName = path.resolve(__dirname, '../data/data2.json')const result = readFilePromise(fullFileName)result.then(data => { console.log(data)})</code></pre><p>参数传递就是在异步里面通过resolve来传递至,然后就会被第一个<code>then</code>处理时接收到,而且通过<code>then</code>的链式操作可以继续将处理后的值传递下去。</p><h3 id="异常捕获"><a href="#异常捕获" class="headerlink" title="异常捕获"></a>异常捕获</h3><p>我们知道then会接收两个参数(函数),第一个参数会在执行resolve之后触发(还能传递参数),第二个参数会在执行reject之后触发(其实也可以传递参数,和resolve传递参数一样),但是上面的例子中,我们没有用到then的第二个参数。这是为何呢 ———— 因为不建议这么用。</p><p>对于Promise中的异常处理,我们建议用catch方法,而不是then的第二个参数。请看下面的代码,以及注释。</p><pre><code>const fullFileName = path.resolve(__dirname, '../data/data2.json')const result = readFilePromise(fullFileName)result.then(data => { console.log(data) return JSON.parse(data).a}).then(a => { console.log(a)).catch(err => { console.log(err.stack) // 这里的 catch 就能捕获 readFilePromise 中触发的 reject ,而且能接收 reject 传递的参数})</code></pre><p>在若干个then串联之后,我们一般会在最后跟一个.catch来捕获异常,而且执行reject时传递的参数也会在catch中获取到。这样做的好处是:</p><ul><li>让程序看起来更加简洁,是一个串联的关系,没有分支(如果用then的两个参数,就会出现分支,影响阅读)</li><li>更像是try - catch的样子,更易理解</li></ul><h3 id="串联多个异步操作"><a href="#串联多个异步操作" class="headerlink" title="串联多个异步操作"></a>串联多个异步操作</h3><p>如果现在有一个需求:先读取data2.json的内容,当成功之后,再去读取data1.json。这样的需求,如果用传统的callback去实现,会变得很麻烦。而且,现在只是两个文件,如果是十几个文件这样做,写出来的代码就没法看了(臭名昭著的callback-hell)。但是用刚刚学到的Promise就可以轻松胜任这项工作:</p><pre><code>const fullFileName2 = path.resolve(__dirname, '../data/data2.json')const result2 = readFilePromise(fullFileName2)const fullFileName1 = path.resolve(__dirname, '../data/data1.json')const result1 = readFilePromise(fullFileName1)result2.then(data => { console.log('data2.json', data) return result1 // 此处只需返回读取 data1.json 的 Promise 即可}).then(data => { console.log('data1.json', data) // data 即可接收到 data1.json 的内容})</code></pre><p>上文“参数传递”提到过,如果then有链式操作,前面步骤返回的值,会被后面的步骤获取到。但是,如果前面步骤返回值是一个Promise的话,情况就不一样了。如果前面返回的是Promise对象,后面的then将会被当做这个返回的Promise的第一个then来对待。</p><h3 id="Promise-all-amp-Promise-race"><a href="#Promise-all-amp-Promise-race" class="headerlink" title="Promise.all & Promise.race"></a>Promise.all & Promise.race</h3><p>现在我需要一起读取data1.json和data2.json这两个文件,等待它们全部都被读取完,再做下一步的操作。此时需要用到Promise.all:</p><pre><code>// Promise.all 接收一个包含多个 promise 对象的数组Promise.all([result1, result2]).then(datas => { // 接收到的 datas 是一个数组,依次包含了多个 promise 返回的内容 console.log(datas[0]) console.log(datas[1])})</code></pre><p>读取两个文件data1.json和data2.json,现在我需要一起读取这两个文件,但是只要有一个已经读取了,就可以进行下一步的操作。此时需要用到Promise.race:</p><pre><code>// Promise.race 接收一个包含多个 promise 对象的数组Promise.race([result1, result2]).then(data => { // data 即最先执行完成的 promise 的返回值 console.log(data)})</code></pre>]]></content>
<summary type="html">
<h1 id="JavaScript异步(二)————Promise"><a href="#JavaScript异步(二)————Promise" class="headerlink" title="JavaScript异步(二)————Promise"></a>JavaScri
</summary>
<category term="JavaScript" scheme="http://tanyibing.com/categories/JavaScript/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
</entry>
<entry>
<title>node中踩的坑(不定时更新)</title>
<link href="http://tanyibing.com/2018/09/14/node%E4%B8%AD%E8%B8%A9%E7%9A%84%E5%9D%91%EF%BC%88%E4%B8%8D%E5%AE%9A%E6%97%B6%E6%9B%B4%E6%96%B0%EF%BC%89/"/>
<id>http://tanyibing.com/2018/09/14/node中踩的坑(不定时更新)/</id>
<published>2018-09-14T09:02:35.000Z</published>
<updated>2018-10-30T13:53:26.461Z</updated>
<content type="html"><![CDATA[<h3 id="2018-9-14"><a href="#2018-9-14" class="headerlink" title="2018-9-14"></a>2018-9-14</h3><p><strong>问题</strong><br>在Koa中想要使用art-template这个模板引擎,但是使用之后在渲染模板的时候一直报下面的错:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/node/1.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p><strong>分析</strong><br>报的错说我的路径必须为一个字符串,我重新检查后,发现并不存在语法错误呀,果断去度娘看看,试了几个方法解决不了我的问题,但是反馈最多的一个方法就是node版本和其他的不兼容。我靠!这要重搞工作量有点大,果断换个模板引擎吧。</p><p><strong>解决方法</strong><br>最后回去官网看一看,发现有<code>art-template</code>和<code>koa-art-template</code>之分,但是配置好像没啥区别啊。把引入的模块改成<code>koa-art-template</code>,再试试,成了!什么鬼!不管了,问题暂时是解决了。</p><p><strong>另外附上项目的地址</strong>:<a href="https://github.com/TanYiBing/cms_demo" target="_blank" rel="noopener">https://github.com/TanYiBing/cms_demo</a></p>]]></content>
<summary type="html">
<h3 id="2018-9-14"><a href="#2018-9-14" class="headerlink" title="2018-9-14"></a>2018-9-14</h3><p><strong>问题</strong><br>在Koa中想要使用art-templa
</summary>
<category term="Node" scheme="http://tanyibing.com/categories/Node/"/>
<category term="Node" scheme="http://tanyibing.com/tags/Node/"/>
<category term="Koa" scheme="http://tanyibing.com/tags/Koa/"/>
</entry>
<entry>
<title>javascript异步(一)</title>
<link href="http://tanyibing.com/2018/09/14/javascript%E5%BC%82%E6%AD%A5%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<id>http://tanyibing.com/2018/09/14/javascript异步(一)/</id>
<published>2018-09-14T08:06:01.000Z</published>
<updated>2018-10-30T13:53:26.453Z</updated>
<content type="html"><![CDATA[<h1 id="JavaScript异步(一)"><a href="#JavaScript异步(一)" class="headerlink" title="JavaScript异步(一)"></a>JavaScript异步(一)</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>昨天刚立了个flag,而我这个人向来都是说话不算话的(手动滑稽),为了防止自己半途而废,所以今天先来开个头,开了头就不好回头了。</p><h2 id="异步和单线程有什么关系?"><a href="#异步和单线程有什么关系?" class="headerlink" title="异步和单线程有什么关系?"></a>异步和单线程有什么关系?</h2><h3 id="1-为啥是单线程"><a href="#1-为啥是单线程" class="headerlink" title="1.为啥是单线程"></a>1.为啥是单线程</h3><p><code>JavaScript</code> 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。</p><p>JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?</p><p>所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。</p><p>为了利用多核CPU的计算能力,HTML5提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。</p><h3 id="2-啥是异步"><a href="#2-啥是异步" class="headerlink" title="2.啥是异步"></a>2.啥是异步</h3><p>我们先看一段代码:</p><pre><code>var i, t = Date.now();for (i = 0; i < 100000000; i++) {}console.log(Date.now() - t) // 250</code></pre><p>上面的程序花费 250ms 的时间执行完成,执行过程中就会有卡顿,其他的事儿就先撂一边不管了。</p><p>执行程序这样没有问题,但是对于 JS 最初使用的环境 ———— 浏览器客户端 ———— 就不一样了。因此在浏览器端运行的js,可能会有大量的网络请求,而一个网络资源啥时候返回,这个时间是不可预估的。这种情况也要傻傻的等着、卡顿着、啥都不做吗?———— 那肯定不行。</p><p>因此,JS 对于这种场景就设计了异步 ———— 即,发起一个网络请求,就先不管这边了,先干其他事儿,网络请求啥时候返回结果,到时候再说。这样就能保证一个网页的流程运行。</p><h3 id="3-事件和回调函数"><a href="#3-事件和回调函数" class="headerlink" title="3.事件和回调函数"></a>3.事件和回调函数</h3><p>“任务队列”是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。</p><p>“任务队列”中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。</p><p>所谓”回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。</p><p>“任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的”定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。</p><h3 id="4-使用回调函数来进行异步操作"><a href="#4-使用回调函数来进行异步操作" class="headerlink" title="4.使用回调函数来进行异步操作"></a>4.使用回调函数来进行异步操作</h3><p>举个栗子:</p><pre><code>var ajax = $.ajax({ url: '/data/data1.json', success: function () { console.log('success') }})</code></pre><p>上面这个<code>ajax</code>操作就是一个异步操作,第二个参数就是一个callback,我们先发个消息出去取一些数据,当然不会立即就能取到,那我们也不闲着,先去干别的,等拿到了通知我们,我们再执行callback进行数据操作。<strong>实现异步的最核心原理,就是将callback作为参数传递给异步执行函数,当有结果返回之后再触发 callback执行,就是如此简单!</strong></p><h3 id="5-常用的异步操作"><a href="#5-常用的异步操作" class="headerlink" title="5.常用的异步操作"></a>5.常用的异步操作</h3><p>开发中我们比较常用的异步操作有;</p><ul><li>网络请求,如 ajax、http</li><li>IO操作,如 readFile、readDir</li><li>定时函数:如 setTimeout、setInterval</li></ul><h2 id="Event-Loop"><a href="#Event-Loop" class="headerlink" title="Event Loop"></a>Event Loop</h2><p><strong>这一部分让我写肯定显得业余,还是去看看阮一峰老师怎么说吧:</strong><a href="http://www.ruanyifeng.com/blog/2014/10/event-loop.html" target="_blank" rel="noopener">JavaScript 运行机制详解:再谈Event Loop</a></p><h2 id="事件绑定算不算异步呢?"><a href="#事件绑定算不算异步呢?" class="headerlink" title="事件绑定算不算异步呢?"></a>事件绑定算不算异步呢?</h2><p>我们看一个事件绑定的操作:</p><pre><code>$btn.on('click', function (e) { console.log('你点击了按钮')})</code></pre><p>这个写法和我们上面的异步操作是不是一样,都定义了callback,那事件绑定算不算异步操作呢?如果你认真看了阮一峰老师的那篇文章,你就会发现,其实这也是异步操作。为什么我会这么问呢?</p><p>因为它们之间还有有些不同之处的:</p><ul><li>event-loop 执行时,调用的源不一样。异步操作是系统自动调用,无论是setTimeout时间到了还是$.ajax请求返回了,系统会自动调用。而事件绑定就需要用户手动触发。</li><li>从设计上来将,事件绑定有着明显的“订阅-发布”的设计模式,而异步操作却没有。</li></ul><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/js/4.jpg" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>但是我们从这张图上又可以发现,我们的事件包括了<code>Network</code>和我们的一些鼠标操作,这些都被认为是异步的。其实仔细想想也对,网络请求其实也相当于订阅和发布啊!</p><p>今天的就到这吧!</p>]]></content>
<summary type="html">
<h1 id="JavaScript异步(一)"><a href="#JavaScript异步(一)" class="headerlink" title="JavaScript异步(一)"></a>JavaScript异步(一)</h1><h2 id="前言"><a href="
</summary>
<category term="JavaScript" scheme="http://tanyibing.com/categories/JavaScript/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
</entry>
<entry>
<title>封装个Koa</title>
<link href="http://tanyibing.com/2018/09/13/%E5%B0%81%E8%A3%85%E4%B8%AAKoa/"/>
<id>http://tanyibing.com/2018/09/13/封装个Koa/</id>
<published>2018-09-13T08:39:37.000Z</published>
<updated>2018-10-30T13:53:26.473Z</updated>
<content type="html"><![CDATA[<p>想了解下<code>Koa</code>是怎么个骚操作,之前使用的是<code>Koa2</code>,里面是<code>async</code>和<code>await</code>,用起来爽歪歪。不过,<code>async</code>和<code>await</code>是ES7里面的东东,还是有必要看一下koa的第一代的,发现里面的中间件是<code>Generator</code>,其实我对这个也不是很理解,ES6里面新增加的,一下子有点难接受,今天就提上日程,看一看怎么搞,自己封装个Koa试试!</p><p>想要真正搞懂这个<code>Generator</code>还是比较烦,我自己感觉整个ES6的主要部分应该就是这个Generator了,相知带它怎么回事先要知道<code>Iterator</code>、然后再看<code>Generator</code>,但是想和异步扯上关系你还要看<code>Chunk</code>函数,还要看看<code>co</code>库,当然啦,<code>Promise</code>肯定要知道,所以感觉这也不是个简单的东西,门道多着呢。</p><p>不说废话了,上代码吧,封装个<code>MyKoa</code>:</p><pre><code>class MyKoa extends Object { constructor(props) { super(props); // 存储所有的中间件 this.middlewares = [] } // 注入中间件 use (generator) { this.middlewares.push(generator) } // 执行中间件 listen () { this._run() } _run () { const ctx = this; const middlewares = ctx.middlewares; co(function* () { let prev = null let i = middlewares.length //从最后一个中间件到第一个中间件的顺序开始遍历 while (i--) { // ctx 作为函数执行时的 this 才能保证多个中间件中数据的共享 //prev 将前面一个中间件传递给当前中间件,才使得中间件里面的 next 指向下一个中间件 prev = middlewares[i].call(ctx, prev); } //执行第一个中间件 yield prev; }) }}</code></pre><p>然后可以试验下效果:</p><pre><code>var app = new MyKoa();app.use(function *(next){ this.body = '1'; yield next; this.body += '5'; console.log(this.body); // 12345});app.use(function *(next){ this.body += '2'; yield next; this.body += '4';});app.use(function *(next){ this.body += '3';});app.listen();</code></pre><p><strong>其实Generator和我们的回调处理异步的callback根本没有一点关系,它的本质是‘暂停’,并将执行权转移,我们只是给它穿上了一层又一层的外套,强行让它和异步‘发生关系’。</strong></p><p><strong>PS:立个flag,2018年过年之前找个机会,我一定彻彻底底研究次js中的异步!!!</strong></p>]]></content>
<summary type="html">
<p>想了解下<code>Koa</code>是怎么个骚操作,之前使用的是<code>Koa2</code>,里面是<code>async</code>和<code>await</code>,用起来爽歪歪。不过,<code>async</code>和<code>await</co
</summary>
<category term="Node" scheme="http://tanyibing.com/categories/Node/"/>
<category term="Node" scheme="http://tanyibing.com/tags/Node/"/>
<category term="Koa" scheme="http://tanyibing.com/tags/Koa/"/>
</entry>
<entry>
<title>Jade怎么玩</title>
<link href="http://tanyibing.com/2018/09/13/Jade%E6%80%8E%E4%B9%88%E7%8E%A9/"/>
<id>http://tanyibing.com/2018/09/13/Jade怎么玩/</id>
<published>2018-09-13T02:24:18.000Z</published>
<updated>2018-10-30T13:53:26.262Z</updated>
<content type="html"><![CDATA[<p>前端想要用模板,那就看看<code>Jade</code>吧,打开我们的<a href="http://jade-lang.com/" target="_blank" rel="noopener">Jade官网</a>,上去看看呢,结果发现官网真的很水,水在哪呢?特喵的,给的代码没有一个注意缩进的,这也就导致后面很多模板使用jade命令时报错,有失水准啊!</p><p>好在咋们国人搞的还是比较靠谱:<a href="http://www.nooong.com/docs/jade_chinese.htm" target="_blank" rel="noopener">点这可以学习Jade</a></p><p><strong>你不会真以为我要写个技术博客吧,我只是来吐槽下,Jade很简单,自己去学吧,几分钟就会了。</strong></p>]]></content>
<summary type="html">
<p>前端想要用模板,那就看看<code>Jade</code>吧,打开我们的<a href="http://jade-lang.com/" target="_blank" rel="noopener">Jade官网</a>,上去看看呢,结果发现官网真的很水,水在哪呢?特喵的,给
</summary>
<category term="Jade" scheme="http://tanyibing.com/categories/Jade/"/>
<category term="Jade" scheme="http://tanyibing.com/tags/Jade/"/>
</entry>
<entry>
<title>Generator函数</title>
<link href="http://tanyibing.com/2018/09/12/Generator%E5%87%BD%E6%95%B0/"/>
<id>http://tanyibing.com/2018/09/12/Generator函数/</id>
<published>2018-09-12T08:09:54.000Z</published>
<updated>2018-10-30T13:53:26.259Z</updated>
<content type="html"><![CDATA[<h1 id="Generator函数"><a href="#Generator函数" class="headerlink" title="Generator函数"></a>Generator函数</h1><p>昨天的<code>Promise</code>对象就是一种异步编程解决方案,今天再看一种方案,这就是<code>Generator</code>函数。<br>网上有很多关于<code>Generator</code>的介绍,例如:</p><blockquote><p>Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。<br>执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。</p></blockquote><h2 id="Generator的创建"><a href="#Generator的创建" class="headerlink" title="Generator的创建"></a>Generator的创建</h2><p>接下来我们看一下怎么定义一个Generator函数:</p><pre><code>function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending';}var hw = helloWorldGenerator();</code></pre><p>我们发现在<code>function</code>关键字和函数名称之间有个星号,这表示这是一个Generator函数。但这样还不行,进入函数我们发现使用了<code>yield</code>表达式,定义了不同的内部状态(<code>yield</code>在英语中就是“产生”的意思)。这样我们一共定义了三个状态:hello、word和一个return语句。</p><h2 id="Generator的调用"><a href="#Generator的调用" class="headerlink" title="Generator的调用"></a>Generator的调用</h2><p>Generator函数返回的是一个遍历器对象,也就是说我们需要调用<code>next</code>方法来遍历函数中的状态:</p><pre><code>hw.next()// { value: 'hello', done: false }hw.next()/ { value: 'world', done: false }hw.next()// { value: 'ending', done: true }hw.next()/ { value: undefined, done: true }</code></pre><p>上面一共运行了四次<code>next</code>方法。</p><p>第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。</p><p>第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。</p><p>第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。</p><p>第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。</p><h2 id="Generator函数的作用"><a href="#Generator函数的作用" class="headerlink" title="Generator函数的作用"></a>Generator函数的作用</h2><p>这个东西看上去好像没啥用,那么它到底能干点啥呢?</p><h3 id="把异步回调变成‘同步’代码"><a href="#把异步回调变成‘同步’代码" class="headerlink" title="把异步回调变成‘同步’代码"></a>把异步回调变成‘同步’代码</h3><p>例如我们写个ajax代码:</p><pre><code>ajax('http://url-1', data1, function (err, result) { if (err) { return handle(err); } ajax('http://url-2', data2, function (err, result) { if (err) { return handle(err); } ajax('http://url-3', data3, function (err, result) { if (err) { return handle(err); } return success(result); }); });});</code></pre><p>这个代码简直不想看,那么我们可以用Generator来改造下:</p><pre><code>try { r1 = yield ajax('http://url-1', data1); r2 = yield ajax('http://url-2', data2); r3 = yield ajax('http://url-3', data3); success(r3);}catch (err) { handle(err);}</code></pre><p>这样看上去好多了,但不是真正的同步代码,至少看上去很像。</p>]]></content>
<summary type="html">
<h1 id="Generator函数"><a href="#Generator函数" class="headerlink" title="Generator函数"></a>Generator函数</h1><p>昨天的<code>Promise</code>对象就是一种异步编程解
</summary>
<category term="ECMAScript6" scheme="http://tanyibing.com/categories/ECMAScript6/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
<category term="ECMAScript6" scheme="http://tanyibing.com/tags/ECMAScript6/"/>
</entry>
<entry>
<title>Promise对象</title>
<link href="http://tanyibing.com/2018/09/11/Promise%E5%AF%B9%E8%B1%A1/"/>
<id>http://tanyibing.com/2018/09/11/Promise对象/</id>
<published>2018-09-11T08:07:08.000Z</published>
<updated>2018-10-30T13:53:26.266Z</updated>
<content type="html"><![CDATA[<h1 id="Promise对象"><a href="#Promise对象" class="headerlink" title="Promise对象"></a>Promise对象</h1><h2 id="Promise的介绍"><a href="#Promise的介绍" class="headerlink" title="Promise的介绍"></a>Promise的介绍</h2><p><code>Promise</code>在<code>ECMAScript6</code>中被正式写进了语言标准,这是异步编程的一种解决方案,较之传统的函数回调和事件,<code>Promise</code>来得更加强大。<br><code>Promise</code>被理解成一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。<br><code>Promise</code>有三个状态,分别是:</p><ul><li><code>pending</code>:进行中</li><li><code>fulfilled</code>:已成功</li><li><code>rejected</code>:已失败</li></ul><p>这三个状态是根据异步操作的结果来决定的,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。这三个状态一旦确定就不会在改变了。</p><h2 id="Promise的使用"><a href="#Promise的使用" class="headerlink" title="Promise的使用"></a>Promise的使用</h2><h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><p>怎么创建一个Promise实例呢:</p><pre><code>const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});</code></pre><p>其中两个方法,<code>resolve</code>用来处理异步操作成功的结果,将其结果传递出去,在后面的<code>then</code>方法中去接受,<code>reject</code>用来处理操作失败的结果并传递出去,也可用<code>then</code>方法接收。</p><h3 id="then方法的使用"><a href="#then方法的使用" class="headerlink" title="then方法的使用"></a>then方法的使用</h3><p><code>Promise</code>实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数:</p><pre><code>promise.then(function(value) { // success}, function(error) { // failure});</code></pre><p>其中第二个函数是可选的。这两个函数就是用来接收上面传出来的结果的。而且需要注意的是<strong>then方法返回是一个新的Promise,也就是说我们可以进行链式编程。</strong></p><h3 id="Promise执行顺序"><a href="#Promise执行顺序" class="headerlink" title="Promise执行顺序"></a>Promise执行顺序</h3><p><code>Promise</code>在新建之后就会立即执行,举个例子:</p><pre><code>let promise = new Promise(function(resolve, reject) { console.log('a'); resolve();});promise.then(function() { console.log('b');});console.log('c');// a// c// b</code></pre><p>上面的代码先输出a,因为Promise新建之后就立即执行,<code>then</code>方法在当前脚本所有同步任务执行完才执行,最后才会输出b。</p><h2 id="Promise和其他技术"><a href="#Promise和其他技术" class="headerlink" title="Promise和其他技术"></a>Promise和其他技术</h2><p>昨天我看了RxJS,发现这两货好像有点像,一个是把所有数据都变成流进行操作。一个是一直返回<code>Promise</code>,通过<code>then</code>方法一直链式操作,那就比较一下吧。</p><table><thead><tr><th style="text-align:center">操作</th><th>可观察对象</th><th>承诺</th></tr></thead><tbody><tr><td style="text-align:center">创建</td><td>new Observable((observer) => {observer.next(123);});</td><td>new Promise((resolve, reject) => {resolve(123);});</td></tr><tr><td style="text-align:center">转换</td><td>obs.map((value) => value * 2 );</td><td>promise.then((value) => value * 2);</td></tr><tr><td style="text-align:center">订阅</td><td>sub = obs.subscribe((value) => {console.log(value)});</td><td>promise.then((value) => {console.log(value);});</td></tr><tr><td style="text-align:center">取消订阅</td><td>sub.unsubscribe();</td><td>承诺被解析时隐式完成。</td></tr></tbody></table><h2 id="Promise的应用"><a href="#Promise的应用" class="headerlink" title="Promise的应用"></a>Promise的应用</h2><h3 id="加载图片"><a href="#加载图片" class="headerlink" title="加载图片"></a>加载图片</h3><p>我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。</p><pre><code>const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; });};</code></pre>]]></content>
<summary type="html">
<h1 id="Promise对象"><a href="#Promise对象" class="headerlink" title="Promise对象"></a>Promise对象</h1><h2 id="Promise的介绍"><a href="#Promise的介绍" cla
</summary>
<category term="ECMAScript6" scheme="http://tanyibing.com/categories/ECMAScript6/"/>
<category term="JavaScript" scheme="http://tanyibing.com/tags/JavaScript/"/>
<category term="ECMAScript6" scheme="http://tanyibing.com/tags/ECMAScript6/"/>
</entry>
<entry>
<title>初入RxJS</title>
<link href="http://tanyibing.com/2018/09/10/%E5%88%9D%E5%85%A5RxJS/"/>
<id>http://tanyibing.com/2018/09/10/初入RxJS/</id>
<published>2018-09-10T03:21:19.000Z</published>
<updated>2018-10-30T13:53:26.470Z</updated>
<content type="html"><![CDATA[<h1 id="RxJS"><a href="#RxJS" class="headerlink" title="RxJS"></a>RxJS</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>刚开始接触angular的时候就发现,RxJS在angular中是使用的最多的框架。它的全称是:<code>Reactive Extension</code>。这套框架其实是源自于微软,微软在2011年就已经开发出这套框架了,但真正火起来是在NetFlix,支持很多语言的一套响应式编程的框架,感兴趣可以去它的官网看看:<a href="reactivex.io">reactivex.io</a>,支持了这么多语言!!!</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/angular/12.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>它的优势在于:<code>在思考的维度上加上时间考量。</code>,这也是一个问题,导致更难理解!</p><h2 id="Observable"><a href="#Observable" class="headerlink" title="Observable"></a>Observable</h2><h3 id="Observable的性质"><a href="#Observable的性质" class="headerlink" title="Observable的性质"></a>Observable的性质</h3><p>Observable有三种状态,分别是:</p><ul><li><code>next</code>:必要。用来处理每个送达值。在开始执行后可能执行零次或多次。</li><li><code>error</code>:可选。用来处理错误通知。错误会中断这个可观察对象实例的执行过程。</li><li><code>complete</code>:可选。用来处理执行完毕(complete)通知。当执行完毕后,这些值就会继续传给下一个处理器。 </li></ul><p>还有特殊的Observable:<code>永不结束的</code>、<code>Never</code>、<code>Empty(结束但不发射)</code>、<code>Throw</code></p><p>本来是想写介绍点操作符的,但是我发现没啥意思,反正文档都能查到,那就记录点自己对Observable的理解吧,具体操作符到时候查api。<br>其实Observable就是将数据以流的形式来进行处理,可以理解成node中的流,我们可以对流进行一系列的操作,最后我们再订阅这个流,对流里面的数据进行使用。</p><p>举个例子吧:</p><pre><code>const a: Array<number> = [1, 2, 3, 4];const a$ = Rx.Observable.from(a).pipe(map( val => val * val));const observer = { next: (val) => console.log(val); error: (err) => console.log(err); complete: () => console.log(`everything is completed`);}a$.subscribe(observer);</code></pre><p>上面的例子会打印出:</p><blockquote><p>1 4 9 16</p></blockquote><p>因为我们对流中的数据进行了map操作,使它们自己乘以自己。这就是对流的一个简单处理,连<code>pipe</code>方法都和node中的流一样。</p><h3 id="Observable的冷和热"><a href="#Observable的冷和热" class="headerlink" title="Observable的冷和热"></a>Observable的冷和热</h3><p>Observable有两种,一种是冷的,一种是热的。怎么去理解呢?<strong>下面的内容在angular的官网上并没有说明</strong></p><ul><li>冷的就是表示,每次你订阅之后我们都会从头执行一遍,就像看重播一样,点击之后都是重头开始播放;</li><li>热代表每次订阅之后大家之间的进度是一样的,就像看直播一样,打开之后都是最新时刻的通知。</li></ul><p>未完待续。</p>]]></content>
<summary type="html">
<h1 id="RxJS"><a href="#RxJS" class="headerlink" title="RxJS"></a>RxJS</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
</entry>
<entry>
<title>angular动画</title>
<link href="http://tanyibing.com/2018/09/07/angular%E5%8A%A8%E7%94%BB/"/>
<id>http://tanyibing.com/2018/09/07/angular动画/</id>
<published>2018-09-07T06:48:12.000Z</published>
<updated>2018-10-30T13:53:26.283Z</updated>
<content type="html"><![CDATA[<h1 id="Angular动画"><a href="#Angular动画" class="headerlink" title="Angular动画"></a>Angular动画</h1><p>在angular2的时候,angular动画还是个核心的组件库,但是到了angular4的时候,为了减小核心库的体积,所以移除出核心组件。但并不是不重要,它依然是angular中很重要的组成部分,而且也是官方提供的支持。</p><p><em>angular动画架构其实很简单,就是在组件里面定义数个触发器,每个触发器会有一系列的状态和过渡效果,其实这就是动画。</em></p><h2 id="State-amp-Transiton(这两个是核心)"><a href="#State-amp-Transiton(这两个是核心)" class="headerlink" title="State & Transiton(这两个是核心)"></a>State & Transiton(这两个是核心)</h2><ul><li>动画其实就是从一个状态过渡到另一个状态</li><li>状态本身包含形状、颜色、大小等等</li><li>State就是定义状态而Transition是定义如何过渡</li></ul><h2 id="Animate函数"><a href="#Animate函数" class="headerlink" title="Animate函数"></a>Animate函数</h2><p><strong>其实在Transition函数中,还会调用另一个函数,那就是Animate</strong></p><ul><li>Animate规定了具体怎么样过渡,比如时间、过渡的速度等</li><li>Animate有多个重载形式</li></ul><h2 id="使用流程"><a href="#使用流程" class="headerlink" title="使用流程"></a>使用流程</h2><h3 id="引入关键模块并声明"><a href="#引入关键模块并声明" class="headerlink" title="引入关键模块并声明"></a>引入关键模块并声明</h3><pre><code>import { BrowserModule } from '@angular/platform-browser';import { BrowserAnimationsModule } from '@angular/platform-browser/animations';@NgModule({ imports: [ BrowserModule, BrowserAnimationsModule ],})export class AppModule { }</code></pre><p>建议在<code>imports</code>中最后引入<code>BrowserAnimationsModule</code>模块,放在前面可能会踩坑。</p><h3 id="定义各种状态"><a href="#定义各种状态" class="headerlink" title="定义各种状态"></a>定义各种状态</h3><p>拿个官网的例子来看看:</p><pre><code>@Component({ ... //这里是一些正常的定义 animations: [ trigger('heroState', [ state('inactive', style({ backgroundColor: '#eee', transform: 'scale(1)' })), state('active', style({ backgroundColor: '#cfd8dc', transform: 'scale(1.1)' })), transition('inactive => active', animate('100ms ease-in')), transition('active => inactive', animate('100ms ease-out')) ]) ]})</code></pre><p>这里定义了两个状态,一个是<code>active</code>还有个是<code>incative</code>,这两个状态分别有不同的style样式,transition定义了从不同状态过渡到另一个状态的过程。</p><h3 id="附加到模板上"><a href="#附加到模板上" class="headerlink" title="附加到模板上"></a>附加到模板上</h3><pre><code>template: ` <ul> <li *ngFor="let hero of heroes" [@heroState]="hero.state" (click)="hero.toggleState()"> {{hero.name}} </li> </ul> `</code></pre><p>只要使用<code>[@triggerName]</code>语法加到模板上就行了。这就是一个简单的动画效果。</p><p><strong>更多的深入知识,例如缓动函数、关键帧需要的话可以去官网看看,在这贴两个好玩的网址出来:</strong></p><p><a href="https://easings.net/zh-cn" target="_blank" rel="noopener">https://easings.net/zh-cn</a><br><a href="http://cubic-bezier.com/#.17,.67,.83,-0.63" target="_blank" rel="noopener">http://cubic-bezier.com/#.17,.67,.83,-0.63</a></p>]]></content>
<summary type="html">
<h1 id="Angular动画"><a href="#Angular动画" class="headerlink" title="Angular动画"></a>Angular动画</h1><p>在angular2的时候,angular动画还是个核心的组件库,但是到了angula
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
</entry>
<entry>
<title>angular实战中踩过的坑(不定时更新)</title>
<link href="http://tanyibing.com/2018/09/07/angular%E5%AE%9E%E6%88%98%E4%B8%AD%E8%B8%A9%E8%BF%87%E7%9A%84%E5%9D%91%EF%BC%88%E4%B8%8D%E5%AE%9A%E6%97%B6%E6%9B%B4%E6%96%B0%EF%BC%89/"/>
<id>http://tanyibing.com/2018/09/07/angular实战中踩过的坑(不定时更新)/</id>
<published>2018-09-07T04:08:48.000Z</published>
<updated>2018-10-30T13:53:26.284Z</updated>
<content type="html"><![CDATA[<h2 id="动态表单中踩过的坑"><a href="#动态表单中踩过的坑" class="headerlink" title="动态表单中踩过的坑"></a>动态表单中踩过的坑</h2><h3 id="2018-9-7"><a href="#2018-9-7" class="headerlink" title="2018-9-7"></a>2018-9-7</h3><p><strong>问题:</strong><br>今天在写动态表单时遇到个问题,我在控制器中生成的<code>formControl</code>死活就是拿不到我输入的值。</p><p><strong>分析:</strong><br>生成<code>formControl</code>的代码肯定没有问题:</p><pre><code>let fb = new FormBuilder();this.formModel = fb.group({ title: ['', Validators.minLength(3)], price: [null, this.positiveNumberValidator], category: ['-1']});</code></pre><p>对吧,是没有问题,那肯定就是出在绑定模板的时候,于是我们认真看看吧。</p><p><strong>问题根源:</strong></p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/angular/9.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>发现了吧,这个<code>Name</code>由于打快了,打成了小写!低级错误!以后拼写细心点!</p><hr><h2 id="版本更新问题"><a href="#版本更新问题" class="headerlink" title="版本更新问题"></a>版本更新问题</h2><p><strong>问题:</strong><br>我想要做个商品筛选的功能,其中我需要使用<code>debounceTime</code>方法来对我的输入行为产生一个延时的处理,我的调用方法如下:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/angular/10.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote><p>引入的是:</p><blockquote><p>import ‘rxjs/RW’;</p></blockquote><p>结果引入报错。</p><p><strong>分析:</strong><br>既然引入报错肯定是更新过了,目录结构发生变化了,甚至连方法的使用也有可能改变了,还是去官方找找吧。</p><p><strong>问题根源:</strong><br>一查果然是更新的问题,而且不出意料,方法也需要修改下,经过修改后引入的方式如下:</p><blockquote><p>import { debounceTime } from ‘rxjs/operators’;</p></blockquote><p>方法也需要改进成下面这种:</p><blockquote><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="/img/angular/11.png" alt="" title=""> </div> <div class="image-caption"></div> </figure></blockquote>]]></content>
<summary type="html">
<h2 id="动态表单中踩过的坑"><a href="#动态表单中踩过的坑" class="headerlink" title="动态表单中踩过的坑"></a>动态表单中踩过的坑</h2><h3 id="2018-9-7"><a href="#2018-9-7" class="
</summary>
<category term="Angular" scheme="http://tanyibing.com/categories/Angular/"/>
<category term="Angular" scheme="http://tanyibing.com/tags/Angular/"/>
<category term="Typescript" scheme="http://tanyibing.com/tags/Typescript/"/>
</entry>
<entry>
<title>Koa中使用Cookie & Session</title>
<link href="http://tanyibing.com/2018/09/06/Koa%E4%B8%AD%E4%BD%BF%E7%94%A8Cookie-Session/"/>
<id>http://tanyibing.com/2018/09/06/Koa中使用Cookie-Session/</id>
<published>2018-09-06T02:43:21.000Z</published>
<updated>2018-10-30T13:53:26.263Z</updated>
<content type="html"><![CDATA[<h1 id="koa中cookie和session使用"><a href="#koa中cookie和session使用" class="headerlink" title="koa中cookie和session使用"></a>koa中cookie和session使用</h1><hr><h3 id="cookie介绍"><a href="#cookie介绍" class="headerlink" title="cookie介绍"></a>cookie介绍</h3><p>cookie 是存储于访问者的计算机中的变量(客户端)。可以让我们用同一个浏览器访问同一个域名的时候共享数据。那为什么不使用http呢?很简单,因为HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何关系的。</p><h3 id="cookie用处"><a href="#cookie用处" class="headerlink" title="cookie用处"></a>cookie用处</h3><p>随便举几个例子:</p><ol><li>存储用户信息,例如登陆信息。</li><li>浏览历史记录。</li><li>猜你喜欢的功能。</li><li>10天免登陆。</li><li>多个页面之间的数据传递。</li><li>实现购物车功能。</li></ol><h3 id="Koa-Cookie的使用"><a href="#Koa-Cookie的使用" class="headerlink" title="Koa Cookie的使用"></a>Koa Cookie的使用</h3><h6 id="Koa中设置Cookie的值"><a href="#Koa中设置Cookie的值" class="headerlink" title="Koa中设置Cookie的值"></a>Koa中设置Cookie的值</h6><pre><code>ctx.cookies.set(name, value, [options])</code></pre><p>通过options设置cookie name的value:</p><table><thead><tr><th style="text-align:center">options名称</th><th>options值</th></tr></thead><tbody><tr><td style="text-align:center">maxAge</td><td>一个数字表示从 Date.now() 得到的毫秒数。</td></tr><tr><td style="text-align:center">expires</td><td>cookie 过期的 Date</td></tr><tr><td style="text-align:center">path</td><td>cookie 路径, 默认是’/‘。</td></tr><tr><td style="text-align:center">secure</td><td>安全 cookie 默认 false,设置成 true 表示只有 https 可以访问。</td></tr><tr><td style="text-align:center">httpOnly</td><td>是否只是服务器可访问 cookie, 默认是 true</td></tr><tr><td style="text-align:center">overwrite</td><td>一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(不管路径或域)是否在设置此 Cookie 时从Set-Cookie 标头中过滤掉。</td></tr></tbody></table><h6 id="Koa中设置中文Cookie"><a href="#Koa中设置中文Cookie" class="headerlink" title="Koa中设置中文Cookie"></a>Koa中设置中文Cookie</h6><pre><code>console.log(new Buffer('hello, world!').toString('base64'));// 转换成 base64 字符串:aGVsbG8sIHdvcmxkIQ==console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString());// 还原 base64 字符串:hello, world!</code></pre><hr><h3 id="Session介绍"><a href="#Session介绍" class="headerlink" title="Session介绍"></a>Session介绍</h3><p>session 是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。</p><h3 id="Session-的工作流程"><a href="#Session-的工作流程" class="headerlink" title="Session 的工作流程"></a>Session 的工作流程</h3><p>当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session 对象,生成一个类似于 key,value的键值对,然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie),找到对应的Session(value)。 客户的信息都保存在Session中。</p><h3 id="koa-session-的使用"><a href="#koa-session-的使用" class="headerlink" title="koa-session 的使用"></a>koa-session 的使用</h3><pre><code>npm install koa-session --save</code></pre><h6 id="引入-express-session"><a href="#引入-express-session" class="headerlink" title="引入 express-session"></a>引入 express-session</h6><pre><code>const session = require('koa-session');</code></pre><h6 id="设置官方文档提供的中间件"><a href="#设置官方文档提供的中间件" class="headerlink" title="设置官方文档提供的中间件"></a>设置官方文档提供的中间件</h6><pre><code>app.keys = ['some secret hurr'];const CONFIG = { key: 'koa:sess', //cookie key (default is koa:sess) maxAge: 86400000, // cookie 的过期时间 maxAge in ms (default is 1 days) overwrite: true, //是否可以 overwrite (默认 default true) httpOnly: true, //cookie 是否只有服务器端可以访问 httpOnly or not (default true) signed: true, //签名默认 true rolling: false, //在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false) renew: false, //(boolean) renew session when session is nearly expired,};app.use(session(CONFIG, app));</code></pre><h6 id="Cookie-和-Session-区别"><a href="#Cookie-和-Session-区别" class="headerlink" title="Cookie 和 Session 区别"></a>Cookie 和 Session 区别</h6><ol><li>cookie 数据存放在客户的浏览器上,session 数据放在服务器上。</li><li>cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使用 session。</li><li>session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用 COOKIE。</li><li>单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。</li></ol>]]></content>
<summary type="html">
<h1 id="koa中cookie和session使用"><a href="#koa中cookie和session使用" class="headerlink" title="koa中cookie和session使用"></a>koa中cookie和session使用</h1><
</summary>