-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathatom.xml
717 lines (471 loc) · 753 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Anran758's blog</title>
<link href="https://anran758.github.io/blog/atom.xml" rel="self"/>
<link href="https://anran758.github.io/blog/"/>
<updated>2024-01-04T03:20:04.521Z</updated>
<id>https://anran758.github.io/blog/</id>
<author>
<name>anran758</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Promise 与异步编程</title>
<link href="https://anran758.github.io/blog/2023/12/18/usage-promise/"/>
<id>https://anran758.github.io/blog/2023/12/18/usage-promise/</id>
<published>2023-12-18T03:11:17.000Z</published>
<updated>2024-01-04T03:20:04.521Z</updated>
<content type="html"><![CDATA[<p>Promise 是 JavaScript 中的一个重要概念,与前端的工作更是息息相关。因此本文将整理一下 Promise 在日常工作中的应用。</p><p><strong>目录</strong></p><ul><li><a href="#%E6%A6%82%E5%BF%B5">概念</a></li><li><a href="#asyncawait">async/await</a><ul><li><a href="#await">await</a></li><li><a href="#%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86">错误处理</a></li></ul></li><li><a href="#promise-%E4%B8%B2%E8%81%94">Promise 串联</a><ul><li><a href="#%E5%B8%B8%E8%A7%84%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95">常规处理方法</a></li><li><a href="#%E4%B8%B2%E8%81%94%E8%87%AA%E5%8A%A8%E5%8C%96">串联自动化</a></li></ul></li><li><a href="#promise-%E5%B9%B6%E5%8F%91">Promise 并发</a><ul><li><a href="#%E6%8E%A7%E5%88%B6%E6%89%B9%E6%AC%A1">控制批次</a></li><li><a href="#%E7%AE%80%E6%98%93%E7%89%88%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6">简易版并发控制</a></li><li><a href="#%E5%8A%A8%E6%80%81%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97">动态任务队列</a></li><li><a href="#%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93">第三方库</a></li></ul></li><li><a href="#%E6%80%BB%E7%BB%93">总结</a></li></ul><span id="more"></span><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>从 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises">MDN | 使用 Promise</a> 中我们能学习到 Promise 的基础使用与错误处理、组合等概念,可以将 <code>Promise</code> 的特点概括为:</p><ul><li>Promise 对象有三种状态,且状态一旦改变就不会再变。其值记录在内部属性 <code>[[PromiseState]]</code> 中:<ul><li>pending: 进行中</li><li>fulfilled: 已成功</li><li>rejected: 已失败</li></ul></li><li>主要用于异步计算,并且可以将异步操作队列化 (链式调用),按照期望的顺序执行,返回符合预期的结果。</li><li>可以在对象之间传递和操作 Promise,帮助我们处理队列</li><li>链式调用的写法更简洁,可以避免回调地狱</li></ul><p>在现实工作中,当我们使用 <code>Promise</code> 时更多是对请求的管理,由于不同请求或任务的异步性。因此我们会根据不同的使用场景处理 Promise 的调度。</p><h2 id="async-await"><a href="#async-await" class="headerlink" title="async/await"></a>async/await</h2><p><code>async/await</code> 是基于 <code>Promise</code> 的一种语法糖,使得异步代码的编写和理解更加像同步代码。</p><p>当一个函数前通过 async 关键字声明,那这个函数的返回值一定会返回一个 <code>Promise</code>,即便函数返回的值与 <code>Promise</code> 无关,也会隐式包装为 <code>Promise</code> 对象:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">getAge</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">18</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">getAge().then(<span class="function"><span class="params">age</span> =></span> <span class="built_in">console</span>.log(<span class="string">`age: <span class="subst">${age}</span>`</span>))</span><br><span class="line"><span class="comment">// age: 18</span></span><br></pre></td></tr></table></figure><h3 id="await"><a href="#await" class="headerlink" title="await"></a>await</h3><p><code>await</code> 操作符通常和 <code>async</code> 是配套使用的,它会等待 <code>Promise</code> 并拆开 Promise 包装直接取到里面的值。当它处于 <code>await</code> 状态时,Promise 还处于 ``,后续的代码将不会被执行,因此看起来像是 “同步” 的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">delayResolve</span>(<span class="params">x, timeout = <span class="number">2000</span></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> resolve(x);</span><br><span class="line"> }, timeout);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> x = <span class="keyword">await</span> delayResolve(<span class="number">2</span>, <span class="number">2000</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(x);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> y = <span class="keyword">await</span> delayResolve(<span class="number">1</span>, <span class="number">1000</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(y);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">main();</span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h3 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h3><p><code>async/await</code> 的错误处理通过是通过 <code>try..catch</code> 来捕获错误。当然,我们也会根据实际业务的需求来 catch 我们真正需要处理的问题。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> response = <span class="keyword">await</span> axios.get(<span class="string">'https://example.com/user'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 处理响应数据</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'User data fetched:'</span>, response.data);</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'Error response:'</span>, error.response);</span><br><span class="line"> <span class="comment">// 做其他错误处理</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>学习了前文的基础概念后,我们可以更近一步的探讨 Promise 的使用。</p><h2 id="Promise-串联"><a href="#Promise-串联" class="headerlink" title="Promise 串联"></a>Promise 串联</h2><p>Promise 串联一般是指多个 Promise 操作按顺序执行,其中每个操作的开始通常依赖于前一个操作的完成。这种串行执行的一个典型场景是在第一个异步操作完成后,其结果被用作第二个异步操作的输入,依此类推。</p><p>考虑以下场景:加载系统时,需要优先读取用户数据,同时需要用户的数据去读取用户的订单的信息,再需要两者信息生成用户报告。因此这是一个存在前后依赖的场景。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fetchUserInfo</span>(<span class="params">userId</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> axios.get(<span class="string">`/api/users/<span class="subst">${userId}</span>`</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fetchUserOrders</span>(<span class="params">userId</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> axios.get(<span class="string">`/api/orders/<span class="subst">${userId}</span>`</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">generateReport</span>(<span class="params">userInfo, orders</span>) </span>{</span><br><span class="line"> <span class="comment">// 根据用户信息和订单生成报告</span></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> userName: userInfo.name,</span><br><span class="line"> totalOrders: orders.length,</span><br><span class="line"> <span class="comment">// ...其他报告数据</span></span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="常规处理方法"><a href="#常规处理方法" class="headerlink" title="常规处理方法"></a>常规处理方法</h3><p>处理串联请求无非有两种方法:</p><p><strong>方法 1: 链式调用 <code>.then()</code></strong></p><p>在这种方法中,我们利用 <code>.then()</code> 的链式调用来处理每个异步任务。这种方式的优点是每个步骤都明确且连贯,但可能导致所谓的“回调地狱”,尤其是在处理多个串联的异步操作时。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> userId = <span class="string">'12345'</span>; <span class="comment">// 假设已知的用户ID</span></span><br><span class="line"></span><br><span class="line">fetchUserInfo(userId)</span><br><span class="line"> .then(<span class="function"><span class="params">response</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> userInfo = response.data;</span><br><span class="line"> <span class="keyword">return</span> fetchUserOrders(userInfo.id); <span class="comment">// 使用用户ID获取订单</span></span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function"><span class="params">response</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> orders = response.data;</span><br><span class="line"> <span class="keyword">return</span> generateReport(userInfo, orders); <span class="comment">// 生成报告</span></span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function"><span class="params">report</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'用户报告:'</span>, report);</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'在处理请求时发生错误:'</span>, error);</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p><strong>方法 2: 使用 <code>async/await</code></strong></p><p><code>async/await</code> 提供了一种更加直观、类似同步的方式来处理异步操作。它使代码更易于阅读和维护,特别是在处理复杂的异步逻辑时。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">getUserReport</span>(<span class="params">userId</span>) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> userInfoResponse = <span class="keyword">await</span> fetchUserInfo(userId);</span><br><span class="line"> <span class="keyword">const</span> userInfo = userInfoResponse.data;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> userOrdersResponse = <span class="keyword">await</span> fetchUserOrders(userInfo.id);</span><br><span class="line"> <span class="keyword">const</span> orders = userOrdersResponse.data;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> report = generateReport(userInfo, orders);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'用户报告:'</span>, report);</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'在处理请求时发生错误:'</span>, error);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> userId = <span class="string">'12345'</span>; <span class="comment">// 假设已知的用户ID</span></span><br><span class="line">getUserReport(userId);</span><br></pre></td></tr></table></figure><p>在这个示例中,使用 <code>async/await</code> 使得代码的逻辑更加清晰和直观,减少了代码的嵌套深度,使错误处理变得简单。</p><h3 id="串联自动化"><a href="#串联自动化" class="headerlink" title="串联自动化"></a>串联自动化</h3><p>以上是日常工作中最常见的需求.但这里我们还可以发散一下思维,考虑更复杂的情况:</p><p>现在有一个数组,数组内有 10 个或更多的异步函数,每个函数都依赖前一个异步函数的返回值需要做处理。在这种请求多了的特殊情况下我们手动维护会显得很冗余,因此可以通过循环来简化逻辑:</p><p><strong>方法 1: 通过数组方法 reduce 组合</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> processFunctions = [processStep1, processStep2, processStep3, ...];</span><br><span class="line"></span><br><span class="line">processFunctions.reduce(<span class="function">(<span class="params">previousPromise, currentFunction</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> previousPromise.then(<span class="function"><span class="params">result</span> =></span> currentFunction(result));</span><br><span class="line">}, <span class="built_in">Promise</span>.resolve(initialValue))</span><br><span class="line">.then(<span class="function"><span class="params">finalResult</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'最终结果:'</span>, finalResult);</span><br><span class="line">})</span><br><span class="line">.catch(<span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'处理过程中发生错误:'</span>, error);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p><strong>方法 2: 循环体和 async/await 的结合</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">handleSequentialTasks</span>(<span class="params">tasks, initialResult</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> result = initialResult;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> task <span class="keyword">of</span> tasks) {</span><br><span class="line"> result = <span class="keyword">await</span> task(result);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'最终结果:'</span>, result);</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'处理过程中发生错误:'</span>, error);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> tasks = [task1, task2, task3, ...];</span><br><span class="line">handleSequentialTasks(tasks, initialValue);</span><br></pre></td></tr></table></figure><h2 id="Promise-并发"><a href="#Promise-并发" class="headerlink" title="Promise 并发"></a>Promise 并发</h2><p>并发(Concurrency)在编程中是指多个任务在同一时间段内启动、执行,但不一定同时完成。在 JavaScript 的 Promise 中,并发通常涉及同时开始多个异步操作,并根据它们何时解决(fulfilled)或被拒绝(rejected)来进行相应的处理。</p><p>Promise 的并发会比串联的场景更复杂。Promise 对象提供了几个静态方法来处理并发情况,让开发者可以根据不同的使用场景选择合适的方法:</p><p><strong>Promise.all(iterable)</strong></p><p><code>Promise.all</code> 静态方法接受一个 Promise 可迭代对象作为输入,当传入的数组中每个都被 resolve 后返回一个 <code>Promise</code>。若任意一个 Promise 被 reject 后就 reject。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise1 = fetch(<span class="string">'https://example.com/api/data1'</span>);</span><br><span class="line"><span class="keyword">const</span> promise2 = fetch(<span class="string">'https://example.com/api/data2'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.all([promise1, promise2])</span><br><span class="line"> .then(<span class="function">(<span class="params">[data1, data2]</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'所有数据已加载:'</span>, data1, data2);</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'加载数据时发生错误:'</span>, error);</span><br><span class="line"> });</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>Promise.allSettled(iterable)</strong></p><p><code>Promise.allSettled</code> 方法同样接受一个 Promise 的可迭代对象。不同于 <code>Promise.all</code>,这个方法等待所有传入的 Promise 都被解决(无论是 fulfilled 或 rejected),然后返回一个 <code>Promise</code>,它解决为一个数组,每个数组元素代表对应的 Promise 的结果。这使得无论成功还是失败,你都可以得到每个 Promise 的结果。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.allSettled([</span><br><span class="line"> <span class="built_in">Promise</span>.resolve(<span class="number">33</span>),</span><br><span class="line"> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>) =></span> <span class="built_in">setTimeout</span>(<span class="function">() =></span> resolve(<span class="number">66</span>), <span class="number">0</span>)),</span><br><span class="line"> <span class="number">99</span>,</span><br><span class="line"> <span class="built_in">Promise</span>.reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"an error"</span>)),</span><br><span class="line">]).then(<span class="function">(<span class="params">values</span>) =></span> <span class="built_in">console</span>.log(values));</span><br><span class="line"></span><br><span class="line"><span class="comment">// [</span></span><br><span class="line"><span class="comment">// { status: 'fulfilled', value: 33 },</span></span><br><span class="line"><span class="comment">// { status: 'fulfilled', value: 66 },</span></span><br><span class="line"><span class="comment">// { status: 'fulfilled', value: 99 },</span></span><br><span class="line"><span class="comment">// { status: 'rejected', reason: Error: an error }</span></span><br><span class="line"><span class="comment">// ]</span></span><br></pre></td></tr></table></figure><p><strong>Promise.race(iterable)</strong></p><p><code>Promise.race</code> 方法接受一个 Promise 的可迭代对象,但与 <code>Promise.all</code> 和 <code>Promise.allSettled</code> 不同,它不等待所有的 Promise 都被解决。相反,<code>Promise.race</code> 返回一个 <code>Promise</code>,它解决或被拒绝取决于传入的迭代对象中哪个 Promise 最先解决或被拒绝。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sleep</span>(<span class="params">time, value, state</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">if</span> (state === <span class="string">"fulfill"</span>) {</span><br><span class="line"> <span class="keyword">return</span> resolve(value);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> reject(<span class="keyword">new</span> <span class="built_in">Error</span>(value));</span><br><span class="line"> }</span><br><span class="line"> }, time);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p1 = sleep(<span class="number">500</span>, <span class="string">"one"</span>, <span class="string">"fulfill"</span>);</span><br><span class="line"><span class="keyword">const</span> p2 = sleep(<span class="number">100</span>, <span class="string">"two"</span>, <span class="string">"fulfill"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.race([p1, p2]).then(<span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(value); <span class="comment">// "two"</span></span><br><span class="line"> <span class="comment">// Both fulfill, but p2 is faster</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p3 = sleep(<span class="number">100</span>, <span class="string">"three"</span>, <span class="string">"fulfill"</span>);</span><br><span class="line"><span class="keyword">const</span> p4 = sleep(<span class="number">500</span>, <span class="string">"four"</span>, <span class="string">"reject"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.race([p3, p4]).then(</span><br><span class="line"> (value) => {</span><br><span class="line"> <span class="built_in">console</span>.log(value); <span class="comment">// "three"</span></span><br><span class="line"> <span class="comment">// p3 is faster, so it fulfills</span></span><br><span class="line"> },</span><br><span class="line"> (error) => {</span><br><span class="line"> <span class="comment">// Not called</span></span><br><span class="line"> },</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p5 = sleep(<span class="number">500</span>, <span class="string">"five"</span>, <span class="string">"fulfill"</span>);</span><br><span class="line"><span class="keyword">const</span> p6 = sleep(<span class="number">100</span>, <span class="string">"six"</span>, <span class="string">"reject"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.race([p5, p6]).then(</span><br><span class="line"> (value) => {</span><br><span class="line"> <span class="comment">// Not called</span></span><br><span class="line"> },</span><br><span class="line"> (error) => {</span><br><span class="line"> <span class="built_in">console</span>.error(error.message); <span class="comment">// "six"</span></span><br><span class="line"> <span class="comment">// p6 is faster, so it rejects</span></span><br><span class="line"> },</span><br><span class="line">);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>Promise.any(iterable)</strong></p><p><code>Promise.any</code> 接受一个 Promise 的可迭代对象,并返回一个 <code>Promise</code>。它解决为迭代对象中第一个被解决的 Promise 的结果。如果所有的 Promise 都被拒绝,<code>Promise.any</code> 会返回一个 <code>AggregateError</code> 实例。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(resolve, <span class="number">500</span>, <span class="string">"one"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> promise2 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(reject, <span class="number">100</span>, <span class="string">"two"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.race([promise1, promise2])</span><br><span class="line"> .then(<span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"succeeded with value:"</span>, value);</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function">(<span class="params">reason</span>) =></span> {</span><br><span class="line"> <span class="comment">// Only promise1 is fulfilled, but promise2 is faster</span></span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"failed with reason:"</span>, reason);</span><br><span class="line"> });</span><br><span class="line"><span class="comment">// failed with reason: two</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="控制批次"><a href="#控制批次" class="headerlink" title="控制批次"></a>控制批次</h3><p>JavaScript 默认提供的并发处理函数很方便我们根据业务场景的不同来处理请求,但显然我们工作中所遇到的需求得考虑更复杂的情况,还需要进一步的封装和扩展我们的 API。</p><p>在服务器端编程,我们经常遇到需要批量处理数据的场景。例如,批量修改数据库中的用户数据。在这种情况下,由于数据库操作的性能限制或者 API 调用限制,我们不能直接一口气修改全部,因为短时间内发出太多的请求数据库也会处理不来导致应用性能下降。因此,我们需要一种方法来限制同时进行的操作任务数,以保证程序的效率和稳定性。</p><p>我们代入实际业务场景:假设有一个社区组织了一次大型户外活动,活动吸引了大量参与者进行在线报名和付费。由于突发情况(比如恶劣天气或其他不可抗力因素),活动不得不取消。这时,组织者需要对所有已付费的参与者进行退款。</p><p>活动组织者发起「解散活动」后,服务端接收到请求后当然也不能一次性全部执行退款的操作啦,毕竟一场活动说不定有上千人。因此我们需要分批次去处理。</p><p>在上述社区活动退款的例子中,服务器端处理退款请求的一个有效方法是实施分批次并发控制。这种方法不仅保护了后端服务免受过载,还确保了整个退款过程的可管理性和可靠性。</p><p>分批次处理时有以下关键问题需要考虑:</p><ol><li><strong>批次大小</strong>:确定每个批次中处理的退款请求数量。这个数字应基于服务器的处理能力和支付网关的限制来确定。</li><li><strong>批次间隔</strong>:设置每个批次之间的时间间隔。这有助于避免短时间内发出过多请求,从而减轻对数据库和支付网关的压力。</li><li><strong>错误处理</strong>:在处理退款请求时,应妥善处理可能发生的错误,并确保能够重新尝试失败的退款操作。</li></ol><h3 id="简易版并发控制"><a href="#简易版并发控制" class="headerlink" title="简易版并发控制"></a>简易版并发控制</h3><p>将所有待处理的异步任务(如退款请求)存放在一个 <code>tasks</code> 数组中,在调用并发请求前将 <code>tasks</code> 数组分割成多个小批次,每个批次包含固定数量的请求。每当前一个批次处理完后,才处理下一个批次的请求,直到所有批次的请求都被处理完毕:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设这些是返回 Promise 的函数</span></span><br><span class="line"><span class="keyword">const</span> tasks = [task1, task2, task3, ...];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 分割任务数组为批次</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">splitIntoBatches</span>(<span class="params">tasks, batchSize</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> batches = [];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < tasks.length; i += batchSize) {</span><br><span class="line"> batches.push(tasks.slice(i, i + batchSize));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> batches;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理单个批次的函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processBatch</span>(<span class="params">batch</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Promise</span>.all(batch.map(<span class="function"><span class="params">task</span> =></span> task()));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 控制并发的主函数</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">processTasksInBatches</span>(<span class="params">tasks, batchSize</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> batches = splitIntoBatches(tasks, batchSize);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> batch <span class="keyword">of</span> batches) {</span><br><span class="line"> <span class="keyword">await</span> processBatch(batch);</span><br><span class="line"> <span class="comment">// 可以在这里加入日志记录或其他处理</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'一个批次处理完毕'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'所有批次处理完毕'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用主函数,假设每批次处理 10 个任务</span></span><br><span class="line">processTasksInBatches(tasks, <span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>这种写法实现的并发简单易懂,也易于维护,在一些并发压力不大,比较简单的业务场景来看是足够了。</p><p>但如果我们将这种处理方式放在时序图上进行分析,就能发现服务器可能有能力处理更多的并发任务,而这种方法可能没有充分利用可用资源。每个批次开始前会依赖于上一个批次中请求响应时间最慢的那一个,因此我们还可以进一步考虑优化并发实现方案。</p><h3 id="动态任务队列"><a href="#动态任务队列" class="headerlink" title="动态任务队列"></a>动态任务队列</h3><p>在之前的 “控制批次” 方法中,我们发现固定处理批次的局限性,尤其是在并发任务数量较大时可能导致的资源利用不足。为了解决这个问题,我们可以考虑采用一种更灵活的方法:维护一个动态的任务队列来处理异步请求:</p><ul><li><strong>任务队列</strong>:创建一个任务队列,其中包含所有待处理的异步任务。</li><li><strong>动态出队和入队</strong>:当队列中的任务完成时,它会被移出队列,同时根据当前的系统负载和任务处理能力,从待处理任务列表中拉取新的任务进入队列。</li><li><strong>并发数控制</strong>:设置一个最大并发数,确保任何时候处理中的任务数量不会超过这个限制。</li></ul><p>我们封装一个函数,提供 <code>concurrency</code> 参数作为并发限制:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parallelLimit</span>(<span class="params">tasks, {concurrency = <span class="number">10</span>}</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> results = [];</span><br><span class="line"> <span class="keyword">const</span> executing = <span class="keyword">new</span> <span class="built_in">Set</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> currentlyRunning = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">let</span> currentIndex = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> next = <span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">if</span> (currentIndex < tasks.length) {</span><br><span class="line"> <span class="comment">// 取出记录数,准备执行</span></span><br><span class="line"> <span class="keyword">const</span> index = currentIndex;</span><br><span class="line"> <span class="keyword">const</span> task = tasks[index];</span><br><span class="line"></span><br><span class="line"> currentIndex += <span class="number">1</span></span><br><span class="line"> currentlyRunning += <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> resultPromise = task().then(<span class="function">(<span class="params">result</span>) =></span> {</span><br><span class="line"> <span class="comment">// 任务执行完毕,更新运行数、保存结果</span></span><br><span class="line"> currentlyRunning -= <span class="number">1</span>;</span><br><span class="line"> results[index] = result;</span><br><span class="line"> executing.delete(resultPromise);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 开启下一个任务</span></span><br><span class="line"> next();</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> executing.add(resultPromise);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 当前运行的任务数小于限制并且还有任务未开始时,继续添加任务</span></span><br><span class="line"> <span class="keyword">if</span> (currentlyRunning < concurrency && currentIndex < tasks.length) {</span><br><span class="line"> next();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (currentlyRunning === <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 所有任务都已完成</span></span><br><span class="line"> resolve(results);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 初始化</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="built_in">Math</span>.min(concurrency, tasks.length); i += <span class="number">1</span>) {</span><br><span class="line"> next();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>该函数会在初始阶段会按照并发数先同步执行指定任务数,若某个任务执行完毕后,在执行完毕的回调中会唤醒下一个任务,直至任务队列执行完毕。</p><p>以下添加一些测试数据用于测试:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">asyncTask</span>(<span class="params">id</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`任务 <span class="subst">${id}</span> 完成`</span>);</span><br><span class="line"> resolve(<span class="string">`结果 <span class="subst">${id}</span>`</span>);</span><br><span class="line"> }, <span class="built_in">Math</span>.random() * <span class="number">2000</span>);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> taskArray = <span class="built_in">Array</span>.from({ <span class="attr">length</span>: <span class="number">10</span> }, <span class="function">(<span class="params">_, i</span>) =></span> <span class="function">() =></span> asyncTask(i + <span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">parallelLimit(taskArray, {<span class="attr">concurrency</span>: <span class="number">3</span>}).then(<span class="function">(<span class="params">results</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'所有任务完成:'</span>, results);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="第三方库"><a href="#第三方库" class="headerlink" title="第三方库"></a>第三方库</h3><p>在实际的项目开发中,特别是面临复杂的并发处理需求时,我们更多会考虑使用成熟的第三库来处理业务问题,它们具备更完善的测试用例来检验边界情况。</p><p>处理并发的热门库有 <code>RxJS</code>、<code>p-map</code> 和 <code>async.js</code>。</p><ul><li><code>RxJS</code> 是一个以响应式编程为核心的库,竟然搭配 <code>Angular</code> 在网页端搭配使用,提供了丰富的操作符和方法来处理异步事件和数据流。</li><li><code>p-map</code> 和 <code>async.js</code> 包的体积更小,更适合在服务端中使用。<code>p-map</code> 专注于提供并发控制功能,而 <code>async.js</code> 提供包括并发控制、队列管理等广泛的异步处理模式,功能会更全。</li></ul><p>笔者在 <code>Node.js</code> 环境下只需要处理并发问题,故用的 <code>p-map</code> 会更多一些。下面简要介绍 <code>p-map</code> 的使用:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pMap <span class="keyword">from</span> <span class="string">'p-map'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> list = <span class="built_in">Array</span>.from({ <span class="attr">length</span>: <span class="number">10</span> }, <span class="function">(<span class="params">_, i</span>) =></span> i)</span><br><span class="line"></span><br><span class="line">pMap(list, asyncTask, { <span class="attr">concurrency</span>: <span class="number">3</span> })</span><br><span class="line"> .then(<span class="function">(<span class="params">results</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'所有任务完成:'</span>, results);</span><br><span class="line"> });</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>p-map</code> 的<a href="https://github.com/sindresorhus/p-map/blob/main/index.js">源码</a>实现很精简,建议想深入复习并发的同学去阅读其底层代码的实现作为参考思路。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在本文中,我们首先回顾了 Promise 的基本概念及其在 JavaScript 异步编程中的常用方法。通过这个基础,我们能够更好地理解如何有效地处理和组织异步代码。</p><p>随后,我们深入到并发处理的实际应用场景,探讨了如何根据具体需求选择合适的并发实现策略。我们讨论了从简单的批次控制到更复杂的动态任务队列的不同方法,展示了在不同场景下优化异步任务处理的多种可能性。</p><p>但值得注意的是,我们自行实现的并发控制工具在没有做足测试用例测试时,可能不适合直接应用于生产环境。在实际的项目开发中,选择成熟且持续维护的第三方库往往是更安全和高效的选择。比如笔者选择的 <code>p-map</code> 稳定性和可靠性相比上文简单实现的版本将会更好。</p><hr><p>参考资料</p><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function">MDN | async 函数</a></li><li><a href="https://github.com/sindresorhus/p-map">GitHub | p-map</a></li></ul>]]></content>
<summary type="html"><p>Promise 是 JavaScript 中的一个重要概念,与前端的工作更是息息相关。因此本文将整理一下 Promise 在日常工作中的应用。</p>
<p><strong>目录</strong></p>
<ul>
<li><a href="#%E6%A6%82%E5%BF%B5">概念</a></li>
<li><a href="#asyncawait">async/await</a><ul>
<li><a href="#await">await</a></li>
<li><a href="#%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86">错误处理</a></li>
</ul>
</li>
<li><a href="#promise-%E4%B8%B2%E8%81%94">Promise 串联</a><ul>
<li><a href="#%E5%B8%B8%E8%A7%84%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95">常规处理方法</a></li>
<li><a href="#%E4%B8%B2%E8%81%94%E8%87%AA%E5%8A%A8%E5%8C%96">串联自动化</a></li>
</ul>
</li>
<li><a href="#promise-%E5%B9%B6%E5%8F%91">Promise 并发</a><ul>
<li><a href="#%E6%8E%A7%E5%88%B6%E6%89%B9%E6%AC%A1">控制批次</a></li>
<li><a href="#%E7%AE%80%E6%98%93%E7%89%88%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6">简易版并发控制</a></li>
<li><a href="#%E5%8A%A8%E6%80%81%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97">动态任务队列</a></li>
<li><a href="#%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93">第三方库</a></li>
</ul>
</li>
<li><a href="#%E6%80%BB%E7%BB%93">总结</a></li>
</ul></summary>
<category term="JavaScript" scheme="https://anran758.github.io/blog/categories/JavaScript/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="Promise" scheme="https://anran758.github.io/blog/tags/Promise/"/>
<category term="并发控制" scheme="https://anran758.github.io/blog/tags/%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/"/>
</entry>
<entry>
<title>数据结构实践</title>
<link href="https://anran758.github.io/blog/2022/05/18/note-02331/"/>
<id>https://anran758.github.io/blog/2022/05/18/note-02331/</id>
<published>2022-05-18T15:18:13.000Z</published>
<updated>2023-12-13T03:46:34.430Z</updated>
<content type="html"><![CDATA[<p>本篇将根据自考实践要求对「数据结构」一科进行简要的复习,代码实现使用 <code>C++</code> 语言实现。</p><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>已知 Q 是一个非空队列,S 是一个空栈。编写算法,仅用队列和栈的 ADT 函数和少量工作变量,将队列 Q 的所有元素逆置。</p><p>栈的基本 ADT 函数有:</p><ol><li>置空栈。函数原型为: <code>void MakeEmpty(SqStack s);</code></li><li>元素e入栈。函数原型为: <code>void Push(SqStack s,ElemType e);</code></li><li>出栈,返回栈顶元素。函数原型为: <code>ElemType pop(SqStack s);</code></li><li>判断栈是否为空。函数原型为: <code>int isEmpty(SqStack s);</code></li></ol><p>队列的基本ADT函数有:</p><ol><li>元素e入队。函数原型为:void enQueue(Queue q,ElemType e);</li><li>出队,返回队头元素。函数原型为:ElemType deQueue(Queue q);(3)(3)判断队是否为空。函数原型为:int isEmpty(Queue q);</li></ol><p>题目要求:</p><ol><li>编程实现队列和栈的ADT函数</li><li>仅用队列和栈的ADT函数和少量工作变量,编写将队列Q的所有元素逆置的函数</li><li>测试该函数</li></ol><span id="more"></span><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 栈的基本 ADT 函数有:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 置空栈。函数原型为: `void MakeEmpty(SqStack s);`</span></span><br><span class="line"><span class="comment">// 2. 元素e入栈。函数原型为: `void Push(SqStack s,ElemType e);`</span></span><br><span class="line"><span class="comment">// 3. 出栈,返回栈顶元素。函数原型为: `ElemType pop(SqStack s);`</span></span><br><span class="line"><span class="comment">// 4. 判断栈是否为空。函数原型为: `int isEmpty(SqStack s);`</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> StackSize 10</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">int</span> ElemType;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 栈结构</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SqStack</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> ElemType data[StackSize];</span><br><span class="line"> <span class="keyword">int</span> top;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">SqStack</span>(): <span class="built_in">top</span>(<span class="number">-1</span>) {}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 1. 置空栈</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">makeEmpty</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>->top = <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. 元素e入栈</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(ElemType e)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>-><span class="built_in">isFull</span>()) {</span><br><span class="line"> std::cout << <span class="string">"栈满"</span> << std::endl;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>->data[++<span class="keyword">this</span>->top] = e;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 出栈,返回栈顶元素</span></span><br><span class="line"> <span class="function">ElemType <span class="title">pop</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>-><span class="built_in">isEmpty</span>()) {</span><br><span class="line"> std::cout << <span class="string">"栈空"</span> << std::endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>->data[<span class="keyword">this</span>->top--];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4. 判断栈是否为空</span></span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">isEmpty</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>->top == <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 5. 栈满</span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">isFull</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>->top == StackSize;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 队列的基本ADT函数有:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// (1)元素e入队。函数原型为:void enQueue(Queue q,ElemType e);</span></span><br><span class="line"><span class="comment">// (2)出队,返回队头元素。函数原型为:ElemType deQueue(Queue q);(</span></span><br><span class="line"><span class="comment">// (3)判断队是否为空。函数原型为:int isEmpty(Queue q);</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> QueueSize 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 队列结构</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Queue</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> ElemType data[QueueSize];</span><br><span class="line"> <span class="keyword">int</span> front, real;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Queue</span>(): <span class="built_in">front</span>(<span class="number">0</span>), <span class="built_in">real</span>(<span class="number">0</span>) {}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 队列是否已满</span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">isQueueFull</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">this</span>->real + <span class="number">1</span>) % QueueSize == <span class="keyword">this</span>->front;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 元素e入队</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">enQueue</span><span class="params">(ElemType e)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">isQueueFull</span>()) {</span><br><span class="line"> std::cout << <span class="string">"队列满"</span> << std::endl;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>->data[<span class="keyword">this</span>->real] = e;</span><br><span class="line"> <span class="comment">// 循环意义下的 +1</span></span><br><span class="line"> <span class="keyword">this</span>->real = (<span class="keyword">this</span>->real + <span class="number">1</span>) % QueueSize;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 出队列</span></span><br><span class="line"> <span class="function">ElemType <span class="title">deQueue</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>-><span class="built_in">isEmpty</span>()) {</span><br><span class="line"> std::cout << <span class="string">"队列空"</span> << std::endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ElemType e = <span class="keyword">this</span>->data[<span class="keyword">this</span>->front];</span><br><span class="line"> <span class="keyword">this</span>->front = (<span class="keyword">this</span>->front + <span class="number">1</span>) % QueueSize;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 判断队列是否为空</span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">isEmpty</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>->front == <span class="keyword">this</span>->real;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 队列元素倒序, 这里注意要用 & 取引用才有副作用</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">reverseQueue</span><span class="params">(Queue &q)</span> </span>{</span><br><span class="line"> SqStack s;</span><br><span class="line"> <span class="keyword">int</span> val;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (!q.<span class="built_in">isEmpty</span>()) {</span><br><span class="line"> val = q.<span class="built_in">deQueue</span>();</span><br><span class="line"> s.<span class="built_in">push</span>(val);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (!s.<span class="built_in">isEmpty</span>()) {</span><br><span class="line"> val = s.<span class="built_in">pop</span>();</span><br><span class="line"> q.<span class="built_in">enQueue</span>(val);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// (1) 编程实现队列和栈的ADT函数</span></span><br><span class="line"><span class="comment">// (2) 仅用队列和栈的ADT函数和少量工作变量,编写将队列Q的所有元素逆置的函数。</span></span><br><span class="line"><span class="comment">// (3) 测试该函数</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"准备测试 stack 数据结构"</span> << endl;</span><br><span class="line"> SqStack s;</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"[stack] 1. test SqStack.push"</span> << endl;</span><br><span class="line"> <span class="keyword">int</span> testData1[] = {<span class="number">109</span>, <span class="number">108</span>, <span class="number">107</span>};</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> s.<span class="built_in">push</span>(testData1[i]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"[stack] 2. test SqStack.isEmpty: "</span> << (s.<span class="built_in">isEmpty</span>() ? <span class="string">""</span> : <span class="string">"非"</span>) << <span class="string">"空栈"</span> << endl;</span><br><span class="line"> cout << <span class="string">"[stack] 3. test SqStack.pop: "</span> << s.<span class="built_in">pop</span>() << endl;</span><br><span class="line"> cout << <span class="string">"[stack] 4. test SqStack.makeEmpty"</span> << endl;</span><br><span class="line"> s.<span class="built_in">makeEmpty</span>();</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"[stack] 5. check stack now is empty: "</span> << (s.<span class="built_in">isEmpty</span>() ? <span class="string">""</span> : <span class="string">"非"</span>) << <span class="string">"空栈"</span> << endl;</span><br><span class="line"> cout << <span class="string">"============================================"</span> << endl;</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"准备测试 queue 数据结构"</span> << endl;</span><br><span class="line"> Queue q;</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"[queue] 1. test SqStack.push"</span> << endl;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> q.<span class="built_in">enQueue</span>(testData1[i]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"[queue] 2. test Queue.isEmpty: "</span> << (q.<span class="built_in">isEmpty</span>() ? <span class="string">"空队列"</span> : <span class="string">"非空队列"</span>) << endl;</span><br><span class="line"> <span class="keyword">while</span> (!q.<span class="built_in">isEmpty</span>()) {</span><br><span class="line"> cout << <span class="string">"[queue] 3. test Queue.pop: "</span> << q.<span class="built_in">deQueue</span>() << endl;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"[queue] 4. check queue now is empty: "</span> << (q.<span class="built_in">isEmpty</span>() ? <span class="string">"空队列"</span> : <span class="string">"非空队列"</span>) << endl;</span><br><span class="line"> cout << endl << endl;</span><br><span class="line"> cout << <span class="string">"============================================"</span> << endl;</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"仅用队列和栈的ADT函数和少量工作变量,编写将队列Q的所有元素逆置的函数。"</span> << endl;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">int</span> reverseTestData[] = {<span class="number">11</span>,<span class="number">12</span>,<span class="number">13</span>,<span class="number">14</span>,<span class="number">15</span>};</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">5</span>; i++) {</span><br><span class="line"></span><br><span class="line"> q.<span class="built_in">enQueue</span>(reverseTestData[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">reverseQueue</span>(q);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(!q.<span class="built_in">isEmpty</span>()) {</span><br><span class="line"> cout << <span class="string">"reverseQueue deQueue: "</span> << q.<span class="built_in">deQueue</span>() << endl;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h2><h3 id="选择排序"><a href="#选择排序" class="headerlink" title="选择排序"></a>选择排序</h3><p>基本思想: 每一趟在待排序的记录中选出关键字最小的记录,依次存放在已排好序的记录序列的最后,直到全部排序完为止。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SelectSort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> k;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> k = i;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = i + <span class="number">1</span>; j < n; j++) {</span><br><span class="line"> <span class="keyword">if</span> (arr[j] < arr[k]) {</span><br><span class="line"> k = j;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (k != i) {</span><br><span class="line"> <span class="built_in">swap</span>(arr[i], arr[k]);</span><br><span class="line"> <span class="keyword">int</span> temp = arr[i];</span><br><span class="line"> arr[i] = arr[k];</span><br><span class="line"> arr[k] = temp;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> arr[] = {<span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">9</span>, <span class="number">8</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">10</span>};</span><br><span class="line"> <span class="keyword">int</span> n = <span class="built_in"><span class="keyword">sizeof</span></span>(arr) / <span class="built_in"><span class="keyword">sizeof</span></span>(arr[<span class="number">0</span>]);</span><br><span class="line"> cout << <span class="built_in"><span class="keyword">sizeof</span></span>(arr[<span class="number">0</span>]) << <span class="string">"\n"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">SelectSort</span>(arr, n);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> cout << arr[i] << <span class="string">" "</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="插入排序"><a href="#插入排序" class="headerlink" title="插入排序"></a>插入排序</h3><p>基本思想: 每次将一个待排序的记录按其关键字的大小插入到前面已经排序好的文件中的适当位置,直到全部记录插入完位置。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">InsertSort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> i, j, tmp;</span><br><span class="line"> <span class="comment">// 对顺序表做直接插入排序</span></span><br><span class="line"> <span class="keyword">for</span>(i = <span class="number">1</span>; i < n; i++) {</span><br><span class="line"> <span class="comment">// 当前值比上一个值小,则交换位置</span></span><br><span class="line"> <span class="keyword">if</span> (arr[i] < arr[i - <span class="number">1</span>]) {</span><br><span class="line"></span><br><span class="line"> tmp = arr[i];</span><br><span class="line"> <span class="comment">// 对有序区逐项向后 diff,寻找合适的插入位置</span></span><br><span class="line"> <span class="keyword">for</span>(j = i - <span class="number">1</span>; j >= <span class="number">0</span> && tmp < arr[j]; j--) {</span><br><span class="line"> arr[j + <span class="number">1</span>] = arr[j];</span><br><span class="line"> }</span><br><span class="line"> arr[j + <span class="number">1</span>] = tmp;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="冒泡排序"><a href="#冒泡排序" class="headerlink" title="冒泡排序"></a>冒泡排序</h3><p>冒泡排序的基本思想是:通过相邻元素之间的比较和交换,使娇小的元素逐渐从底部移向顶部,就像水底下气泡一样逐渐向上冒泡。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">BubbleSort</span><span class="params">(<span class="keyword">int</span> *arr, <span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> i, j, flag, temp;</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> flag = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从右向左对比</span></span><br><span class="line"> <span class="keyword">for</span> (j = n - <span class="number">1</span>; j >= i; j--) {</span><br><span class="line"> <span class="keyword">if</span> (arr[j] < arr[j - <span class="number">1</span>]) {</span><br><span class="line"> <span class="built_in">swap</span>(arr[j], arr[j - <span class="number">1</span>]);</span><br><span class="line"> <span class="comment">// temp = arr[j];</span></span><br><span class="line"> <span class="comment">// arr[j] = arr[j - 1];</span></span><br><span class="line"> <span class="comment">// arr[j - 1] = temp;</span></span><br><span class="line"> flag = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (flag == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>本篇将根据自考实践要求对「数据结构」一科进行简要的复习,代码实现使用 <code>C++</code> 语言实现。</p>
<h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>已知 Q 是一个非空队列,S 是一个空栈。编写算法,仅用队列和栈的 ADT 函数和少量工作变量,将队列 Q 的所有元素逆置。</p>
<p>栈的基本 ADT 函数有:</p>
<ol>
<li>置空栈。函数原型为: <code>void MakeEmpty(SqStack s);</code></li>
<li>元素e入栈。函数原型为: <code>void Push(SqStack s,ElemType e);</code></li>
<li>出栈,返回栈顶元素。函数原型为: <code>ElemType pop(SqStack s);</code></li>
<li>判断栈是否为空。函数原型为: <code>int isEmpty(SqStack s);</code></li>
</ol>
<p>队列的基本ADT函数有:</p>
<ol>
<li>元素e入队。函数原型为:void enQueue(Queue q,ElemType e);</li>
<li>出队,返回队头元素。函数原型为:ElemType deQueue(Queue q);(3)(3)判断队是否为空。函数原型为:int isEmpty(Queue q);</li>
</ol>
<p>题目要求:</p>
<ol>
<li>编程实现队列和栈的ADT函数</li>
<li>仅用队列和栈的ADT函数和少量工作变量,编写将队列Q的所有元素逆置的函数</li>
<li>测试该函数</li>
</ol></summary>
<category term="计算机科学与技术" scheme="https://anran758.github.io/blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E4%B8%8E%E6%8A%80%E6%9C%AF/"/>
<category term="C++" scheme="https://anran758.github.io/blog/tags/C/"/>
<category term="数据结构" scheme="https://anran758.github.io/blog/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>MySQL 实践</title>
<link href="https://anran758.github.io/blog/2022/05/16/mysql-note/"/>
<id>https://anran758.github.io/blog/2022/05/16/mysql-note/</id>
<published>2022-05-16T03:25:00.000Z</published>
<updated>2023-12-13T03:46:34.409Z</updated>
<content type="html"><![CDATA[<p>由于自考的实践考核要求有需要用到 mysql 进行考核,故记录一下在 mac 环境下试手的笔记。</p><span id="more"></span><h2 id="初始环境"><a href="#初始环境" class="headerlink" title="初始环境"></a>初始环境</h2><p>首先在 <a href="https://downloads.mysql.com/archives/community/">mysql</a> 官网中下载你想要的版本。可以直接下载 dmg 安装包,按照安装指示一步一步安装,并设置 mysql 的密码。</p><p>下载完毕后,一般情况下直接通过命令行使用 <code>mysql</code> 命令会找不到对应的命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">➜ ~ mysql -v</span><br><span class="line">zsh: <span class="built_in">command</span> not found: mysql</span><br></pre></td></tr></table></figure><p>因此需要对当前的命令行工具配置对应的环境变量,比如笔者使用的是 <code>zsh</code>,则打开 <code>~/.zshrc</code> 文件添加以下配置:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="variable">${PATH}</span>:/usr/<span class="built_in">local</span>/mysql/bin/</span><br></pre></td></tr></table></figure><p>若使用 <code>bash</code> 的用户同理,直接在 <code>~/.bashrc</code> 添加相同代码。添加完毕后通过 <code>source</code> 命令重新加载对应的环境变量: <code>source ~/.zshrc</code></p><p>接着就可以在命令行直接使用 <code>mysql</code> 了。输入 <code>mysql -u root -p</code> 登录 mysql,密码是在安装阶段时设置的密码。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">➜ ~ mysql -u root -p</span><br><span class="line">Enter password:</span><br><span class="line">Welcome to the MySQL monitor. Commands end with ; or \g.</span><br><span class="line">Your MySQL connection id is 13</span><br><span class="line">Server version: 8.0.29 MySQL Community Server - GPL</span><br><span class="line"></span><br><span class="line">Copyright (c) 2000, 2022, Oracle and/or its affiliates.</span><br><span class="line"></span><br><span class="line">Oracle is a registered trademark of Oracle Corporation and/or its</span><br><span class="line">affiliates. Other names may be trademarks of their respective</span><br><span class="line">owners.</span><br><span class="line"></span><br><span class="line">Type <span class="string">'help;'</span> or <span class="string">'\h'</span> <span class="keyword">for</span> <span class="built_in">help</span>. Type <span class="string">'\c'</span> to clear the current input statement.</span><br><span class="line"></span><br><span class="line">mysql></span><br></pre></td></tr></table></figure><h2 id="数据库操作"><a href="#数据库操作" class="headerlink" title="数据库操作"></a>数据库操作</h2><blockquote><p>DATABASE 可以不区分大小写,但只能要么全小写,要么全大写。一般会将这些参数用大写写出。</p></blockquote><h3 id="创建数据库"><a href="#创建数据库" class="headerlink" title="创建数据库"></a>创建数据库</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 还可以通过 DEFAULT CHARACTER SET 选项设置默认的编码集</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> DATABASE DANNY_DATABASE;</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.01</span> sec)</span><br></pre></td></tr></table></figure><h3 id="查看现有的数据库"><a href="#查看现有的数据库" class="headerlink" title="查看现有的数据库"></a>查看现有的数据库</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> DATABASES;</span><br><span class="line"><span class="operator">+</span><span class="comment">----------------------------+</span></span><br><span class="line"><span class="operator">|</span> Database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------------------+</span></span><br><span class="line"><span class="operator">|</span> information_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> DANNY_DATABASE <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> mysql <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> performance_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sys <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="切换到指定数据库"><a href="#切换到指定数据库" class="headerlink" title="切换到指定数据库"></a>切换到指定数据库</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> USE DANNY_DATABASE</span><br></pre></td></tr></table></figure><h3 id="数据库的查看与删除"><a href="#数据库的查看与删除" class="headerlink" title="数据库的查看与删除"></a>数据库的查看与删除</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建数据库: 准备稍后移除的数据库</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> DATABASE DANNY_DATABASE_WAIT_DELETE;</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.01</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> DATABASES;</span><br><span class="line"><span class="operator">+</span><span class="comment">----------------------------+</span></span><br><span class="line"><span class="operator">|</span> Database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------------------+</span></span><br><span class="line"><span class="operator">|</span> information_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> DANNY_DATABASE <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> DANNY_DATABASE_WAIT_DELETE <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> mysql <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> performance_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sys <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除数据库</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">DROP</span> DATABASE DANNY_DATABASE_WAIT_DELETE;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.02</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> DATABASES;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------+</span></span><br><span class="line"><span class="operator">|</span> Database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------+</span></span><br><span class="line"><span class="operator">|</span> information_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> DANNY_DATABASE <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> mysql <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> performance_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sys <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------+</span></span><br><span class="line"><span class="number">5</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="查看当前使用的数据库"><a href="#查看当前使用的数据库" class="headerlink" title="查看当前使用的数据库"></a>查看当前使用的数据库</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 未选择的情况下</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> DATABASE();</span><br><span class="line"><span class="operator">+</span><span class="comment">----------------+</span></span><br><span class="line"><span class="operator">|</span> DATABASE() <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">null</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 切换指定数据库</span></span><br><span class="line">use DANNY_DATABASE;</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> DATABASE();</span><br><span class="line"><span class="operator">+</span><span class="comment">----------------+</span></span><br><span class="line"><span class="operator">|</span> DATABASE() <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------+</span></span><br><span class="line"><span class="operator">|</span> danny_database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h2 id="数据表操作"><a href="#数据表操作" class="headerlink" title="数据表操作"></a>数据表操作</h2><h3 id="创建数据表"><a href="#创建数据表" class="headerlink" title="创建数据表"></a>创建数据表</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建名为 customers 的数据表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> <span class="keyword">TABLE</span> IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> customers(</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_id <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_name <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_sex <span class="type">CHAR</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_address <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_contact <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">PRIMARY</span> KEY(cust_id)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> );</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.11</span> sec)</span><br></pre></td></tr></table></figure><p>其中 <code>IF NOT EXISTS</code> 参数是可选的,它的意思为若 customers 表不存在则创建它。</p><h3 id="查看数据表与表列"><a href="#查看数据表与表列" class="headerlink" title="查看数据表与表列"></a>查看数据表与表列</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看当前用户在当前数据库中可以访问的数据表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> TABLES;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> Tables_in_danny_database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> customers <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="number">1</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看指定数据表中列的信息</span></span><br><span class="line"><span class="comment">-- DESC customers; 等价于如下命令</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> COLUMNS <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> Field <span class="operator">|</span> Type <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span> <span class="operator">|</span> Extra <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> PRI <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> auto_increment <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_name <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_sex <span class="operator">|</span> <span class="type">char</span>(<span class="number">1</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> <span class="number">0</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_address <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_contact <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="number">5</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="删除数据表"><a href="#删除数据表" class="headerlink" title="删除数据表"></a>删除数据表</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 添加一个数据表用于演示删除</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> <span class="keyword">TABLE</span> IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> customers_1(</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_id <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_name <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_sex <span class="type">CHAR</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_address <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> cust_contact <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">PRIMARY</span> KEY(cust_id)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> );</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.11</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看当前的数据表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> tables;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> Tables_in_danny_database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> customers <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> customers_1 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除指定数据表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">DROP</span> TABLES customers_1;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.02</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> tables;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> Tables_in_danny_database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> customers <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="数据表添加新列"><a href="#数据表添加新列" class="headerlink" title="数据表添加新列"></a>数据表添加新列</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 插入新列</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">alter</span> <span class="keyword">TABLE</span> customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">ADD</span> <span class="keyword">COLUMN</span> cust_city <span class="type">char</span>(<span class="number">10</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">'guangzhou'</span> AFTER cust_sex;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.06</span> sec)</span><br><span class="line">Records: <span class="number">0</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 确认表列状态</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> COLUMNS <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> Field <span class="operator">|</span> Type <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span> <span class="operator">|</span> Extra <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> PRI <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> auto_increment <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_name <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_sex <span class="operator">|</span> <span class="type">char</span>(<span class="number">1</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> <span class="number">0</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_city <span class="operator">|</span> <span class="type">char</span>(<span class="number">10</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> guangzhou <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_address <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_contact <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="数据表修改表列"><a href="#数据表修改表列" class="headerlink" title="数据表修改表列"></a>数据表修改表列</h3><p>修改整列: 将列名 cust_sex 修改 sex,并修改默认值</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">alter</span> <span class="keyword">TABLE</span> customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> CHANGE <span class="keyword">COLUMN</span> cust_sex sex <span class="type">char</span>(<span class="number">1</span>) <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">'M'</span>;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.04</span> sec)</span><br><span class="line">Records: <span class="number">0</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> COLUMNS <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> Field <span class="operator">|</span> Type <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span> <span class="operator">|</span> Extra <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> PRI <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> auto_increment <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_name <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sex <span class="operator">|</span> <span class="type">char</span>(<span class="number">1</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> M <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_city <span class="operator">|</span> <span class="type">char</span>(<span class="number">10</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> guangzhou <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_address <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_contact <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>仅修改列的类型</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">ALTER</span> <span class="keyword">TABLE</span> customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> MODIFY <span class="keyword">COLUMN</span> cust_address <span class="type">varchar</span>(<span class="number">50</span>);</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.06</span> sec)</span><br><span class="line">Records: <span class="number">0</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">show</span> COLUMNS <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> Field <span class="operator">|</span> Type <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span> <span class="operator">|</span> Extra <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> PRI <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> auto_increment <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_name <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sex <span class="operator">|</span> <span class="type">char</span>(<span class="number">1</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> M <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_city <span class="operator">|</span> <span class="type">char</span>(<span class="number">10</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> guangzhou <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_address <span class="operator">|</span> <span class="type">varchar</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_contact <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+-----------+----------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>修改指定列的指定字段</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">ALTER</span> <span class="keyword">TABLE</span> customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">ALTER</span> <span class="keyword">COLUMN</span> cust_city <span class="keyword">SET</span> <span class="keyword">DEFAULT</span> <span class="string">'shenzhen'</span>;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.03</span> sec)</span><br><span class="line">Records: <span class="number">0</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> COLUMNS <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> Field <span class="operator">|</span> Type <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span> <span class="operator">|</span> Extra <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> PRI <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> auto_increment <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_name <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sex <span class="operator">|</span> <span class="type">char</span>(<span class="number">1</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> M <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_city <span class="operator">|</span> <span class="type">char</span>(<span class="number">10</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> shenzhen <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_address <span class="operator">|</span> <span class="type">varchar</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_contact <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+----------+----------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>移除数据表列: 移除 cust_contact 数据表项</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">ALTER</span> <span class="keyword">TABLE</span> danny_database.customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">DROP</span> <span class="keyword">COLUMN</span> cust_contact;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.04</span> sec)</span><br><span class="line">Records: <span class="number">0</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> COLUMNS <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> Field <span class="operator">|</span> Type <span class="operator">|</span> <span class="keyword">Null</span> <span class="operator">|</span> Key <span class="operator">|</span> <span class="keyword">Default</span> <span class="operator">|</span> Extra <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+----------+----------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> <span class="type">int</span>(<span class="number">11</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> PRI <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> auto_increment <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_name <span class="operator">|</span> <span class="type">char</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sex <span class="operator">|</span> <span class="type">char</span>(<span class="number">1</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> M <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_city <span class="operator">|</span> <span class="type">char</span>(<span class="number">10</span>) <span class="operator">|</span> <span class="keyword">NO</span> <span class="operator">|</span> <span class="operator">|</span> shenzhen <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cust_address <span class="operator">|</span> <span class="type">varchar</span>(<span class="number">50</span>) <span class="operator">|</span> YES <span class="operator">|</span> <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------+-------------+------+-----+----------+----------------+</span></span><br><span class="line"><span class="number">5</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="数据项操作"><a href="#数据项操作" class="headerlink" title="数据项操作"></a>数据项操作</h2><h3 id="添加数据"><a href="#添加数据" class="headerlink" title="添加数据"></a>添加数据</h3><p>默认情况下在命令行中 mysql 是不能直接插入中文的,这个跟字符集有关。可输入下面命令修改数据库或表的字符集:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">-- 设置名为 danny_database 的数据库字符集</span></span><br><span class="line"><span class="keyword">ALTER</span> DATABASE danny_database <span class="type">character</span> <span class="keyword">SET</span> utf8;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 设置名为 customers 的数据库表字符集 (Tip: 若数据库已经被设置为 utf8, 则无需再设置表的字符集)</span></span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> customers <span class="keyword">convert</span> <span class="keyword">to</span> <span class="type">character</span> <span class="keyword">SET</span> utf8;</span><br></pre></td></tr></table></figure><p>为数据表插入数据,显式设置字段</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> danny_database.customers(cust_id, cust_name, sex, cust_address)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">VALUES</span>(<span class="number">901</span>, <span class="string">'张三'</span>, <span class="keyword">DEFAULT</span>, <span class="string">'广州市'</span>);</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.02</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> danny_database.customers(cust_id, cust_name, sex, cust_address) </span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">VALUES</span>(<span class="number">0</span>, <span class="string">'李四'</span>, <span class="keyword">DEFAULT</span>, <span class="string">'广州市'</span>);</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.01</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> sex <span class="operator">|</span> cust_city <span class="operator">|</span> cust_address <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>由于 cust_id 是自增的,因此可以将此字段的值设置为 0 或 NULL 会自动自增。上例 “李四” 的 cust_id 在创建后就被自增为 902。</p><p>还可以通过 <code>SET</code> 语句设置部分值:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> danny_database.customers <span class="keyword">SET</span> cust_name<span class="operator">=</span><span class="string">'王五'</span>, cust_address<span class="operator">=</span><span class="string">'武汉市'</span>, sex<span class="operator">=</span><span class="keyword">DEFAULT</span>;</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="查询数据"><a href="#查询数据" class="headerlink" title="查询数据"></a>查询数据</h3><p>可通过 <code>SELECT</code> 语句查询数据:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> sex <span class="operator">|</span> cust_city <span class="operator">|</span> cust_address <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">903</span> <span class="operator">|</span> 王五 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 武汉市 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>仅展示指定字段:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> sex <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> M <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> M <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">903</span> <span class="operator">|</span> 王五 <span class="operator">|</span> M <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>通过 <code>WHERE</code> 子句设置查询条件,筛选出符合查询条件的数据:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> cust_id,cust_name,cust_address <span class="keyword">FROM</span> customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">WHERE</span> cust_address<span class="operator">=</span>"广州市";</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> cust_address <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+--------------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="删除数据"><a href="#删除数据" class="headerlink" title="删除数据"></a>删除数据</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 添加几项测试数据</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> danny_database.customers(cust_id, cust_name, sex, cust_address) </span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">VALUES</span>(<span class="number">1</span>, <span class="string">'test1'</span>, <span class="keyword">DEFAULT</span>, <span class="string">'深圳市'</span>);</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.02</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> sex <span class="operator">|</span> cust_city <span class="operator">|</span> cust_address <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> test1 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 深圳市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">903</span> <span class="operator">|</span> 王五 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 武汉市 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="number">4</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除表数据</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">DELETE</span> <span class="keyword">FROM</span> customers</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">WHERE</span> cust_id<span class="operator">=</span><span class="number">1</span>;</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.02</span> sec)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> sex <span class="operator">|</span> cust_city <span class="operator">|</span> cust_address <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">903</span> <span class="operator">|</span> 王五 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 武汉市 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br></pre></td></tr></table></figure><h3 id="更新数据"><a href="#更新数据" class="headerlink" title="更新数据"></a>更新数据</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 更新数据</span></span><br><span class="line">mysql<span class="operator">></span> UPDATE customers <span class="keyword">SET</span> cust_address<span class="operator">=</span>"深圳市" <span class="keyword">WHERE</span> cust_name<span class="operator">=</span>"李四";</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.00</span> sec)</span><br><span class="line"><span class="keyword">Rows</span> matched: <span class="number">1</span> Changed: <span class="number">1</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> customers;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> cust_id <span class="operator">|</span> cust_name <span class="operator">|</span> sex <span class="operator">|</span> cust_city <span class="operator">|</span> cust_address <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">901</span> <span class="operator">|</span> 张三 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 广州市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">902</span> <span class="operator">|</span> 李四 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 深圳市 <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">903</span> <span class="operator">|</span> 王五 <span class="operator">|</span> M <span class="operator">|</span> shenzhen <span class="operator">|</span> 武汉市 <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+-----------+------+-----------+--------------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>以一个 eShop 的需求为例做个简单的测试吧。</p><h3 id="创建-eshop-数据库"><a href="#创建-eshop-数据库" class="headerlink" title="创建 eshop 数据库"></a>创建 eshop 数据库</h3><p>在 MySQL 中创建一个名为 eshop 的数据库,选择字符集为 utf8mb4</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> DATABASE IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> eshop <span class="keyword">DEFAULT</span> <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4;</span><br><span class="line">Query OK, <span class="number">1</span> <span class="type">row</span> affected (<span class="number">0.01</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SHOW</span> DATABASES;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------+</span></span><br><span class="line"><span class="operator">|</span> Database <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------+</span></span><br><span class="line"><span class="operator">|</span> information_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> DANNY_DATABASE <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> eshop <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> mysql <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> performance_schema <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> sys <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------+</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.01</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 切换数据库</span></span><br><span class="line">mysql<span class="operator">></span> use eshop;</span><br><span class="line">Database changed</span><br></pre></td></tr></table></figure><h3 id="创建数据表及相关记录"><a href="#创建数据表及相关记录" class="headerlink" title="创建数据表及相关记录"></a>创建数据表及相关记录</h3><p>相关表信息如下</p><p>表名:用户(t_user)</p><table><thead><tr><th>字段名</th><th>类型</th><th>大小</th></tr></thead><tbody><tr><td>用户ID (id)</td><td>自增类型</td><td></td></tr><tr><td>姓名 (user_name)</td><td>文本</td><td>50,非空</td></tr><tr><td>联系电话 (phone_no)</td><td>文本</td><td>20,非空</td></tr></tbody></table><p>表名:商品(product)</p><table><thead><tr><th>字段名</th><th>类型</th><th>大小</th></tr></thead><tbody><tr><td>商品ID(id)</td><td>自增类型</td><td></td></tr><tr><td>商品名称(product_name)</td><td>文本</td><td>50,非空</td></tr><tr><td>价格(price)</td><td>数值类型</td><td>(整数位9位,小数位2位),非空</td></tr></tbody></table><p>表名:购物车 (shopping_cart)</p><table><thead><tr><th>字段名</th><th>类型</th><th>大小</th></tr></thead><tbody><tr><td>用户id(user_id)</td><td>整数</td><td>非空,主键,参考用户表主键</td></tr><tr><td>商品id(product_id)</td><td>整数</td><td>非空,主键,参考商品表主键</td></tr><tr><td>商品数量(quantity)</td><td>整数</td><td>非空</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 用户表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> <span class="keyword">TABLE</span> IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> t_user(</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `id` <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT <span class="keyword">PRIMARY</span> KEY,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `user_name` <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `phone_no` <span class="type">CHAR</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span></span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> );</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.06</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 商品表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> <span class="keyword">TABLE</span> IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> product(</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `id` <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT <span class="keyword">PRIMARY</span> KEY,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `product_name` <span class="type">CHAR</span>(<span class="number">50</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `price` <span class="keyword">DOUBLE</span>(<span class="number">9</span>, <span class="number">2</span>)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> );</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.06</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 购物车</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">CREATE</span> <span class="keyword">TABLE</span> IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> shopping_cart(</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `user_id` <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `product_id` <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> `quantity` <span class="type">INT</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">PRIMARY</span> KEY(`user_id`, `product_id`)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> );</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.05</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看数据表</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">show</span> tables;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="operator">|</span> Tables_in_eshop <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="operator">|</span> product <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> shopping_cart <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> t_user <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="录入用户数据"><a href="#录入用户数据" class="headerlink" title="录入用户数据"></a>录入用户数据</h3><p>用户信息</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>;张三; <span class="number">13333333333</span>;</span><br><span class="line"><span class="number">2</span>;李四; <span class="number">13666666666</span></span><br><span class="line"><span class="number">3</span>;王五; <span class="number">13888888888</span></span><br><span class="line"><span class="number">4</span>;赵六; <span class="number">13999999999</span></span><br></pre></td></tr></table></figure><p>商品信息</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>; C<span class="operator">+</span><span class="operator">+</span>程序设计教程; <span class="number">45.5</span></span><br><span class="line"><span class="number">2</span>; 数据结构; <span class="number">33.7</span></span><br><span class="line"><span class="number">3</span>; 操作系统; <span class="number">51</span></span><br></pre></td></tr></table></figure><p>购物车</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>; <span class="number">1</span>; <span class="number">5</span></span><br><span class="line"><span class="number">1</span>; <span class="number">2</span>; <span class="number">3</span></span><br><span class="line"><span class="number">2</span>; <span class="number">3</span>; <span class="number">6</span></span><br><span class="line"><span class="number">2</span>; <span class="number">4</span>; <span class="number">8</span></span><br></pre></td></tr></table></figure><p>录入数据:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 插入用户表数据</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> t_user</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (id, user_name, phone_no)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">VALUES</span></span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">1</span>, <span class="string">'张三'</span>, <span class="string">'13333333333'</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">2</span>, <span class="string">'李四'</span>, <span class="string">'13666666666'</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">3</span>, <span class="string">'王五'</span>, <span class="string">'13888888888'</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">4</span>, <span class="string">'赵六'</span>, <span class="string">'13999999999'</span>);</span><br><span class="line">Query OK, <span class="number">4</span> <span class="keyword">rows</span> affected (<span class="number">0.02</span> sec)</span><br><span class="line">Records: <span class="number">4</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_user;</span><br><span class="line"><span class="operator">+</span><span class="comment">----+-----------+-------------+</span></span><br><span class="line"><span class="operator">|</span> id <span class="operator">|</span> user_name <span class="operator">|</span> phone_no <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-----------+-------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> 张三 <span class="operator">|</span> <span class="number">13333333333</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span> 李四 <span class="operator">|</span> <span class="number">13666666666</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span> 王五 <span class="operator">|</span> <span class="number">13888888888</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">4</span> <span class="operator">|</span> 赵六 <span class="operator">|</span> <span class="number">13999999999</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-----------+-------------+</span></span><br><span class="line"><span class="number">4</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入「商品信息」</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> product</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (id, product_name, price)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">VALUES</span></span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">1</span>, <span class="string">'C++程序设计教程'</span>, <span class="number">45.5</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">2</span>, <span class="string">'数据结构'</span>, <span class="number">33.7</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">3</span>, <span class="string">'操作系统'</span>, <span class="number">51</span>);</span><br><span class="line">Query OK, <span class="number">3</span> <span class="keyword">rows</span> affected (<span class="number">0.01</span> sec)</span><br><span class="line">Records: <span class="number">3</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> product;</span><br><span class="line"><span class="operator">+</span><span class="comment">----+-----------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> id <span class="operator">|</span> product_name <span class="operator">|</span> price <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-----------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> C<span class="operator">+</span><span class="operator">+</span>程序设计教程 <span class="operator">|</span> <span class="number">45.50</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span> 数据结构 <span class="operator">|</span> <span class="number">33.70</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span> 操作系统 <span class="operator">|</span> <span class="number">51.00</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-----------------------+-------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入购物车</span></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">INSERT</span> <span class="keyword">INTO</span> shopping_cart</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (user_id, product_id, quantity)</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">VALUES</span></span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">1</span>, <span class="number">1</span>, <span class="number">5</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">2</span>, <span class="number">3</span>, <span class="number">6</span>),</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> (<span class="number">2</span>, <span class="number">4</span>, <span class="number">8</span>);</span><br><span class="line">Query OK, <span class="number">4</span> <span class="keyword">rows</span> affected (<span class="number">0.01</span> sec)</span><br><span class="line">Records: <span class="number">4</span> Duplicates: <span class="number">0</span> Warnings: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> shopping_cart;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+------------+----------+</span></span><br><span class="line"><span class="operator">|</span> user_id <span class="operator">|</span> product_id <span class="operator">|</span> quantity <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+------------+----------+</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">5</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span> <span class="number">6</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span> <span class="number">4</span> <span class="operator">|</span> <span class="number">8</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+------------+----------+</span></span><br><span class="line"><span class="number">4</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h3 id="数据的查询与更新"><a href="#数据的查询与更新" class="headerlink" title="数据的查询与更新"></a>数据的查询与更新</h3><p>使用 SQL 语句列出「张三」购买商品清单信息,以<strong>购买数量</strong>升序排列:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> u.user_name, p.product_name, u.phone_no, p.price, s.quantity <span class="keyword">FROM</span> t_user u, product p, shopping_cart s</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">WHERE</span> u.user_name<span class="operator">=</span>"张三" <span class="keyword">AND</span> u.id <span class="operator">=</span> s.user_id <span class="keyword">AND</span> p.id <span class="operator">=</span> s.product_id </span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> quantity <span class="keyword">asc</span></span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> LIMIT <span class="number">100</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+-----------------------+-------------+-------+----------+</span></span><br><span class="line"><span class="operator">|</span> user_name <span class="operator">|</span> product_name <span class="operator">|</span> phone_no <span class="operator">|</span> price <span class="operator">|</span> quantity <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+-----------------------+-------------+-------+----------+</span></span><br><span class="line"><span class="operator">|</span> 张三 <span class="operator">|</span> 数据结构 <span class="operator">|</span> <span class="number">13333333333</span> <span class="operator">|</span> <span class="number">33.70</span> <span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> 张三 <span class="operator">|</span> C<span class="operator">+</span><span class="operator">+</span>程序设计教程 <span class="operator">|</span> <span class="number">13333333333</span> <span class="operator">|</span> <span class="number">45.50</span> <span class="operator">|</span> <span class="number">5</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+-----------------------+-------------+-------+----------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.01</span> sec)</span><br></pre></td></tr></table></figure><p>使用 SQL 语句选出李四购买商品的总价:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> u.user_name, p.product_name, p.price, s.quantity, p.price<span class="operator">*</span>s.quantity <span class="keyword">AS</span> total_price <span class="keyword">FROM</span> t_user u, product p, shopping_cart s</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">WHERE</span> u.user_name<span class="operator">=</span>"李四" <span class="keyword">AND</span> u.id <span class="operator">=</span> s.user_id <span class="keyword">AND</span> p.id <span class="operator">=</span> s.product_id </span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> LIMIT <span class="number">100</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+--------------+-------+----------+-------------+</span></span><br><span class="line"><span class="operator">|</span> user_name <span class="operator">|</span> product_name <span class="operator">|</span> price <span class="operator">|</span> quantity <span class="operator">|</span> total_price <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+--------------+-------+----------+-------------+</span></span><br><span class="line"><span class="operator">|</span> 李四 <span class="operator">|</span> 操作系统 <span class="operator">|</span> <span class="number">51.00</span> <span class="operator">|</span> <span class="number">6</span> <span class="operator">|</span> <span class="number">306.00</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+--------------+-------+----------+-------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>使用 SQL 语句列出购买数量排前两位的商品名称:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">></span> <span class="keyword">SELECT</span> p.product_name, p.price, s.quantity <span class="keyword">FROM</span> product p, shopping_cart s</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">WHERE</span> p.id <span class="operator">=</span> s.product_id</span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> quantity <span class="keyword">desc</span></span><br><span class="line"> <span class="operator">-</span><span class="operator">></span> LIMIT <span class="number">2</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------+-------+----------+</span></span><br><span class="line"><span class="operator">|</span> product_name <span class="operator">|</span> price <span class="operator">|</span> quantity <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------+-------+----------+</span></span><br><span class="line"><span class="operator">|</span> 操作系统 <span class="operator">|</span> <span class="number">51.00</span> <span class="operator">|</span> <span class="number">6</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> C<span class="operator">+</span><span class="operator">+</span>程序设计教程 <span class="operator">|</span> <span class="number">45.50</span> <span class="operator">|</span> <span class="number">5</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------+-------+----------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h2 id="忘记密码"><a href="#忘记密码" class="headerlink" title="忘记密码"></a>忘记密码</h2><p>若忘记数据库密码后可通过 <code>mysqld_safe</code> 来修改密码:</p><ol><li><p>在系统偏好设置中关闭 mysql 服务</p></li><li><p>打开终端,输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">➜ ~ <span class="built_in">cd</span> /usr/<span class="built_in">local</span>/mysql/bin</span><br><span class="line">➜ ~ sudo su</span><br></pre></td></tr></table></figure></li><li><p>命令行变成以 <code>sh-3.2#</code> 开头后继续输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sh-3.2<span class="comment"># ./mysqld_safe --skip-grant-tables &</span></span><br><span class="line"></span><br><span class="line">mysqld_safe Logging to <span class="string">'/usr/local/mysql/data/DannydeMBP.err'</span>. </span><br><span class="line">mysqld_safe Starting mysqld daemon with databases from /usr/<span class="built_in">local</span>/mysql/data</span><br></pre></td></tr></table></figure></li><li><p>新开个命令行窗口,进入 <code>mysql</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">➜ ~ /usr/<span class="built_in">local</span>/mysql/bin/mysql</span><br><span class="line"></span><br><span class="line">Enter password:</span><br><span class="line">Welcome to the MySQL monitor. Commands end with ; or \g.</span><br><span class="line">Your MySQL connection id is 30</span><br><span class="line">Server version: 5.7.31</span><br><span class="line"></span><br><span class="line">Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.</span><br><span class="line"></span><br><span class="line">Oracle is a registered trademark of Oracle Corporation and/or its</span><br><span class="line">affiliates. Other names may be trademarks of their respective</span><br><span class="line">owners.</span><br><span class="line"></span><br><span class="line">Type <span class="string">'help;'</span> or <span class="string">'\h'</span> <span class="keyword">for</span> <span class="built_in">help</span>. Type <span class="string">'\c'</span> to clear the current input statement.</span><br><span class="line"></span><br><span class="line">mysql></span><br><span class="line">mysql> use mysql</span><br><span class="line"></span><br><span class="line">Reading table information <span class="keyword">for</span> completion of table and column names</span><br><span class="line">You can turn off this feature to get a quicker startup with -A</span><br><span class="line"></span><br><span class="line">Database changed</span><br></pre></td></tr></table></figure></li><li><p>更新密码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mysql> update user <span class="built_in">set</span> authentication_string=password(<span class="string">'admin'</span>) <span class="built_in">where</span> Host=<span class="string">'localhost'</span> and User=<span class="string">'root'</span>;</span><br><span class="line"></span><br><span class="line">Query OK, 1 row affected, 1 warning (0.01 sec)</span><br><span class="line">Rows matched: 1 Changed: 1 Warnings: 1</span><br></pre></td></tr></table></figure></li><li><p>输入 <code>exit</code> 命令退出 <code>mysql</code>,查出 <code>mysqld_safe</code> 进程号并杀掉:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql> <span class="built_in">exit</span></span><br><span class="line">Bye</span><br><span class="line"></span><br><span class="line">➜ ~ ps -ax | grep mysql</span><br><span class="line"> 8553 ttys004 0:00.03 /bin/sh ./mysqld_safe --skip-grant-tables</span><br><span class="line"> 8623 ttys004 0:00.92 /usr/<span class="built_in">local</span>/mysql-5.7.31-macos10.14-x86_64/bin/mysqld --basedir=/usr/<span class="built_in">local</span>/mysql-5.7.31-macos10.14-x86_64 --datadir=/usr/<span class="built_in">local</span>/mysql-5.7.31-macos10.14-x86_64/data --plugin-dir=/usr/<span class="built_in">local</span>/mysql-5.7.31-macos10.14-x86_64/lib/plugin --user=mysql --skip-grant-tables --log-error=host-3-187.can.danny1.network.err --pid-file=host-3-187.can.danny1.network.pid</span><br><span class="line"></span><br><span class="line"><span class="comment"># 杀掉 mysql 的进程</span></span><br><span class="line">➜ ~ <span class="built_in">kill</span> -9 8553</span><br><span class="line">➜ ~ <span class="built_in">kill</span> -9 8623</span><br></pre></td></tr></table></figure></li><li><p>此时返回系统偏好设置中看到 mysql 被关闭后就算正确退出了。接着继续输入 <code>mysql -u root -p</code> 命令连接数据库,再输入刚才修改的密码即可。</p></li></ol><hr><p><strong>参考资料</strong></p><ul><li><a href="https://dev.mysql.com/doc/refman/5.7/en/sql-statements.html">SQL Statements</a></li></ul>]]></content>
<summary type="html"><p>由于自考的实践考核要求有需要用到 mysql 进行考核,故记录一下在 mac 环境下试手的笔记。</p></summary>
<category term="计算机科学与技术" scheme="https://anran758.github.io/blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E4%B8%8E%E6%8A%80%E6%9C%AF/"/>
<category term="MySql" scheme="https://anran758.github.io/blog/tags/MySql/"/>
<category term="SQL" scheme="https://anran758.github.io/blog/tags/SQL/"/>
<category term="数据库" scheme="https://anran758.github.io/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
</entry>
<entry>
<title>组件通信: EventBus 的原理解析与应用</title>
<link href="https://anran758.github.io/blog/2022/01/22/event-bus/"/>
<id>https://anran758.github.io/blog/2022/01/22/event-bus/</id>
<published>2022-01-22T02:40:49.000Z</published>
<updated>2023-12-15T06:23:43.790Z</updated>
<content type="html"><![CDATA[<p>在开发复杂的单页面应用时,我们经常会遇到一个问题:如何高效地在组件或模块之间进行通信?这里,<code>EventBus</code>(事件总线)就派上了用场。简单来说,<code>EventBus</code> 是一种设计模式,它允许不同组件或模块之间通过事件来通信,而无需直接引用彼此。</p><p><code>EventBus</code> 是传统的组件通信解决方案,下面我们将讲解 <code>EventBus</code> 跨组件通信的原理、实现方式以及该如何使用。</p><span id="more"></span><h2 id="原理解析"><a href="#原理解析" class="headerlink" title="原理解析"></a>原理解析</h2><p><code>EventBus</code> 的核心在于提供一个中央机制,允许不同的组件或模块相互通信,而不必直接引用对方。它是一种典型的<strong>发布-订阅(pub-sub)</strong>模式,这是一种广泛使用的设计模式,用于解耦发送者和接收者。</p><p>在这个模式中,EventBus 充当了一个中介的角色:它允许组件订阅那些它们感兴趣的事件,并在这些事件发生时接收通知。同样,当某个事件发生时,比如用户的一个动作或者数据的变化,EventBus 负责将这一消息广播给所有订阅了该事件的组件。</p><p>它基于三个核心操作:注册事件(<code>on(event, callback)</code>)、触发事件(<code>emit(event, ...args)</code>)、以及移除事件(<code>off(event, callback)</code>)。因此,EventBus 的基本代码可以看到:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventBus</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">on</span>(<span class="params">event, callback</span>)</span> {</span><br><span class="line"> <span class="comment">// 注册事件监听器</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">emit</span>(<span class="params">event, ...args</span>)</span> {</span><br><span class="line"> <span class="comment">// 触发事件</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">off</span>(<span class="params">event, callback</span>)</span> {</span><br><span class="line"> <span class="comment">// 移除事件监听器</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>显然,我们需要有一个私有变量来储存用户的函数,此时为类添加 <code>events</code> 属性。<code>events</code> 属性是一个对象映射,其中每个属性表示一个事件名称,对应的值是一个回调函数的数组,这个数组存储了所有订阅了该事件的回调函数。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventBus</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> events: Record<<span class="built_in">string</span>, <span class="built_in">Function</span>[]> = {};</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当用户执行订阅事件 <code>on</code> 时,回调函数会被添加到相应事件名称的数组中。这样,同一个事件可以被不同组件或模块订阅,而每个订阅者的回调函数都会被正确地保存在事件队列中。最后,当触发事件 <code>emit</code> 时,事件队列中的每个回调函数都会被执行,实现了事件的触发和通知功能。若已经没有订阅需求,则可以通过 <code>off</code> 移除已经订阅的事件。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><p>接下来我们按照前文所述完善我们的代码实现:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventBus</span> </span>{</span><br><span class="line"> <span class="comment">// 事件存储对象,用于保存不同事件的回调函数</span></span><br><span class="line"> <span class="keyword">private</span> events: Record<<span class="built_in">string</span>, <span class="built_in">Function</span>[]> = {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 注册事件监听器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="variable">eventName</span></span> - 事件名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="variable">callback</span></span> - 回调函数,当事件触发时执行</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns <span class="variable">this</span></span> - 返回 EventBus 实例,支持链式调用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> on(eventName: <span class="built_in">string</span>, <span class="attr">callback</span>: <span class="built_in">Function</span>): <span class="built_in">this</span> {</span><br><span class="line"> <span class="comment">// 检查回调函数是否为函数类型</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> callback !== <span class="string">"function"</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"EventBus 'on' method expects a callback function."</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果事件不存在,创建一个空数组用于存储回调函数</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">this</span>.events[eventName]) {</span><br><span class="line"> <span class="built_in">this</span>.events[eventName] = [];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将回调函数添加到事件的回调函数列表中</span></span><br><span class="line"> <span class="built_in">this</span>.events[eventName].push(callback);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 支持链式调用</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 触发事件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="variable">eventName</span></span> - 要触发的事件名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="variable">args</span></span> - 传递给回调函数的参数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns <span class="variable">this</span></span> - 返回 EventBus 实例,支持链式调用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> emit(eventName: <span class="built_in">string</span>, ...args: <span class="built_in">any</span>[]): <span class="built_in">this</span> {</span><br><span class="line"> <span class="comment">// 获取事件对应的回调函数列表</span></span><br><span class="line"> <span class="keyword">const</span> callbacks = <span class="built_in">this</span>.events[eventName];</span><br><span class="line"> <span class="keyword">if</span> (callbacks) {</span><br><span class="line"> <span class="comment">// 遍历执行每个回调函数,并传递参数</span></span><br><span class="line"> callbacks.forEach(<span class="function">(<span class="params">callback</span>) =></span> callback(...args));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 支持链式调用</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 移除事件监听器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="variable">event</span></span> - 要移除的事件名称或事件名称数组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="variable">callback</span></span> - 要移除的回调函数(可选)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns <span class="variable">this</span></span> - 返回 EventBus 实例,支持链式调用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> off(event?: <span class="built_in">string</span> | <span class="built_in">string</span>[], callback?: <span class="built_in">Function</span>): <span class="built_in">this</span> {</span><br><span class="line"> <span class="comment">// 清空所有事件监听器</span></span><br><span class="line"> <span class="keyword">if</span> (!event || (<span class="built_in">Array</span>.isArray(event) && !event.length)) {</span><br><span class="line"> <span class="built_in">this</span>.events = {};</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 处理事件数组</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(event)) {</span><br><span class="line"> event.forEach(<span class="function">(<span class="params">e</span>) =></span> <span class="built_in">this</span>.off(e, callback));</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果没有提供回调函数,则删除该事件的所有监听器</span></span><br><span class="line"> <span class="keyword">if</span> (!callback) {</span><br><span class="line"> <span class="keyword">delete</span> <span class="built_in">this</span>.events[event];</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 移除特定的回调函数</span></span><br><span class="line"> <span class="keyword">const</span> callbacks = <span class="built_in">this</span>.events[event];</span><br><span class="line"> <span class="keyword">if</span> (callbacks) {</span><br><span class="line"> <span class="keyword">const</span> index = callbacks.indexOf(callback);</span><br><span class="line"> <span class="keyword">if</span> (index > -<span class="number">1</span>) {</span><br><span class="line"> callbacks.splice(index, <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 支持链式调用</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当涉及到一次性的事件监听需求时,我们可以进一步扩展 EventBus,以支持一次性事件监听。允许用户在某个事件触发后,自动移除事件监听器,以确保回调函数只执行一次:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventBus</span> </span>{</span><br><span class="line"> <span class="comment">// other code ...</span></span><br><span class="line"> <span class="keyword">public</span> once(eventName: <span class="built_in">string</span>, <span class="attr">callback</span>: <span class="built_in">Function</span>): <span class="built_in">this</span> {</span><br><span class="line"> <span class="keyword">const</span> onceWrapper = <span class="function">(<span class="params">...args: <span class="built_in">any</span>[]</span>) =></span> {</span><br><span class="line"> <span class="built_in">this</span>.off(eventName, onceWrapper);</span><br><span class="line"> callback(...args);</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="built_in">this</span>.on(eventName, onceWrapper);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="使用方式"><a href="#使用方式" class="headerlink" title="使用方式"></a>使用方式</h2><p>我们将类的封装到 <code>event-bus.ts</code> 中,通过模块的来管理:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">EventBus</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们现在已经封装好了一个类,若我们像使用则需要实例化。此处再文件内直接实例化一个类:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建 EventBus 实例并导出</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> eventBus = <span class="keyword">new</span> EventBus();</span><br></pre></td></tr></table></figure><p>这样使用时可以提供两种方式:</p><ol><li><p>引入已经实例化的 eventBus</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { eventBus } <span class="keyword">from</span> <span class="string">'./event-bus'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 订阅事件</span></span><br><span class="line">eventBus.on(<span class="string">'eventName'</span>, callback);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 触发事件</span></span><br><span class="line">eventBus.emit(<span class="string">'eventName'</span>, data);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除事件</span></span><br><span class="line">eventBus.off(<span class="string">'eventName'</span>, callback);</span><br></pre></td></tr></table></figure></li><li><p>需要多个独立的事件总线实例时,或者希望在不同模块或组件之间使用不同的事件总线时,可以选择额外实例化 eventBus。这样做的目的可能是为了隔离命名的冲突、组件与模块逻辑隔离等原因。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// events.ts</span></span><br><span class="line"><span class="keyword">import</span> { EventBus } <span class="keyword">from</span> <span class="string">'./event-bus'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建独立的事件总线实例</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> eventBusA = <span class="keyword">new</span> EventBus();</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> eventBusB = <span class="keyword">new</span> EventBus();</span><br></pre></td></tr></table></figure><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> {eventBusA, eventBusB} <span class="keyword">from</span> <span class="string">'./events'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在不同模块或组件中使用不同的事件总线</span></span><br><span class="line">eventBusA.on(<span class="string">'eventA'</span>, callbackA);</span><br><span class="line">eventBusB.on(<span class="string">'eventB'</span>, callbackB);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 触发不同事件总线上的事件</span></span><br><span class="line">eventBusA.emit(<span class="string">'eventA'</span>, dataA);</span><br><span class="line">eventBusB.emit(<span class="string">'eventB'</span>, dataB);</span><br></pre></td></tr></table></figure></li></ol><p>以下是 CodeSandbox 的演示代码:</p><iframe src="https://codesandbox.io/embed/qc4vf?view=Editor+%2B+Preview&module=%2Fsrc%2FApp.tsx" style="width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;" title="React event bus" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在本文中,我们深入探讨了 EventBus 的原理,了解了它是如何工作的。我们学习了它的核心操作。除了本文所提及的实现方式,有时候在生产项目中,为了确保代码的可靠性,我们可以考虑使用成熟的第三方库,例如 <a href="https://github.com/developit/mitt">mitt</a> 或 <a href="https://github.com/scottcorgan/tiny-emitter">tiny-emitter</a>。</p><p>这些库已经经过广泛的测试和使用,可以提供稳定和可靠的 EventBus 功能。</p>]]></content>
<summary type="html"><p>在开发复杂的单页面应用时,我们经常会遇到一个问题:如何高效地在组件或模块之间进行通信?这里,<code>EventBus</code>(事件总线)就派上了用场。简单来说,<code>EventBus</code> 是一种设计模式,它允许不同组件或模块之间通过事件来通信,而无需直接引用彼此。</p>
<p><code>EventBus</code> 是传统的组件通信解决方案,下面我们将讲解 <code>EventBus</code> 跨组件通信的原理、实现方式以及该如何使用。</p></summary>
<category term="JavaScript" scheme="https://anran758.github.io/blog/categories/JavaScript/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="pub-sub" scheme="https://anran758.github.io/blog/tags/pub-sub/"/>
<category term="设计模式" scheme="https://anran758.github.io/blog/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>Redux 食用指南</title>
<link href="https://anran758.github.io/blog/2021/10/11/redux/"/>
<id>https://anran758.github.io/blog/2021/10/11/redux/</id>
<published>2021-10-11T04:08:31.000Z</published>
<updated>2023-12-11T11:14:42.910Z</updated>
<content type="html"><![CDATA[<p>Redux 是一个强大的状态管理框架,被广泛用于管理应用程序的状态。它的设计理念是让状态的更新可预测和透明。本文将简要探讨 Redux 的核心机制和实际应用。</p><span id="more"></span><p>在 Redux 中,有一个状态对象负责应用程序的整个状态.<strong>Redux store 是应用程序状态的唯一真实来源</strong>。</p><p>如果应用程序想要更新状态,只能通过 <code>Redux store</code> 执行,单向数据流可以更轻松地对应用程序中的状态进行监测管理。</p><p>Redux store 是一个保存和管理应用程序状态的 state,使用 Redux 对象中的 <code>createStore()</code> 来创建一个 redux store,此方法将 reducer 函数作为必需参数.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> reducer = <span class="function">(<span class="params">state = <span class="number">5</span></span>) =></span> state;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = Redux.createStore(reducer);</span><br></pre></td></tr></table></figure><h2 id="获取数据"><a href="#获取数据" class="headerlink" title="获取数据"></a>获取数据</h2><p>Redux store 对象提供了几种允许你与之交互的方法,可以使用 <code>getState()</code> 方法检索 Redux store 对象中保存的当前的 <code>state</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = Redux.createStore(</span><br><span class="line"> (state = <span class="number">5</span>) => state</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更改此行下方的代码</span></span><br><span class="line"><span class="keyword">const</span> currentState = store.getState();</span><br></pre></td></tr></table></figure><h3 id="更新状态"><a href="#更新状态" class="headerlink" title="更新状态"></a>更新状态</h3><p>由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。在 Redux 中,所有状态更新都由 <strong>dispatch action</strong> 触发,<code>action</code> 只是一个 JavaScript 对象,其中包含有关已发生的 <code>action</code> 事件的信息。</p><p>Redux store 接收这些 action 对象,然后更新相应的状态。<code>action</code> 对象中必须要带有 <code>type</code> 属性,reducer 才能根据 <code>type</code> 进行区分处理。<br><code>action</code> 除了 <code>type</code> 属性外,还可以附带数据给 reducer 做相应的处理,这个数据是可选的。</p><p>我们可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store,然后 store 根据发生的 action 进行状态的更新。</p><h3 id="reducer"><a href="#reducer" class="headerlink" title="reducer"></a>reducer</h3><p>reducer 将 state 和 action 作为参数,并且它总是返回一个新的 state。这是 reducer 的唯一的作用,它不应有任何其他的作用。比如它不应调用 API 接口,也不应存在任何潜在的副作用。reducer 只是一个<strong>接受状态和动作,然后返回新状态的纯函数</strong>。</p><p>在 reducer 中一般通过 switch 进行判断 action 的类型,做不同的处理。</p><h2 id="订阅事件"><a href="#订阅事件" class="headerlink" title="订阅事件"></a>订阅事件</h2><p><code>store.subscribe()</code> 可以订阅 <code>store</code> 的数据变化,它接收一个回调函数作为参数。当 <code>store</code> 数据更新时会调用该回调函数。</p><h2 id="模块划分"><a href="#模块划分" class="headerlink" title="模块划分"></a>模块划分</h2><p>当应用程序的状态开始变得越来越复杂时,将状态划分为多个部分可能是个更好的选择。我们可以考虑将不同的模块进行划分,Login 作为一个模块,Account 作为另一个模块。</p><p>但对 state 进行模块划分也不能破坏 redux 中将数据存入简单 state 的原则。因此可以生成多个 reducer, 再将它们合并到 root reducer 中。</p><p>redux 提供了 <code>combineReducers()</code> 函数对 reducer 进行合并。它接收一个对象作为参数,对象中的 key/value 别分对应着 module name 和相对应的 reducer 函数。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">const</span> rootReducer = Redux.combineReducers({</span><br><span class="line"> counter: counterReducer,</span><br><span class="line"> auth: authReducer</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = Redux.createStore(rootReducer);</span><br></pre></td></tr></table></figure><h2 id="异步"><a href="#异步" class="headerlink" title="异步"></a>异步</h2><p>redux 本身是不能直接处理异步操作,因此需要引入中间件来处理这些问题。在 <code>createStore</code> 时,还可以传入第二个可选参数,这个参数就是传递给 redux 的中间件函数。</p><p>Redux 提供了 <code>applyMiddleware()</code> 来创建一个中间件,一般处理 redux 异步的中间件有 <code>redux-thunk</code>、<code>redux-saga</code> 等。</p><h3 id="redux-thunk"><a href="#redux-thunk" class="headerlink" title="redux-thunk"></a>redux-thunk</h3><p><code>redux-thunk</code> 允许 action 创建函数返回一个函数而不是一个 action 对象。这个返回的函数接收 <code>dispatch</code> 和 <code>getState</code> 作为参数,允许直接进行异步操作和状态的分发。</p><p>例如,一个异步获取数据的 <code>thunk</code> 可能如下所示:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fetchData</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function">(<span class="params">dispatch, getState</span>) =></span> {</span><br><span class="line"> <span class="comment">// 异步操作</span></span><br><span class="line"> fetch(<span class="string">'some-api-url'</span>)</span><br><span class="line"> .then(<span class="function"><span class="params">response</span> =></span> response.json())</span><br><span class="line"> .then(<span class="function"><span class="params">data</span> =></span> dispatch({ <span class="attr">type</span>: <span class="string">'FETCH_DATA_SUCCESS'</span>, <span class="attr">payload</span>: data }))</span><br><span class="line"> .catch(<span class="function"><span class="params">error</span> =></span> dispatch({ <span class="attr">type</span>: <span class="string">'FETCH_DATA_ERROR'</span>, error }));</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="redux-saga"><a href="#redux-saga" class="headerlink" title="redux-saga"></a>redux-saga</h3><p><code>redux-saga</code> 是一个更高级的中间件,它使用 ES6 的 <code>Generator</code> 函数来让你以同步的方式写异步代码。saga 监听发起的 action,并决定基于这些 action 执行哪些副作用(如异步获取数据、访问浏览器缓存等)。</p><p>一个简单的 <code>saga</code> 可能如下所示:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">fetchDataSaga</span>(<span class="params">action</span>) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> data = <span class="keyword">yield</span> call(fetch, <span class="string">'some-api-url'</span>);</span><br><span class="line"> <span class="keyword">yield</span> put({ <span class="attr">type</span>: <span class="string">'FETCH_DATA_SUCCESS'</span>, <span class="attr">payload</span>: data });</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="keyword">yield</span> put({ <span class="attr">type</span>: <span class="string">'FETCH_DATA_ERROR'</span>, error });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="React-与-Redux"><a href="#React-与-Redux" class="headerlink" title="React 与 Redux"></a>React 与 Redux</h2><p>在 React 应用中,Redux 被用来跨组件共享状态。使用 <code>react-redux</code> 库可以方便地将 Redux 集成到 React 应用中。</p><h3 id="Provider-组件"><a href="#Provider-组件" class="headerlink" title="Provider 组件"></a><code>Provider</code> 组件</h3><p><code>Provider</code> 是 <code>react-redux</code> 提供的一个组件,它使 Redux store 对 React 应用中的所有组件可用。通常,我们在应用的最顶层包裹 <code>Provider</code> 并传入 store:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { Provider } <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"><span class="keyword">import</span> { store } <span class="keyword">from</span> <span class="string">'./store'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> App = <span class="function">() =></span> (</span><br><span class="line"> <Provider store={store}></span><br><span class="line"> <MyRootComponent /></span><br><span class="line"> </Provider></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="connect-函数"><a href="#connect-函数" class="headerlink" title="connect 函数"></a><code>connect</code> 函数</h3><p><code>connect</code> 是一个高阶函数,用于将 React 组件连接到 Redux store。它接受两个参数:<code>mapStateToProps</code> 和 <code>mapDispatchToProps</code>,分别用于从 store 中读取状态和向 store 发起 actions。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { connect } <span class="keyword">from</span> <span class="string">'react-redux'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mapStateToProps = <span class="function"><span class="params">state</span> =></span> ({</span><br><span class="line"> items: state.items</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mapDispatchToProps = <span class="function"><span class="params">dispatch</span> =></span> ({</span><br><span class="line"> fetchData: <span class="function">() =></span> dispatch(fetchData())</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect(mapStateToProps, mapDispatchToProps)(MyComponent);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Redux 提供了一种统一、可预测的方式来管理应用程序的状态。通过使用 actions, reducers 和 store,开发者可以以一种高度解耦的方式来管理状态和 UI。</p><p>当结合异步处理和 React 集成时,Redux 成为了一个强大的工具,能够提升大型应用程序的开发和维护效率。</p>]]></content>
<summary type="html"><p>Redux 是一个强大的状态管理框架,被广泛用于管理应用程序的状态。它的设计理念是让状态的更新可预测和透明。本文将简要探讨 Redux 的核心机制和实际应用。</p></summary>
<category term="React" scheme="https://anran758.github.io/blog/categories/React/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="React" scheme="https://anran758.github.io/blog/tags/React/"/>
</entry>
<entry>
<title>计算机网络原理笔记</title>
<link href="https://anran758.github.io/blog/2021/08/15/note-04741/"/>
<id>https://anran758.github.io/blog/2021/08/15/note-04741/</id>
<published>2021-08-15T04:56:15.000Z</published>
<updated>2023-12-11T11:21:59.277Z</updated>
<content type="html"><![CDATA[<p>计算机网络原理学习笔记。</p><!-- omit in toc --><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><blockquote><p>下面目录定位有些使用不了。若需要跳转到具体段落应使用侧边栏文章定位</p></blockquote><ul><li><a href="#%E8%AE%A1%E7%AE%97%E7%B1%BB%E9%80%9F%E8%A7%88">计算类速览</a><ul><li><a href="#%E9%80%9F%E7%8E%87%E4%B8%8E%E5%B8%A6%E5%AE%BD">速率与带宽</a></li><li><a href="#tcp-%E6%8A%A5%E6%96%87%E6%AE%B5%E7%A1%AE%E8%AE%A4%E5%8F%B7">TCP 报文段确认号</a></li><li><a href="#%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB">汉明距离</a></li><li><a href="#%E5%BE%AA%E7%8E%AF%E5%86%97%E4%BD%99%E7%A0%81">循环冗余码</a></li></ul></li><li><a href="#%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E6%A6%82%E8%BF%B0">计算机网络概述</a><ul><li><a href="#%E5%B8%B8%E8%A7%81%E5%BA%94%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E5%8F%B7">常见应用的端口号</a></li><li><a href="#tcpiposi-%E5%8F%82%E8%80%83%E6%A8%A1%E5%9E%8B">TCP/IP、OSI 参考模型</a></li><li><a href="#%E7%AE%80%E8%BF%B0osi%E5%8F%82%E6%95%B0%E6%A8%A1%E5%9E%8B%E7%89%A9%E7%90%86%E5%B1%82%E7%9A%84%E4%B8%BB%E8%A6%81%E5%8A%9F%E8%83%BD%E5%8F%8A%E8%AF%A5%E5%B1%82%E5%8D%8F%E8%AE%AE%E8%A7%84%E5%AE%9A%E7%9A%84%E5%9B%9B%E4%B8%AA%E7%89%B9%E6%80%A7">简述OSI参数模型物理层的主要功能及该层协议规定的四个特性。</a></li></ul></li><li><a href="#%E5%BA%94%E7%94%A8%E5%B1%82">应用层</a><ul><li><a href="#%E7%AE%80%E8%BF%B0%E5%85%B8%E5%9E%8B%E7%9A%84http%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95%E5%8F%8A%E5%85%B6%E4%BD%9C%E7%94%A8">简述典型的HTTP请求方法及其作用</a></li><li><a href="#%E7%AE%80%E8%BF%B0-pop3-%E5%8D%8F%E8%AE%AE%E4%BA%A4%E4%BA%92%E8%BF%87%E7%A8%8B">简述 POP3 协议交互过程</a></li></ul></li><li><a href="#%E4%BC%A0%E8%BE%93%E5%B1%82">传输层</a><ul><li><a href="#tcpip">TCP/IP</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E4%BC%A0%E8%BE%93%E5%B1%82%E6%89%80%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%8A%9F%E8%83%BD">简述传输层所实现的功能</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E4%BC%A0%E8%BE%93%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%8F%AF%E9%9D%A0%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E7%9A%84%E4%B8%BB%E8%A6%81%E6%8E%AA%E6%96%BD">简述传输层实现可靠数据传输的主要措施</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E4%BF%9D%E8%AF%81%E7%BD%91%E7%BB%9C%E4%BC%A0%E8%BE%93%E5%8F%AF%E9%9D%A0%E6%80%A7%E7%9A%84%E7%A1%AE%E8%AE%A4%E4%B8%8E%E9%87%8D%E4%BC%A0%E6%9C%BA%E5%88%B6%E7%9A%84%E6%A6%82%E5%BF%B5">简述保证网络传输可靠性的确认与重传机制的概念</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E5%B7%AE%E9%94%99%E6%8E%A7%E5%88%B6%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E5%B7%AE%E9%94%99%E6%8E%A7%E5%88%B6%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%96%B9%E6%B3%95">简述差错控制的概念以及差错控制的基本方法</a></li><li><a href="#%E7%AE%80%E8%BF%B0tcp%E6%89%80%E6%8F%90%E4%BE%9B%E7%9A%84%E9%9D%A2%E5%90%91%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1">简述TCP所提供的面向连接服务</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E4%B8%BA-udp-%E5%A5%97%E6%8E%A5%E5%AD%97%E5%88%86%E9%85%8D%E7%AB%AF%E5%8F%A3%E5%8F%B7%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95">简述为 UDP 套接字分配端口号的两种方法</a></li><li><a href="#%E7%AE%80%E8%BF%B0-udp-%E6%8F%90%E4%BE%9B%E7%9A%84%E6%9C%8D%E5%8A%A1%E7%9A%84%E4%B8%BB%E8%A6%81%E7%89%B9%E5%BE%81">简述 UDP 提供的服务的主要特征</a></li></ul></li><li><a href="#%E7%BD%91%E7%BB%9C%E5%B1%82">网络层</a><ul><li><a href="#%E7%AE%80%E8%BF%B0%E8%99%9A%E7%94%B5%E8%B7%AF%E7%9A%84%E6%A6%82%E5%BF%B5%E5%8F%8A%E5%85%B6%E6%9E%84%E6%88%90%E8%A6%81%E7%B4%A0">简述虚电路的概念及其构成要素</a></li><li><a href="#%E8%99%9A%E7%94%B5%E8%B7%AF%E4%BA%A4%E6%8D%A2%E5%92%8C%E6%95%B0%E6%8D%AE%E4%BA%A4%E6%8D%A2%E7%9A%84%E4%B8%BB%E8%A6%81%E5%B7%AE%E5%88%AB">虚电路交换和数据交换的主要差别</a></li><li><a href="#%E7%94%B5%E8%B7%AF%E4%BA%A4%E6%8D%A2%E7%9A%84%E7%89%B9%E7%82%B9%E5%92%8C%E4%BC%98%E7%BC%BA%E7%82%B9">电路交换的特点和优缺点</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E6%B0%B8%E4%B9%85%E8%99%9A%E7%94%B5%E8%B7%AF%E4%B8%8E%E4%BA%A4%E6%8D%A2%E8%99%9A%E7%94%B5%E8%B7%AF%E7%9A%84%E5%8C%BA%E5%88%AB">简述永久虚电路与交换虚电路的区别</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E8%B7%AF%E7%94%B1%E5%99%A8%E8%BE%93%E5%85%A5%E7%AB%AF%E5%8F%A3%E6%8E%A5%E5%8F%97%E4%B8%8E%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE%E7%9A%84%E8%BF%87%E7%A8%8B">简述路由器输入端口接受与处理数据的过程</a></li></ul></li><li><a href="#%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82%E4%B8%8E%E5%B1%80%E5%9F%9F%E7%BD%91">数据链路层与局域网</a><ul><li><a href="#%E5%B8%A7%E7%9A%84%E7%BB%84%E6%88%90">帧的组成</a></li><li><a href="#-%E5%A4%9A%E8%B7%AF%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE-">==== 多路访问控制协议 ====</a><ul><li><a href="#%E9%9D%9E%E5%9D%9A%E6%8C%81-csma-%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86">非坚持 csma 的基本原理</a></li><li><a href="#1-%E5%9D%9A%E6%8C%81-csma-%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86">1-坚持 csma 的基本原理</a></li></ul></li><li><a href="#-%E5%B1%80%E5%9F%9F%E7%BD%91-">==== 局域网 ====</a><ul><li><a href="#%E7%AE%80%E8%BF%B0%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE-arp-%E7%9A%84%E4%BD%9C%E7%94%A8%E5%92%8C%E5%9F%BA%E6%9C%AC%E6%80%9D%E6%83%B3">简述地址解析协议 ARP 的作用和基本思想</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E8%99%9A%E6%8B%9F%E5%B1%80%E5%9F%9F%E7%BD%91vlan%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E5%88%92%E5%88%86%E6%96%B9%E6%B3%95">简述虚拟局域网(VLAN)的概念以及划分方法</a></li></ul></li></ul></li><li><a href="#%E7%89%A9%E7%90%86%E5%B1%82">物理层</a><ul><li><a href="#%E7%AE%80%E8%BF%B0-cmi-%E7%A0%81%E7%9A%84%E7%BC%96%E7%A0%81%E8%A7%84%E5%88%99%E5%B9%B6%E7%94%BB%E5%87%BA%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%AF%94%E7%89%B9%E5%BA%8F%E5%88%97-1011010011-%E7%9A%84-cmi-%E7%A0%81%E4%BF%A1%E5%8F%B7%E6%B3%A2%E5%BD%A2">简述 CMI 码的编码规则,并画出二进制比特序列 1011010011 的 CMI 码信号波形</a></li><li><a href="#%E7%B1%B3%E5%8B%92%E7%A0%81%E7%9A%84%E7%BC%96%E7%A0%81%E8%A7%84%E5%88%99">米勒码的编码规则</a></li></ul></li><li><a href="#%E6%97%A0%E7%BA%BF%E4%B8%8E%E7%A7%BB%E5%8A%A8%E7%BD%91%E7%BB%9C">无线与移动网络</a><ul><li><a href="#%E7%AE%80%E8%BF%B0-4-%E4%B8%AA-ieee-80211-%E6%A0%87%E5%87%86%E5%85%B7%E6%9C%89%E7%9A%84%E5%85%B1%E5%90%8C%E7%89%B9%E5%BE%81">简述 4 个 IEEE 802.11 标准具有的共同特征</a></li></ul></li><li><a href="#%E7%AE%80%E7%AD%94%E9%A2%98">简答题</a><ul><li><a href="#%E6%AF%8F%E4%B8%AA-as-%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87-bgp%E8%BE%B9%E7%95%8C%E7%BD%91%E5%85%B3%E5%8D%8F%E8%AE%AE-%E5%AE%9E%E7%8E%B0%E5%93%AA%E4%BA%9B%E5%8A%9F%E8%83%BD">每个 AS 可以通过 BGP(边界网关协议) 实现哪些功能</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E5%BA%94%E6%BB%A1%E8%B6%B3%E7%9A%84%E8%A6%81%E6%B1%82">简述数字签名应满足的要求</a></li></ul></li><li><a href="#%E5%9F%BA%E7%A1%80%E8%AE%A1%E7%AE%97">基础计算</a><ul><li><a href="#%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E4%BA%8C%E8%BF%9B%E5%88%B6">十进制转二进制</a></li><li><a href="#2n-%E9%80%9F%E6%9F%A5%E8%A1%A8">2^n 速查表</a></li><li><a href="#%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81%E9%80%9F%E8%A7%88">子网掩码速览</a></li><li><a href="#%E9%80%9A%E8%BF%87-ip-%E5%9C%B0%E5%9D%80%E4%B8%8E%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81%E6%8E%A8%E7%AE%97%E5%87%BA%E5%85%B6%E4%BB%96%E4%BF%A1%E6%81%AF">通过 IP 地址与子网掩码推算出其他信息</a></li></ul></li></ul><hr><span id="more"></span><h2 id="计算类速览"><a href="#计算类速览" class="headerlink" title="计算类速览"></a>计算类速览</h2><h3 id="速率与带宽"><a href="#速率与带宽" class="headerlink" title="速率与带宽"></a>速率与带宽</h3><ol><li><p>传输时延:链路发送到结束所用的时间</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dt = L(分组长度) / R(链路带宽)</span><br></pre></td></tr></table></figure></li><li><p>传播时延: 从发送端到接收端传输所需的时间</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dp = D(链路长度) / V(信号传播速度)</span><br></pre></td></tr></table></figure></li><li><p>时延带宽(乘)积:传播时延和链路带宽的乘积</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">G = dp(传播时延) * R(链路带宽)</span><br></pre></td></tr></table></figure></li></ol><h3 id="TCP-报文段确认号"><a href="#TCP-报文段确认号" class="headerlink" title="TCP 报文段确认号"></a>TCP 报文段确认号</h3><p>题目一般是主机 A 会发送两个 TCP 报文段给主机 B,其实有字节序号分别为 n1 和 n2。</p><ol><li>算携带了多少字节: 字节数=n2-n1</li><li>接收到第一个报文段返回的确认号是 n2</li><li>如果主机 B 接收到第二个报文段后的确认号是 n3, 问第二个携带多少字节: 字节数=n3-n2</li><li>如果第一段丢失了,第二段到达了,主机 B 返回的确认号是: n1, 即要求主机 B 重传之前没有接受到的数据</li></ol><!-- ### 传输编码**不归零**就一个柱子,**归零码**就到一半柱子就向下画。**双极**就是还有个负数的表差分码,碰到 1 就改变方向双向码(曼切斯特码),1 画左半边,0 画右半边 --><h3 id="汉明距离"><a href="#汉明距离" class="headerlink" title="汉明距离"></a>汉明距离</h3><p>两个等长码字之间的,对应位不同的位数,成为<strong>两个码字的汉明距离</strong>。汉明距离是两个码字进行<strong>按位异或</strong>后 1 的个数。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">01100101</span><br><span class="line">10011101</span><br><span class="line">-------- 进行异或(^)</span><br><span class="line">11111000</span><br><span class="line">--------</span><br><span class="line">5个1,汉明距离 = 5</span><br></pre></td></tr></table></figure><h3 id="循环冗余码"><a href="#循环冗余码" class="headerlink" title="循环冗余码"></a>循环冗余码</h3><p>多项式 <code>G(x)=x4 + x3 + 1</code>,对位串 <code>101100111101</code> 进行 CRC 编码,结果为:</p><p><code>G(x)=x4 + x3 + 1</code> 对应的比特位为 <code>11001</code>,则在待编位串后面添加 <code>0000</code>.</p><p>位串除 <code>11001</code></p><hr><h2 id="计算机网络概述"><a href="#计算机网络概述" class="headerlink" title="计算机网络概述"></a>计算机网络概述</h2><h3 id="常见应用的端口号"><a href="#常见应用的端口号" class="headerlink" title="常见应用的端口号"></a>常见应用的端口号</h3><table><thead><tr><th>应用</th><th>端口号</th></tr></thead><tbody><tr><td>TCP/FTP</td><td>21</td></tr><tr><td>SMTP</td><td>25</td></tr><tr><td>HTTP</td><td>80</td></tr><tr><td>POP3 服务器</td><td>110</td></tr></tbody></table><h3 id="TCP-IP、OSI-参考模型"><a href="#TCP-IP、OSI-参考模型" class="headerlink" title="TCP/IP、OSI 参考模型"></a>TCP/IP、OSI 参考模型</h3><img data-src="/blog/2021/08/15/note-04741/osi-modal.png" class="" title="参考模型"><table><thead><tr><th>OSI模型</th><th>单位</th></tr></thead><tbody><tr><td>7. 应用层</td><td>报文</td></tr><tr><td>4. 传输层</td><td>数据报/报文段</td></tr><tr><td>3. 网络层</td><td>分组/包</td></tr><tr><td>2. 数据链路层</td><td>帧</td></tr><tr><td>1. 物理层</td><td>比特流</td></tr></tbody></table><h3 id="简述OSI参数模型物理层的主要功能及该层协议规定的四个特性。"><a href="#简述OSI参数模型物理层的主要功能及该层协议规定的四个特性。" class="headerlink" title="简述OSI参数模型物理层的主要功能及该层协议规定的四个特性。"></a>简述OSI参数模型物理层的主要功能及该层协议规定的四个特性。</h3><p>物理层的主要功能是实现比特流的透明传输,为数据链路层提供数据传输服务。</p><p>物理层协议规定的特性包括:</p><ol><li>机械特性</li><li>电气特性</li><li>功能特性</li><li>规程特性</li></ol><hr><h2 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h2><p>网络协议的三要素包括</p><ul><li>语法: 定义实体之间交换信息的格式与结构</li><li>语义: 定义实体之间交换的信息中需要发送哪些控制信息,这些信息的具体含义,以及针对不同含义的控制信息,接收信息端应如何响应。</li><li>时序: 定义实体之间交换信息的顺序以及如何匹配或适应彼此的速度</li></ul><h3 id="简述典型的HTTP请求方法及其作用"><a href="#简述典型的HTTP请求方法及其作用" class="headerlink" title="简述典型的HTTP请求方法及其作用"></a>简述典型的HTTP请求方法及其作用</h3><ol><li>GET: 读取由 URL 所标识的信息</li><li>POST: 给服务器添加信息</li><li>HEAD: 请求读取由 URL 所标识的信息首部,无需在相应报文中包含对象</li><li>OPTION: 请求一些选项的信息</li><li>PUT: 在指明的 URL 下存储一个文档</li></ol><h3 id="简述-POP3-协议交互过程"><a href="#简述-POP3-协议交互过程" class="headerlink" title="简述 POP3 协议交互过程"></a>简述 POP3 协议交互过程</h3><blockquote><p>POP3 是邮件读取协议,可用于接收邮件。</p></blockquote><ol><li><strong>授权阶段</strong>: 用户代理需要向邮件服务器发送用户名和口令,服务器鉴别用户身份,授权访问邮箱。</li><li><strong>事务处理阶段</strong>: 用户代理向邮件服务器发送 POP3 命令,实现邮件读取,为邮件做删除编辑、取消邮件删除标记以及获取邮件的统计信息等操作。</li><li><strong>更新阶段</strong>: 客户发出来 quit 命令,结束 POP3 回话,服务器删除哪些被标记为删除的邮件。</li></ol><hr><h2 id="传输层"><a href="#传输层" class="headerlink" title="传输层"></a>传输层</h2><p>传输层核心任务:为应用进程之间提供端到端的逻辑通信服务。</p><h3 id="TCP-IP"><a href="#TCP-IP" class="headerlink" title="TCP/IP"></a>TCP/IP</h3><blockquote><p>核心层: <strong>传输层</strong><br>网络互联层核心协议: IP 协议 </p></blockquote><h3 id="简述传输层所实现的功能"><a href="#简述传输层所实现的功能" class="headerlink" title="简述传输层所实现的功能"></a>简述传输层所实现的功能</h3><p>实现的功能:</p><ol><li>传输层<strong>寻址</strong></li><li>对应用层报文进行<strong>分段和重组</strong></li><li>对报文进行<strong>差错检测</strong></li><li>实现进程间端到端的<strong>可靠数据传输控制</strong></li><li>面向应用层实现<strong>复用与分解</strong></li><li>流量控制</li><li>拥塞控制</li></ol><h3 id="简述传输层实现可靠数据传输的主要措施"><a href="#简述传输层实现可靠数据传输的主要措施" class="headerlink" title="简述传输层实现可靠数据传输的主要措施"></a>简述传输层实现可靠数据传输的主要措施</h3><blockquote><p>不可靠传输信道在数据传输中可能发生:</p><ol><li>比特差错</li><li>乱序</li><li>数据丢失</li></ol></blockquote><ol><li>差错控制: 利用差错编码实现数据报传输过程中的比特差检测(甚至是纠正)。</li><li>确认: 「接收方」向「发送方」反馈接受状态</li><li>重传: 「发送方」重新发送「接收方」没有正确接收到的数据</li><li>序号: 确保数据按序提交</li><li>计时器: 解决数据丢失问题</li></ol><h3 id="简述保证网络传输可靠性的确认与重传机制的概念"><a href="#简述保证网络传输可靠性的确认与重传机制的概念" class="headerlink" title="简述保证网络传输可靠性的确认与重传机制的概念"></a>简述保证网络传输可靠性的确认与重传机制的概念</h3><ul><li>确认是指数据分组接受节点再收到每个分组后,要求想发送节点会送正确接受分组的确认信息。</li><li>在规定时间内,如果发送节点没有接收到「接收方」返回的确认信息,就认为该数据分组发送失败,发送节点会重传该数据分组。</li></ul><h3 id="简述差错控制的概念以及差错控制的基本方法"><a href="#简述差错控制的概念以及差错控制的基本方法" class="headerlink" title="简述差错控制的概念以及差错控制的基本方法"></a>简述差错控制的概念以及差错控制的基本方法</h3><p><strong>差错控制</strong>就是通过差错编码技术实现对<strong>信息传输的检测</strong>,并通过某种机制进行差错纠正和处理。 </p><p>差错检测的基本方法有:</p><ol><li>检错重发</li><li>检错丢弃</li><li>前向纠错</li><li>反馈校验</li></ol><h3 id="简述TCP所提供的面向连接服务"><a href="#简述TCP所提供的面向连接服务" class="headerlink" title="简述TCP所提供的面向连接服务"></a>简述TCP所提供的面向连接服务</h3><p>在生成报文开始传送之前,TCP 客户和服务器相互交换传输层的控制信息,完成握手。在客户进程与服务器进程的套接字之间建立一条逻辑的 TCP 连接。</p><h3 id="简述为-UDP-套接字分配端口号的两种方法"><a href="#简述为-UDP-套接字分配端口号的两种方法" class="headerlink" title="简述为 UDP 套接字分配端口号的两种方法"></a>简述为 UDP 套接字分配端口号的两种方法</h3><ol><li><strong>传输层自动分配</strong>: 创建一个 UDP 套接字时,传输层自动为该套接字分配一个端口号,该端口号当前未被该主机任何其他 UDP 套接字使用。</li><li><strong>手动绑定</strong>: 在创建 UDP 套接字后,通过调用 bind 函数来绑定一个特定的端口号。</li></ol><h3 id="简述-UDP-提供的服务的主要特征"><a href="#简述-UDP-提供的服务的主要特征" class="headerlink" title="简述 UDP 提供的服务的主要特征"></a>简述 UDP 提供的服务的主要特征</h3><ol><li>应用进程更容易控制发送什么数据以及什么时候发送。</li><li>无需建立连接</li><li>无连接状态</li><li>首部开销小,仅有8字节的开销</li></ol><hr><h2 id="网络层"><a href="#网络层" class="headerlink" title="网络层"></a>网络层</h2><p>网络层提供的功能有:</p><ol><li>连接建立</li><li>路由</li><li>转发</li></ol><h3 id="简述虚电路的概念及其构成要素"><a href="#简述虚电路的概念及其构成要素" class="headerlink" title="简述虚电路的概念及其构成要素"></a>简述虚电路的概念及其构成要素</h3><p>虚电路是源主机到目的主机的一条路径上建立的一条网络层逻辑连接,成为虚电路。</p><blockquote><p>comment: 因为是逻辑连接,不是真实的电路连接,故称为虚电路</p></blockquote><p>一条虚电路由 3 个要素组成:</p><ol><li>从源主机到目的主机之间的一条路径</li><li>该路径上每条链路各有一个虚电路标记(VCID)</li><li>该路径上每台分组交互机的转发表记录虚电路标识的接续关系</li></ol><h3 id="虚电路交换和数据交换的主要差别"><a href="#虚电路交换和数据交换的主要差别" class="headerlink" title="虚电路交换和数据交换的主要差别"></a>虚电路交换和数据交换的主要差别</h3><ul><li>虚电路网络通常由<strong>网络完成顺序控制、差错控制和流量控制等功能</strong>,向端系统提供无差错数据传送服务,而端系统则可以很简单。</li><li>数据报网络的<strong>顺序控制、差错控制和流量控制等功能需要由端系统完成</strong>,网络实现的功能很简单,比如基本的路由与转发功能。</li></ul><h3 id="电路交换的特点和优缺点"><a href="#电路交换的特点和优缺点" class="headerlink" title="电路交换的特点和优缺点"></a>电路交换的特点和优缺点</h3><p>电路交换的特点是有连接的,在通信时需要先建立电路连接,在通讯过程中独占一个信道,在通讯结束后需要拆除电路连接。</p><p>优点: 实时性高,时延和时延抖动都较小<br>缺点: 对于突发性数据传输,信道利用率低,且传输速率单一。</p><h3 id="简述永久虚电路与交换虚电路的区别"><a href="#简述永久虚电路与交换虚电路的区别" class="headerlink" title="简述永久虚电路与交换虚电路的区别"></a>简述永久虚电路与交换虚电路的区别</h3><p>永久虚电路是一种提前建立、长期使用的虚电路,虚电路的建立时间开销基本上可以忽略。<br>交换虚电路是根据通信需要而临时建立的虚电路,通信结束后立即拆除,虚电路的建立和拆除时间有时相对影响较大。</p><h3 id="简述路由器输入端口接受与处理数据的过程"><a href="#简述路由器输入端口接受与处理数据的过程" class="headerlink" title="简述路由器输入端口接受与处理数据的过程"></a>简述路由器输入端口接受与处理数据的过程</h3><p>输入端口接受信号,还原数据链路层帧,提取 IP 数据报,根据 IP 数据报的目的 IP 地址检索路由表,决策将数据报交换到哪个输出端口</p><hr><h2 id="数据链路层与局域网"><a href="#数据链路层与局域网" class="headerlink" title="数据链路层与局域网"></a>数据链路层与局域网</h2><p>数据链路层提供的服务有:</p><ol><li>组帧</li><li>链路接入</li><li>可靠交付</li><li>差错控制</li></ol><h3 id="帧的组成"><a href="#帧的组成" class="headerlink" title="帧的组成"></a>帧的组成</h3><p><strong>HDLC</strong>: 帧组成:</p><ol><li><code>管理帧</code></li><li><code>信息帧</code></li><li><code>无序号帧</code></li></ol><p><strong>IEEE 802.11 帧</strong>:</p><ol><li><code>管理帧</code></li><li><code>控制帧</code></li><li><code>数据帧</code></li></ol><p><code>PPP (point to point protocol)</code> 数据帧结构:</p><ol><li>标志(01111110)</li><li>地址(11111111)</li><li>控制(00000011)</li><li>协议</li><li>信息</li><li>校验和</li><li>标志(01111110)</li></ol><h3 id="多路访问控制协议"><a href="#多路访问控制协议" class="headerlink" title="==== 多路访问控制协议 ===="></a>==== 多路访问控制协议 ====</h3><h4 id="非坚持-csma-的基本原理"><a href="#非坚持-csma-的基本原理" class="headerlink" title="非坚持 csma 的基本原理"></a>非坚持 csma 的基本原理</h4><ul><li>若通信站有数据发送,先监听信道,若<strong>发现信道空闲,则立即发送数据</strong>(与 <code>1-坚持 CSMA</code> 第一步一致)</li><li>若发现信道忙,则<strong>等待一个随机时间</strong>,然后再<strong>重新监听信道</strong>,尝试发送数据。</li><li>若发送数据时产生冲突,则<strong>等待一个随机时间</strong>,然后<strong>重新开始监听信道</strong>,尝试发送数据。</li></ul><p>这是个做事不太着急的协议。将上面文绉绉的描述用通俗的话来理解是:它在寝室中想要去洗澡</p><ol><li>它会先看看有没有人在用浴室,没人在用就直接去洗澡</li><li>去洗澡时发现有人也想用了,它会礼让给其他人。自个再晚一段时间再看看还有没有人用,没人用就自个用了</li><li>如果已经有人在用浴室了,那又晚点再看看</li></ol><h4 id="1-坚持-csma-的基本原理"><a href="#1-坚持-csma-的基本原理" class="headerlink" title="1-坚持 csma 的基本原理"></a>1-坚持 csma 的基本原理</h4><ul><li>若通信站有数据发送,先监听信道,若<strong>发现信道空闲,则立即发送数据</strong>(与 <code>非坚持 CSMA</code> 第一步一致)</li><li>若发现信道忙,则继续监听信道,直至发现信道空闲,然后立即发送数据。</li></ul><p>通俗话理解: 顾名思义,坚持不懈。如果浴室有人用了,我就守在门口。有人出来我就立马进去。</p><h3 id="局域网"><a href="#局域网" class="headerlink" title="==== 局域网 ===="></a>==== 局域网 ====</h3><h4 id="简述地址解析协议-ARP-的作用和基本思想"><a href="#简述地址解析协议-ARP-的作用和基本思想" class="headerlink" title="简述地址解析协议 ARP 的作用和基本思想"></a>简述地址解析协议 ARP 的作用和基本思想</h4><p>ARP 用于根据本网内<strong>目的主机</strong>或<strong>默认网关的 IP 地址</strong>获取其 MAC 地址。</p><p>基本思想是: 在每一台主机中设置专用内存区域作为 ARP 高速缓存区域,储存该主机所在局域网中其他主机和路由器(默认网关)的 IP 地址与 MAC 地址之间的映射,并且要经常更新这个映射表。</p><p>ARP 在局域网中通过广播 ARP 查询报文的方式,来询问某目的站的 IP 地址对应的 MAC 地址,即知道本网内某主机的 IP 地址就能知道它的 MAC 地址。</p><h4 id="简述虚拟局域网-VLAN-的概念以及划分方法"><a href="#简述虚拟局域网-VLAN-的概念以及划分方法" class="headerlink" title="简述虚拟局域网(VLAN)的概念以及划分方法"></a>简述虚拟局域网(VLAN)的概念以及划分方法</h4><p>虚拟局域网是一种基于交换机的逻辑分隔广播域的局域网应用形式。划分方法主要有 3 种:</p><ol><li>基于交换机端口划分</li><li>基于 MAC 地址划分</li><li>基于上层协议或地址划分</li></ol><hr><h2 id="物理层"><a href="#物理层" class="headerlink" title="物理层"></a>物理层</h2><h3 id="简述-CMI-码的编码规则,并画出二进制比特序列-1011010011-的-CMI-码信号波形"><a href="#简述-CMI-码的编码规则,并画出二进制比特序列-1011010011-的-CMI-码信号波形" class="headerlink" title="简述 CMI 码的编码规则,并画出二进制比特序列 1011010011 的 CMI 码信号波形"></a>简述 CMI 码的编码规则,并画出二进制比特序列 1011010011 的 CMI 码信号波形</h3><p>CMI 码的编码规则是将信息码的 0 编码为双极不归零码的 01,信息码的 1 交替编码为双极不归零码的 11 和 00。</p><img data-src="/blog/2021/08/15/note-04741/CMI-1.png" class=""><h3 id="米勒码的编码规则"><a href="#米勒码的编码规则" class="headerlink" title="米勒码的编码规则"></a>米勒码的编码规则</h3><blockquote><p>P229</p></blockquote><ol><li>信息码的 1 编码为「双极非归零码」的 01 或 10(占半格)</li><li>信息码连 1 时,后面的 1 要换编码</li><li>信息码的 0 编码为 00 或 11,中间码元不跳变(占一格)</li><li>单个 0 时不跳变</li><li>多个 0 时,间隔跳变</li><li>(备注): 有两极</li></ol><hr><h2 id="无线与移动网络"><a href="#无线与移动网络" class="headerlink" title="无线与移动网络"></a>无线与移动网络</h2><h3 id="简述-4-个-IEEE-802-11-标准具有的共同特征"><a href="#简述-4-个-IEEE-802-11-标准具有的共同特征" class="headerlink" title="简述 4 个 IEEE 802.11 标准具有的共同特征"></a>简述 4 个 IEEE 802.11 标准具有的共同特征</h3><ol><li>都使用相同介质访问协议 CSMA/CA。</li><li>链路层帧使用相同的帧格式</li><li>都具有降低传输速率以传输更远距离的能力</li><li>都支持“基础设施模式”和“自组织模式”两种模式</li></ol><hr><h2 id="简答题"><a href="#简答题" class="headerlink" title="简答题"></a>简答题</h2><h3 id="每个-AS-可以通过-BGP-边界网关协议-实现哪些功能"><a href="#每个-AS-可以通过-BGP-边界网关协议-实现哪些功能" class="headerlink" title="每个 AS 可以通过 BGP(边界网关协议) 实现哪些功能"></a>每个 AS 可以通过 BGP(边界网关协议) 实现哪些功能</h3><blockquote><p>AS: Autonomous system, 自治系统</p></blockquote><ol><li>从相邻 AS 获取某子网的可达性信息。</li><li>向本 AS 内部的所有路由器传播跨 AS 的某子网可达性信息。</li><li>基于某子网可达性信息和 AS 策略,觉得到达该子网的最佳路由</li></ol><h3 id="简述数字签名应满足的要求"><a href="#简述数字签名应满足的要求" class="headerlink" title="简述数字签名应满足的要求"></a>简述数字签名应满足的要求</h3><ol><li>接收方能够确认或证实发送方的签名,但不能伪造</li><li>发送发发送签名给接受方后,就不能否认他所签发的信息</li><li>接收方对已收到的签名信息不能再否认,既有收报认证</li><li>第三者可以确认收发双方之间的消息传送,但不能伪造这一过</li></ol><hr><h2 id="基础计算"><a href="#基础计算" class="headerlink" title="基础计算"></a>基础计算</h2><h3 id="十进制转二进制"><a href="#十进制转二进制" class="headerlink" title="十进制转二进制"></a>十进制转二进制</h3><p>十进制转二进制主要的方法是<strong>除2取余,逆序排列法</strong>。</p><p>可以写一个简单的 <code>js</code> 函数打印每次计算的结果。例如将整数 <code>251</code> 转为二进制的过程是:</p><details> <summary>点击展开详细代码</summary><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">convertToBinary</span>(<span class="params">n, buffer = []</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> a = <span class="built_in">Math</span>.floor(n / <span class="number">2</span>)</span><br><span class="line"> <span class="keyword">const</span> b = n % <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"> buffer.push(b);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`<span class="subst">${n}</span> / 2 = <span class="subst">${a}</span>...<span class="subst">${b}</span>`</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (a === <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">const</span> result = buffer.reverse().join(<span class="string">''</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`将每次取到的余数逆转排序后,最终转换后的二进制是: <span class="subst">${result}</span>`</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> convertToBinary(a, buffer);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">convertToBinary(<span class="number">521</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 521 / 2 = 260...1</span></span><br><span class="line"><span class="comment"> * 260 / 2 = 130...0</span></span><br><span class="line"><span class="comment"> * 130 / 2 = 65...0</span></span><br><span class="line"><span class="comment"> * 65 / 2 = 32...1</span></span><br><span class="line"><span class="comment"> * 32 / 2 = 16...0</span></span><br><span class="line"><span class="comment"> * 16 / 2 = 8...0</span></span><br><span class="line"><span class="comment"> * 8 / 2 = 4...0</span></span><br><span class="line"><span class="comment"> * 4 / 2 = 2...0</span></span><br><span class="line"><span class="comment"> * 2 / 2 = 1...0</span></span><br><span class="line"><span class="comment"> * 1 / 2 = 0...1</span></span><br><span class="line"><span class="comment"> * 将每次取到的余数逆转排序后,最终转换后的二进制是: 1000001001</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// js 的 toString 方法还可以将数值转为指定进制</span></span><br><span class="line"><span class="keyword">var</span> fn = <span class="function">(<span class="params">n, base = <span class="number">2</span></span>) =></span> n.toString(base);</span><br><span class="line"></span><br><span class="line">fn(<span class="number">521</span>); <span class="comment">// "1000001001"</span></span><br></pre></td></tr></table></figure></details><h3 id="2-n-速查表"><a href="#2-n-速查表" class="headerlink" title="2^n 速查表"></a>2^n 速查表</h3><details> <summary>2 的 N 次方速查表</summary><table><thead><tr><th>次方</th><th>值</th></tr></thead><tbody><tr><td>2^1</td><td>2</td></tr><tr><td>2^2</td><td>4</td></tr><tr><td>2^3</td><td>8</td></tr><tr><td>2^4</td><td>16</td></tr><tr><td>2^5</td><td>32</td></tr><tr><td>2^6</td><td>64</td></tr><tr><td>2^7</td><td>128</td></tr><tr><td>2^8</td><td>256</td></tr><tr><td>2^9</td><td>512</td></tr><tr><td>2^10</td><td>1024</td></tr><tr><td>2^11</td><td>2048</td></tr><tr><td>2^12</td><td>4096</td></tr><tr><td>2^13</td><td>8192</td></tr><tr><td>2^14</td><td>16384</td></tr><tr><td>2^15</td><td>32768</td></tr><tr><td>2^16</td><td>65536</td></tr><tr><td>2^17</td><td>131072</td></tr><tr><td>2^18</td><td>262144</td></tr><tr><td>2^19</td><td>524288</td></tr><tr><td>2^20</td><td>1048576</td></tr></tbody></table></details><h3 id="子网掩码速览"><a href="#子网掩码速览" class="headerlink" title="子网掩码速览"></a>子网掩码速览</h3><table><thead><tr><th>类别</th><th>子网掩码十进制</th><th>子网掩码二进制</th></tr></thead><tbody><tr><td>A</td><td><code>255.0.0.0</code></td><td><code>11111111 00000000 00000000 00000000</code></td></tr><tr><td>B</td><td><code>255.255.0.0</code></td><td><code>11111111 11111111 00000000 00000000</code></td></tr><tr><td>C</td><td><code>255.255.255.0</code></td><td><code>11111111 11111111 11111111 00000000</code></td></tr></tbody></table><h3 id="通过-IP-地址与子网掩码推算出其他信息"><a href="#通过-IP-地址与子网掩码推算出其他信息" class="headerlink" title="通过 IP 地址与子网掩码推算出其他信息"></a>通过 IP 地址与子网掩码推算出其他信息</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1. 子网地址: 主机 IP 地址 & 子网掩码</span><br><span class="line">2. 广播地址: 子网地址 | 子网掩码反码</span><br></pre></td></tr></table></figure><!-- ### 计算子网掩码在求子网掩码之前必须先搞清楚要**划分的子网数目**,以及每个子网内的**所需主机数目**。1. 将子网数目**转化为二进制**来表示2. 取得该二进制的位数,为 `N`3. 取得该类 IP 地址的子网掩码,将其主机地址部分的的**前 N 位设为 1**,即得出该IP地址划分子网的子网掩码。| 划分子网数 | 子网位数 | 子网掩码(二进制) | 子网掩码(十进制) | 每个子网主机数 || ---------- | -------- | ------------------------------------- | ------------------ | -------------- || 1~2 | 1 | `11111111 11111111 11111111 10000000` | `255.255.255.128` | 126 || 3~4 | 2 | `11111111 11111111 11111111 11000000` | `255.255.255.192` | 62 || 5~8 | 3 | `11111111 11111111 11111111 11100000` | `255.255.255.224` | 30 || 9~16 | 4 | `11111111 11111111 11111111 11110000` | `255.255.255.240` | 14 || 17~32 | 5 | `11111111 11111111 11111111 11111000` | `255.255.255.248` | 6 || 33~64 | 6 | `11111111 11111111 11111111 11111100` | `255.255.255.252` | 2 |### 通过要划分的子网数求子网掩码**Q:** 将一个 **C 类 IP 地址**划分为 **8 个子网**,其掩码是? **A:** 按照以上的步骤操作:1. 子网数目为 8,`十进制 8` 转为二进制是 `1000`2. 二进制位数为 `4` 位3. 子网掩码的取值是: **对应网络前缀,全部为 1,其余位(主机部分)全部为 0**。IPv4 的长度是 32 位。其中,C 类 IP 地址的网络前缀有 24 位,其余 8 位是主机号。将 子网掩码是 `255.255.255.0` --><!-- Math.ceil(Math.sqrt(2)) -->]]></content>
<summary type="html"><p>计算机网络原理学习笔记。</p>
<!-- omit in toc -->
<h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><blockquote>
<p>下面目录定位有些使用不了。若需要跳转到具体段落应使用侧边栏文章定位</p>
</blockquote>
<ul>
<li><a href="#%E8%AE%A1%E7%AE%97%E7%B1%BB%E9%80%9F%E8%A7%88">计算类速览</a><ul>
<li><a href="#%E9%80%9F%E7%8E%87%E4%B8%8E%E5%B8%A6%E5%AE%BD">速率与带宽</a></li>
<li><a href="#tcp-%E6%8A%A5%E6%96%87%E6%AE%B5%E7%A1%AE%E8%AE%A4%E5%8F%B7">TCP 报文段确认号</a></li>
<li><a href="#%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB">汉明距离</a></li>
<li><a href="#%E5%BE%AA%E7%8E%AF%E5%86%97%E4%BD%99%E7%A0%81">循环冗余码</a></li>
</ul>
</li>
<li><a href="#%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E6%A6%82%E8%BF%B0">计算机网络概述</a><ul>
<li><a href="#%E5%B8%B8%E8%A7%81%E5%BA%94%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E5%8F%B7">常见应用的端口号</a></li>
<li><a href="#tcpiposi-%E5%8F%82%E8%80%83%E6%A8%A1%E5%9E%8B">TCP/IP、OSI 参考模型</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0osi%E5%8F%82%E6%95%B0%E6%A8%A1%E5%9E%8B%E7%89%A9%E7%90%86%E5%B1%82%E7%9A%84%E4%B8%BB%E8%A6%81%E5%8A%9F%E8%83%BD%E5%8F%8A%E8%AF%A5%E5%B1%82%E5%8D%8F%E8%AE%AE%E8%A7%84%E5%AE%9A%E7%9A%84%E5%9B%9B%E4%B8%AA%E7%89%B9%E6%80%A7">简述OSI参数模型物理层的主要功能及该层协议规定的四个特性。</a></li>
</ul>
</li>
<li><a href="#%E5%BA%94%E7%94%A8%E5%B1%82">应用层</a><ul>
<li><a href="#%E7%AE%80%E8%BF%B0%E5%85%B8%E5%9E%8B%E7%9A%84http%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95%E5%8F%8A%E5%85%B6%E4%BD%9C%E7%94%A8">简述典型的HTTP请求方法及其作用</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0-pop3-%E5%8D%8F%E8%AE%AE%E4%BA%A4%E4%BA%92%E8%BF%87%E7%A8%8B">简述 POP3 协议交互过程</a></li>
</ul>
</li>
<li><a href="#%E4%BC%A0%E8%BE%93%E5%B1%82">传输层</a><ul>
<li><a href="#tcpip">TCP/IP</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E4%BC%A0%E8%BE%93%E5%B1%82%E6%89%80%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%8A%9F%E8%83%BD">简述传输层所实现的功能</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E4%BC%A0%E8%BE%93%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%8F%AF%E9%9D%A0%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E7%9A%84%E4%B8%BB%E8%A6%81%E6%8E%AA%E6%96%BD">简述传输层实现可靠数据传输的主要措施</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E4%BF%9D%E8%AF%81%E7%BD%91%E7%BB%9C%E4%BC%A0%E8%BE%93%E5%8F%AF%E9%9D%A0%E6%80%A7%E7%9A%84%E7%A1%AE%E8%AE%A4%E4%B8%8E%E9%87%8D%E4%BC%A0%E6%9C%BA%E5%88%B6%E7%9A%84%E6%A6%82%E5%BF%B5">简述保证网络传输可靠性的确认与重传机制的概念</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E5%B7%AE%E9%94%99%E6%8E%A7%E5%88%B6%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E5%B7%AE%E9%94%99%E6%8E%A7%E5%88%B6%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%96%B9%E6%B3%95">简述差错控制的概念以及差错控制的基本方法</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0tcp%E6%89%80%E6%8F%90%E4%BE%9B%E7%9A%84%E9%9D%A2%E5%90%91%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1">简述TCP所提供的面向连接服务</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E4%B8%BA-udp-%E5%A5%97%E6%8E%A5%E5%AD%97%E5%88%86%E9%85%8D%E7%AB%AF%E5%8F%A3%E5%8F%B7%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95">简述为 UDP 套接字分配端口号的两种方法</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0-udp-%E6%8F%90%E4%BE%9B%E7%9A%84%E6%9C%8D%E5%8A%A1%E7%9A%84%E4%B8%BB%E8%A6%81%E7%89%B9%E5%BE%81">简述 UDP 提供的服务的主要特征</a></li>
</ul>
</li>
<li><a href="#%E7%BD%91%E7%BB%9C%E5%B1%82">网络层</a><ul>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%99%9A%E7%94%B5%E8%B7%AF%E7%9A%84%E6%A6%82%E5%BF%B5%E5%8F%8A%E5%85%B6%E6%9E%84%E6%88%90%E8%A6%81%E7%B4%A0">简述虚电路的概念及其构成要素</a></li>
<li><a href="#%E8%99%9A%E7%94%B5%E8%B7%AF%E4%BA%A4%E6%8D%A2%E5%92%8C%E6%95%B0%E6%8D%AE%E4%BA%A4%E6%8D%A2%E7%9A%84%E4%B8%BB%E8%A6%81%E5%B7%AE%E5%88%AB">虚电路交换和数据交换的主要差别</a></li>
<li><a href="#%E7%94%B5%E8%B7%AF%E4%BA%A4%E6%8D%A2%E7%9A%84%E7%89%B9%E7%82%B9%E5%92%8C%E4%BC%98%E7%BC%BA%E7%82%B9">电路交换的特点和优缺点</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E6%B0%B8%E4%B9%85%E8%99%9A%E7%94%B5%E8%B7%AF%E4%B8%8E%E4%BA%A4%E6%8D%A2%E8%99%9A%E7%94%B5%E8%B7%AF%E7%9A%84%E5%8C%BA%E5%88%AB">简述永久虚电路与交换虚电路的区别</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%B7%AF%E7%94%B1%E5%99%A8%E8%BE%93%E5%85%A5%E7%AB%AF%E5%8F%A3%E6%8E%A5%E5%8F%97%E4%B8%8E%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE%E7%9A%84%E8%BF%87%E7%A8%8B">简述路由器输入端口接受与处理数据的过程</a></li>
</ul>
</li>
<li><a href="#%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82%E4%B8%8E%E5%B1%80%E5%9F%9F%E7%BD%91">数据链路层与局域网</a><ul>
<li><a href="#%E5%B8%A7%E7%9A%84%E7%BB%84%E6%88%90">帧的组成</a></li>
<li><a href="#-%E5%A4%9A%E8%B7%AF%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE-">==== 多路访问控制协议 ====</a><ul>
<li><a href="#%E9%9D%9E%E5%9D%9A%E6%8C%81-csma-%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86">非坚持 csma 的基本原理</a></li>
<li><a href="#1-%E5%9D%9A%E6%8C%81-csma-%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86">1-坚持 csma 的基本原理</a></li>
</ul>
</li>
<li><a href="#-%E5%B1%80%E5%9F%9F%E7%BD%91-">==== 局域网 ====</a><ul>
<li><a href="#%E7%AE%80%E8%BF%B0%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE-arp-%E7%9A%84%E4%BD%9C%E7%94%A8%E5%92%8C%E5%9F%BA%E6%9C%AC%E6%80%9D%E6%83%B3">简述地址解析协议 ARP 的作用和基本思想</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%99%9A%E6%8B%9F%E5%B1%80%E5%9F%9F%E7%BD%91vlan%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E5%88%92%E5%88%86%E6%96%B9%E6%B3%95">简述虚拟局域网(VLAN)的概念以及划分方法</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#%E7%89%A9%E7%90%86%E5%B1%82">物理层</a><ul>
<li><a href="#%E7%AE%80%E8%BF%B0-cmi-%E7%A0%81%E7%9A%84%E7%BC%96%E7%A0%81%E8%A7%84%E5%88%99%E5%B9%B6%E7%94%BB%E5%87%BA%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%AF%94%E7%89%B9%E5%BA%8F%E5%88%97-1011010011-%E7%9A%84-cmi-%E7%A0%81%E4%BF%A1%E5%8F%B7%E6%B3%A2%E5%BD%A2">简述 CMI 码的编码规则,并画出二进制比特序列 1011010011 的 CMI 码信号波形</a></li>
<li><a href="#%E7%B1%B3%E5%8B%92%E7%A0%81%E7%9A%84%E7%BC%96%E7%A0%81%E8%A7%84%E5%88%99">米勒码的编码规则</a></li>
</ul>
</li>
<li><a href="#%E6%97%A0%E7%BA%BF%E4%B8%8E%E7%A7%BB%E5%8A%A8%E7%BD%91%E7%BB%9C">无线与移动网络</a><ul>
<li><a href="#%E7%AE%80%E8%BF%B0-4-%E4%B8%AA-ieee-80211-%E6%A0%87%E5%87%86%E5%85%B7%E6%9C%89%E7%9A%84%E5%85%B1%E5%90%8C%E7%89%B9%E5%BE%81">简述 4 个 IEEE 802.11 标准具有的共同特征</a></li>
</ul>
</li>
<li><a href="#%E7%AE%80%E7%AD%94%E9%A2%98">简答题</a><ul>
<li><a href="#%E6%AF%8F%E4%B8%AA-as-%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87-bgp%E8%BE%B9%E7%95%8C%E7%BD%91%E5%85%B3%E5%8D%8F%E8%AE%AE-%E5%AE%9E%E7%8E%B0%E5%93%AA%E4%BA%9B%E5%8A%9F%E8%83%BD">每个 AS 可以通过 BGP(边界网关协议) 实现哪些功能</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E5%BA%94%E6%BB%A1%E8%B6%B3%E7%9A%84%E8%A6%81%E6%B1%82">简述数字签名应满足的要求</a></li>
</ul>
</li>
<li><a href="#%E5%9F%BA%E7%A1%80%E8%AE%A1%E7%AE%97">基础计算</a><ul>
<li><a href="#%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E4%BA%8C%E8%BF%9B%E5%88%B6">十进制转二进制</a></li>
<li><a href="#2n-%E9%80%9F%E6%9F%A5%E8%A1%A8">2^n 速查表</a></li>
<li><a href="#%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81%E9%80%9F%E8%A7%88">子网掩码速览</a></li>
<li><a href="#%E9%80%9A%E8%BF%87-ip-%E5%9C%B0%E5%9D%80%E4%B8%8E%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81%E6%8E%A8%E7%AE%97%E5%87%BA%E5%85%B6%E4%BB%96%E4%BF%A1%E6%81%AF">通过 IP 地址与子网掩码推算出其他信息</a></li>
</ul>
</li>
</ul>
<hr></summary>
<category term="计算机科学与技术" scheme="https://anran758.github.io/blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E4%B8%8E%E6%8A%80%E6%9C%AF/"/>
<category term="计算机科学与技术" scheme="https://anran758.github.io/blog/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E4%B8%8E%E6%8A%80%E6%9C%AF/"/>
<category term="自考" scheme="https://anran758.github.io/blog/tags/%E8%87%AA%E8%80%83/"/>
</entry>
<entry>
<title>Accessibility Parsing 无障碍页面分析</title>
<link href="https://anran758.github.io/blog/2021/04/13/w3c-validator/"/>
<id>https://anran758.github.io/blog/2021/04/13/w3c-validator/</id>
<published>2021-04-13T07:00:00.000Z</published>
<updated>2021-10-04T09:19:41.966Z</updated>
<content type="html"><![CDATA[<img data-src="/blog/2021/04/13/w3c-validator/banner.jpg" class=""><p>最近项目需要做 Accessibility 的处理,在这段时间的接触了很多无障碍相关的技术。除了基础的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/Accessibility/ARIA">ARIA</a> 和相对单位的影响等基础知识外,本篇想介绍一下比较实用的 Accessibility Parsing tools。</p><span id="more"></span><hr><p><a href="http://validator.w3.org/nu/">Nu Html Checker</a> 是用于检查 HTML page 是否符合语义化,捕获可能会遗漏的错误,以便进行修复。它提供输入 URL 地址、上传 HTML 文件和文字输入的形式来检查代码。</p><p>以 <a href="https://developer.mozilla.org/zh-CN/">MDN 文档</a> 为例:</p><img data-src="/blog/2021/04/13/w3c-validator/mdn-nu.png" class="" title="mdn nu"><p>可以看到在 Input 框输入 URL 后,点击 check button 得到关于 HTML 语义化的分析报告。它主要分为两个等级:</p><ol><li><strong>Error</strong>: 被标记为 Error 的问题是因为它们是可访问性、可用性、互操作性、安全性或可维护性的潜在问题。或者因为它们可能导致性能下降,或可能导致脚本以难以解决的方式失败。</li><li><strong>Warning</strong>: 被标记为 warning 的代码意味着这种写法不够好,但对 Accessibility 的问题影响有限。</li></ol><p>可见 MDN 文档对于 Accessibility 的支持还是很不错的。</p><p>但如果我们的 page 是使用 <code>SPA (single-page application)</code>,也就是页面的内容是动态生成的话。那就需要等内容加载完毕后,右键保存为 HTML 文件后再上传分析,或通过开发者工具直接 copy HTML 字符串进行分析。</p><p>下面任取一个 SPA 网站,直接 copy 网站的 HTML code 做分析,步骤如下:</p><ol><li>当 SPA page 的内容加载完毕后,打开浏览器开发者工具。</li><li>Tab 选择 <code>Elements</code>, 右键 <code><html></code> 节点,选择 <code>Copy</code> -> <code>Copy outHTML</code>。</li><li>将 Nu HTML checker 中的 Check by 选为 text input, 在 input 框粘贴刚才 copy 的 HTML code。</li><li>由于 copy 中不会把 <code><!DOCTYPE html></code> 带过来,因此我们还得加上这句。</li><li>点击 check button。</li></ol><img data-src="/blog/2021/04/13/w3c-validator/check-by-textinput.png" class="" title="测试 SPA page"><p>可以发现该页面可以优化的东西还有很多,但错误数量一多就有点眼花缭乱的。因此 Nu Html Checker 还提供 <code>Message Filtering</code> 以展示关键的信息:</p><img data-src="/blog/2021/04/13/w3c-validator/Message-Filtering.png" class="" title="Message Filtering"><p>当然,有时候我们主要想关注这个 page 关于 accessibility 相关的结果,不想看那么多额外的信息。此时就可以使用 <a href="https://labs.diginclusion.com/tools/bookmarklets/wcag-parsing-filter/">WCAG Parsing Validation Filter bookmarklet</a> 生成 accessibility 的信息简报。</p><p>该篇文档讲的东西其实很简单,无非就是它们提供了一段代码,用于提取关于 accessibility 的信息:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javascript:(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="keyword">var</span> filterStrings=[<span class="string">"tag seen"</span>,<span class="string">"Stray end tag"</span>,<span class="string">"Bad start tag"</span>,<span class="string">"violates nesting rules"</span>,<span class="string">"Duplicate ID"</span>,<span class="string">"Unclosed element"</span>,<span class="string">"not allowed as child of element"</span>,<span class="string">"unclosed elements"</span>,<span class="string">"unquoted attribute value"</span>,<span class="string">"Duplicate attribute"</span>,<span class="string">"descendant of an element with the attribute"</span>],filterRE=filterStrings.join(<span class="string">"|"</span>),i,nT=<span class="number">0</span>,nP1=<span class="number">0</span>,result,resultText,results,resultsP1={},root=<span class="built_in">document</span>.getElementById(<span class="string">"results"</span>);<span class="keyword">if</span>(!root){<span class="keyword">return</span>}results=root.getElementsByTagName(<span class="string">"li"</span>);<span class="keyword">for</span>(i=results.length-<span class="number">1</span>;i>=<span class="number">0</span>;i--){result=results[i];<span class="keyword">if</span>(result.id.substr(<span class="number">0</span>,<span class="number">3</span>)===<span class="string">"vnu"</span>){<span class="keyword">if</span>(result.className!==<span class="string">"info"</span>){nT=nT+<span class="number">1</span>}resultText=<span class="string">""</span>+result.textContent;resultText=resultText.substring(<span class="number">0</span>,resultText.indexOf(<span class="string">"."</span>));<span class="keyword">if</span>(resultText.match(filterRE)==<span class="literal">null</span>){result.style.display=<span class="string">"none"</span>;result.className=result.className+<span class="string">"a11y-ignore"</span>}<span class="keyword">else</span>{resultsP1[resultText.substr(<span class="number">7</span>)]=<span class="literal">true</span>;nP1=nP1+<span class="number">1</span>}}}resultText=<span class="string">""</span>;<span class="keyword">for</span>(i <span class="keyword">in</span> resultsP1){<span class="keyword">if</span>(resultsP1.hasOwnProperty(i)){resultText=i+<span class="string">"; "</span>+resultText}}<span class="keyword">var</span> str=nT+<span class="string">" validation errors and warnings.\n"</span>+nP1+<span class="string">" errors that may impact accessibility:\n"</span>+resultText;<span class="built_in">console</span>.log(<span class="string">"%c[WCAG Parsing Validation Filter bookmarklet@v4]:\n"</span>,<span class="string">"font-weight: bold"</span>,<span class="string">"https://labs.diginclusion.com/tools/bookmarklets/wcag-parsing-filter/\n\n"</span>+str);alert(str)})();</span><br></pre></td></tr></table></figure><p>以上是一段自执行的 JavaScript 代码,源代码是直接将信息通过 <code>alert</code> 打印出来。但我觉得不太方便复制,因此在原基础上增加了 <code>console</code> 的输出方式,便于复制信息。</p><p><strong>使用方式</strong>:</p><ol><li>打开浏览器书签管理器 (bookmark manage)</li><li>如果使用的是 chrome 浏览器的话,在右上角点开 “Add new bookmark”</li><li>书签名可自己决定,URL 输入如上代码即可,保存书签</li><li>在 Nu Html Checker 解析后的结果页中,点击刚才新建的标签就能看到弹出来的结果了</li></ol><img data-src="/blog/2021/04/13/w3c-validator/bookmaket.png" class="" title="WCAG Parsing Validation Filter bookmarklet 使用"><p>上图 <code>console</code> 中输出的信息如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[WCAG Parsing Validation Filter bookmarklet@v4]: https://labs.diginclusion.com/tools/bookmarklets/wcag-parsing-filter/</span><br><span class="line"></span><br><span class="line">91 validation errors and warnings.</span><br><span class="line">6 errors that may impact accessibility:</span><br><span class="line">Element a not allowed as child of element ul in this context; Element object not allowed as child of element ul in this context; Element style not allowed as child of element body in this context; </span><br></pre></td></tr></table></figure><p>从这份信息简报我们可以了解到:这个 page 有 91 个 <code>errors</code> 和 <code>warnings</code>。其中有 6 份错误会影响 accessibility。主要的错误是因为不合理的标签嵌套所引起的错误。这份简报就已经将我们所期望了解的信息都简短的概括了出,便于我们分析。</p>]]></content>
<summary type="html"><img src="/blog/2021/04/13/w3c-validator/banner.jpg" class="">
<p>最近项目需要做 Accessibility 的处理,在这段时间的接触了很多无障碍相关的技术。除了基础的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/Accessibility/ARIA">ARIA</a> 和相对单位的影响等基础知识外,本篇想介绍一下比较实用的 Accessibility Parsing tools。</p></summary>
<category term="a11y" scheme="https://anran758.github.io/blog/categories/a11y/"/>
<category term="a11y" scheme="https://anran758.github.io/blog/tags/a11y/"/>
<category term="W3C" scheme="https://anran758.github.io/blog/tags/W3C/"/>
</entry>
<entry>
<title>软件工程笔记</title>
<link href="https://anran758.github.io/blog/2021/02/10/note-023333/"/>
<id>https://anran758.github.io/blog/2021/02/10/note-023333/</id>
<published>2021-02-10T04:00:00.000Z</published>
<updated>2022-05-23T06:15:12.063Z</updated>
<content type="html"><![CDATA[<p>软件工程相关笔记。</p><hr><ul><li><a href="#1-%E7%BB%AA%E8%AE%BA">1. 绪论</a><ul><li><a href="#%E7%AE%80%E8%BF%B0%E8%BD%AF%E4%BB%B6%E5%8D%B1%E6%9C%BA%E4%B8%8E%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E6%8F%90%E5%87%BA%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E6%A6%82%E5%BF%B5%E7%9A%84%E7%9B%AE%E7%9A%84">简述软件危机与软件工程的概念以及提出软件工程概念的目的</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E7%9A%84%E6%9C%AC%E8%B4%A8">简述软件开发的本质</a></li></ul></li><li><a href="#2-%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E4%B8%8E%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E8%A7%84%E7%BA%A6">2. 软件需求与软件需求规约</a><ul><li><a href="#%E5%88%9D%E5%A7%8B%E5%8F%91%E7%8E%B0%E9%9C%80%E6%B1%82">初始发现需求</a></li><li><a href="#%E9%9C%80%E6%B1%82%E8%A7%84%E7%BA%A6%E5%AE%9A%E4%B9%89">需求规约定义</a></li><li><a href="#%E9%9C%80%E6%B1%82%E8%A7%84%E7%BA%A6%E7%9A%84%E4%B8%89%E7%A7%8D%E5%9F%BA%E6%9C%AC%E5%BD%A2%E5%BC%8F">需求规约的三种基本形式</a></li></ul></li><li><a href="#3-%E7%BB%93%E6%9E%84%E5%8C%96%E6%96%B9%E6%B3%95">3. 结构化方法</a><ul><li><a href="#%E6%A8%A1%E5%9D%97%E7%9A%84%E5%86%85%E8%81%9A%E6%80%A7">模块的内聚性</a></li><li><a href="#%E6%A8%A1%E5%9D%97%E9%97%B4%E7%9A%84%E8%80%A6%E5%90%88%E7%B1%BB%E5%9E%8B">模块间的耦合类型</a></li><li><a href="#%E7%A8%8B%E5%BA%8F%E6%B5%81%E7%A8%8B%E5%9B%BE%E4%B8%BB%E8%A6%81%E7%94%A8%E4%BA%8E%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E7%9A%84%E5%93%AA%E4%B8%80%E9%98%B6%E6%AE%B5%E5%AE%83%E7%9A%84%E4%B8%BB%E8%A6%81%E4%BC%98%E7%BC%BA%E7%82%B9%E6%9C%89%E5%93%AA%E4%BA%9B">程序流程图主要用于软件开发的哪一阶段?它的主要优缺点有哪些?</a></li><li><a href="#%E7%B3%BB%E7%BB%9F%E6%B5%81%E7%A8%8B%E5%9B%BE%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%B5%81%E7%A8%8B%E5%9B%BE%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB">系统流程图与数据流程图有什么区别?</a></li><li><a href="#%E6%BC%94%E5%8C%96%E6%A8%A1%E5%9E%8B%E7%9A%84%E4%B8%BB%E8%A6%81%E7%89%B9%E5%BE%81%E6%98%AF%E4%BB%80%E4%B9%88%E5%AE%83%E5%AD%98%E5%9C%A8%E4%BB%80%E4%B9%88%E4%B8%8D%E8%B6%B3">演化模型的主要特征是什么?它存在什么不足?</a></li></ul></li><li><a href="#4-rupuml">4. RUP、UML</a><ul><li><a href="#%E5%9B%BE%E5%BD%A2%E5%B7%A5%E5%85%B7%E7%9A%84%E7%94%A8%E9%80%94">图形工具的用途</a></li><li><a href="#%E4%BB%80%E4%B9%88%E6%98%AF-uml-%E5%AE%83%E6%9C%89%E4%BB%80%E4%B9%88%E7%89%B9%E7%82%B9">什么是 UML? 它有什么特点?</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E6%B3%9B%E5%8C%96%E7%9A%84%E6%A6%82%E5%BF%B5%E5%8F%8A%E5%85%B6%E7%BA%A6%E6%9D%9F">简述泛化的概念及其约束</a></li><li><a href="#rup-%E7%9A%84%E7%89%B9%E7%82%B9%E4%B9%8B%E4%B8%80%E6%98%AF%E8%BF%AD%E4%BB%A3%E5%A2%9E%E9%87%8F%E5%BC%8F%E5%BC%80%E5%8F%91%E5%AE%83%E8%A7%84%E5%AE%9A%E4%BA%86-4-%E4%B8%AA%E5%BC%80%E5%8F%91%E9%98%B6%E6%AE%B5%E8%AF%B7%E7%AE%80%E8%BF%B0%E6%AF%8F%E6%AC%A1%E8%BF%AD%E4%BB%A3%E5%9C%A8%E5%90%84%E9%98%B6%E6%AE%B5%E7%9A%84%E7%9B%AE%E6%A0%87">RUP 的特点之一是迭代、增量式开发,它规定了 4 个开发阶段。请简述每次迭代在各阶段的目标。</a></li><li><a href="#%E7%AE%80%E8%BF%B0-rup-%E5%92%8C-uml-%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB">简述 RUP 和 UML 之间的关系</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%E4%B8%8E%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E4%B8%A4%E4%B8%AA%E9%98%B6%E6%AE%B5%E4%BB%BB%E5%8A%A1%E7%9A%84%E4%B8%BB%E8%A6%81%E5%8C%BA%E5%88%AB">简述需求分析与软件设计两个阶段任务的主要区别</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E4%BA%8B%E5%8A%A1%E8%AE%BE%E8%AE%A1%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4">简述事务设计的基本步骤</a></li></ul></li><li><a href="#6-%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95">6. 软件测试</a><ul><li><a href="#%E7%99%BD%E7%9B%92%E6%B5%8B%E8%AF%95%E6%B3%95%E5%92%8C%E9%BB%91%E7%9B%92%E6%B5%8B%E8%AF%95%E6%B3%95%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88">白盒测试法和黑盒测试法的区别是什么?</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4">简述软件测试的基本步骤</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E8%B7%AF%E5%BE%84%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%87%A0%E7%A7%8D%E5%85%B8%E5%9E%8B%E7%9A%84%E6%B5%8B%E8%AF%95%E7%AD%96%E7%95%A5">简述路径测试中几种典型的测试策略。</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E5%9B%A0%E6%9E%9C%E5%9B%BE%E6%96%B9%E6%B3%95%E7%94%9F%E6%88%90%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4">简述因果图方法生成测试用例的基本步骤</a></li></ul></li><li><a href="#7-%E8%BD%AF%E4%BB%B6%E7%94%9F%E5%AD%98%E5%91%A8%E6%9C%9F%E8%BF%87%E7%A8%8B%E4%B8%8E%E7%AE%A1%E7%90%86">7. 软件生存周期过程与管理</a><ul><li><a href="#%E8%BD%AF%E4%BB%B6%E7%94%9F%E5%AD%98%E5%91%A8%E6%9C%9F%E6%A8%A1%E5%9E%8B">软件生存周期模型</a></li><li><a href="#%E7%AE%80%E8%BF%B0%E6%BC%94%E5%8C%96%E6%A8%A1%E5%9E%8B%E5%8F%8A%E5%85%B6%E4%B8%BB%E8%A6%81%E7%89%B9%E5%BE%81">简述演化模型及其主要特征</a></li></ul></li><li><a href="#8-%E9%9B%86%E6%88%90%E5%8C%96%E8%83%BD%E5%8A%9B%E6%88%90%E7%86%9F%E5%BA%A6%E6%A8%A1%E5%9E%8B">8. 集成化能力成熟度模型</a><ul><li><a href="#%E8%83%BD%E5%8A%9B%E7%AD%89%E7%BA%A7">能力等级</a></li><li><a href="#%E6%88%90%E7%86%9F%E5%BA%A6%E7%AD%89%E7%BA%A7">成熟度等级</a></li></ul></li><li><a href="#%E5%85%B6%E4%BB%96">其他</a></li></ul><span id="more"></span><h2 id="1-绪论"><a href="#1-绪论" class="headerlink" title="1. 绪论"></a>1. 绪论</h2><h3 id="简述软件危机与软件工程的概念以及提出软件工程概念的目的"><a href="#简述软件危机与软件工程的概念以及提出软件工程概念的目的" class="headerlink" title="简述软件危机与软件工程的概念以及提出软件工程概念的目的"></a>简述软件危机与软件工程的概念以及提出软件工程概念的目的</h3><ol><li><strong>软件危机</strong>: 是指软件生产率、软件质量远远不能满足社会发展的需求,成为社会、经济发展的制约因素的现象</li><li><strong>软件工程</strong>: 是应用计算机科学理论和技术以及工程管理原则和方法,按预算和进度实现满足用户需求的软件产品的工程,或以此为研究对象的学科</li><li><strong>提出软件工程概念的目的</strong>: 软件工程是倡导以工程的原理、原则和方法进行开发,以期解决出现的软件危机</li></ol><h3 id="简述软件开发的本质"><a href="#简述软件开发的本质" class="headerlink" title="简述软件开发的本质"></a>简述软件开发的本质</h3><p>不同抽象层<strong>术语之间</strong>的 “映射”,以及不同抽象层<strong>处理逻辑之间</strong>的映射。</p><h2 id="2-软件需求与软件需求规约"><a href="#2-软件需求与软件需求规约" class="headerlink" title="2. 软件需求与软件需求规约"></a>2. 软件需求与软件需求规约</h2><h3 id="初始发现需求"><a href="#初始发现需求" class="headerlink" title="初始发现需求"></a>初始发现需求</h3><p>初始发现需求的常见技术包括: 自悟、交谈、观察、小组会、提炼</p><h3 id="需求规约定义"><a href="#需求规约定义" class="headerlink" title="需求规约定义"></a>需求规约定义</h3><p>需求规约是一个软件项/产品/系统所有需求陈述的正式文档。它表述了软件产品/系统的概念模型。</p><p>它一般满足以下四点性质:</p><ol><li><strong>重要性和稳定性程度</strong>: 按需求的重要性和稳定性,对需求进行分级</li><li><strong>可修改的</strong>: 在不过多影响其他需求的前提下,可以容易修改单一的需求。</li><li><strong>完整的</strong>: 没有被遗漏的需求</li><li><strong>一致的</strong>: 不存在互斥的需求</li></ol><h3 id="需求规约的三种基本形式"><a href="#需求规约的三种基本形式" class="headerlink" title="需求规约的三种基本形式"></a>需求规约的三种基本形式</h3><!-- 2021.04 No.31 简述需求规约的三种基本形式 --><ol><li>非形式化的需求规约</li><li>半形式化的需求规约</li><li>形式化的需求规约</li></ol><h2 id="3-结构化方法"><a href="#3-结构化方法" class="headerlink" title="3. 结构化方法"></a>3. 结构化方法</h2><h3 id="模块的内聚性"><a href="#模块的内聚性" class="headerlink" title="模块的内聚性"></a>模块的内聚性</h3><p>内聚是测量一个模块化系统好坏的标准之一。主要分为7种评分:</p><ul><li><strong>功能内聚</strong> 10</li><li><strong>顺序内聚</strong> 9</li><li><strong>通信内聚</strong> 7</li><li>步骤内聚 5</li><li>时间内聚 3</li><li>逻辑内聚 1</li><li>偶然内聚 0</li></ul><p>前三种是可以接收的,后四种要尽量避免。</p><h3 id="模块间的耦合类型"><a href="#模块间的耦合类型" class="headerlink" title="模块间的耦合类型"></a>模块间的耦合类型</h3><p>常见的模块间耦合类型有 5 种,由强到弱有: 内容耦合、公共耦合、控制耦合、标记耦合、数据耦合。</p><h3 id="程序流程图主要用于软件开发的哪一阶段?它的主要优缺点有哪些?"><a href="#程序流程图主要用于软件开发的哪一阶段?它的主要优缺点有哪些?" class="headerlink" title="程序流程图主要用于软件开发的哪一阶段?它的主要优缺点有哪些?"></a>程序流程图主要用于软件开发的哪一阶段?它的主要优缺点有哪些?</h3><p>主要应用于 <strong>软件的详细设计阶段</strong>。</p><ul><li><p>主要优点是: <strong>对控制流程的描绘很直观,便于初学者掌握</strong>。</p></li><li><p>主要缺点是:</p><ol><li>不是一种逐步求精的工具,它诱使程序员过早地考虑程序的控制流程,而不考虑程序的全局结构。</li><li>所表达的控制流,往往不受任何约束,可随意转移。从而影响甚至破坏好的系统结构</li><li>不易表达数据结构</li></ol><img data-src="/blog/2021/02/10/note-023333/img-1.png" class="" title="系统流程图"></li></ul><h3 id="系统流程图与数据流程图有什么区别?"><a href="#系统流程图与数据流程图有什么区别?" class="headerlink" title="系统流程图与数据流程图有什么区别?"></a>系统流程图与数据流程图有什么区别?</h3><ol><li><strong>系统流程图</strong>是描述系统<strong>物理模型</strong>的工具,<strong>数据流程图</strong>是描述<strong>系统逻辑</strong>模型的工具。</li><li><strong>系统流程图</strong>从<strong>系统功能的角度</strong>抽象地描述<strong>系统的各个部分及其相互之间信息流动的情况</strong>。</li><li><strong>数据流程图</strong>从<strong>数据传送和加工的角度</strong>抽象地描述<strong>信息在系统中流动和数据处理的情况</strong>。</li></ol><h3 id="演化模型的主要特征是什么?它存在什么不足?"><a href="#演化模型的主要特征是什么?它存在什么不足?" class="headerlink" title="演化模型的主要特征是什么?它存在什么不足?"></a>演化模型的主要特征是什么?它存在什么不足?</h3><ol><li>该模型显式地把需求获取扩展到需求阶段,即为了第二个构造增量,使用了第一个构造增量来精化需求。</li><li>演化模型在一定程度上可以减少软件开发活动的满目性。</li></ol><p><strong>不足</strong>: 在演化模型的使用中,即便很好地理解了需求或设计,也<strong>很容易弱化需求分析阶段的工作</strong>。</p><h2 id="4-RUP、UML"><a href="#4-RUP、UML" class="headerlink" title="4. RUP、UML"></a>4. RUP、UML</h2><h3 id="图形工具的用途"><a href="#图形工具的用途" class="headerlink" title="图形工具的用途"></a>图形工具的用途</h3><ol><li>类图: 可视化表达<strong>系统静态结构模型</strong>的工具。</li><li>用况图: 表达<strong>系统功能模型</strong>的图形化工具。</li><li>状态图: 显示一个状态机的图。</li><li>顺序图: 一种交互图。由一组对象以及时序组织的对象之间的关系组成。</li></ol><h3 id="什么是-UML-它有什么特点?"><a href="#什么是-UML-它有什么特点?" class="headerlink" title="什么是 UML? 它有什么特点?"></a>什么是 UML? 它有什么特点?</h3><blockquote><p>UML: Unified Modeling Language, 统一建模语言</p></blockquote><ol><li>UML 是面向对象方法,它是一种根据客体之间的关系来构造系统模型的系统化方法。</li><li>UML 是一种可视化语言, 可用于规约系统的制品、构造系统的制品,建立系统制品的文档。这意味着 UML 可作为软件需求规约、设计和实现的工具。</li><li>UML 给出了方法学中不同抽象层次术语以及模型表达工具。</li></ol><h3 id="简述泛化的概念及其约束"><a href="#简述泛化的概念及其约束" class="headerlink" title="简述泛化的概念及其约束"></a>简述泛化的概念及其约束</h3><p>泛化是一般性类目(父类)和它的较为特殊的类目(子类)之间的关系,有时称为 “is-a-kind-of” 关系,UML 给出了 4 个约束:</p><ol><li>完整</li><li>不完整</li><li>互斥</li><li>重叠</li></ol><h3 id="RUP-的特点之一是迭代、增量式开发,它规定了-4-个开发阶段。请简述每次迭代在各阶段的目标。"><a href="#RUP-的特点之一是迭代、增量式开发,它规定了-4-个开发阶段。请简述每次迭代在各阶段的目标。" class="headerlink" title="RUP 的特点之一是迭代、增量式开发,它规定了 4 个开发阶段。请简述每次迭代在各阶段的目标。"></a>RUP 的特点之一是迭代、增量式开发,它规定了 4 个开发阶段。请简述每次迭代在各阶段的目标。</h3><ol><li><strong>初始阶段</strong>的基本目标:获得与特定用况和平台无关的系统体系结构轮廓,已建立产品功能范围;编制初始业务示例,从业务角度指出该项目的价值,减少项目主要错误风险。</li><li><strong>精化阶段</strong>的基本目标:捕获并描述系统的大部分需求,建立系统体系结构基线的第一个版本,主要包括用况模型和分析模型,减少次要的错误风险;到该阶段末,就能估算成本、进度,并能详细地规划构造阶段。</li><li><strong>构造阶段</strong>的基本目标:通过演化,形成最终的系统体系结构基线,开发完整的系统,确保产品可以开始向客户交付。</li><li><strong>移交阶段</strong>的基本目标:确保有一个实在的产品发布给用户群。</li></ol><h3 id="简述-RUP-和-UML-之间的关系"><a href="#简述-RUP-和-UML-之间的关系" class="headerlink" title="简述 RUP 和 UML 之间的关系"></a>简述 RUP 和 UML 之间的关系</h3><ol><li>RUP 和 UML 构成了一种特定的软件开发方法学</li><li>UML 作为一种可视化建模语言,给出了<strong>表达事务和事务之间关系的基本术语</strong>,给出了多种模型的表达工具。</li><li>RUP 利用 UML 的术语定义了 <strong>需求获取层</strong>、<strong>系统建模层</strong>、<strong>设计层</strong>、<strong>实现层</strong>,并给出各层模型映射的基本活动以及相关的指导。</li></ol><h3 id="简述需求分析与软件设计两个阶段任务的主要区别"><a href="#简述需求分析与软件设计两个阶段任务的主要区别" class="headerlink" title="简述需求分析与软件设计两个阶段任务的主要区别"></a>简述需求分析与软件设计两个阶段任务的主要区别</h3><p><strong>需求分析阶段</strong>的主要任务是<strong>定义软件的用户需求</strong>,即<strong>定义待开发的软件能做什么</strong>。</p><p><strong>软件设计阶段</strong>的主要任务是<strong>定义软件的实现细节,以满足用户需求</strong>,即<strong>研究如何实现软件</strong>。</p><h3 id="简述事务设计的基本步骤"><a href="#简述事务设计的基本步骤" class="headerlink" title="简述事务设计的基本步骤"></a>简述事务设计的基本步骤</h3><ol><li>设计准备,复审并精化系统的模型</li><li>确定事务处理中心</li><li>设计系统模块结构图的顶层和第一层</li><li>自顶向下,逐步求精</li></ol><h2 id="6-软件测试"><a href="#6-软件测试" class="headerlink" title="6. 软件测试"></a>6. 软件测试</h2><p>软件测试技术一般分为白盒测试技术和黑盒测试技术。</p><h3 id="白盒测试法和黑盒测试法的区别是什么?"><a href="#白盒测试法和黑盒测试法的区别是什么?" class="headerlink" title="白盒测试法和黑盒测试法的区别是什么?"></a>白盒测试法和黑盒测试法的区别是什么?</h3><p>白盒测试法<strong>完全了解程序的结构和处理过程</strong>,这种方法按照程序内部的逻辑结构以及有关信息设计或选择测试你用例,检查程序中每条通路是否都能按照预定要求正确工作。</p><p>黑盒测试法<strong>着眼于软件的外部特征,不考虑软件的内部逻辑和内部特征</strong>,只依据程序的需求规格说明书检查是否满足功能要求,测试要在软件的接口处进行。</p><h3 id="简述软件测试的基本步骤"><a href="#简述软件测试的基本步骤" class="headerlink" title="简述软件测试的基本步骤"></a>简述软件测试的基本步骤</h3><ol><li><strong>单元测试</strong>: 主要检验软件设计的最小单元——模块。该测试以详细设计文档为指导,测试模块内的重要控制路径。</li><li><strong>集成测试</strong>: 集成测试是软件组装的一个系统化技术,其目标是<strong>发现与接口有关的错误</strong>。将经过单元测试的模块构成一个满足设计要求的软件结构。</li><li><strong>有效性测试</strong>: 目标是发现<strong>软件实现的功能与需求规格说明书不一致的地方</strong>。</li><li><strong>系统测试</strong>: 验证将软件运行于更大系统中时整个系统的有效性。</li></ol><h3 id="简述路径测试中几种典型的测试策略。"><a href="#简述路径测试中几种典型的测试策略。" class="headerlink" title="简述路径测试中几种典型的测试策略。"></a>简述路径测试中几种典型的测试策略。</h3><ol><li>路径覆盖: 执行所有与可能穿过程序控制流的路径。(T 条件走一遍)</li><li>语句覆盖: 至少执行过程中所有语句一次。</li><li>分支覆盖: 至少将程序中每一个分支执行一次。</li><li>条件覆盖: 每个判定的<strong>所有可能的条件取值</strong>至少执行一次。(全取假值)</li><li>条件组合覆盖: 设计足够多的测试用例,使每个判定中的所有可能的条件取值组合至少执行一次。</li></ol><h3 id="简述因果图方法生成测试用例的基本步骤"><a href="#简述因果图方法生成测试用例的基本步骤" class="headerlink" title="简述因果图方法生成测试用例的基本步骤"></a>简述因果图方法生成测试用例的基本步骤</h3><ol><li>通过对软件规格说明书的分析,找出一个模块的原因和结果,并给每个原因和结果赋予一个标识符。</li><li>分析原因和结果之间以及原因与原因之间对应的关系,并画出因果图</li><li>在因果图上表示一些特定的约束或限制条件</li><li>把因果图转换判定表</li><li>为判定表的每一列设计测试用例</li></ol><h2 id="7-软件生存周期过程与管理"><a href="#7-软件生存周期过程与管理" class="headerlink" title="7. 软件生存周期过程与管理"></a>7. 软件生存周期过程与管理</h2><h3 id="软件生存周期模型"><a href="#软件生存周期模型" class="headerlink" title="软件生存周期模型"></a>软件生存周期模型</h3><p>时间顺序:</p><ul><li><strong>瀑布模型</strong>(1970, 20世纪60年代到80年代的主要成果)</li><li>演化模型</li><li>螺旋模型(1988, 加入了前两者所忽略的风险分析)</li><li>喷泉模型: 体现了软件创建所固有的迭代和无间隙的特征。</li></ul><h3 id="简述演化模型及其主要特征"><a href="#简述演化模型及其主要特征" class="headerlink" title="简述演化模型及其主要特征"></a>简述演化模型及其主要特征</h3><p>演化模型主要针对<strong>事先不能完整定义需求</strong>的软件开发,在用户提出待开发系统的核心需求的基础上,软件开发人员首先开发一个核心系统并投入运行,<strong>以便用户能够有效地提出反馈</strong>,即提出精化系统能力的需求。</p><p>接着,软件开发人员根据用户反馈,实施开发的迭代过程均由需求,设计,编码、测试、集成等阶段组成。为整个系统添加一个可定制的、可管理的子集;如果在一次迭代中,有的需求不能满足用户的要求,可在下一次迭代中予以修正。</p><p>演化模型的主要特征是: 该模型<strong>显式地把需求获取扩展到需求阶段</strong>,即为了第二个构造增量使用,使用第一个构造增量来精化需求。</p><h2 id="8-集成化能力成熟度模型"><a href="#8-集成化能力成熟度模型" class="headerlink" title="8. 集成化能力成熟度模型"></a>8. 集成化能力成熟度模型</h2><h3 id="能力等级"><a href="#能力等级" class="headerlink" title="能力等级"></a>能力等级</h3><p>一种过程改善路径,该路径可使组织<strong>针对单一过程域</strong>不断改善该过程域。</p><ul><li>0 级: 未完成级</li><li>1 级: 已执行级</li><li>2 级: 已管理级</li><li>3 级: 已定义级</li><li>4 级: 已定量管理级</li><li>5 级: 持续优化级</li></ul><h3 id="成熟度等级"><a href="#成熟度等级" class="headerlink" title="成熟度等级"></a>成熟度等级</h3><p>一种过程改善路径,该路径可使组织<strong>针对一组过程域</strong>不断改善该过程域。</p><ul><li>1 级: 初始级</li><li>2 级: 已管理级</li><li>3 级: 已定义级</li><li>4 级: 已定量管理级</li><li>5 级: 持续优化级</li></ul><p>能力等级和成熟度等级等级只有 1 级名称不同,2~5 是相等名称。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><ol><li>计算机软件一般是指计算机系统中的程序及其 <strong>文档(或数据和文档)</strong></li><li>对于单一一个需求,必须具有的基本性质: <strong>必要的</strong>、<strong>无歧义的</strong>、<strong>可追踪的</strong>、<strong>可测试的</strong>、<strong>可测量的</strong>。</li><li>需求人员通过提出问题/用户回答的方式,直接询问用户需要的初始发现需求技术是 <strong>交谈</strong>。</li><li>在结构化分析方法中,表示 “数据的静态结构“ 的术语是 <strong>数据存储</strong>。</li><li>为保证加入的模块没有引进新的错误,可能需要进行<strong>回归</strong>测试。</li><li>CMMI 的成熟度等级和能力等级还可用于<strong>评选活动</strong>和<strong>估算</strong>。</li></ol>]]></content>
<summary type="html"><p>软件工程相关笔记。</p>
<hr>
<ul>
<li><a href="#1-%E7%BB%AA%E8%AE%BA">1. 绪论</a><ul>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%BD%AF%E4%BB%B6%E5%8D%B1%E6%9C%BA%E4%B8%8E%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E6%8F%90%E5%87%BA%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E6%A6%82%E5%BF%B5%E7%9A%84%E7%9B%AE%E7%9A%84">简述软件危机与软件工程的概念以及提出软件工程概念的目的</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E7%9A%84%E6%9C%AC%E8%B4%A8">简述软件开发的本质</a></li>
</ul>
</li>
<li><a href="#2-%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E4%B8%8E%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E8%A7%84%E7%BA%A6">2. 软件需求与软件需求规约</a><ul>
<li><a href="#%E5%88%9D%E5%A7%8B%E5%8F%91%E7%8E%B0%E9%9C%80%E6%B1%82">初始发现需求</a></li>
<li><a href="#%E9%9C%80%E6%B1%82%E8%A7%84%E7%BA%A6%E5%AE%9A%E4%B9%89">需求规约定义</a></li>
<li><a href="#%E9%9C%80%E6%B1%82%E8%A7%84%E7%BA%A6%E7%9A%84%E4%B8%89%E7%A7%8D%E5%9F%BA%E6%9C%AC%E5%BD%A2%E5%BC%8F">需求规约的三种基本形式</a></li>
</ul>
</li>
<li><a href="#3-%E7%BB%93%E6%9E%84%E5%8C%96%E6%96%B9%E6%B3%95">3. 结构化方法</a><ul>
<li><a href="#%E6%A8%A1%E5%9D%97%E7%9A%84%E5%86%85%E8%81%9A%E6%80%A7">模块的内聚性</a></li>
<li><a href="#%E6%A8%A1%E5%9D%97%E9%97%B4%E7%9A%84%E8%80%A6%E5%90%88%E7%B1%BB%E5%9E%8B">模块间的耦合类型</a></li>
<li><a href="#%E7%A8%8B%E5%BA%8F%E6%B5%81%E7%A8%8B%E5%9B%BE%E4%B8%BB%E8%A6%81%E7%94%A8%E4%BA%8E%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E7%9A%84%E5%93%AA%E4%B8%80%E9%98%B6%E6%AE%B5%E5%AE%83%E7%9A%84%E4%B8%BB%E8%A6%81%E4%BC%98%E7%BC%BA%E7%82%B9%E6%9C%89%E5%93%AA%E4%BA%9B">程序流程图主要用于软件开发的哪一阶段?它的主要优缺点有哪些?</a></li>
<li><a href="#%E7%B3%BB%E7%BB%9F%E6%B5%81%E7%A8%8B%E5%9B%BE%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%B5%81%E7%A8%8B%E5%9B%BE%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB">系统流程图与数据流程图有什么区别?</a></li>
<li><a href="#%E6%BC%94%E5%8C%96%E6%A8%A1%E5%9E%8B%E7%9A%84%E4%B8%BB%E8%A6%81%E7%89%B9%E5%BE%81%E6%98%AF%E4%BB%80%E4%B9%88%E5%AE%83%E5%AD%98%E5%9C%A8%E4%BB%80%E4%B9%88%E4%B8%8D%E8%B6%B3">演化模型的主要特征是什么?它存在什么不足?</a></li>
</ul>
</li>
<li><a href="#4-rupuml">4. RUP、UML</a><ul>
<li><a href="#%E5%9B%BE%E5%BD%A2%E5%B7%A5%E5%85%B7%E7%9A%84%E7%94%A8%E9%80%94">图形工具的用途</a></li>
<li><a href="#%E4%BB%80%E4%B9%88%E6%98%AF-uml-%E5%AE%83%E6%9C%89%E4%BB%80%E4%B9%88%E7%89%B9%E7%82%B9">什么是 UML? 它有什么特点?</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E6%B3%9B%E5%8C%96%E7%9A%84%E6%A6%82%E5%BF%B5%E5%8F%8A%E5%85%B6%E7%BA%A6%E6%9D%9F">简述泛化的概念及其约束</a></li>
<li><a href="#rup-%E7%9A%84%E7%89%B9%E7%82%B9%E4%B9%8B%E4%B8%80%E6%98%AF%E8%BF%AD%E4%BB%A3%E5%A2%9E%E9%87%8F%E5%BC%8F%E5%BC%80%E5%8F%91%E5%AE%83%E8%A7%84%E5%AE%9A%E4%BA%86-4-%E4%B8%AA%E5%BC%80%E5%8F%91%E9%98%B6%E6%AE%B5%E8%AF%B7%E7%AE%80%E8%BF%B0%E6%AF%8F%E6%AC%A1%E8%BF%AD%E4%BB%A3%E5%9C%A8%E5%90%84%E9%98%B6%E6%AE%B5%E7%9A%84%E7%9B%AE%E6%A0%87">RUP 的特点之一是迭代、增量式开发,它规定了 4 个开发阶段。请简述每次迭代在各阶段的目标。</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0-rup-%E5%92%8C-uml-%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB">简述 RUP 和 UML 之间的关系</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%E4%B8%8E%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E4%B8%A4%E4%B8%AA%E9%98%B6%E6%AE%B5%E4%BB%BB%E5%8A%A1%E7%9A%84%E4%B8%BB%E8%A6%81%E5%8C%BA%E5%88%AB">简述需求分析与软件设计两个阶段任务的主要区别</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E4%BA%8B%E5%8A%A1%E8%AE%BE%E8%AE%A1%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4">简述事务设计的基本步骤</a></li>
</ul>
</li>
<li><a href="#6-%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95">6. 软件测试</a><ul>
<li><a href="#%E7%99%BD%E7%9B%92%E6%B5%8B%E8%AF%95%E6%B3%95%E5%92%8C%E9%BB%91%E7%9B%92%E6%B5%8B%E8%AF%95%E6%B3%95%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88">白盒测试法和黑盒测试法的区别是什么?</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4">简述软件测试的基本步骤</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E8%B7%AF%E5%BE%84%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%87%A0%E7%A7%8D%E5%85%B8%E5%9E%8B%E7%9A%84%E6%B5%8B%E8%AF%95%E7%AD%96%E7%95%A5">简述路径测试中几种典型的测试策略。</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E5%9B%A0%E6%9E%9C%E5%9B%BE%E6%96%B9%E6%B3%95%E7%94%9F%E6%88%90%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4">简述因果图方法生成测试用例的基本步骤</a></li>
</ul>
</li>
<li><a href="#7-%E8%BD%AF%E4%BB%B6%E7%94%9F%E5%AD%98%E5%91%A8%E6%9C%9F%E8%BF%87%E7%A8%8B%E4%B8%8E%E7%AE%A1%E7%90%86">7. 软件生存周期过程与管理</a><ul>
<li><a href="#%E8%BD%AF%E4%BB%B6%E7%94%9F%E5%AD%98%E5%91%A8%E6%9C%9F%E6%A8%A1%E5%9E%8B">软件生存周期模型</a></li>
<li><a href="#%E7%AE%80%E8%BF%B0%E6%BC%94%E5%8C%96%E6%A8%A1%E5%9E%8B%E5%8F%8A%E5%85%B6%E4%B8%BB%E8%A6%81%E7%89%B9%E5%BE%81">简述演化模型及其主要特征</a></li>
</ul>
</li>
<li><a href="#8-%E9%9B%86%E6%88%90%E5%8C%96%E8%83%BD%E5%8A%9B%E6%88%90%E7%86%9F%E5%BA%A6%E6%A8%A1%E5%9E%8B">8. 集成化能力成熟度模型</a><ul>
<li><a href="#%E8%83%BD%E5%8A%9B%E7%AD%89%E7%BA%A7">能力等级</a></li>
<li><a href="#%E6%88%90%E7%86%9F%E5%BA%A6%E7%AD%89%E7%BA%A7">成熟度等级</a></li>
</ul>
</li>
<li><a href="#%E5%85%B6%E4%BB%96">其他</a></li>
</ul></summary>
</entry>
<entry>
<title>JavaScript 实现二叉树</title>
<link href="https://anran758.github.io/blog/2021/01/05/binary-tree/"/>
<id>https://anran758.github.io/blog/2021/01/05/binary-tree/</id>
<published>2021-01-05T06:35:00.000Z</published>
<updated>2022-05-23T06:14:04.426Z</updated>
<content type="html"><![CDATA[<p>二叉树数据结构的学习与笔记。</p><!-- omit in toc --><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><a href="#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%82%A8%E5%AD%98%E7%BB%93%E6%9E%84">二叉树的储存结构</a></li><li><a href="#%E9%A1%BA%E5%BA%8F%E7%BB%93%E6%9E%84%E8%BD%AC%E9%93%BE%E5%BC%8F%E7%BB%93%E6%9E%84">顺序结构转链式结构</a></li><li><a href="#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%81%8D%E5%8E%86">二叉树的遍历</a><ul><li><a href="#%E5%89%8D%E5%BA%8F%E5%BA%8F%E9%81%8D%E5%8E%86">前序序遍历</a></li><li><a href="#%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86">中序遍历</a></li><li><a href="#%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86">后序遍历</a></li><li><a href="#%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86">层序遍历</a></li></ul></li><li><a href="#%E5%90%88%E5%B9%B6%E4%BA%8C%E5%8F%89%E6%A0%91">合并二叉树</a></li><li><a href="#%E4%BA%8C%E5%8F%89%E6%8E%92%E5%BA%8F%E6%A0%91-bst">二叉排序树 (BST)</a><ul><li><a href="#%E9%AB%98%E5%BA%A6%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91">高度平衡二叉搜索树</a></li><li><a href="#%E5%88%A4%E6%96%AD%E6%8C%87%E5%AE%9A%E6%A0%91%E6%98%AF%E5%90%A6%E6%98%AF%E5%B9%B3%E8%A1%A1%E6%A0%91">判断指定树是否是平衡树</a></li></ul></li></ul><span id="more"></span><h2 id="二叉树的储存结构"><a href="#二叉树的储存结构" class="headerlink" title="二叉树的储存结构"></a>二叉树的储存结构</h2><p>二叉树有两种储存方式,一种是顺序储存结构,一种是链式储存结构。</p><p>顺序储存结构就是二叉树从上至下,每层从左到右给树中节点进行编号:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">0</span>,<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]</span><br></pre></td></tr></table></figure><p>0 是根节点,1 是根的左节点,2 是根的右节点,3 是根的左节点的左节点,4 是根的左节点的右节点…… 依照这个顺序排列下去。设 <code>i</code> 为顺序表中节点的索引, <code>Qi</code> 代表顺序表上储存的节点, <code>n</code> 为顺序表的长度,则可知:</p><ol><li><code>i = 0</code>,<code>Qi</code> 节点是根节点</li><li>若 <code>2i+1 < n</code>, 则索引 <code>2i+1</code> 上储存的是 <code>Qi</code> 的左节点。反之,则没有节点。</li><li>若 <code>2i+2 < n</code>, 则索引 <code>2i+2</code> 上储存的是 <code>Qi</code> 的右节点。反之,则没有节点。</li><li>**<code>Qi</code> 的双亲节点的索引为 <code>(i-1)/2</code>**。比如 <code>i=4</code>, <code>(i-1)/2</code> 向下取整等于 <code>1</code>, 索引为 <code>4</code> 的双亲节点为 <code>1</code>。</li></ol><p>链式储存的结构大致如下:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TreeNode</span> </span>{</span><br><span class="line"> val: <span class="built_in">number</span></span><br><span class="line"> left: TreeNode | <span class="literal">null</span></span><br><span class="line"> right: TreeNode | <span class="literal">null</span></span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">val?: <span class="built_in">number</span>, left?: TreeNode | <span class="literal">null</span>, right?: TreeNode | <span class="literal">null</span></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.val = (val===<span class="literal">undefined</span> ? <span class="number">0</span> : val)</span><br><span class="line"> <span class="built_in">this</span>.left = (left===<span class="literal">undefined</span> ? <span class="literal">null</span> : left)</span><br><span class="line"> <span class="built_in">this</span>.right = (right===<span class="literal">undefined</span> ? <span class="literal">null</span> : right)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="顺序结构转链式结构"><a href="#顺序结构转链式结构" class="headerlink" title="顺序结构转链式结构"></a>顺序结构转链式结构</h2><p>利用二叉树的性质,可以将顺序储存方式转换为对应的链式结构:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TreeNode</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">val, left, right</span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.val = (val === <span class="literal">undefined</span> ? <span class="number">0</span> : val)</span><br><span class="line"> <span class="built_in">this</span>.left = (left === <span class="literal">undefined</span> ? <span class="literal">null</span> : left)</span><br><span class="line"> <span class="built_in">this</span>.right = (right === <span class="literal">undefined</span> ? <span class="literal">null</span> : right)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toLinkedListBinaryTree</span>(<span class="params">list</span>) </span>{</span><br><span class="line"> <span class="comment">// 临时用于储存被转换为链表的节点</span></span><br><span class="line"> <span class="keyword">const</span> nodelist = [];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < list.length; i++) {</span><br><span class="line"> <span class="keyword">const</span> node = <span class="keyword">new</span> TreeNode(list[i]);</span><br><span class="line"> nodelist.push(node);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 根节点没有双亲节点</span></span><br><span class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 由结论 4 可得双亲节点的索引</span></span><br><span class="line"> <span class="keyword">const</span> parentIdx = <span class="built_in">Math</span>.floor((i - <span class="number">1</span>) / <span class="number">2</span>);</span><br><span class="line"> <span class="keyword">const</span> parent = nodelist[parentIdx];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 当前层从左向右赋值,若左节点被赋值,则剩下右节点没有被赋值</span></span><br><span class="line"> <span class="keyword">if</span> (parent.left) {</span><br><span class="line"> parent.right = node;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> parent.left = node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> nodelist.shift()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在 console 进行测试</span></span><br><span class="line">cnsole.log(toLinkedListBinaryTree([<span class="number">0</span>,<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">9</span>]));</span><br></pre></td></tr></table></figure><h2 id="二叉树的遍历"><a href="#二叉树的遍历" class="headerlink" title="二叉树的遍历"></a>二叉树的遍历</h2><p>遍历二叉树是指沿着某条搜索路径周游二叉树,依次对树中的每个节点访问且仅访问一次。</p><p>二叉树的遍历方式可以分为<strong>递归</strong>和<strong>非递归</strong>方式。遍历算法也可以分为**深度优先搜索 (Depth-First-Search,DFS)<strong>和</strong>广度优先搜索 (Breadth-First Search)**。</p><p>根据二叉树的递归定义,遍历一颗非空二叉树的问题可分为三个子问题: 访问根节点 (D),遍历左子树 (L),遍历右子树 (R)。遍历的顺序可分为: DLR (前序)、LDR (中序)、LRD (后序) 和 DRL (前序)、RDL (中序)、RLD (后序)。前三种是先左后右,后三种是先右后左。一般没有提别指明的话,我们谈论二叉树的遍历,都是在讲前三种。</p><p>二叉树的前序遍历、中序遍历、后序遍历都可以通过<strong>递归方式</strong>和<strong>非递归方式</strong>实现。</p><h3 id="前序序遍历"><a href="#前序序遍历" class="headerlink" title="前序序遍历"></a>前序序遍历</h3><p>递归形式:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">preorderTraversal</span>(<span class="params">root: TreeNode | <span class="literal">null</span></span>): <span class="title">number</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> postorder(root, [])</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">postorder</span>(<span class="params">root?: TreeNode, result = []</span>): <span class="title">number</span>[] </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root) <span class="keyword">return</span> result;</span><br><span class="line"></span><br><span class="line"> result.push(root.val);</span><br><span class="line"> postorder(root.left, result);</span><br><span class="line"> postorder(root.right, result);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="中序遍历"><a href="#中序遍历" class="headerlink" title="中序遍历"></a>中序遍历</h3><p>递归形式:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">inorderTraversal</span>(<span class="params">root: TreeNode | <span class="literal">null</span></span>): <span class="title">number</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> inorder(root, [])</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">inorder</span>(<span class="params">root?: TreeNode, result = []</span>): <span class="title">number</span>[] </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root) <span class="keyword">return</span> result;</span><br><span class="line"></span><br><span class="line"> inorder(root.left, result);</span><br><span class="line"> result.push(root.val);</span><br><span class="line"> inorder(root.right, result);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="后序遍历"><a href="#后序遍历" class="headerlink" title="后序遍历"></a>后序遍历</h3><p>递归形式:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">postorderTraversal</span>(<span class="params">root: TreeNode | <span class="literal">null</span></span>): <span class="title">number</span>[] </span>{</span><br><span class="line"> <span class="keyword">return</span> postorder(root, [])</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">postorder</span>(<span class="params">root?: TreeNode, result = []</span>): <span class="title">number</span>[] </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root) <span class="keyword">return</span> result;</span><br><span class="line"></span><br><span class="line"> postorder(root.left, result);</span><br><span class="line"> postorder(root.right, result);</span><br><span class="line"> result.push(root.val);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="层序遍历"><a href="#层序遍历" class="headerlink" title="层序遍历"></a>层序遍历</h3><p>层序遍历就是把二叉树分层,然后每一层从左到右遍历:</p><img data-src="/blog/2021/01/05/binary-tree/hierarchical_diagram.jpg" class=""><p>层序遍历二叉树很自然就能想到使用 BFS(广度优先搜索) 来遍历每层。</p><p>该算法采用一个队列来缓存二叉树的节点,若树不为空,先将二叉树根节点输出,先将根节点入队,再到循环体内出队。若根节点还有左孩子,则将左孩子也添加到队列中。若有右孩子,也将右孩子也添加到队列中。如此下去,直到队列为空:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 按层输出二叉树的值</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">levelOrder</span>(<span class="params">root: TreeNode | <span class="literal">null</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 队列,先进先出</span></span><br><span class="line"> <span class="keyword">const</span> queue = [root];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (queue.length) {</span><br><span class="line"> <span class="comment">// 取队首的元素</span></span><br><span class="line"> <span class="keyword">const</span> node = queue.shift();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'node --> '</span>, node.val)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若有左右节点,则添加至队列</span></span><br><span class="line"> <span class="keyword">if</span> (node.left) queue.push(node.left);</span><br><span class="line"> <span class="keyword">if</span> (node.right) queue.push(node.right);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>若想将每一层的值都存入数组中,则可以采用二维数组进行储存:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">levelOrder</span>(<span class="params">root: TreeNode | <span class="literal">null</span></span>): <span class="title">number</span>[][] </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root) <span class="keyword">return</span> [];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最终会返回的结果</span></span><br><span class="line"> <span class="keyword">const</span> result = [];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 队列,先进先出</span></span><br><span class="line"> <span class="keyword">const</span> queue = [root];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (queue.length) {</span><br><span class="line"> <span class="comment">// 当前层级</span></span><br><span class="line"> <span class="keyword">const</span> level = [];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 当前队列的长度</span></span><br><span class="line"> <span class="keyword">const</span> n = queue.length;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < n; i += <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">const</span> node = queue.shift();</span><br><span class="line"> level.push(node.val);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若有左右节点,则添加至队列</span></span><br><span class="line"> <span class="comment">// 由于已经储存上一轮的节点数,因此这里不会影响 n 的值</span></span><br><span class="line"> <span class="keyword">if</span> (node.left) queue.push(node.left);</span><br><span class="line"> <span class="keyword">if</span> (node.right) queue.push(node.right);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> result.push(level);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="合并二叉树"><a href="#合并二叉树" class="headerlink" title="合并二叉树"></a><a href="https://leetcode-cn.com/problems/merge-two-binary-trees/">合并二叉树</a></h2><p>不考虑副作用的话,可以直接将 root1 作为结果,修改 root1 的值即可。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mergeTrees</span>(<span class="params">root1?: TreeNode, root2?: TreeNode</span>): <span class="title">TreeNode</span> | <span class="title">null</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root1 || !root2) <span class="keyword">return</span> root1 || root2;</span><br><span class="line"></span><br><span class="line"> root1.val += root2.val;</span><br><span class="line"> root1.left = mergeTrees(root1.left, root2.left);</span><br><span class="line"> root1.right = mergeTrees(root1.right, root2.right);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> root1;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="二叉排序树-BST"><a href="#二叉排序树-BST" class="headerlink" title="二叉排序树 (BST)"></a>二叉排序树 (BST)</h2><p>二叉排序树(Binary Sort Tree)又称二叉查找树,它是一种特殊的二叉树,它或为空树,或具有以下性质的二叉树:</p><ol><li>它的右子树非空,则右子树上所有节点的值都大于根节点的值。</li><li>它的左子树非空,则左子树上所有节点的值都小于根节点的值。</li><li>左右子树各是一颗二叉排序树。</li></ol><p>以下为创建二叉排序树的代码:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sortedArrayToBST</span>(<span class="params">nums: <span class="built_in">number</span>[]</span>): <span class="title">TreeNode</span> | <span class="title">null</span> </span>{</span><br><span class="line"> <span class="keyword">let</span> tree = <span class="literal">null</span>, node;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(nums.length) {</span><br><span class="line"> node = <span class="keyword">new</span> TreeNode(nums.shift())</span><br><span class="line"> tree = insertBST(tree, node)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> tree;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">insertBST</span>(<span class="params">tree: TreeNode, node: TreeNode</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> parent, p = tree;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(p) {</span><br><span class="line"> <span class="comment">// parent 指向 p 的双亲</span></span><br><span class="line"> parent = p;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 要插入的节点的值小于 p 的值,赋值为左节点</span></span><br><span class="line"> <span class="comment">// 要插入的节点的值大于 p 的值,赋值为右节点</span></span><br><span class="line"> p = node.val < p.val ? p.left : p.right;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tree == <span class="literal">null</span>) <span class="keyword">return</span> node;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// console.log('p',parent.val, node.val)</span></span><br><span class="line"> <span class="keyword">if</span>(node.val < parent.val) {</span><br><span class="line"> parent.left = node;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> parent.right = node;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> tree;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="高度平衡二叉搜索树"><a href="#高度平衡二叉搜索树" class="headerlink" title="高度平衡二叉搜索树"></a><a href="https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/">高度平衡二叉搜索树</a></h3><p><strong>高度平衡</strong>二叉树是一棵满足「每个节点的左右两个子树的<strong>高度差</strong>的绝对值不超过 1」的二叉树。</p><p>Q: 给定已按升序排序的整数数组,将其构建为二叉树。</p><p>A: 因为数组已经排过序了,因此可以直接采用二分法进行构建。先去中间的元素,再向两侧递归构建:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sortedArrayToBST</span>(<span class="params">nums: <span class="built_in">number</span>[]</span>): <span class="title">TreeNode</span> | <span class="title">null</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> dfs(nums, <span class="number">0</span>, nums.length - <span class="number">1</span>)</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">dfs</span>(<span class="params">nums: <span class="built_in">number</span>[], min: <span class="built_in">number</span>, max: <span class="built_in">number</span></span>): <span class="title">TreeNode</span> | <span class="title">null</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (min > max) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 取中间的索引,先减后加的方式可以避免索引值溢出</span></span><br><span class="line"> <span class="keyword">const</span> mid = min + <span class="built_in">Math</span>.floor((max - min) / <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 由于是采用二分法,因此左右子树的高度差不会超过 1</span></span><br><span class="line"> <span class="keyword">const</span> root = <span class="keyword">new</span> TreeNode(</span><br><span class="line"> nums[mid],</span><br><span class="line"> dfs(nums, min, mid - <span class="number">1</span>),</span><br><span class="line"> dfs(nums, mid + <span class="number">1</span>, max)</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> root;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="判断指定树是否是平衡树"><a href="#判断指定树是否是平衡树" class="headerlink" title="判断指定树是否是平衡树"></a>判断指定树是否是平衡树</h3><p>可以采用自底向上进行遍历,该遍历方法类似于后序遍历:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isBalanced</span>(<span class="params">root: TreeNode | <span class="literal">null</span></span>): <span class="title">boolean</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> height(root) !== -<span class="number">1</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">height</span>(<span class="params">root?: TreeNode</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!root) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> left = height(root.left);</span><br><span class="line"> <span class="keyword">if</span> (left == -<span class="number">1</span>) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> right = height(root.right);</span><br><span class="line"> <span class="keyword">if</span> (right == -<span class="number">1</span>) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 高度差超过 1</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">Math</span>.abs(left - right) > <span class="number">1</span>) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 当前层 + 1</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.max(left, right) + <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>时间复杂度:O(n)O(n),其中 nn 是二叉树中的节点个数。使用自底向上的递归,每个节点的计算高度和判断是否平衡都只需要处理一次,最坏情况下需要遍历二叉树中的所有节点,因此时间复杂度是 O(n)O(n)。</li><li>空间复杂度:O(n)O(n),其中 nn 是二叉树中的节点个数。空间复杂度主要取决于递归调用的层数,递归调用的层数不会超过 nn。</li></ul>]]></content>
<summary type="html"><p>二叉树数据结构的学习与笔记。</p>
<!-- omit in toc -->
<h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul>
<li><a href="#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%82%A8%E5%AD%98%E7%BB%93%E6%9E%84">二叉树的储存结构</a></li>
<li><a href="#%E9%A1%BA%E5%BA%8F%E7%BB%93%E6%9E%84%E8%BD%AC%E9%93%BE%E5%BC%8F%E7%BB%93%E6%9E%84">顺序结构转链式结构</a></li>
<li><a href="#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%81%8D%E5%8E%86">二叉树的遍历</a><ul>
<li><a href="#%E5%89%8D%E5%BA%8F%E5%BA%8F%E9%81%8D%E5%8E%86">前序序遍历</a></li>
<li><a href="#%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86">中序遍历</a></li>
<li><a href="#%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86">后序遍历</a></li>
<li><a href="#%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86">层序遍历</a></li>
</ul>
</li>
<li><a href="#%E5%90%88%E5%B9%B6%E4%BA%8C%E5%8F%89%E6%A0%91">合并二叉树</a></li>
<li><a href="#%E4%BA%8C%E5%8F%89%E6%8E%92%E5%BA%8F%E6%A0%91-bst">二叉排序树 (BST)</a><ul>
<li><a href="#%E9%AB%98%E5%BA%A6%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91">高度平衡二叉搜索树</a></li>
<li><a href="#%E5%88%A4%E6%96%AD%E6%8C%87%E5%AE%9A%E6%A0%91%E6%98%AF%E5%90%A6%E6%98%AF%E5%B9%B3%E8%A1%A1%E6%A0%91">判断指定树是否是平衡树</a></li>
</ul>
</li>
</ul></summary>
<category term="数据结构" scheme="https://anran758.github.io/blog/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="数据结构" scheme="https://anran758.github.io/blog/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="二叉树" scheme="https://anran758.github.io/blog/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
</entry>
<entry>
<title>闭包与链式设计的使用示例</title>
<link href="https://anran758.github.io/blog/2020/12/22/closure-and-function-chaining/"/>
<id>https://anran758.github.io/blog/2020/12/22/closure-and-function-chaining/</id>
<published>2020-12-22T12:30:09.000Z</published>
<updated>2021-10-04T09:19:41.613Z</updated>
<content type="html"><![CDATA[<p>最近遇到了个按需请求数据的需求,非常适合用于讲解闭包与链式设计的例子,故来分享一下思路。</p><span id="more"></span><p>大致需求如下: 目前有个 list, list 中每项 item 都是可展开的折叠项。当展开某个折叠项时,需要根据 item 的 code 另外去取 name 的映射。考虑到列表的数据量非常大,且一次性查询过多 code 时,接口的查询效率会明显降低,故采用按需请求映射的方案。</p><p>屏蔽与本例无关的属性,瘦身后的 list 数据结构大致如下:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> DataType {</span><br><span class="line"> code: <span class="built_in">string</span>;</span><br><span class="line"> paymentTransaction: <span class="built_in">string</span>[];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ListType = DataType[];</span><br></pre></td></tr></table></figure><p>我们知道大型企业中的数据会比较复杂,比较常见的一种情况是数据中有一个 id 或 code 是用于跟另一个数据项相关联的。学习过数据库的同学很容易就联想到了<strong>外键</strong>这个概念。</p><p>现在我们就要取出这些 code 发送给服务端去查询。考虑到 code 可能会有重复,因此可以将 codes 存入 <code>Set</code> 中,利用 <code>Set</code> 的特性去重。除此之外,为了使 name 映射可以被复用,每次从接口返回的 name 映射将会被缓存起来。若下次再触发事件时有对应的 key,便不再查询。</p><p>我们可以将这段逻辑抽离出来作为一个依赖收集的函数:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> mapping = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">collectionCodes</span>(<span class="params">initCodes?: <span class="built_in">string</span>[] | <span class="built_in">Set</span><<span class="built_in">string</span>></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> codes = <span class="keyword">new</span> <span class="built_in">Set</span><<span class="built_in">string</span>>(initCodes)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="function"><span class="title">append</span>(<span class="params">code: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="keyword">if</span> (!mapping.has(code)) {</span><br><span class="line"> codes.add(code);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> },</span><br><span class="line"> <span class="function"><span class="title">empty</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> !codes.size;</span><br><span class="line"> },</span><br><span class="line"> <span class="function"><span class="title">value</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> codes;</span><br><span class="line"> },</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>collectionCodes</code> 函数是用于收集 codes。它内部利用了闭包的特性将 codes 缓存了起来,并且在添加新的 code 之前会判断 code 在 local 的映射中是否已经存在。<code>append</code> 返回的 <code>this</code> 是经典的链式调用设计,允许多次链式添加。当本次依赖收集结束后,调用 <code>value</code> 方法获取最终的 codes。</p><p>可以写一些简单的 mock 数据进行尝试:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleNameMapping</span>(<span class="params">data: DataType</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> codes = collectionCodes()</span><br><span class="line"> .append(data.code)</span><br><span class="line"> .append(<span class="string">'code-append-1'</span>)</span><br><span class="line"> .append(<span class="string">'code-append-1'</span>)</span><br><span class="line"> .append(<span class="string">'code-append-2'</span>);</span><br><span class="line"></span><br><span class="line"> data.paymentTransaction.forEach(<span class="function"><span class="params">code</span> =></span> codes.append(code));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (codes.empty()) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'can get values from existing mapping.'</span>)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果请求的数据需要转为数组,可以 Array.from 进行转换</span></span><br><span class="line"> <span class="keyword">const</span> list = <span class="built_in">Array</span>.from(codes.value());</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fetch data before, codes --> '</span>, list);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// mock 获取数据后拿到 name mapping 后,存入 mapping 中的行为.</span></span><br><span class="line"> <span class="comment">// 注意,Set 类型也可以用 forEach 方法,不一定得转为数组才可以操作</span></span><br><span class="line"> list.forEach(<span class="function"><span class="params">code</span> =></span> mapping.set(code, <span class="string">`random-name-<span class="subst">${<span class="built_in">Math</span>.random()}</span>`</span>))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mockItemData = {</span><br><span class="line"> code: <span class="string">'code-main'</span>,</span><br><span class="line"> paymentTransaction: [</span><br><span class="line"> <span class="string">'code-payment-4'</span>,</span><br><span class="line"> <span class="string">'code-payment-1'</span>,</span><br><span class="line"> <span class="string">'code-payment-2'</span>,</span><br><span class="line"> <span class="string">'code-payment-1'</span>,</span><br><span class="line"> <span class="string">'code-payment-3'</span>,</span><br><span class="line"> ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">handleNameMapping(mockItemData);</span><br><span class="line"><span class="comment">// fetch data before, codes --> (7) ["code-main", "code-append-1", "code-append-2", "code-payment-4", "code-payment-1", "code-payment-2", "code-payment-3"]</span></span><br><span class="line"></span><br><span class="line">handleNameMapping(mockItemData);</span><br><span class="line"><span class="comment">// can get values from existing mapping.</span></span><br></pre></td></tr></table></figure><p><code>handleNameMapping</code> 在发起请求前会做 code 收集,若本次收集中没有需要 fetch 的 code,那就避免发送无用的 HTTP 请求,从而达到了优化的目的。</p><p>最终示例的 TS 代码如下。若想直接在控制台尝试效果的话,可以通过 ts 官网中的 <a href="https://www.typescriptlang.org/play#code/JYOwLgpgTgZghgYwgAgCJzHAKgTwA4oDeAUMsggPYAmEAXMgM5hSgDmA3KcnnDgLYRwWKHBANEYYBRD0mLEKwDaAXU4BfYsUpiwyPnDx42yALzIQEAO7IAsgYAUASk7EYAVxAJJ08hQA2fhBeUiAAwtQQDPagwGDhNAwA-LLMbCrIAD7IAMoQYAA8cmwAfI7IJGTaTL4JpuZWOXmFqQrF0SCx8ZHOmmRQeW5QIOVcZAYEIFT2lDQp8qxlFWTLwDDI9gCE+oZsAHQAFnBRMxCOi6PLlREMu3BUUyc9l2QaF30DQ8hg+8AMnJdqAA0FwgfDwYBwThGz2Q-TAg2GGxONwYwAAXhB-ssgRcAG5wPxuCBQpaXOEImqRLEvYEvdSadyebzDQ6TQIAOTgAjsOwU9ioGDg9HQmFwBHOVx0lIYdUoASCzK6UUcF1uhkEUwFmF2j1V4w19gA5CcALT6yYmgCMhpVlzVEymxoiZvVFuttuW9oNTpoLodJoATDaXGQtXBdjx+IIwMJROJgtJdjAKFAAKKIfb2aYRMomYrSr2TbM0M4h5CrdbI3ag8GQs7Q5ZVfwQXZ+CisI0IUTIVh5ZD4wmRZAwKAUPjICAAD1+kgUenGe2DF3JQyxrzIAHoN8hAEGagBzzQDv0YAhG0AIW6AB1NAHbGgAB0wCBkYAbeMAXHJnwAjfoAYf8A98qAU7lkABBKAiHAk1HcdAG34wAZCLvQAjYy4KpdD8Gc6j-ACgLHYtIl2AciScJ5fDEZtW3bI0YDyBB9mQMNkAAIwgZN+kBaVkBNE180Nej4KYHouC3PQKAQABrZBAHbgwA15UvQA4FUAf6NAAYlcwuRQbYjDnMTX0ADW1AFNFedeVYZBAFo5E8wIfXYuO3QALm0AeENX1yXRAG8fQBo9UAfTkv0ACldhxTdNSOQQBO00AVZtX0AWDlAAA5QAs7UAdP1HxfQBZIy-QBZk0AHXkuHYsAkzcjM0NMfMFL2Bg8jQ+iAAMREmMcTRAOSTQAEkIOxvl2QqqFQxw1Dys5iFeWCeP4gBJSA+BFOA6iWE56B9CATX0UBWK4SMBCEQr42ZehFAuEaTWm6MTQAFkmy4VrW8ArW25Zdt4GawEDQ6yGOqN9utWkjtNPazoAZkO5RgTUFxWSoDk5J5RSOz4Xi+O60E+p6L6fu5Bc+UBrqerBzggA">Playground</a> 编译为可直接运行的 js 代码:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> DataType {</span><br><span class="line"> code: <span class="built_in">string</span>;</span><br><span class="line"> paymentTransaction: <span class="built_in">string</span>[];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mapping = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">collectionCodes</span>(<span class="params">initCodes?: <span class="built_in">string</span>[] | <span class="built_in">Set</span><<span class="built_in">string</span>></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> codes = <span class="keyword">new</span> <span class="built_in">Set</span><<span class="built_in">string</span>>(initCodes);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="function"><span class="title">append</span>(<span class="params">code: <span class="built_in">string</span></span>)</span> {</span><br><span class="line"> <span class="keyword">if</span> (!mapping.has(code)) {</span><br><span class="line"> codes.add(code);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> },</span><br><span class="line"> <span class="function"><span class="title">empty</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> !codes.size;</span><br><span class="line"> },</span><br><span class="line"> <span class="function"><span class="title">value</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> codes;</span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleNameMapping</span>(<span class="params">data: DataType</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> codes = collectionCodes()</span><br><span class="line"> .append(data.code)</span><br><span class="line"> .append(<span class="string">'code-append-1'</span>)</span><br><span class="line"> .append(<span class="string">'code-append-1'</span>)</span><br><span class="line"> .append(<span class="string">'code-append-2'</span>);</span><br><span class="line"></span><br><span class="line"> data.paymentTransaction.forEach(<span class="function">(<span class="params">code</span>) =></span> codes.append(code));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (codes.empty()) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'can get values from existing mapping.'</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果请求的数据需要转为数组,可以 Array.from 进行转换</span></span><br><span class="line"> <span class="keyword">const</span> list = <span class="built_in">Array</span>.from(codes.value());</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fetch data before, codes --> '</span>, list);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// mock 获取数据后拿到 name mapping 后,存入 mapping 中的行为.</span></span><br><span class="line"> <span class="comment">// 注意,Set 类型也可以用 forEach 方法,不一定得转为数组才可以操作</span></span><br><span class="line"> list.forEach(<span class="function"><span class="params">code</span> =></span> mapping.set(code, <span class="string">`random-name-<span class="subst">${<span class="built_in">Math</span>.random()}</span>`</span>))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mockItemData = {</span><br><span class="line"> code: <span class="string">'code-main'</span>,</span><br><span class="line"> paymentTransaction: [</span><br><span class="line"> <span class="string">'code-payment-4'</span>,</span><br><span class="line"> <span class="string">'code-payment-1'</span>,</span><br><span class="line"> <span class="string">'code-payment-2'</span>,</span><br><span class="line"> <span class="string">'code-payment-1'</span>,</span><br><span class="line"> <span class="string">'code-payment-3'</span>,</span><br><span class="line"> ],</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">handleNameMapping(mockItemData);</span><br><span class="line"><span class="comment">// fetch data before, codes --> (7) ["code-main", "code-append-1", "code-append-2", "code-payment-4", "code-payment-1", "code-payment-2", "code-payment-3"]</span></span><br><span class="line"></span><br><span class="line">handleNameMapping(mockItemData);</span><br><span class="line"><span class="comment">// can get values from existing mapping.</span></span><br></pre></td></tr></table></figure><p>本例的分析就到此结束了,虽然在本例中链式调用没有充分展示出自己的优势,但也可以作为一个设计思路用于参考。</p>]]></content>
<summary type="html"><p>最近遇到了个按需请求数据的需求,非常适合用于讲解闭包与链式设计的例子,故来分享一下思路。</p></summary>
<category term="JavaScript" scheme="https://anran758.github.io/blog/categories/JavaScript/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="TypeScript" scheme="https://anran758.github.io/blog/tags/TypeScript/"/>
<category term="闭包" scheme="https://anran758.github.io/blog/tags/%E9%97%AD%E5%8C%85/"/>
<category term="链式设计" scheme="https://anran758.github.io/blog/tags/%E9%93%BE%E5%BC%8F%E8%AE%BE%E8%AE%A1/"/>
</entry>
<entry>
<title>React 知识回顾 (优化篇)</title>
<link href="https://anran758.github.io/blog/2020/11/04/react-retrospection-2/"/>
<id>https://anran758.github.io/blog/2020/11/04/react-retrospection-2/</id>
<published>2020-11-04T15:48:43.000Z</published>
<updated>2021-10-04T09:19:41.956Z</updated>
<content type="html"><![CDATA[<img data-src="/blog/2020/11/04/react-retrospection-2/banner.png" class=""><p>接下来对 React 性能相关的问题进行知识回顾。</p><span id="more"></span><details><summary>完整目录概览</summary><ul><li><a href="#react-%E4%BB%A3%E7%A0%81%E5%A4%8D%E7%94%A8">React 代码复用</a><ul><li><a href="#render-props">Render props</a></li><li><a href="#%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6%E5%88%86%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88">高阶函数、高阶组件分别是什么?</a></li></ul></li><li><a href="#react-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96">React 性能优化</a></li><li><a href="#%E7%88%B6%E7%BB%84%E4%BB%B6%E5%9C%A8%E6%89%A7%E8%A1%8C-render-%E6%97%B6%E4%BC%9A%E4%B8%8D%E4%BC%9A%E8%A7%A6%E5%8F%91%E5%AD%90%E7%BB%84%E4%BB%B6%E7%9A%84-render-%E4%BA%8B%E4%BB%B6%E5%A6%82%E6%9E%9C%E4%BC%9A%E8%AF%A5%E6%80%8E%E4%B9%88%E9%81%BF%E5%85%8D">父组件在执行 render 时会不会触发子组件的 render 事件?如果会该怎么避免?</a></li><li><a href="#%E6%B8%B2%E6%9F%93%E5%88%97%E8%A1%A8%E4%B8%BA%E5%95%A5%E8%A6%81%E7%94%A8-key">渲染列表为啥要用 key?</a></li><li><a href="#%E8%99%9A%E6%8B%9F-dom-%E6%98%AF%E5%A6%82%E4%BD%95%E6%8F%90%E5%8D%87%E6%80%A7%E8%83%BD%E7%9A%84">虚拟 dom 是如何提升性能的</a></li><li><a href="#%E7%AE%80%E8%BF%B0-react-diffing-%E7%AE%97%E6%B3%95">简述 React Diffing 算法</a></li><li><a href="#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6%E6%80%8E%E4%B9%88%E4%BD%BF%E7%94%A8">异步组件怎么使用?</a></li><li><a href="#jsx-%E6%98%AF%E5%A6%82%E4%BD%95%E7%BC%96%E8%AF%91%E4%B8%BA-js-%E4%BB%A3%E7%A0%81%E7%9A%84">JSX 是如何编译为 js 代码的?</a></li><li><a href="#%E6%80%8E%E4%B9%88%E5%AF%B9%E7%BB%84%E4%BB%B6%E7%9A%84%E5%8F%82%E6%95%B0%E5%81%9A%E7%B1%BB%E5%9E%8B%E7%BA%A6%E6%9D%9F%E5%91%A2">怎么对组件的参数做类型约束呢?</a></li></ul></details><h2 id="React-代码复用"><a href="#React-代码复用" class="headerlink" title="React 代码复用"></a>React 代码复用</h2><ul><li><a href="https://react.html.cn/docs/render-props.html">Render Props</a></li><li>高阶组件 (HOC)</li><li>自定义 Hooks</li><li>Mixins (已被 React 废弃)</li></ul><h3 id="Render-props"><a href="#Render-props" class="headerlink" title="Render props"></a>Render props</h3><p><code>Render props</code> 是一种在 React 组件之间共享代码的简单技术。具体的行为是:</p><ol><li>子组件接收一个用于渲染指定视图的 <code>prop</code> 属性,该属性的类型是函数。</li><li>父组件在组件内部定义该函数后,将函数的引入传给子组件</li><li>子组件将组件内部 <code>state</code> 作为实参传给从外面传来的函数,并将函数的返回结果渲染在指定的视图区域。</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 组件使用</span></span><br><span class="line"><Mouse render={<span class="function">(<span class="params">x, y</span>) =></span> <span class="xml"><span class="tag"><<span class="name">span</span>></span>x: {x}, y: {y}<span class="tag"></<span class="name">span</span>></span></span>} /></span><br><span class="line"></span><br><span class="line"><span class="comment">// 组件内部大致实现</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Mouse</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> state = { <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> };</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <section></span><br><span class="line"> <header>头部信息</header></span><br><span class="line"> <main>{<span class="built_in">this</span>.props.render(<span class="built_in">this</span>.state)}</main></span><br><span class="line"> <footer>底部信息</footer></span><br><span class="line"> </section></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>准确来说 <code>Render props</code> 是一个用于告知组件需要渲染什么内容的函数属性。<code>props</code> 的命名可以由自己定义,比如用于在内容区域渲染的 prop 名可以叫 <code>render</code>,同时还可以再接收一个 <code>renderHead</code> 的 prop 用于渲染头部的信息。</p><h3 id="高阶函数、高阶组件分别是什么?"><a href="#高阶函数、高阶组件分别是什么?" class="headerlink" title="高阶函数、高阶组件分别是什么?"></a>高阶函数、高阶组件分别是什么?</h3><p><strong>高阶函数</strong>就是<strong>接收其它函数作为参数</strong>的函数就称之为高阶函数,像数组的 <code>map</code> 、<code>sort</code>、<code>filter</code> 都是高阶函数。</p><p><strong>高阶组件(Higher-order component, HOC)</strong> 是 React 用于复用组件逻辑的一种高级技巧。它具体的行为是:</p><p>函数<strong>接收一个组件作为参数</strong>,在函数体内<strong>定义一个新组件</strong>,新组件内<strong>编写可复用的逻辑</strong>并应用到参数组件中。最后再将<strong>新组件作为函数的返回值</strong> return 出去。<br><code>redux</code> 中的 <code>connect</code> 函数就是一个高阶组件。</p><h2 id="React-性能优化"><a href="#React-性能优化" class="headerlink" title="React 性能优化"></a>React 性能优化</h2><ol><li>对比 <code>props/state</code> 新旧值的变化来决定是否渲染组件,参见:<a href="#%E7%88%B6%E7%BB%84%E4%BB%B6%E5%9C%A8%E6%89%A7%E8%A1%8C-render-%E6%97%B6%E4%BC%9A%E4%B8%8D%E4%BC%9A%E8%A7%A6%E5%8F%91%E5%AD%90%E7%BB%84%E4%BB%B6%E7%9A%84-render-%E4%BA%8B%E4%BB%B6%E5%A6%82%E6%9E%9C%E4%BC%9A%E8%AF%A5%E6%80%8E%E4%B9%88%E9%81%BF%E5%85%8D">父组件在执行 render 时会不会触发子组件的 render 事件?如果会该怎么避免?</a></li><li>列表渲染时每项添加唯一的 <code>key</code>。参见:<a href="#%E6%B8%B2%E6%9F%93%E5%88%97%E8%A1%A8%E4%B8%BA%E5%95%A5%E8%A6%81%E7%94%A8-key">渲染列表为啥要用 key?</a></li><li>定时器、DOM 事件等在组件销毁时一同销毁,从而避免内存泄露。</li><li>代码分割,使用异步组件。</li><li>Hooks 使用 <code>useMemo</code> 缓存上一次计算的结果,避免重复计算值。</li></ol><h2 id="父组件在执行-render-时会不会触发子组件的-render-事件?如果会该怎么避免?"><a href="#父组件在执行-render-时会不会触发子组件的-render-事件?如果会该怎么避免?" class="headerlink" title="父组件在执行 render 时会不会触发子组件的 render 事件?如果会该怎么避免?"></a>父组件在执行 render 时会不会触发子组件的 render 事件?如果会该怎么避免?</h2><p>如果父组件渲染后,子组件接收的 props 也跟着发生了改变,那么默认情况下会触发子组件的渲染。</p><p>若子组件接受的 props 没有发生改变,那就得判断子组件的状况。</p><p>如果子组件是继承于 <code>Component</code> 声明的组件,并且没有使用 <code>shouldComponentUpdate</code> 做避免重复渲染的处理,那么子组件会触发 <code>render</code> 事件。</p><p>为了避免重复渲染,类组件可以使用 <code>shouldComponentUpdate</code> 来决定是否进行渲染。也可以将继承于 <code>Component</code> 组件改为继承 <code>PureComponment</code>,该组件会浅对比 <code>Props</code> 是否进行改变,从而决定是否渲染组件。</p><p>如果是函数组件,可以通过 <code>React.memo</code> 来对函数组件进行缓存。</p><h2 id="渲染列表为啥要用-key?"><a href="#渲染列表为啥要用-key?" class="headerlink" title="渲染列表为啥要用 key?"></a>渲染列表为啥要用 key?</h2><p>渲染列表时,如果不给列表子项传 <code>key</code> 的话,React 将默认使用 <code>index</code> 作为 <code>key</code>,同时会在控制台发出警告。</p><p><code>key</code> 在兄弟节点之间必须唯一,要避免使用数组下标 <code>index</code> 作为 <code>key</code>。因为使用数组下标作为 `key 时,若数组的顺序发生了改变,将会影响 Diffing 算法的效率。</p><p>若列表的节点是组件的话,还可能会影响组件的 <code>state</code> 数据。因为组件实例是基于 <code>key</code> 来决定是否更新与复用。当顺序发生了变化,则 <code>key</code> 也会相应得被修改,从而导致子组件间的数据错乱。</p><p>React 使用的 Diffing 算法是通过 <code>tag</code> 和 <code>key</code> 判断是否是同一个元素(<code>sameNode</code>)。使用唯一的 <code>key</code> 有助于 React 识别哪些元素发生改变,如节点添加或删除。这样有助于减少渲染次数,从而优化性能。</p><p>如果数组中的数据没有唯一的 <code>key</code>,可以引入 <a href="https://www.npmjs.com/package/shortid">shortid</a> 预先给数组中每项数据生成唯一的 <code>id</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> shortid = <span class="built_in">require</span>(<span class="string">'shortid'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addId</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> ...data,</span><br><span class="line"> id: shortid.generate(),</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> newList = list.map(addId);</span><br></pre></td></tr></table></figure><p>若确定没有列表的顺序不会发生变化同时没有其他唯一的 <code>key</code> 来标识列表项时才能使用数组的下标。</p><h2 id="虚拟-dom-是如何提升性能的"><a href="#虚拟-dom-是如何提升性能的" class="headerlink" title="虚拟 dom 是如何提升性能的"></a>虚拟 dom 是如何提升性能的</h2><p>当组件触发更新时,虚拟 DOM 通过 Diffing 算法比对新旧节点的变化以决定是否渲染 DOM 节点,从而减少渲染提升性能。因为修改真实 DOM 所耗费的性能远比操作 JavaScript 多几倍,因此使用虚拟 DOM 在渲染性能上会高效的多。</p><h2 id="简述-React-Diffing-算法"><a href="#简述-React-Diffing-算法" class="headerlink" title="简述 React Diffing 算法"></a>简述 React Diffing 算法</h2><p>Diffing 算法(Diffing Algorithm) 会先比较两个根元素的变化:</p><ol><li>当<strong>节点类型变化</strong>时,将会卸载原有的树而建立新树。如父节点 <code><div></code> 标签被修改为 <code><section></code> 标签,则它们自身及 <code>children</code> 下的节点都会被重新渲染。</li><li>当 <strong>DOM 节点类型相同</strong>时,保留相同的 DOM 节点,仅更新发生改变的属性。</li><li>当<strong>组件类型相同时</strong>,组件更新时组件实例保持不变,React 将更新组件实例的 props, 并调用生命周期 <code>componentWillReceiveProps()</code> 和 <code>componentwillupdate()</code>,最后再调用 <code>render</code>。若 <code>render</code> 中还有子组件,将递归触发 Diff。</li><li>当<strong>列表节点发生变化,列表项没有设置 key 时</strong>, 那么 Diffing 算法会逐个对比节点的变化。如果是尾部新增节点,那 Diff 算法会 Diff 到列表末尾,仅新增元素即可,不会有其他的性能损耗。若新增的数据不在数组的尾部而是在中间,那么 Diffing 算法比较到中间时判断出节点发生变化,将会丢弃后面所有节点并重新渲染。</li><li>当<strong>列表节点发生变化,列表项有设置 key 时</strong>, React 可以通过 <code>key</code> 来匹配新旧节点间的对应关系,可以很快完成 Diff 并避免重复渲染的问题。</li></ol><h2 id="异步组件怎么使用?"><a href="#异步组件怎么使用?" class="headerlink" title="异步组件怎么使用?"></a>异步组件怎么使用?</h2><ol><li><p>通过动态 <code>import()</code> 语法对组件代码进行分割。</p></li><li><p>使用 <code>React.lazy</code> 函数,结合 <code>import()</code> 语法引入动态组件。在组件首次渲染时,会自动导入包含 <code>MyComponent</code> 的包。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> MyComponent = React.lazy(<span class="function">() =></span> <span class="keyword">import</span>(<span class="string">'./MyComponent'</span>));</span><br></pre></td></tr></table></figure></li><li><p>在 <code>React.Suspense</code> 组件中渲染 <code>lazy</code> 组件,同时可以使用 <code>fallback</code> 做优雅降级(添加 <code>loading</code> 效果):</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><React.Suspense fallback={<span class="xml"><span class="tag"><<span class="name">div</span>></span>Loading...<span class="tag"></<span class="name">div</span>></span></span>}></span><br><span class="line"> <MyComponent /></span><br><span class="line"></React.Suspense></span><br></pre></td></tr></table></figure></li><li><p>封装一个错误捕获组件(比如组件命名为 <code>MyErrorBoundary</code>),组件内通过生命周期 <code>getDerivedStateFromError</code> 捕获错误信息。当异步组件加载失败时,将捕获到错误信息处理后给用户做错误提示功能。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><MyErrorBoundary></span><br><span class="line"> <React.Suspense fallback={<span class="xml"><span class="tag"><<span class="name">div</span>></span>Loading...<span class="tag"></<span class="name">div</span>></span></span>}></span><br><span class="line"> <MyComponent /></span><br><span class="line"> </React.Suspense></span><br><span class="line"></MyErrorBoundary></span><br></pre></td></tr></table></figure></li></ol><h2 id="JSX-是如何编译为-js-代码的?"><a href="#JSX-是如何编译为-js-代码的?" class="headerlink" title="JSX 是如何编译为 js 代码的?"></a>JSX 是如何编译为 js 代码的?</h2><p>在 React v17 之前,JSX 会被编译为 <code>React.createElement(component, props, ...children)</code> 函数,执行会返回 <code>vnode</code>,<code>vnode</code> 通过 <code>patch</code> 之类的方法渲染到页面。</p><p>React v17 之后更新了 JSX 转换规则。新的 JSX 转换不会将 JSX 转换为 <code>React.createElement</code>,而是自动从 React 的 <code>package</code> 中引入新的入口函数(<code>react/jsx-runtime</code>)并调用。这意味着我们不用在每个组件文件中显式引入 <code>React</code>。</p><h2 id="怎么对组件的参数做类型约束呢"><a href="#怎么对组件的参数做类型约束呢" class="headerlink" title="怎么对组件的参数做类型约束呢?"></a>怎么对组件的参数做类型约束呢?</h2><p>要对组件的参数做类型约束的话,可以引入 <code>prop-types</code> 来配置对应的 <code>propTypes</code> 属性。<br><code>Flow</code> 和 <code>TypesScript</code> 则可以对整个应用做类型检查。</p>]]></content>
<summary type="html"><img src="/blog/2020/11/04/react-retrospection-2/banner.png" class="">
<p>接下来对 React 性能相关的问题进行知识回顾。</p></summary>
<category term="React" scheme="https://anran758.github.io/blog/categories/React/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="React" scheme="https://anran758.github.io/blog/tags/React/"/>
</entry>
<entry>
<title>React 知识回顾 (使用篇)</title>
<link href="https://anran758.github.io/blog/2020/10/31/react-retrospection/"/>
<id>https://anran758.github.io/blog/2020/10/31/react-retrospection/</id>
<published>2020-10-31T12:19:22.000Z</published>
<updated>2021-10-04T09:19:41.957Z</updated>
<content type="html"><![CDATA[<img data-src="/blog/2020/10/31/react-retrospection/banner.png" class=""><p>使用 React 进行项目开发也有好几个项目了,趁着最近有空来对 React 的知识做一个简单的复盘。</p><span id="more"></span><details><summary>完整目录概览</summary><ul><li><a href="#react-%E6%98%AF%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81%E8%BF%98%E6%98%AF%E5%8F%8C%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81%E5%AE%83%E8%BF%98%E6%9C%89%E5%85%B6%E4%BB%96%E7%89%B9%E7%82%B9%E5%90%97">React 是单向数据流还是双向数据流?它还有其他特点吗?</a></li><li><a href="#setstate">setState</a><ul><li><a href="#react-%E9%80%9A%E8%BF%87%E4%BB%80%E4%B9%88%E6%96%B9%E5%BC%8F%E6%9D%A5%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE">React 通过什么方式来更新数据</a></li><li><a href="#react-%E4%B8%8D%E8%83%BD%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9-state-%E5%90%97">React 不能直接修改 State 吗?</a></li><li><a href="#setstate-%E6%98%AF%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5%E7%9A%84">setState 是同步还是异步的?</a></li><li><a href="#setstate-%E5%B0%8F%E6%B5%8B">setState 小测</a></li></ul></li><li><a href="#react-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">React 生命周期</a><ul><li><a href="#constructor-%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">constructor (构造函数)</a></li><li><a href="#static-getderivedstatefromprops">static getDerivedStateFromProps</a></li><li><a href="#shouldcomponentupdate">shouldComponentUpdate</a></li><li><a href="#render">render</a></li><li><a href="#getsnapshotbeforeupdate">getSnapshotBeforeUpdate</a></li><li><a href="#componentdidmount">componentDidMount</a></li><li><a href="#componentdidupdate">componentDidUpdate</a></li><li><a href="#componentwillunmount">componentWillUnmount</a></li><li><a href="#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%98%B6%E6%AE%B5">生命周期阶段</a></li><li><a href="#%E5%85%B6%E4%BB%96%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">其他生命周期</a></li></ul></li><li><a href="#react-%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1">React 组件通信</a><ul><li><a href="#reactcontext-%E6%80%8E%E4%B9%88%E4%BD%BF%E7%94%A8">React.Context 怎么使用</a></li></ul></li><li><a href="#%E5%87%BD%E6%95%B0%E7%BB%84%E4%BB%B6%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%8E%E7%B1%BB%E7%BB%84%E4%BB%B6%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB">函数组件是什么?与类组件有什么区别?</a></li><li><a href="#hooks">Hooks</a><ul><li><a href="#hook-vs-class">Hook vs class</a></li><li><a href="#hooks-%E7%9A%84%E4%BD%BF%E7%94%A8">Hooks 的使用</a></li><li><a href="#hook-%E4%B9%8B%E9%97%B4%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B7%AE%E5%BC%82">Hook 之间的一些差异</a></li><li><a href="#%E8%87%AA%E5%AE%9A%E4%B9%89-hook-%E7%9A%84%E4%BD%BF%E7%94%A8">自定义 Hook 的使用</a></li><li><a href="#hook-%E4%BD%BF%E7%94%A8%E7%BA%A6%E6%9D%9F">Hook 使用约束</a></li><li><a href="#class-%E7%BB%84%E4%BB%B6%E4%B8%8E-hook-%E4%B9%8B%E9%97%B4%E7%9A%84%E6%98%A0%E5%B0%84%E4%B8%8E%E8%BD%AC%E6%8D%A2">class 组件与 Hook 之间的映射与转换</a><ul><li><a href="#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">生命周期</a></li><li><a href="#hooks-%E6%B2%A1%E6%9C%89%E5%AE%9E%E7%8E%B0%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90">Hooks 没有实现的生命周期钩子</a></li><li><a href="#%E8%BD%AC%E6%8D%A2%E5%AE%9E%E4%BE%8B%E5%8F%98%E9%87%8F">转换实例变量</a></li><li><a href="#%E5%BC%BA%E5%88%B6%E6%9B%B4%E6%96%B0-hook-%E7%BB%84%E4%BB%B6">强制更新 Hook 组件</a></li><li><a href="#%E8%8E%B7%E5%8F%96%E6%97%A7%E7%9A%84-props-%E5%92%8C-state">获取旧的 props 和 state</a></li></ul></li></ul></li><li><a href="#%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E7%9A%84%E5%8C%BA%E5%88%AB">受控组件与非受控组件的区别</a></li><li><a href="#portals-%E6%98%AF%E4%BB%80%E4%B9%88">Portals 是什么?</a></li></ul></details><h2 id="React-是单向数据流还是双向数据流?它还有其他特点吗?"><a href="#React-是单向数据流还是双向数据流?它还有其他特点吗?" class="headerlink" title="React 是单向数据流还是双向数据流?它还有其他特点吗?"></a>React 是单向数据流还是双向数据流?它还有其他特点吗?</h2><p>React 是单向数据流,数据是从上向下流。它的其他主要特点时:</p><ul><li>数据驱动视图</li><li>声明式编写 UI</li><li>组件化开发</li></ul><h2 id="setState"><a href="#setState" class="headerlink" title="setState"></a>setState</h2><h3 id="React-通过什么方式来更新数据"><a href="#React-通过什么方式来更新数据" class="headerlink" title="React 通过什么方式来更新数据"></a>React 通过什么方式来更新数据</h3><p>React 是通过 <code>setState</code> 来更新数据的。调用多个 <code>setState</code> 不会立即更新数据,而会批量延迟更新后再将数据合并。</p><p>除了 <code>setState</code> 外还可以使用 <code>forceUpdate</code> 跳过当前组件的 <code>shouldComponentUpdate</code> diff,强制触发组件渲染(避免使用该方式)。</p><h3 id="React-不能直接修改-State-吗?"><a href="#React-不能直接修改-State-吗?" class="headerlink" title="React 不能直接修改 State 吗?"></a>React 不能直接修改 State 吗?</h3><ol><li>直接修改 state 不会触发组件的渲染。</li><li>若直接修改 state 引用的值,在实际使用时会导致错误的值出现</li><li>修改后的 state 可能会被后续调用的 <code>setState</code> 覆盖</li></ol><h3 id="setState-是同步还是异步的?"><a href="#setState-是同步还是异步的?" class="headerlink" title="setState 是同步还是异步的?"></a>setState 是同步还是异步的?</h3><p>出于性能的考虑,React 可能会把多个 <code>setState</code> 合并成一个调用。</p><p><code>React</code> 内有个 <code>batchUpdate(批量更新)</code> 的机制,在 React 可以控制的区域 (如组件生命周期、React 封装的事件处理器) 设置标识位 <code>isBatchingUpdate</code> 来决定是否触发更新。</p><p>比如在 React 中注册的 <code>onClick</code> 事件或是 <code>componentDidMount</code> 中直接使用 <code>setState</code> 都是异步的。若想拿到触发更新后的值,可以给 <code>setState</code> 第二个参数传递一个函数,该函数在<strong>数据更新后会触发的回调函数</strong>,函数的参数就是更新后最新的值。</p><p>不受 React 控制的代码快中使用 <code>setState</code> 是同步的,比如在 <code>setTimeout</code> 或是原生的事件监听器中使用。</p><h3 id="setState-小测"><a href="#setState-小测" class="headerlink" title="setState 小测"></a>setState 小测</h3><p>输出以下结果:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({ <span class="attr">count</span>: <span class="built_in">this</span>.state.count + <span class="number">1</span> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"1 -->"</span>, <span class="built_in">this</span>.state.count);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">this</span>.setState({ <span class="attr">count</span>: <span class="built_in">this</span>.state.count + <span class="number">1</span> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"2 -->"</span>, <span class="built_in">this</span>.state.count);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({ <span class="attr">count</span>: <span class="built_in">this</span>.state.count + <span class="number">1</span> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"3 -->"</span>, <span class="built_in">this</span>.state.count);</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({ <span class="attr">count</span>: <span class="built_in">this</span>.state.count + <span class="number">1</span> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"4 -->"</span>, <span class="built_in">this</span>.state.count);</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出结果为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1 --> 0</span><br><span class="line">2 --> 0</span><br><span class="line">3 --> 2</span><br><span class="line">4 --> 3</span><br></pre></td></tr></table></figure><p>解答: 调用 <code>setState</code> 后不会立即更新 state,开头两次调用会被异步合并调用,因此只有一次调用。一轮事件循环结束后,调用第 3、4 次 <code>setState</code>。由于在 <code>setTimeout</code> 中调用是同步更新的,因此都能正常的叠加数据。</p><h2 id="React-生命周期"><a href="#React-生命周期" class="headerlink" title="React 生命周期"></a>React 生命周期</h2><p>React 的生命周期主要是指组件<strong>在特定阶段会执行的函数</strong>。以下是 class 组件的部分<a href="https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/">生命周期图谱</a>:</p><img data-src="/blog/2020/10/31/react-retrospection/react-lifecycle.png" class="" title="React 生命周期速查图"><p>从上图可以看出:React 的生命周期按照类型划分,可分为 <strong>挂载时(Mounting)、更新时(Updating)、卸载时(Unmounting)</strong> 。图中的生命周期函数效果如下:</p><h3 id="constructor-构造函数"><a href="#constructor-构造函数" class="headerlink" title="constructor (构造函数)"></a>constructor (构造函数)</h3><ul><li><strong>触发条件</strong>: 组件初始化时 </li><li><strong>是否可以使用 <code>setState</code></strong>: X </li><li><strong>使用场景</strong>: 初始化 <code>state</code> 或者对方法绑定 <code>this</code>。在构造函数中便于自动化测试。</li></ul><h3 id="static-getDerivedStateFromProps"><a href="#static-getDerivedStateFromProps" class="headerlink" title="static getDerivedStateFromProps"></a>static getDerivedStateFromProps</h3><blockquote><p>Tips: 不常用方法</p></blockquote><ul><li><strong>触发条件</strong>: 调用 <code>render</code> 函数之前 </li><li><strong>是否可以使用 <code>setState</code></strong>: X </li><li><strong>函数行为</strong>: 函数可以返回一个对象用于更新组件内部的 <code>state</code> 数据,若返回 <code>null</code> 则什么都不更新。</li><li><strong>使用场景</strong>: 用于 state 依赖 props 的情况,也就是状态派生。值得注意的是派生 state 会导致代码冗余,并使组件难以维护。</li></ul><h3 id="shouldComponentUpdate"><a href="#shouldComponentUpdate" class="headerlink" title="shouldComponentUpdate"></a>shouldComponentUpdate</h3><blockquote><p>Tips: 不常用方法</p></blockquote><ul><li><strong>触发条件</strong>: 当 <code>props</code>/<code>state</code> 发生变化 </li><li><strong>是否可以使用 <code>setState</code></strong>: X </li><li><strong>函数行为</strong>: 函数的返回值决定组件是否触发 <code>render</code>,返回值为 <code>true</code> 则触发渲染,反之则阻止渲染。(组件内不写该函数的话,则调用默认函数。默认函数只会返回 <code>true</code>,即只要 <code>props</code>/<code>state</code> 发生变化,就更新组件) </li><li><strong>使用场景</strong>: 组件的性能优化,仅仅是浅比较 props 和 state 的变化的话,可以使用内置的 <a href="https://zh-hans.reactjs.org/docs/react-api.html#reactpurecomponent">PureComponent</a> 来代替 <code>Component</code> 组件。</li></ul><h3 id="render"><a href="#render" class="headerlink" title="render"></a>render</h3><ul><li><strong>触发条件</strong>: 渲染组件时 </li><li><strong>是否可以使用 <code>setState</code></strong>: X </li><li><strong>函数行为</strong>: 函数的返回值决定视图的渲染效果</li><li><strong>使用场景</strong>: class 组件中唯一<strong>必须要实现</strong>的生命周期函数。</li></ul><h3 id="getSnapshotBeforeUpdate"><a href="#getSnapshotBeforeUpdate" class="headerlink" title="getSnapshotBeforeUpdate"></a>getSnapshotBeforeUpdate</h3><blockquote><p>Tips: 不常用方法</p></blockquote><ul><li><strong>触发条件</strong>: 在最近一次渲染输出(提交到 DOM 节点)之前调用 </li><li><strong>是否可以使用 <code>setState</code></strong>: X </li><li><strong>函数行为</strong>: 函数的返回值将传入给 <code>componentDidUpdate</code> 第三个参数中。若只实现了该函数,但没有使用 <code>componentDidUpdate</code> 的话,React 将会在控制台抛出警告 </li><li><strong>使用场景</strong>: 可以在组件发生更改之前从 DOM 中捕获一些信息(例如,列表的滚动位置)</li></ul><h3 id="componentDidMount"><a href="#componentDidMount" class="headerlink" title="componentDidMount"></a>componentDidMount</h3><ul><li><strong>触发条件</strong>: 组件挂载后(插入 DOM 树中)立即调用,该函数只会被触发一次 </li><li><strong>是否可以使用 <code>setState</code></strong>: Y (可以<strong>直接调用</strong>,但会触发额外渲染) </li><li><strong>使用场景</strong>: 从网络请求中获取数据、订阅事件等</li></ul><h3 id="componentDidUpdate"><a href="#componentDidUpdate" class="headerlink" title="componentDidUpdate"></a>componentDidUpdate</h3><ul><li><strong>触发条件</strong>: 组件更新完毕后(首次渲染不会触发) </li><li><strong>是否可以使用 <code>setState</code></strong>: Y (更新语句须<strong>放在条件语句</strong>中,不然可能会造成死循环) </li><li><strong>使用场景</strong>: 对比新旧值的变化,进而判断是否需要发送网络请求。比如监听路由的变化</li></ul><h3 id="componentWillUnmount"><a href="#componentWillUnmount" class="headerlink" title="componentWillUnmount"></a>componentWillUnmount</h3><ul><li><strong>触发条件</strong>: 组件卸载及销毁之前直接调用 </li><li><strong>是否可以使用 <code>setState</code></strong>: X </li><li><strong>使用场景</strong>: 清除 timer,取消网络请求或清除在 <code>componentDidMount</code> 中创建的订阅等</li></ul><h3 id="生命周期阶段"><a href="#生命周期阶段" class="headerlink" title="生命周期阶段"></a>生命周期阶段</h3><p>针对 React 生命周期中函数的调用顺序,笔者写了一个简易的 Demo 用于演示: <a href="https://codesandbox.io/s/react-lifecycle-forked-2dvdg?file=/src/Parent.jsx">React 生命周期示例</a></p><p><strong>React 组件挂载阶段</strong>先后会触发 <code>constuctor</code>、<code>static getDerivedStateFromProps</code>、<code>render</code>、<code>componentDidMount</code> 函数。若 <code>render</code> 函数内还有子组件存在的话,则会进一步递归:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">[Parent]: constuctor</span><br><span class="line">[Parent]: static getDerivedStateFromProps</span><br><span class="line">[Parent]: render</span><br><span class="line">[Children]: constuctor</span><br><span class="line">[Children]: static getDerivedStateFromProps</span><br><span class="line">[Children]: render</span><br><span class="line">[Children]: componentDidMount</span><br><span class="line">[Children]: 挂载阶段结束!</span><br><span class="line">[Parent]: componentDidMount</span><br><span class="line">[Parent]: 挂载阶段结束!</span><br></pre></td></tr></table></figure><p><strong>React 组件更新阶段</strong>主要是组件的 props 或 state 发生变化时触发。若组件内还有子组件,则子组件会判断是否也需要触发更新。默认情况下 <code>component</code> 组件是只要父组件发生了变化,子组件也会跟着变化。以下是更新父组件 <code>state</code> 数据时所触发的生命周期函数:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[Parent]: static getDerivedStateFromProps</span><br><span class="line">[Parent]: shouldComponentUpdate</span><br><span class="line">[Parent]: render</span><br><span class="line">[Children]: static getDerivedStateFromProps</span><br><span class="line">[Children]: shouldComponentUpdate</span><br><span class="line">[Children]: render</span><br><span class="line">[Children]: getSnapshotBeforeUpdate</span><br><span class="line">[Parent]: getSnapshotBeforeUpdate</span><br><span class="line">[Children]: componentDidUpdate</span><br><span class="line">[Children]: 更新阶段结束!</span><br><span class="line">[Parent]: componentDidUpdate</span><br><span class="line">[Parent]: 更新阶段结束!</span><br></pre></td></tr></table></figure><p>值得注意的是: 在本例 Demo 中没有给子组件传参,但子组件也触发了渲染。但从应用的角度上考虑,既然你子组件没有需要更新的东西,那就没有必要触发渲染吧?</p><p>因此 <code>Component</code> 组件上可以使用 <code>shouldComponentUpdate</code> 或者将 <code>Component</code> 组件替换为 <code>PureComponment</code> 组件来做优化。在生命周期图中也可以看到: <code>shouldComponentUpdate</code> 返回 <code>false</code> 时,将不再继续触发下面的函数。</p><p>有时你可能在某些情况下想主动触发渲染而又不被 <code>shouldComponentUpdate</code> 阻止渲染该怎么办呢?可以使用 <code>forceUpdate()</code> 跳过 <code>shouldComponentUpdate</code> 的 diff,进而渲染视图。(需要使用强制渲染的场景较少,一般不推荐这种方式进行开发)</p><p><strong>React 组件销毁阶段</strong>也没啥好说的了。父组件先触发销毁前的函数,再逐层向下触发:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[Parent]: componentWillUnmount</span><br><span class="line">[Parent]: 卸载阶段结束!</span><br><span class="line">[Children]: componentWillUnmount</span><br><span class="line">[Children]: 卸载阶段结束!</span><br></pre></td></tr></table></figure><h3 id="其他生命周期"><a href="#其他生命周期" class="headerlink" title="其他生命周期"></a>其他生命周期</h3><p>除了上图比较常见的生命周期外,还有一些过时的 API 就没有额外介绍了。因为它们可能在未来的版本会被移除:</p><ul><li><a href="https://zh-hans.reactjs.org/docs/react-component.html#unsafe_componentwillmount">UNSAFE_componentWillMount()</a>: 在组件即将被挂载到页面的时刻自动执行。应该使用 componentDidUpdate 来代替该函数。</li><li><a href="https://zh-hans.reactjs.org/docs/react-component.html#unsafe_componentwillupdate">UNSAFE_componentWillUpdate()</a></li><li><a href="https://zh-hans.reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops">UNSAFE_componentWillReceiveProps()</a>:当父组件某个 props 更新前,可以调用 <code>setState</code> 覆盖内部的某个 state。</li></ul><p>上图没有给出错误处理的情况,以下信息作为补充: 当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:</p><ul><li><a href="https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror">static getDerivedStateFromError()</a></li><li><a href="https://zh-hans.reactjs.org/docs/react-component.html#componentdidcatch">componentDidCatch()</a></li></ul><h2 id="React-组件通信"><a href="#React-组件通信" class="headerlink" title="React 组件通信"></a>React 组件通信</h2><ol><li>父组件通过 props 给子组件传递数据。子组件通过触发父组件提供的回调函数来给父组件传递消息或数据</li><li><code>React.Context</code> 可以跨层级组件共享数据</li><li>自定义事件</li><li>引入 <code>Redux</code>/<code>Mobx</code> 之类的状态管理器</li></ol><h3 id="React-Context-怎么使用"><a href="#React-Context-怎么使用" class="headerlink" title="React.Context 怎么使用"></a>React.Context 怎么使用</h3><p><code>Context</code> 可以共享对于组件树而言是全局的数据,比如全局主题、首选语言等。使用方式如下:</p><ol><li><p><code>React.createContext</code> 函数用于生成 <code>Context</code> 对象。可以在创建时给 <code>Context</code> 设置默认值:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ThemeContext = React.createContext(<span class="string">'light'</span>);</span><br></pre></td></tr></table></figure></li><li><p><code>Context</code> 对象中有一个 <code>Provider(提供者)</code> 组件,<code>Provider</code> 组件接受一个 <code>value</code> 属性用以将数据传递给消费组件。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><ThemeContext.Provider value=<span class="string">"dark"</span>></span><br><span class="line"> <page /></span><br><span class="line"></ThemeContext.Provider></span><br></pre></td></tr></table></figure></li><li><p>获取 <code>Context</code> 提供的值可以通过 <code>contextType</code> 或者 <code>Consumer(消费者)</code> 组件中获取。<code>contextType</code> 只能用于类组件,并且只能挂载一个 <code>Context</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">let</span> value = <span class="built_in">this</span>.context;</span><br><span class="line"> <span class="comment">/* 在组件挂载完成后,使用 MyContext 的值执行一些有副作用的操作 */</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">let</span> value = <span class="built_in">this</span>.context;</span><br><span class="line"> <span class="comment">/* 基于 MyContext 的值进行渲染 */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">MyClass.contextType = MyContext;</span><br></pre></td></tr></table></figure><p>若想给组件挂载多个 <code>Context</code>, 或者在函数组件内使用 <code>Context</code> 可以使用 <code>Consumer</code> 组件:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><ThemeContext.Consumer></span><br><span class="line"> {<span class="function"><span class="params">theme</span> =></span> (</span><br><span class="line"> <UserContext.Consumer></span><br><span class="line"> {<span class="function"><span class="params">user</span> =></span> (</span><br><span class="line"> <ProfilePage user={user} theme={theme} /></span><br><span class="line"> )}</span><br><span class="line"> </UserContext.Consumer></span><br><span class="line"> )}</span><br><span class="line"></ThemeContext.Consumer></span><br></pre></td></tr></table></figure></li></ol><p><code>Context</code> 通常适用于传递较为简单的数据信息,若数据太过复杂,还是需要引入状态管理(<code>Redux</code>/<code>Mbox</code>)。</p><h2 id="函数组件是什么?与类组件有什么区别?"><a href="#函数组件是什么?与类组件有什么区别?" class="headerlink" title="函数组件是什么?与类组件有什么区别?"></a>函数组件是什么?与类组件有什么区别?</h2><p>函数组件本质上是一个纯函数,它接受 props 属性,最后返回 JSX。</p><p>与类组件的差别在于: 它没有实例、不能通过 <code>extends</code> 继承于其他方法、也没有生命周期和 <code>state</code>。以前函数组件常作为无状态组件,React 16.8+ 可以引入 <code>Hooks</code> 为函数组件支持状态和副作用操作。</p><h2 id="Hooks"><a href="#Hooks" class="headerlink" title="Hooks"></a>Hooks</h2><h3 id="Hook-vs-class"><a href="#Hook-vs-class" class="headerlink" title="Hook vs class"></a>Hook vs class</h3><p><strong>类组件的不足</strong>:</p><ul><li>状态逻辑复用难,缺少复用机制。渲染属性和高阶组件导致层级冗余。</li><li>组件趋向复杂难以维护。生命周期函数混杂不相干逻辑,相干逻辑分散在不同生命周期中。</li><li>this 指向令人困扰。内联函数过度创建新句柄,类成员函数不能保证 this。</li></ul><p><strong>Hooks 的优点</strong>:</p><ul><li>自定义 Hook 方便复用状态逻辑</li><li>副作用的关注点分离</li><li>函数组件没有 this 问题</li></ul><p><strong>Hooks 现有的不足</strong>:</p><ul><li>不能完全取代 class 组件的生命周期,部分不常用的生命周期暂时没有实现。</li><li>Hooks 的运作方式带来了一定的学习成本,需要转换现有的编程思维,增加了心智负担。</li></ul><h3 id="Hooks-的使用"><a href="#Hooks-的使用" class="headerlink" title="Hooks 的使用"></a>Hooks 的使用</h3><blockquote><p>描述 Hooks 有哪些常用的方法和大致用途</p></blockquote><ol><li><p><code>useState</code>: 使函数组件支持设置 <code>state</code> 数据,可用于代替类组件的 <code>constructor</code> 函数。</p></li><li><p><code>useEffect</code>: 使函数组件支持操作副作用 (effect) 的能力,Hook 第二个参数是 effect 的依赖项。当依赖项是空时,effect 函数仅会在组件挂载后执行一遍。若有一个或多个依赖项时,只要任意一个依赖项发生变化,就会触发 effect 函数的执行。effect 函数里可以做一些如获取页面数据、订阅事件等操作。</p><p>除此之外,<code>useEffect</code> 还可以返回一个函数用于做清除操作,这个清除操作时可选的。常用于清理订阅事件、DOM 事件等。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 绑定 DOM 事件</span></span><br><span class="line">useEffect(<span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">document</span>.addEventListener(<span class="string">'click'</span>, handleClick);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// useEffect 回调函数的返回值是函数的话,当组件卸载时会执行该函数</span></span><br><span class="line"> <span class="comment">// 若没有需要清除的东西,则可以忽略这一步骤</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">document</span>.removeEventListener(<span class="string">'click'</span>, handleClick);</span><br><span class="line"> };</span><br><span class="line">}, [handleClick]);</span><br></pre></td></tr></table></figure></li><li><p><code>useLayoutEffect</code>: <code>useEffect</code> 的 effect 执行的时机是在浏览器完成布局和绘制<strong>之后</strong>会延迟调用。若想要 DOM 变更的同时同步执行 effect 的话可以使用 <code>useLayoutEffect</code>。它们之间只是执行的时机不同,其他都一样。</p></li><li><p><code>useContext</code>: 接收一个 <code>Context</code> 对象,并返回 <code>Context</code> 的当前值。相当于类组件的 <code>static contextType = MyContext</code>。</p></li><li><p><code>useReducer</code> 是 <code>useState</code> 的代替方案,它的工作方式有点类似于 <code>Redux</code>,通过函数来操作 state。适合 <code>state</code> 逻辑较为复杂且包含多个子值,或是新的 <code>state</code> 依赖于旧的 <code>state</code> 的场景。</p></li><li><p><code>useMemo</code> 主要用于性能优化,它可以缓存变量的值,避免每次组件更新后都需要重复计算值。</p></li><li><p><code>useCallbck</code> 用于缓存函数,避免函数被重复创建,它是 <code>useMemo</code> 的语法糖。<code>useCallback(fn, deps)</code> 的效果相当于是 <code>useMemo(() => fn, deps)</code>。</p></li></ol><h3 id="Hook-之间的一些差异"><a href="#Hook-之间的一些差异" class="headerlink" title="Hook 之间的一些差异"></a>Hook 之间的一些差异</h3><ol><li><p><strong>React.memo 与 React.useMemo</strong>:</p><p><code>memo</code> 针对一个组件的渲染是否重复执行,<code>useMemo</code> 定义一段函数逻辑是否重复执行。</p></li><li><p><strong>React.useMemo 与 React.useCallback</strong>:</p><p><code>useMemo(() => fn)</code> 返回的是一个函数,将等同于 <code>useCallback(fn)</code>。</p></li><li><p><strong>React.useStatus 与 React.useRef</strong>:</p><p><code>React.useStatus</code> 相当于类的 <code>state</code>;<code>React.useRef</code> 相当于类的内部属性。前者参与渲染,后者的修改不会触发渲染。</p></li></ol><h3 id="自定义-Hook-的使用"><a href="#自定义-Hook-的使用" class="headerlink" title="自定义 Hook 的使用"></a>自定义 Hook 的使用</h3><p>自定义 Hook 的命名规则是以 <code>use</code> 开头的函数,比如 <code>useLocalStorage</code> 就符合自定义 Hook 的命名规范。<br>使用自定义 Hook 的场景有很多,如表单处理、动画、订阅声明、定时器等等可复用的逻辑都能通过自定义 Hook 来抽象实现。</p><p>在自定义 Hook 中,可以使用 Hooks 函数将可复用的逻辑和功能提取出来,并将内部的 <code>state</code> 或操作的方法从自定义 Hook 函数中返回出来。函数组件使用时就可以像调用普通函数一祥调用自定义 Hook 函数, 并将自定义 Hook 返回的 <code>state</code> 和操作方法通过解构保存到变量中。</p><p>下面是 <a href="https://usehooks.com/useLocalStorage/">useLocalStorage</a> 的实现,它将 state 同步到本地存储,以使其在页面刷新后保持不变。 用法与 useState 相似,不同之处在于我们传入了本地存储键,以便我们可以在页面加载时默认为该值,而不是指定的初始值。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { useState } <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Usage</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// Similar to useState but first arg is key to the value in local storage.</span></span><br><span class="line"> <span class="keyword">const</span> [name, setName] = useLocalStorage(<span class="string">'name'</span>, <span class="string">'Bob'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> <input</span><br><span class="line"> type=<span class="string">"text"</span></span><br><span class="line"> placeholder=<span class="string">"Enter your name"</span></span><br><span class="line"> value={name}</span><br><span class="line"> onChange={<span class="function"><span class="params">e</span> =></span> setName(e.target.value)}</span><br><span class="line"> /></span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Hook</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useLocalStorage</span>(<span class="params">key, initialValue</span>) </span>{</span><br><span class="line"> <span class="comment">// State to store our value</span></span><br><span class="line"> <span class="comment">// Pass initial state function to useState so logic is only executed once</span></span><br><span class="line"> <span class="keyword">const</span> [storedValue, setStoredValue] = useState(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// Get from local storage by key</span></span><br><span class="line"> <span class="keyword">const</span> item = <span class="built_in">window</span>.localStorage.getItem(key);</span><br><span class="line"> <span class="comment">// Parse stored json or if none return initialValue</span></span><br><span class="line"> <span class="keyword">return</span> item ? <span class="built_in">JSON</span>.parse(item) : initialValue;</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="comment">// If error also return initialValue</span></span><br><span class="line"> <span class="built_in">console</span>.log(error);</span><br><span class="line"> <span class="keyword">return</span> initialValue;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Return a wrapped version of useState's setter function that ...</span></span><br><span class="line"> <span class="comment">// ... persists the new value to localStorage.</span></span><br><span class="line"> <span class="keyword">const</span> setValue = <span class="function"><span class="params">value</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// Allow value to be a function so we have same API as useState</span></span><br><span class="line"> <span class="keyword">const</span> valueToStore =</span><br><span class="line"> value <span class="keyword">instanceof</span> <span class="built_in">Function</span> ? value(storedValue) : value;</span><br><span class="line"> <span class="comment">// Save state</span></span><br><span class="line"> setStoredValue(valueToStore);</span><br><span class="line"> <span class="comment">// Save to local storage</span></span><br><span class="line"> <span class="built_in">window</span>.localStorage.setItem(key, <span class="built_in">JSON</span>.stringify(valueToStore));</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="comment">// A more advanced implementation would handle the error case</span></span><br><span class="line"> <span class="built_in">console</span>.log(error);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> [storedValue, setValue];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>注意: 自定义 Hook 函数在定义时,也可以使用另一个自定义 Hook 函数。</strong></p><h3 id="Hook-使用约束"><a href="#Hook-使用约束" class="headerlink" title="Hook 使用约束"></a>Hook 使用约束</h3><ol><li>只能在<strong>函数组件最顶层</strong>调用 Hook,不能在循环、条件判断或子函数中调用。</li><li>只能在<strong>函数组件</strong>或者是<strong>自定义 Hook 函数</strong>中调用,普通的 js 函数不能使用。</li></ol><h3 id="class-组件与-Hook-之间的映射与转换"><a href="#class-组件与-Hook-之间的映射与转换" class="headerlink" title="class 组件与 Hook 之间的映射与转换"></a>class 组件与 Hook 之间的映射与转换</h3><p>函数组件相比 class 组件会缺少很多功能,但大多可以通过 Hook 的方式来实现。</p><h4 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h4><ul><li><p><strong>constructor</strong>:class 组件的构造函数一般是用于初始化 <code>state</code> 数据或是给事件绑定 <code>this</code> 指向的。函数组件内没有 this 指向的问题,因此可以忽略。而 <code>state</code> 可以通过 <code>useState</code>/<code>useReducer</code> 来实现。</p></li><li><p><strong>getDerivedStateFromProps</strong>:<code>getDerivedStateFromProps</code> 一般用于在组件 props 发生变化时派生 <code>state</code>。Hooks 实现同等效果如下:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ScrollView</span>(<span class="params">{row}</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [isScrollingDown, setIsScrollingDown] = useState(<span class="literal">false</span>);</span><br><span class="line"> <span class="keyword">const</span> [prevRow, setPrevRow] = useState(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (row !== prevRow) {</span><br><span class="line"> <span class="comment">// Row 自上次渲染以来发生过改变。更新 isScrollingDown。</span></span><br><span class="line"> setIsScrollingDown(prevRow !== <span class="literal">null</span> && row > prevRow);</span><br><span class="line"> setPrevRow(row);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="string">`Scrolling down: <span class="subst">${isScrollingDown}</span>`</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p><strong>shouldComponentUpdate</strong>: 使用 <code>React.memo</code> 应用到函数组件中后,当 props 发生变化时,会对 props 的新旧值进行前对比,相当于是 <code>PureComponent</code> 的功能。如果你还想自己定义比较函数的话,可以给 <code>React.memo</code> 的第二个参数传一个函数,若函数返回 <code>true</code> 则跳过更新。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Button = React.memo(<span class="function">(<span class="params">props</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">button</span>></span>{props.text}<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line">});</span><br></pre></td></tr></table></figure></li><li><p><strong>render</strong>: 函数组件本身就是一个 <code>render</code> 函数。</p></li><li><p><strong>componentDidMount / componentDidUpdate / componentWillUnmount</strong>:</p><p><code>useEffect</code> 第二个参数的依赖项为空时,相当于 <code>componentDidMount</code>,组件挂载后只会执行一次。每个 <code>useEffect</code> 返回的函数相当于是 <code>componentWillUnmount</code> 同等效果的操作。若有依赖,则 effect 函数相当于是 <code>componentDidUpdate</code>:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 没有依赖项,仅执行一次</span></span><br><span class="line">useEffect(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">const</span> subscription = props.source.subscribe();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 相当于 componentWillUnmount</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function">() =></span> {</span><br><span class="line"> subscription.unsubscribe();</span><br><span class="line"> };</span><br><span class="line">}, []);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 若有依赖项,相当于 componentDidUpdate</span></span><br><span class="line"><span class="comment">// 当 page 发生变化时会触发 effect 函数</span></span><br><span class="line">useEffect(<span class="function">() =></span> {</span><br><span class="line"> fetchList({ page });</span><br><span class="line">}, [page]);</span><br></pre></td></tr></table></figure></li></ul><h4 id="Hooks-没有实现的生命周期钩子"><a href="#Hooks-没有实现的生命周期钩子" class="headerlink" title="Hooks 没有实现的生命周期钩子"></a>Hooks 没有实现的生命周期钩子</h4><ul><li>getSnapshotBeforeUpdate</li><li>getDerivedStateFromError</li><li>componentDidCatch</li></ul><h4 id="转换实例变量"><a href="#转换实例变量" class="headerlink" title="转换实例变量"></a>转换实例变量</h4><p>使用 <code>useRef</code> 设置可变数据。</p><h4 id="强制更新-Hook-组件"><a href="#强制更新-Hook-组件" class="headerlink" title="强制更新 Hook 组件"></a>强制更新 Hook 组件</h4><p>设置一个<strong>没有实际作用</strong>的 <code>state</code>,然后强制更新 <code>state</code> 的值触发渲染。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Todo = <span class="function">() =></span> {</span><br><span class="line"> <span class="comment">// 使用 useState,用随机数据更新也行</span></span><br><span class="line"> <span class="keyword">const</span> [ignored, forceUpdate] = useReducer(<span class="function"><span class="params">x</span> =></span> x + <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">handleClick</span>(<span class="params"></span>) </span>{</span><br><span class="line"> forceUpdate();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">button</span> <span class="attr">click</span>=<span class="string">{handleClick}</span>></span>强制更新组件<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="获取旧的-props-和-state"><a href="#获取旧的-props-和-state" class="headerlink" title="获取旧的 props 和 state"></a>获取旧的 props 和 state</h4><p>可以通过 <code>useRef</code> 来保存数据,因为渲染时不会覆盖掉可变数据。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Counter</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [count, setCount] = useState(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> prevCountRef = useRef();</span><br><span class="line"> useEffect(<span class="function">() =></span> {</span><br><span class="line"> prevCountRef.current = count;</span><br><span class="line"> }, []);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> prevCount = prevCountRef.current;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">h1</span>></span>Now: {count}, before: {prevCount}<span class="tag"></<span class="name">h1</span>></span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="受控组件与非受控组件的区别"><a href="#受控组件与非受控组件的区别" class="headerlink" title="受控组件与非受控组件的区别"></a>受控组件与非受控组件的区别</h2><p>受控组件主要是指表单的值受到 <code>state</code> 的控制,它需要自行监听 <code>onChange</code> 事件来更新 <code>state</code>。</p><p>由于受控组件每次都要编写事件处理器才能更新 <code>state</code> 数据、可能会有点麻烦,React 提供另一种代替方案是<strong>非受控组件</strong>。</p><p>非受控组件将<strong>真实数据储存在 DOM 节点</strong>中,它可以为表单项设置默认值,不需要手动更新数据。当需要用到表单数据时再通过 <code>ref</code> 从 DOM 节点中取出数据即可。</p><p><strong>注意: 多数情况下React 推荐编写受控组件。</strong></p><p>扩展资料: <a href="https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/">受控和非受控制使用场景的选择</a></p><h2 id="Portals-是什么"><a href="#Portals-是什么" class="headerlink" title="Portals 是什么?"></a>Portals 是什么?</h2><p><code>Portals</code> 就像个传送门,它可以将子节点渲染到存在于父组件以外的 DOM 节点的方案。</p><p>比如 <code>Dialog</code> 是一个全局组件,按照传统渲染组件的方式,<code>Dialog</code> 可能会受到其容器 css 的影响。因此可以使用 <code>Portals</code> 让组件在视觉上渲染到 <code><body></code> 中,使其样式不受 <code>overflow: hidden</code> 或 <code>z-index</code> 的影响。</p>]]></content>
<summary type="html"><img src="/blog/2020/10/31/react-retrospection/banner.png" class="">
<p>使用 React 进行项目开发也有好几个项目了,趁着最近有空来对 React 的知识做一个简单的复盘。</p></summary>
<category term="React" scheme="https://anran758.github.io/blog/categories/React/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
<category term="React" scheme="https://anran758.github.io/blog/tags/React/"/>
</entry>
<entry>
<title>Hexo 常见问题解决方案</title>
<link href="https://anran758.github.io/blog/2020/09/27/hexo-issue/"/>
<id>https://anran758.github.io/blog/2020/09/27/hexo-issue/</id>
<published>2020-09-27T13:12:42.000Z</published>
<updated>2021-10-04T09:19:41.866Z</updated>
<content type="html"><![CDATA[<img data-src="/blog/2020/09/27/hexo-issue/banner.png" class=""><p>记录 Hexo 升级或使用时遇到的问题和一些解决方案。</p><span id="more"></span><h2 id="TypeError-config-d-getTime-is-not-a-function"><a href="#TypeError-config-d-getTime-is-not-a-function" class="headerlink" title="TypeError: config._d.getTime is not a function"></a>TypeError: config._d.getTime is not a function</h2><p>经过排查,本次发生错误是由 <code>hexo-related-popular-posts</code> 引发,在该库源码中使用 <code>moment</code> 初始化 <code>list.date</code> 导致了错误。 <code>list.date</code> 通过打印值可以看到是一个 <code>moment</code> 对象,但这个 <code>moment</code> 对象并不规范或者说可能在某处修改了这个 <code>moment</code> 对象的值。</p><p><code>moment</code> 内部初始化有一段逻辑是:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">this</span>._d = <span class="keyword">new</span> <span class="built_in">Date</span>(config._d != <span class="literal">null</span> ? config._d.getTime() : <span class="literal">NaN</span>);</span><br></pre></td></tr></table></figure><p>这个 <code>config</code> 就是 <code>moment(list.date)</code> 传入的 <code>list.date</code> 的值。<code>config._d</code> 是一个时间类型的<em>字符串</em>,并不是 <code>Date</code> 类型,因此没有 <code>getTime</code> 的方法。</p><p>临时解决方法有两种,一是将 <code>theme/next/_config.yml</code> 中的 <code>related_posts.params.isDate</code> 设为 <code>false</code>,也就是推荐列表中不展示时间。</p><p>二是修改源码,做一层错误处理。从 <code>node_modules</code> 中打开文件(\node_modules\hexo-related-popular-posts\lib\list-json.js), 在编辑器中查找以下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (inOptions.isDate && list.date != <span class="string">''</span>) {</span><br><span class="line"> ret.date = moment(list.date).format(config.date_format || <span class="string">'YYYY-MM-DD'</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>修改为:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (inOptions.isDate && list.date != <span class="string">''</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ret.date = moment(list.date).format(config.date_format || <span class="string">'YYYY-MM-DD'</span>)</span><br><span class="line"> } <span class="keyword">catch</span>(ex) {</span><br><span class="line"> ret.date = moment(list.date._d).format(config.date_format || <span class="string">'YYYY-MM-DD'</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上述只是临时的解决方案,由于不好确定是哪一方的原因,也不想继续耗费太多精力在上面。</p><p>错误日志如下,以供参考:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">Unhandled rejection Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk) [Line 19, Column 14]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\_partials\head\head-unique.njk) [Line 10, Column 23]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk) [Line 3, Column 3]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\_partials\header\index.njk) [Line 6, Column 15]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\_partials\header\sub-menu.njk) [Line 2, Column 29]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\_partials\header\sub-menu.njk)</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk) [Line 5, Column 3]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\post.njk) [Line 9, Column 12]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\_macro\post.njk) [Line 214, Column 16]</span><br><span class="line"> Template render error: (C:\Users\user\Desktop\project\blog\themes\next\layout\_partials\post\post-related.njk)</span><br><span class="line"> TypeError: config._d.getTime is not a function</span><br><span class="line"> at Object._prettifyError (C:\Users\user\Desktop\project\blog\node_modules\nunjucks\src\lib.js:36:11)</span><br><span class="line"> at C:\Users\user\Desktop\project\blog\node_modules\nunjucks\src\environment.js:561:19</span><br><span class="line"> at Template.root [as rootRenderFunc] (eval at _compile (C:\Users\user\Desktop\project\blog\node_modules\nunjucks\src\environment.js:631:18), <anonymous>:45:3)</span><br><span class="line"> at Template.render (C:\Users\user\Desktop\project\blog\node_modules\nunjucks\src\environment.js:550:10)</span><br><span class="line"> at C:\Users\user\Desktop\project\blog\themes\next\scripts\renderer.js:35:29</span><br><span class="line"> at _View._compiled.locals [as _compiled] (C:\Users\user\Desktop\project\blog\node_modules\hexo\lib\theme\view.js:136:50)</span><br><span class="line"> at _View.render (C:\Users\user\Desktop\project\blog\node_modules\hexo\lib\theme\view.js:39:17)</span><br><span class="line"> at C:\Users\user\Desktop\project\blog\node_modules\hexo\lib\hexo\index.js:64:21</span><br><span class="line"> at tryCatcher (C:\Users\user\Desktop\project\blog\node_modules\bluebird\js\release\util.js:16:23)</span><br><span class="line"> at C:\Users\user\Desktop\project\blog\node_modules\bluebird\js\release\method.js:15:34</span><br><span class="line"> at RouteStream._read (C:\Users\user\Desktop\project\blog\node_modules\hexo\lib\hexo\router.js:47:5)</span><br><span class="line"> at RouteStream.Readable.read (_stream_readable.js:470:10)</span><br><span class="line"> at resume_ (_stream_readable.js:949:12)</span><br><span class="line"> at process._tickCallback (internal/process/next_tick.js:63:19)</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><img src="/blog/2020/09/27/hexo-issue/banner.png" class="">
<p>记录 Hexo 升级或使用时遇到的问题和一些解决方案。</p></summary>
<category term="Hexo" scheme="https://anran758.github.io/blog/categories/Hexo/"/>
<category term="Hexo" scheme="https://anran758.github.io/blog/tags/Hexo/"/>
</entry>
<entry>
<title>React vs Vue</title>
<link href="https://anran758.github.io/blog/2020/07/05/react-vs-vue/"/>
<id>https://anran758.github.io/blog/2020/07/05/react-vs-vue/</id>
<published>2020-07-05T03:10:56.000Z</published>
<updated>2023-12-13T03:44:01.356Z</updated>
<content type="html"><![CDATA[<p>在项目架构时选择合适的前端框架是至关重要的。React 和 Vue 都是流行的选择,但它们在灵活性、易用性和性能方面各有特点。本文旨在深入比较这两个框架,让我们在开发前选择技术架构有个参考。</p><span id="more"></span><h2 id="React"><a href="#React" class="headerlink" title="React"></a>React</h2><p>React 在处理复杂业务时显示出较高的灵活性。它提供多样的技术方案选择,适用于需要高度自定义的场景。React 的特点包括:</p><ul><li>组件名称需要以大写字母开头。</li><li>使用 JSX 语法,组件内需要包裹一个元素,可以使用Fragment作为占位符。</li><li>响应式设计,主要关注数据。</li><li>事件绑定采用驼峰命名方式。</li><li>不允许直接修改 state,以保持性能。</li><li>构造函数中接受参数。</li><li>单向数据流,专注于视图层和数据渲染。</li><li>有助于自动化测试。</li><li>state 或 props 改变时,render 函数会重新执行。</li><li>使用虚拟 DOM 来减少真实 DOM 操作,提升性能。</li><li>跨端应用实现,例如 React Native。</li></ul><p>但它的缺点也很明显:</p><ul><li><strong>学习曲线较陡</strong>: JSX 和组件生命周期等概念对新手而言可能较难掌握。</li><li><strong>只关注视图层</strong>: 需要与其他库结合使用以构建完整的解决方案。但 react 的生态非常丰富,甚至会有多种不同的变成风格,社区中没有一个统一认可的解决方案,这会让不熟悉 react 生态的新用户看的眼花缭乱。</li></ul><h2 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h2><p>Vue 提供了丰富的 API,使功能实现变得简单。它适合于快速开发和较少复杂度的项目。Vue 的特点包括:</p><ul><li>易学性,提供了详尽的文档和指导。尤其作者是国人,也提供了友好的中文文档支持。</li><li>更简洁的模板语法糖,如 v-bind 和 v-model。</li><li>详细的错误提示和开发工具,使调试更加方便。</li><li>数据双向绑定,简化了表单输入和数据展示。</li><li>更轻量级,适合小型到中型项目。</li><li>提供了过渡效果和动画的集成支持。</li><li>可以更方便地集成到现有的项目中。</li><li>提供了类似于 React 的虚拟 DOM 和组件系统。</li><li>相比 react 生态的复杂, vue 官方提供了整套最基础的 web 开发架构所需的生态。当官方的提供的库无法满足需求后可以允许你去用其他第三方库,相当于起步阶段减少了选择的烦恼。对新手会比较友好。</li></ul><p>然而,Vue 也有它的局限性:</p><ul><li>规模限制: 对于非常大型和复杂的应用,Vue 可能不如 React 灵活。</li><li>过度依赖单文件组件: 可能导致项目结构和组织方式较为单一</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>React 和 Vue 各有所长,选择哪一个取决于特定项目的需求、开发团队的技能和偏好。React 更适合需要高度灵活和可扩展性的大型应用,而 Vue 在快速开发和简单性方面表现更佳,适合新手和中小型项目。理解每个框架的优缺点有助于做出更合适的选择。</p>]]></content>
<summary type="html"><p>在项目架构时选择合适的前端框架是至关重要的。React 和 Vue 都是流行的选择,但它们在灵活性、易用性和性能方面各有特点。本文旨在深入比较这两个框架,让我们在开发前选择技术架构有个参考。</p></summary>
<category term="JavaScript" scheme="https://anran758.github.io/blog/categories/JavaScript/"/>
<category term="React" scheme="https://anran758.github.io/blog/tags/React/"/>
<category term="Vue" scheme="https://anran758.github.io/blog/tags/Vue/"/>
</entry>
<entry>
<title>webpack + Travis CI 自动部署项目应用</title>
<link href="https://anran758.github.io/blog/2020/06/08/github-travis-build/"/>
<id>https://anran758.github.io/blog/2020/06/08/github-travis-build/</id>
<published>2020-06-08T04:43:57.000Z</published>
<updated>2021-10-04T09:19:41.840Z</updated>
<content type="html"><![CDATA[<img data-src="/blog/2020/06/08/github-travis-build/banner.png" class=""><p>我们知道 <strong><a href="https://pages.github.com/">Github Pages</a></strong> 是 Github 免费提供给用户展示页面的一项服务。当我们完成项目开发后,想将页面部署到 Github Pages 时,该要怎么操作呢?</p><p>可以在 GitHub 的储存库设置中设置用于展示页面的分支,该分支只保留构建后的静态资源,也就是源码与编译后的静态资源分离。按照传统的做法是:手动运行编译命令,编译后再复制到指定分支中。这样操作很繁琐,但使用 <code>Travis CI</code> 持续集成服务之后就可以不用操心这些事了。</p><span id="more"></span><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>既然我们要使用 <a href="https://travis-ci.com/">Travis CI</a>,首先得搞清楚人家具体是干嘛的吧?</p><p><code>Travis CI</code> 是一个 **持续集成(Continuous integration, CI)**。它与 git 相耦合,每当有 commit 提交时,它将自动触发构建与测试。若运行结果符合预期,才将新代码集成到 <strong>主流(mainline)</strong> 中,这样使应用更加健壮。</p><p>值得注意的是,<code>Travis CI</code> 提倡每次 commit 都是独立较小的改动,而不是突然提交一大堆代码。因为这有助于后续构建失败时可以回退到正常的版本。</p><p>运行构建时,<code>Travis CI</code> 将 GitHub 存储库克隆到全新的虚拟环境中,并执行一系列任务来构建和测试代码。如果这些任务中的一项或多项失败,则将构建视为已损坏。如果所有任务均未失败,则认为构建已通过,<code>Travis CI</code> 会将代码部署到 Web 服务器或应用程序主机中(在本文中是指 <code>Github Pages</code> 服务)。</p><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p>在使用之前,需要准备一个 <strong>Github</strong> 的账号对 <code>Travis CI</code> 进行授权。</p><ol><li>接着通过 Github 的账号登录 <a href="https://travis-ci.com/signin">Travis CI</a>,点击 <code>SIGN IN WITH GITHUB</code>。</li><li>点击后会被重定向到 Github 进行授权。 <img data-src="/blog/2020/06/08/github-travis-build/travis-1.jpeg" class=""></li><li>授权后,若是第一次登录的话会被重定向至<a href="https://travis-ci.com/getting_started">引导页</a>: <img data-src="/blog/2020/06/08/github-travis-build/travis-2.jpeg" class=""></li><li>点击引导页第一步的按钮,使用 <strong>GitHub Apps</strong> 激活储存库。可以选择给全部储存库都激活,也可以激活指定储存库。本文以 <code><username>.github.io</code> 为例:<blockquote><p>注意: 这个 <code>username</code> 是你自己的 Github 用户名。笔者的 <code>username</code> 为 <code>anran758</code> 那储存库的名字就为 **<a href="https://github.com/anran758/anran758.github.io">anran758.github.io</a>**。 </p></blockquote><img data-src="/blog/2020/06/08/github-travis-build/travis-3.jpeg" class=""> </li><li>激活后会被重定向到<a href="https://travis-ci.com/account/repositories">设置页</a>,点击待部署的储存库右侧的 <code>setting</code> 按钮,跳转至 <code>Travis CI</code> 储存库设置页。我们需要在此页设置部署 <code>Github Pages</code> 时所需的环境变量:<img data-src="/blog/2020/06/08/github-travis-build/travis-4.jpeg" class=""></li></ol><p>环境变量的值需要从 Github 拿<strong>拥有部署权限的 token</strong>:</p><ol><li>打开 <a href="https://github.com/">Github</a>,点击头像,再点击 <a href="https://github.com/settings/profile">Settings</a> 进入设置页: <img data-src="/blog/2020/06/08/github-travis-build/github-1.png" class=""></li><li>进入设置页面后在左侧边栏点击<a href="https://github.com/settings/apps">开发者设置</a>: <img data-src="/blog/2020/06/08/github-travis-build/github-2.jpeg" class=""></li><li>跳转后在左侧边栏点击 <a href="https://github.com/settings/tokens">Personal access tokens</a>, 然后在头部点击 <a href="https://github.com/settings/tokens/new">Generate new token</a>: <img data-src="/blog/2020/06/08/github-travis-build/github-3.jpeg" class=""></li><li>填写 token 备注、权限,最后点击生成 token: <img data-src="/blog/2020/06/08/github-travis-build/github-4.png" class=""></li><li>生成 token 后点击复制按钮,复制到粘贴板: <img data-src="/blog/2020/06/08/github-travis-build/github-5.png" class=""> <strong>注意要妥善保管好 token,重新刷新页面后这个 token 将不会再展示出来。如果忘记了 token 的话,也只能在 token 编辑页中重新生成。这会导致所有用到该 token 的应用都要更新值。</strong> 比方说有三个应用使用了该 token,重新生成后只在一个应用更新的值,那其他两个应用不更新就无法使用了。 <img data-src="/blog/2020/06/08/github-travis-build/github-6.jpeg" class=""> </li><li>复制 token 后切回 <code>Travis CI</code> 储存库的设置页,添加环境变量:<img data-src="/blog/2020/06/08/github-travis-build/github-7.jpeg" class=""> </li></ol><p>这样我们的准备工作就完成的差不多了。</p><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>在项目目录中新建文件 <code>.travis.yml</code>,内容如下:</p><figure class="highlight yml"><figcaption><span>/.travis.yml</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">language:</span> <span class="string">node_js</span></span><br><span class="line"><span class="attr">node_js:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">lts/*</span></span><br><span class="line"></span><br><span class="line"><span class="attr">install:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">yarn</span> <span class="string">install</span> <span class="comment"># npm ci</span></span><br><span class="line"><span class="attr">script:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">yarn</span> <span class="string">test</span> <span class="comment"># npm run test</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">yarn</span> <span class="string">build</span> <span class="comment"># npm run build</span></span><br><span class="line"></span><br><span class="line"><span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">provider:</span> <span class="string">pages</span></span><br><span class="line"> <span class="attr">local_dir:</span> <span class="string">dist</span></span><br><span class="line"> <span class="attr">target_branch:</span> <span class="string">master</span></span><br><span class="line"> <span class="attr">on:</span></span><br><span class="line"> <span class="attr">branch:</span> <span class="string">develop</span></span><br><span class="line"> <span class="attr">token:</span> <span class="string">$GITHUB_TOKEN</span></span><br><span class="line"> <span class="attr">skip_cleanup:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">keep_history:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">committer_from_gh:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>由于 webpack 项目依赖 Node.js,因此语言(<code>language</code>) 设置为 <code>node_js</code>,同时还指定使用最新的 LTS Node.js 版本(<code>lts/*</code>)。</p><p><code>install</code> 是安装部署所需的依赖项,<code>script</code> 则是用于运行测试或构建脚本。他们都是 <code>Travis</code> 的工作生命周期(Job Lifecycle)必触发的钩子(阶段)。</p><p><code>install</code> 钩子若有脚本/命令运行失败的话,整个构建会停止。而 <code>script</code> 钩子表现则不同,当有脚本/命令运行失败后虽然构建会失败,但还会继续执行后面的脚本。如 <code>yarn test</code> 运行失败后会继续跑 <code>yarn build</code> 命令。</p><p>以下是 <code>Travis CI</code> 主要的阶段流程图:</p><pre class="mermaid" style="text-align: center;">graph TDA[before_install] --> B[install]A & B -.-> Z((停止构建))B --> C[before_script]C --> D[scrip]D --> E(after_success)D --> F(after_failure)E & F --> G[before_deploy]G --> H[deploy]H --> I[after_deploy]I --> J[after_script]</pre><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><p>通过 <code>deploy</code> 可以指定部署方式,下面将逐个介绍部署所用的选项:</p><p><a href="https://docs.travis-ci.com/user/deployment/#supported-providers">provider</a> 是部署类型。现在我们想将页面部署到 Github Pages,那就需要将 <code>provider</code> 设为 <code>pages</code>。</p><p><code>local_dir</code> 指定要推送到 Github Pages 的目录,默认为当前目录。webpack 默认的输出目录是 <code>/dist</code>,因此需要将值设为 <code>dist</code>。除此之外,<code>Travis CI</code> 默认情况下会删除构建期间创建的所有文件,因此需要设置 <code>skip_cleanup: true</code> 保留构建出来的 <code>dist</code> 目录.</p><p>若 <code>on.branch</code> 有 commit 提交的话,<code>Travis CI</code> 将从 <code>on.branch</code> 分支运行编译脚本,编译后会把 <code>local_dir</code> 目录强制推送到 <code>target_branch</code> 中。(<code>target_branch</code> 默认值为 <code>gh-pages</code>)</p><p>现在我们要部署的储存库是 <code><username>.github.io</code>。这种类型的储存库有些特殊——它只能在 <code>master</code> 分支展示构建后的代码,而不能修改为其他分支。在 GitHub 储存库的 <code>Settings</code> 中的 <code>Source</code> 选项可以看到详细信息:</p><img data-src="/blog/2020/06/08/github-travis-build/deploy-1.jpeg" class=""><p>然而其他储存库则没有这种限制:</p><img data-src="/blog/2020/06/08/github-travis-build/deploy-2.jpeg" class=""><p>因此要部署到 <code><username>.github.io</code> 储存库的话,<code>target_branch</code> 只能设为 <code>master</code>,触发编译的 <code>on.branch</code> 分支则可以自己定义。</p><p>其他储存库可以按照标准流程来开发:</p><ul><li><code>develop</code> 作为开发分支</li><li><code>master</code> 作为主分支</li><li><code>gh-pages</code> 作为页面展示分支</li></ul><p>等功能开发并测试完毕后,将 <code>develop</code> 的代码合并到 <code>master</code> 分支并推送至远程。<code>Traivis CI</code> 检测到 <code>matser</code> 有 <code>commit</code> 提交后会自动运行脚本构建,构建完毕后将输出目录推送至 <code>gh-pages</code> 分支。</p><p>当然 Github Pages 也不是随便来一个人就可以部署的,你想要部署到储存库中首先得有该储存库的操作权限吧?<code>token</code> 就是证明你身份的东西。在上文中我们预先设置好了一个名为 <code>GITHUB_TOKEN</code> 的环境变量,此处我们可以通过 <code>$GITHUB_TOKEN</code> 直接取出该环境变量的值即可。</p><p>其他还有一些细节问题可以调整:比如推送构建后的代码到 <code>target_branch</code> 时使用的是强制推送(<code>git push --force</code>),如果你觉得这种强制覆盖历史记录的方式有点暴力的话,可以设置 <code>keep_history: true</code> 来保留提交记录。</p><p>自动部署后 <code>commit</code> 提交者默认是 <code>Travis CI</code> 的信息。也可以设置 <code>committer_from_gh</code> 允许 <code>Travs CI</code> 使用令牌所有者的个人信息来提交 <code>commit</code>。</p><p>配置完毕后现在只需将 <code>.travis.yml</code> 提交到远程,<code>Travis CI</code> 就开始工作了:</p><img data-src="/blog/2020/06/08/github-travis-build/deploy-3.jpeg" class=""><p>甚至还可以在 Github <code>commit</code> 信息中看到编译的情况:</p><img data-src="/blog/2020/06/08/github-travis-build/deploy-4.jpeg" class=""><p>如果构建出问题的话,<code>Travis CI</code> 还会发邮件提示你:</p><img data-src="/blog/2020/06/08/github-travis-build/deploy-5.png" class=""><p>部署成功后就可以直接通过浏览器访问啦~ 储存库部署的是 <code><username>.github.io</code> 的话,访问链接为 <code>https://<username>.github.io/</code>。其他储存库可以访问 <code>https://<username>.github.io/<repoName></code>。</p><p>比如笔者的主页与博客是两个项目分离的,部署后的链接地址为 <a href="https://anran758.github.io/">https://anran758.github.io</a> 和 <a href="https://anran758.github.io/blog">https://anran758.github.io/blog</a>。</p><h2 id="Travis-CI-CLI"><a href="#Travis-CI-CLI" class="headerlink" title="Travis CI CLI"></a>Travis CI CLI</h2><p>还可以通过 <a href="https://github.com/travis-ci/travis.rb#logs">Travis CI CLI</a> 来进行操作:</p><p>按照文档的 <a href="https://github.com/travis-ci/travis.rb#installation">Installation</a> 部分安装 <code>Travis CI CLI</code>。</p><p>安装完毕后通过命令行进入储存库目录,输入 <code>travis -v</code> 来检查是否安装成功。</p><p><code>Travis CI</code> 有两个不同域名版本的 API,一个是 <code>.com</code> 新版本,<code>.org</code> 是旧版本的。先确定自己使用的是哪个平台,再设定它:</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 默认是 .org</span></span><br><span class="line">travis endpoint</span><br><span class="line"><span class="meta">#</span><span class="bash"> API endpoint: https://api.travis-ci.org/</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 笔者使用的是 .com 的平台,因此需要修改默认的模式。设置 `--com` 和 `--pro` 的效果是相等的。</span></span><br><span class="line">travis endpoint --com --set-default</span><br><span class="line"><span class="meta">#</span><span class="bash"> API endpoint: https://api.travis-ci.com/ (stored as default)</span></span><br></pre></td></tr></table></figure><p>确定版本后输入 <code>travis login</code> 或 <code>travis login --pro</code> 进行登录。Mac os 系统可能会遇到 <code>Travis Ci CLI</code> 依赖的 <code>ruby</code> 版本和系统自带 <code>ruby</code> 有冲突:</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">travis login --com</span><br><span class="line"><span class="meta">#</span><span class="bash"> We need your GitHub login to identify you.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> This information will not be sent to Travis CI, only to api.github.com.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> The password will not be displayed.</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Try running with --github-token or --auto <span class="keyword">if</span> you don<span class="string">'t want to enter your password anyway.</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Username: anran758</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Password <span class="keyword">for</span> anran758: ***********</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Unknown error</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="keyword">for</span> a full error report, run travis report --pro</span></span><br></pre></td></tr></table></figure><p>若不想处理这些麻烦的依赖问题,可以在 Travis CI 的<a href="https://travis-ci.com/account/preferences">个人设置页</a> 复制 <code>access_token</code> 到 <code>~/.travis/config.yml</code> 的配置中:</p><img data-src="/blog/2020/06/08/github-travis-build/travis-log-settings.jpeg" class=""> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> code ~/.travis/config.yml <span class="comment"># 通过 vscode 进行修改</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 通过 vim 进行修改</span></span><br><span class="line">vim ~/.travis/config.yml</span><br></pre></td></tr></table></figure><p>修改 <code>endpoints</code> 下的 <code>access_token</code> 并保存后,在命令输入 <code>travis accounts --pro</code> 检查是否成功:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">travis accounts --pro</span><br><span class="line"><span class="meta">#</span><span class="bash"> travis accounts --pro</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> anran758 (Anran758): not subscribed, 18 repositories</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> To <span class="built_in">set</span> up a subscription, please visit travis-ci.com.</span></span><br></pre></td></tr></table></figure><p>这样就登录完毕啦~ 接着在输入 <code>travis logs</code> 就可以查看日志:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查看最新构建的日志</span></span><br><span class="line">travis logs</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看指定构建日志</span></span><br><span class="line">travis logs 2</span><br></pre></td></tr></table></figure><p>还可以清空指定构建的日志:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> travis logs 2 --d <span class="comment"># -d 简短选项</span></span></span><br><span class="line">travis logs 2 --delete</span><br></pre></td></tr></table></figure><p>参考资料:</p><ul><li><a href="https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line">Creating a personal access token for the command line</a></li><li><a href="https://docs.travis-ci.com/user/deployment/pages/">GitHub Pages Deployment</a></li><li><a href="https://docs.travis-ci.com/user/job-lifecycle/">Job Lifecycle</a></li></ul>]]></content>
<summary type="html"><img src="/blog/2020/06/08/github-travis-build/banner.png" class="">
<p>我们知道 <strong><a href="https://pages.github.com/">Github Pages</a></strong> 是 Github 免费提供给用户展示页面的一项服务。当我们完成项目开发后,想将页面部署到 Github Pages 时,该要怎么操作呢?</p>
<p>可以在 GitHub 的储存库设置中设置用于展示页面的分支,该分支只保留构建后的静态资源,也就是源码与编译后的静态资源分离。按照传统的做法是:手动运行编译命令,编译后再复制到指定分支中。这样操作很繁琐,但使用 <code>Travis CI</code> 持续集成服务之后就可以不用操心这些事了。</p></summary>
<category term="环境搭建" scheme="https://anran758.github.io/blog/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<category term="webpack" scheme="https://anran758.github.io/blog/tags/webpack/"/>
<category term="环境搭建" scheme="https://anran758.github.io/blog/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<category term="CI" scheme="https://anran758.github.io/blog/tags/CI/"/>
</entry>
<entry>
<title>从零构建 webpack 脚手架(基础篇)</title>
<link href="https://anran758.github.io/blog/2020/05/04/webpack-example/"/>
<id>https://anran758.github.io/blog/2020/05/04/webpack-example/</id>
<published>2020-05-04T14:58:56.000Z</published>
<updated>2021-10-04T09:19:41.974Z</updated>
<content type="html"><![CDATA[<img data-src="/blog/2020/05/04/webpack-example/banner.png" class=""><p>webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它对于前端工程师来说可谓是如雷贯耳,基本上现在的大型应用都是通过 webpack 进行构建的。</p><p>webpack 具有高度可配置性,它拥有非常丰富的配置。在过去一段时间内曾有人将熟练配置 webpack 的人称呼为 “webapck 工程师”。当然,这称呼只是个玩笑话,但也能从侧面了解到 webpack 配置的灵活与复杂。</p><p>为了能够熟练掌握 webpack 的使用,接下来通过几个例子循序渐进的学习如何使用 webpack。</p><p>以下 <code>Demo</code> 都可以在 Github 的 <a href="https://github.com/anran758/webpack-example">webpack-example</a> 中找到对应的示例,欢迎 star~</p><span id="more"></span><h2 id="起步"><a href="#起步" class="headerlink" title="起步"></a>起步</h2><p>从 <code>[email protected]</code> 开始,就可以不用再引入配置文件来打包项目。若没有提供配置的话,webpack 将按照默认规则进行打包。默认情况下 <code>src/index</code> 是项目的源代码入口,打包后的代码会输出到 <code>dist/main.js</code> 上。</p><p>首先来初始化一个项目,项目名为 <a href="https://github.com/anran758/webpack-example/tree/master/getting-started">getting-started</a>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 创建项目文件夹</span></span><br><span class="line">mkdir getting-started</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 进入项目目录</span></span><br><span class="line">cd getting-started</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> npm 项目</span></span><br><span class="line">npm init -y</span><br></pre></td></tr></table></figure><p>初始化项目后,项目目录会新增一个 <code>package.json</code>,该文件记录了项目依赖的相关信息。若想要使用 webpack 的话需要安装它的依赖: <code>webpack</code> (本体)和 <code>webpack-cli</code> (可以在命令行操作 webpack 的工具):</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> -D 和 --save-dev 选项都可以用于安装开发依赖</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> npm i --save-dev webpack webpack-cli</span></span><br><span class="line">npm i -D webpack webpack-cli</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 或者使用 yarn 安装开发依赖</span></span><br><span class="line">yarn add -D webpack webpack-cli</span><br></pre></td></tr></table></figure><p>接着创建 webpack 所需的默认入口文件 <code>src/index.js</code> 以及测试模块所用的 <code>src/log.js</code> 文件。此时的项目结构大致如下:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> .</span><br><span class="line"> ├── package.json</span><br><span class="line"><span class="addition">+ ├── src</span></span><br><span class="line"><span class="addition">+ │ ├── index.js</span></span><br><span class="line"><span class="addition">+ │ └── log.js</span></span><br><span class="line"> └── node_modules</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/log.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> log = <span class="function">(<span class="params">name</span>) =></span> <span class="built_in">console</span>.log(<span class="string">`Hello <span class="subst">${name}</span>!`</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// src/index.js</span></span><br><span class="line"><span class="keyword">import</span> { log } <span class="keyword">from</span> <span class="string">'./log'</span></span><br><span class="line"></span><br><span class="line">log(<span class="string">'anran758'</span>);</span><br></pre></td></tr></table></figure><p><code>src/log.js</code> 导出了一个工具函数,它负责向控制台发送消息。<code>src/index.js</code> 是默认的入口文件,它引入 <code>log</code> 函数并调用了它。</p><p>上面的代码很简单,像这种模块化的代码按照传统 <code><script src></code> 引入的话,浏览器是不能正确执行的。可以在根目录上创建一个 <code>index.html</code> 引入 js 脚本来测试一下:</p><figure class="highlight html"><figcaption><span>/index.html</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Test<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="comment"><!-- 引入脚本 --></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./src/index.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>创建文件后,将上例代码复制到 <code>index.html</code> 中。保存并打开该文件,看看浏览器能否正确处理模块逻辑。不出意外的话,文件在浏览器打开后,浏览器开发者工具会抛出错误信息:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Uncaught SyntaxError: Cannot use import statement outside a module</span><br></pre></td></tr></table></figure><p>言下之意就是说浏览器不能正确的解析 <code>ES module</code> 语句,此时 webpack 就可以派上用场啦~ 在 <code>package.json</code> 中的 <code>scripts</code> 字段中添加如下命令:</p><figure class="highlight diff"><figcaption><span>/package.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> "scripts": {</span><br><span class="line"><span class="addition">+ "build": "webpack"</span></span><br><span class="line"><span class="deletion">- "test": "echo \"Error: no test specified\" && exit 1"</span></span><br><span class="line"> },</span><br></pre></td></tr></table></figure><p>在命令行输入 <code>npm run build</code> 调用 <code>webpack</code> 对当前项目进行编译,编译后的结果会输出到 <code>dist/main.js</code> 文件中(即便本地没有 dist 目录,它都会自动创建该目录)。输出文件后,修改 <code>index.html</code> 对 js 的引用:</p><figure class="highlight diff"><figcaption><span>/index.html</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> <body></span><br><span class="line"><span class="addition">+ <script src="./dist/main.js"></script></span></span><br><span class="line"><span class="deletion">- <script src="./src/index.js"></script></span></span><br><span class="line"> </body></span><br></pre></td></tr></table></figure><p>重新刷新页面后就能看到 <code>log</code> 正确的输出了 <code>Hello anran758!</code>。点击 log 右侧的链接,可以跳转至 <code>Source</code> 面板,将代码格式化后可以清晰地看到编译后 js 的变化:</p><img data-src="/blog/2020/05/04/webpack-example/example-1.png" class=""><h2 id="使用配置"><a href="#使用配置" class="headerlink" title="使用配置"></a>使用配置</h2><p>当然,上例代码只不过是小试牛刀。对于正式的项目会有更复杂的需求,因此需要自定义配置。<code>webpack</code> 主要有两种方式接收配置:</p><p><strong>第一种:</strong> 通过 <code>Node.js</code> API引入 webpack 包,在调用 webpack 函数时传入配置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">"webpack"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> webpackConfig = {</span><br><span class="line"> <span class="comment">// webpack 配置对象</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">webpack(webpackConfig, <span class="function">(<span class="params">err, stats</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err || stats.hasErrors()) {</span><br><span class="line"> <span class="comment">// 在这里处理错误</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 处理完成</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p><strong>第二种:</strong> 通过 <code>webpack-cli</code> 在终端使使用 webpack 时指定配置。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">webpack [--config webpack.config.js]</span><br></pre></td></tr></table></figure><p>两种方法内配置都是相似的,只是调用的形式不同。本篇先使用 <code>webpack-cli</code> 来做示例。</p><p>webpack 接受一个特定的配置文件,配置文件要求导出一个对象、函数、<code>Promise</code> 或多个配置对象组成的数组。</p><p>现在将上一章的 Demo 复制一份出来,并重命名为 **<a href="https://github.com/anran758/webpack-example/tree/master/getting-started-config">getting-started-config</a>**,在该目录下新建 <code>webpack.config.js</code> 文件,文件内容如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="comment">// 起点或是应用程序的起点入口</span></span><br><span class="line"> entry: <span class="string">"./src/index"</span>,</span><br><span class="line"> output: {</span><br><span class="line"> <span class="comment">// 编译后的输出路径</span></span><br><span class="line"> <span class="comment">// 注意此处必须是绝对路径,不然 webpack 将会抛错(使用 Node.js 的 path 模块)</span></span><br><span class="line"> path: path.resolve(__dirname, <span class="string">"dist"</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 输出 bundle 的名称</span></span><br><span class="line"> filename: <span class="string">"bundle.js"</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的配置主要是定义了程序入口、编译后的文件输出目录。然后在 <code>src/index.js</code> 中修改一些内容用来打包后测试文件是否被正确被编译:</p><figure class="highlight diff"><figcaption><span>src/index.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> import { log } from './log'</span><br><span class="line"></span><br><span class="line"><span class="addition">+ log('本节在测试配置噢');</span></span><br><span class="line"><span class="deletion">- log('anran758');</span></span><br></pre></td></tr></table></figure><p>随后在终端输入 <code>num run build</code> 进行编译,可以看到 <code>dist</code> 目录下多了个 <code>bundle.js</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> npm run build</span></span><br><span class="line"><span class="meta">></span><span class="bash"> webpack --config ./webpack.config.js</span></span><br><span class="line"></span><br><span class="line">Hash: 3cd5f3bbfaf23f01de37</span><br><span class="line">Version: webpack 4.43.0</span><br><span class="line">Time: 117ms</span><br><span class="line">Built at: 05/06/2020 1:01:37 PM</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line">bundle.js 1010 bytes 0 [emitted] main</span><br><span class="line">Entrypoint main = bundle.js</span><br><span class="line">[0] ./src/index.js + 1 modules 123 bytes {0} [built]</span><br><span class="line"> | ./src/index.js 62 bytes [built]</span><br><span class="line"> | ./src/log.js 61 bytes [built]</span><br><span class="line"></span><br><span class="line">WARNING in configuration</span><br><span class="line">The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.</span><br><span class="line">You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/</span><br></pre></td></tr></table></figure><p>由于我们输出的文件名被修改了,此时还得修改 <code>html</code> 的引入路径。但每改一次输出目录,<code>HTML</code> 中的引入路径也得跟着改,这样替换的话就比较容易出纰漏。那能不能让 webpack 自动帮我们插入资源呢?答案是可以的。</p><h2 id="Plugin"><a href="#Plugin" class="headerlink" title="Plugin"></a>Plugin</h2><p>webpack 提供**插件(plugin)**的功能,它可以用于各种方式自定义 webpack 构建过程。</p><p><strong><a href="https://webpack.js.org/plugins/html-webpack-plugin/">html-webpack-plugin</a></strong> 可以在运行 webpack 时自动生成一个 <code>HTML</code> 文件,并将打包后的 <code>js</code> 代码自动插入到文档中。下面来安装它:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i --D html-webpack-plugin</span><br></pre></td></tr></table></figure><p>安装后在 <code>webpack.config.js</code> 中使用该插件:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"> const path = require('path');</span><br><span class="line"><span class="addition">+ const HtmlWebpackPlugin = require('html-webpack-plugin');</span></span><br><span class="line"></span><br><span class="line"> module.exports = {</span><br><span class="line"> // 起点或是应用程序的起点入口</span><br><span class="line"> entry: "./src/index",</span><br><span class="line"></span><br><span class="line"> // 输出配置</span><br><span class="line"> output: {</span><br><span class="line"> // 编译后的输出路径</span><br><span class="line"> // 注意此处必须是绝对路径,不然 webpack 将会抛错(使用 Node.js 的 path 模块)</span><br><span class="line"> path: path.resolve(__dirname, "dist"),</span><br><span class="line"></span><br><span class="line"> // 输出 bundle 的名称</span><br><span class="line"> filename: "bundle.js",</span><br><span class="line"> },</span><br><span class="line"><span class="addition">+ plugins: [</span></span><br><span class="line"><span class="addition">+ new HtmlWebpackPlugin({</span></span><br><span class="line"><span class="addition">+ title: 'Test Configuration'</span></span><br><span class="line"><span class="addition">+ })</span></span><br><span class="line"><span class="addition">+ ],</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>重新编译后 <code>HTML</code> 也被输出到 <code>dist</code> 目录下。查看 <code>dist/index.html</code> 的源码可以发现:不仅源码被压缩了,同时 <code><script></code> 标签也正确的引入了 <code>bundle.js</code>。</p><p>此时目录结构如下:</p><blockquote><p>后续目录展示会将 <code>node_modules</code>、<code>package-lock.json</code>、<code>yarn.lock</code> 这种对项目架构讲解影响不大的目录省略掉..</p></blockquote><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── dist</span><br><span class="line">│ ├── bundle.js</span><br><span class="line">│ ├── index.html</span><br><span class="line">│ └── main.js</span><br><span class="line">├── index.html</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│ ├── index.js</span><br><span class="line">│ └── log.js</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure><p>处理完资源自动插入的问题后,还有一个问题需要我们处理:虽然 webpack 现在能自动生成 <code>HTML</code> 并插入脚本,但我们还得在 <code>HTML</code> 中写其他代码逻辑呀,总不能去改 <code>/dist/index.html</code> 文件吧?</p><p>这个问题也很好解决。<code>html-webpack-plugin</code> 在初始化实例时,传入的配置中可以加上 <code>template</code> 属性来指定模板。配置后直接在指定模板上进行编码就可以解决这个问题了:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"> const path = require('path');</span><br><span class="line"> const HtmlWebpackPlugin = require('html-webpack-plugin');</span><br><span class="line"></span><br><span class="line"> module.exports = {</span><br><span class="line"> // 起点或是应用程序的起点入口</span><br><span class="line"> entry: "./src/index",</span><br><span class="line"> </span><br><span class="line"> // 输出配置</span><br><span class="line"> output: {</span><br><span class="line"> // 编译后的输出路径</span><br><span class="line"> // 注意此处必须是绝对路径,不然 webpack 将会抛错(使用 Node.js 的 path 模块)</span><br><span class="line"> path: path.resolve(__dirname, "dist"),</span><br><span class="line"> </span><br><span class="line"> // 输出 bundle 的名称</span><br><span class="line"> filename: "bundle.js",</span><br><span class="line"> },</span><br><span class="line"> plugins: [</span><br><span class="line"> // html-webpack-plugin</span><br><span class="line"> // https://github.com/jantimon/html-webpack-plugin#configuration</span><br><span class="line"> new HtmlWebpackPlugin({</span><br><span class="line"> title: 'Test Configuration',</span><br><span class="line"><span class="addition">+ template: path.resolve(__dirname, "./index.html"),</span></span><br><span class="line"> })</span><br><span class="line"> ],</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用模板后 <code>html-webpack-plugin</code> 也会自动将脚本插入到模板中。因此可以将模板中的 <code><script></code> 给去掉了。为了测试输出的文件是否使用了模板,在 <code><body></code> 内随便插入一句话,重新打包后预览输出的文件是否包含这句话:</p><figure class="highlight diff"><figcaption><span>/index.html</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"> <!DOCTYPE html></span><br><span class="line"> <html lang="en"></span><br><span class="line"> <head></span><br><span class="line"> <meta charset="UTF-8"></span><br><span class="line"> <meta name="viewport" content="width=device-width, initial-scale=1.0"></span><br><span class="line"><span class="addition">+ <title>Test Config</title></span></span><br><span class="line"><span class="deletion">- <title>Test</title></span></span><br><span class="line"> </head></span><br><span class="line"> <body></span><br><span class="line"><span class="addition">+ <p>Test Config</p></span></span><br><span class="line"><span class="deletion">- <script src="./dist/main.js"></script></span></span><br><span class="line"> </body></span><br><span class="line"> </html></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>修改文件后,重新打包就能看到模板也被压缩输出至 <code>/dist/index.html</code> 了,<code>script</code> 标签也正常的插入了。</p><h2 id="清理目录"><a href="#清理目录" class="headerlink" title="清理目录"></a>清理目录</h2><p>现在来看编译后的目录,我们发现 <code>dist/mian.js</code> 这文件是使用配置之前编译出来的文件,现在我们的项目已经不再需要它了。这种历史遗留的旧文件就应该在每次编译之前就被扔进垃圾桶,只输出最新的结果。</p><p><strong><a href="https://www.npmjs.com/package/clean-webpack-plugin">clean-webpack-plugin</a></strong> 或 <strong><a href="https://www.npmjs.com/package/rimraf">rimraf</a></strong> 可以完成清理功能。前者是比较流行的 webpack 清除插件,后者是通用的 unix 删除命令(安装该依赖包后 windows 平台也能用)。如果仅是清理 <code>/dist</code> 目录下文件的话,个人是比较倾向使用 <code>rimraf</code>的,因为它更小更灵活。而 <code>clean-webpack-plugin</code> 是针对 webpack 输出做的一系列操作。</p><p>在终端安装依赖:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D rimraf</span><br></pre></td></tr></table></figure><p><code>rimraf</code> 的命令行的语法是: <code>rimraf <path> [<path> ...]</code>,我们在 <code>package.json</code> 的 <code>scirpts</code> 中修改 <code>build</code> 的命令:</p><figure class="highlight diff"><figcaption><span>/package.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">"scripts": {</span><br><span class="line"><span class="addition">+ "build": "rimraf ./dist && webpack --config ./webpack.config.js"</span></span><br><span class="line"><span class="deletion">- "build": "webpack --config ./webpack.config.js"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> npm run build</span></span><br><span class="line"></span><br><span class="line"><span class="meta">></span><span class="bash"> rimraf ./dist && webpack --config ./webpack.config.js</span></span><br><span class="line"></span><br><span class="line">Hash: 763fe4b004e1c33c6876</span><br><span class="line">Version: webpack 4.43.0</span><br><span class="line">Time: 342ms</span><br><span class="line">Built at: 05/06/2020 2:35:49 PM</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> bundle.js 1010 bytes 0 [emitted] main</span><br><span class="line">index.html 209 bytes [emitted] </span><br><span class="line">Entrypoint main = bundle.js</span><br><span class="line">[0] ./src/index.js + 1 modules 123 bytes {0} [built]</span><br><span class="line"> | ./src/index.js 62 bytes [built]</span><br><span class="line"> | ./src/log.js 61 bytes [built]</span><br><span class="line"></span><br><span class="line">WARNING in configuration</span><br><span class="line">The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.</span><br><span class="line">You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/</span><br><span class="line">Child HtmlWebpackCompiler:</span><br><span class="line"> 1 asset</span><br><span class="line"> Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0</span><br><span class="line"> 1 module</span><br></pre></td></tr></table></figure><p>这样 webpack 输出的 <code>/dist</code> 目录始终是最新的东西。</p><h2 id="loader"><a href="#loader" class="headerlink" title="loader"></a>loader</h2><p>在正常的页面中,引入 <code>css</code> 样式表会让页面变得更美观。引入图片可以让页面内容更丰富。</p><p>然而 webpack 本体只能处理原生的 JavaScript 模块,你让它处理 <code>css</code> 或图片资源,它是无法直接处理的。为了处理这种问题,webpack 提供了 <strong><a href="https://webpack.js.org/concepts/loaders/">loader</a></strong> 的机制,用于对模块外的源码进行转换。</p><p><code>loader</code> 一般是单独的包,我们可以在社区找到对应 <code>loader</code> 来处理特定的资源。在使用前通过 <code>npm</code> 安装到项目的开发依赖中即可。<code>loader</code> 可以通过<a href="https://webpack.js.org/concepts/loaders/#configuration">配置</a>、<a href="https://webpack.js.org/concepts/loaders/#inline">内联</a>或 <a href="https://webpack.js.org/concepts/loaders/#cli">Cli</a> 这三种方式来使用。下文主要以 <code>配置</code> 的方式来使用。</p><h3 id="css"><a href="#css" class="headerlink" title="css"></a>css</h3><p>往常引入 <code>css</code> 样式表无非就是在 <code>html</code> 中通过 <code><link></code> 标签引入。现在想通过 webpack 来管理依赖得需要安装对应的 <code>loader</code> 来处理这些事。</p><p><strong><a href="https://github.com/webpack-contrib/css-loader">css-loader</a></strong> 可以让 webpack 可以引入 <code>css</code> 资源。光有让 webpack 识别 css 的能还不够。为了能将 <code>css</code> 资源进行导出,还要安装 <strong><a href="https://github.com/webpack-contrib/mini-css-extract-plugin">mini-css-extract-plugin</a></strong> 插件:</p><p>现在将上一节的 Demo 复制并重名为 <a href="https://github.com/anran758/webpack-example/tree/master/getting-started-loader-css">getting-started-loader-css</a>。进入新的项目目录后安装依赖:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -D css-loader mini-css-extract-plugin</span><br></pre></td></tr></table></figure><p>在更改配置之前,为了使项目结构更清晰,咱们按照文件类型重新调整<strong>源码目录结构</strong>。将 <code>src</code> 下的 <code>js</code> 文件都放进 <code>js</code> 文件夹中。同时创建 <code>/src/css/style.css</code> 样式表。调整后的目录结构如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│ ├── index.html</span><br><span class="line">│ ├── css</span><br><span class="line">│ │ └── style.css</span><br><span class="line">│ └── js</span><br><span class="line">│ ├── index.js</span><br><span class="line">│ └── log.js</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure><p>现在将 <a href="https://codepen.io/anran758/pen/QWbdgPx">Flexbox 布局用例</a> 中结尾的 Demo 迁移到项目中,测试一下效果:</p><details> <summary>HTML 源码</summary> <figure class="highlight html"><figcaption><span>/src/index.html</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Test<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panels"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel1"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item name"</span>></span>Alice<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>I<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item desc"</span>></span>Pixiv Content ID: 65843704<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel2"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item name"</span>></span>Birthday<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>II<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item desc"</span>></span>Pixiv Content ID: 70487844<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel3"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item name"</span>></span>Dream<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>III<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item desc"</span>></span>Pixiv Content ID: 65040104<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel4"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item name"</span>></span>Daliy<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>IV<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item desc"</span>></span>Pixiv Content ID: 64702860<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel5"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item name"</span>></span>Schoolyard<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>V<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item desc"</span>></span>Pixiv Content ID: 67270728<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure></details><details> <summary>CSS 源码</summary> <figure class="highlight css"><figcaption><span>/src/css/style.css</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">html</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">'helvetica neue'</span>;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">font-weight</span>: <span class="number">200</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#f7f7f7</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">body</span>,</span><br><span class="line"><span class="selector-tag">p</span> {</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panels</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">min-height</span>: <span class="number">100vh</span>;</span><br><span class="line"> <span class="attribute">overflow</span>: hidden;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">flex-direction</span>: column;</span><br><span class="line"> <span class="attribute">color</span>: white;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#ececec</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: center;</span><br><span class="line"> <span class="attribute">box-shadow</span>: inset <span class="number">0</span> <span class="number">0</span> <span class="number">0</span> <span class="number">5px</span> <span class="built_in">rgba</span>(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.1</span>);</span><br><span class="line"> <span class="attribute">transition</span>: font-size <span class="number">0.7s</span> <span class="built_in">cubic-bezier</span>(<span class="number">0.61</span>, -<span class="number">0.19</span>, <span class="number">0.7</span>, -<span class="number">0.11</span>),</span><br><span class="line"> flex <span class="number">0.7s</span> <span class="built_in">cubic-bezier</span>(<span class="number">0.61</span>, -<span class="number">0.19</span>, <span class="number">0.7</span>, -<span class="number">0.11</span>), background <span class="number">0.2s</span>;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">background-size</span>: cover;</span><br><span class="line"> <span class="attribute">background-position</span>: center;</span><br><span class="line"> <span class="attribute">cursor</span>: pointer;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel1</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#f4f8ea</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel2</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#fffcdd</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel3</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#beddcf</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel4</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#c3cbd8</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel5</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#dfe0e4</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.item</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">1</span> <span class="number">0</span> auto;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">transition</span>: transform <span class="number">0.5s</span>;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">1.6em</span>;</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">'Amatic SC'</span>, cursive;</span><br><span class="line"> <span class="attribute">text-shadow</span>: <span class="number">0</span> <span class="number">0</span> <span class="number">4px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.72</span>), <span class="number">0</span> <span class="number">0</span> <span class="number">14px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.45</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.name</span> {</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">100%</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel</span> <span class="selector-class">.index</span> {</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">4em</span> <span class="meta">!important</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.desc</span> {</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">100%</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.open-active</span> <span class="selector-class">.name</span>,</span><br><span class="line"><span class="selector-class">.open-active</span> <span class="selector-class">.desc</span> {</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel</span><span class="selector-class">.open</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">3</span>;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">40px</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></details><details> <summary>JavaScript 源码</summary> <figure class="highlight js"><figcaption><span>/src/js/index.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { log } <span class="keyword">from</span> <span class="string">'./log'</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">'../css/style.css'</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">installEvent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> panels = <span class="built_in">document</span>.querySelectorAll(<span class="string">'.panel'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">toggleOpen</span>(<span class="params"></span>) </span>{</span><br><span class="line"> panels.forEach(<span class="function"><span class="params">item</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (item === <span class="built_in">this</span>) <span class="keyword">return</span>;</span><br><span class="line"> item.classList.remove(<span class="string">'open'</span>)</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="built_in">this</span>.classList.toggle(<span class="string">'open'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">toggleActicon</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (e.propertyName.includes(<span class="string">'flex-grow'</span>)) {</span><br><span class="line"> <span class="built_in">this</span>.classList.toggle(<span class="string">'open-active'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 给每个元素注册事件</span></span><br><span class="line"> panels.forEach(<span class="function"><span class="params">panel</span> =></span> {</span><br><span class="line"> panel.addEventListener(<span class="string">'click'</span>, toggleOpen)</span><br><span class="line"> panel.addEventListener(<span class="string">'transitionend'</span>, toggleActicon)</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">installEvent();</span><br><span class="line">log(<span class="string">'本节在测试配置噢'</span>);</span><br></pre></td></tr></table></figure></details><p>修改 webpack 配置,引入 <code>css-loader</code> 和 <code>mini-css-extract-plugin</code>。既然已经对源码目录进行分类了,那顺便也给输出目录的文件也进行分类整理吧:</p><figure class="highlight diff"><figcaption><span>/webpack.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"> const path = require('path');</span><br><span class="line"><span class="addition">+ const HtmlWebpackPlugin = require('html-webpack-plugin');</span></span><br><span class="line"><span class="addition">+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');</span></span><br><span class="line"></span><br><span class="line">module.exports = {</span><br><span class="line"> // 起点或是应用程序的起点入口</span><br><span class="line"> entry: "./src/js/index",</span><br><span class="line"></span><br><span class="line"> // 输出配置</span><br><span class="line"> output: {</span><br><span class="line"> // 编译后的输出路径</span><br><span class="line"> // 注意此处必须是绝对路径,不然 webpack 将会抛错(使用 Node.js 的 path 模块)</span><br><span class="line"> path: path.resolve(__dirname, "dist"),</span><br><span class="line"></span><br><span class="line"> // 输出 bundle 的名称</span><br><span class="line"><span class="deletion">- filename: "bundle.js",</span></span><br><span class="line"><span class="addition">+ filename: "js/bundle.js",</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ module: {</span></span><br><span class="line"><span class="addition">+ rules: [</span></span><br><span class="line"><span class="addition">+ {</span></span><br><span class="line"><span class="addition">+ test: /\.css$/i,</span></span><br><span class="line"><span class="addition">+ use: [MiniCssExtractPlugin.loader, 'css-loader'],</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ ],</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"> plugins: [</span><br><span class="line"> // html-webpack-plugin</span><br><span class="line"> // https://github.com/jantimon/html-webpack-plugin#configuration</span><br><span class="line"> new HtmlWebpackPlugin({</span><br><span class="line"> title: 'Test Configuration',</span><br><span class="line"><span class="deletion">- template: path.resolve(__dirname, "./index.html"),</span></span><br><span class="line"><span class="addition">+ template: path.resolve(__dirname, "./src/index.html"),</span></span><br><span class="line"><span class="addition">+ }),</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ // 提取 css 到单独的文件</span></span><br><span class="line"><span class="addition">+ // https://github.com/webpack-contrib/mini-css-extract-plugin</span></span><br><span class="line"><span class="addition">+ new MiniCssExtractPlugin({</span></span><br><span class="line"><span class="addition">+ // 选项类似于 webpackOptions.output 中的相同选项,该选项是可选的</span></span><br><span class="line"><span class="addition">+ filename: 'css/index.css',</span></span><br><span class="line"><span class="addition">+ })</span></span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在我们根据上面的配置来解读 <code>loader</code> 的使用:</p><p>在上面的配置中,**<a href="https://webpack.js.org/configuration/module/">module</a>** 规定了如何处理项目中的不同类型的模块。**<a href="https://webpack.js.org/configuration/module/#modulerules">rules</a>** 是创建模块时,匹配请求的 <code>rule</code> (规则)数组。<code>rule</code> 是一个对象,其中最常见的属性就是 <a href="https://webpack.js.org/configuration/module/#ruletest">test</a> 、 <a href="https://webpack.js.org/configuration/module/#ruleuse">use</a> 和 <a href="https://webpack.js.org/configuration/module/#ruleloader">loader</a>。</p><p><code>rule.test</code> 是匹配条件,通常会给它提供一个<strong>正则表达式</strong>或是<strong>由正则表达式组成的数组</strong>。如果配置了 <code>test</code> 属性,那这个 <code>rule</code> 将匹配指定条件。比如匹配条件写为 <code>test: /\.css$/i</code>,这意味着给后缀为 <code>.css</code> 的文件使用 <code>loader</code>。</p><p><code>rule.use</code> 顾名思义就是使用,给符合匹配条件的文件使用 <code>loader</code>。它可以接收一个字符串,这个字符串会通过 webpack 的 <a href="https://webpack.js.org/configuration/resolve/#resolveloader">resolveLoader</a> 选项进行解析。该选项可以不配置,它内置有解析规则。比如下例中默认会从 <code>node_modules</code> 中查找依赖:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">use: <span class="string">'css-loader'</span></span><br></pre></td></tr></table></figure><p><code>rule.use</code> 还可以是应用于模块的 <a href="https://webpack.js.org/configuration/module/#useentry">UseEntry</a> 对象。<code>UseEntry</code> 对象内主要有 <code>loader</code> 和 <code>options</code> 两个属性:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// use 传入 UseEntry 类型的对象</span></span><br><span class="line">use: {</span><br><span class="line"> <span class="comment">// 必选项,要告诉 webpack 使用什么 loader</span></span><br><span class="line"> loader: <span class="string">'css-loader'</span>,</span><br><span class="line"> <span class="comment">// 可选项,传递给 loader 选项</span></span><br><span class="line"> options: {</span><br><span class="line"> modules: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>如果 <code>UseEntry</code> 对象内只设置 <code>loader</code> 属性,那它与单传的字符串的效果是一样的。而 <code>options</code> 是传递给 <code>loader</code> 的配置项,不同 <code>loader</code> 会提供有不同的 <code>options</code>。值得注意的是,如果 <code>use</code> 是以对象形式传入,**<code>loader</code> 属性是必填的,而 <code>options</code> 是可选的**。</p><p><code>rule.use</code> 还可以是一个函数,函数形参是正在加载的模块对象参数,最终该函数要返回 <code>UseEntry</code> 对象或数组:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">use: <span class="function">(<span class="params">info</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(info);</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> loader: <span class="string">'svgo-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> plugins: [{</span><br><span class="line"> cleanupIDs: { <span class="attr">prefix</span>: basename(info.resource) }</span><br><span class="line"> }]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印出函数的形参 <code>info</code> 可以看到该对象拥有如下属性:</p><ul><li><code>compiler</code>: 当前的 webpack 编译器(可以未定义)</li><li><code>issuer</code>: 导入正在加载的模块的模块的路径</li><li><code>realResource</code>: 始终是要加载的模块的路径</li><li><code>resource</code>: 要加载的模块的路径,通常等于 <code>realResource</code>。除非在请求字符串中通过 <a href="https://webpack.js.org/concepts/loaders/#inline">!=!</a> 覆盖资源名。</li></ul><p>由此可见,使用函数方式可用于按模块更改 loader 选项。</p><p><code>rule.use</code> 最常见的使用形式还是提供一个数组,数组中每项可以是字符串、<code>UseEntry</code> 对象、<code>UseEntry</code> 函数。这也是一个套娃的过程:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">use: [{ <span class="attr">loader</span>: MiniCssExtractPlugin.loader }, <span class="string">'css-loader'</span>],</span><br></pre></td></tr></table></figure><p>这里需要注意的是,**<code>rule</code> 中使用多个 <code>loader</code> 要注意其顺序<strong>。使用数组 <code>loader</code> 将会</strong>从右至左进行应用**。</p><p>比如上例中最先通过 <code>css-loader</code> 来处理 <code>.css</code> 文件的引入问题,再通过 <code>MiniCssExtractPlugin.loader</code> (Tips: 该值是 <code>loader</code> 的绝对路径)来提取出文件。如果反过来应用就会出问题了,<code>webpack</code> 都不知道如何引用 <code>css</code> 文件,自然提取不出东西啦。</p><p><code>rule.loader</code> 是 <code>rule.use</code> 的缩写,等价于 <code>rule.use: [{ loader }]</code>。webpack 像这样简写的配置属性还有很多,这样做有利也有弊。对于熟手来说,提供这种简便选项可以减少配置的嵌套关系,但对新手来说,这配置有种错综复杂的感觉。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="comment">// 匹配文件规则</span></span><br><span class="line"> test: <span class="regexp">/\.css$/i</span>,</span><br><span class="line"> <span class="comment">// rule.use 简写形式</span></span><br><span class="line"> loader: <span class="string">'css-loader'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接下来回归正题。重新编译 webpack,编译后的目录结构如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── dist</span><br><span class="line">│ ├── css</span><br><span class="line">│ │ └── index.css</span><br><span class="line">│ ├── index.html</span><br><span class="line">│ └── js</span><br><span class="line">│ └── bundle.js</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│ ├── css</span><br><span class="line">│ │ └── style.css</span><br><span class="line">│ ├── index.html</span><br><span class="line">│ └── js</span><br><span class="line">│ ├── index.js</span><br><span class="line">│ └── log.js</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure><h3 id="image"><a href="#image" class="headerlink" title="image"></a>image</h3><p>图片资源也是项目中的常见资源,引入图片资源同样需要安装 <code>loader</code>。处理图片资源的 <code>loader</code> 主要有两种,分别是 <code>url-loader</code> 和 <code>file-loader</code>。</p><h4 id="file-loader"><a href="#file-loader" class="headerlink" title="file-loader"></a>file-loader</h4><p><strong><a href="https://github.com/webpack-contrib/file-loader">file-loader</a></strong> 是将 <code>import/require()</code> 引入的文件解析为 url,并把文件输出到输出目录中。</p><p>复制一份新 <code>Demo</code> 并重命名为 **<a href="https://github.com/anran758/webpack-example/tree/master/getting-started-loader-images">getting-started-loader-images</a>**。在安装 <code>loader</code> 之前先做一个小优化:</p><p>如果我们会频繁修改源码文件,修改完后又要重新编译,这个步骤实际是有点繁琐的。webpack 有个 <code>watch</code> 选项可以监听文件变化,若文件有修改 webpack 将自动编译(若修改的是配置文件的话,还是需要重新运行命令)。</p><p>在 <code>package.json</code> 的 <code>script</code> 中给 webpack 添加 <code>-w</code> 选项:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">"scripts": {</span><br><span class="line"> "build:watch": "rimraf ./dist && webpack --config ./webpack.config.js -w"</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>接下来就可以安装依赖了:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D file-loader</span><br></pre></td></tr></table></figure><p>新建一个 <code>/src/images</code> 文件夹,往里面添加一些图片:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> .</span><br><span class="line"> ├── package.json</span><br><span class="line"> ├── src</span><br><span class="line"> │ ├── css</span><br><span class="line"> │ │ └── style.css</span><br><span class="line"><span class="addition">+ │ ├── images</span></span><br><span class="line"><span class="addition">+ │ │ ├── 01.jpg</span></span><br><span class="line"><span class="addition">+ │ │ ├── 02.png</span></span><br><span class="line"><span class="addition">+ │ │ ├── 03.jpg</span></span><br><span class="line"><span class="addition">+ │ │ ├── 04.png</span></span><br><span class="line"><span class="addition">+ │ │ ├── 05.png</span></span><br><span class="line"><span class="addition">+ │ │ ├── 06.jpg</span></span><br><span class="line"><span class="addition">+ │ │ ├── webpack.jpg</span></span><br><span class="line"><span class="addition">+ │ │ └── webpack.svg</span></span><br><span class="line"> │ ├── index.html</span><br><span class="line"> │ └── js</span><br><span class="line"> │ ├── index.js</span><br><span class="line"> │ └── log.js</span><br><span class="line"> └── webpack.config.js</span><br></pre></td></tr></table></figure><p>在 <code>webpack.config.js</code> 中配置 <code>loader</code>:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: /\.html$/i,</span><br><span class="line"> loader: 'html-loader',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> // 匹配文件规则</span><br><span class="line"> test: /\.css$/i,</span><br><span class="line"> // use 从右至左进行应用</span><br><span class="line"> use: [MiniCssExtractPlugin.loader, 'css-loader'],</span><br><span class="line"> },</span><br><span class="line"><span class="addition">+ {</span></span><br><span class="line"><span class="addition">+ test: /\.(png|jpe?g|gif|webp|svg)(\?.*)?$/,</span></span><br><span class="line"><span class="addition">+ use: {</span></span><br><span class="line"><span class="addition">+ loader: 'file-loader',</span></span><br><span class="line"><span class="addition">+ options: {</span></span><br><span class="line"><span class="addition">+ name: 'img/[name].[hash:8].[ext]'</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"> ],</span><br></pre></td></tr></table></figure><p>默认情况下图片会被输出到 <code>dist</code> 目录中,文件名也会被更改为一长串的哈希值。为了保持目录整洁,将要被输出的图片资源都归类到 <code>img</code> 目录中。</p><p>可以通过设定 <code>name</code> 或 <code>publicPath</code> 来指定目录:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 直接设置 name</span></span><br><span class="line">use: {</span><br><span class="line"> loader: <span class="string">'file-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> name: <span class="string">'img/[name].[hash:8].[ext]'</span>,</span><br><span class="line"> },</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者使用 publicPath,效果与上例等价</span></span><br><span class="line">use: {</span><br><span class="line"> loader: <span class="string">'file-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> publicPath: <span class="string">'img'</span>,</span><br><span class="line"> name: <span class="string">'[name].[hash:8].[ext]'</span>,</span><br><span class="line"> },</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p><code>name</code> 属性的值可以用 <code>/</code> 分层。除去最末尾一层的是文件名,前面每层 <code>/</code> 分隔都是嵌套的文件夹。比如值为 <code>static/img/[name].[hash:8].[ext]</code> 最后输出的结果是:根目录创建一个 <code>static</code> 目录,<code>static</code> 内又会创建一个 <code>img</code> 目录,<code>img</code> 内输出被引用的图片资源。</p><p>由于匹配的图片资源有很多,咱们不能写死输出的文件名,不然会引发重名问题,操作系统不准这样干。这时 **<a href="https://github.com/webpack-contrib/file-loader#placeholders">占位符(placeholder)</a>**就能排上用场了。<code>name</code> 中方括号包裹起来的是占位符,不同占位符会被替换成不同的信息。</p><p>比如上例中使用了三个占位符: <code>name</code> 是文件的名称、<code>hash</code> 是指定用于对文件内容进行 hash (哈希)处理的 hash 方法,后面冒号加数值代表截取 hash 的长度为 8、<code>ext</code> 是文件的扩展名。在文件名加入 <code>hash</code> 的用意是针对浏览器缓存而特意加入的。现在可以不用在意这种优化问题,未来会专门另起一篇文章讲优化的问题。</p><p>现在修改完 webapck 配置,接着再来完善上一节的 Demo。在 <code>/src/css/styles.css</code> 中使用 <code>backgournd-image</code> 引入图片:</p><details> <summary>css 引入图片资源</summary> <figure class="highlight css"><figcaption><span>/src/css/style.css</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 省略其他代码... */</span></span><br><span class="line"><span class="selector-class">.panel1</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#f4f8ea</span>;</span><br><span class="line"> <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">'../images/01.jpg'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel2</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#fffcdd</span>;</span><br><span class="line"> <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">'../images/02.png'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel3</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#beddcf</span>;</span><br><span class="line"> <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">'../images/03.jpg'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel4</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#c3cbd8</span>;</span><br><span class="line"> <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">'../images/04.png'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel5</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#dfe0e4</span>;</span><br><span class="line"> <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">'../images/05.png'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></details><p>重新编译后的结果如下:</p><details> <summary>编译结果</summary> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">></span><span class="bash"> rimraf ./dist && webpack --config ./webpack.config.js -w</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> webpack is watching the files…</span><br><span class="line"></span><br><span class="line"> Hash: 398663f1f4d417d17c94</span><br><span class="line"> Version: webpack 4.43.0</span><br><span class="line"> Time: 1086ms</span><br><span class="line"> Built at: 05/29/2020 2:19:03 PM</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> css/index.css 1.72 KiB 0 [emitted] main</span><br><span class="line"> img/01.a8e7ddb2.jpg 170 KiB [emitted] </span><br><span class="line"> img/02.46713ed3.png 744 KiB [emitted] [big] </span><br><span class="line"> img/03.70b4bb75.jpg 529 KiB [emitted] [big] </span><br><span class="line"> img/04.b7d3aa38.png 368 KiB [emitted] [big] </span><br><span class="line"> img/05.875a8bc2.png 499 KiB [emitted] [big] </span><br><span class="line"> index.html 990 bytes [emitted] </span><br><span class="line"> js/bundle.js 1.33 KiB 0 [emitted] main</span><br><span class="line"> Entrypoint main = css/index.css js/bundle.js</span><br><span class="line"> [0] ./src/css/style.css 39 bytes {0} [built]</span><br><span class="line"> [1] ./src/js/index.js + 1 modules 938 bytes {0} [built]</span><br><span class="line"> | ./src/js/index.js 873 bytes [built]</span><br><span class="line"> | ./src/js/log.js 60 bytes [built]</span><br><span class="line"> + 1 hidden module</span><br><span class="line"></span><br><span class="line"> WARNING in configuration</span><br><span class="line"> The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.</span><br><span class="line"> You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/</span><br><span class="line"></span><br><span class="line"> WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).</span><br><span class="line"> This can impact web performance.</span><br><span class="line"> Assets: </span><br><span class="line"> img/04.b7d3aa38.png (368 KiB)</span><br><span class="line"> img/05.875a8bc2.png (499 KiB)</span><br><span class="line"> img/02.46713ed3.png (744 KiB)</span><br><span class="line"> img/03.70b4bb75.jpg (529 KiB)</span><br><span class="line"></span><br><span class="line"> WARNING in webpack performance recommendations: </span><br><span class="line"> You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.</span><br><span class="line"> For more info visit https://webpack.js.org/guides/code-splitting/</span><br><span class="line"> Child HtmlWebpackCompiler:</span><br><span class="line"> 1 asset</span><br><span class="line"> Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0</span><br><span class="line"> [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 1.01 KiB {0} [built]</span><br><span class="line"> Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!src/css/style.css:</span><br><span class="line"> Entrypoint mini-css-extract-plugin = *</span><br><span class="line"> [0] ./node_modules/css-loader/dist/cjs.js!./src/css/style.css 3.09 KiB {0} [built]</span><br><span class="line"> [3] ./src/images/01.jpg 63 bytes {0} [built]</span><br><span class="line"> [4] ./src/images/02.png 63 bytes {0} [built]</span><br><span class="line"> [5] ./src/images/03.jpg 63 bytes {0} [built]</span><br><span class="line"> [6] ./src/images/04.png 63 bytes {0} [built]</span><br><span class="line"> [7] ./src/images/05.png 63 bytes {0} [built]</span><br><span class="line"> + 2 hidden modules</span><br></pre></td></tr></table></figure></details><p>当我们重新打开 <code>/dist/index.html</code> 时会发现图片并没有加载出来?查看 <code>css</code> 源码后发现原来是路径有问题,编译后的路径是 <code>img/01.a8e7ddb2.jpg</code> 这种相对路径。</p><p>由于 <code>css</code> 本身有一个文件夹,通过相对路径引入,那就会从 css 目录下进行查找。实际找到的是 <code>dist/css/img/01.a8e7ddb2.jpg</code> 这条路径。</p><p>遇到这种情况怎么办呢?我们可以给 <code>MiniCssExtractPlugin.loader</code> 添加 <code>publicPath</code> 选项用以修正路径,重新编译后就可以看到图片正确被加载了:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="comment">// 匹配文件规则</span></span><br><span class="line"> test: <span class="regexp">/\.css$/i</span>,</span><br><span class="line"> <span class="comment">// use 从右至左进行应用</span></span><br><span class="line"> use: [</span><br><span class="line"> {</span><br><span class="line"> loader: MiniCssExtractPlugin.loader,</span><br><span class="line"> options: {</span><br><span class="line"> publicPath: <span class="string">'../'</span>,</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="string">'css-loader'</span></span><br><span class="line"> ],</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>在 js 中也可以引用文件,打开 <code>/src/js/index.js</code>, 在原先的基础上添加如下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> img1 <span class="keyword">from</span> <span class="string">'../images/06.jpg'</span>;</span><br><span class="line"><span class="keyword">import</span> img2 <span class="keyword">from</span> <span class="string">'../images/webpack.jpg'</span>;</span><br><span class="line"><span class="keyword">import</span> img3 <span class="keyword">from</span> <span class="string">'../images/webpack.svg'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 省略其他代码...</span></span><br><span class="line"></span><br><span class="line">log(<span class="string">'测试图片引入~'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'img1 --> '</span>, img1);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'img2 --> '</span>, img2);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'img3 --> '</span>, img3);</span><br></pre></td></tr></table></figure><p>重新编译后可以在 <code>Console</code> 面板可以看到 js 输出了文件资源的路径:</p><img data-src="/blog/2020/05/04/webpack-example/file-loader-1.png" class=""><h4 id="url-loader"><a href="#url-loader" class="headerlink" title="url-loader"></a>url-loader</h4><p><a href="https://www.webpackjs.com/loaders/url-loader/">url-loader</a> 功能也类似于 <code>file-loader</code>,不同的是当文件大小(单位 byte)小于 <code>limit</code> 时,可以返回一个 <code>DataURL</code>。</p><p>为什么要用 <code>DataURL</code> 呢?我们知道页面上每加载一个图片资源,都会发起一个 <code>HTTP</code> 请求。而建立 <code>HTTP</code> 请求的过程是需要花时间的。因此可以将文件转为 <code>DataURL</code> 嵌入 <code>html/css/js</code> 文件中,这样可以有效减少 <code>HTTP</code> 建立连接时所带来额外的时间开销了。同时 <code>html/css/js</code> 文件也可以被浏览器缓存,<code>DataURL</code> 被引入后也能一同被缓存。</p><p>图片转 <code>DataURL</code> 也有缺点,那就是<strong>编码后文本储存所占的空间比图片会更大</strong>。这其实就是传输体积与 HTTP 连接数的权衡。所以最佳做法是将小图片转为 <code>DataURL</code>,转换后并不会有过多体积溢出,而大尺寸图片照常引入即可。</p><p>安装 <code>url-loader</code>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install url-loader -D</span><br></pre></td></tr></table></figure><p>修改 <code>webpack.config.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">rules: [</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 匹配文件规则</span></span><br><span class="line"> test: <span class="regexp">/\.css$/i</span>,</span><br><span class="line"> <span class="comment">// use 从右至左进行应用</span></span><br><span class="line"> use: [</span><br><span class="line"> {</span><br><span class="line"> loader: MiniCssExtractPlugin.loader,</span><br><span class="line"> options: { <span class="attr">publicPath</span>: <span class="string">'../'</span> }</span><br><span class="line"> },</span><br><span class="line"> <span class="string">'css-loader'</span></span><br><span class="line"> ],</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.(png|jpe?g|gif|webp)(\?.*)?$/</span>,</span><br><span class="line"> use: {</span><br><span class="line"> loader: <span class="string">'url-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> limit: <span class="number">10000</span>,</span><br><span class="line"> name: <span class="string">'img/[name].[hash:8].[ext]'</span></span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.(svg)(\?.*)?$/</span>,</span><br><span class="line"> use: {</span><br><span class="line"> loader: <span class="string">'file-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> name: <span class="string">'img/[name].[hash:8].[ext]'</span></span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line">],</span><br></pre></td></tr></table></figure><p>在上例中将 <code>png</code>、<code>jpg</code>、<code>jpeg</code>、<code>gif</code>、<code>webp</code> 文件交给 <code>url-loader</code> 处理,而 <code>svg</code> 仍由 <code>file-loader</code> 处理。这样做的理由是: <code>DataURL</code> 内联 svg 会破坏 <code>sprite 系统</code> (将多个 svg 合为一张使用的技术) 中使用的<a href="https://css-tricks.com/svg-fragment-identifiers-work/">Fragment Identifiers</a>,因此不将 svg 转为 <code>DataURL</code>。</p><p>给 <code>url-loader</code> 设定匹配规则后,配置 <code>name</code> 和 <code>limit</code> 选项。<code>url-loader</code> 的 <code>name</code> 选项与 <code>file-loader</code> 的 <code>name</code> 作用是相同的,就不再累述。</p><p><code>limit</code> 是指定以<strong>字节(byte)</strong> 为单位的文件最大尺寸。当文件尺寸<strong>小于等于</strong> <code>limit</code> 所设的值,那文件将会被转为 <code>DataURL</code>。相反,若文件尺寸<strong>大于</strong> <code>limit</code> 时,则使用备用 <code>loader</code>。默认备用 <code>loader</code> 是 <code>file-loader</code>。可以设定 <code>fallback</code> 选项来修改备用 <code>loader</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> loader: <span class="string">'url-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> limit: <span class="number">10000</span>,</span><br><span class="line"> name: <span class="string">'img/[name].[hash:8].[ext]'</span></span><br><span class="line"> fallback: <span class="string">'file-loader'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>limit</code> 的选值不易过大,可以设为 <code>10240</code> (10KB)或 <code>10000</code>,也可以根据项目实际情况进行调整。</p><p>现在来测试 <code>limit</code> 的效果。unix 系统可以在终端使用 <code>ls -l</code> 命令来查看文件信息:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">➜ getting-started-loader-images git:(master) ✗ cd ./src/images</span><br><span class="line">➜ images git:(master) ✗ ls -l </span><br><span class="line">total 6144</span><br><span class="line">-rwxr-xr-x 1 anran staff 173596 May 28 17:41 01.jpg</span><br><span class="line">-rwxr-xr-x 1 anran staff 761560 May 28 17:41 02.png</span><br><span class="line">-rwxr-xr-x 1 anran staff 542065 May 28 17:41 03.jpg</span><br><span class="line">-rwxr-xr-x 1 anran staff 376562 May 28 17:41 04.png</span><br><span class="line">-rwxr-xr-x 1 anran staff 510812 May 28 17:41 05.png</span><br><span class="line">-rw-r--r-- 1 anran staff 760117 May 28 17:41 06.jpg</span><br><span class="line">-rw-r--r--@ 1 anran staff 6943 May 30 13:54 webpack.jpg</span><br><span class="line">-rw------- 1 anran staff 647 May 28 21:33 webpack.svg</span><br></pre></td></tr></table></figure><p>从输出的信息可以看到 <code>webpack.svg</code> (647B) 和 <code>webpack.jpg</code> (6943B) 的文件尺寸都低于设定的 <code>limit: 10000</code>。由于 <code>svg</code> 文件不通过 <code>url-loader</code> 处理,那按照预想它将会被输出到 <code>/dist/img</code> 中。<code>webpack.jpg</code> 可以被 <code>url-loader</code>,那编译后应该被嵌入到 <code>js</code> 代码中。</p><p>重新编译测试一下:</p><details> <summary>编译结果</summary><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">➜ getting-started-loader-images git:(master) ✗ npm run build</span><br><span class="line"></span><br><span class="line"><span class="meta">></span><span class="bash"> [email protected] build /Users/anran/project_my/webpack-example/getting-started-loader-images</span></span><br><span class="line"><span class="meta">></span><span class="bash"> rimraf ./dist && webpack --config ./webpack.config.js</span></span><br><span class="line"></span><br><span class="line">Hash: 8d2e8c8220e86d46e388</span><br><span class="line">Version: webpack 4.43.0</span><br><span class="line">Time: 692ms</span><br><span class="line">Built at: 05/30/2020 2:08:46 PM</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> css/index.css 1.63 KiB 0 [emitted] main</span><br><span class="line"> img/01.a8e7ddb2.jpg 170 KiB [emitted] </span><br><span class="line"> img/02.46713ed3.png 744 KiB [emitted] [big] </span><br><span class="line"> img/03.70b4bb75.jpg 529 KiB [emitted] [big] </span><br><span class="line"> img/04.b7d3aa38.png 368 KiB [emitted] [big] </span><br><span class="line"> img/05.875a8bc2.png 499 KiB [emitted] [big] </span><br><span class="line"> img/06.5b8e9d1e.jpg 742 KiB [emitted] [big] </span><br><span class="line">img/webpack.258a5471.svg 647 bytes [emitted] </span><br><span class="line"> index.html 990 bytes [emitted] </span><br><span class="line"> js/bundle.js 10.5 KiB 0 [emitted] main</span><br><span class="line">Entrypoint main = css/index.css js/bundle.js</span><br><span class="line">[0] ./src/css/style.css 39 bytes {0} [built]</span><br><span class="line">[1] ./src/js/index.js + 4 modules 10.1 KiB {0} [built]</span><br><span class="line"> | ./src/js/index.js 881 bytes [built]</span><br><span class="line"> | ./src/js/log.js 60 bytes [built]</span><br><span class="line"> | ./src/images/06.jpg 63 bytes [built]</span><br><span class="line"> | ./src/images/webpack.jpg 9.08 KiB [built]</span><br><span class="line"> | ./src/images/webpack.svg 68 bytes [built]</span><br><span class="line"> + 1 hidden module</span><br><span class="line"></span><br><span class="line">WARNING in configuration</span><br><span class="line">The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.</span><br><span class="line">You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/</span><br><span class="line"></span><br><span class="line">WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).</span><br><span class="line">This can impact web performance.</span><br><span class="line">Assets: </span><br><span class="line"> img/04.b7d3aa38.png (368 KiB)</span><br><span class="line"> img/03.70b4bb75.jpg (529 KiB)</span><br><span class="line"> img/05.875a8bc2.png (499 KiB)</span><br><span class="line"> img/02.46713ed3.png (744 KiB)</span><br><span class="line"> img/06.5b8e9d1e.jpg (742 KiB)</span><br><span class="line"></span><br><span class="line">WARNING in webpack performance recommendations: </span><br><span class="line">You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.</span><br><span class="line">For more info visit https://webpack.js.org/guides/code-splitting/</span><br><span class="line">Child HtmlWebpackCompiler:</span><br><span class="line"> 1 asset</span><br><span class="line"> Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0</span><br><span class="line"> [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 1.37 KiB {0} [built]</span><br><span class="line">Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!src/css/style.css:</span><br><span class="line"> Entrypoint mini-css-extract-plugin = *</span><br><span class="line"> [0] ./node_modules/css-loader/dist/cjs.js!./src/css/style.css 2.98 KiB {0} [built]</span><br><span class="line"> [3] ./src/images/01.jpg 63 bytes {0} [built]</span><br><span class="line"> [4] ./src/images/02.png 63 bytes {0} [built]</span><br><span class="line"> [5] ./src/images/03.jpg 63 bytes {0} [built]</span><br><span class="line"> [6] ./src/images/04.png 63 bytes {0} [built]</span><br><span class="line"> [7] ./src/images/05.png 63 bytes {0} [built]</span><br><span class="line"> + 2 hidden modules</span><br></pre></td></tr></table></figure></details><details> <summary>编译后的目录</summary><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"> .</span><br><span class="line">├── dist</span><br><span class="line">│ ├── css</span><br><span class="line">│ │ └── index.css</span><br><span class="line">│ ├── img</span><br><span class="line">│ │ ├── 01.a8e7ddb2.jpg</span><br><span class="line">│ │ ├── 02.46713ed3.png</span><br><span class="line">│ │ ├── 03.70b4bb75.jpg</span><br><span class="line">│ │ ├── 04.b7d3aa38.png</span><br><span class="line">│ │ ├── 05.875a8bc2.png</span><br><span class="line">│ │ ├── 06.5b8e9d1e.jpg</span><br><span class="line">│ │ └── webpack.258a5471.svg</span><br><span class="line">│ ├── index.html</span><br><span class="line">│ └── js</span><br><span class="line">│ └── bundle.js</span><br><span class="line">├── package-lock.json</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│ ├── css</span><br><span class="line">│ │ └── style.css</span><br><span class="line">│ ├── images</span><br><span class="line">│ │ ├── 01.jpg</span><br><span class="line">│ │ ├── 02.png</span><br><span class="line">│ │ ├── 03.jpg</span><br><span class="line">│ │ ├── 04.png</span><br><span class="line">│ │ ├── 05.png</span><br><span class="line">│ │ ├── 06.jpg</span><br><span class="line">│ │ ├── webpack.jpg</span><br><span class="line">│ │ └── webpack.svg</span><br><span class="line">│ ├── index.html</span><br><span class="line">│ └── js</span><br><span class="line">│ ├── index.js</span><br><span class="line">│ └── log.js</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure></details><p>重新打开 <code>/dist/index.html</code> 后可以在浏览器控制台看到如下输出的信息:</p><img data-src="/blog/2020/05/04/webpack-example/url-loader-js.jpeg" class=""><h2 id="HTML-资源引入"><a href="#HTML-资源引入" class="headerlink" title="HTML 资源引入"></a>HTML 资源引入</h2><p>在 <code>HTML</code> 中有一种常见的情况是:在模板中通过相对路径引入图片、脚本等资源时,发现引入的资源都没有被打包进去。</p><p>为什么会发生这种情况呢?原来是 webpack 默认不会处理 <code>html</code> 中的资源引入。为了能使 <code>HTML</code> 能通过相对路径引入资源,主要有 3 种解决的方案:</p><h3 id="lodash-template"><a href="#lodash-template" class="headerlink" title="lodash template"></a>lodash template</h3><p>现在项目中 <code>/src/index.html</code> 是作为 <code>html-webpack-plugin</code> 的模板,在模板中可以使用 <a href="https://lodash.com/docs/4.17.15#template">lodash template</a> 语法(以下简称模板语法)来插入内容。语法格式为: <code><%= value %></code></p><p>比如在 <code>src/index.html</code> 的模板中插入图片:</p><figure class="highlight html"><figcaption><span>/src/index.html</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panels"</span>></span></span><br><span class="line"> <span class="comment"><!-- 其他代码略... --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel6"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">class</span>=<span class="string">"img"</span> <span class="attr">src</span>=<span class="string">"<%= require('./images/06.jpg').default %>"</span> <span class="attr">alt</span>=<span class="string">""</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight css"><figcaption><span>/src/css/style.css</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 为了使页面美观,再添加一些样式 */</span></span><br><span class="line"><span class="selector-class">.panel6</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">overflow</span>: hidden;</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#061927</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel6</span> <span class="selector-class">.item</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel6</span> <span class="selector-class">.img</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="attribute">transition</span>: transform <span class="number">0.4s</span> <span class="number">0.6s</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel6</span><span class="selector-class">.open</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">2</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.panel6</span><span class="selector-class">.open</span> <span class="selector-class">.img</span> {</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">1.2</span>);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>上例将通过 <code>require()</code> 函数引入图片。webpack 引入图片时默认是通过 <code>ESModule</code> 来引入的,因此解析的结果大致为 <code>{default: module}</code> 这种形式。因此后面还需要再加一个 <code>default</code>。这样就能正确的引入资源啦。</p><h3 id="静态目录"><a href="#静态目录" class="headerlink" title="静态目录"></a>静态目录</h3><p><strong>第二种</strong>就是新增一个静态目录 <code>static</code>(或者叫 <code>public</code>)。</p><p><code>HTML</code> 默认不是引用不了源码目录上的资源吗?那我就直接将资源输出到 <code>dist</code> 目录上。模板引用资源时直接引入输出后的文件不就行啦?</p><p><strong><a href="https://github.com/webpack-contrib/copy-webpack-plugin">copy-webpack-plugin</a></strong> 可以完成这种迁移的功能。它将从 <code>form</code> 处复制文件/文件夹,复制到 <code>to</code> (默认是 webpack 的输出目录)中。现在来安装它:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D copy-webpack-plugin</span><br></pre></td></tr></table></figure><p>新增 <code>static</code> 目录,并添加一些测试文件:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"> .</span><br><span class="line"> ├── package.json</span><br><span class="line"> ├── src</span><br><span class="line"> │ ├── css</span><br><span class="line"> │ │ └── style.css</span><br><span class="line"> │ ├── images</span><br><span class="line"> │ │ ├── 01.jpg</span><br><span class="line"> │ │ ├── 02.png</span><br><span class="line"> │ │ ├── 03.jpg</span><br><span class="line"> │ │ ├── 04.png</span><br><span class="line"> │ │ ├── 05.png</span><br><span class="line"> │ │ ├── 06.jpg</span><br><span class="line"> │ │ ├── webpack.jpg</span><br><span class="line"> │ │ └── webpack.svg</span><br><span class="line"> │ ├── index.html</span><br><span class="line"> │ ├── js</span><br><span class="line"> │ │ ├── index.js</span><br><span class="line"> │ │ └── log.js</span><br><span class="line"><span class="addition">+ │ └── static</span></span><br><span class="line"><span class="addition">+ │ └── images</span></span><br><span class="line"><span class="addition">+ │ ├── 06.jpg</span></span><br><span class="line"><span class="addition">+ │ ├── webpack.jpg</span></span><br><span class="line"><span class="addition">+ │ └── webpack.svg</span></span><br><span class="line"> └── webpack.config.js</span><br></pre></td></tr></table></figure><p>现在将 <code>src/static/images</code> 的所有文件(不管代码里有没有引入这些文件)都复制到 <code>dist/img</code> 中。</p><figure class="highlight js"><figcaption><span>/webpack.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line">{</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> CopyPlugin({</span><br><span class="line"> patterns: [</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">from</span>: path.resolve(__dirname, <span class="string">'src/static/images'</span>),</span><br><span class="line"> to: path.resolve(__dirname, <span class="string">'dist/img'</span>)</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果你不仅想要复制图片还想要复制其他诸如 css 样式表、js 脚本甚至是 excel 文件到输出目录的话。那可以考虑将 <code>static</code> 目录与 dist 目录进行合并,将 <code>static</code> 和 <code>dist</code> 下的目录名保持一致。</p><p>比如将 <code>static</code> 的下 <code>images</code> 文件夹更名为图片输出目录 <code>img</code>,这样打包后会输出到同一个目录中:</p><figure class="highlight js"><figcaption><span>/webpack.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line">{</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> CopyPlugin({</span><br><span class="line"> patterns: [</span><br><span class="line"> <span class="comment">// 如果只传 string 的话,那这个 string 相当于 from</span></span><br><span class="line"> <span class="comment">// path.resolve(__dirname, 'src', 'static'),</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// to 默认是 `compiler.options.output`, 也就是 dist 目录</span></span><br><span class="line"> <span class="comment">// {</span></span><br><span class="line"> <span class="comment">// from: path.resolve(__dirname, 'src/static'),</span></span><br><span class="line"> <span class="comment">// to: ''</span></span><br><span class="line"> <span class="comment">// },</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 当前配置中与上面两例等价</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">from</span>: path.resolve(__dirname, <span class="string">'src/static'</span>),</span><br><span class="line"> to: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>若指定文件/文件夹不想复制到 <code>dist</code> 中,还可以使用 <code>globOptions.ignore</code> 来忽略:</p><figure class="highlight js"><figcaption><span>/webpack.config.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line">{</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> CopyPlugin({</span><br><span class="line"> patterns: [</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">from</span>: path.resolve(__dirname, <span class="string">'src/static'</span>),</span><br><span class="line"> to: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> globOptions: {</span><br><span class="line"> ignore: [<span class="string">'/**/webpack.jpg'</span>, <span class="string">'/**/img/webpack.svg'</span>],</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>重新修改模板中的图片的引入的路径,使其指向输出目录的 <code>img</code>:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel6"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">class</span>=<span class="string">"img"</span> <span class="attr">src</span>=<span class="string">"./img/06.jpg"</span> <span class="attr">alt</span>=<span class="string">""</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>VI<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>编译后就能看到图片正确被引用了。</p><h3 id="html-loader"><a href="#html-loader" class="headerlink" title="html-loader"></a>html-loader</h3><p><strong>最后一种</strong>是安装 <code>html-loader</code>,让 webapck 可以处理 <code>html</code> 资源的引入。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -D html-loader</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.html$/i</span>,</span><br><span class="line"> loader: <span class="string">'html-loader'</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 省略其他 rule...</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>配置 <code>html-loader</code> 后,<code>HTML</code> 访问相对路径的资源就由 <code>html-loader</code> 来进行引入。将模板中的路径改为<strong>源码相对路径</strong>:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel6"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">class</span>=<span class="string">"img"</span> <span class="attr">src</span>=<span class="string">"./images/06.jpg"</span> <span class="attr">alt</span>=<span class="string">""</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>VI<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>在实际编译时,<code><img class="img" src="./images/06.jpg" alt=""></code> 中 <code>src</code> 的值会被转为 <code>require('./images/06.jpg')</code>,通过 webpack 引入后再将编译后的结果传入图片的 <code>src</code> 属性中。</p><p>此时重新编译后就可以正确引入了。但<strong>配置 <code>html-loader</code> 的方法会与方法二访问静态目录资源有点冲突</strong>。配置 <code>html-loader</code> 后就不能通过 <code>./</code>、<code>../</code> 这种相对路径来访问资输出目录的资源了。</p><p>如果我们配置了 <code>html-loader</code> 的同时又还想访问静态资源怎么办呢?这时可以通过根路径 <code>/</code> 逐层来访问,这样 <code>html-loader</code> 就不会处理这种路径:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"panel panel6"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">class</span>=<span class="string">"img"</span> <span class="attr">src</span>=<span class="string">"/img/06.jpg"</span> <span class="attr">alt</span>=<span class="string">""</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span> <span class="attr">class</span>=<span class="string">"item index"</span>></span>VI<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>现在问题又来了,若我们通过根路径来访问资源的话,那就不能单纯地打开文件来在浏览器查看效果了。因为直接打开文件到浏览器上,是通过 <code>file://</code> 协议打开的。浏览器实际上访问的路径是文件的绝对地址。</p><p>比如笔者打开文件后,浏览器地址栏展示的 url 是: <code>file:///Users/anran/project_my/webpack-example/getting-started-static-assets/dist/index.html</code>。现在通过根路径访问资源,需要浏览器补全为完整的 URL,经过浏览器补全后绝对路径是 <code>file:///img/06.jpg</code>。这样路径都是错误的自然就访问不到想要的资源啦。</p><p>如果有写过 <code>SPA(单页面应用)</code> 项目的朋友应该很熟悉。将 <code>SPA</code> 项目打包后直接访问 <code>index.html</code> 页面是空白的,这种情况多半就是从根路径引入资源失败而引起的。</p><p>这个问题解决的办法也很简单,就是将编译后的项目部署到服务器上,直接通过服务器进行访问,问题就迎刃而解了。为什么这样就可以解决了呢?</p><p>比如笔者的网站域名是 <code>anran758.github.io</code>,现在将页面部署到服务器后,直接在浏览器访问 <code>https://anran758.github.io/</code>,实际上访问的是 <code>/dist/index.html</code> 文件。<code>html</code> 通过相对路径访问<code>/img/06.jpg</code>,那补全后图片的路径就是 <code>https://anran758.github.io/img/06.jpg</code>。这样自然就能访问资源啦。</p><p>我们不妨通过 <code>Node.js</code> 起一个本地服务器测试一下。在 <code>/dist</code> 同级目录上新建一个 <code>server.js</code> 脚本,添加如下代码:</p><figure class="highlight js"><figcaption><span>/server.js</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">'./webpack.config'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line"><span class="keyword">const</span> PORT = <span class="number">8001</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置静态资源入口</span></span><br><span class="line">app.use(express.static(config.output.path));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听端口</span></span><br><span class="line">app.listen(PORT, <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.log(err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Listening at http://localhost:'</span> + PORT + <span class="string">'\n'</span>);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>上例脚本代码是通过 <a href="https://expressjs.com/">express</a> 快速搭建一个本地服务器,将服务器静态资源入口设为 <code>webpack.config.js</code> 的输出目录(也就是 <code>/dist</code>),随后启动服务器。</p><p><code>express</code> 是基于 <code>Node.js</code> 的 web 框架,要使用它之前需要安装依赖:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -D express</span><br></pre></td></tr></table></figure><p><code>package.json</code> 中添加个快捷入口,并在终端运行该脚本:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="comment">// 其他脚本..</span></span><br><span class="line"> <span class="attr">"test:prod"</span>: <span class="string">"node server.js"</span></span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">➜ getting-started-static-assets git:(master) ✗ npm run test:prod </span><br><span class="line"></span><br><span class="line"><span class="meta">></span><span class="bash"> [email protected] <span class="built_in">test</span>:prod /Users/anran/project_my/webpack-example/getting-started-static-assets</span></span><br><span class="line"><span class="meta">></span><span class="bash"> node server.js</span></span><br><span class="line"></span><br><span class="line">Server is running at http://localhost:8001 . Press Ctrl+C to stop.</span><br></pre></td></tr></table></figure><p>打开 <a href="http://localhost:8001/">http://localhost:8001</a> 后就能看到图片资源正确被引用了。</p><img data-src="/blog/2020/05/04/webpack-example/file-loader-2.jpeg" class=""><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>好啦,现在 webpack 基础篇也到了尾声。我们对上述知识做一个简单的小结:</p><p>webpack 是一个静态模块打包工具,它本体虽然只支持处理 javascript 的模块,但可以通过 loader 让 webpack 完成原本它不能处理的功能。</p><p>webpack 的提供插件的功能,插件可以针对某种需求做特定处理,比如自动给 <code>html</code> 插入资源。</p><p>除了静态目录的文件外,我们发现 webpack 输出的文件都是有依赖关系的。为什么会这么说呢?仔细看看 webpack 处理的逻辑就能想清楚了:</p><p>webpack 从程序的入口 <code>/src/js/index.js</code> 开始处理,入口文件引入了 <code>style.css</code>,而 <code>style.css</code> 内又引用了图片资源。然后 <code>HTML</code> 再通过 webpack 插件引入模板,再将这些资源插入模板中。这就是文件的依赖关系,这些依赖关系最终会生成一个**依赖图(Dependency Graph)**。</p><p>想必看到这里的各位对 webpack 都有了个比较清晰的概念了吧?当然这只是一个开始,后面还有一些高级的概念在本文中由于篇幅的限制无法一并理清。若对笔者 webpack 的笔记感兴趣的话可以继续关注此系列的更新,下一篇将围绕开发环境进行梳理。</p><p><strong>参考资料:</strong></p><ul><li><a href="https://webpack.js.org/">Webpack document</a></li><li><a href="https://github.com/facebook/create-react-app/pull/1180">Use file-loader for svgs #1180</a></li></ul>]]></content>
<summary type="html"><img src="/blog/2020/05/04/webpack-example/banner.png" class="">
<p>webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它对于前端工程师来说可谓是如雷贯耳,基本上现在的大型应用都是通过 webpack 进行构建的。</p>
<p>webpack 具有高度可配置性,它拥有非常丰富的配置。在过去一段时间内曾有人将熟练配置 webpack 的人称呼为 “webapck 工程师”。当然,这称呼只是个玩笑话,但也能从侧面了解到 webpack 配置的灵活与复杂。</p>
<p>为了能够熟练掌握 webpack 的使用,接下来通过几个例子循序渐进的学习如何使用 webpack。</p>
<p>以下 <code>Demo</code> 都可以在 Github 的 <a href="https://github.com/anran758/webpack-example">webpack-example</a> 中找到对应的示例,欢迎 star~</p></summary>
<category term="webpack" scheme="https://anran758.github.io/blog/categories/webpack/"/>
<category term="webpack" scheme="https://anran758.github.io/blog/tags/webpack/"/>
<category term="脚手架" scheme="https://anran758.github.io/blog/tags/%E8%84%9A%E6%89%8B%E6%9E%B6/"/>
<category term="环境搭建" scheme="https://anran758.github.io/blog/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
</entry>
<entry>
<title>Flexbox 布局实际用例</title>
<link href="https://anran758.github.io/blog/2020/02/22/flexbox-use-cases/"/>
<id>https://anran758.github.io/blog/2020/02/22/flexbox-use-cases/</id>
<published>2020-02-22T08:28:44.000Z</published>
<updated>2023-12-13T06:14:38.987Z</updated>
<content type="html"><![CDATA[<p>上篇文章介绍了 flexbox 的属性与示例,本文再通过几个 flex 布局的案例来体会 flex 布局的特性带来的便利和问题~</p><img data-src="/blog/2020/02/22/flexbox-use-cases/banner.png" class=""><span id="more"></span><h2 id="格式化上下文"><a href="#格式化上下文" class="headerlink" title="格式化上下文"></a>格式化上下文</h2><p>当我们给父容器设置 flex 属性后,flex 容器会在容器内创建一个新的 **flex 格式化上下文(formatting context)**。在这上下文中 <code>float</code>、 <code>clear</code> 将失去作用,<code>vertical-align</code> 对于 flex 元素也不再会起作用。</p><p>在实际开发中,当我们使用行内元素(<code>inline</code>、<code>inline-block</code>) 时,有时候可能会看到元素之间会有一个奇怪的间隙,并且设置的字体越大间隙就越大。原来这个间隙是我们在编写源代码时标签换行导致,不换行就不会出现这种情况。</p><p>多数情况下,我们在编写代码时会习惯用编辑器对代码进行格式化,格式化后会使这些标签换行从而导致间隙。这在要求像素级还原的项目中就有点尴尬了。</p><p>以前常见的做法是在父元素设置 <code>font-size: 0</code> 消除间隙,再设置子元素的字体大小。这样做确实有点麻烦,因此在 flex 上下文中,这些间隙默认就会被清除。</p><iframe height="328" style="width: 100%;" scrolling="no" title="flex vs inline gap" src="https://codepen.io/anran758/embed/GRJNQNb?height=328&theme-id=32168&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/GRJNQNb'>flex vs inline gap</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h2 id="圣杯布局"><a href="#圣杯布局" class="headerlink" title="圣杯布局"></a>圣杯布局</h2><p>通常我们使用 <code>flex</code> 布局更多的是用于整体的布局设计,如:</p><iframe height="382" style="width: 100%;" scrolling="no" title="[flex] 圣杯布局" src="https://codepen.io/anran758/embed/NWqbYvX?height=382&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/NWqbYvX'>[flex] 圣杯布局</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><p>在互联网早期,由于用户网路的限制,经常会出现 <code>html</code> 的内容显示出来但页面样式还没加载出来的情况,这会导致用户没能最先看到想看的东西。因此 <a href="https://alistapart.com/author/matthewlevine/">Matthew Levine</a> 在 2006 年提出了圣杯布局的概念,在 <code>HTML</code> 源代码中将用户想看的内容挪到次要内容的前面。</p><p>上例 demo 就是使用 flex 布局实现的圣杯布局,虽然在 <code>HTML</code> 源码里 <code>Main</code> 处于其他两块内容之上,但通过 <code>order</code> 属性可以调整元素间的顺序。</p><p>除此之外,还可以通过媒体查询(<code>@media</code>)做响应式页面,当屏幕宽度小于 <code>640px</code> 后仅需修改几项 flex 属性就可以改变布局排列的方式,十分灵活。</p><p>如果你使用过 <code>react/vue</code> 主流 UI 库的话,你就会发现他们使用布局容器也是 <code>flex</code> 布局实现的,比如 <a href="https://element.eleme.cn/#/zh-CN/component/container">Element UI</a>、<a href="https://next.ant.design/components/layout-cn/">Ant Design</a> 等。</p><h2 id="栅格布局"><a href="#栅格布局" class="headerlink" title="栅格布局"></a>栅格布局</h2><p>栅格布局也可以通过 flex 来实现:在以下的 demo 中,<code>HTML</code> 源码内的各元素都是平级,通过调整 flex 属性实现了跨行或跨列的效果。</p><iframe height="501" style="width: 100%;" scrolling="no" title="[flex] 栅格布局" src="https://codepen.io/anran758/embed/ZEGpdXP?height=501&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/ZEGpdXP'>[flex] 栅格布局</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h2 id="justify-content-尾列不整齐"><a href="#justify-content-尾列不整齐" class="headerlink" title="justify-content 尾列不整齐"></a>justify-content 尾列不整齐</h2><blockquote><p><a href="https://www.zhangxinxu.com/wordpress/2019/08/css-flex-last-align/">让CSS flex布局最后一行列表左对齐的N种方法</a> –By 张鑫旭</p></blockquote><p>多数情况下使用 <code>justify-content</code> 是要求子元素们散开,但尾列元素不够的时候,散开就显得很奇怪了,为此我们可以做如下处理:</p><iframe height="640" style="width: 100%;" scrolling="no" title="[flex] 解决 justify-content 尾部不整齐的问题" src="https://codepen.io/anran758/embed/VwLmJJm?height=640&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/VwLmJJm'>[flex] 解决 justify-content 尾部不整齐的问题</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h2 id="动画"><a href="#动画" class="headerlink" title="动画"></a>动画</h2><p>在 MDN <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties">Animatable CSS properties</a> 上列出了可以使用 <code>Animations</code> 或 <code>Transitions</code> 进行动画处理的属性,其中就有 <code>flex</code> 属性。因此还可以结合动画进行布局设计:</p><iframe height="510" style="width: 100%;" scrolling="no" title="[flex] flex 与动画" src="https://codepen.io/anran758/embed/QWbdgPx?height=510&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/QWbdgPx'>[flex] flex 与动画</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h2 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h2><p>通过以上几个案例是不是对 flex 布局的灵活有了更深的感受呢?以上 demo 大多借鉴已有的思路,如果你有什么好的想法,也可以自己动手尝试一番或分享出来~</p><p><strong>参考资料:</strong></p><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes">MDN | 使用 CSS 弹性盒子</a></li><li><a href="https://www.zhihu.com/question/28542816">一丝 | 以下 CSS 栅格布局除了用 table 以外,有什么其他的方法吗?</a></li><li><a href="https://www.zhangxinxu.com/wordpress/2019/08/css-flex-last-align/">张鑫旭 | 让 CSS flex布局最后一行列表左对齐的N种方法</a></li></ul><p><strong>Pixiv 背景图例:</strong></p><ol><li><a href="https://www.pixiv.net/artworks/65843704">ちょけ | アリスミクと白うさぎ</a></li><li><a href="https://www.pixiv.net/artworks/70487844">Azit | Miku</a></li><li><a href="https://www.pixiv.net/artworks/65040104">ぽむ | もっと高くまで!</a></li><li><a href="https://www.pixiv.net/artworks/64702860">雨陌 | 8.31</a></li><li><a href="https://www.pixiv.net/artworks/67270728">akino | つもりつもるキモチ。</a></li></ol>]]></content>
<summary type="html"><p>上篇文章介绍了 flexbox 的属性与示例,本文再通过几个 flex 布局的案例来体会 flex 布局的特性带来的便利和问题~</p>
<img src="/blog/2020/02/22/flexbox-use-cases/banner.png" class=""></summary>
<category term="CSS" scheme="https://anran758.github.io/blog/categories/CSS/"/>
<category term="css" scheme="https://anran758.github.io/blog/tags/css/"/>
<category term="layout" scheme="https://anran758.github.io/blog/tags/layout/"/>
<category term="flexbox" scheme="https://anran758.github.io/blog/tags/flexbox/"/>
<category term="transition" scheme="https://anran758.github.io/blog/tags/transition/"/>
</entry>
<entry>
<title>Flexbox 布局入门</title>
<link href="https://anran758.github.io/blog/2020/02/05/css-getting-started-with-flexbox/"/>
<id>https://anran758.github.io/blog/2020/02/05/css-getting-started-with-flexbox/</id>
<published>2020-02-05T13:36:54.000Z</published>
<updated>2023-12-13T06:14:38.316Z</updated>
<content type="html"><![CDATA[<p>互联网早期实现布局是需要通过多种不同属性组合才能实现我们想要的布局。</p><p>比如常见的垂直居中,刚接触 css 的朋友看到 <code>vertical-align: middle;</code> 这个属性可能就会认为它就是用于垂直居中的,但实际上并没有那么简单。如果想要通过该属性来实现垂直居中,还需要其他小伙伴配合。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">6px</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: center;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#e44b27</span>;</span><br><span class="line"> <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 该伪类是实现垂直居中关键 */</span></span><br><span class="line"><span class="selector-class">.container</span>:after {</span><br><span class="line"> content: <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.content</span> {</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">white-space</span>: normal;</span><br><span class="line"> <span class="attribute">vertical-align</span>: middle;</span><br><span class="line"> <span class="attribute">text-align</span>: left;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"content"</span>></span>我想居中!<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>这样看来,为了实现垂直居中布局,我们还得打一套组合拳才能出来才行,是不是看起来有点麻烦的样子?</p><p>W3C 在 2009 年提出的 <code>Fiexbox(flex)</code> 布局<a href="https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/">草案</a>,就是针对用户界面设计优化的 CSS 盒模型。如果使用 flex 布局来实现上面的垂直居中布局的话,可以简化为:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">6px</span>;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#e44b27</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 使用 flex 布局 */</span></span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span>我想居中!<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>修改后的代码就显得更精简了,也不需要其他小伙伴来搭把手。布局的事情就让 flex 家族自己来解决即可。</p><span id="more"></span><hr><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>应用 flex 布局的容器我们通常称为 **弹性盒子/容器(flex container)**。弹性容器可以由 <code>display: flex</code> 或 <code>display: inline-flex</code> 生成。弹性盒子的子项常称为 **弹性元素/项目(flex items)**,它以 flex 布局模型进行布局。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex | inline-flex;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果想要学习 flex 布局的工作方式,最先需要学习的是它自身的术语。下面直接引用 flex 草案中术语的介绍图:</p><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-terms.svg" class="" title="术语介绍图(en)"><p>别被原版英文术语给吓倒了,咱们翻译一下其实就很好理解了:</p><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-terms-tra-cn.svg" class="" title="翻译后的术语介绍"><p>在术语示意图中可以看到两根轴,分别是**主轴(main axis)<strong>和</strong>垂直交叉轴(cross axis)<strong>。同时标注了</strong>主轴起点(main start)<strong>与</strong>终点(main end)<strong>,</strong>交叉轴的起点(cross start)<strong>与</strong>终点(cross end)**。</p><p>默认情况下 flex 布局是按主轴的方向进行布局的。flex 元素所占据的 <strong>主轴空间(main size)</strong> 就是 flex 元素的宽度(width)、所占据的 <strong>交叉轴空间(cross size)</strong> 就是 flex 元素的高度(height)。</p><hr><h2 id="flex-容器属性"><a href="#flex-容器属性" class="headerlink" title="flex 容器属性"></a>flex 容器属性</h2><p>flex 容器里可以通过以下几种属性来控制容器的行为:</p><ul><li>flex-direction</li><li>flex-wrap</li><li>flex-flow</li><li>justify-content</li><li>align-content</li><li>align-items</li></ul><p>为了更好的观察各属性的行为,笔者在 codepen 上给不同属性都写了 demo 做参考。</p><p>目前有个新规范(<a href="https://drafts.csswg.org/css-align-3/#propdef-justify-content">CSS Box Alignment Module Level 3</a>)正处于工作草案的状态中,对一些属性添加新值,如 <code>[first|last]? baseline</code>、<code>self-start</code>、<code>self-end</code>、<code>start</code>、<code>end</code>、<code>left</code>、<code>right</code>、<code>unsafe | safe</code>。</p><p>这些新值多数浏览器都没实现,为了便于演示,此处仅讲解初始版本的值。<code>Firefox</code> 浏览器对新值实现的比较超前,也建议通过使用 <code>Firefox</code> 浏览器来查看 demo。</p><h3 id="flex-direction"><a href="#flex-direction" class="headerlink" title="flex-direction"></a>flex-direction</h3><p><code>flex-direction</code> 指示内部元素如何在 flex 容器中布局。可以简单的理解为 flex 容器的布局方向。其默认值为 <code>row</code>,可选语法如下:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 常用属性 */</span></span><br><span class="line"><span class="attribute">flex-direction</span>: row | row-reverse | column | column-reverse;</span><br></pre></td></tr></table></figure><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-row.png" class="" title="flex-direction-row 示例"> --><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-row-reverse.png" class="" title="flex-direction-row-reverse 示例"> --><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-column.png" class="" title="flex-direction-column 示例图"> --><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-column-reverse.png" class="" title="flex-direction-column-reverse 示例图"> --><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-direction-dir.png" class="" title="flex-direction dir 效果示例图"> --><ul><li><code>row</code>: <strong>主轴起点和主轴终点与内容方向相同</strong>。简而言之就是内容从左到右进行布局。</li><li><code>row-reverse</code>: 与 <code>row</code> 行为相同,但主轴起点和主轴终点对调了位置。</li><li><code>column</code>: 主轴由水平方向转为垂直方向,布局从上往下排。</li><li><code>column-reverse</code>: 主轴由水平方向转为垂直方向,布局从上往下排。</li></ul><p><strong>值得注意的是,全局属性 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir">dir</a> 的作用是指示元素的文本的方向性,该属性会受到 <code>row</code> 和 <code>row-reverse</code> 的影响。</strong></p><!-- **本例 codepen demo: [[vue demo] flex-direction](https://codepen.io/anran758/pen/XWJLqjM)** --><iframe height="486" style="width: 100%;" scrolling="no" title="[vue demo] flex-direction" src="https://codepen.io/anran758/embed/XWJLqjM?height=486&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/XWJLqjM'>[vue demo] flex-direction</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h3 id="flex-wrap"><a href="#flex-wrap" class="headerlink" title="flex-wrap"></a>flex-wrap</h3><p><code>flex-wrap</code> 指定 flex 元素单行显示还是多行显示 。如果可以换行,你甚至还可以通过该属性控制行的堆叠方向。它的取值如下所示:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">flex-wrap</span>: <span class="built_in">nowrap</span>(默认值) | wrap | wrap-reverse;</span><br></pre></td></tr></table></figure><p>可以通过本例 demo 右上角的按钮来修改元素的数量,观察三个值之间的变化:</p><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-wrap.png" class="" title="flex-wrap 示例图"> --><iframe height="357" style="width: 100%;" scrolling="no" title="[vue demo] flex-wrap" src="https://codepen.io/anran758/embed/OJPexQp?height=357&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/OJPexQp'>[vue demo] flex-wrap</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><ul><li><code>nowrap</code>: flex 容器宁愿压榨元素的空间也不肯换行。甚至压缩到一定地步后还会溢出容器。</li><li><code>wrap</code>: 若子项超过容器所容纳的宽度,则允许断行展示。</li><li><code>wrap-reverse</code>: 和 <code>wrap</code> 的行为一样,只是<strong>交叉轴起点与终点互换</strong>。</li></ul><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-wrap-diff-reverse.png" class="" title="对比 reverse"> --><!-- **本例 codepen demo: [[vue demo] flex-wrap](https://codepen.io/anran758/pen/OJPexQp?editors=1100)** --><h3 id="flex-flow"><a href="#flex-flow" class="headerlink" title="flex-flow"></a>flex-flow</h3><p><code>flex-flow</code> 属性是 <a href="#flex-direction">flex-direction</a> 和 <a href="#flex-wrap">flex-wrap</a> 的简写。这个没啥好说的,也就不额外写 demo 了。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 语法 */</span></span><br><span class="line"><span class="attribute">flex-flow</span>: <flex-direction> || <flex-wrap>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 单独设置 flex-direction 的属性 */</span></span><br><span class="line"><span class="attribute">flex-flow</span>: row;</span><br><span class="line"><span class="attribute">flex-flow</span>: column;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 单独设置 flex-wrap 的属性 */</span></span><br><span class="line"><span class="attribute">flex-flow</span>: nowrap;</span><br><span class="line"><span class="attribute">flex-flow</span>: wrap;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 同时设置两种属性,建议按照语法顺序进行书写 */</span></span><br><span class="line"><span class="attribute">flex-flow</span>: row nowrap;</span><br><span class="line"><span class="attribute">flex-flow</span>: column wrap;</span><br></pre></td></tr></table></figure><h3 id="justify-content"><a href="#justify-content" class="headerlink" title="justify-content"></a>justify-content</h3><p><code>justify-content</code> 属性定义了容器主轴中各 flex 元素之间的对齐方式。这是 flex 布局中常用的属性之一。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">justify-content</span>: normal |</span><br><span class="line"> space-between | space-around | space-evenly |</span><br><span class="line"> center | flex-start | flex-end</span><br></pre></td></tr></table></figure><p>在初始版本中,<code>justify-content</code> 的默认值为 <code>flex-start</code>。但在最新版本中的 chrome 浏览器被修改为了 <code>normal</code>。</p><p>为了对比属性之间的差异,本例 demo 将元素的两侧 <code>margin</code> 清空:</p><iframe height="400" style="width: 100%;" scrolling="no" title="[vue demo] justify-content" src="https://codepen.io/anran758/embed/mdyZZWM?height=400&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/mdyZZWM'>[vue demo] justify-content</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/justify-content-position.png" class="" title="按内容位置分类"> --><ul><li><code>normal</code>: 排列效果等同 <code>flex-start</code>。</li><li><code>flex-start</code>: 默认情况是左对齐,从行首开始排列。每行第一个 flex 元素与行首对齐,同时所有后续的 flex 元素与前一个对齐。</li><li><code>flex-end</code>: 默认情况下是右对齐,从行尾开始排列。每行最后一个 flex 元素与行尾对齐,其他元素将与后一个对齐。</li><li><code>center</code>: 该值使元素居中对齐。</li></ul><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/justify-content-distribution.png" class="" title="按内容位置分类"> --><ul><li><code>space-between</code>: 首尾两端对齐,内部元素之间的间距相等。</li><li><code>space-around</code>: 在每行上均匀分配弹性元素。相邻元素间距离相同,<strong>首尾两个元素的距离是相邻元素之间距离的一半</strong>。</li><li><code>space-evenly</code>: 主轴内各元素两侧均匀分配剩余空间。(注意此处与 <code>space-around</code> 的差异)</li></ul><!-- **本例 codepen demo: [[vue demo] justify-content](https://codepen.io/anran758/pen/mdyZZWM?editors=1100)** --><h3 id="align-items"><a href="#align-items" class="headerlink" title="align-items"></a>align-items</h3><p><code>align-items</code> 属性除了可以在 flex 布局中有效,还可以在 <code>grid(网格)</code> 布局中应用。在 flex 布局中它的作用是<strong>决定交叉轴的对齐方式</strong>。这也是 flex</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 主流浏览器已经实现的值 */</span></span><br><span class="line"><span class="attribute">align-items</span>: normal | flex-start | flex-end | center | baseline | stretch</span><br><span class="line"></span><br><span class="line">/* 新草案添加的值 */</span><br><span class="line">align-items: | start | end | [ first | last ]baseline | left | right</span><br></pre></td></tr></table></figure><iframe height="420" style="width: 100%;" scrolling="no" title="[vue demo] align-items" src="https://codepen.io/anran758/embed/qBEeNjb?height=420&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/qBEeNjb'>[vue demo] align-items</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-items-normal.png" class=""> --><ul><li><code>normal</code>: 在 flex 布局中 <code>normal</code> 的表现效果如同 <code>stretch</code> 一样。</li><li><code>stretch</code>: 弹性元素被在交叉轴轴方向被拉伸到与容器相同的高度或宽度。若容器没有设置高度,则取当前行中最高元素的高度,如本例中<strong>元素 4</strong> 是第一行中最高的元素,那第一行中的高度都被拉伸到与最高元素相同的高度。第二行中<strong>最高的元素是元素 2</strong>,因此第二行高度都取至元素 2。</li></ul><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-items-position.png" class=""> --><ul><li><code>flex-start</code>: 元素向交叉轴起点对齐。</li><li><code>flex-end</code>: 元素向交叉轴终点对齐。</li><li><code>center</code>: 元素在交叉轴居中。</li></ul><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-items-baseline.png" class=""><ul><li><code>baseline</code>: 所有元素向基线对齐。侧轴起点到元素基线距离最大的元素将会于侧轴起点对齐以确定基线。<strong>在例子中放大元素 6 的 <code>font-size</code>, 与 <code>center</code> 进行对比就能看到差异了。</strong></li></ul><!-- **本例 codepen demo: [[vue demo] align-items](https://codepen.io/anran758/pen/qBEeNjb)** --><h3 id="align-content"><a href="#align-content" class="headerlink" title="align-content"></a>align-content</h3><p><code>justify-content</code> 是作用于主轴上,而 <code>align-content</code> 则是用于定义交叉轴的对齐方式。值得注意的是,若 <strong>flex 容器内只有一根轴线,该属性将不起作用</strong>。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 主流浏览器已经实现的值 */</span></span><br><span class="line"><span class="attribute">align-content</span>: normal | space-between | space-around | space-evenly | stretch | center | flex-start | flex-end</span><br><span class="line"></span><br><span class="line">/* 主流浏览器多数未实现的值 */</span><br><span class="line">align-content: [first|last]? baseline, start, end, left, right</span><br></pre></td></tr></table></figure><p>父容器设置了 flex 布局后,若子元素没有设定 <code>height</code> 属性的话,默认会将容器内的子元素进行拉伸。</p><p>为了便于观察两者的差异,笔者在 demo 中新增一列进行对比。左列的 flex 元素使用 <code>height</code> 属性,右列使用 <code>min-height</code> 属性。同时将 flex 容器高度设置为 <code>400px</code>:</p><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-content-normal.png" class=""> --><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-content-flex-position.png" class=""> --><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-content-space.png" class=""> --><iframe height="657" style="width: 100%;" scrolling="no" title="[vue demo] align-content" src="https://codepen.io/anran758/embed/OJPKWyj?height=657&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/OJPKWyj'>[vue demo] align-content</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><ul><li><code>normal</code>: 像未设置值,元素处于默认位置。</li><li><code>stretch</code>: 拉伸所有行来填满剩余空间。剩余空间平均的分配给每一行(若某元素设置了高度,那么该值对这个元素将不会起作用)。</li><li><code>flex-start</code>: 交叉轴起点对齐。</li><li><code>flex-end</code>: 交叉轴终点对齐。</li><li><code>center</code>: 交叉轴居中对齐。</li><li><code>space-between</code>: 交叉轴两端对齐,行之间间距相等</li><li><code>space-around</code>: 交叉轴均匀对齐,行两端间距相等</li><li><code>space-evenly</code>: 交叉轴内各元素两侧均匀分配剩余空间。</li></ul><!-- **本例 codepen demo: [[vue demo] align-content](https://codepen.io/anran758/pen/OJPKWyj)** --><hr><h2 id="Flex-Item"><a href="#Flex-Item" class="headerlink" title="Flex Item"></a>Flex Item</h2><p>Flex Container(弹性容器)的一级子元素就是 Flex item(弹性元素)。以下主要应用于 Flex item 的属性。</p><ul><li>flex-basis</li><li>flex-grow</li><li>flex-shrink</li><li>flex</li><li>align-self</li><li>order</li></ul><h3 id="flex-grow"><a href="#flex-grow" class="headerlink" title="flex-grow"></a>flex-grow</h3><p><code>flex-grow</code> 属性用于定义元素所占有的比例,它接受一个正整数,默认值为 <code>0</code>。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">flex-grow</span>: <number></span><br><span class="line"></span><br><span class="line">/* 例子: 仅接受正数的值 */</span><br><span class="line">flex-grow: <span class="number">1</span>;</span><br></pre></td></tr></table></figure><!-- --><iframe height="340" style="width: 100%;" scrolling="no" title="[vue] flex-grow" src="https://codepen.io/anran758/embed/LYEwdRV?height=340&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/LYEwdRV'>[vue] flex-grow</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><!-- **本例 codepen demo: [align-content](https://codepen.io/anran758/pen/OJPKWyj)** --><h3 id="flex-shrink"><a href="#flex-shrink" class="headerlink" title="flex-shrink"></a>flex-shrink</h3><p>与 <code>flex-grow</code> 相反,<code>flex-shrink</code> 属性处理元素收缩的问题,默认为 <code>1</code>,意味着元素默认会随着容器缩小而等比例缩小。当值为 <code>0</code> 时则不缩放。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">flex-shrink</span>: <number></span><br><span class="line"></span><br><span class="line">/* 例子: 默认缩放 */</span><br><span class="line">flex-shrink: <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 例子: 使元素不缩放 */</span></span><br><span class="line"><span class="attribute">flex-shrink</span>: <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>在以下 demo 中,各 flex 项目的宽高相等。当父容器有足够的空间时,元素不需要紧衣缩食,因此 <code>flex-shrink</code> 也没有机会表现出它的作用。</p><!-- --><iframe height="275" style="width: 100%;" scrolling="no" title="[vue] flex-shrink" src="https://codepen.io/anran758/embed/eYmqKpG?height=275&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/eYmqKpG'>[vue] flex-shrink</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><p>将 flex 容器尺寸调小后可以发现,<code>flex-shrink</code> 的值越大,元素被压榨的空间越多。</p><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-shrink-2.png" class=""> --><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-shrink-3.png" class=""><h3 id="flex-basis"><a href="#flex-basis" class="headerlink" title="flex-basis"></a>flex-basis</h3><p><code>flex-basis</code> 指定了 flex 元素在主轴空间(main size)所占的初始大小。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">flex-basis</span>: <<span class="string">'width'</span>></span><br></pre></td></tr></table></figure><p>当一个元素同时被设置了 <code>flex-basis</code> (值为 <code>auto</code> 除外)和 <code>width</code> 属性时,<code>flex-basis</code> 具有更高的优先级。</p><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-basis.png" class=""><p><code>W3C</code> 鼓励使用 <code>flex</code> 简写属性(下一小节进行秒速)来控制灵活性,而不是直接使用 <code>flex-basis</code> 属性。因为简写属性 <code>flex</code> 可以正确地重置任何未指定的属性以适应常见的用途。</p><iframe height="366" style="width: 100%;" scrolling="no" title="[vue] flex-basis" src="https://codepen.io/anran758/embed/yLymboP?height=366&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/yLymboP'>[vue] flex-basis</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h3 id="flex"><a href="#flex" class="headerlink" title="flex"></a>flex</h3><p><code>flex</code> 属性是 <code>flex-grow</code>、<code>flex-shrink</code>、<code>flex-basis</code> 的简写,规定了弹性元素如何伸缩以适应 flex 容器中的可用空间,默认值为 <code>0 1 auto</code>。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">flex</span>: none | [ <<span class="string">'flex-grow'</span>> <<span class="string">'flex-shrink'</span>>? || <<span class="string">'flex-basis'</span>> ]</span><br></pre></td></tr></table></figure><p><code>flex</code> 属性可以指定 1 个,2 个或 3 个值。</p><p><strong>单值语法</strong>: 值必须为以下其中之一:</p><ul><li>一个无单位数(<code><number></code>): 它会被当作 <code><flex-grow></code> 的值。</li><li>一个有效的宽度(width)值: 它会被当作 <code><flex-basis></code>的值。</li><li>关键字 <code>none</code>、<code>auto</code>、<code>initial</code>。</li></ul><p><strong>双值语法</strong>: 第一个值必须为一个无单位数,并且它会被当作 <code><flex-grow></code> 的值。第二个值必须为以下之一:</p><ul><li>一个无单位数:它会被当作 <code><flex-shrink></code> 的值。</li><li>一个有效的宽度值: 它会被当作 <code><flex-basis></code> 的值。</li></ul><p><strong>三值语法:</strong></p><ul><li>第一个值必须为一个无单位数,并且它会被当作 <code><flex-grow></code> 的值。</li><li>第二个值必须为一个无单位数,并且它会被当作 <code><flex-shrink></code> 的值。</li><li>第三个值必须为一个有效的宽度值, 并且它会被当作 <code><flex-basis></code> 的值。</li></ul><p>这个属性没啥好演示的,其实就是之前介绍的三个属性的组合:</p><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-flex.png" class=""><iframe height="345" style="width: 100%;" scrolling="no" title="[vue] flex" src="https://codepen.io/anran758/embed/dyPxaXw?height=345&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/dyPxaXw'>[vue] flex</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h3 id="align-self"><a href="#align-self" class="headerlink" title="align-self"></a>align-self</h3><p><code>align-self</code> 属性在 flex 布局中作用于单个 flex 元素上,它将控制指定元素在交叉轴上的位置。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">align-self</span>: auto | normal | stretch | center | flex-start | flex-end;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 多数浏览器未实现的功能 */</span></span><br><span class="line"><span class="attribute">align-self</span>: start | end | self-start | self-end | [first | last]? baseline;</span><br></pre></td></tr></table></figure><ul><li><code>auto</code>: 设置为父元素的 align-items 值,如果该元素没有父元素的话,就设置为 <code>stretch</code>。</li><li><code>normal</code>: 在 flex 布局中,相当于 <code>stretch</code> 的效果。</li><li><code>stretch</code>: flex 元素将会基于容器的宽和高,按照自身 margin box 的 cross-size 拉伸。</li><li><code>center</code>: 使项目在交叉轴中居中。</li><li><code>flex-start</code>: flex 元素会对齐到 cross-axis 的首端。</li><li><code>flex-end</code>: flex 元素会对齐到 cross-axis 的尾端。</li></ul><!-- <img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/align-self.png" class=""> --><iframe height="762" style="width: 100%;" scrolling="no" title="[vue] align-self" src="https://codepen.io/anran758/embed/mdJbyKz?height=762&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/mdJbyKz'>[vue] align-self</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h3 id="order"><a href="#order" class="headerlink" title="order"></a>order</h3><p><code>order</code> 属性用于设置指定 flex 元素在容器中的顺序。容器中的 flex 元素按升序值排序,若值相同则按其源代码出现的顺序进行排序,默认值为 <code>0</code>。它接受一个整数值(integer),如 <code>-2</code>、<code>0</code>、<code>3</code> 等。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">order</span>: <integer></span><br></pre></td></tr></table></figure><p>我们可以操作下面的 demo 来控制元素的顺序,比如将第三项元素通过 <code>order</code> 在移动到第一位。</p><img data-src="/blog/2020/02/05/css-getting-started-with-flexbox/flex-order.png" class=""><iframe height="863" style="width: 100%;" scrolling="no" title="[vue] order" src="https://codepen.io/anran758/embed/wvawEbL?height=863&theme-id=32168&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/wvawEbL'>[vue] order</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><hr><h2 id="兼容性"><a href="#兼容性" class="headerlink" title="兼容性"></a>兼容性</h2><iframe data-feature="flexbox" src="https://caniuse.bitsofco.de/embed/index.html?feat=flexbox&periods=future_3,current,past_1,past_2&accessible-colours=false" frameborder="0" width="100%" height="400px"></iframe><p>要将学到的新东西应用到实际项目中就不得不考虑其兼容性了。通过 <a href="https://caniuse.com/#feat=flexbox">caniuse</a> 我们可以看到:flex 布局经过多年的发展,主流浏览器都已经对 flex 布局基本模块都实现完毕了。</p><p><code>PC</code> 端需要考虑的是要不要兼容 <code>IE</code>,移动端最低兼容为 <code>ios 3.2+</code>、<code>Android 2.1+</code>。如果你需要开发微信小程序,那么小程序官方就推荐使用 flex 布局。</p><p>早期 flex 布局是通过 <code>display: box;</code> 来申明,这是使用了旧的规范,后来该值被 <code>flex</code> 给替换掉了。还有一些很低版本的浏览器或许还需要添加浏览器前缀才能使用 flex 布局。因此你在某处看到如下代码也不用感到奇怪,这是开发者在给布局做兼容呢:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.flex-center</span> {</span><br><span class="line"> <span class="attribute">display</span>: -webkit-box;</span><br><span class="line"> <span class="attribute">display</span>: -webkit-flex;</span><br><span class="line"> <span class="attribute">display</span>: -moz-box;</span><br><span class="line"> <span class="attribute">display</span>: -ms-flexbox;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> -webkit-box-pack: center;</span><br><span class="line"> -webkit-<span class="attribute">justify-content</span>: center;</span><br><span class="line"> -moz-box-pack: center;</span><br><span class="line"> -ms-<span class="attribute">flex</span>-pack: center;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> -webkit-box-align: center;</span><br><span class="line"> -webkit-<span class="attribute">align-items</span>: center;</span><br><span class="line"> -moz-box-align: center;</span><br><span class="line"> -ms-<span class="attribute">flex</span>-align: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但如果要我们在开发时手动写这种兼容好像不是很靠谱,兼容又冗余。所幸现在的前端开发都会使用脚手架,这些脚手架一般都会内置 <code>postcss</code> 和 <code>autoprefix</code> 之类的插件来帮助我们完成这些事。</p><p>还有一些朋友可能会说,我们老项目还是得要兼容 <code>IE 8+</code> 呀,是不是意味着跟 flex 布局无缘了?其实不是的,github 上有一个叫 <a href="https://github.com/jonathantneal/flexibility">flexibility</a> 的 <code>polyfill</code> 可以让 <code>IE8 +</code> 也实现 flex 布局效果.</p><hr><h2 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h2><p>本篇介绍了 flex 布局该如何使用、各属性的作用与效果,下一篇再详细讲讲 flex 布局在实际工作中的妙用~</p><p><strong>参考资料:</strong></p><ol><li><a href="https://www.w3.org/TR/css-flexbox-1/">CSS Flexible Box Layout Module Level 1</a></li><li><a href="https://drafts.csswg.org/css-align-3/">CSS Box Alignment Module Level 3</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Glossary/Flex_Item">Flex Item</a></li></ol><!-- 在 flex 布局中,弹性容器的子元素可以在任何方向上排布,也可以 “弹性伸缩” 其尺寸。既可以增加尺寸以填满未使用的空间,也可以收缩尺寸以避免父元素溢出。子元素的水平对齐和垂直对齐都能很方便的进行操控。 -->]]></content>
<summary type="html"><p>互联网早期实现布局是需要通过多种不同属性组合才能实现我们想要的布局。</p>
<p>比如常见的垂直居中,刚接触 css 的朋友看到 <code>vertical-align: middle;</code> 这个属性可能就会认为它就是用于垂直居中的,但实际上并没有那么简单。如果想要通过该属性来实现垂直居中,还需要其他小伙伴配合。</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> &#123;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">6px</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: center;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#e44b27</span>;</span><br><span class="line"> <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 该伪类是实现垂直居中关键 */</span></span><br><span class="line"><span class="selector-class">.container</span>:after &#123;</span><br><span class="line"> content: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.content</span> &#123;</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">white-space</span>: normal;</span><br><span class="line"> <span class="attribute">vertical-align</span>: middle;</span><br><span class="line"> <span class="attribute">text-align</span>: left;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;content&quot;</span>&gt;</span>我想居中!<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>这样看来,为了实现垂直居中布局,我们还得打一套组合拳才能出来才行,是不是看起来有点麻烦的样子?</p>
<p>W3C 在 2009 年提出的 <code>Fiexbox(flex)</code> 布局<a href="https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/">草案</a>,就是针对用户界面设计优化的 CSS 盒模型。如果使用 flex 布局来实现上面的垂直居中布局的话,可以简化为:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> &#123;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">6px</span>;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#e44b27</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 使用 flex 布局 */</span></span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">div</span>&gt;</span>我想居中!<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>修改后的代码就显得更精简了,也不需要其他小伙伴来搭把手。布局的事情就让 flex 家族自己来解决即可。</p></summary>
<category term="CSS" scheme="https://anran758.github.io/blog/categories/CSS/"/>
<category term="css" scheme="https://anran758.github.io/blog/tags/css/"/>
<category term="layout" scheme="https://anran758.github.io/blog/tags/layout/"/>
<category term="flexbox" scheme="https://anran758.github.io/blog/tags/flexbox/"/>
</entry>
<entry>
<title>Hexo blog 的升级与同步方案</title>
<link href="https://anran758.github.io/blog/2019/11/02/hexo-blog-upgrade-and-version-control/"/>
<id>https://anran758.github.io/blog/2019/11/02/hexo-blog-upgrade-and-version-control/</id>
<published>2019-11-02T01:44:24.000Z</published>
<updated>2021-10-04T09:19:41.853Z</updated>
<content type="html"><![CDATA[<p>前一篇我们介绍了如何使用 <code>Hexo</code> 框架及 <code>Next</code> 主题搭建博客。这次来聊聊如何安全的更新博客与主题的版本。</p><p><img data-src="https://user-images.githubusercontent.com/16272760/63487983-da41b080-c4df-11e9-951c-64883a8a5e9b.png" alt="next theme"></p><span id="more"></span><hr><p>早期写博客时笔者就有考虑过使用 <code>git</code> 来做版本控制,那时 <code>github</code> 私人仓库还没有免费开放,国内虽然有 <code>coding</code> 和码云这些平台有开放少量的私人仓库,但由于懒得折腾就选了最方便同步的 <code>OneDrive</code>(因为它只需将文件夹移入就可以实现跨设备共享)。</p><p>后来笔者因为工作的原因,需要在多设备中频繁切换,这种简单同步方式就会暴露出一些问题。比如说,在<strong>设备 A</strong> 想对博客做一些自定义的修改,其中可能会动到依赖,但此时<strong>设备 B</strong> 的文件正在同步,那这样可能会导致文件不一致的问题。可能会将旧的文件重新同步过来,这可能会导致程序报错,还不易于排查。</p><p>冲突文件合并失败会额外添加如 <code>index-anran758's MacBook Pro.js</code> 之类的同名文件,并且发生冲突时是隐式的,你甚至不知道发生了冲突,这种体验使用不太友好。</p><p>因此 <code>OneDrive</code> 的同步方式适用于改动不会太大的文件。</p><hr><p>如果你对 <code>git</code> 版本控制比较熟悉的话,那可以通过 <code>git</code> 对 blog 进行版本控制。</p><p>使用源码托管平台的话就如上文所说主要有这么几种选择:</p><p>国内的 <a href="https://gitee.com/">gitee(码云)</a>、<a href="https://coding.net/">coding</a> 是一个不错的选择,代码的上传于下载速度也比较可观。国外可以使用 <a href="https://github.com/">github</a>,github 的私人仓库是今年才开放无限制免费创建仓库数量的,缺点由于众所周知的问题,有时可能拉代码速度较慢。</p><p>笔者使用的是 github 作为源码托管,下文将要介绍的方法对于 <code>git</code> 仓库是通用,因此根据自身的喜好选择对应的平台。</p><h2 id="博客托管"><a href="#博客托管" class="headerlink" title="博客托管"></a>博客托管</h2><p>托管 blog 源码的步骤如下:</p><ol><li><p>找到对应的平台,创建私人仓库(注意是 <strong>Private</strong>,不要将自己的私人配置也开源咯)。</p></li><li><p>仓库创建完毕后,得到仓库的地址。打开命令行,进入 <code>/blog</code> 目录下并输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 初始化 git 项目</span></span><br><span class="line">git init</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加一个名为 origin 的 remote</span></span><br><span class="line"><span class="comment"># your_repo_path 是你创建仓库得到的仓库地址</span></span><br><span class="line">git remote add origin your_repo_path</span><br></pre></td></tr></table></figure></li><li><p>由于 <code>/theme/next</code> 本身也是一个仓库,<code>git</code> 无法提交嵌套仓库的文件夹,因此需要在 <code>.gitignore</code> 添加配置,忽略该文件夹</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 其他忽略规则...</span></span><br><span class="line">themes/next/</span><br></pre></td></tr></table></figure></li><li><p>提交代码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 提交代码</span></span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">"new: blog 数据开始进行版本控制"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置上游(-u)并推送至远程的 master 分支</span></span><br><span class="line">git push -u origin master</span><br></pre></td></tr></table></figure></li><li><p>这样我们就完成了博客的源码托管。</p></li></ol><h2 id="主题托管"><a href="#主题托管" class="headerlink" title="主题托管"></a>主题托管</h2><p><code>Next theme</code> 官网介绍的安装方式如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入 blog 目录</span></span><br><span class="line"><span class="built_in">cd</span> blog</span><br><span class="line"></span><br><span class="line"><span class="comment"># 言下之意就是将该库克隆到 themes 目录下的 next 文件夹中</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/theme-next/hexo-theme-next themes/next</span><br></pre></td></tr></table></figure><p>在 <code>Next theme 7.0+</code> 版本中,主题嵌入了检查版本更新的代码,每当运行本地服务器时,都会进行检查版本号的更新。当有新的版本发布时会在命令行输出警告:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">WARN Your theme NexT is outdated. Current version: v7.4.2, latest version: v7.5.0</span><br><span class="line">WARN Visit https://github.com/theme-next/hexo-theme-next/releases <span class="keyword">for</span> more information.</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这时你想体验 <code>Next</code> 的新特性的话可能会有点麻烦,因为原先我们在旧版本上修改了配置,或添加了一些自定义的布局。这将会造成代码冲突。</p><p>因此我们需要独立开两条分支:</p><ul><li><code>master</code> 分支是官方发布的正式版本,我们不去修改 <code>master</code> 分支的中的任何文件。</li><li>另一条是我们自己创建的新分支,笔者命名为 <code>customize</code>, 言下之意为该分支含有我们自定义的修改,包括私人配置等。</li></ul><p>除此之外,由于主题配置文件(<code>theme/next/_config.yml</code>)中含有某些应用的 <code>appid</code> 或者 <code>secret</code>,这些配置不应该被其他人随意看到以防冒名滥用。因此我们应该将该项目额外添加一个 <code>remote</code> 来保存我们的私人配置。 具体操作如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 此时已经下载到了主题文件夹</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建并切换新分支</span></span><br><span class="line">git checkout -B customize</span><br><span class="line"></span><br><span class="line"><span class="comment"># 进行主题配置或其他修改操作</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交改动(未推送)</span></span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">"chg: 修改为自定义配置"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加一个名为 userRepo(名字可以自己定义,只要自己能搞清是哪个来源即可) 的新 remote,</span></span><br><span class="line">git remote add userRepo [email protected]:anran758/hexo-xxx-next.git</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置上游(即以后使用 git pull/status 时默认拉取 userRepo 源的 customize 分支),并推送指定 remote</span></span><br><span class="line">git push -u userRepo customize</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如此就完成了代码的追踪,以后使用 <code>next</code> 主题就不是从 <code>hexo-theme-next</code> 中获取了,而是我们自己的私人仓库 <code>hexo-xxx-next</code> 中获取,安装方式是一样的。</p><h2 id="版本升级"><a href="#版本升级" class="headerlink" title="版本升级"></a>版本升级</h2><h3 id="Next"><a href="#Next" class="headerlink" title="Next"></a>Next</h3><p>前文说过我们将源码托管的需求之一就是为了解决代码合并的问题,为了体验新版本的特性,我们需要将新版本的代码合并进我们的分支:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 从 origin/master 获取最新版本的代码</span></span><br><span class="line"><span class="comment"># 理论上我们不修改 master 分支的代码不会发生冲突</span></span><br><span class="line">git fetch origin</span><br><span class="line">git pull --no-commit origin master</span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换至 customize 分支</span></span><br><span class="line">git checkout customize</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查本地是否有文件改动,有的话需要进行 commit 提交或者使用 git stash 藏起来</span></span><br><span class="line">git status</span><br><span class="line"></span><br><span class="line"><span class="comment"># 合并代码</span></span><br><span class="line">git merge master</span><br></pre></td></tr></table></figure><p>我们最起码修改过 _config.yml,因此会发生冲突也不奇怪,有冲突咱们就解决冲突。</p><p>如果你使用 vscode 进行编码,侧边栏有一个源代码管理,打开它可以看到冲突的文件。</p><p>打开冲突的文件,判断冲突项确定要保留(删除)的代码,解决冲突后,提交到缓存区(git add .(file))。缓冲区有本次升级所涉及的代码,可以大致预览一下本次的更新都做了什么事</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将缓冲区的文件提交至 commit</span></span><br><span class="line">git commit -m <span class="string">"Merge release v(version) into customize branch"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交代码</span></span><br><span class="line">git push</span><br><span class="line"><span class="comment"># Counting objects: 99, done.</span></span><br><span class="line"><span class="comment"># Delta compression using up to 4 threads.</span></span><br><span class="line"><span class="comment"># Compressing objects: 100% (57/57), done.</span></span><br><span class="line"><span class="comment"># Writing objects: 100% (99/99), 12.86 KiB | 346.00 KiB/s, done.</span></span><br><span class="line"><span class="comment"># Total 99 (delta 71), reused 64 (delta 42)</span></span><br><span class="line"><span class="comment"># remote: Resolving deltas: 100% (71/71), completed with 41 local objects.</span></span><br><span class="line"><span class="comment"># To github.com:anran758/hexo-xxx-next.git</span></span><br><span class="line"><span class="comment"># 4a70c18..54805a2 customize -> customize</span></span><br></pre></td></tr></table></figure><p>升级完后运行本地服务器最后会输出一条:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INFO Congratulations! Your are using the latest version of theme NexT.</span><br></pre></td></tr></table></figure><h3 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h3><p>若最新版本的 <code>Hexo</code> 引入了你想要的新功能,你想更新 <code>Hexo</code> 版本的话,首先确定版本号变动的是哪一位。</p><p><code>package.json</code> 的版本号格式是数字由点分隔,如 <code>主版本号.功能版本号.补丁版本号</code>。若更新是主(大)版本号的话,则需要先修改 <code>dependencies</code> 依赖中 <code>hexo</code> 的主版本号,再输入 <code>npm update</code>。</p><p>以下是 <code>hexo@v3</code> 更新为 <code>hexo@v4</code> 的示例:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> // ...</span><br><span class="line"> "dependencies": {</span><br><span class="line"><span class="addition">+ "hexo": "^4.0.0",</span></span><br><span class="line"><span class="deletion">- "hexo": "^3.9.0",</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>命令行输入:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> npx hexo -v</span></span><br><span class="line">hexo: 3.9.0</span><br><span class="line">hexo-cli: 2.0.0</span><br><span class="line">os: Darwin 17.7.0 darwin x64</span><br><span class="line">node: 12.13.1</span><br><span class="line">v8: 7.7.299.13-node.16</span><br><span class="line">uv: 1.33.1</span><br><span class="line">zlib: 1.2.11</span><br><span class="line">brotli: 1.0.7</span><br><span class="line">ares: 1.15.0</span><br><span class="line">modules: 72</span><br><span class="line">nghttp2: 1.39.2</span><br><span class="line">napi: 5</span><br><span class="line">llhttp: 1.1.4</span><br><span class="line">http_parser: 2.8.0</span><br><span class="line">openssl: 1.1.1d</span><br><span class="line">cldr: 35.1</span><br><span class="line">icu: 64.2</span><br><span class="line">tz: 2019c</span><br><span class="line">unicode: 12.1</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> npm update</span></span><br><span class="line">+ [email protected]</span><br><span class="line">added 71 packages from 90 contributors, updated 14 packages and moved 5 packages in 12.513s</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> npx hexo -v</span></span><br><span class="line">hexo: 4.2.0</span><br><span class="line">hexo-cli: 3.1.0</span><br><span class="line">os: Darwin 17.7.0 darwin x64</span><br><span class="line">node: 12.13.1</span><br><span class="line">v8: 7.7.299.13-node.16</span><br><span class="line">uv: 1.33.1</span><br><span class="line">zlib: 1.2.11</span><br><span class="line">brotli: 1.0.7</span><br><span class="line">ares: 1.15.0</span><br><span class="line">modules: 72</span><br><span class="line">nghttp2: 1.39.2</span><br><span class="line">napi: 5</span><br><span class="line">llhttp: 1.1.4</span><br><span class="line">http_parser: 2.8.0</span><br><span class="line">openssl: 1.1.1d</span><br><span class="line">cldr: 35.1</span><br><span class="line">icu: 64.2</span><br><span class="line">tz: 2019c</span><br><span class="line">unicode: 12.1</span><br></pre></td></tr></table></figure><p>若只是后面两位版本号有变更的话,仅需输入 <code>npm update</code> 即可。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>单单从升级版本来合并代码的角度来看,实际上本地 commit 也可以做这种事,将 <code>commit</code> 储存在本地(<code>.git</code>)中不提交远端也是没有问题的,<code>OneDrive</code>也可以完成同步。</p><p>但从安全和可调试的角度来看,<code>OneDrive</code>的同步方式存在一定风险(懒的代价)。使用 <code>git</code> 版本控制可以清晰看到每一次提交的修改,不会多出奇奇怪怪的东西。必要的时候还可以进行回滚,相对来说更安全。但这种方案需要使用者了解一定的 <code>git</code> 知识。</p><p>从操作步骤来看,使用的 <code>git</code> 同步方案会产生多个仓库,这些仓库一般是拥有权限的人才能查看(修改)源码。比如完成了本文中两个仓库源码同步后,在另一台设备初次同步的步骤是:</p><ol><li>通过 <code>git clone</code> 下载 blog 本体。</li><li>通过 <code>git clone</code> 下载私人仓库 <code>next theme</code> 到 <code>/theme</code> 目录下。</li><li>进入两个仓库内安装对应的依赖</li></ol><p>以上可以在 blog 项目下的 <code>package.json</code> 设置 <code>scripts</code>,通过一条命令来完成这些事。</p><p>由此我们可以看到,相比 <code>OneDrive</code> 的懒人方案,<code>git</code> 方案的操作步骤会更繁琐。更新方式也从自动更新变成手动更新。</p><p>两者种方案各有利弊,具体采用什么方案就看朋友们的习惯啦~</p><hr><p>本文涉及到的 <code>git</code> 命令都是可以在 <a href="https://github.com/anran758/Front-End-Lab/tree/master/git">git 速查方案</a> 查找相应的解释。</p>]]></content>
<summary type="html"><p>前一篇我们介绍了如何使用 <code>Hexo</code> 框架及 <code>Next</code> 主题搭建博客。这次来聊聊如何安全的更新博客与主题的版本。</p>
<p><img src="https://user-images.githubusercontent.com/16272760/63487983-da41b080-c4df-11e9-951c-64883a8a5e9b.png" alt="next theme"></p></summary>
<category term="Hexo" scheme="https://anran758.github.io/blog/categories/Hexo/"/>
<category term="git" scheme="https://anran758.github.io/blog/tags/git/"/>
<category term="Hexo" scheme="https://anran758.github.io/blog/tags/Hexo/"/>
<category term="blog" scheme="https://anran758.github.io/blog/tags/blog/"/>
<category term="Next" scheme="https://anran758.github.io/blog/tags/Next/"/>
</entry>
<entry>
<title>将 JSON 数据格式输出至页面上</title>
<link href="https://anran758.github.io/blog/2019/08/24/js-%E5%B0%86JSON%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E8%BE%93%E5%87%BA%E8%87%B3%E9%A1%B5%E9%9D%A2%E4%B8%8A/"/>
<id>https://anran758.github.io/blog/2019/08/24/js-%E5%B0%86JSON%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E8%BE%93%E5%87%BA%E8%87%B3%E9%A1%B5%E9%9D%A2%E4%B8%8A/</id>
<published>2019-08-24T08:04:52.000Z</published>
<updated>2021-10-04T09:19:41.890Z</updated>
<content type="html"><![CDATA[<p><code>JSON</code> 是一种轻量级的数据交换格式,它有键值对集合(js 中的对象)和数组两种结构。<code>JSON</code>是一个通用的格式,在前后端语言中都能跟该 <code>JSON</code> 打交道。</p><p>有时候我们需要将 <code>JSON</code> 格式输入至页面展示的需求,其中还需要保持一定的索引,那么该如何实现呢?</p><span id="more"></span><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>我们将对象转为 <code>JSON</code> 字符串时会经常使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">JSON.stringify</a> 这个 API,其实该方法就内置有格式化的参数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> userInfo = {<span class="attr">name</span>: <span class="string">'anran758'</span>,<span class="attr">github</span>: <span class="string">'https://github.com/anran758'</span>};</span><br><span class="line"><span class="keyword">var</span> info = <span class="built_in">JSON</span>.stringify(userInfo, <span class="literal">null</span>, <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(info);</span><br><span class="line"><span class="comment">// "{↵ "name": "anran758",↵ "github": "https://github.com/anran758"↵}"</span></span><br></pre></td></tr></table></figure><p>在上面的代码中,我们第一个参数(<code>value</code>)传入了一个需要序列化的对象。第二个参数是<code>replacer</code>,用以对属性转换和处理,由于我们不需要额外的处理,因此传入一个<code>null</code>;第三个参数则是空格索引的个数,封顶是<code>10</code>,<code>0</code>或不传则没有空格。</p><p>在控制台打印出信息后,我们可以看的出来格式化的数据是带换行符,并且有缩进的格式。接下来我们就要考虑如何输出到页面中。</p><h2 id="输出"><a href="#输出" class="headerlink" title="输出"></a>输出</h2><p>只要学过<code>HTML</code>的朋友都知道,我们直接将数据输入至<code>HTML</code>中,空格缩进会被浏览器给忽略掉的。因此不能输入到 <code><div></code> 中。这时候又想到,<code>JSON</code>格式实际上也算是代码的一种,那能不能输入至雷士代码块的标签中呢?答案是可以的。</p><p><code>HTML</code> 中有两个标签可以展示源代码: <code><pre></code> 和 <code><code></code> 。它们之间不同之处在于:</p><ul><li><code><pre></code> 表示预定义<strong>格式文本</strong>,按照原文件中的编排,以等宽字体的形式展现出来,<strong>文本中的空白符(比如空格和换行符)都会显示出来</strong>。</li><li><code><code></code> 则是呈现一段计算机代码,它以浏览器的默认等宽字体显示,<strong>但并不一定会完整呈现原来的格式</strong>。</li></ul><p>这些标签知识实际上算是比较冷门的知识,或许远古的面试题会考这种知识点,平时很少会遇到。但是如果你经常使用<code>markdown</code>的话,那么这些标签在<code>markdown</code>中有不同的别名:</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">比如 markdown 语法中的 ``,实际上等同于 <span class="xml"><span class="tag"><<span class="name">code</span>></span></span> 标签。实际作用是短代码块标签</span><br><span class="line"></span><br><span class="line">而 markdown 语法中的长代码块就等同于 <span class="code">`<pre>`</span> 标签,不同的博客或者网站的应用中还可以对 <span class="code">`<pre>`</span> 加类名,用以展示不同的语言的语法高亮。</span><br></pre></td></tr></table></figure><p>通过三者之间的对比可以看出,只有 <code><pre></code> 才是符合我们需求的。</p><iframe height="300" style="width: 100%;" scrolling="no" title="代码格式输出 - demo1" src="//codepen.io/anran758/embed/NWKdVYQ/?height=300&theme-id=32168&default-tab=html,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/NWKdVYQ/'>代码格式输出 - demo1</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><p>确定好展示的方式后,就可以考虑进一步扩展格式化的功能。比如对象中还有属性是 <code>JSON</code> 字符串的话,咱也进一步的解析,直至最底层。想实现这种功能需要编写一个递归函数,考虑如下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isPlainObject = <span class="function">(<span class="params">v</span>) =></span> <span class="built_in">Object</span>.prototype.toString.call(v) === <span class="string">"[object Object]"</span></span><br><span class="line"><span class="keyword">const</span> isString = <span class="function">(<span class="params">v</span>) =></span> <span class="built_in">Object</span>.prototype.toString.call(v) === <span class="string">"[object String]"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 格式 JSON 字符串为对象</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author <span class="variable">anran758</span></span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{ any }</span></span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">formatJsonStrAsObj</span>(<span class="params">sample</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = sample;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isString(temp)) {</span><br><span class="line"> <span class="comment">// 因为有解析失败的可能,使用 try catch 做相应处理</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> temp = <span class="built_in">JSON</span>.parse(temp);</span><br><span class="line"> } <span class="keyword">catch</span> (ex) {</span><br><span class="line"> <span class="comment">// parse error,return this sample</span></span><br><span class="line"> <span class="keyword">return</span> sample;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isPlainObject(temp)) {</span><br><span class="line"> temp = { ...temp };</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Object</span>.keys(temp).forEach(<span class="function"><span class="params">key</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> item = temp[key];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 字符串或者对象进行递归确认</span></span><br><span class="line"> <span class="keyword">if</span> (isString(item) || isPlainObject(item)) {</span><br><span class="line"> temp[key] = formatJsonStrAsObj(item);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> temp;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将 JSON 字符串转换为带缩进的字符串</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{*}</span> </span>sample JSON 字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{number}</span> </span>[indnt=2] 缩进数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">formatJSONIndnt</span>(<span class="params">sample, indnt = <span class="number">2</span></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> newSample = formatJsonStrAsObj(sample);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isString(newSample)) <span class="keyword">return</span> newSample;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">JSON</span>.stringify(newSample, <span class="literal">null</span>, indnt);</span><br><span class="line"> } <span class="keyword">catch</span> (ex) {</span><br><span class="line"> <span class="keyword">return</span> newSample.toString();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> info = <span class="built_in">JSON</span>.stringify({</span><br><span class="line"> name: <span class="string">'anran758'</span>,</span><br><span class="line"> avatar: <span class="string">'https://xxx'</span>,</span><br><span class="line"> detail: <span class="built_in">JSON</span>.stringify({</span><br><span class="line"> desc: <span class="string">'some description'</span>,</span><br><span class="line"> level: <span class="number">2</span>,</span><br><span class="line"> })</span><br><span class="line">})</span><br><span class="line"><span class="keyword">const</span> data = formatJSONIndnt(info);</span><br><span class="line"><span class="built_in">console</span>.log(data);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以直接将 data 输出至 dom 中</span></span><br></pre></td></tr></table></figure><h2 id="输入"><a href="#输入" class="headerlink" title="输入"></a>输入</h2><p>上文讲了如何将数据输出至页面,以及扩展格式化功能的示例。接下来讲解输入方面的应用。</p><p>当用户从别的地方复制数据想粘贴至输入框时,可以在输入框上设置监控事件,触发事件后尝试帮用户格式化数据,示例代码如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">pre</span> <span class="attr">class</span>=<span class="string">"preview pre"</span>></span><span class="tag"></<span class="name">pre</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">class</span>=<span class="string">"textarea"</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> info = <span class="built_in">JSON</span>.stringify({</span><br><span class="line"> name: <span class="string">'anran758'</span>,</span><br><span class="line"> avatar: <span class="string">'https://xxx'</span>,</span><br><span class="line"> detail: <span class="built_in">JSON</span>.stringify({</span><br><span class="line"> desc: <span class="string">'some description'</span>,</span><br><span class="line"> level: <span class="number">2</span>,</span><br><span class="line"> })</span><br><span class="line">})</span><br><span class="line"><span class="keyword">const</span> data = formatJSONIndnt(info);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> textarea = <span class="built_in">document</span>.querySelector(<span class="string">'.textarea'</span>);</span><br><span class="line"><span class="keyword">const</span> preview = <span class="built_in">document</span>.querySelector(<span class="string">'.pre'</span>);</span><br><span class="line"></span><br><span class="line">preview.innerHTML = data;</span><br><span class="line">textarea.addEventListener(<span class="string">'paste'</span>, <span class="function">(<span class="params">e</span>) =></span> {</span><br><span class="line"> <span class="comment">// 阻止默认事件</span></span><br><span class="line"> e.preventDefault();</span><br><span class="line"> <span class="keyword">const</span> value = (e.clipboardData || <span class="built_in">window</span>.clipboardData).getData(<span class="string">'text'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里使用了上面定义的函数,进行格式化数据</span></span><br><span class="line"> e.target.value = formatJSONIndnt(value, <span class="number">2</span>);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">body</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">box-sizing</span>: border-box;</span><br><span class="line"> <span class="attribute">min-height</span>: <span class="number">100vh</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.preview</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#f5fcff</span>;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#d3eeff</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">3px</span>;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.textarea</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">margin-left</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">'SFMono-Regular'</span>, Consolas, <span class="string">'Liberation Mono'</span>, Menlo, Courier,</span><br><span class="line"> monospace;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.preview</span> + <span class="selector-class">.preview</span> {</span><br><span class="line"> <span class="attribute">margin-left</span>: <span class="number">10px</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><iframe height="300" style="width: 100%;" scrolling="no" title="代码格式输入 - demo1" src="//codepen.io/anran758/embed/oNvBrWm/?height=300&theme-id=32168&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/anran758/pen/oNvBrWm/'>代码格式输入 - demo1</a> by anran758 (<a href='https://codepen.io/anran758'>@anran758</a>) on <a href='https://codepen.io'>CodePen</a>.</iframe><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">JSON.stringify() - JavaScript | MDN</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"><code><pre></code> - HTML(超文本标记语言) | MDN</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"><code><code></code> - HTML(超文本标记语言) | MDN</a></li></ul>]]></content>
<summary type="html"><p><code>JSON</code> 是一种轻量级的数据交换格式,它有键值对集合(js 中的对象)和数组两种结构。<code>JSON</code>是一个通用的格式,在前后端语言中都能跟该 <code>JSON</code> 打交道。</p>
<p>有时候我们需要将 <code>JSON</code> 格式输入至页面展示的需求,其中还需要保持一定的索引,那么该如何实现呢?</p></summary>
<category term="JavaScript" scheme="https://anran758.github.io/blog/categories/JavaScript/"/>
<category term="JavaScript" scheme="https://anran758.github.io/blog/tags/JavaScript/"/>
</entry>
</feed>