-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
2917 lines (2677 loc) · 486 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[C++对象内存布局初探(1)]]></title>
<url>http://nightn.com/2018/08/05/cpp-object-memory-1/</url>
<content type="html"><![CDATA[<p>C++ 对象内存布局是一个深入而有趣的话题。<code>sizeof</code> 可以返回对象占用的内存大小,那具体存每个字节的是什么呢?对象的哪些成员会直接放到对象中,哪些不会?每个成员的排布顺序是怎么样的?当发生继承或复合时,子类对象的内存布局又是怎样的?虚函数的实现机制是什么?动态绑定在内存中是怎么体现?如何避免重复继承?「C++对象内存布局初探」系列便尝试着从内存布局的角度去回答这些问题,这是本系列的第一篇。</p>
<a id="more"></a>
<p>在本篇文章中,我将对 5 类不同情况下的对象布局进行较为详细的介绍,通过 <strong>代码 + UML 图 + 实际内存布局 + 结论分析</strong> 的流程对每一种情况进行讨论。这 5 种情况包括:</p>
<ul>
<li>没有继承的情况</li>
<li>单一继承(不包含 <code>virtual function</code>)</li>
<li>单一继承(包含 <code>virtual function</code>)</li>
<li>多重继承</li>
<li>虚继承</li>
</ul>
<p>示例代码的运行环境:</p>
<ul>
<li>操作系统: win10 专业版</li>
<li>编译器:GCC4.9</li>
</ul>
<h2 id="一、-最简单情况:没有继承"><a href="#一、-最简单情况:没有继承" class="headerlink" title="一、 最简单情况:没有继承"></a>一、 最简单情况:没有继承</h2><h3 id="1-代码及-UML-图"><a href="#1-代码及-UML-图" class="headerlink" title="1. 代码及 UML 图"></a>1. 代码及 UML 图</h3><pre class=" language-c++"><code class="language-c++">class Point2d {
public:
// constructor(s)
// operators
// access functions
private:
long long x = 0x11;
long long y = 0x22;
};
class Point3d {
public:
// constructor(s)
// operators
// access functions
private:
long long x = 0x1111;
long long y = 0x2222;
long long z = 0x3333;
};
</code></pre>
<p><img src="/img/uml-no-inheritance.png" alt="1533467905287"></p>
<h3 id="2-内存布局"><a href="#2-内存布局" class="headerlink" title="2. 内存布局"></a>2. 内存布局</h3><p><img src="/img/model-no-inheritance.png" alt="1533467946831"></p>
<h3 id="3-结论"><a href="#3-结论" class="headerlink" title="3. 结论"></a>3. 结论</h3><p><code>Point2d</code> 和 <code>Point3d</code> 是两个独立的类,它们实例化出来的对象仅包含 <code>non-static data member</code> 。对于各个数据成员在内存中的排列顺序,C++标准并没有强制规定。但大部分编译器实现中,都<strong>按照数据成员声明顺序来进行对象的内存布局</strong> (如上图所示)。</p>
<h2 id="二、单一继承不含-virtual-function"><a href="#二、单一继承不含-virtual-function" class="headerlink" title="二、单一继承不含 virtual function"></a>二、单一继承不含 virtual function</h2><p><code>Point3d</code> 公开继承 <code>Point2d</code> ,并且两个类中都没有 <code>virtual function</code> 。</p>
<h3 id="1-代码及-UML-图-1"><a href="#1-代码及-UML-图-1" class="headerlink" title="1. 代码及 UML 图"></a>1. 代码及 UML 图</h3><pre class=" language-c++"><code class="language-c++">class Point2d {
public:
long long get_x() const { return x; }
long long get_y() const { return y; }
private:
long long x = 0x11;
long long y = 0x22;
};
class Point3d: public Point2d {
public:
long long get_z() const { return z; }
private:
long long z = 0x3333;
};
</code></pre>
<p><img src="/img/uml-single-inheri-no-virtual.png" alt="1533468789392"></p>
<h3 id="2-内存布局-1"><a href="#2-内存布局-1" class="headerlink" title="2. 内存布局"></a>2. 内存布局</h3><p><img src="/img/model-single-inheri-no-virtual.png" alt="1533468881218"></p>
<h3 id="3-结论-1"><a href="#3-结论-1" class="headerlink" title="3. 结论"></a>3. 结论</h3><p>在继承体系中,子类对象包含了父类实体部分(subobject),而且 subobject 位于前面,而后是子类中的成员。</p>
<h2 id="三、单一继承并含-virtual-function"><a href="#三、单一继承并含-virtual-function" class="headerlink" title="三、单一继承并含 virtual function"></a>三、单一继承并含 virtual function</h2><p>假如我们现在要对点进行绘制,显然无论是 <code>Point2d</code> 还是 <code>Point3d</code> 都需要可以绘制,但它们绘制的实现不同。因此,我们在 <code>Point2d</code> 中定义名为 <code>draw</code> 的 <code>virtual function</code> 。另外,我们单独在 <code>Point3d</code> 单独定义一个名为 <code>rotate3d</code> 的 <code>virtual function</code> 。</p>
<h3 id="1-代码及-UML-图-2"><a href="#1-代码及-UML-图-2" class="headerlink" title="1. 代码及 UML 图"></a>1. 代码及 UML 图</h3><pre class=" language-c++"><code class="language-c++">class Point2d {
public:
long long get_x() const { return x; }
long long get_y() const { return y; }
virtual void draw() { cout << "Point2d::draw()" << endl; }
private:
long long x = 0x11;
long long y = 0x22;
};
class Point3d: public Point2d {
public:
long long get_z() const { return z; }
void draw() override { cout << "Point3d::draw()" << endl; }
virtual void rotate3d() { cout << "Point3d::draw()" << endl; }
private:
long long z = 0x3333;
};
</code></pre>
<p><img src="/img/uml-single-inheri-virtual.png" alt="1533471900774"></p>
<h3 id="2-内存布局-2"><a href="#2-内存布局-2" class="headerlink" title="2. 内存布局"></a>2. 内存布局</h3><p><img src="/img/model-single-inheri-virtual.png" alt=""></p>
<h3 id="3-结论-2"><a href="#3-结论-2" class="headerlink" title="3. 结论"></a>3. 结论</h3><p>上面的内存布局图咋看之下有点复杂,不用怕,容我慢慢道来。上图分为上下两部分,上半部分是与 <code>p2</code> 对象相关的内存布局,下半部分是与 <code>p3</code> 对象相关的内存布局。每个部分又包括了对象本身和 <code>vtbl</code> 。什么是 <code>vtbl</code> ,什么又是 <code>vptr</code> ,为什么多了虚函数,对象的内存布局就变得这么复杂了呢。我先简单的解释一下。</p>
<p>考虑一个简单的继承体系:<code>Base</code> 为父类,<code>Derived1</code> 和 <code>Derived2</code> 都公开继承 <code>Base</code> 。且 <code>Base</code> 类中定义了虚函数,子类们可以重写(override)父类定义的虚函数。虚函数是 C++ 用来支持多态的一种函数,所谓多态,是通过动态绑定实现的。当我们通过一个父类指针(如 <code>Base* pb</code>)或引用去调用虚函数时,便会发生动态绑定,即静态类型为 <code>Base</code> ,动态类型由运行时 <code>pb</code> 所绑定的对象决定,绑定不同类型的对象,所调用的虚函数版本也不同,看起来父类指针具有多种状态,具体是什么状态需要等到运行时进行动态绑定,这就是多态。多态就是通过虚函数表 <code>vtbl</code> 实现的。</p>
<p>每一个带有虚函数的类,它所实例出来的对象,除了包含自身的 <code>data member</code> 外,还有一个指针 <code>vptr</code> ,全称是 <code>virtual function table pointer</code> ,即虚函数表指针,顾名思义,它里面存放了这个类的虚函数表的地址。如上图所示,<code>p2</code> 对象的内存布局中,除了包含 <code>p2.x</code> 和 <code>p2.y</code> ,还包含了一个 <code>vptr</code> ,其值为 <code>00 00 00 00 00 49 15 b0</code> ,它是 <code>Point2d</code> 虚函数表的地址,我们顺着这个地址找到了对应的虚函数表,即图中的 <code>vptr of Point2d</code> ,这个虚表存放的便是 <code>Point2d</code> 所有的虚函数。同理,<code>p3</code> 对象的内存布局也是类似的,不同的是,<code>Point3d</code> 的虚函数表包含了两个虚函数:<code>draw()</code> 和 <code>rotate3d()</code> 。非常重要的一点,<code>Point3d</code> 虚函数表中的第一个虚函数是 <code>Point3d::draw()</code> ,而不是父类版本的 <code>draw()</code> ,这是由于子类重写了父类的虚函数。关于虚函数表,还有几个细节值得注意:</p>
<ul>
<li><strong>虚函数表的结尾标志</strong>。从上图可以看到,虚函数表的结尾标志是一个值为 0,大小为 8 bytes 的一个块。GCC 是这样的,但是,并不是每一个编译器都是如此。</li>
<li><strong>虚表的 -1 位置</strong>。细心的读者可能会发现,虚表第一个虚函数的上面还有一个地址,它其实指向的是这个类的 <code>type_info</code> 对象,<code>type_info</code> 对象描述了类的简要信息(如 name, hash_code 等),用以支持 <code>RTTI(runtime type identification)</code> 。我们使用的 <code>typeid</code> 运算符,应该就是通过访问 <code>type_info</code> 对象实现的。</li>
<li><strong>虚表是类层面上的,而不是对象层面上的</strong>。即同一个类的不同对象,共享同一个虚表。</li>
</ul>
<p>以下通过一个简单的例子,结合上面的内存布局图,解释对象是如何与虚表进行交互,实现多态的。</p>
<pre class=" language-c++"><code class="language-c++">Point2d p2;
Point3d p3;
Point2d* ptr = &p2;
ptr->draw(); // 调用 Point2d::draw()
ptr = &p3;
ptr->draw(); // 调用 Point3d::draw()
</code></pre>
<p>同样是 <code>ptr->draw()</code> ,为什么每次调用的版本不一样呢?首先,定义了 <code>p2</code> 和 <code>p3</code> 对象,如上图所示,这两个对象的地址分别为 <code>0x6ffe30</code> 和 <code>0x6ffe10</code> 。<code>ptr</code> 是一个指向父类 <code>Point2d</code> 的指针。一开始,将 <code>p2</code> 的地址初始化 <code>ptr</code> ,即当前 <code>ptr</code> 指向 <code>p2</code> 对象,当执行到 <code>ptr->draw()</code> 时,由于 <code>draw()</code> 是虚函数,因此会去 <code>p2</code> 对象中的 <code>vptr</code> 所指的虚表中查找,虚表地址为 <code>0x4915b0</code> ,所以调用的是父类的虚函数。然后,将 <code>ptr</code> 重新赋值为 <code>p3</code> 的地址,此时再执行 <code>ptr->draw()</code> 时,会去 <code>p3</code> 对象中的 <code>vptr</code> 所指虚表中查找,虚表地址为 <code>0x4915d0</code>,所以调用的是子类的虚函数。</p>
<p>虚函数表的引入实现了高弹性的多态,但也引入了额外的负担。</p>
<ul>
<li>对象要增加一个 slot 作为 <code>vtpr</code> ,这样才能确保对象能够找到 <code>vtbl</code> 。</li>
<li>带有虚函数的类都需要有一个关联的 <code>vtbl</code> ,存放了所有虚函数的指针,以及再提供必要的 slot 作为分隔符,以及支持 <code>RTTI</code> 。</li>
<li>构造函数需要扩展。对象新增了一个 <code>vptr</code> 隐式成员,显然,它也需要初始化,编译器在合成默认构造函数或扩展现有构造函数的时候,必定会添加初始化 <code>vptr</code> 的代码。</li>
<li>析构函数需要扩展。也是为了处理额外的 <code>vptr</code> 隐式成员。</li>
</ul>
<h2 id="四、多重继承"><a href="#四、多重继承" class="headerlink" title="四、多重继承"></a>四、多重继承</h2><h3 id="1-代码及-UML-图-3"><a href="#1-代码及-UML-图-3" class="headerlink" title="1. 代码及 UML 图"></a>1. 代码及 UML 图</h3><pre class=" language-c++"><code class="language-c++">class Base1 {
public:
long long x = 0x1111;
virtual void f1() { cout << "Base1::f1()" << endl; }
virtual void g1() { cout << "Base1::g1()" << endl; }
};
class Base2 {
long long x = 0x2222;
void f2() { cout << "Base2::f2()" << endl; }
};
class Base3 {
long long x = 0x3333;
virtual void f3() { cout << "Base3::f3()" << endl; }
};
class Derived: public Base1, public Base2, public Base3 {
long long x = 0x8888;
void f1() override { cout << "Derived::f1()" << endl; }
virtual hello() { cout << "Derived::hello()" << endl; }
virtual bye() { cout << "Derived::hello()" << endl; }
};
</code></pre>
<p><img src="/img/uml-multi-inheritance.png" alt="1533477298831"></p>
<h3 id="2-内存布局-3"><a href="#2-内存布局-3" class="headerlink" title="2. 内存布局"></a>2. 内存布局</h3><p><img src="/img/model-multi-inheritance.png" alt=""></p>
<h3 id="3-结论-3"><a href="#3-结论-3" class="headerlink" title="3. 结论"></a>3. 结论</h3><p>由内存布局图,可以得出以下结论:</p>
<ul>
<li>多重继承体系中的子类,每多继承一个带有虚函数的父类,就会在子类对象中多一个 <code>vptr</code> 。上图中,<code>Base1</code> 和 <code>Base3</code> 都带有虚函数,因此在子类的 <code>Base1 subobject</code> 和 <code>Base3 subobject</code> 各有一个 <code>vptr</code> ,为了后面叙述方便,我将它们分别记作 <code>vptr1</code> 和 <code>vptr2</code> ,它们所指向的虚函数表分别记作 <code>vtbl1</code> 和 <code>vtbl2</code> 。由于 <code>Base2</code> 不带有虚函数,所以 <code>Base2 subobject</code> 仅包含数据成员 <code>Base2::x</code> 。 </li>
<li>多重继承体系中的子类,其对象中各个 subobject 中的顺序与继承声明列表的顺序一致。上图中子类对象的内存布局从上到下分别为:<code>Base1 subobject</code>, <code>Base2 subobject</code>, <code>Base3 subobject</code> 以及 <code>Derived self part</code>。</li>
<li>如果子类继承了多个带有虚函数的父类,且子类中也有专属于自己的虚函数(如 <code>Derived::hello()</code> 和 <code>Derived::bye()</code>),那么,专属子类的虚函数会附加在继承列表中第一个带有虚函数父类的虚函数表中。如图中所示,<code>Derived::hello()</code> 和 <code>Derived::bye()</code> 都位于 <code>vtbl1</code> 中。</li>
<li>如果子类重写了父类的虚函数,则子类版本的虚函数会将虚表中父类版本的虚函数替换掉。如图所示,<code>vtbl1</code> 中的虚函数顺序,一开始我们可以想象成这样:<code>Base1::f1()</code>, <code>Base1::g1()</code>, <code>Derived::hello()</code>, <code>Derived::bye()</code> 。子类定义了重写了 <code>f1()</code> ,所以 <code>vtbl1</code> 的顺序变成了现在这样:<code>Derived::f1()</code>, <code>Base1::g1()</code>, <code>Derived::hello()</code>, <code>Derived::bye()</code> 。这便是子类重写父类虚函数的内在原理。</li>
<li>每一个虚表的 -1 位置都带有 <code>type_info</code> 对象的指针,GCC 中,多个父类的虚表是连续存储的,最后一个虚表末尾是 <code>0</code> ,倒数第二个虚表末尾是 <code>-8</code> ,然后是 <code>-16</code> , <code>-24</code> … 以此类推。</li>
</ul>
<blockquote>
<p>TODO: 虚继承</p>
</blockquote>
]]></content>
<categories>
<category> C++ </category>
</categories>
<tags>
<tag> C++ </tag>
<tag> 对象模型 </tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gulp for beginners(译)]]></title>
<url>http://nightn.com/2018/01/18/gulp-for-beginners/</url>
<content type="html"><![CDATA[<p>Gulp 是优化项目、提高开发效率的一个非常有用的工具,尤其适用于前端领域。它有非常多的插件,可以实现很多自动化的功能,如创建本地 web 服务器、开发过程中文件修改实时刷新浏览器、对 Sass 或 LESS 进行实时编译、优化资源文件(如拼接压缩 CSS, JS 文件,压缩图片等)。本文翻译自 Zell Liew 的 <a href="https://css-tricks.com/gulp-for-beginners/" target="_blank" rel="external">Gulp for Beginners</a> 一文,原文为英文,采用意译,部分有改动。文章略长,但对快速了解如何使用 Gulp 非常有帮助。</p>
<a id="more"></a>
<p>Gulp 常用于管理 web 开发过程中的多个任务,经常用在以下这些前端任务:</p>
<ul>
<li>创建 web 服务器</li>
<li>当源码文件保存时,自动刷新浏览器</li>
<li>使用诸如 Sass 或 LESS 的预编译器</li>
<li>优化资源文件,如 CSS, JavaScript 和图片</li>
</ul>
<p>以上只是 Gulp 的一部分。如果你深入了解,甚至可以用 Gulp 构建一个静态网站生成器。总之,Gulp 非常强大,如果你想创建一套自定义的构建流程,那么你必须学会如何使用 Gulp。</p>
<p>这篇文件介绍了 Gulp 的基础知识,看完之后你就可以更深入的探索 Gulp。</p>
<p>在我们详细介绍如何使用 Gulp 之前,我们先聊一聊相比于其他类似的工具,为什么我们会选择 Gulp。</p>
<h3 id="为什么使用-Gulp"><a href="#为什么使用-Gulp" class="headerlink" title="为什么使用 Gulp"></a>为什么使用 Gulp</h3><p>诸如 Gulp 的工具都被称为「构建工具」,因为它们可以运行多个任务来构建一个网站。目前最流行的构建工具就是 Gulp 和 Grunt。当然,除此之外还有其他的构建工具,如 Broccoli 致力于资源文件的编译。</p>
<p>已经有非常多的文件阐述了 Grunt 和 Gulp 的区别,以及解释为什么用这个而不是那个。最大的不同是你如何用它们来配置你的工作流。比起 Grunt,Gulp 的配置更加短小和简单,并且 Gulp 运行得更快。</p>
<p>现在让我们开始用 Gulp 来构建工作流吧。</p>
<h3 id="总体概述"><a href="#总体概述" class="headerlink" title="总体概述"></a>总体概述</h3><p>通过跟着这篇文章进行操作,最后你将自己完成一个工作流的构建,主要包括以下任务:</p>
<ul>
<li>创建 web 服务器</li>
<li>将 Sass 编译为 CSS</li>
<li>保存源码文件的同时刷新浏览器</li>
<li>优化生产环境下的资源文件,如 CSS, JS, fonts, images</li>
</ul>
<p>你也会学到如何将多个不同的任务链接成一个容易理解和执行的简短命令。</p>
<p>首先在你的电脑上安装 Gulp</p>
<h3 id="安装-Gulp"><a href="#安装-Gulp" class="headerlink" title="安装 Gulp"></a>安装 Gulp</h3><p>安装 Gulp 之前,确保你的电脑已经安装好了 Node.js。如果还没有安装 Node.js,到 <a href="https://nodejs.org/" target="_blank" rel="external">Node 官网</a> 上下载并安装。</p>
<p>装好 Node.js 后,就可以在命令行中通过以下命令安装 Gulp。</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp -g
</code></pre>
<h3 id="创建-Gulp-项目"><a href="#创建-Gulp-项目" class="headerlink" title="创建 Gulp 项目"></a>创建 Gulp 项目</h3><p>首先创建一个名为 <code>project</code> 的文件夹作为我们项目的根目录,并在命令行中跳转到该目录下,执行 <code>npm init</code> 命令来初始化项目。</p>
<pre class=" language-shell"><code class="language-shell">npm init
</code></pre>
<p>运行以上命令后,要求输入项目的基本参数,如项目名称、版本、描述、主入口、测试脚本、作者、许可等,我们可以暂时默认,因为这些参数最终可以在生成的 <code>package.json</code> 中进行配置,执行结果如下图:</p>
<p><img src="./img/npm-init.png" alt=""></p>
<blockquote>
<p><code>package.json</code> 是所有 npm 项目的配置文件,它描述了项目的基本信息,更重要的是,它还记录了该项目依赖的第三方模块,我们在 github 上下载的其他人的项目,我们必须要执行 <code>npm install</code> 命令后才能正常运行项目,其实这个命令就是根据当前项目的 <code>package.js</code> ,下载所有的依赖模块,放到 <code>/node_modules</code> 中,有了这些依赖模块,项目才能正常运行。</p>
</blockquote>
<p>自动创建好 <code>package.json</code> 后,就可以安装项目的依赖包了,既然这是一个 Gulp 项目,那首先要安装 Gulp 到项目(之前安装的是全局环境)。执行以下命令:</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp --save-dev
</code></pre>
<p>安装完后,你会发现在项目根目录中会出现一个 <code>node_modules</code> 文件夹,这个文件夹就存放着我们项目依赖的所有第三方模块(包括刚刚装好的 gulp)。另外你还会发现 <code>package.jsaon</code> 也发生了变化,在 <code>devDependencies</code> 属性中,出现了 <code>gulp</code> 及其版本。如下图所示:</p>
<p><img src="./img/install-gulp.png" alt=""></p>
<blockquote>
<p>有人可能会奇怪,为什么我只是安装了 <code>gulp</code> ,而 node_modules 文件夹中会出现那么多第三方模块。其实这并不奇怪,<code>gulp</code> 也是一个 npm 项目,它也有它的 <code>package.json</code> 文件,里面记录着 <code>gulp</code> 所依赖的第三方模块,为了使用 <code>gulp</code> 当然也需要这些模块了。那 <code>gulp</code> 所依赖的第三方包又依赖其他的包呢?答案是 npm 会递归地下载所有的依赖模块。这就是 node_modules 文件夹中有这么多依赖包的原因。</p>
</blockquote>
<p>以上就准备好 Gulp 项目的基本内容,下面让我们定义文件的目录结构。</p>
<h3 id="定义文件目录结构"><a href="#定义文件目录结构" class="headerlink" title="定义文件目录结构"></a>定义文件目录结构</h3><p>Gulp 非常的灵活,因此可以用在任何目录结构。本文使用的目录结构如下图所示:</p>
<p><img src="./img/project-structure.png" alt=""></p>
<p><code>app</code> 目录用于存放开发环境下的文件,我们开发时所有的代码文件和资源文件都放在 <code>app</code> 目录,<code>dist</code> 目录用于存放将在生成环境下使用的优化过的文件。<code>gulpfile.js</code> 就是 Gulp 的配置文件,接下来我们将在这个配置文件中创建 Gulp 任务。</p>
<h3 id="创建第一个-Gulp-任务"><a href="#创建第一个-Gulp-任务" class="headerlink" title="创建第一个 Gulp 任务"></a>创建第一个 Gulp 任务</h3><p>首先使用 <code>require</code> 引入 Gulp 依赖,以便在之后的代码中使用。</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> gulp <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'gulp'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<blockquote>
<p><code>require</code> 语句告诉 Node,去 <code>node_modules</code> 目录中搜索名为 <code>gulp</code> 的包。一旦找到了,我们将包导出的内容赋值给变量 <code>gulp</code> 。</p>
</blockquote>
<p>接下来用变量 <code>gulp</code> 编写一个 Gulp 任务,结构如下:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'task-name'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// Stuff here </span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p><code>task-name</code> 指定了任务的名称,有了这个名称之后,就可以在命令行中运行该任务,运行的命令为 <code>gulp task-name</code>。</p>
<p>为了测试,我们创建一个 <code>hello</code> 任务吧,这个任务只是简单的打印 <code>Hello Gulp</code>。</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hello Gulp'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>我们在命令行执行 <code>gulp hello</code> 来测试一下</p>
<pre class=" language-shell"><code class="language-shell">gulp hello
</code></pre>
<p>运行结果如下,命令行中打印了 <code>Hello Gulp</code> 。</p>
<p><img src="./img/gulp-hello.png" alt=""></p>
<p>以上只是一个简单的测试,真正的 Gulp 任务比这复杂得多,它常常包含两个额外的 Gulp 方法,以及很多 Gulp 插件。一个真正的 Gulp 任务看起来可能是这样的:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'task-name'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'source-files'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 利用 gulp.src 获取源文件</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">aGulpPlugin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 将它发送给一个 gulp 插件</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'destination'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 将处理后的文件输出到目标文件夹</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>如你所见,一个真正的 gulp 任务包含两个额外的方法:<code>gulp.src</code> 和 <code>gulp.dest</code> 。<code>gulp.src</code> 告诉 Gulp ,任务所需要的文件从哪里来;而 <code>gulp.dest</code> 告诉 Gulp ,任务完成之后输出文件放到哪去。</p>
<p>下面让我构建一个将 Sass 文件编译为 CSS 文件的任务吧。</p>
<h3 id="利用-Gulp-来预编译"><a href="#利用-Gulp-来预编译" class="headerlink" title="利用 Gulp 来预编译"></a>利用 Gulp 来预编译</h3><p>为了将 Sass 编译为 CSS,我们需要借助名为 <code>gulp-sass</code> 的插件,通过以下命令将 <code>gulp-sass</code> 插件安装到项目中。</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp-sass --save-dev
</code></pre>
<p>安装好 <code>gulp-sass</code> 之后,我们修改 <code>gulpfile.js</code> ,首先引入 <code>gulp-sass</code> :</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> sass <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'gulp-sass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>然后我们创建一个名为 <code>sass</code> 的 Gulp 任务:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/scss/styles.scss'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">sass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 利用 gulp-sass 将 Sass 转为 CSS</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'app/css'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>styles.scss 是我们在 app/scss 新建的测试文件,下面我们在 styles.scss 添加一些 Sass 代码,看看我们这个 Gulp 任务能不能成功转换。在 styles.scss 添加如下代码:</p>
<pre class=" language-scss"><code class="language-scss"><span class="token comment" spellcheck="true">// styles.scss</span>
<span class="token selector">.testing </span><span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">percentage</span><span class="token punctuation">(</span><span class="token number">5</span> <span class="token operator">/</span> <span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>在命令行中运行 <code>gulp sass</code> 。任务执行完后,你将会发现在 app/css 目录下多了一个 <code>styles.css</code> 文件,其内容如下:</p>
<pre class=" language-css"><code class="language-css"><span class="token selector"><span class="token class">.testing</span> </span><span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">71.42857%</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
</code></pre>
<p>可见我们的 <code>sass</code> 任务成功完成了使命。</p>
<blockquote>
<p><code>gulp-sass</code> 使用了 LibSass 将 Sass 转换为 CSS,它比基于 Ruby 的方法更快。不过,如果你仍然想用基础 Ruby 的方法,你可以试试 <code>gulp-ruby-sass</code> 或 <code>gulp-compass</code> 插件。</p>
</blockquote>
<p>有时候我们需要同时将多个 <code>.scss</code> 文件编译成 CSS 文件,这个时候我们可以借助 Node globs 来实现。</p>
<h3 id="Globbing-in-Node"><a href="#Globbing-in-Node" class="headerlink" title="Globbing in Node"></a>Globbing in Node</h3><p>Globs 是一个匹配模型,它允许你将多个文件添加进 <code>gulp.src</code> 。就像正则表达式,但 glob 只能用于文件路径。</p>
<p>常用的 4 中 globbing 模式有:</p>
<ul>
<li><code>*.scss</code> : <code>*</code> 是一个通配符,表示匹配根目录下所有后缀名为 <code>.scss</code> 的文件。</li>
<li><code>**/*.scss</code> : 匹配根目录以其子目录中所有后缀名为 <code>.scss</code> 的文件。</li>
<li><code>!not-me.scss</code> :排除指定文件。</li>
<li><code>*.+(scss|sass)</code> : 允许多个匹配模式。本例中,匹配根目录下所有后缀名为 <code>.scss</code> 和 <code>.sass</code> 的文件。</li>
</ul>
<p>下面我们修改 <code>gulpfile.js</code> 中的 <code>sass</code> 任务:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">sass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 利用 gulp-sass 将 Sass 转为 CSS</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'app/css'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>现在,任何位于 app/scss 及其子目录的后缀名为 <code>.scss</code> 的文件,都会自动被添加进 <code>gulp.src</code> 。如我们在 app/scss 目录中额外添加了一个 <code>print.scss</code> 文件。然后执行 <code>gulp sass</code> ,会发现 app/css 目录下多了一个 <code>print.css</code> ,说明该任务同时对多个文件进行了转换。</p>
<p><img src="./img/gulp-sass.png" alt=""></p>
<p>现在,我们可以通过运行 <code>gulp sass</code> 同时实现对多个 scss 文件的转换。问题是,每次修改了 scss 文件,都得运行一次 <code>gulp sass</code> 命令来将其转换为 CSS 文件,这个过程非常繁琐的。</p>
<p>幸运的是,我们可以通过 <code>watch</code> 方法来自动运行 <code>sass</code> 任务。</p>
<h3 id="观察(Watching)-sass-文件的变化"><a href="#观察(Watching)-sass-文件的变化" class="headerlink" title="观察(Watching) sass 文件的变化"></a>观察(Watching) sass 文件的变化</h3><p>Gulp 提供了一个 <code>watch</code> 方法用来检查文件是否被保存,它的基本语法是这样的:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">// Gulp watch syntax</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'files-to-watch'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'tasks'</span><span class="token punctuation">,</span> <span class="token string">'to'</span><span class="token punctuation">,</span> <span class="token string">'run'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>对于我们的项目,可以这么写:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">// Gulp watch syntax</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>更多的时候,我们想 watch 不同类型的文件,因此我们可以把多个 watch 封装到一个 <code>watch</code> 任务。</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'watch'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// Other watchers</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>现在运行 <code>gulpt watch</code> ,开启 watch 任务。现在我们修改并保存匹配的文件后,将立即执行指定的任务,如下图所示:</p>
<p><img src="./img/gulp-watch.gif" alt=""></p>
<h3 id="通过-Browser-Sync-实现浏览器同步刷新"><a href="#通过-Browser-Sync-实现浏览器同步刷新" class="headerlink" title="通过 Browser Sync 实现浏览器同步刷新"></a>通过 Browser Sync 实现浏览器同步刷新</h3><p>Browser Sync 通过创建一个 web 服务器,使 web 开发更加快捷。</p>
<p>首先我们将 Browser Sync 安装到项目。</p>
<pre class=" language-shell"><code class="language-shell">npm install browser-sync --save-dev
</code></pre>
<p>安装好后,在 <code>gulpfile.js</code> 引入。</p>
<pre class=" language-shell"><code class="language-shell">var browserSync = require('browser-sync').create();
</code></pre>
<p>接下来,我们创建一个 <code>browserSync</code> 任务,该任务利用 Browser Sync 来让 Gulp 创建一个服务器。</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'browserSync'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
browserSync<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
server<span class="token punctuation">:</span> <span class="token punctuation">{</span>
baseDir<span class="token punctuation">:</span> <span class="token string">'app'</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>baseDir 指定了 server 的根目录。</p>
<p>另外我们还需要修改 <code>sass</code> 任务的代码,确保当 <code>sass</code> 任务运行的时候,将更新过的样式注入到浏览器。</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">sass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 利用 gulp-sass 将 Sass 转为 CSS</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'app/css'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>browserSync<span class="token punctuation">.</span><span class="token function">reload</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
stream<span class="token punctuation">:</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>为了执行 <code>watch</code> 之前,先执行一次 <code>browserSync</code> 和 <code>sass</code> 任务,我们修改 <code>watch</code> 任务的代码,如下:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'watch'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token string">'browserSync'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// Other watchers</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>这样一来,<code>watch</code> 任务执行之前,会先执行 <code>sass</code> 和 <code>browserSync</code> ,然后通过监听 <code>sass</code> 任务的执行来刷新浏览器视图。效果如下图:</p>
<p><img src="./img/browser-sync.gif" alt=""></p>
<p>除了监听 sass 文件的保存之后,我们还可以监听 html 和 js 文件的保存,实现只要文件一保存,就更新浏览器视图。因此,更新 <code>watch</code> 任务代码如下:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'watch'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token string">'browserSync'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 当 HTML 或 JS 文件发生变化时,重新载入浏览器</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/*.html'</span><span class="token punctuation">,</span> browserSync<span class="token punctuation">.</span>reload<span class="token punctuation">)</span><span class="token punctuation">;</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/js/**/*.js'</span><span class="token punctuation">,</span> browserSync<span class="token punctuation">.</span>reload<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>到目前为止,我们已经通过 Gulp 实现了三个功能:</p>
<ul>
<li>创建一个用于开发的服务器</li>
<li>使用 Sass 预编译器</li>
<li>当文件发生变化时,同步刷新浏览器</li>
</ul>
<p>以下的部分将会讨论如何通过 Gulp 来优化资源文件,先从优化 CSS 和 JavaScript 文件说起。</p>
<h3 id="优化-CSS-和-JavaScript-文件"><a href="#优化-CSS-和-JavaScript-文件" class="headerlink" title="优化 CSS 和 JavaScript 文件"></a>优化 CSS 和 JavaScript 文件</h3><p>优化包括两个方面:压缩和拼接。</p>
<p>开发者常常面临的问题是,很难按正确的顺序拼接脚本文件。</p>
<p>比如我们在 <code>index.html</code> 中引入了 3 个脚本文件:</p>
<pre class=" language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>js/lib/a-library.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>js/lib/another-library.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>js/main.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
</code></pre>
<p>这些脚本文件位于不同的目录,我们很难利用传统的插件(如 <code>gulp-concatenate</code>)将它们正确的拼接。</p>
<p>所幸,一个叫做 <code>gulp-useref</code> 的插件解决了这个问题。</p>
<p><code>gulp-useref</code> 能将任何数量的 CSS 和 JavaScript 文件拼接成一个单独的文件。它通过查找以 <code><!--build:</code> 开头,以 <code><!--endbuild--></code> 结尾的代码块来实现的。标记语法如下:</p>
<pre class=" language-html"><code class="language-html"><span class="token comment" spellcheck="true"><!-- build:<type> <path> --></span>
... HTML Markup, list of script / link tags
<span class="token comment" spellcheck="true"><!--endbuild--></span>
</code></pre>
<p><code><type></code> 可以是 <code>js</code>, <code>css</code> , <code>remove</code> 。最好将 <code><type></code> 设置为你想拼接文件的类型。如果你把 <code>type</code> 设置为 <code>remove</code> ,Gulp 将会忽略这个 build block。</p>
<p><code><path></code> 是拼接后的文件存放的路径。</p>
<p>举个例子,比如我们想将以下 3 个 JS 文件拼接,并把拼接好的文件存放到 <code>js</code> 目录,名为 <code>main.min.js</code> 。我们可以这么写:</p>
<pre class=" language-html"><code class="language-html"><span class="token comment" spellcheck="true"><!-- build:js js/main/min.js --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>js/lib/a-library.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>js/lib/another-library.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>js/main.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token comment" spellcheck="true"><!-- endbuild --></span>
</code></pre>
<p>好了,熟悉了 <code>gulp-useref</code> 的基本标记语法,让我们来实战以下吧。先将 <code>gulp-useref</code> 安装到项目目录,并在 <code>gulpfile.js</code> 文件中引入:</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp-useref --save-dev
</code></pre>
<pre class=" language-shell"><code class="language-shell">var useref = require('gulp-useref');
</code></pre>
<p>创建 <code>useref</code> 任务:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'useref'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/*.html'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">useref</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>然后在命令行执行 <code>gulp useref</code> ,你会发现三个脚本文件会合并为一个 <code>main.min.js</code> ,并且 html 中引用脚本的代码也自动修改了。</p>
<p>拼接完之后还需要对 JS 文件进行压缩。我们采用 <code>gulp-uglify</code> 插件来压缩 JS 文件,另外我们还需要一个 <code>gulp-if</code> 插件来保证我们只对 JS 文件进行压缩。</p>
<p>安装 <code>gulp-uglify</code> 和 <code>gulp-if</code>:</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp-uglify --save-dev
npm install gulp-if --save-dev
</code></pre>
<p>在 <code>gulpfile.js</code> 中引入 <code>gulp-uglify</code> 和 <code>gulp-if</code> :</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> uglify <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'gulp-uglify'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> gulpIf <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'gulp-if'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>修改 <code>useref</code> 任务的代码:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'useref'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/*.html'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">useref</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">gulpIf</span><span class="token punctuation">(</span><span class="token string">'*.js'</span><span class="token punctuation">,</span> <span class="token function">uglify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>再次执行 <code>gulp useref</code>, 发现 <code>main.min.js</code> 中的代码已经是压缩过了的了。</p>
<p>我们可以用相同的方法拼接 CSS 文件,对样式的引用标签进行标记:</p>
<pre class=" language-html"><code class="language-html"><span class="token comment" spellcheck="true"><!-- build:css css/styles.min.css --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>css/styles.css<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>css/another-stylesheet.css<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token comment" spellcheck="true"><!-- endbuild --></span>
</code></pre>
<p>另外,我们再安装一个 CSS 文件的压缩插件,叫做 <code>gulp-cssnano</code> :</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp-cssnano --save-dev
</code></pre>
<p>引入 <code>gulp-cssnano</code> 并修改 <code>useref</code> 任务:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'useref'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/*.html'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">useref</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">gulpIf</span><span class="token punctuation">(</span><span class="token string">'*.js'</span><span class="token punctuation">,</span> <span class="token function">uglify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">gulpIf</span><span class="token punctuation">(</span><span class="token string">'*.css'</span><span class="token punctuation">,</span> <span class="token function">cssnano</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>在命令行运行 <code>gulp useref</code> 之后,可以看到 CSS 文件也被拼接和压缩了。</p>
<p>现在通过 <code>useref</code> 任务,可以同时拼接压缩 CSS 文件和 JS 文件了。接下来让我们看看怎么优化图片吧。</p>
<h3 id="优化图片"><a href="#优化图片" class="headerlink" title="优化图片"></a>优化图片</h3><p>你可能猜到了,我们又需要安装插件了。没错,这次我们需要安装 <code>gulp-imagemin</code> 插件,专门用来压缩图片的。</p>
<p>安装并引入:</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp-imagemin --save-dev
</code></pre>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> imagemin <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'gulp-imagemin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>我们将对 <code>png</code> , <code>jpg</code> , <code>gif</code> 和 ·<code>svg</code> 格式的图片都进行压缩,新增 <code>images</code> 任务,代码如下:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'images'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/images/**/*.+(png|jpg|gif|svg)'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">imagemin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist/images'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>不同类型的图片可以进行不同的优化,例如,你可以通过将 <code>interlaced</code> 字段设置为 <code>true</code> ,来生成 interlaced GIFs。代码如下:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'images'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/images/**/*.+(png|jpg|gif|svg)'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">imagemin</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// Setting interlaced to true</span>
interlaced<span class="token punctuation">:</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist/images'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>压缩图片是非常耗时的过程,为了避免没有必要的重复处理,我们使用 <code>gulp-cache</code> 插件,安装并引入:</p>
<pre class=" language-shell"><code class="language-shell">npm install gulp-cache --save-dev
</code></pre>
<pre class=" language-shell"><code class="language-shell">var cache = require('gulp-cache');
</code></pre>
<p>改进 <code>images</code> 任务的代码:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'images'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/images/**/*.+(png|jpg|gif|svg)'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token function">imagemin</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// Setting interlaced to true</span>
interlaced<span class="token punctuation">:</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist/images'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>以上便是图片的优化过程。接下来我们来处理字体目录。</p>
<h3 id="复制字体目录到-dist"><a href="#复制字体目录到-dist" class="headerlink" title="复制字体目录到 dist"></a>复制字体目录到 dist</h3><p>因为字体文件已经是优化过了的,因此我们只要将字体文件夹复制到 dist 目录即可,这个非常简单,看一下代码:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'fonts'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span><span class="token string">'app/fonts/**/*'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span><span class="token string">'dist/fonts'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>到目前为止,我们一共有 6 个任务,它们分别是:</p>
<ul>
<li><code>sass</code> - 将 Sass 转换为 CSS</li>
<li><code>watch</code> - 监听文件的变化,实时刷新浏览器</li>
<li><code>browserSync</code> - 创建本地 web 服务器</li>
<li><code>useref</code> - 拼接并压缩 JS 和 CSS 文件</li>
<li><code>images</code> - 压缩图片资源</li>
<li><code>fonts</code> - 复制字体文件夹</li>
</ul>
<p>每一个任务我们都得单独用不同的命令调用,因此我们想把所有任务整合到一个命令。在我们这么做之前,让我们先看看如何自动清除生成的文件。</p>
<h3 id="自动清除生成的文件"><a href="#自动清除生成的文件" class="headerlink" title="自动清除生成的文件"></a>自动清除生成的文件</h3><p>前面我们已经学会了如何自动生成文件,那么当我们不需要某些文件时,如何自动清除它们呢。我们使用 <code>del</code> 来帮助我们完成清除。首先,在项目中安装 <code>del</code> 。</p>
<pre class=" language-shell"><code class="language-shell">npm install del --save-dev
</code></pre>
<p>在 <code>gulpfile.js</code> 中引入:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> del <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'del'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>添加 <code>clean:dist</code> 任务:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'clean:dist'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> del<span class="token punctuation">.</span><span class="token function">sync</span><span class="token punctuation">(</span><span class="token string">'dist'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>另外,如果我们想清除缓存,再创建一个 <code>cache:clear</code> 任务:</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'cache:clear'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> cache<span class="token punctuation">.</span><span class="token function">clearAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>OK! 现在我们来将多个任务整合到一起吧!</p>
<h3 id="整合-Gulp-任务"><a href="#整合-Gulp-任务" class="headerlink" title="整合 Gulp 任务"></a>整合 Gulp 任务</h3><p>到目前为止我们所有的任务可以分为两类:</p>
<ul>
<li>第一类是开发过程中的任务。包括将 Sass 编译为 CSS,监听源文件变化并重新刷新浏览器</li>
<li>第二类是优化过程中的任务。包括优化资源文件 CSS,JavaScript 和图片,以及将字体文件夹从 <code>app</code> 复制到 <code>dist</code> </li>
</ul>
<p>我们已经将第一类任务整合为一个简单的 <code>gulp watch</code> 命令。</p>
<p>第二类的任务包括 <code>clean:dist</code> , <code>sass</code> , <code>useref</code> , <code>images</code> , <code>fonts</code> ,我们通过创建一个 <code>build</code> 任务将它们整合到一起。</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'build'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'clean:dist'</span><span class="token punctuation">,</span> <span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token string">'useref'</span><span class="token punctuation">,</span> <span class="token string">'images'</span><span class="token punctuation">,</span> <span class="token string">'fonts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Building files'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>然后,上述代码是有问题的。有可能 <code>clean:dist</code> 任务还没有完成,<code>sass</code> , <code>useref</code> ,甚至 <code>images</code> 都执行完了,这会导致新生成的文件都会被 <code>clean:dist</code> 删除。</p>
<p>为了保证任务的执行顺序,我们需要安装一个额外的插件 <code>run-sequence</code> 。</p>
<pre class=" language-shell"><code class="language-shell">npm install run-sequence --save-dev
</code></pre>
<p>引入 <code>run-sequence</code> ,并更新 <code>build</code> 任务:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> runSequence <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'run-sequence'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">onBuild</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Build successfully'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'build'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>onBuild<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">runSequence</span><span class="token punctuation">(</span><span class="token string">'clean:dist'</span><span class="token punctuation">,</span>
<span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token string">'useref'</span><span class="token punctuation">,</span> <span class="token string">'images'</span><span class="token punctuation">,</span> <span class="token string">'fonts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
onBuild
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>以上代码确保了 <code>clean:dist</code> 任务首先执行,等它执行完成后,才开始执行后续的任务,后续这些任务的执行顺序无关紧要,所以将它们放到同一个数组中。全部任务执行完之后,调用我们自定义的回调函数 <code>onBuild</code> 。</p>
<p>为了保证前后一致,我们将前面定义的 <code>watch</code> 任务也用 <code>run-sequence</code> 来实现。</p>
<pre class=" language-javascript"><code class="language-javascript">gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'watch'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/scss/**/*.scss'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 当 HTML 或 JS 文件发生变化时,重新载入浏览器</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/*.html'</span><span class="token punctuation">,</span> browserSync<span class="token punctuation">.</span>reload<span class="token punctuation">)</span><span class="token punctuation">;</span>
gulp<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span><span class="token string">'app/js/**/*.js'</span><span class="token punctuation">,</span> browserSync<span class="token punctuation">.</span>reload<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">'default'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">runSequence</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'sass'</span><span class="token punctuation">,</span> <span class="token string">'browserSync'</span><span class="token punctuation">,</span> <span class="token string">'watch'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>注意到,我们去掉了 <code>watch</code> 原先的第二个参数,而是把 <code>sass</code> , <code>broserSync</code> 和 <code>watch</code> 任务通过 <code>run-sequence</code> 整合到了一个 <code>default</code> 任务。为什么要命名为 <code>default</code> 呢?因此这样我们只要简单的在命令行运行 <code>gulp</code> ,就可以很方便的执行这个任务了。</p>
]]></content>
<categories>
<category> Front End </category>
</categories>
<tags>
<tag> Gulp </tag>
<tag> 前端工程化 </tag>
<tag> Node.js </tag>
</tags>
</entry>
<entry>
<title><![CDATA[从地图类型切换控件谈JS代码优化]]></title>
<url>http://nightn.com/2017/11/28/js-optimize-maptype-demo/</url>
<content type="html"><![CDATA[<p>本文以 JavaScript 开发自定义百度地图类型切换控件为主线,记录了控件从实现到一步步优化过程中的思考与总结,其中不少关于 JavaScript 代码优化的 tip 在很多场合都很实用。主要知识点包括:HTML 与 CSS 之间的松耦合、JS 的事件委托、HTML 自定义特性、DOM 节点访问及遍历、JQuery 常用方法的使用及百度地图 API 的调用等。这些都是比较基础的知识点,在此尽可能完整的记录,以便今后查阅及完善。</p>
<a id="more"></a>
<h2 id="一、实现"><a href="#一、实现" class="headerlink" title="一、实现"></a>一、实现</h2><h3 id="1-百度-API-内部实现"><a href="#1-百度-API-内部实现" class="headerlink" title="1. 百度 API 内部实现"></a>1. 百度 API 内部实现</h3><p>百度地图 JS 版本 API 引入及地图初始化可以参考<a href="http://developer.baidu.com/map/jsdemo.htm#a1_2" target="_blank" rel="external">百度地图API示例</a> ,在此不再赘述,给出基本的地图展示及内置地图类型切换控件的代码,如下:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// 地图初始化</span>
<span class="token keyword">var</span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BMap<span class="token punctuation">.</span>Map</span><span class="token punctuation">(</span><span class="token string">'map'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">centerAndZoom</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">BMap<span class="token punctuation">.</span>Point</span><span class="token punctuation">(</span><span class="token number">116.404</span><span class="token punctuation">,</span> <span class="token number">39.915</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 添加地图切换控件</span>
map<span class="token punctuation">.</span><span class="token function">addControl</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">BMap<span class="token punctuation">.</span>MapTypeControl</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
mapTypes<span class="token punctuation">:</span> <span class="token punctuation">[</span>
BMAP_NORMAL_MAP<span class="token punctuation">,</span>
BMAP_HYBRID_MAP
<span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>实现效果如下:</p>
<p><img src="http://on2kkr82s.bkt.clouddn.com/17-11-28/42778500.jpg" width="400"></p>
<p>虽然可以实现基本的地图类型切换,但关于自定义样式和控件显示位置上有很多限制,因此以下提供地图类型切换控件的自定义实现。</p>
<h3 id="2-自定义实现"><a href="#2-自定义实现" class="headerlink" title="2. 自定义实现"></a>2. 自定义实现</h3><p>自定义实现将新增一个 div 控件元素,并将其添加到地图之上,自定义控件包括「地图」、「卫星」、「混合」三种地图类型的切换。</p>
<p>HTML 代码:</p>
<pre class=" language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>map-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ctrl1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>地图<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ctrl2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>卫星<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ctrl3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>混合<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
</code></pre>
<p>CSS 样式:</p>
<pre class=" language-css"><code class="language-css"><span class="token selector"><span class="token id">#map-control</span> </span><span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">120</span>px<span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">30</span>px<span class="token punctuation">;</span>
<span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token number">30</span>px<span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
<span class="token property">top</span><span class="token punctuation">:</span> <span class="token number">10</span>px<span class="token punctuation">;</span>
<span class="token property">right</span><span class="token punctuation">:</span> <span class="token number">10</span>px<span class="token punctuation">;</span>
<span class="token property">border</span><span class="token punctuation">:</span> <span class="token number">1</span>px solid <span class="token hexcode">#8EA8E0</span><span class="token punctuation">;</span>
<span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">4</span>px<span class="token punctuation">;</span>
<span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token hexcode">#FFF</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token id">#map-control</span> > div </span><span class="token punctuation">{</span>
<span class="token property">flex</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token id">#ctrl1</span> </span><span class="token punctuation">{</span>
<span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#FFF</span><span class="token punctuation">;</span>
<span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token hexcode">#8EA8E0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>map-control 采用 flext 的布局,让三个子 div 横向均匀分布;另外,position 属性采用 absolute,使其位于地图上方。在此,将 ctrl1 块的样式初始化为激活状态。<strong>第 1 版的 JS 代码如下</strong>:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token keyword">var</span> ctrl1 <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'ctrl1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> ctrl2 <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'ctrl2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> ctrl3 <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'ctrl3'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">/**
* Version 1
* 为三个子元素分别添加 DOM2 级事件处理程序,处理样式及地图切换
*/</span>
ctrl1<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// 去除其他两个子元素的激活样式</span>
ctrl2<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#000'</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#000'</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 为当前元素添加激活样式</span>
ctrl1<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
ctrl1<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#8EA8E0'</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 地图类型切换</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_NORMAL_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// 去除其他两个子元素的激活样式</span>
ctrl1<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#000'</span><span class="token punctuation">;</span>
ctrl1<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#000'</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 为当前元素添加激活样式</span>
ctrl2<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#8EA8E0'</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 地图类型切换</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_SATELLITE_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment" spellcheck="true">// 去除其他两个子元素的激活样式</span>
ctrl1<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#000'</span><span class="token punctuation">;</span>
ctrl1<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#000'</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 为当前元素添加激活样式</span>
ctrl3<span class="token punctuation">.</span>style<span class="token punctuation">.</span>color <span class="token operator">=</span> <span class="token string">'#FFF'</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundColor <span class="token operator">=</span> <span class="token string">'#8EA8E0'</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 地图类型切换</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_HYBRID_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>上述程序完全能够实现地图类型的切换效果,但是存在很多问题:</p>
<ul>
<li><strong>HTML 与 CSS 耦合严重</strong>。利用 JS 对每个元素的样式进行直接修改是很不可取的,一方面导致代码冗余,另一方面如果有一处需要修改,那其余地方都得修改。(如现在需要将元素激活时的背景元素从蓝色改变为绿色,那么就得修改三个事件处理程序的语句)。</li>
<li><strong>事件处理程序繁多,扩展性差</strong>。可以看到,上述每一个子元素都添加了一个事件处理程序,代码复用性差的同时,也提高了程序运行时的内存占用。</li>
<li>其他问题下文论述。</li>
</ul>
<p>针对以上实现存在的问题,以下提出逐步改进方案。</p>
<h2 id="二、改进"><a href="#二、改进" class="headerlink" title="二、改进"></a>二、改进</h2><h3 id="1-降低-HTML-与-CSS-的耦合"><a href="#1-降低-HTML-与-CSS-的耦合" class="headerlink" title="1. 降低 HTML 与 CSS 的耦合"></a>1. 降低 HTML 与 CSS 的耦合</h3><p>低耦合是软件设计的基本原则,为了降低 HTML 和 CSS 的耦合,我们引入一个新的 CSS 类:current,它表示当前选中元素的样式:</p>
<pre class=" language-css"><code class="language-css"><span class="token selector"><span class="token id">#map-control</span> <span class="token class">.current</span> </span><span class="token punctuation">{</span>
<span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#FFF</span><span class="token punctuation">;</span>
<span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token hexcode">#8EA8E0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p><strong>第 2 版 JS 代码如下</strong>:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">/**
* Version 2
* 利用 current 样式类,降低 HTML 和 CSS 的耦合
*/</span>
ctrl1<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
ctrl2<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl1<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_NORMAL_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
ctrl1<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_SATELLITE_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
ctrl1<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl2<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ctrl3<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_HYBRID_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>通过以上改进,我们将样式与元素相分离,若要修改激活元素的样式,只需要修改 current 样式类即可。</p>
<blockquote>
<p><strong>Tip 1 解耦 HTML/CSS</strong></p>
<p>在使用 JavaScript 修改元素样式的时候,尽量修改元素的样式类,而不是直接修改样式本身。</p>
</blockquote>
<h3 id="2-使用事件委托"><a href="#2-使用事件委托" class="headerlink" title="2. 使用事件委托"></a>2. 使用事件委托</h3><p>以上代码还可以进一步改进。我们知道在 DOM 事件冒泡的过程中,事件的触发是从当前元素逐级往上传递,因此当我们需要监听很多子元素事件的时候,实际上只监听其父元素的事件即可,当然需要在父元素的事件处理程序中对当前点击的子元素进行具体的判断。这样一来可以减少事件处理程序的数量,提供代码复用和内存利用率。这种方法就叫作事件委托。</p>
<p><strong>第 3 版 js 代码如下</strong>:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">/**
* Version 3
* 使用事件委托,减少事件处理程序数目
*/</span>
<span class="token keyword">var</span> mapControl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'map-control'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
mapControl<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> child <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>firstElementChild<span class="token punctuation">;</span>
<span class="token keyword">while</span><span class="token punctuation">(</span>child <span class="token operator">!==</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lastElementChild<span class="token punctuation">)</span> <span class="token punctuation">{</span>
child<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
child <span class="token operator">=</span> child<span class="token punctuation">.</span>nextElementSibling<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
child<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">switch</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">case</span> <span class="token string">'ctrl1'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_NORMAL_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">case</span> <span class="token string">'ctrl2'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_SATELLITE_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">case</span> <span class="token string">'ctrl3'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>BMAP_HYBRID_MAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>第 3 版代码看起来比第 2 版还要复杂,但它的思路是非常简单的。在父元素 mapControl 添加一个事件处理函数,该函数有一个事件对象 event,它记录当前被点击元素的一些属性以及本次事件的一些属性,利用我们可以通过 event.target 获取到当前点击的元素。在事件处理函数内部,我们首先遍历了 mapControl 的子元素,并将它们的样式类 current 都移除掉(原生 JS 遍历确实有点麻烦,在这里用 do-while 循环应该更好),然后通过 event.target 获取当前点击的元素,并将样式类 current 添加到该元素,以上完成了点击时的样式切换。</p>
<p>接下来是点击后的地图类型切换了,在此利用 event.target.id 属性确定当前点击的是哪一个元素,然后再设置对应的地图类型。总的来说,利用事件委托可以将事件处理程序的数目降到最少,提高代码复用。</p>
<blockquote>
<p><strong>Tip 2 使用事件委托</strong></p>
<p>如果要为多个并列的元素分别添加类似的事件处理程序,可以考虑利用事件委托,将事件处理程序添加到这些并列元素的父元素上。</p>
</blockquote>
<h3 id="3-自定义-HTML-特性"><a href="#3-自定义-HTML-特性" class="headerlink" title="3. 自定义 HTML 特性"></a>3. 自定义 HTML 特性</h3><p>以上 switch 语句看起来很不优雅,代码量很大,看起来很尴尬。为此我们引入一个 mapTypeArr 的数组,并为 mapControl 元素下的每一个 div 添加一个自定义属性,从而不再需要 id 属性。</p>
<p>更新后的 HTML 代码如下:</p>
<pre class=" language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>map-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">data-maptype</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>current<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>地图<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">data-maptype</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>卫星<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">data-maptype</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>混合<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
</code></pre>
<p><strong>第 4 版 js 代码如下</strong>:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">/**
* Version 4
* 利用 HTML 自定义特性,避免 switch
*/</span>
<span class="token keyword">var</span> mapControl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'map-control'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> mapTypeArr <span class="token operator">=</span> <span class="token punctuation">[</span>BMAP_NORMAL_MAP<span class="token punctuation">,</span> BMAP_SATELLITE_MAP<span class="token punctuation">,</span> BMAP_HYBRID_MAP<span class="token punctuation">]</span><span class="token punctuation">;</span>
mapControl<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> child <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>firstElementChild<span class="token punctuation">;</span>
<span class="token keyword">var</span> target <span class="token operator">=</span> event<span class="token punctuation">.</span>target<span class="token punctuation">;</span>
<span class="token keyword">while</span><span class="token punctuation">(</span>child <span class="token operator">!==</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lastElementChild<span class="token punctuation">)</span> <span class="token punctuation">{</span>
child<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
child <span class="token operator">=</span> child<span class="token punctuation">.</span>nextElementSibling<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
child<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
target<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>mapTypeArr<span class="token punctuation">[</span><span class="token function">parseInt</span><span class="token punctuation">(</span>target<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'data-maptype'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>第 4 版代码同第 3 版相比,通过自定义的 HTML 特性和一个 mapTypeArr 数组,优化了 setMapType() 相关语句。</p>
<blockquote>
<p><strong>Tip3 HTML 自定义特性</strong></p>
<p>我们可以自定义 HTML 元素的特性,自定义的特性一般以 data- 开头,统一采用小写。原生 DOM 元素的 getAttribute() 方法也能获取元素的自定义特性。</p>
</blockquote>
<p>另外可以注意到一个细节,event.target 使用了多次,为了提高程序性能,我们用了一个局部变量 target 将 event.target 保存起来,避免属性的全局查找。</p>
<blockquote>
<p><strong>Tip4 避免属性的全局查找</strong></p>
<p>如果经常需要用到元素的某一个属性,为了避免每一次调用时都进行一次查找,可以用一个局部变量将该属性进行缓存。避免使用 with,因为 with 会加长作用域链,使得属性的查找变慢。</p>
</blockquote>
<h3 id="4-使用-JQuery-改进元素查找"><a href="#4-使用-JQuery-改进元素查找" class="headerlink" title="4. 使用 JQuery 改进元素查找"></a>4. 使用 JQuery 改进元素查找</h3><p>从第 4 版 js 代码可以看到,程序依然比较冗长的原因主要是在元素的遍历部分。为此,我们使用 JQuery 改进元素的查找。</p>
<p><strong>第 5 版 js 代码如下</strong>:</p>
<pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">/**
* Version 5
* 使用 jquery 改进元素查找
*/</span>
<span class="token keyword">var</span> $mapControl <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#map-control'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> mapTypeArr <span class="token operator">=</span> <span class="token punctuation">[</span>BMAP_NORMAL_MAP<span class="token punctuation">,</span> BMAP_SATELLITE_MAP<span class="token punctuation">,</span> BMAP_HYBRID_MAP<span class="token punctuation">]</span><span class="token punctuation">;</span>
$mapControl<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> $target <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<span class="token punctuation">)</span><span class="token punctuation">;</span>
$target<span class="token punctuation">.</span><span class="token function">addClass</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">siblings</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">removeClass</span><span class="token punctuation">(</span><span class="token string">'current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
map<span class="token punctuation">.</span><span class="token function">setMapType</span><span class="token punctuation">(</span>mapTypeArr<span class="token punctuation">[</span>$target<span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'data-maptype'</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>使用了 JQuery 后,地图类型切换控件的实现变得非常的精简。通过事件对象 event 获取当前当前的元素,并将其转换为 JQuery 元素对象,并利用 JQuery 的链式编程方法,用一条语句激活当前元素的样式,并去除其他元素的样式。最后设置地图类型。虽然 JQuery 性能不一定比得上原生 JS,但可以极大的简化代码量,这在很多场合是非常用帮助的。</p>
<blockquote>
<p><strong>Tip5 合理使用 JQuery 可以极大精简你的代码</strong></p>
</blockquote>
<h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><p>以上便是「从地图类型切换控件谈 JS 代码优化」的全部内容,后续如果有更加简单高效的实现方式,再进行补充,也欢迎大家提出自己的思路。以下将优化过程中用到的 Tip 进行总结:</p>
<ul>
<li><p>Tip 1 解耦 HTML/CSS</p>
<p>在使用 JavaScript 修改元素样式的时候,尽量修改元素的样式类,而不是直接修改样式本身。</p>
</li>
<li><p>Tip 2 使用事件委托</p>
<p>如果要为多个并列的元素分别添加类似的事件处理程序,可以考虑利用事件委托,将事件处理程序添加到这些并列元素的父元素上。</p>
</li>
<li><p>Tip3 HTML 自定义特性</p>
<p>我们可以自定义 HTML 元素的特性,自定义的特性一般以 data- 开头,统一采用小写。原生 DOM 元素的 getAttribute() 方法也能获取元素的自定义特性。</p>
</li>
<li><p>Tip4 避免属性的全局查找</p>
<p>如果经常需要用到元素的某一个属性,为了避免每一次调用时都进行一次查找,可以用一个局部变量将该属性进行缓存。避免使用 with,因为 with 会加长作用域链,使得属性的查找变慢。</p>
</li>
<li><p>Tip5 合理使用 JQuery 可以极大精简你的代码</p>
</li>
</ul>
<hr>
<p>原文地址:<a href="http://nightn.com/2017/11/28/js-optimize-maptype-demo">http://nightn.com/2017/11/28/js-optimize-maptype-demo</a></p>
]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> JavaScript </tag>
<tag> 百度地图 </tag>
<tag> JS事件 </tag>
<tag> JQuery </tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构学习笔记:排序算法]]></title>
<url>http://nightn.com/2017/06/07/DSA-sorting/</url>
<content type="html"><![CDATA[<p>本文对常见排序算法进行了总结,如插入排序、希尔排序、桶排序、快速排序等。对于每个排序算法,给出它的概述、简单实现及复杂度分析。</p>
<a id="more"></a>
<h2 id="1-插入排序"><a href="#1-插入排序" class="headerlink" title="1. 插入排序"></a>1. 插入排序</h2><h3 id="1-1-插入排序概述"><a href="#1-1-插入排序概述" class="headerlink" title="1.1 插入排序概述"></a>1.1 插入排序概述</h3><p><strong>插入排序(Insertion Sort)</strong>是一种非常简单的排序方法,对于大小为 N 的数组,考虑初始状态为单独的第一个元素,之后每次插入一个元素,使得子数组总处于排序好的状态,这样,经过 N-1 次插入,整个数组将处于有序状态。下表以一个简单的例子来描述插入排序的过程。</p>
<table>
<thead>
<tr>
<th></th>
<th style="text-align:center">A[0]</th>
<th style="text-align:center">A[1]</th>
<th style="text-align:center">A[2]</th>
<th style="text-align:center">A[3]</th>
<th style="text-align:center">A[4]</th>
<th style="text-align:center">移动次数</th>
</tr>
</thead>
<tbody>
<tr>
<td>原始序列</td>
<td style="text-align:center"><strong>42</strong></td>
<td style="text-align:center">31</td>
<td style="text-align:center">50</td>
<td style="text-align:center">17</td>
<td style="text-align:center">20</td>
<td style="text-align:center">/</td>
</tr>
<tr>
<td>第 1 次插入</td>
<td style="text-align:center"><strong>31</strong></td>
<td style="text-align:center"><strong>42</strong></td>
<td style="text-align:center">50</td>
<td style="text-align:center">17</td>
<td style="text-align:center">20</td>
<td style="text-align:center">1</td>
</tr>
<tr>
<td>第 2 次插入</td>
<td style="text-align:center"><strong>31</strong></td>
<td style="text-align:center"><strong>42</strong></td>
<td style="text-align:center"><strong>50</strong></td>
<td style="text-align:center">17</td>
<td style="text-align:center">20</td>
<td style="text-align:center">0</td>
</tr>
<tr>
<td>第 3 次插入</td>
<td style="text-align:center"><strong>17</strong></td>
<td style="text-align:center"><strong>31</strong></td>
<td style="text-align:center"><strong>42</strong></td>
<td style="text-align:center"><strong>50</strong></td>
<td style="text-align:center">20</td>
<td style="text-align:center">3</td>
</tr>
<tr>
<td>第 4 次插入</td>
<td style="text-align:center"><strong>17</strong></td>
<td style="text-align:center"><strong>20</strong></td>
<td style="text-align:center"><strong>31</strong></td>
<td style="text-align:center"><strong>42</strong></td>
<td style="text-align:center"><strong>50</strong></td>
<td style="text-align:center">3</td>
</tr>
</tbody>
</table>
<p>表中对一个大小 N = 5 的数组进行插入排序,初始状态认为只有第一个元素 42 处于排序好的状态,第 1 次将 31 插入子序列,由于插入后,子序列将不再处于有序状态,因此需要进行元素交换,在此交换 1 次即可;然后进行第 2 次,第 3 次插入,每次都对当前子序列进行排序(表中粗体部分表示当前已经排序好的序列),以此类推,直到第 N - 1 次(在此是第 4 次)插入,整个序列都将排好序。</p>
<h3 id="1-2-插入排序实现"><a href="#1-2-插入排序实现" class="headerlink" title="1.2 插入排序实现"></a>1.2 插入排序实现</h3><p>插入排序的实现也非常简单,在此以 C 语言为例,对大小为 N 的数组进行排序,代码如下。</p>
<pre class=" language-c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">InsertionSort_swap</span><span class="token punctuation">(</span><span class="token keyword">int</span> A<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">int</span> N<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator"><</span> N<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> i<span class="token punctuation">;</span> j <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> A<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator"><</span> A<span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> j<span class="token operator">--</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">int</span> temp <span class="token operator">=</span> A<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>
A<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> A<span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
A<span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> temp<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>外层 for 循环表示从第 1 次到 第 N-1 次插入,内层 for 循环表示对于每一次插入,都将新元素与原有元素进行比较,如果前者较小,则往前移动一位,直至达到有序状态。</p>
<p>上述实现虽然简单易懂,但只要仔细观察,就能发现仍有改进的余地。内层 for 循环我们通过 3 次赋值来实现相邻元素的交换,而事实上,对于新插入的元素,我们只是把当做一个比较的基准,我们不应该在每一次比较后都对这一基准进行移动,以避免显示的元素交换。因此,我们有了如下的实现。</p>
<pre class=" language-c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">InsertionSort</span><span class="token punctuation">(</span><span class="token keyword">int</span> A<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token keyword">int</span> N<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator"><</span> N<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">int</span> temp <span class="token operator">=</span> A<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> j<span class="token punctuation">;</span>
<span class="token keyword">for</span><span class="token punctuation">(</span>j <span class="token operator">=</span> i<span class="token punctuation">;</span> j <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> temp <span class="token operator"><</span> A<span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> j<span class="token operator">--</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
A<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> A<span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
A<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> temp<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>改进之后,避免了繁琐的元素交换。在本机测试中,算法的性能也得到提升。</p>
<table>
<thead>
<tr>
<th style="text-align:center">Algorithm</th>
<th style="text-align:center">InsertionSort_swap</th>
<th style="text-align:center">InsertionSort</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">N = 20000</td>
<td style="text-align:center">0.410 s</td>
<td style="text-align:center">0.269 s</td>
</tr>
<tr>
<td style="text-align:center">N = 50000</td>
<td style="text-align:center">2.443 s</td>
<td style="text-align:center">1.570 s</td>
</tr>
<tr>
<td style="text-align:center">N = 100000</td>
<td style="text-align:center">9.675 s</td>
<td style="text-align:center">6.123 s</td>
</tr>
</tbody>
</table>
<h3 id="1-3-插入排序分析"><a href="#1-3-插入排序分析" class="headerlink" title="1.3 插入排序分析"></a>1.3 插入排序分析</h3><p>从 <strong>InsertionSort_swap</strong> 和 <strong>InsertionSort</strong> 的对比中,可以看到不使用显示交换时,算法性能的提升。另外还可以看到,随着 N 的增加,算法耗时并不是线性增长的,基本上是呈现 $N^2$ 的增长趋势(如:N 从 50000 到 100000,规模变成原来的 2 倍,而耗时变成原来的 6.123 / 1.570 = 3.9 倍)。<strong>而事实上,插入排序的时间复杂度的确是 $O(N^2)$,</strong> 以下进行简单分析。</p>
<ul>
<li><p><strong>Worst-case</strong></p>
<p>对于 <strong>InsertionSort</strong> ,需要插入 N - 1 次,<strong>考虑最坏情况(即逆序列)</strong>,第 $i$ 次插入需要进行 $i+1$ 次赋值,因此总赋值次数为:</p>
<p>$$\sum_{i=2}^Ni=2+3+4+…+N=\Theta(N^2)$$</p>
</li>
</ul>
<ul>
<li><p><strong>Best-case</strong></p>
<p>对于<strong>最优情况</strong>(即排好序的序列),每次插入都不需要移动元素,即不会进入内层 for 循环,因此<strong>其时间复杂度为 $O(N)$</strong>。</p>
</li>
<li><p><strong>Average-case</strong></p>
<p>而一般情况下,<strong>插入排序的平均时间复杂度为 $\Theta(N^2)$</strong>。为什么呢?在此直接引用几个结论来简单证明一下。我们注意到,对于越倾向于无序的序列,插入排序耗时越长,这二者是相关联的。那么,我们如何衡量一个序列的无序程度呢?其实就是看这个序列的<strong>逆序数的对数</strong>,逆序数的定义是:对一对数 (i, j),i < j 但是 A[i] > A[j],则称 (i, j) 是一对逆序数。</p>
<p><strong>设一个序列的逆序数是 $I$,那么用插入排序算法对这一序列排序的时间复杂度为 $O(I+N)$</strong>。那么<strong>插入排序平均时间复杂度的问题</strong>,就转变为<strong>序列的平均逆序数对数的问题</strong>。经证明,<strong>一个大小为 N 的序列,其平均逆序数为 $N\times(N-1)/4$</strong> (证明略)。由此推出,插入排序的平均时间复杂度为 $O(N^2)$,更一般地,对于任何仅交换相邻元素的排序算法,$N^2$ 是一个下界。</p>
</li>
</ul>
<p>因此,对于基本有序的输入,插入排序算法的执行效率较快;而倾向于无序的输入,插入排序并不令人满意。</p>
<h2 id="2-希尔排序"><a href="#2-希尔排序" class="headerlink" title="2. 希尔排序"></a>2. 希尔排序</h2><p>未完待续……</p>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}
});
</script>
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>