-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
820 lines (393 loc) · 389 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>2024年终总结</title>
<link href="/2024/12/30/2024%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2024/12/30/2024%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>小醉闲眠 —— Gap Year。</p><span id="more"></span><ul><li>一月<ul><li><strong>工作</strong> :很忙,有些累。</li></ul></li><li>二月<ul><li><strong>春节</strong> :原地过了一个属于我的新年,开心。</li></ul></li><li>三月<ul><li><strong>八号</strong> :只身赴坡,这天要写进我的人生履历。</li><li><strong>养伤</strong> :过去几个月累积的运动伤害终于全部返还,膝盖寄,所有运动暂停。</li></ul></li><li>四月<ul><li><strong>提离职</strong></li></ul></li><li>五月<ul><li><strong>离职</strong> 、交接</li><li><strong>找房</strong> 、搬家</li></ul></li><li>六月<ul><li><strong>生病</strong> :阳了小半个月,咽痛鼻塞,终日浑浑噩噩。</li></ul></li><li>七月<ul><li>刷剧、吃零食、睡觉:好像胖了些,衣服开始紧了。</li></ul></li><li>八月<ul><li>奥运会:关注乒乓赛事。</li><li>黑神话悟空:挨打式通关,很虐很惊艳。至今没想明白为什么我一个糖豆人玩家要来凑这个热闹。</li></ul></li><li>九月<ul><li>刷剧、吃零食、睡觉:好像又胖了些,衣服开始穿不进了。</li></ul></li><li>十月<ul><li><strong>跳舞</strong> :再不去舞室,卡就过期了…</li><li>刷剧、吃零食、睡觉:好像又胖了些,衣服没有能穿的进的了。</li></ul></li><li>十一月<ul><li><strong>跳舞</strong></li><li>刷剧、少吃零食、睡觉</li></ul></li><li>十二月<ul><li><strong>跳舞</strong></li><li>少吃零食、睡觉</li></ul></li></ul><h4 id="三月八日"><a href="#三月八日" class="headerlink" title="三月八日"></a>三月八日</h4><p>前往新加坡,为看一场演唱会。出发前看其他博主的 vlog,各个哭的略显浮夸。<br>惊喜加曲,我哭的比博主还浮夸。</p><p>非常难忘的一晚,千金难买。</p><h4 id="工作暂停、生活先行"><a href="#工作暂停、生活先行" class="headerlink" title="工作暂停、生活先行"></a>工作暂停、生活先行</h4><p>终于在年少时的期待落实后,带着一些微妙的心情,离开曾经梦想的公司。<br>温暖的环境光、舒适的办公椅、整洁宽敞的工作台、纯粹的人际关系。<br>三年,这些都没变。</p><p>数次梦回学生时代,可能只是我想要一个暑假了吧。</p><h4 id="寒来暑往"><a href="#寒来暑往" class="headerlink" title="寒来暑往"></a>寒来暑往</h4><p>某天下午在去理发店的路上,不紧不慢的走着,悠闲的等红灯,顺便晒会太阳。<br>不禁感叹,原来人生还可以如此惬意。</p><p>很快,原定的暑假被我挥霍一空,那,那再来个寒假吧…<br>才发现,原来无所事事是这么快乐。</p><h4 id="无心插柳柳成荫"><a href="#无心插柳柳成荫" class="headerlink" title="无心插柳柳成荫"></a>无心插柳柳成荫</h4><p>膝盖活动受限后,养了半年。蹲、起、盘腿,都能做了,不会再疼。<br>舞室卡还剩三个月到期,转卡也没转出去,索性硬着头皮自己上。</p><p>第一个月,跟不上,就硬跳,站在边儿上,尽量不影响别人。<br>第二个月,还是跟不上,依旧硬跳,能记多些记多些。<br>后来的某一天,某一个瞬间,突然发现自己能记住一个八拍的动作了。<br>怎么记的不知道,但跳出来了。那股欣喜的心情,到现在还记得。</p><p>第三个月,两个八拍、三个八拍。这堂课的舞我可以顺利的完成,开始琢磨发力和定点。剧也不刷了,开始看各种 KPOP Live 和翻跳的视频。<br>慢慢的,我开始往前站,往中心站,不再盯着别人和老师,而是专注自己的动作。录舞也敢上前冲了。</p><p>有一天下课,老师拍拍我肩膀说“有进步啊”。<br>四个字,那天晚上入睡都是咧着牙睡的。<br>…<br>…<br>等反应过来时,已经晚了,又上头了…</p><h4 id="全年总结"><a href="#全年总结" class="headerlink" title="全年总结"></a>全年总结</h4><p>日上三竿我独眠,鱼在一边,酒在一边。<br>浓油赤酱饱三餐,早也香甜,晚也香甜。<br>睡衣得体胜丝绵,胖也可穿,瘦也可穿。<br>雨过天青驾小船,行也安然,躺也安然。</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>vscode-subtitle-reader插件开发</title>
<link href="/2024/11/16/vscode-subtitle-reader%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/"/>
<url>/2024/11/16/vscode-subtitle-reader%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/</url>
<content type="html"><![CDATA[<p>一个 vscode 插件开发记录,主要是用来满足自己阅读字幕文本的需要。</p><span id="more"></span><h3 id="一、想法诞生"><a href="#一、想法诞生" class="headerlink" title="一、想法诞生"></a>一、想法诞生</h3><p>习惯性收集影视剧字幕,模仿剧中角色说话的语气和神态,戏瘾大发的时候常有,于是阅读字幕文件也成了日常。</p><p>最开始用 Mac 自带文稿读,无字距行距无排版,看的眼睛生疼。</p><p>后来找到 Aegisub,编辑功能足够强大,既能更改字幕样式也能载入音频进行校准…</p><p>再后了解到 Pr 、剪映等剪辑软件也是可以自动导入、导出、预览字幕文件,但电脑风扇也同时飞起…</p><p>折腾一圈之后,觉得还不如直接用 vscode 打开字幕文本,像 markdown 文件预览那样在侧边也整一个预览面板。</p><p>之后就,撸起袖子干活。</p><h3 id="二、基础功能"><a href="#二、基础功能" class="headerlink" title="二、基础功能"></a>二、基础功能</h3><ul><li>字幕文件 <code>.ass</code>、<code>.ssa</code>、<code>.srt</code> 语法高亮</li><li>字幕文件阅读面板<ul><li>面板内容同步滚动</li><li>面板内容同步更新</li><li>面板内容样式</li><li>主语言样式切换(双语字幕)</li></ul></li><li>定制化图标<ul><li>字幕文件图标</li><li>功能按钮图标</li><li>插件logo</li></ul></li></ul><h4 id="自定义配置"><a href="#自定义配置" class="headerlink" title="自定义配置"></a>自定义配置</h4><table><thead><tr><th>配置项</th><th>说明</th><th>默认值</th></tr></thead><tbody><tr><td><strong>subtitleReader.autoOpen</strong></td><td>字幕文件打开时,是否自动打开阅读面板</td><td>true</td></tr><tr><td><strong>subtitleReader.autoClose</strong></td><td>字幕文件打开时,是否自动关闭阅读面板</td><td>false</td></tr><tr><td><strong>subtitleReader.showDialogueLineNumber</strong></td><td>是否展示行数</td><td>true</td></tr><tr><td><strong>subtitleReader.style</strong></td><td>自定义样式</td><td>{}</td></tr></tbody></table><p>默认用户打开字幕类型文件时,自动打开阅读面板,并展示字幕行数。</p><h3 id="三、着手开发"><a href="#三、着手开发" class="headerlink" title="三、着手开发"></a>三、着手开发</h3><h4 id="1-数据传递"><a href="#1-数据传递" class="headerlink" title="1. 数据传递"></a>1. 数据传递</h4><img src="/2024/11/16/vscode-subtitle-reader%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/extension%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%E5%9B%BE.png" class=""><p>代码主要分为 extension 和 webview 两部分:</p><ul><li>extension 可以看为一个 node 应用,负责命令注册、生成页面内容。</li><li>webview 则是网页是容器,用来完成页面显示和面板交互。</li></ul><p>二者通过调用 vscode.postMessage 接口传递数据信息,除此以外,其余部分(编译、开发)都是相对独立的。</p><h4 id="2-热更新、编译打包"><a href="#2-热更新、编译打包" class="headerlink" title="2. 热更新、编译打包"></a>2. 热更新、编译打包</h4><p>vscode 官方提供的命令行工具 vsce 可将项目代码打包成 <code>.vsix</code> 文件,也是发布到 marketplace 的最终产物。<br><code>package.json</code> 中 main 的属性用于设置插件入口文件路径,发布和安装时都会校验以确认插件的可用性。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">// package.json</span><br><span class="line">{</span><br><span class="line"> ...</span><br><span class="line"> "main": "./dist/extension.js",</span><br><span class="line"> "scripts": {</span><br><span class="line"> "build-extension": "NODE_ENV=production webpack --config ./build/webpack.extension.js",</span><br><span class="line"> "build-panel": "NODE_ENV=production webpack --config ./build/webpack.panel.js",</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>两部分编译,extension 打包应用相关代码,webview 打包相关静态文件,最终都输出到 <code>/dist</code> 路径下。</p><h5 id="extension-编译"><a href="#extension-编译" class="headerlink" title="extension 编译"></a>extension 编译</h5><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extension.ts <span class="comment"># 扩展打包入口文件,所需功能接口 & 命令注册</span></span><br></pre></td></tr></table></figure><p>dev: 开发模式下会自动打开新的 vscode 调试窗口和测试文件,不支持热更新;<br>prod: webpack 将以上文件打包到输出路径;</p><h5 id="webview-编译"><a href="#webview-编译" class="headerlink" title="webview 编译"></a>webview 编译</h5><figure class="highlight sh"><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">main.scss</span><br><span class="line">main.ts</span><br><span class="line">index.html <span class="comment"># 模板文件:不参与编译,需要打包到最终产物里,供 extension 渲染调用</span></span><br></pre></td></tr></table></figure><p>dev: <code>index.html</code>中的 js、css 路径是动态渲染的。若为开发模式,则会访问本地 dev server 的端口,以支持热更新;<br>prod: webpack 将以上三个静态文件打包到输出路径;</p><p>需要注意的是,若想要在 webview 中加载本地资源,需要将资源文件夹的 uri 提前使用 <code>localResourceRoots</code> 注册,如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> webviewPanel = vscode.<span class="property">window</span>.<span class="title function_">createWebviewPanel</span>({</span><br><span class="line"> <span class="attr">localResourceRoots</span>: [ your assets uri ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="3-UI-绘制"><a href="#3-UI-绘制" class="headerlink" title="3. UI 绘制"></a>3. UI 绘制</h4><p>功能快速开发时,先随便写了一套方便测试,写完自己还觉得挺好,今年回头一看啥也不是(<a href="https://github.com/Kuro-P/vscode-subtitle-reader/blob/791a1a4262d02ba4ddeaa1ced9d19ff723c0279b/images/extension-screenshot.png">点我查看</a>)…</p><p>于是趁着那点为组内分享会筹备的知识还没完全忘记。以紫色为基调,重新绘制了一套。</p><img src="/2024/11/16/vscode-subtitle-reader%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/UIdesign.png" class=""><h4 id="4-明暗主题切换"><a href="#4-明暗主题切换" class="headerlink" title="4. 明暗主题切换"></a>4. 明暗主题切换</h4><p>vscode 会在 webview 的 body 元素上注入 <code>data-vscode-theme-kind</code> 属性,用来标明用户当前使用的明暗主题,例如暗色主题值为 <code>vscode-dark</code>,webview 的 css 就会根据 body 元素此属性的值赋予元素对应的主题颜色。</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">@mixin</span> if-dark {</span><br><span class="line"> <span class="keyword">@at-root</span> {</span><br><span class="line"> <span class="selector-tag">body</span><span class="selector-attr">[data-vscode-theme-kind=<span class="string">"vscode-dark"</span>]</span> & {</span><br><span class="line"> <span class="keyword">@content</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="selector-tag">body</span><span class="selector-attr">[data-vscode-theme-kind=<span class="string">"vscode-high-contrast"</span>]</span> & {</span><br><span class="line"> <span class="keyword">@content</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@mixin</span> <span class="attribute">color</span>(<span class="variable">$dark-color</span>, <span class="variable">$color</span>) {</span><br><span class="line"> <span class="attribute">color</span>: <span class="variable">$color</span>;</span><br><span class="line"> <span class="keyword">@include</span> if-dark {</span><br><span class="line"> <span class="attribute">color</span>: <span class="variable">$dark-color</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><ul><li>打包 extension ( vsce package)</li><li>优化包体积<ul><li>压缩图片</li><li>生产环境不需要的文件移除(添加至 .vscodeignore 以避免打包)</li></ul></li><li>本地测试<ul><li>在本地 vscode 中安装打包好的测试包(需要更改 package.json 的版本,避免 vscode 使用旧包)</li></ul></li><li>发布<ul><li>通过 marketplace UI 界面手动上传 .vsix 文件</li><li>或者通过本地命令行中使用 vsce publish 发布</li></ul></li></ul><img src="/2024/11/16/vscode-subtitle-reader%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/marketplace.png" class=""><img src="/2024/11/16/vscode-subtitle-reader%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/extension-page.png" class="" title="extension page"><h3 id="五、开发日志"><a href="#五、开发日志" class="headerlink" title="五、开发日志"></a>五、开发日志</h3><ul><li>2023.5.5 看文档,完成 srt、ass 文件语法高亮(参照 vscode-subtitles)</li><li>2023.5.19 预览面板可以根据打开的文件自动打开,但是打开多个文件时 webview 显示有些问题</li><li>2023.6.7 内容更新已经写完,截止至此,插件的基本功能几乎都有了。本想参照 markdown-all-in-one 做同步滚动,但是 markdown 是滚动同步是双向的。目前只完成了单向同步,留给下次优化了</li><li>2024.4.30 优化编译脚本,实现 webviewPanel 开发时的热更新</li><li>2024.5.7 基础功能都已开发完毕,编译命令也调试好了,之后等有空发到 vscode 社区里就可以用了</li><li>2024.9.16 重新绘制样式和插件图标,统一 UI 风格</li><li>2024.10.17 发布到 marketplace</li><li>2024.11.6 添加自动发布 CI</li></ul><h3 id="六、一些心情"><a href="#六、一些心情" class="headerlink" title="六、一些心情"></a>六、一些心情</h3><p>功能实现并不难,重要的是取舍。不必要的想法太多只会耽误时间和进度。<br>开始和收尾工作同样重要,否则半途而废有头没尾也只是一个再次被抛弃的想法而已。</p>]]></content>
</entry>
<entry>
<title>libpag 初使用 & render-pag 的诞生</title>
<link href="/2023/12/28/libpag%E5%88%9D%E4%BD%BF%E7%94%A8-render-pag%E7%9A%84%E8%AF%9E%E7%94%9F/"/>
<url>/2023/12/28/libpag%E5%88%9D%E4%BD%BF%E7%94%A8-render-pag%E7%9A%84%E8%AF%9E%E7%94%9F/</url>
<content type="html"><![CDATA[<p>去年设计部门与客户端团队落地了一项新的动画方案: libpag,是腾讯开源的一个全平台动效插件。<br>正好去年年中有个前端需求需要用到 pag 动画,调研后封装成库方便组内调用。<br>今年主要是维护和修 bug,并支持了 React 组件渲染。 </p><span id="more"></span><h3 id="libpag-调研"><a href="#libpag-调研" class="headerlink" title="libpag 调研"></a>libpag 调研</h3><p>libpag 核心代码为 C++ 代码,其 Web 端是基于 WebAssembly + WebGL实现,最终生成的动画文件是二进制文件,所以在体积上很占优势。<br>引入时需要引入 <code>libpag.js</code> 和 <code>libpag.wasm</code> 两个文件。</p><h3 id="文档"><a href="#文档" class="headerlink" title="文档"></a>文档</h3><p>官方文档 <a href="https://pag.art/">https://pag.art</a> <br/><br>Github <a href="https://github.com/Tencent/libpag">https://github.com/Tencent/libpag</a> <br/><br>Github demo <a href="https://github.com/libpag/pag-web">https://github.com/libpag/pag-web</a> <br/><br>libpag 官方 CDN <a href="https://cdn.jsdelivr.net/npm/libpag@latest/lib/">https://cdn.jsdelivr.net/npm/libpag@latest/lib/</a> <br/><br>ffavc 官方 CDN <a href="https://cdn.jsdelivr.net/npm/ffavc@latest/lib/">https://cdn.jsdelivr.net/npm/ffavc@latest/lib/</a> <br/><br>(需要注意的是,腾讯的 CDN 并没有提供 Gzip, 所以访问起来会比较慢)</p><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>生成文件格式为二进制,文件相比于 json 小很多,且不用考虑图片文件外挂的问题(如 Lottie Web)</li><li>利用 canvas 实现播放,无需用户手动触发<ul><li>如果包含 BMP 序列帧则需要用户手动触发交互(因为 BMP 会调用视频标签),使用官方推荐的解码器 (ffavc) 可解决这一问题</li></ul></li><li>如果动画较长较复杂且没有交互,则更推荐使用 MP4。PAG 的优势在于矢量性能优于视频、BMP 预合成、可编辑性、跨平台支持等</li></ul><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ul><li>ES6 方式引入需要额外处理 .wasm 文件<ul><li>libpag 依赖的 wasm 文件是比较大的,有 2.9M 左右;官方推荐的方式的将 wasm 放到 CDN 上并开启 Gzip 压缩,压缩后的 Gzip 文件大概是 890k,可以秒加载</li></ul></li><li>由于底层用 WebAssembly 来编译 C++ 代码,需要手动 GC(手动 destroy 实例)</li></ul><h4 id="兼容性"><a href="#兼容性" class="headerlink" title="兼容性"></a>兼容性</h4><table><thead><tr><th><a href="http://godban.github.io/browsers-support-badges/"><img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" /></a><br/>Chrome</th><th><a href="http://godban.github.io/browsers-support-badges/"><img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" /></a><br/>Safari</th><th><a href="http://godban.github.io/browsers-support-badges/"><img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" /></a><br/>Chrome for Android</th><th><a href="http://godban.github.io/browsers-support-badges/"><img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" /></a><br/>Safari on iOS</th></tr></thead><tbody><tr><td>Chrome >= 69</td><td>Safari >= 11.3</td><td>Android >= 7.0</td><td>iOS >= 11.3</td></tr></tbody></table><p><strong>公司客户端需要兼容到 IOS >= 11、Android >= 5,针对不支持 pag 动画播放的设备将显示静态图片</strong></p><h3 id="绘制流程"><a href="#绘制流程" class="headerlink" title="绘制流程"></a>绘制流程</h3><img src="/2023/12/28/libpag%E5%88%9D%E4%BD%BF%E7%94%A8-render-pag%E7%9A%84%E8%AF%9E%E7%94%9F/render-pag%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B.png" class="" width="600"><h3 id="简要代码"><a href="#简要代码" class="headerlink" title="简要代码"></a>简要代码</h3><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="keyword">import</span> { <span class="title class_">PAGInit</span> } <span class="keyword">from</span> <span class="string">"libpag"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化 wasm 实例</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PAG</span> = <span class="keyword">await</span> <span class="title class_">PAGInit</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化 pag 文件数据</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">PAGData</span> = <span class="keyword">await</span> <span class="title function_">fetch</span>(pagUrl);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">PAGFileBuffer</span> = <span class="keyword">await</span> <span class="title class_">PAGData</span>.<span class="title function_">arrayBuffer</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">PAGFile</span> = <span class="keyword">await</span> <span class="variable constant_">PAG</span>.<span class="property">PAGFile</span>.<span class="title function_">load</span>(<span class="title class_">PAGFileBuffer</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 canvas</span></span><br><span class="line"><span class="keyword">const</span> canvasEl = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">"canvas"</span>);</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(canvasEl);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 绘制画布(结束)</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">PAGView</span> = <span class="keyword">await</span> <span class="variable constant_">PAG</span>.<span class="property">PAGView</span>.<span class="title function_">init</span>(<span class="title class_">PAGFile</span>, canvasEl);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="交互能力"><a href="#交互能力" class="headerlink" title="交互能力"></a>交互能力</h3><p>render-pag 用到的 libpag 功能如下(libpag 本身支持很多能力,可以在官网查看 PAGView Class 的方法)</p><ul><li>支持替换图片</li><li>支持替换文字</li><li>支持声音输出</li><li>支持播放/暂停/指定播放次数</li><li>设置播放进度/获取播放进度</li><li>获取动画总时长</li><li>刷新当前帧</li></ul><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><h4 id="wasm-文件加载"><a href="#wasm-文件加载" class="headerlink" title="wasm 文件加载"></a>wasm 文件加载</h4><ul><li>加载慢:开启 Gzip。<ul><li>wasm 文件有 2.9M,官方推荐开启 Gzip 并放到 CDN 上,以减少加载时间,或者让客户端缓存在本地。</li></ul></li><li>按需加载:不支持。<ul><li>绘制层的 wasm 是一个整体,并且已压缩到最小。</li></ul></li></ul><h4 id="卡顿-崩溃"><a href="#卡顿-崩溃" class="headerlink" title="卡顿 & 崩溃"></a>卡顿 & 崩溃</h4><ul><li>受 PAG 渲染性能影响,不要在同屏播放多个 PAG 动画,否则动画会明显卡顿</li><li>4.1.18 以下的老版本 libpag 会内存泄露使 Android、iOS Webview 崩溃,升级 4.2.x 以后问题得到解决</li><li>不需要 ffavc 的场景则尽量不要使用 ffavc,额外 wasm 的文件引入也会占用内存</li><li>加了特殊 AE 特效的 pag 文件在不建议在使用,在 Android 下会明显卡顿(需要 ffavc 解码器的动画同理)</li><li>优化 pag 文件体积,过多的图层和位图的引入都会增加文件体积,同样会导致播放卡顿</li></ul><h4 id="动画导出"><a href="#动画导出" class="headerlink" title="动画导出"></a>动画导出</h4><ul><li>使用 BMP 的动画导出后变糊了?<ul><li>可以将 BMP 序列帧导出看成视频的每一帧导出,为了优化体积,PAG 会进行压缩,就像压缩图片那样。这就是为什么不用 BMP 的文件比较清晰,因为矢量图只需要记录路径,不需要对图片素材做处理</li></ul></li><li>设计师导出的 pag 文件不是 30 帧或者 60 帧?<ul><li>AE 插件导出默认为 24 帧,带 BMP 的最大 30 帧,不带 BMP 的最大 60帧</li><li>(设计师在 AE 中预览的效果不代表最终在用户手机上的效果,由于帧数限制可能没有那么丝滑)</li></ul></li></ul>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> JavaScript </tag>
<tag> NodeJS </tag>
<tag> 开发小结 </tag>
</tags>
</entry>
<entry>
<title>dweb-cli 开发小结</title>
<link href="/2023/12/20/dweb-cli%E5%BC%80%E5%8F%91%E5%B0%8F%E7%BB%93/"/>
<url>/2023/12/20/dweb-cli%E5%BC%80%E5%8F%91%E5%B0%8F%E7%BB%93/</url>
<content type="html"><![CDATA[<p>每次都是从老项目中 <code>Ctrl+C</code> & <code>Ctrl+V</code> 拷贝一份文件目录和编译脚本,然后删删改改就可以跑新项目了。大多项目的编译模块其实都大同小异。于是在 2022 年中的时候写了一个 cli,主要功能是【快速生成项目模板】。<br>今年按需维护和更新,从 v1 升级至 v2 ,出于灵活性考虑,将编译配置一并生成到项目中。</p><span id="more"></span><h3 id="前期准备"><a href="#前期准备" class="headerlink" title="前期准备"></a>前期准备</h3><ul><li>确定项目名称</li><li>确定主要功能</li><li>确定通用依赖版本</li><li>确定命令行基础交互功能</li><li>确定脚手架结构</li><li>确定模板项目结构</li></ul><h3 id="项目搭建"><a href="#项目搭建" class="headerlink" title="项目搭建"></a>项目搭建</h3><h4 id="项目名称"><a href="#项目名称" class="headerlink" title="项目名称"></a>项目名称</h4><p>项目名尽量做到好记、一目了然。脚手架名字定为 dweb-cli ,表示这是一个用于 douban web 项目开发的工具。</p><h4 id="主要功能"><a href="#主要功能" class="headerlink" title="主要功能"></a>主要功能</h4><ul><li>创建项目(dweb create)</li><li>编译项目(dweb run)<!-- - 集成编译脚本:让用户可以通过 dweb-cli 直接编译脚手架生成的项目、而无需再手动配置。 --></li></ul><h4 id="依赖版本"><a href="#依赖版本" class="headerlink" title="依赖版本"></a>依赖版本</h4><p><code>Webpack 5</code>、<code>React 17.0.2</code></p><h4 id="命令行交互"><a href="#命令行交互" class="headerlink" title="命令行交互"></a>命令行交互</h4><p><strong>1. 项目创建</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dweb create <projectName></span><br></pre></td></tr></table></figure><p>自定义选项:</p><ul><li>是否需要 react router</li><li>是否需要 reset.css</li><li>UI 适配模式:none | flex2rem | viewport</li><li>是否在创建完成后自动执行 <code>npm install</code></li></ul><p><strong>2. 项目编译</strong></p><figure class="highlight sh"><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">dweb run serve <span class="comment"># 本地启用 webpack-dev-server 开发</span></span><br><span class="line">dweb run dev <span class="comment"># 开发模式</span></span><br><span class="line">dweb run build <span class="comment"># 生产模式</span></span><br></pre></td></tr></table></figure><p>可选参数:</p><ul><li><code>-c, --config</code> 指定 webpack 配置路径</li><li><code>-d, --deploy-config</code> 指定编译完成后的回调函数(文件)路径</li><li><code>-e, --entry</code> 指定要编译的入口名,默认编译全部入口</li><li><code>--dev-server-options</code> 指定 webpack-dev-server 配置路径</li><li><code>--free-config</code> 完全使用的 <code>--config</code> 中指定的配置,不使用 cli 自带的默认配置</li></ul><p><strong>3. 其他命令</strong></p><figure class="highlight sh"><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">dweb clean <span class="comment"># 清理 /dist 文件夹</span></span><br><span class="line">dweb <span class="built_in">export</span> <configType> <span class="comment"># 导出 cli 中的某项配置到当前目录</span></span><br></pre></td></tr></table></figure><h4 id="脚手架目录结构"><a href="#脚手架目录结构" class="headerlink" title="脚手架目录结构"></a>脚手架目录结构</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">├── bin <span class="comment"># 命令执行文件</span></span><br><span class="line">├── build/utils <span class="comment"># 构建工具</span></span><br><span class="line">├── lib <span class="comment"># 命令相关的实现</span></span><br><span class="line">├── template <span class="comment"># 项目模板</span></span><br><span class="line">├── .github/workflows <span class="comment"># github ci 配置文件</span></span><br><span class="line">├── .husky <span class="comment"># 代码提交相关的钩子配置(eslint)</span></span><br></pre></td></tr></table></figure><h4 id="模板目录结构"><a href="#模板目录结构" class="headerlink" title="模板目录结构"></a>模板目录结构</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">├── src</span><br><span class="line">│ ├── common</span><br><span class="line">│ │ ├── assets</span><br><span class="line">│ │ ├── components</span><br><span class="line">│ │ │ └── App</span><br><span class="line">│ │ │ ├── index.tsx</span><br><span class="line">│ │ │ └── style.scss</span><br><span class="line">│ │ ├── const</span><br><span class="line">│ │ │ └── index.ts</span><br><span class="line">│ │ ├── types</span><br><span class="line">│ │ │ └── index.d.ts</span><br><span class="line">│ │ └── utils</span><br><span class="line">│ │ └── axios-instance.ts</span><br><span class="line">│ └── pages</span><br><span class="line">│ ├── home</span><br><span class="line">│ │ ├── index.tsx</span><br><span class="line">│ │ └── style.scss</span><br><span class="line">│ └── screenshot</span><br><span class="line">│ ├── index.tsx</span><br><span class="line">│ └── style.scss</span><br><span class="line">├── tsconfig.json</span><br><span class="line">├── webpack.config.js</span><br><span class="line">├── package.json</span><br><span class="line">├── README.md</span><br><span class="line">└── yarn.lock</span><br></pre></td></tr></table></figure><h3 id="v1-升级至-v2"><a href="#v1-升级至-v2" class="headerlink" title="v1 升级至 v2"></a>v1 升级至 v2</h3><p><strong>v1 将编译脚本集成在 cli 内部,编译工作交给 cli。</strong><br><strong>v2 将编译脚本生成到项目中,编译工作交还给项目,本身只保留生成功能。</strong></p><p>这个更改是因为团队成员在使用 v1 版本时更希望能灵活配置编译脚本。起初 dweb v1 尝试使用自定义配置文件的路径用来支持灵活配置,但大家对于 dweb 内已集成的 loader 和编译配置实际上并不知晓(很少会点进项目/文档查看),更希望能”<strong>直接</strong>“看到当前项目的“编译配置”。<br>所以 v2 版本的 dweb 不再集成编译工作,而是将基础的编译配置放到项目模板中,创建新项目时会自动在新项目中生成一套默认的编译脚本。</p><p>不过这样做的弊端就是原本升级依赖只需要升级 dweb 的版本,而现在依然需要逐个项目进行升级。</p><h4 id="其他更新"><a href="#其他更新" class="headerlink" title="其他更新"></a>其他更新</h4><ul><li>使用 <code>swc-loader</code> 替换 <code>babel-loader</code> 以减少编译耗时</li><li>支持第三方插件对 css module 单独编译</li><li>增加 Log 埋点、登录状态凭证等基础项目配置</li><li>增加 CI 配置支持 mr 自动发包、发送提醒</li></ul><h3 id="开发过程中用到的工具"><a href="#开发过程中用到的工具" class="headerlink" title="开发过程中用到的工具"></a>开发过程中用到的工具</h3><ul><li><a href="https://github.com/tj/commander.js">commander</a>:命令行参数接收工具</li><li><a href="https://github.com/chalk/chalk">chalk</a>:命令行美化工具,用于调整输出信息各字体颜色</li><li><a href="https://github.com/SBoudrias/Inquirer.js">inquirer</a>:命令行交互工具,用于获取用户交互结果 并用 promise 返回值</li><li><a href="https://github.com/sindresorhus/ora">ora</a>:命令行进度条美化工具</li><li><a href="https://github.com/handlebars-lang/handlebars.js">handlebars</a>:模板渲染工具</li></ul>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> JavaScript </tag>
<tag> NodeJS </tag>
<tag> 开发小结 </tag>
</tags>
</entry>
<entry>
<title>2023年终总结</title>
<link href="/2023/12/19/2023%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2023/12/19/2023%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>我的一半 2023。</p><span id="more"></span><p>一篇比往年提前了两个月的年度总结。</p><ul><li>一月<ul><li><strong>工作</strong> 为过年的活动忙到焦头烂额。</li></ul></li><li>二月<ul><li><strong>除夕</strong> 上午到家,晚上抱着电脑窝在沙发里,看着春晚与bug齐飞。</li><li><strong>假期</strong> 白天在网吧里与好友开黑玩的不亦乐乎、晚上回家与老爸对着零食山一边观影一边开炫。</li><li><strong>元宵节</strong> 这次在家里过完元宵再走。想着今年的节假日多回来里陪陪家人。</li><li><strong>返程</strong> 不知的糖果的魔力还是幸福和快乐的代价,长胖5kg(甚至抹了零头)。</li></ul></li><li>三月<ul><li><strong>工作</strong></li></ul></li><li>四月<ul><li><strong>工作</strong> </li><li><strong>汉服</strong> 第一次尝试,走在游乐场里差点被路人的直直的目光戳死。衣服很喜欢,但应该不会再穿了。</li></ul></li><li>五月<ul><li><strong>工作</strong> </li><li><strong>取消游玩计划</strong> 本想五一去沈阳找朋友耍两天,游玩笔记已经做好,就差一张车票。和朋友的时间。</li></ul></li><li>六月<ul><li><strong>工作</strong></li><li><strong>端午节</strong> 回家待三天,美食&家人&好友&游戏。</li></ul></li><li>七月<ul><li><strong>噩梦</strong> 世事难料。</li></ul></li><li>八、九<ul><li><strong>工作</strong> 忙碌</li></ul></li><li>十月<ul><li><strong>十一假期</strong><ul><li><strong>搬家</strong> 十一假期换了一个离公司只有一站地铁的小区</li><li><strong>游戏</strong> 往日之影沉浸式通关</li><li><strong>健身房</strong> 鬼使神差</li><li><strong>舞蹈室</strong> 上不上课另说,先把卡办了</li></ul></li><li><strong>工作</strong> 忙碌</li></ul></li><li>十一月、十二月<ul><li><strong>工作</strong> 忙碌</li></ul></li></ul><h4 id="黑白色的夏天"><a href="#黑白色的夏天" class="headerlink" title="黑白色的夏天"></a>黑白色的夏天</h4><p>记不清别的,只记得推开门看见父亲哭的不成样子的脸,和再一推开门跪倒在地上哭泣的母亲。</p><p>七月下了好多场雨。</p><h4 id="入秋"><a href="#入秋" class="headerlink" title="入秋"></a>入秋</h4><p>早上健身 + 白天工作 + 周末跳舞 = 每天晚上洗漱后几乎倒头就睡。</p><p>不想,也无暇顾及其他。</p><h4 id="冬日"><a href="#冬日" class="headerlink" title="冬日"></a>冬日</h4><p>健身课还在坚持上,没有目标,纯粹的精神支撑。</p><p>舞蹈老师教的很认真,大家对新手都很包容。</p><h4 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h4><p>这一年大大小小的运营活动页没少写,颇有种“流水的前端,铁打的运营”既视感。由怕写到烦,但是定制化的活动却只增不减。访问量、营收、社区活跃度只要为其中一项带来效果就行,可是…</p><p>今年组里有两位前端的先后离职:<br>一位是负责公司基础设施搭非常靠谱的同事但是因为身体原因不得不得离开,<br>另一位是负责老项目和登录业务的同事任职八年了但这次没有与公司续签。</p><p>主动离开or被动离开,此时探讨已经没有意义。</p><p>到明年七月就是我入职的三周年,最喜欢公司的自由度了没有之一。<br>三年。螺栓螺母螺丝钉,都拧过,也练出了傲人的肱二头肌。</p><h4 id="全年总结"><a href="#全年总结" class="headerlink" title="全年总结"></a>全年总结</h4><p>先活着吧,其他的我再想办法 :-)</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>2022年终总结</title>
<link href="/2023/01/13/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2023/01/13/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>工作是否也有五年之痒,2022 年度吐槽汇总。</p><span id="more"></span><p>一篇比往年迟了半个月的年度总结。拖延症的魔抓终于也伸向了年终总结…</p><ul><li>一月<ul><li><strong>工作</strong> :年假见底,在公司苟到了年前最后一刻。除夕前一天 Leader 给谈了谈入职半年的工作表现;</li></ul></li><li>二月<ul><li><strong>回家过年(一)</strong> :过年了,老妈为了准备了一桌子的零食。晚上我打开电视看花滑表演,她在我旁边嗑瓜子。或许好久没这么安逸过了,感动哭了。</li><li><strong>回家过年(二)</strong> :接下来的三四天里,天天以泪洗面,别问,问就是感动的。一年过去了,别的方面没见长,泪腺到是发达了。</li><li><strong>回家过年(二)</strong> :过年几乎每天都会出去串个门或者跟朋友吃饭。还去了家乡的海棠山爬到了山顶。在商场的娃娃机里抓出了好几只娃娃,小时候在娃娃机上的怨念,长大了算是找补回来了。</li></ul></li><li>三月: 工作</li><li>四月、五月<ul><li><strong>居家工作</strong> :公司园区附近出现了阳性,全员居家办公。居家这段时间正是需求比较多的时期,电脑放床上,睡醒了敲,敲完了睡。此时总是会想念公司宽阔的办公桌,清晰的扩展屏,自动饮水机和温度适宜的空调,以及到点联系不到人的状态。</li></ul></li><li>六月、七月:工作</li><li>八月<ul><li><strong>云南九日游(一)</strong> :单方面宣布大理就是海拉鲁本鲁,不接受任何反驳。云南的天和云给我感觉就是,动漫诚不欺我。苍山上的云从山峰一直延绵到脚下,伴随着下午三四点明晃晃的阳光,明亮美丽,仿佛置身天堂,我当时都怀疑是不是上帝要来接我了,同行的朋友瞟了我一眼,示意我别废话了赶紧把氧气罐打开。没想到我一米六五的东北大汉居然折在高原反应上,但是同样东北出身的她却啥事没有,这是旅行中我觉得最离谱的事。</li><li><strong>云南见朋友(二)</strong> :云南之行其实是一场说走就走的旅行。我们去了丽江、大理、昆明,最后一天去见了见大学同学,我的同学到还是老样子,以前爱笑,现在也是。继 2020 年之后的又一次彻夜长谈,听起来她的 2021 也并不是一帆风顺。</li><li><strong>工作(噩梦模式)</strong> :在忙碌期抽空出去玩回来的下场就是放假回来忙成狗。跟 PM 提出了延期的想法,被驳回。着急上火加高强度开发,直接给我人中干出了三四个大痘,那段时间照镜子我感觉我就是山本小次郎。</li></ul></li><li>九月<ul><li><strong>工作(噩梦模式)</strong> :头疼欲裂,布洛芬止痛片,每个打工人都值得拥有。</li><li><strong>环球影城一日游</strong> :水上表演和剧场表演都非常精彩,想着什么时候能带父母也来看看。</li></ul></li><li>十月、十一月、十二月<ul><li><strong>BEC 考试取消</strong> :想考个中级,大概备考了小 3 个月,北京突发疫情,朝阳区考点几乎全军覆没,考试被取消。</li><li><strong>工作(地狱模式)</strong> :走在去公司的路上,经常会怀疑自己开启了地狱副本。OKR 加上应接不暇的业务需求,有点让我喘不过气。好消息是按期干完了,坏消息新需求在接下来的时间里依旧骑脸输出。</li><li><strong>游戏笔记本</strong> :游戏下好了才发现老电脑带不起来,等反应回来时发现新的笔记本已经下单了。之前本想自己搭个台式,但是租的房子实在没地方放,搬家也会是问题,故作罢。这是第二次为了游戏买设备了,买完十分后悔,后悔为啥不早点买。感谢暴雪退出中国市场,让我拓展视野去玩其他好游戏。</li><li><strong>阳了</strong> :没想到居家一个月,大门不出二门不迈也能阳。没发烧,嗓子冒烟几天之后好了。</li></ul></li></ul><h3 id="关于职业"><a href="#关于职业" class="headerlink" title="关于职业"></a>关于职业</h3><p>算上今年,作为一名前端开发,已经工作了五年了。那些曾经在网页上吸引我的、跳来跳去的动画,现在也只是静静看着,心中再无波澜,只是想着,这花里胡哨的交互可不能让我们公司设计师给看到(bushi。</p><p>有的时候很羡慕客户端的开发同学,原生语言写的页面流畅又好用,也不用受到浏览器的层层限制,可以直接调用系统级 api,他们想法和声音也能受到大家的重视,开发出的产品也可以留存。再对比于随写随弃的前端活动页,真的羡慕的不是一点半点…</p><p>如果职业选择能读档重来,我还会选择做前端吗。当年对这份职业的热忱,也被这几年这些细碎的事情消磨的一干二净。</p><p>呃啊,被嫌弃的切图仔的一生…</p><h3 id="全年总结"><a href="#全年总结" class="headerlink" title="全年总结"></a>全年总结</h3><p>虽然是忙到脚打后脑勺的一年,但是内心变得更佛系了,很多事变得无所谓了…</p><p>看了下去年给今年TODO,是早睡早起 + 治好拖延症,嗯…今年还是未完成,留给明年继续吧…</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>【译】Why Is WKWebView So Heavy and Why Is Leaking It So Bad?</title>
<link href="/2022/06/09/%E3%80%90%E8%AF%91%E3%80%91Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/"/>
<url>/2022/06/09/%E3%80%90%E8%AF%91%E3%80%91Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/</url>
<content type="html"><![CDATA[<p>从 iOS8 开始,就引入了新的浏览器控件 WKWebView,用于取代 UIWebView。在新版本系统中使用 UIWebView 会发出警告 ⚠️ 提醒更换控件。坊间传闻 WKWebView 存在内存占用过大的问题…</p><p><strong>声明:这是一篇翻译水文,有用的内容不多,之前是因为好奇翻译了一半,翻译完发现并没有什么有用的知识点…</strong></p><span id="more"></span><p>在 Embrace 公司,我们帮助移动应用公司解决他们最困难的生产问题。其中常见的 bug 是 iOS 上对 WKWebView 的不当管理产生的。而问题是 Webview 对象在资源中占用较重。大量被占用的内存未被正确的释放则会导致系统卡顿、死机甚至崩溃。</p><p>本文中,我们将会介绍以下内容:</p><ul><li>为什么 WKWebView 会这么重</li><li>常见的 WKWebView 导致的内存泄露方式</li><li>使用 WKWebView 时怎样发现内存泄露</li><li>使用 WKWebView 的最佳实践</li></ul><h3 id="为什么-WKWebView-会这么重"><a href="#为什么-WKWebView-会这么重" class="headerlink" title="为什么 WKWebView 会这么重"></a>为什么 WKWebView 会这么重</h3><p>在开始之前,我们在先前的文章中已经介绍了<a href="https://blog.embrace.io/bug-of-the-month-blank-webviews/">content process 终止导致 WebView 阻塞显示空白</a>以及<a href="https://blog.embrace.io/bug-of-the-month-blank-web-views-caused-by-downgrading/">降级导致的 WebView 空包</a>。如果你依然因为空白的 WebView 而苦恼,看看这些文章或许会有帮助。</p><p>文本将主要探讨在加载过程中 WebView 被阻塞以及在你的 App 中存在了过多的 WebView 的问题。WebView 是可控的最重的对象之一。基本上,你可以用你的 App 来启动另一个应用并添加两个附属进程 —— content process 和 networking process。</p><p>所以如果你的应用中有 <strong>一个 WebView</strong> ,则意味着你的应用实际运行在 <strong>三个系统进程</strong> 上:应用进程、Web Content Process 和 Web Networking Process。</p><p>有 <strong>两个 WebView</strong> 则意味着有 <strong>五个进程</strong> 。<br>有 <strong>三个 WenView</strong> 则意味着有 <strong>七个进程</strong> 。</p><p>当示例个数成倍增加时,并没有形成一个规模经济效应(即进程越多越高效)。事实上,正相反。创建的 WebView 越多,你的 App 运行就越慢。</p><h3 id="常见的-WKWebView-导致的内存泄露方式"><a href="#常见的-WKWebView-导致的内存泄露方式" class="headerlink" title="常见的 WKWebView 导致的内存泄露方式"></a>常见的 WKWebView 导致的内存泄露方式</h3><p>WKWebView 致使内存泄露最常见的原因就是 <strong>新建</strong> ,而不是复用已经创建好的实例。一些时候,工程师们以为他们已经复用了 Webview 了,但是他们并没有检查在 Xcode 已经构建的 WKWebView 实例。因为 WKWebView 是存放在 Apple 系统目录中,工程师在调试性能问题时很容易把这部分忽略掉。</p><p>例如,你有一个轮播组件(Carousel),每当用户滑动时就会加载一个 WebView,内容如以下几种:</p><ul><li>加载一篇 新闻/杂志 网站的文章</li><li>加载一个 电商 网站的产品列表页</li><li>加载一段 流媒体 如视频</li></ul><p>对于轮播组件来说,在内存中的 WebView 数量最好永远不要超过两个。一个为用户展示当前内容,另一个用作下一个内容的承接。一旦用户滑动切换到下一个 WebView,应该清空第一个 WebView 并且使之为下一次切换做准备。这样无论用户切换多少次,你的 App 中始终就只有两个 WebView。</p><p>对于 ScrollView,在同一时间内可能会有多个可见的 WebView 存在。这种情况下,其最大数量取决于填满屏幕大小需要的 WebView 个数外加一个用于预加载 WebView。</p><p>另一中泄露方式是已崩溃的 WebView 一直被保留而没有得到释放。 无论用户在何时遇见白屏页面,你都应该有一个状态码来确定当前页面是应该重新加载还是应该被移除。例如,如果是付款页面出了问题,你会想去重新加载;如果是广告页面出了问题,当你不能够修复时你会选择删除它。</p><h3 id="使用-WKWebView-时怎样发现内存泄露"><a href="#使用-WKWebView-时怎样发现内存泄露" class="headerlink" title="使用 WKWebView 时怎样发现内存泄露"></a>使用 WKWebView 时怎样发现内存泄露</h3><p>通过 Xcode 内存图表来查看内存泄露。 用 Xcode 进行 debug 时,查看 WebView 模块,可以在展开左边侧栏中看到当前内存中的 WebView 数量。此时滑过刚刚我们创建的组件,就可以看到到内存使用情况。</p><img src="/2022/06/09/%E3%80%90%E8%AF%91%E3%80%91Why-Is-WKWebView-So-Heavy-and-Why-Is-Leaking-It-So-Bad/Xcode-debug.png" class="" title="Xcode-debug"><h3 id="使用-WKWebView-的最佳实践"><a href="#使用-WKWebView-的最佳实践" class="headerlink" title="使用 WKWebView 的最佳实践"></a>使用 WKWebView 的最佳实践</h3><p>首先最好的实践就是限制应用应用内 WKWebView 的数量。在 iOS 应用中,最繁重的操作之一就是创建新的 WKWebView。它们占用大量的内存并添加额外的进程。无论何时尝试将 WebView 用系统本身的某功能来代替,都是有意义的。</p><p>第二个实践是复用已有的 WKWebView 而不是新建。清除现有的旧内容并将新内容加载到现有的 WKWebView 实例中,这比直接删除和创建的性能要好的多。</p><p>第三个实践是写一些适当的测试用例来标记溢出的 WebView,代码可以严格一点。如果你仅在轮播组件中使用了 WebView,那么你很明确的知道同一时刻的内存中最多应该包含两个 WebView。当超过两个 WebView 存在时,测试用例将报错。</p><p>同理,如果你有一个产品列表在 ScrollView 中,那么你就可以通过计算填满屏幕所需的 WebView 数量来计算最大值。测试用例也是同样的方法。利用 Xcode 的内存图和适当的测试用例来发现 WebView 的泄露是很重要的,这样就可以使你的应用程序性能更佳。</p><h3 id="总结一下"><a href="#总结一下" class="headerlink" title="总结一下"></a>总结一下</h3><p>iOS 应用卡顿和反应慢的问题之一是创建了太多 WKWebView 实例,对已存在的 webview 没复用也没销毁(这不卡才怪…)。</p><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="https://blog.embrace.io/wkwebview-memory-leaks/">https://blog.embrace.io/wkwebview-memory-leaks/</a></p><h3 id="翻译总结"><a href="#翻译总结" class="headerlink" title="翻译总结"></a>翻译总结</h3><ul><li>文章部分内容写的过于重复,并不是很干货,让我想起了国内的营销号;</li><li>强烈怀疑原文是隔壁机翻成英文的,或者作者母语并非英语;</li><li>机翻比自己脑子翻好用…</li></ul>]]></content>
<categories>
<category> App </category>
</categories>
<tags>
<tag> App </tag>
<tag> Webview </tag>
<tag> 翻译 </tag>
</tags>
</entry>
<entry>
<title>网工相关</title>
<link href="/2022/03/11/%E7%BD%91%E5%B7%A5%E7%9B%B8%E5%85%B3/"/>
<url>/2022/03/11/%E7%BD%91%E5%B7%A5%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<p>之前网工学习部分知识点总结,主要是整理汇总下分散在不同地方的笔记。<br>工作中用到的不多,仅当笔记备份。</p><span id="more"></span><h2 id="加密算法"><a href="#加密算法" class="headerlink" title="加密算法"></a>加密算法</h2><h3 id="可逆算法"><a href="#可逆算法" class="headerlink" title="可逆算法"></a>可逆算法</h3><p>字面意思,经过算法处理后的信息可通过某种逆向计算得到原信息的算法称为可逆算法。<br>PS:常见的 base64 并不是一种加密算法。它的编码过程完全公开,逆向解码即可得到原来的信息。经过 base64 编码后的字符串也都是常规字符,这样在信息传递的过程中,UNICODE 字符串就不会发生不能识别或者丢失的现象了。故 base64 只是一种编码方式,并不能归类为加密算法当中。</p><h4 id="对称加密"><a href="#对称加密" class="headerlink" title="对称加密"></a>对称加密</h4><p>对称加密指的是加密、解密用的是同一个密钥。特点是 加密速度快,但安全性不高。<br><strong>常见的对称加密算法有:DES、3DES、AES。</strong></p><h4 id="非对称加密"><a href="#非对称加密" class="headerlink" title="非对称加密"></a>非对称加密</h4><p>非对称加密指的是加密、解密用的是不同的密钥,它们是成对出现的、称为公钥和私钥,公钥加密的内容,用私钥才可以解密。特点是 安全性高,加密速度慢。<br><strong>常见的非对称加密算法有:RSA。</strong></p><h3 id="不可逆算法"><a href="#不可逆算法" class="headerlink" title="不可逆算法"></a>不可逆算法</h3><p>不可逆算法的特征是加密过程中不需要使用密钥,输入明文后通过算法生成密文,密文信息无法被解密。(常见的场景是:用户账密校验, 常见的算法是:哈希算法-信息摘要算法)<br><strong>常见算法:HASH 算法。</strong></p><ul><li>MD5 算法:通过不可逆的字符串变换法,产生一个唯一的 MD5 信息摘要。<br>(每个文件都有一个数字指纹)</li><li>SHA 算法:信息摘要算法,主要用于验证数据的完整性。在传输过程中,若数据发生变化,那么就会产生不同的摘要。</li></ul><p>更多算法细节介绍参考:<a href="https://juejin.cn/post/7035816723978649607">常见加密算法</a></p><h2 id="HTTPS-中用的是什么算法"><a href="#HTTPS-中用的是什么算法" class="headerlink" title="HTTPS 中用的是什么算法"></a>HTTPS 中用的是什么算法</h2><p>// TODO 我记得这里分成了两步 既要保证安全 又要保证速度 查一下书吧</p><h3 id="HTTPS-证书过期,内容是否还是加密的"><a href="#HTTPS-证书过期,内容是否还是加密的" class="headerlink" title="HTTPS 证书过期,内容是否还是加密的"></a>HTTPS 证书过期,内容是否还是加密的</h3><p>不论证书是否有效,只要用户通过浏览器确认要发起协商会话,那么就依然会从证书里拿到密钥,走一个加密通话的过程。<br>(证书的作用主要是认证,去辨明浏览器的真伪)<br>参考文章:<a href="https://juejin.cn/post/7035816723978649607">如果没有有效的证书,HTTPS连接是否加密</a></p><h2 id="域名解析-A记录、CNAME、NX"><a href="#域名解析-A记录、CNAME、NX" class="headerlink" title="域名解析 A记录、CNAME、NX"></a>域名解析 A记录、CNAME、NX</h2><ul><li><strong>A记录</strong> 就是把一个域名解析到一个IP地址(Address,特制数字IP地址),创建不带 www 的域名记录需要在域名前加一个 ‘@’;</li><li><strong>CNAME记录</strong> 可以看做是称域名的别名。它可以将多个域名指向同一个ip(实际上都指向了一个域名);</li><li><strong>MX记录 (Mail eXchanger)</strong> 设置域名邮箱用的:直接添加记录值为对应的域名邮箱,网易、腾讯、等等;</li><li><strong>AAAA 记录</strong> 是一个指向 IPv6 地址的记录;</li><li><strong>NS 记录 (Name Server)</strong> 用于记录域名服务器,用于指定域名由哪台服务器来解析。可以使用 nslook -qt=ns [域名] 来查询当前 ip;</li></ul><p>参考文章:</p><ul><li><a href="https://www.jianshu.com/p/4ddd138233cc">A记录、CNAME、NS记录 设置技巧</a></li><li><a href="https://www.cnblogs.com/hanggegege/p/5861212.html">相关概念</a></li></ul>]]></content>
<categories>
<category> 计算机相关知识 </category>
</categories>
<tags>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>2021年终总结</title>
<link href="/2021/12/03/2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2021/12/03/2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>2021年终总结(✖️)<br>2021大师难度通关记(✔️)</p><span id="more"></span><ul><li>1月 <ul><li><strong>筹备团建</strong> :疫情管控比较严,筹备工作进行的并不顺利;</li><li><strong>年终绩效(年终奖)</strong> :坚持工作不跑路的精神支柱,做好了绩效为D的准备,领导比较照顾给了C,拿了一笔钱准备回家过年;</li></ul></li><li>2月 <ul><li><strong>各部门聚餐</strong> :春节前的两周平均每天都有聚餐,在中午,或是在晚上;<ul><li>团队聚餐常常在一家湘菜馆,印象最深的一道菜是红辣椒炒绿辣椒;</li><li>趁着聚餐活动去玩了一把卡丁车,体验在轰鸣声中狂飙的感觉,只是车技捉急;</li></ul></li><li><strong>回家过年</strong> :应国家号召”就地过年“,有顾虑的人留下,没有顾虑的人启程返乡;</li><li><strong>整理照片</strong> :筛选了一些从毕业到工作后的照片整理成相册。只是照片数量较多,即使到返程也没能整理完;</li></ul></li><li>3月<ul><li><strong>加班</strong></li></ul></li><li>4月<ul><li><strong>加班/看工作</strong></li></ul></li><li>5月<ul><li><strong>加班/看工作/面试</strong></li></ul></li><li>6月<ul><li><strong>加班/看工作/面试</strong></li><li><strong>好友毕业/奇葩解压馆/欢乐谷</strong></li><li><strong>离职申请</strong></li></ul></li><li>7月<ul><li><strong>最后一次团建</strong> : 团建是在一个草原上,视野开阔,景色优美;夜晚下起了雨,同事们在屋内远程联调排查 bug;</li><li><strong>离职</strong> :HR 给了满满一页的表格需要对应的负责人签字,下午找遍了楼层的每一处,没想到居然是在这一天才将各个大佬和部门的位置熟悉了个遍。同事将我送出园区的时候,天还亮着。迎着夕阳下班,感觉真实又不真实。</li><li><strong>搬家</strong> :又从海淀/昌平区搬回朝阳区了;</li><li><strong>入职</strong> :那两天恰逢北京暴雨,于是入职时间往后顺移了两天,提前感受到了公司的人文主义关怀。</li><li><strong>加班</strong></li></ul></li><li>8月<ul><li><strong>加班</strong></li></ul></li><li>9月<ul><li><strong>工作</strong></li></ul></li><li>10月<ul><li><strong>国庆假期</strong> :通关了积灰已久的塞尔达,终于以一副兽皮人面的样子来到了公主身前。</li><li><strong>工作</strong></li></ul></li><li>11月<ul><li><strong>工作</strong></li><li><strong>海口/火星演唱会</strong> :演唱会跟想象中的体验出入还蛮大的,如果可以重来一次,能不能把票钱还我。</li></ul></li><li>12月<ul><li><strong>工作</strong></li></ul></li></ul><h3 id="一些无关紧要的记录"><a href="#一些无关紧要的记录" class="headerlink" title="一些无关紧要的记录"></a>一些无关紧要的记录</h3><ul><li>鼠标:换成了罗技的 Anywhere3,很香很丝滑;</li><li>模玩:上半年入了从大学就开始惦记的 MARK 3,下半年为了情怀入了 MARK 85;<br>(嗯,明年打算喝西北风 (  ̄ー ̄) );</li><li>记录工具:印象笔记内容变多之后,不管是网页端还是客户端,用起来都很笨重,而且一言不合就进行设备限制弹广告,bug 也变多了,逐渐让人难以忍受,遂寻找新的笔记工具。期间试过 马克飞象、Notion、网易云笔记、语雀。最后选择了语雀,不管是目录分类还是工具布局,都非常合理,用起来得心应手。<br>现在已经成为 dida、语雀 的重度使用用户了。</li></ul><h3 id="为什么辞职"><a href="#为什么辞职" class="headerlink" title="为什么辞职"></a>为什么辞职</h3><p>给了自己一年的时间去适应公司,约定好若一年之后还是如刚入职般的状态,便离职。<br>这一年过的并不轻松,从未知道原来安心敲代码,没有其他事打扰是这么奢侈的事。或许当时匆匆做选择,没有深思熟虑就入职等同于埋坑。</p><p>最焦虑的时候,恰逢朋友毕业,来北京陪我两天。<br>这两天我们去了减压馆,去了欢乐谷。<br>但是人在乐园里,心在工作上,这份焦灼似乎并没有被化解。</p><p>好友返程的那天上午,早会时分,我站在人群后面,听着别人的需求进度,思绪早已飘到了九霄云外。<br>回到工位后,打开电脑,开始码离职申请,</p><!-- 这一天晚上,在下班回家的路上,我终于不用再在心里起草新一版的离职申请了。 --><p>这一天晚上,下班回家走在路上,我感觉到,心底的那块石头终于被搬走了。</p><h3 id="关于前公司"><a href="#关于前公司" class="headerlink" title="关于前公司"></a>关于前公司</h3><p>周围的同事们都很优秀,大家之间的氛围很棒,目前遇到的公司中无出其右。<br>这里的人追求技术,敢于创新。<br>只是,来来往往很多人,还没来得及再次打招呼,工位就空了。<br>待了一年多,大大小小的会议,各种各样的事情,朝令夕改的要求,也将我对大厂的幻想磨的粉碎。<br>公司说不上哪里好,也说不上哪里特别不好。<br>只是在我漫漫找工作的路途中,为我上了重要一课,</p><h3 id="关于现公司"><a href="#关于现公司" class="headerlink" title="关于现公司"></a>关于现公司</h3><p>哪怕已经入职新公司小半年了,可是这对我来说依然还是如梦如幻般的经历。<br>工作的自由度很高,曾经有的束缚现在都没有了。<br>同时也有些难过,失去了一些共同工作的伙伴,曾经是一群人,现在是一个人。</p><p>但做人也不能太贪心,世上本就没有两全之事。</p><h3 id="一场义无反顾的海口之旅"><a href="#一场义无反顾的海口之旅" class="headerlink" title="一场义无反顾的海口之旅"></a>一场义无反顾的海口之旅</h3><p>忙忙碌碌的下半年,一直想去的演唱会终于在年底有了消息,<br>觉得无论如何也要去看一看,害怕一错过就又等不来了。</p><p>虽然疫情防控各种措施,跨省看演唱会困难重重,好在抵达海口后一切顺利。<br>海风吹拂的那一瞬间,让我觉得这一切都是值得的。</p><p>因为一场演唱会,结识了一群小伙伴。<br>我们在落日海边散步,在海大夜市吃夜宵,去逛免税店,去尝椰子鸡。<br>三天短暂而又充实的海口之旅,玩的很开心。<br>这次受时间限制,没来得及去三亚玩一圈,如果有机会,想去亚特兰蒂斯水世界玩一玩。</p><h3 id="全年总结"><a href="#全年总结" class="headerlink" title="全年总结"></a>全年总结</h3><p>受尽心理折磨终于迎来转折的一年,生活节奏和心态也在慢慢调整过来。<br>努力工作是为了更好的生活,要保持本心。<br>只可惜,方方面面而言,今年也是没啥进步的一年 ╮(╯▽╰)╭。<br>希望新一年的自己能战胜懒惰,把拖延的毛病改掉,还有就是早点起床早点上班,不要天天睡到11点才起床收拾去公司…</p><p>PS:不确定字迹是否跟心态有关系,上半年“草书”,下半年“行楷”。</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>《写给大家看的设计书》- 小结</title>
<link href="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/"/>
<url>/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/</url>
<content type="html"><![CDATA[<p>很好的排版设计入门书,19年年底的时候在地铁里用了不到两周的时间看完了,一边看书,一边拿出平日里设计师给的设计稿对比着看,总会有一种“我不明白,但我大受震撼”的感觉。 书很快读完了,读书小结却从19年拖到了21年,再过三个月就22年了,现在拿出来翻翻,如获新书( ̄ε(# ̄)。</p><span id="more"></span><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>好的排版会让信息的可读性大大增加,一眼抓住重点。这本书主要介绍排版中最基础的四大设计原则 <strong>“亲密性、对齐、重复、对比”,</strong> 读完之后会发现在设计稿和 PPT 的应用中有一些规律可循。书中有大量示例,小结中只摘取最具代表性的几个,以方便查看和参考。</p><h3 id="亲密性(Proximity)"><a href="#亲密性(Proximity)" class="headerlink" title="亲密性(Proximity)"></a>亲密性(Proximity)</h3><h4 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h4><p>物理位置的接近意味着存在关联。<strong>彼此相关的项应当组织在一起,形成一个视觉单元,</strong> 而不是多个孤立的元素。<br>要有意识地注意你是怎样阅读的,你的视线怎样移动:从哪里开始;沿着怎样的路径;到哪里结束;读完之后,接下来看哪里?整个过程当是一个合理的过程,有确定的开始,而且要有确定的结束。</p><h4 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h4><ol><li><p>调整前(左) / 调整后(右)</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E4%BA%B2%E5%AF%86%E6%80%A7-1.png" class=""></li><li><p>从左到右 逐次调整</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E4%BA%B2%E5%AF%86%E6%80%A7-2.png" class=""></li></ol><h4 id="要避免的问题"><a href="#要避免的问题" class="headerlink" title="要避免的问题"></a>要避免的问题</h4><ul><li>避免一个页面上有太多孤立的元素;</li><li>不要在元素之间留出同样大小的空白,除非各组同属于一个子集;</li><li>不属于一组的元素之间不要建立关系:如果元素彼此无关,就把它们分开;</li><li>不要仅仅因为有空白就把元素放在角落或中央;</li></ul><h3 id="对齐(Alignment)"><a href="#对齐(Alignment)" class="headerlink" title="对齐(Alignment)"></a>对齐(Alignment)</h3><p>思想:任何东西都不能在页面上随意安放。每个元素都应当与页面上另一个元素有某种视觉联系。这样能建立一种清晰、精巧的外观。<br><strong>如果页面上某些元素是对齐的,即使对齐的元素物理位置是彼此分离的,但在你眼里(以及你的心里),它们之间也会有一条看不见的线把彼此连接在一起。</strong><br>对齐的根本目的是 <strong>使页面统一而且有条理。</strong></p><h4 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h4><ol><li><p>调整前视线在不同位置停留5次 调整后结构变的清晰有序</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E5%AF%B9%E9%BD%90-1.png" class=""></li><li><p>文章主题与标题采用相同的对齐方式 才不会显得杂乱</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E5%AF%B9%E9%BD%90-2.png" class=""></li><li><p>对齐线”边界的强度为布局提供了力度</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E5%AF%B9%E9%BD%90-3.png" class=""></li></ol><h4 id="要避免的问题-1"><a href="#要避免的问题-1" class="headerlink" title="要避免的问题"></a>要避免的问题</h4><ul><li>避免在页面上混合使用多种文本对齐方式;<br>(也就是说,不要将某些文本居中,而另外一些文本右对齐)</li><li>尽量不要将居中对齐作为默认选择,除非你有意识地想要想要创建一种比较正式的表示;</li></ul><h3 id="重复(Repetition)"><a href="#重复(Repetition)" class="headerlink" title="重复(Repetition)"></a>重复(Repetition)</h3><p>思想:设计中视觉元素的重复可以将作品中的各部分连在一起,从而 <strong>统一</strong> 并增强整个作品。<br>重复的元素可以是一种粗字体、一条粗线、某种颜色、空间关系等。<br>不过 <strong>重复不只是自然的一致,而是统一设计各个部分的有意识的行为。</strong><br><strong>如果一个作品看起来很有趣,它往往也更易阅读。</strong><br>(列表项就是一个典型应用重复的例子)</p><h4 id="示例-2"><a href="#示例-2" class="headerlink" title="示例"></a>示例</h4><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E9%87%8D%E5%A4%8D-1.png" class="" width="450"><h4 id="要避免的问题-2"><a href="#要避免的问题-2" class="headerlink" title="要避免的问题"></a>要避免的问题</h4><ul><li>避免过多地重复一个元素,重复太多会让人讨厌。要注意对比的价值;</li></ul><h3 id="对比(Contrast)"><a href="#对比(Contrast)" class="headerlink" title="对比(Contrast)"></a>对比(Contrast)</h3><p>思想:对比是为页面增加视觉效果最有效的途径,也是在不同元素之间建立一种有组织的层次结构最有效的方法。<br>可以通过字体、线宽、颜色、形状、大小、空间等来增加对比。但要记住一个原则:要想实现有效的对比,对比就必须强烈,<strong>如果元素不同,那就让它们截然不同。</strong></p><h4 id="示例-3"><a href="#示例-3" class="headerlink" title="示例"></a>示例</h4><ol><li><p>背景色与文字颜色的强烈对比 更易阅读</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E5%AF%B9%E6%AF%94-1.png" class=""></li><li><p>一条无形的对角线以及放大的图片也增加了趣味性</p><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E5%AF%B9%E6%AF%94-2.png" class=""></li></ol><h4 id="要避免的问题-3"><a href="#要避免的问题-3" class="headerlink" title="要避免的问题"></a>要避免的问题</h4><ul><li>不要将一种粗线与一种更粗的线进行对比,不要将棕色文本与黑色标题建立对比。要避免使用两种或多种类似的字体;</li><li>如果各个项不完全一样,那就让它们截然不同;</li></ul><h3 id="颜色运用"><a href="#颜色运用" class="headerlink" title="颜色运用"></a>颜色运用</h3><h4 id="色轮基础"><a href="#色轮基础" class="headerlink" title="色轮基础"></a>色轮基础</h4><ul><li>色轮的基础是 <strong>红、黄、蓝</strong> 3种颜色,它们被称之为 <strong>三原色,</strong> 因为它们无法被创建。</li><li><strong>将相邻的三原色等量的混合</strong> ,就会得到 <strong>三间色(secondary color)</strong> 。</li><li>其余位置将相邻的两个颜色等量混合,就会得到 <strong>第三色</strong> 。</li></ul><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E8%89%B2%E8%BD%AE%E5%9F%BA%E7%A1%80.png" class=""><h4 id="颜色关系"><a href="#颜色关系" class="headerlink" title="颜色关系"></a>颜色关系</h4><ul><li>互补色:<strong>色轮上相对的颜色为互补色</strong> 。利用它们的对立关系,常见搭配是一种作为主色,而另一种用于强调。</li><li>三色组:<strong>彼此等距的三种颜色叫做三色组</strong> 。三色组中的颜色都有基础色使其相互连接,因此看上去十分协调。</li><li>分裂互补三色组:从色轮的一边选择一种颜色,再找出它的互补色,但并不直接使用这个互补色,而是使用 <strong>该互补色两侧的颜色</strong> 。这样的组合会有一种更为细致的边界。</li><li>类似色:<strong>类似色由色轮上彼此相邻的颜色组成</strong> ,它们都有相同的基础色。用不同的亮色和暗色组合一组类似色,会获得醒目的效果。</li><li>单色组:<strong>单色组合由一种色调及其相应的多种亮色和暗色组成</strong> ,如黑白照片。</li><li>暗色和亮色的组合:<strong>不使用色调,而是使用这些颜的不同亮色和暗色</strong> 。这样既丰富了选择,也可以放心颜色的协调性。</li></ul><img src="/2021/09/14/%E3%80%8A%E5%86%99%E7%BB%99%E5%A4%A7%E5%AE%B6%E7%9C%8B%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B9%A6%E3%80%8B-%E5%B0%8F%E7%BB%93/%E9%A2%9C%E8%89%B2%E5%85%B3%E7%B3%BB.png" class=""><h4 id="颜色变化"><a href="#颜色变化" class="headerlink" title="颜色变化"></a>颜色变化</h4><ul><li>亮色和暗色<ul><li>纯色就是 <strong>色调</strong> 。</li><li>向色调增加黑色就构成一个 <strong>暗色</strong> 。</li><li>向色调增加白色就构成一个 <strong>亮色</strong> 。</li></ul></li><li>色质:指某种颜色的明暗度、深浅度或色调。</li><li>暖色与冷色<ul><li>暖色:其中包含红色或者黄色。暖色是趋进型的,趋于做视觉提醒;</li><li>冷色:其中包含蓝色。冷色属于后退型的,更趋于做背景色;</li></ul></li></ul><h4 id="颜色模型"><a href="#颜色模型" class="headerlink" title="颜色模型"></a>颜色模型</h4><ul><li>CMYK:由四种墨色组成,可以打印成千上万种颜色。常用于纸张打印、书籍印刷等;</li><li>RGB:由 Red、Green、Blue 三种颜色组成。常用于显示器、电视、手机屏幕等;</li></ul><h4 id="要注意的问题"><a href="#要注意的问题" class="headerlink" title="要注意的问题"></a>要注意的问题</h4><ul><li>避免组合中的色质过于接近。如果色质很接近,对比太过微弱,看上去就会模糊不清;</li><li>不要让冷暖色过于均衡,要充分利用冷暖色的视觉特质;</li><li>CMYK 和 RGB 之间的转换会有数据损失,所以最好用 RGB 处理图像,最后再把它们转换为 CMYK 格式;</li></ul><h3 id="文字与字体"><a href="#文字与字体" class="headerlink" title="文字与字体"></a>文字与字体</h3><ul><li>衬线体:衬线又被称为“字脚”,衬线体(Serif)就是有边角装饰的字体,如宋体;</li><li>无衬线体:无衬线体(Sans-serif)则与衬线体相反,通常是机械和统一粗细的线条,没有边角的装饰,易读性更好,如黑体;</li></ul><h3 id="标点符号"><a href="#标点符号" class="headerlink" title="标点符号"></a>标点符号</h3><ul><li>跟随在有样式文字后的标点:如果一个单词的样式是粗体,那么跟随在文字之后的符号也应该是粗体。</li><li>括号中的标点:<ul><li>如果若括号中的文字是整个句子的一部分,那么标点就应该在括号之外<br>(就像这个例子一样)。</li><li>如果括号内的文字是一个完整的句子,那么标点应该出现在括号内。<br>(这就是一个标点出现在括号内的例子。)</li></ul></li><li>(英文)标点后面一个空格,破折号两侧无空格。</li><li>方框中的文字:如果你确实要把文字放进方框里,那就要在四周留出足够的空间。</li></ul><h3 id="更多提示与技巧"><a href="#更多提示与技巧" class="headerlink" title="更多提示与技巧"></a>更多提示与技巧</h3><ul><li>创建中心点:页面上应该有一个 <strong>最突出的主导元素。</strong></li><li>使用有对比的子标题:不仅视觉效果强烈,而且能充分地表达含义。</li><li>段落缩进:第一段不要缩进。即使跟在子标题后面也如此。<br><strong>段落之间要么有额外的空间,要么缩进,但不要二者都有。</strong></li><li>主标题可采用无衬线体来增加对比。正文中可以使用衬线体。<br>若使用无衬线体,则需要预留较宽的行间距,行的长度应缩短;</li><li>不要把元素堆到角落里,也别总要填满空白,更不要把它们都变成一个尺寸或者类似尺寸。</li><li><strong>打破规则:</strong> 只要你清楚有哪些规则,适当地打破规则,且结果合理,那就大胆去做。<br>只要让人看出来你是有意为之,而不是把页面弄的杂乱无序就好。</li></ul>]]></content>
<categories>
<category> 闲暇读物 </category>
</categories>
<tags>
<tag> 读书小结 </tag>
</tags>
</entry>
<entry>
<title>H5 与 App 之间的交互</title>
<link href="/2021/04/12/H5%E4%B8%8EApp%E4%B9%8B%E9%97%B4%E7%9A%84%E4%BA%A4%E4%BA%92/"/>
<url>/2021/04/12/H5%E4%B8%8EApp%E4%B9%8B%E9%97%B4%E7%9A%84%E4%BA%A4%E4%BA%92/</url>
<content type="html"><![CDATA[<p>遗留了很久的一个学习任务,最近正好在总结归纳小程序在 App 之间的交互,顺便拾起一些学过的和没学过的知识。主要涉及的知识点:URL Scheme、Webview、H5 与 App 之间的通信以及 JSBridge 的概念。</p><span id="more"></span><h2 id="H5-唤起-App"><a href="#H5-唤起-App" class="headerlink" title="H5 唤起 App"></a>H5 唤起 App</h2><h3 id="唤起方式"><a href="#唤起方式" class="headerlink" title="唤起方式"></a>唤起方式</h3><p><strong>H5 可以通过 location.href、iframe、a 标签三种方式来调用 URL Scheme 来实现唤起 App。</strong><br>URL Scheme 是系统提供的一种机制,它可以由应用程序注册,然后其他程序通过 URL Scheme 来调用该应用程序,其基本格式为 <strong><code>scheme://[path][?query]</code></strong> 。</p><ul><li><strong>scheme</strong> :应用标识,已安装的APP注册在系统中的标识;</li><li><strong>path</strong> :应用行为,表示应用某个页面或功能;</li><li><strong>query</strong> :应用参数,标识应用页面或者应用功能所需的条件参数;<br>但是此方式无法得知 App 是否唤起成功,有可能存在 App 未下载的情况。通常用计时器,监听页面是否已隐藏(监听页面 visibilityChange),若未曾隐藏则认为打开失败,再根据不同的平台跳转不同的渠道下载页。</li></ul><p>除此之外,还有一种链接叫:<strong>Universal Link (通用链接)</strong> , 是 Apple 在 iOS9 推出的一种能够通过 HTTPS 链接来启动 APP 的功能。当应用支持此链接时,则会无缝跳转到 APP,而不需要其他判断;但需要注意的是,这个链接是可以访问的,直接在浏览器中打开并不会跳转 App,需要跨域访问才可以。</p><p>一个完善的唤起流程如下:</p><div id="flowchart-0" class="flow-chart"></div><h3 id="若在小程序的-Webview-中尝试唤起-App-会怎么样?"><a href="#若在小程序的-Webview-中尝试唤起-App-会怎么样?" class="headerlink" title="若在小程序的 Webview 中尝试唤起 App 会怎么样?"></a>若在小程序的 Webview 中尝试唤起 App 会怎么样?</h3><p>(以下的逻辑参照上图的流程)<br>由于微信拦截了 URL Scheme,所以并不会打开 App;此时就会判断 Webview 环境,跳转对应的下载页:<br>如:在 Mac 上的开发者工具中会跳转 App Store:</p><img src="/2021/04/12/H5%E4%B8%8EApp%E4%B9%8B%E9%97%B4%E7%9A%84%E4%BA%A4%E4%BA%92/IOS.png" class="" width="400"><p>而在安卓手机的微信小程序中则会跳转(腾讯的安装渠道):</p><img src="/2021/04/12/H5%E4%B8%8EApp%E4%B9%8B%E9%97%B4%E7%9A%84%E4%BA%A4%E4%BA%92/Android.png" class="" width="400"><p>但是由于这两个地址的域名都没有在微信后台配置,所以都会被微信认成不可信的域名,只会跳转到一个空白页面,然后提示域名不可信。</p><p>PS:主要禁止的原因是,小程序不允许将流量导出到 APP 之外;<br>2021.4.12 不过网上还有一种说法就是将 App 关联到腾讯的应用宝上,到时候就会自己跳转到 App Store (<a href="https://www.zhihu.com/question/24029212">参考</a>)<br>2021.4.13 经验证,此方法不可行,在信任应用宝域名的前提下(sj.qq.com),依旧无法唤起下载/跳转:</p><ul><li>安卓表现为停止在当前 H5 并提示使用浏览器打开;</li><li>IOS 表现为卡在当前 H5,点击下载按钮无反应;</li></ul><h2 id="H5-与-App-之间的通信"><a href="#H5-与-App-之间的通信" class="headerlink" title="H5 与 App 之间的通信"></a>H5 与 App 之间的通信</h2><h3 id="关于-Webview-能力"><a href="#关于-Webview-能力" class="headerlink" title="关于 Webview 能力"></a>关于 Webview 能力</h3><p>Webview 是 Android / IOS 操作系统的一个组件,它可以让应用程序直接显示网页内容。<br>它提供了很多能力,其中最重要的一项就是 <strong>添加 JS 和执行 JS</strong> 的能力。H5 与 App 之间的通信正是依赖于此。</p><p>还有很多通用能力如 <strong>注入 Cookie</strong> 、<strong>添加/移除响应头</strong> 、<strong>监听页面返回操作</strong> 、<strong>拦截 Url</strong> 、<strong>拦截弹窗</strong> 、获取/放置证书、监听下载事件 等等。更多 API 可以查阅 <a href="https://developer.android.com/reference/android/webkit/WebView">官方文档</a>。</p><h3 id="通信方式"><a href="#通信方式" class="headerlink" title="通信方式"></a>通信方式</h3><p>通信过程主要依赖 Webview 提供的 JS API,可以简单的看成两个方向:</p><ul><li>App 调用 JS 代码</li><li>JS 调用 App 代码</li></ul><p>接下来示例的方法均以 Android API 为例。</p><h4 id="App-调用-JS-代码"><a href="#App-调用-JS-代码" class="headerlink" title="App 调用 JS 代码"></a>App 调用 JS 代码</h4><p>在 Webview 中是可以获取到 window 对象的,所以 App 可以访问挂载在全局对象上的方法。只需告知 App 方法名即可。<br>Andiroid 中使用这两个方法执行 JS:</p><ul><li>使用 WebView 的 <strong><code>loadUrl()</code></strong> 方法:参数 为 js 文件路径;</li><li>使用 WebView 的 <strong><code>evaluateJavascript()</code></strong> 方法:参数为 js 方法名,以及回调函数;</li></ul><h4 id="JS-调用-App-代码"><a href="#JS-调用-App-代码" class="headerlink" title="JS 调用 App 代码"></a>JS 调用 App 代码</h4><p>JS 调用 App 代码主要有两种方式,一种是页面发起行为,App <strong>拦截</strong> 行为解析语义响应操作;另一种是 App 提前将方法映射成 JS,<strong>注入</strong> 到 window 对象上供 JS 调用。</p><ul><li>通过 WebChromeClient 的 <strong><code>onJsAlert()</code></strong> 、 <strong><code>onJsConfirm()</code></strong> 、 <strong><code>onJsPrompt()</code></strong> 方法回调 <strong>拦截 JS 对话框</strong> <code>alert()</code>、<code>confirm()</code>、<code>prompt()</code> 消息;<ul><li>得到消息内容后解析,再做相应的处理;</li></ul></li><li>通过 WebViewClient 的 <strong><code>shouldOverrideUrlLoading()</code></strong> 方法回调 <strong>拦截 url</strong> ;<ul><li>一般使用这种方法拦截事先约定好 URL Scheme 上的挂载参数,再执行不同的逻辑;</li></ul></li><li>通过 WebView 的 <strong><code>addJavascriptInterface()</code></strong> 进行对象映射;<ul><li>此方法可以将 Java 对象映射映射成 JS 对象,JS 直接调用即可。</li></ul></li></ul><h3 id="关于-JS-Bridge"><a href="#关于-JS-Bridge" class="headerlink" title="关于 JS Bridge"></a>关于 JS Bridge</h3><p>JS Bridge 只是 Native 和 H5 交互方案的一种统称,犹如它的名字一样,Webview 和 H5 将 JS 用作沟通的桥梁。它赋予了 JavaScript 操作 Native 的能力,同时也给了 Native 调用 JavaScript 的能力。上述的通信方案都可以称之为 JS Bridge 的实现。</p><h3 id="联调注意事项"><a href="#联调注意事项" class="headerlink" title="联调注意事项"></a>联调注意事项</h3><ol><li>同一方法,若确定方法名、参数等没有问题,但是调用结果与预期不一致,注意同时对比 IOS 端和 Android 端表现是否一致,若表现不一致,则应找对应的客户端同事去修改;</li><li>注意测试 App 版本号,以及 H5 中引用的 sdk 版本号,排查问题时考虑是否是版本过旧导致的;</li><li>对于用作工具的测试页面出现问题,即时反馈,有可能是测试页面未更新。</li></ol><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><ul><li><a href="https://blog.csdn.net/carson_ho/article/details/64904691">Android:你要的WebView与 JS 交互方式 都在这里了</a></li><li><a href="https://segmentfault.com/a/1190000016759517">h5 与原生 app 交互的原理</a></li><li><a href="https://alibaba-cloud.medium.com/in-depth-profiling-of-jsbridge-63dc797f8c77">In-depth Profiling of JSBridge</a></li><li><a href="https://developer.android.com/reference/android/os/Build.VERSION">Android Build Version</a><script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/flowchart/1.6.5/flowchart.min.js"></script><textarea id="flowchart-0-code" style="display: none">st=>start: 调用唤起 App 的方法op1=>operation: 页面打开与 App 约定好的 URLop2=>operation: 用定时器监听 visibilityChange 事件c1=>condition: 页面是否隐藏op3=>operation: 调起失败op4=>operation: 判断当前 webview 平台op5=>operation: 跳转到对应渠道下载页面op6=>operation: 调起成功e=>end: 页面的其他逻辑操作st->op1->op2->c1c1(no)->op3->op4->op5c1(yes)->op6->e</textarea><textarea id="flowchart-0-options" style="display: none">{"scale":1,"line-width":2,"line-length":50,"text-margin":10,"font-size":12}</textarea><script> var code = document.getElementById("flowchart-0-code").value; var options = JSON.parse(decodeURIComponent(document.getElementById("flowchart-0-options").value)); var diagram = flowchart.parse(code); diagram.drawSVG("flowchart-0", options);</script></li></ul>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> App </tag>
<tag> Webview </tag>
<tag> 交互 </tag>
</tags>
</entry>
<entry>
<title>微信小程序从 0 到 1</title>
<link href="/2021/03/22/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BB%8E0%E5%88%B01/"/>
<url>/2021/03/22/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BB%8E0%E5%88%B01/</url>
<content type="html"><![CDATA[<p>大概这一年左右的时间,都在跟小程序相关的需求。从开发到上线,流程上会跟以往的 Web 开发有些不同。此前除了大学时的一次课设,其他时间未曾接触过小程序,算是从 0 开始吧。不过得益于 Uniapp 基于 Vue.js 的语法封装,除了小程序自己的 API 之外,语法学习成本几乎没有。</p><span id="more"></span><h2 id="与H5相比,孰优孰劣"><a href="#与H5相比,孰优孰劣" class="headerlink" title="与H5相比,孰优孰劣"></a>与H5相比,孰优孰劣</h2><h3 id="对比"><a href="#对比" class="headerlink" title="对比"></a>对比</h3><ul><li>运行环境<ul><li>网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应;</li><li>在小程序中渲染层和逻辑层分别运行在不同的线程中。即 <a href="https://developers.weixin.qq.com/ebook?action=get_post_info&volumn=1&lang=zh_CN&book=miniprogram&docid=0006a2289c8bb0bb0086ee8c056c0a"><strong>双线程模型</strong></a>。</li></ul></li><li>开发差异<ul><li>小程序原生写法很像前端框架中的 Vue,也是 MVVM 模式,但是写法上没有完全照抄,都可以用类似虚拟 DOM 的形式能保证你的数据变化自动响应到模板;</li><li>小程序里不能使用任何 window 下的属性和方法;</li><li>小程序不可以过虚拟 DOM 来操作 DOM,不能使用任何 DOM 和 BOM 相关API;<ul><li>这是因为:小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API;</li></ul></li><li>小程序提供了很多 SDK 方法,几乎涵盖了 APP 能赋予 H5 的所有能力;</li><li>小程序类似于离线包,只要用户访问过,就会把主包代码下载到本地。</li></ul></li><li>维护成本<ul><li><strong>网页开发者需要面对各式的浏览器兼容</strong> :如 PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对 Safari、Chrome 以及 iOS、Android 系统中的各式 WebView,<strong>开发时只需要常用的编辑器和浏览器即可</strong> ;</li><li><strong>小程序开发过程中需要面对的是两大操作系统</strong> iOS 和 Android 的 <strong>微信客户端</strong> ,以及用于辅助开发的小程序开发者工具;小程序的 <strong>开发者需要经过申请小程序帐号、安装小程序开发者工具、配置项目</strong> 等等过程才可进行小程序开发。</li></ul></li></ul><h2 id="开发前准备"><a href="#开发前准备" class="headerlink" title="开发前准备"></a>开发前准备</h2><ul><li><a href="https://mp.weixin.qq.com/wxopen/waregister?action=step1">申请小程序账号</a></li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html">安装开发者工具</a></li><li>将 AppId 填入开发者工具,新建/导入项目即可<br>(若无 AppId,可去注册新账号,或者使用<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/sandbox.html">测试号</a>)</li></ul><h3 id="账号相关权限"><a href="#账号相关权限" class="headerlink" title="账号相关权限"></a>账号相关权限</h3><p>开发者和测试相关的权限需要在微信后台添加;权限分为项目成员和体验成员,都有数量限制。<br>一般将开发者添加为 <strong>项目成员</strong> ,将测试人员或者 PM 添加为 <strong>体验成员</strong> 。</p><h2 id="开发上手"><a href="#开发上手" class="headerlink" title="开发上手"></a>开发上手</h2><h3 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/">小程序开发文档</a></li><li><a href="https://mp.weixin.qq.com/?token=&lang=zh_CN">微信管理后台</a></li></ul><h3 id="项目目录"><a href="#项目目录" class="headerlink" title="项目目录"></a>项目目录</h3><p>一个小程序主体部分由三个文件组成,必须放在项目的根目录:</p><table><thead><tr><th>文件</th><th>必须</th><th>作用</th></tr></thead><tbody><tr><td><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/app.html">app.js</a></td><td>是</td><td>小程序逻辑:调用小程序实例、小程序生命周期 hook</td></tr><tr><td><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/config.html">app.json</a></td><td>是</td><td>全局配置:决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等</td></tr><tr><td>app.wxss</td><td>否</td><td>小程序公共样式表</td></tr><tr><td>project.config.json</td><td>是</td><td>项目配置文件(如:appId、编译时配置、依赖等)</td></tr></tbody></table><h3 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h3><h4 id="小程序运行机制"><a href="#小程序运行机制" class="headerlink" title="小程序运行机制"></a><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html">小程序运行机制</a></h4><h5 id="冷启动"><a href="#冷启动" class="headerlink" title="冷启动"></a>冷启动</h5><p>如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。冷启动不保留上次的浏览场景,打开即直接进入首页(可以使用 <code>restartStrategy</code> 配置冷启动进入的页面)。</p><h5 id="热启动"><a href="#热启动" class="headerlink" title="热启动"></a>热启动</h5><p>如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。热启动保留上次浏览的 path。</p><h4 id="小程序更新机制"><a href="#小程序更新机制" class="headerlink" title="小程序更新机制"></a><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/update-mechanism.html">小程序更新机制</a></h4><p>开发者在管理后台发布新版本的小程序之后,微信客户端会静默更新到新版本。但是无法立刻影响到所有现网用户,最差情况下,也在发布之后 24 小时之内下发新版本信息到用户。<br>如果需要马上应用最新版本,可以使用 <a href="https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html">wx.getUpdateManager API</a> 进行处理。</p><ul><li>UpdateManager.applyUpdate():强制小程序重启并使用新版本,在小程序新版本下载完成后调用;</li><li>UpdateManager.onCheckForUpdate():监听向微信后台请求检查更新结果事件。微信在小程序冷启动时自动检查更新,不需由开发者主动触发;</li><li>UpdateManager.onUpdateReady():监听小程序有版本更新事件。客户端主动触发下载(无需开发者触发),下载成功后回调;</li><li>UpdateManager.onUpdateFailed():监听小程序更新失败事件;</li></ul><h4 id="基础库"><a href="#基础库" class="headerlink" title="基础库"></a><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/client-lib/version.html">基础库</a></h4><p>小程序的能力需要微信客户端来支撑,每一个基础库都只能在对应的客户端版本上运行,高版本的基础库无法兼容低版本的微信客户端。<br>参考:<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/client-lib/version.html">基础库版本分布</a></p><h4 id="分包"><a href="#分包" class="headerlink" title="分包"></a><a href="https://w.cnblogs.com/fsg6/p/13655175.html">分包</a></h4><p>某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。这样做可以优化小程序首次启动的下载时间,在多团队共同开发时可以更好的解耦协作。<br>在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。</p><p>目前小程序分包大小有以下限制:</p><ul><li>整个小程序所有分包大小不超过 20M</li><li>单个分包/主包大小不能超过 2M</li></ul><p>开发者通过在 app.json subpackages 字段声明项目分包结构:<br>写成 subPackages 也支持:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"pages"</span>:[</span><br><span class="line"> <span class="string">"pages/index"</span>,</span><br><span class="line"> <span class="string">"pages/logs"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">"subpackages"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"root"</span>: <span class="string">"packageA"</span>,</span><br><span class="line"> <span class="string">"pages"</span>: [</span><br><span class="line"> <span class="string">"pages/cat"</span>,</span><br><span class="line"> ]</span><br><span class="line"> }, {</span><br><span class="line"> <span class="string">"root"</span>: <span class="string">"packageB"</span>,</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"pack2"</span>,</span><br><span class="line"> <span class="string">"pages"</span>: [</span><br><span class="line"> <span class="string">"pages/apple"</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><strong>打包原则</strong></p><ul><li>声明 subpackages 后,将按 subpackages 配置路径进行打包,<strong>subpackages 配置路径外的目录将被打包到 app(主包)中</strong> ;</li><li>app(主包)也可以有自己的 pages(即最外层的 pages 字段);</li><li>subpackage 的根目录不能是另外一个 subpackage 内的子目录;</li><li>tabBar 页面必须在 app(主包)内。</li></ul><p><strong>引用原则</strong></p><ul><li>packageA 无法 require packageB JS 文件,但可以 require app、自己 package 内的 JS 文件;</li><li>packageA 无法 import packageB 的 template,但可以 require app、自己 package 内的 template;</li><li>packageA 无法使用 packageB 的资源,但可以使用 app、自己 package 内的资源。</li></ul><p>例如:nodemodules 包中引用的代码会打包到主包中,因为该文件路径在 subPages 之外。</p><h4 id="鉴权登录"><a href="#鉴权登录" class="headerlink" title="鉴权登录"></a><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html">鉴权登录</a></h4><p><img src="https://res.wx.qq.com/wxdoc/dist/assets/img/api-login.2fcc9f35.jpg" alt="小程序鉴权登录流程图"></p><h3 id="常用-API-及能力"><a href="#常用-API-及能力" class="headerlink" title="常用 API 及能力"></a><a href="https://developers.weixin.qq.com/miniprogram/dev/api/">常用 API 及能力</a></h3><ul><li><p>常用事件如 Tap、longPress 参照:<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html#%E4%BA%8B%E4%BB%B6%E5%88%86%E7%B1%BB">WXML的冒泡事件列表</a></p></li><li><p>getApp():获取全局的应用实例,全局数据可以在 App 中设置</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="title class_">App</span>({</span><br><span class="line"> <span class="attr">globalData</span>: <span class="number">1</span></span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// a.js</span></span><br><span class="line"><span class="keyword">var</span> app = <span class="title function_">getApp</span>()</span><br><span class="line">app.<span class="property">globalData</span>++</span><br></pre></td></tr></table></figure></li><li><p>授权相关信息</p><ul><li>获取用户手机号<ul><li>需要将 <code><button></code> 组件 open-type 的值设置为 getPhoneNumber,当用户点击同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据;<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><button open-type=<span class="string">"getPhoneNumber"</span> bindgetphonenumber=<span class="string">"getPhoneNumber"</span>></button></span><br></pre></td></tr></table></figure></li></ul></li><li>wx.getSetting() 获取用户当前权限配置,常可以用来在调用某项系统功能时,查看用户是否授权(例如保存存图片到相册)</li><li>wx.authorize() 向用户发起授权请求,调用后会立即弹窗询问用户是否同意授权小程序使用某项功能( <strong>如果用户之前已经同意授权,则不会出现弹窗</strong> )<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下边这段代码就是上两个 API 的应用</span></span><br><span class="line">wx.<span class="title function_">getSetting</span>({</span><br><span class="line"> <span class="attr">success</span>: <span class="function">(<span class="params">res?: any</span>) =></span> {</span><br><span class="line"> <span class="comment">// 判断是否已经授权</span></span><br><span class="line"> <span class="keyword">if</span> (!res[<span class="string">'scope.writePhotosAlbum'</span>]) {</span><br><span class="line"> wx.<span class="title function_">authorize</span>({</span><br><span class="line"> <span class="attr">scope</span>: <span class="string">'scope.writePhotosAlbum'</span>,</span><br><span class="line"> <span class="attr">success</span>: <span class="function">() =></span> {</span><br><span class="line"> <span class="comment">// 存储图片</span></span><br><span class="line"> wx.<span class="title function_">saveImageToPhotosAlbum</span>(...) </span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 调起客户端小程序设置界面,返回用户设置的操作结果</span></span><br><span class="line"> wx.<span class="title function_">openSetting</span>()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure></li><li>若想取消当前用户授权,可【点击小程序右上角三个点】->【设置】->【用户信息】里取消授权</li></ul></li><li><p>生成小程序二维码</p><ul><li>wxacode.createQRCode:获取小程序二维码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,有数量限制;</li></ul></li><li><p>小程序运行版本的区分</p><ul><li>__wxConfig.envVersion:会返回当前小程序运行版本<ul><li>develop - 开发版</li><li>trial - 体验版</li><li>release - 正式版</li></ul></li><li>注:此方法没有在官方文档上注明,只是挂在在全局 this 下,使用时注意该对象是否存在。</li><li>客户端分享的小程序链接可以指定小程序版本,需要跟客户端同学提前约定好,例如:<img src="/2021/03/22/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BB%8E0%E5%88%B01/%E7%BA%A6%E5%AE%9AminiprogramType%E5%80%BC.png" class="" title="约定 miniprogramType 的值g"></li></ul></li><li><p><strong>与其他第三方应用进行交互</strong></p><ul><li>跳转第三方小程序:需要将被调用的第三方小程序的 AppId 加入到小程序项目白名单中,正式版只能打开正式版;<ul><li>wx.navigateToMiniProgram</li><li>wx.navigateBackMiniProgram</li></ul></li><li>小程序内关注公众号:<ul><li><code><official-account></code> 原生组件,只能关注与小程序主体相同的公众号(后台配置),且样式不允许自定义,使用场景受限(扫码);</li></ul></li><li>小程序唤起 app:<ul><li>直接唤起:否;</li><li><del>由 app 直接调起小程序,然后小程序可以通过操作再调起 app;</del></li><li><del>从 app 分享出去的小程序,可以调起 app:需要将 <code><button></code> 组件 open-type 的值设置为 launchApp,可通过 app-parameter 参数给 App 传参(<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/launchApp.html">详情</a>)<br>(PS:App分享到小程序的参数,支持选择 正式版、体验版、开发板)</del></li><li>2021.5.19 后,小程序不再支持唤起 App 的能力;</li><li>无法从小程序的 webview 组件中唤起 App,微信做了 Url Scheme 拦截;</li></ul></li><li>小程序内 webview 访问 H5:<ul><li><code><web-view></code> 原生组件,个人类型的小程序暂不支持使用,需要在微信后台将域名加入白名单;</li><li>在微信后台下载”校验文件“,并将校验文件上传至网站根目录,供小程序平台进行验证,验证通过了才能成功添加域名;</li><li>注:若要从 webview 打开的 H5 跳转回小程序原生页,则需要提前引入 wx-js-sdk,使用 <code>wx.miniProgram.navigateTo</code> 方法 (<a href="https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html">官方文档</a>)</li></ul></li><li>小程序内打开公众号文章:<ul><li>使用 <code><web-view></code> 组件即可打开相关联的公众号文章,非关联的公众号则提示“无法打开图文消息”;</li><li>在微信管理后台:设置 -> 关联设置 中可以看到“关联的公众号”,(需要到公众号中关联小程序)。</li></ul></li></ul></li><li><p><strong>第三方应用与小程序的交互</strong></p><ul><li>APP 调起微信小程序(只能调用与当前APP相关联的小程序) <a href="https://www.jianshu.com/p/abe336ca2fed">参考</a><ul><li>在微信管理后台:设置 -> 关联设置 中可以看到“关联的移动应用”;</li><li>可跳转到指定页面</li><li><a href="https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/Launching_a_Mini_Program.html">限制</a>: <ul><li>APP和小程序相同主体:如果在同一个主体下,不存在调用个数限制;</li><li>APP和小程序不同主体:如果不在同一个主体下,一个app最多只能关联3个小程序。也就是说,非相同主体的小程序最多拉起3个;</li></ul></li></ul></li><li>外部 H5 调起微信小程序<ul><li>直接调用:否;</li><li>可根原生同学协商,使用 APP 提供的 SDK 方法调用;</li><li>或者使用使用微信云开发能力的托管 H5,免鉴权直接跳转任意合法的小程序;</li></ul></li><li>短信跳转小程序 <ul><li>直接调用:否;</li><li>微信开放能力 - 服务端接口 - 可以获取打开小程序任意页面的 URL Link。适用于从短信、邮件、网页、微信中直接打开;</li><li>使用微信云开发能力,打开M页跳转小程序(待调研);</li></ul></li><li>公众号打开小程序(只能调用与当前公众号相关联的小程序)<a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html#22">参考</a></li></ul></li></ul><h3 id="常见的问题"><a href="#常见的问题" class="headerlink" title="常见的问题"></a>常见的问题</h3><ul><li><a href="https://blog.csdn.net/c5211314963/article/details/80323443">微信小程序之 1rpx 的边框在部分机型上展示不全</a></li><li>小程序 tabbar 关联的页面,不可用用 navigateTo 进行跳转,只能使用 swtichTab 方法。</li><li>小程序原生组件样式覆盖不掉?<ul><li>由于原生组件脱离在 WebView 渲染流程外,故层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。</li></ul></li><li>小程序内打开的微信公众号文章不支持二维码长按识别(公众号二维码除外),这是小程序在新版微信做的限制(详见:<a href="https://developers.weixin.qq.com/community/minihome/doc/0006e436d8cb90a8c48b075165b400">「微信7.0.23」版本中,出现小程序中内嵌的公众号文章里的二维码无法长按识别的问题?</a>)</li><li>小程序内图片没有识别二维码的能力和相关的API(只可以识别小程序码)</li><li>测试时怎样关闭域名校验?<ul><li>在小程序的右上角设置里,将程序置为调试状态即可。</li></ul></li></ul><h3 id="项目测试"><a href="#项目测试" class="headerlink" title="项目测试"></a>项目测试</h3><h4 id="可以从-H5-直接进入小程序体验版"><a href="#可以从-H5-直接进入小程序体验版" class="headerlink" title="可以从 H5 直接进入小程序体验版"></a>可以从 H5 直接进入小程序体验版</h4><p>在移动端打开:</p><p><code>https://open.weixin.qq.com/sns/getexpappinfo?appid={AppId}&path={pagesPath}.html</code></p><p>即可访问小程序体验版,并跳转到对应路径(注意:此链接只能在移动端微信中打开).</p><p>也可以,通过判断微信版本,自己写一个测试/入口构造页面来作为测试入口。</p><h4 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h4><p>小程序的所有缓存数据上线为 10MB,像 storage 中的数据,除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。<br>清除缓存:</p><ul><li>发现-小程序-在列表中删除掉测试的小程序;</li><li>微信-我-设置-通用-存储空间;</li><li>安卓在私信聊天页输入 <a href="debugx5.qq.com">debugx5.qq.com</a> ,利用腾讯的工具清理 cookie;</li><li>退出登录,重新登录。</li></ul><h4 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h4><p>由于安卓9的安全限制,无法信任用户自行安装的证书,正常状态连代理打开小程序会报错“获取运行环境失败”;<br>将手机 root 后,解决证书信任问题后才能访问。</p><h4 id="IOS"><a href="#IOS" class="headerlink" title="IOS"></a>IOS</h4><p>直接连代理,访问小程序即可。</p><h3 id="框架对比"><a href="#框架对比" class="headerlink" title="框架对比"></a>框架对比</h3><p>业内知常见小程序框架:wepy、mpvue、uni-app、taro、chameleon。<br>主流框架对比:<a href="https://juejin.cn/post/6844904118901817351">详情</a><br>主流框架性能对比:<a href="https://dashen.zhuanspirit.com/pages/viewpage.action?pageId=44424143">详情</a><br>目前使用的是 Uniapp,因为可以编译多平台的小程序,且与 Vue 的语法能无缝衔接,开发成本较低。</p><h3 id="个人偏见"><a href="#个人偏见" class="headerlink" title="个人偏见"></a>个人偏见</h3><h4 id="关于功能开发"><a href="#关于功能开发" class="headerlink" title="关于功能开发"></a>关于功能开发</h4><p>从一个开发者的角度,我并不希望听到 PM 说“这个功能和页面要和APP保持一致”。<br>个人认为 APP 承载的功能是核心且重的,也是在用户体验上最优的一端,若将 H5 和小程序的功能完全与 APP 拉齐,不仅开发周期长,维护难度高,同时会让小程序和 H5 失去本身的轻量优势。<br>小程序 和 H5 应该承载更多引流的功能,而不是一整套完备的 APP,当然了,这句话是针对公司有核心 APP 的情况;若是主要产品就是小程序方向,就看功能利弊的权衡了,只是个人认为“小程序”不应该变成一个庞然大物,对于 PM 而言应该更侧重于对于不同端的用户给出不同的产品特性,而不是一味的追求“复刻”。</p><h4 id="关于设计还原"><a href="#关于设计还原" class="headerlink" title="关于设计还原"></a>关于设计还原</h4><p>由于小程序提供的通用的原生组件有的时候,是不允许开发者更改某些样式的,此时要跟设计同学及时反馈;若要自己开发某些组件,记得增加工期。<br>用现有的框架也可以:<a href="https://zhuanlan.zhihu.com/p/204245080?utm_source=qq">汇总9款优秀的开源小程序UI框架</a></p><h4 id="关于部署上线"><a href="#关于部署上线" class="headerlink" title="关于部署上线"></a>关于部署上线</h4><p>最后一点,不管是开发还是审核部署,小程序强烈依赖微信运行环境,被封禁和能否上线的话语权(例如小程序中有游戏广告之类的,通常就会被封禁)并非掌握在自己手里,需要做好被封禁时的准备,域名同理。</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><ul><li><a href="https://developers.weixin.qq.com/miniprogram/dev/framework/">小程序开发文档</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDAxMDg4MA==&mid=2650959369&idx=1&sn=8a5f69ffcf1ebfe44c73eb8021ee4363&scene=21#wechat_redirect">小程序更新机制-各类方案对比</a></li><li><a href="https://zhuanlan.zhihu.com/p/106267803?isappinstalled=0">小程序登录授权及获取用户信息API详解</a></li><li><a href="https://www.jianshu.com/p/de0976045bdb">多途径唤起小程序/APP</a></li></ul>]]></content>
<categories>
<category> 前端 </category>
<category> 微信开发相关 </category>
</categories>
<tags>
<tag> 微信 </tag>
<tag> 小程序 </tag>
</tags>
</entry>
<entry>
<title>2020年终总结</title>
<link href="/2020/12/31/2020%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2020/12/31/2020%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>2020年终总结(✖️)<br>2020年流水账(✔️)</p><span id="more"></span><ul><li>1.22 <strong>回家过年</strong></li><li>2.9 <strong>疫情</strong> 家里东拼西凑最好的口罩都给我拿到了北京,高铁上人很少,人们不怎么说话也不怎么吃东西</li><li>3.20 <strong>被裁</strong> P2P 行业终没能熬过政策寒冬,和之前的同事们由于疫情的原因最后也没能聚上一餐</li><li>4.2 <strong>新公司</strong> 入职某二手平台,开启9点后下班的生活</li><li>4.4 <strong>搬家</strong> 不搬家来公司要一个半小时</li><li>5.1 <strong>购入Switch</strong> 笔记本拿去维修了,于是买了心仪已久的 Switch 和塞尔达来消磨时光</li><li>8.18 <strong>同学婚礼</strong> 去了满洲里见证大学室友的婚礼,两三年不见,大家变化都很大,仿佛只有我还在原地</li><li>9.27 <strong>购入Mac Pro</strong> 赶着最后一波教育优惠,买了自己的 MacBook,顺带入了 Air Pods</li><li>10.15 <strong>搬家</strong> 又换房子了,房东不租了给了一个月搬家时间,自如赔付了一个月的房租</li><li>10.29 <strong>转正</strong> 改了六七版述职PPT,终于成为公司正式的一员了,有时会不禁会想,如果时间能回到以前就好了</li><li>11.1 <strong>欢乐谷</strong> 搭上万圣节的尾巴和公司的小伙伴们去了欢乐谷,各种失重旋转高速俯冲的过山车超级好玩,可是一起同行的小伙伴却说啥也不来第二次了</li><li>12.8 <strong>取消成都之旅</strong> 一场说走就走的成都之旅最终没能逃离疫情的影响,周五买的元旦机票也只能匆匆退掉</li><li>12.28 <strong>深圳&优化</strong> 一起吃饭的小伙伴有一位还在试用期,被”调“去了深圳,31号就是在公司的最后一天,有点不舍</li></ul><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><h3 id="这一年,最大的变化就是换公司了吧。"><a href="#这一年,最大的变化就是换公司了吧。" class="headerlink" title="这一年,最大的变化就是换公司了吧。"></a>这一年,最大的变化就是换公司了吧。</h3><p>新公司 leader 人挺好,每月都会面谈与我们沟通,只是我每次都不知道说什么,只是每次的问题都没有什么变化。<br>新公司的同事们都很年轻,也很 nice,虽然我依旧不是很健谈,饭桌上也总是沉默。</p><p><strong>技术</strong> </p><ul><li>之前没接触过小程序,没用过 TS,也没用过 React,今年在工作中都浅浅的用到了;</li><li>公司内很多内部封装好的组件库,用起来很方便,也有专门负责维护的同学;</li><li>内部技术分享很丰富,但自己的消化速度没想象那么强;</li></ul><p><strong>作息</strong><br>疫情期间找工作,虽然已经做好了就算新公司是 996 也无所谓的准备,但真的接触这种作息才发现,如果可以我还是希望有自己的时间;</p><h3 id="购入-Mac"><a href="#购入-Mac" class="headerlink" title="购入 Mac"></a>购入 Mac</h3><p>在公司配备的 Windows 上设计稿总是展示不完全,要么看不清阴影,要么找不到虚线,被设计谈了两三次之后,索性就买了。<br>刚开始不是很适应新版 Mac 的键盘手感,后来敲着敲着也无所谓了。<br>Air Pods 着实很惊艳,开启降噪模式,专心程度 up~ up~。</p><h3 id="神奇的小程序(uniapp-ts)"><a href="#神奇的小程序(uniapp-ts)" class="headerlink" title="神奇的小程序(uniapp + ts)"></a>神奇的小程序(uniapp + ts)</h3><p>首次接触小程序还是在实习的时候,在学校的一个课题中用过一点,当时也没觉得什么。但是新公司小程序体积很大,改一行代码,编译要好久好久…先要等 uniapp 编译完,然后要等小程序开发者工具编译,这两段编译时间足够我接杯水顺便去上个厕所;<br>6、7月份的时候想要尝试过去优化这个编译速度,却发现无从下手,小程序项目的编译工具是 uniapp 自己的,开发者工具的编译也无法介入,最后组内同事给出一个方案,就是注释掉开发中不需要的路由,然而速度依然差强人意;<br>买了 Mac 之后,以为多少速度能快点,神奇的是,不仅没快,全量编译小程序一不小心就能把 Mac 搞死机,看着黑屏的电脑,我不仅发出感叹:”这,就是小程序的力量吗。“</p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><h3 id="无法拉上拉链的伴娘服。"><a href="#无法拉上拉链的伴娘服。" class="headerlink" title="无法拉上拉链的伴娘服。"></a>无法拉上拉链的伴娘服。</h3><p>刚收到大学室友结婚消息的时候,还是挺震惊的,我们还一起相聚的夜晚仿佛还在昨天,现在算算,我都已经工作两年半了,毕业一别就我们就没再见过面。<br>室友是辽宁人,是我们正儿八经的东北妹砸,远嫁到了内蒙古,很佩服她的勇气,毕竟亲人和好友都不在那边,而且是一个全新的生活环境。<br>也是靠着这次机会,和久别的同学见了一面。感觉大家都变了,大部分已经褪去了学生的稚气,仿佛只有我,也只有我,行为处事依旧像个没长大的学生。<br>草原很美,一望无垠,很羡慕这里的生活节奏,草原上的牛羊偶尔成团,偶尔散开,都是低着头各吃各的草。<br>坐了两个小时的车,从满洲里到新左旗。从乌云密布下着雨的草原一边,行驶到只有落日和霞光的另一边,如此美景,想发朋友圈却没信号。<br>见了新娘,感叹时光飞逝,试了伴娘服,感叹体重为什么不飞逝;加之参加婚礼之前,去剪了剪头发,婚礼当天的我就像是个胖胖的人妖,丢人到是没有,辣眼倒是会有一点。<br>要走了,拥抱一下,一转身两行热泪就下来了:”为什么哭了?“,”因为明天还要上班…“。</p><h3 id="身体"><a href="#身体" class="headerlink" title="身体"></a>身体</h3><p>虽然逢人便说”我还年轻“,虽然别人也常说”你还年轻“,但身体仿佛已经不太认同年轻这两个字了。<br>越来越重的眼袋和越来越僵硬的后背,连尾椎的疼痛也变得越来越难以忍受。<br>除此之外,体重也以肉眼可见的速度上涨,如此说来,今年自己都没有做过几顿饭,全靠外卖过活。<br>再这样下去,怕是要早早步入 ICU 了。</p><h2 id="这一年"><a href="#这一年" class="headerlink" title="这一年"></a>这一年</h2><p>这一年,即使有千百种不适应,也要有千百种方式去适应。</p><p>絮叨絮叨,一年比一年能叨叨,年终总结写的像越来越像流水账。挺丧的一年,年底之前还是没能去海拉鲁城堡看公主一眼,也没能跟帮助过的我人道声谢,崩坏的查莉娅线稿依旧没能画完…</p><p>对 2021 没有特别想立的 flag,希望做一些正确的事吧。</p><p>PS: 不要听民谣写年终总结, (╯°□°)╯( ┻━┻ 越写越丧。</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>【译】Can NodeJS use ES6 import syntax ?</title>
<link href="/2020/12/10/%E3%80%90%E8%AF%91%E3%80%91Can-NodeJS-use-ES6-import-syntax/"/>
<url>/2020/12/10/%E3%80%90%E8%AF%91%E3%80%91Can-NodeJS-use-ES6-import-syntax/</url>
<content type="html"><![CDATA[<p>偶然在一篇文章中看到 Node 可以使用 import 语法了,无需再使用 babel 做额外的转换,遂去了解下 Node 相关的更新。本文主要介绍在最新版本 Node(14.15.1) 中如何使用 import 语法。大部分内容翻译自官网和外网文章。关于 JS 模块机制之前已经总结过一篇<a href="/2019/01/03/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/#more">文章</a>,这里不再赘述。</p><p>( PS:原本是公司部门要求的 kpi 文章,现做了精简 )</p><span id="more"></span><h2 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h2><p>本文主要内容:</p><ul><li>Node 对 ES Modules 的支持</li><li>在 Node 使用 import 语法</li><li>Node 中 ES Modules 的现状和未来</li></ul><h2 id="Node-对-ES-Modules-支持"><a href="#Node-对-ES-Modules-支持" class="headerlink" title="Node 对 ES Modules 支持"></a>Node 对 ES Modules 支持</h2><p>Node 13.2.0 开始正式支持 ES Modules 特性(移除了 –experimental-modules 启动参数).</p><p>注意:相关的 ESM 的实验性标志都虽然被移除<br>(但是由于 ESM loader 还是实验性的,所以运行 ES Modules 代码依然会有警告:</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">(node:47324) ExperimentalWarning: The ESM module loader is experimental.</span><br></pre></td></tr></table></figure><h2 id="在-NodeJS-中使用-ES-Modules"><a href="#在-NodeJS-中使用-ES-Modules" class="headerlink" title="在 NodeJS 中使用 ES Modules"></a>在 NodeJS 中使用 ES Modules</h2><p>使 Node 支持 ES modules 有两种方式:</p><ol><li>在 package.json中,增加 <code>type: "module"</code>配置,即可在 node 代码中使用 <code>import</code>和<code>export</code>语法:</li></ol><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></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── index.js</span><br><span class="line">├── package.json</span><br><span class="line">└── utils</span><br><span class="line"> └── speak.js</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// utils/speak.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">speak</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Come from speak.'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">import</span> { speak } <span class="keyword">from</span> <span class="string">'./utils/speak.js'</span>;</span><br><span class="line"><span class="title function_">speak</span>(); <span class="comment">//come from speak</span></span><br></pre></td></tr></table></figure><ol start="2"><li>在 .mjs 文件中直接使用 <code>import</code>和<code>export</code>;</li></ol><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></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── index.mjs</span><br><span class="line">├── package.json</span><br><span class="line">└── utils</span><br><span class="line"> └── sing.mjs</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// utils/sing.mjs</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">sing</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Come from sing'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.mjs</span></span><br><span class="line"><span class="keyword">import</span> { sing } <span class="keyword">from</span> <span class="string">'./utils/sing.mjs'</span>;</span><br><span class="line"><span class="title function_">sing</span>(); <span class="comment">//come from sing</span></span><br></pre></td></tr></table></figure><p>注意:</p><ul><li>若不添加上述两项中任一项,直接使用在 Node 中使用 ES modules,则会抛出警告: <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">Warning: To load an ES module, <span class="built_in">set</span> <span class="string">"type"</span>: <span class="string">"module"</span> <span class="keyword">in</span> the package.json or use the .mjs extension.</span><br></pre></td></tr></table></figure></li><li><strong>根据ESM规范,使用import关键字并不会像 CommonJS 模块那样,在默认情况下以文件扩展名完成文件路径。因此,ES Modules 必须明确文件扩展名。</strong></li></ul><h3 id="模块作用域"><a href="#模块作用域" class="headerlink" title="模块作用域"></a>模块作用域</h3><p>一个模块的作用域,由父级中有 <code>type: "module"</code> 的 package.json 文件路径定义。而使用<code>.mjs</code>扩展文件加载模块,则不受限于包的作用域。<br>同理,<code>package.json</code>中没有<code>type</code>标志的包都会默认采用 CommonJS 模块机制,<code>.cjs</code>类型的扩展文件使用 CommonJS 方式加载模块同样不受限于包的作用域。</p><h3 id="包的入口"><a href="#包的入口" class="headerlink" title="包的入口"></a>包的入口</h3><p>定义包的入口有两种方式,在 package.json 中定义<code>main</code>字段或者<code>exports</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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"main"</span>: <span class="string">"./main.js"</span>,</span><br><span class="line"> <span class="string">"exports"</span>: <span class="string">"./main.js"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>需要注意的是,当<code>exports</code>字段被定义后,包的所有子路径都将被封装,子路径的文件不可再被导入。例如 require(‘pkg/subpath.js’) 将会报错:</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">ERR_PACKAGE_PATH_NOT_EXPORTED error.</span><br></pre></td></tr></table></figure><p>参考官方文档:<a href="https://nodejs.org/api/packages.html#packages_main_entry_point_export">https://nodejs.org/api/packages.html#packages_main_entry_point_export</a></p><h3 id="两个模块机制在执行时机上的区别"><a href="#两个模块机制在执行时机上的区别" class="headerlink" title="两个模块机制在执行时机上的区别"></a>两个模块机制在执行时机上的区别</h3><ul><li>ES Modules 导入的模块会被预解析,以便在代码运行前导入:<ul><li>根据 EMS 规范 import / export 必须位于模块顶级,不能位于作用域内;</li><li>模块内的 import/export 会提升到模块顶部;</li></ul></li><li>在 CommonJS 中,模块将在运行时解析;</li></ul><p>举一个简单的例子来直观的对比下二者的差别:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ES Modules</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// a.js</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Come from a.js.'</span>);</span><br><span class="line"><span class="keyword">import</span> { hello } <span class="keyword">from</span> <span class="string">'./b.js'</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(hello);</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.js</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Come from b.js.'</span>);</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> hello = <span class="string">'Hello from b.js'</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><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Come from b.js.</span><br><span class="line">Come from a.js.</span><br><span class="line">Hello from b.js</span><br></pre></td></tr></table></figure><p>同样的代码使用 CommonJS 机制:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// CommonJS</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// a.js</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Come from a.js.'</span>);</span><br><span class="line"><span class="keyword">const</span> hello = <span class="built_in">require</span>(<span class="string">'./b.js'</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(hello);</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.js</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Come from b.js.'</span>);</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="string">'Hello from b.js'</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><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Come from a.js.</span><br><span class="line">Come from b.js.</span><br><span class="line">Hello from b.js</span><br></pre></td></tr></table></figure><p>可以看到 ES Modules 预先解析了模块代码,而 CommonJS 是代码运行的时候解析的。</p><h3 id="两个模块在原理上的区别"><a href="#两个模块在原理上的区别" class="headerlink" title="两个模块在原理上的区别"></a>两个模块在原理上的区别</h3><ol><li>CommonJS</li></ol><p>Node 将每个文件都视为独立的模块,它定义了一个 Module 构造函数,它代表模块自身:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Module</span>(<span class="params">id = <span class="string">''</span>, parent</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">id</span> = id;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">path</span> = path.<span class="title function_">dirname</span>(id);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">exports</span> = {};</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">parent</span> = parent;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">filename</span> = <span class="literal">null</span>;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">loaded</span> = <span class="literal">false</span>;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">children</span> = [];</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>而 require 函数接收一个代表模块ID或者路径的值作为参数,它返回的是用<code>module.exports</code>导出的对象。在执行代码模块之前,NodeJs 将使一 个包装器对模块中的代码其进行封装:</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></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"><span class="built_in">exports</span>, <span class="built_in">require</span>, <span class="variable language_">module</span>, __filename, __dirname</span>) { </span><br><span class="line"> <span class="comment">// Module code actually lives in here </span></span><br><span class="line">}); </span><br></pre></td></tr></table></figure><blockquote><p>引自 NodeJS 官网</p><p>通过这样做,Node.js 实现了以下几点:</p><ul><li>它保持了顶层的变量(用 var、 const 或 let 定义)作用在模块范围内,而不是全局对象。</li><li>它有助于提供一些看似全局的但实际上是模块特定的变量,例如:<ul><li>实现者可以用于从模块中导出值的 module 和 exports 对象。</li><li>包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。</li></ul></li></ul></blockquote><p>简言之,每个模块都有自己的函数包装器, Node 通过此种方式确保模块内的代码对它是私有的。在包装器执行之前,模块内的导出内容是不确定的。<br>除此之外,第一次加载的模块会被缓存到 <code>Module._cache</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">Resolution (解析) –> Loading (加载) –> Wrapping (私有化) –> Evaluation (执行) –> Caching (缓存)</span><br></pre></td></tr></table></figure><ol start="2"><li>ES Modules</li></ol><p>在 ESM 中,import 语句用于在解析代码时导入模块依赖的静态链接。文件的依赖关系在编译阶段就确定了。对于 ESM,模块的加载大致分为三步:</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">Construction (解析) -> Instantiation (实例化、建立链接) -> Evaluation (执行)</span><br></pre></td></tr></table></figure><p>这些步骤是异步执行的,每一步都可以看作是相互独立的。这一点跟 CommonJS 有很大不同,对于 CommonJS 来说,每一步都是同步进行的。</p><h3 id="两种模块间的相互引用"><a href="#两种模块间的相互引用" class="headerlink" title="两种模块间的相互引用"></a>两种模块间的相互引用</h3><p>CommonJS 和 ES Modules 都支持 <code>Dynamic import()</code>。它可以支持两种模块机制的导入:</p><h4 id="在-CommonJS-文件中导入-ES-Modules-模块"><a href="#在-CommonJS-文件中导入-ES-Modules-模块" class="headerlink" title="在 CommonJS 文件中导入 ES Modules 模块"></a>在 CommonJS 文件中导入 ES Modules 模块</h4><p>由于 ES Modules 的加载、解析和执行都是异步的,而 require() 的过程是同步的、所以不能通过 require() 来引用一个 ES6 模块。ES6 提议的 import() 函数将会返回一个 Promise,它在 ES Modules 加载后标记完成。借助于此,我们可以在 CommonJS 中使用异步的方式导入 ES Modules:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 then() 来进行模块导入后的操作</span></span><br><span class="line"><span class="keyword">import</span>(<span class="string">"es6-modules.mjs"</span>).<span class="title function_">then</span>(<span class="function">(<span class="params"><span class="variable language_">module</span></span>)=></span>{<span class="comment">/*…*/</span>}).<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>)=></span>{<span class="comment">/**…*/</span>})</span><br><span class="line"><span class="comment">// 或者使用 async 函数</span></span><br><span class="line">(<span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">'./es6-modules.mjs'</span>);</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><h3 id="在-ES-Modules-文件中导入-CommonJS-模块"><a href="#在-ES-Modules-文件中导入-CommonJS-模块" class="headerlink" title="在 ES Modules 文件中导入 CommonJS 模块"></a>在 ES Modules 文件中导入 CommonJS 模块</h3><p>在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块,因为在 ES6 模块里异步加载并非是必须的:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="keyword">default</span> <span class="keyword">as</span> cjs } <span class="keyword">from</span> <span class="string">'cjs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The following import statement is "syntax sugar" (equivalent but sweeter)</span></span><br><span class="line"><span class="comment">// for `{ default as cjsSugar }` in the above import statement:</span></span><br><span class="line"><span class="keyword">import</span> cjsSugar <span class="keyword">from</span> <span class="string">'cjs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(cjs);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(cjs === cjsSugar);</span><br></pre></td></tr></table></figure><h2 id="Node-中-ES-Modules-的现状和未来"><a href="#Node-中-ES-Modules-的现状和未来" class="headerlink" title="Node 中 ES Modules 的现状和未来"></a>Node 中 ES Modules 的现状和未来</h2><p>在引入 ES6 标准之前,服务器端 JavaScript 代码都是依赖 CommonJS 模块机制进行包管理的。<br>如今,随着 ES Modules 的引入,开发人员可以享受到与发布规范相关的许多好处。但需要注意的是,截止至当前时间(2020.11.30),在最新版 Node v15.1.0 中,该特性依然是实验性的(Stability: 1),不建议在生产环境中使用该功能。</p><p>最后,由于两种模块格式之间存在不兼容问题,将当前项目从 CommonJS 到 ES Modules 转换将是一个很大的挑战。可以借助 Babel 相关插件实现 CommonJS 和 ES Modules 间的相互转换:</p><ul><li><a href="https://babel.docschina.org/docs/en/babel-plugin-transform-modules-commonjs">plugin-transform-modules-commonjs</a></li><li><a href="https://www.npmjs.com/package/babel-plugin-transform-commonjs">babel-plugin-transform-commonjs</a></li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><h3 id="翻译原文"><a href="#翻译原文" class="headerlink" title="翻译原文"></a>翻译原文</h3><ul><li><a href="https://blog.logrocket.com/es-modules-in-node-today/">es-modules-in-node-today</a></li><li><a href="https://aotu.io/notes/2017/04/22/an-update-on-es6-modules-in-node-js/index.html">an-update-on-es6-modules-in-node-js</a></li></ul><h3 id="官方文档"><a href="#官方文档" class="headerlink" title="官方文档"></a>官方文档</h3><ul><li><a href="https://nodejs.org/dist/latest-v14.x/docs/api/modules.html">Node Documentation</a></li><li><a href="https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V13.md#13.4.0">Node version13+ release log</a></li><li><a href="https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V14.md#14.15.0">Node version14+ release log</a></li><li><a href="https://github.com/nodejs/ecmascript-modules/blob/modules-lkgr/doc/api/modules.md#the-module-wrapper">Node modules wrapper</a></li><li><a href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L195">Node Source code: cjs</a></li><li><a href="https://tc39.es/ecma262/#sec-modules">ECMA262 Modules</a></li><li><a href="https://github.com/tc39/proposal-dynamic-import">TC39 Proposal Dynamic import</a></li></ul>]]></content>
<categories>
<category> 后端 </category>
</categories>
<tags>
<tag> NodeJS </tag>
<tag> 翻译 </tag>
</tags>
</entry>
<entry>
<title>【译】从 ES2016 到 ES2020 的所有特性</title>
<link href="/2020/07/23/%E3%80%90%E8%AF%91%E3%80%91%E4%BB%8E-ES2016-%E5%88%B0-ES2020-%E7%9A%84%E6%89%80%E6%9C%89%E7%89%B9%E6%80%A7/"/>
<url>/2020/07/23/%E3%80%90%E8%AF%91%E3%80%91%E4%BB%8E-ES2016-%E5%88%B0-ES2020-%E7%9A%84%E6%89%80%E6%9C%89%E7%89%B9%E6%80%A7/</url>
<content type="html"><![CDATA[<p>自 ECMA2015 (6th) 大幅更新之后, ECMA 标准变更成每年6月发布一个版本进行小幅度更新。为方便温习和查找,汇总一下近五年的所有版本特性。本文共涵盖了 ES2016、ES2017、ES2018、ES2019、ES2020 五个版本的更新内容。翻译有删改,仅供快速查找使用。</p><span id="more"></span><h3 id="前言:关于ECMA"><a href="#前言:关于ECMA" class="headerlink" title="前言:关于ECMA"></a>前言:关于ECMA</h3><p>ECMA 相关stage-x 处于某个阶段,描述的是 ECMA 标准相关的内容。根据提案划分界限,stage-x 大致分为以下阶段:</p><ul><li>stage-0:还是一个设想,只能由 TC39 成员或 TC39 贡献者提出。</li><li>stage-1:提案阶段,比较正式的提议,只能由 TC39 成员发起,这个提案要解决的问题必须有正式的书面描述。</li><li>stage-2:草案,有了初始规范,必须对功能语法和语义进行正式描述,包括一些实验性的实现。</li><li>stage-3:候选,该提议基本已经实现,需要等待实验验证,用户反馈及验收测试通过。</li><li>stage-4:已完成,必须通过 Test262 验收测试,下一步就纳入 ECMA 标准。</li></ul><p>总结起来就是数字越大,越成熟。</p><h3 id="ES2016-新特性"><a href="#ES2016-新特性" class="headerlink" title="ES2016 新特性"></a>ES2016 新特性</h3><p>ES2016 只更新了两个特性:</p><ul><li>Array.prototype.includes()</li><li>指数运算符</li></ul><h4 id="Array-prototype-includes"><a href="#Array-prototype-includes" class="headerlink" title="Array.prototype.includes()"></a>Array.prototype.includes()</h4><p>该方法用于检测数组中是否包含某个值,包含则返回 true,否则返回 false。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="number">2</span>);</span><br><span class="line"><span class="comment">// true</span></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="number">3</span>);</span><br><span class="line"><span class="comment">// false</span></span><br></pre></td></tr></table></figure><blockquote><p>结合 fromIndex 使用:</p></blockquote><p>可以为 <code>.includes()</code> 提供一个起始索引,默认是 0,接受负数值。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [ <span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">9</span>, <span class="number">11</span> ];</span><br><span class="line"></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="number">3</span>, <span class="number">1</span>);</span><br><span class="line"><span class="comment">// find the number 3 starting from array index 1</span></span><br><span class="line"><span class="comment">// true</span></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="number">5</span>, <span class="number">4</span>);</span><br><span class="line"><span class="comment">//false</span></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="number">1</span>, -<span class="number">1</span>);</span><br><span class="line"><span class="comment">// find the number 1 starting from the ending of the array going backwards</span></span><br><span class="line"><span class="comment">// false</span></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="number">11</span>, -<span class="number">3</span>);</span><br><span class="line"><span class="comment">// true</span></span><br></pre></td></tr></table></figure><h4 id="指数操作符"><a href="#指数操作符" class="headerlink" title="指数操作符 (**)"></a>指数操作符 (**)</h4><p>在 ES2016 前我们会这样写:</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></pre></td><td class="code"><pre><span class="line"><span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="number">2</span>, <span class="number">2</span>);</span><br><span class="line"><span class="comment">// 4</span></span><br><span class="line"><span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line"><span class="comment">// 8</span></span><br></pre></td></tr></table></figure><p>现在,有了指数运算符之后,可以这样写:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2</span> ** <span class="number">2</span>;</span><br><span class="line"><span class="comment">// 4</span></span><br><span class="line"><span class="number">2</span> ** <span class="number">3</span>;</span><br><span class="line"><span class="comment">// 8</span></span><br></pre></td></tr></table></figure><p>这在多次操作指数运算的时候很有用:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2</span> ** <span class="number">2</span> ** <span class="number">2</span></span><br><span class="line"><span class="comment">// 16</span></span><br><span class="line"><span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="number">2</span>, <span class="number">2</span>), <span class="number">2</span>);</span><br><span class="line"><span class="comment">// 16</span></span><br></pre></td></tr></table></figure><p><code>Math.pow()</code> 需要连续调用,这会使代码看起来很长不宜阅读。使用指数运算符的方式更快更简洁。</p><h3 id="ES2017-新特性"><a href="#ES2017-新特性" class="headerlink" title="ES2017 新特性"></a>ES2017 新特性</h3><p>ES2017 介绍了更多新特性,如 String padding,Object.entries(), Object.values(), 原子性操作, 以及 Async、Await 等。</p><h4 id="字符串填充-String-padStart-和-String-padEnd"><a href="#字符串填充-String-padStart-和-String-padEnd" class="headerlink" title="字符串填充 ( String.padStart() 和 String.padEnd() )"></a>字符串填充 ( String.padStart() 和 String.padEnd() )</h4><p><code>.padStart()</code> 对字符串头部进行填充, <code>.padEnd()</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></pre></td><td class="code"><pre><span class="line"><span class="string">"hello"</span>.<span class="title function_">padStart</span>(<span class="number">6</span>);</span><br><span class="line"><span class="comment">// " hello"</span></span><br><span class="line"><span class="string">"hello"</span>.<span class="title function_">padEnd</span>(<span class="number">6</span>);</span><br><span class="line"><span class="comment">// "hello "</span></span><br></pre></td></tr></table></figure><p>为什么只填充1个空格而不是6个?是因为 “hello” 一共是五个字符,而 <code>.padStart</code> 和 <code>.padEnd</code> 的入参是填充后的字符串长度,所以之只会填充一个空格。 </p><blockquote><p>使用 padStart 实现文本右对齐</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> strings = [<span class="string">"short"</span>, <span class="string">"medium length"</span>, <span class="string">"very long string"</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> longestString = strings.<span class="title function_">sort</span>(<span class="function">(<span class="params">s1, s2</span>) =></span> s2.<span class="property">length</span> - s1.<span class="property">length</span>).<span class="title function_">map</span>(<span class="function"><span class="params">str</span> =></span> str.<span class="property">length</span>)[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">strings.<span class="title function_">forEach</span>(<span class="function"><span class="params">str</span> =></span> <span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">padStart</span>(longestString)));</span><br><span class="line"></span><br><span class="line"><span class="comment">// very long string</span></span><br><span class="line"><span class="comment">// medium length</span></span><br><span class="line"><span class="comment">// short</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>第一步获取了数组中最长字符串的长度,接下来用该长度填充数组中的每个字符串,即打印出一组右对齐的字符串。</p><blockquote><p>自定义填充值</p></blockquote><p>除了默认的空格,还可以使用字符串和数字进行填充。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"hello"</span>.<span class="title function_">padEnd</span>(<span class="number">13</span>,<span class="string">" Alberto"</span>);</span><br><span class="line"><span class="comment">// "hello Alberto"</span></span><br><span class="line"><span class="string">"1"</span>.<span class="title function_">padStart</span>(<span class="number">3</span>, <span class="number">0</span>);</span><br><span class="line"><span class="comment">// "001"</span></span><br><span class="line"><span class="string">"99"</span>.<span class="title function_">padStart</span>(<span class="number">3</span>, <span class="number">0</span>);</span><br><span class="line"><span class="comment">// "099"</span></span><br></pre></td></tr></table></figure><h4 id="Object-entries-和-Object-values"><a href="#Object-entries-和-Object-values" class="headerlink" title="Object.entries() 和 Object.values()"></a>Object.entries() 和 Object.values()</h4><p>首先创建一个Object:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> family = {</span><br><span class="line"> <span class="attr">father</span>: <span class="string">"Jonathan Kent"</span>,</span><br><span class="line"> <span class="attr">mother</span>: <span class="string">"Martha Kent"</span>,</span><br><span class="line"> <span class="attr">son</span>: <span class="string">"Clark Kent"</span>,</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在上个版本的 javascript 中,我们可以使用如下方式获取 Object 中的值:</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></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(family);</span><br><span class="line"><span class="comment">// ["father", "mother", "son"]</span></span><br><span class="line">family.<span class="property">father</span>;</span><br><span class="line"><span class="string">"Jonathan Kent"</span></span><br></pre></td></tr></table></figure><p><code>Object.keys()</code> 仅会返回对象中所有的键名。</p><p>现在又多了两种可以访问对象的方法:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">values</span>(family);</span><br><span class="line"><span class="comment">// ["Jonathan Kent", "Martha Kent", "Clark Kent"]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">entries</span>(family);</span><br><span class="line"><span class="comment">// ["father", "Jonathan Kent"]</span></span><br><span class="line"><span class="comment">// ["mother", "Martha Kent"]</span></span><br><span class="line"><span class="comment">// ["son", "Clark Kent"]</span></span><br></pre></td></tr></table></figure><p><code>Object.values()</code> 以数组形式返回对象所有值。<br><code>Object.entries()</code> 同样以数组形式返回对象中的键值对。</p><h4 id="Object-getOwnPropertyDescriptors"><a href="#Object-getOwnPropertyDescriptors" class="headerlink" title="Object.getOwnPropertyDescriptors()"></a>Object.getOwnPropertyDescriptors()</h4><p>这个方法会返回对象所有自身属性的描述。描述性的字段有:<code>value</code>,<code>writable</code>, <code>get</code>, <code>set</code>, <code>configurable</code> 和 <code>enumerable</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myObj = {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">"Alberto"</span>,</span><br><span class="line"> <span class="attr">age</span>: <span class="number">25</span>,</span><br><span class="line"> <span class="title function_">greet</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"hello"</span>);</span><br><span class="line"> },</span><br><span class="line">}</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptors</span>(myObj);</span><br><span class="line"><span class="comment">// age: {value: 25, writable: true, enumerable: true, configurable: true}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// greet: {value: ƒ, writable: true, enumerable: true, configurable: true}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// name: {value: "Alberto", writable: true, enumerable: true, configurable: true}</span></span><br></pre></td></tr></table></figure><h4 id="尾行逗号"><a href="#尾行逗号" class="headerlink" title="尾行逗号"></a>尾行逗号</h4><p>这仅仅是语法上的一个小改变。现在在写 Object 属性值时,我们可以在每个值后边加上一个逗号,不论它是否是最后一个。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// from this</span></span><br><span class="line"><span class="keyword">const</span> object = {</span><br><span class="line"> <span class="attr">prop1</span>: <span class="string">"prop"</span>,</span><br><span class="line"> <span class="attr">prop2</span>: <span class="string">"propop"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// to this</span></span><br><span class="line"><span class="keyword">const</span> object = {</span><br><span class="line"> <span class="attr">prop1</span>: <span class="string">"prop"</span>,</span><br><span class="line"> <span class="attr">prop2</span>: <span class="string">"propop"</span>,</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意上述第二个例子中的最后一个逗号,即使你不写它也不会报错,只是写上会更方便的开发者们协作。</p><h4 id="共享内存和原子性操作"><a href="#共享内存和原子性操作" class="headerlink" title="共享内存和原子性操作"></a>共享内存和原子性操作</h4><p>下述引自 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics">MDN</a>:</p><blockquote><p>多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。</p></blockquote><p>这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。</p><p>方法示例:</p><ul><li>add / sub</li><li>and / or / xor</li><li>load / store</li></ul><p><code>Atomics</code> 通常和 <code>SharedArrayBuffer</code> 对象(通用的固定长度二进制数据缓冲区)一起使用。<br>来看一下几个 <code>Atomics</code>方法的使用示例:</p><h5 id="Atomics-add-Atomics-sub-Atomics-load-and-Atomics-store"><a href="#Atomics-add-Atomics-sub-Atomics-load-and-Atomics-store" class="headerlink" title="Atomics.add(), Atomics.sub(), Atomics.load(), and Atomics.store()"></a>Atomics.add(), Atomics.sub(), Atomics.load(), and Atomics.store()</h5><p><code>Atomics.add()</code> 共接受三个参数:array、index、value。并返回该索引在执行操作前的值。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// create a `SharedArrayBuffer`</span></span><br><span class="line"><span class="keyword">const</span> buffer = <span class="keyword">new</span> <span class="title class_">SharedArrayBuffer</span>(<span class="number">16</span>);</span><br><span class="line"><span class="keyword">const</span> uint8 = <span class="keyword">new</span> <span class="title class_">Uint8Array</span>(buffer);</span><br><span class="line"></span><br><span class="line"><span class="comment">// add a value at the first position</span></span><br><span class="line">uint8[<span class="number">0</span>] = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Atomics</span>.<span class="title function_">add</span>(uint8, <span class="number">0</span>, <span class="number">5</span>));</span><br><span class="line"><span class="comment">// 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 10 + 5 = 15</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(uint8[<span class="number">0</span>])</span><br><span class="line"><span class="comment">// 15</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Atomics</span>.<span class="title function_">load</span>(uint8, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// 15</span></span><br></pre></td></tr></table></figure><p>要从数组中检索特定的值,可以使用 <code>Atomics.load()</code> 并传递两个参数,一个数组和一个索引。<br><code>Atomics.sub()</code> 的使用方式与 <code>Atomics.add()</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="comment">// create a `SharedArrayBuffer`</span></span><br><span class="line"><span class="keyword">const</span> buffer = <span class="keyword">new</span> <span class="title class_">SharedArrayBuffer</span>(<span class="number">16</span>);</span><br><span class="line"><span class="keyword">const</span> uint8 = <span class="keyword">new</span> <span class="title class_">Uint8Array</span>(buffer);</span><br><span class="line"></span><br><span class="line"><span class="comment">// add a value at the first position</span></span><br><span class="line">uint8[<span class="number">0</span>] = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Atomics</span>.<span class="title function_">sub</span>(uint8, <span class="number">0</span>, <span class="number">5</span>));</span><br><span class="line"><span class="comment">// 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 10 - 5 = 5</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(uint8[<span class="number">0</span>])</span><br><span class="line"><span class="comment">// 5</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Atomics</span>.<span class="title function_">store</span>(uint8, <span class="number">0</span>, <span class="number">3</span>));</span><br><span class="line"><span class="comment">// 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Atomics</span>.<span class="title function_">load</span>(uint8, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>上述示例调用 <code>Atomics.sub()</code> 方法,实现 unit8[0] - 5 ,相当于 10 - 5。如同 <code>Atomics.add()</code> 一样,该方法也会返回数组中该索引在执行操作前的值。</p><p>使用 <code>Atomics.store()</code> 来存储一个值,使用 <code>Atomics.load()</code> 来加载一个值。</p><h5 id="Atomics-and-Atomics-or-Atomics-xor"><a href="#Atomics-and-Atomics-or-Atomics-xor" class="headerlink" title="Atomics.and(), Atomics.or(), Atomics.xor()"></a>Atomics.and(), Atomics.or(), Atomics.xor()</h5><p>这三个方法都在数组的给定位置执行按位的 AND、OR 和 XOR 操作。不再赘述。</p><h4 id="Async-和-Await"><a href="#Async-和-Await" class="headerlink" title="Async 和 Await"></a>Async 和 Await</h4><p>ES2017 提供了两个操作 Promise 的新方法:”async/await”。</p><h5 id="回顾一下-Promise"><a href="#回顾一下-Promise" class="headerlink" title="回顾一下 Promise"></a>回顾一下 Promise</h5><p>在介绍新语法之前,让我们快速浏览下之前我们是怎么使用 Promise 的:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// fetch a user from github</span></span><br><span class="line"><span class="title function_">fetch</span>(<span class="string">'api.github.com/user/AlbertoMontalesi'</span>).<span class="title function_">then</span>( <span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="comment">// return the data in json format</span></span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">json</span>();</span><br><span class="line">}).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="comment">// if everything went well, print the data</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line">}).<span class="title function_">catch</span>( <span class="function"><span class="params">err</span> =></span> {</span><br><span class="line"> <span class="comment">// or print the error</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>上述是一个非常简单的例子:请求一个 Github 用户的数据,并打印。下面来看个复杂点的:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><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="keyword">function</span> <span class="title function_">walk</span>(<span class="params">amount</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (amount < <span class="number">500</span>) {</span><br><span class="line"> reject (<span class="string">"the value is too small"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> <span class="title function_">resolve</span>(<span class="string">`you walked for <span class="subst">${amount}</span>ms`</span>),amount);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">walk</span>(<span class="number">1000</span>).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">walk</span>(<span class="number">500</span>);</span><br><span class="line">}).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">walk</span>(<span class="number">700</span>);</span><br><span class="line">}).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">walk</span>(<span class="number">800</span>);</span><br><span class="line">}).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">walk</span>(<span class="number">100</span>);</span><br><span class="line">}).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">walk</span>(<span class="number">400</span>);</span><br><span class="line">}).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">walk</span>(<span class="number">600</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// you walked for 1000ms</span></span><br><span class="line"><span class="comment">// you walked for 500ms</span></span><br><span class="line"><span class="comment">// you walked for 700ms</span></span><br><span class="line"><span class="comment">// you walked for 800ms</span></span><br><span class="line"><span class="comment">// uncaught exception: the value is too small</span></span><br></pre></td></tr></table></figure><p>来看下,如何用新语法 async / await 来重写 <code>Promise</code>。</p><h5 id="Async-和-Await-1"><a href="#Async-和-Await-1" class="headerlink" title="Async 和 Await"></a>Async 和 Await</h5><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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="keyword">function</span> <span class="title function_">walk</span>(<span class="params">amount</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (amount < <span class="number">500</span>) {</span><br><span class="line"> reject (<span class="string">"the value is too small"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> <span class="title function_">resolve</span>(<span class="string">`you walked for <span class="subst">${amount}</span>ms`</span>),amount);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// create an async function</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">go</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// use the keyword `await` to wait for the response</span></span><br><span class="line"> <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">walk</span>(<span class="number">500</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> <span class="keyword">const</span> res2 = <span class="keyword">await</span> <span class="title function_">walk</span>(<span class="number">900</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res2);</span><br><span class="line"> <span class="keyword">const</span> res3 = <span class="keyword">await</span> <span class="title function_">walk</span>(<span class="number">600</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res3);</span><br><span class="line"> <span class="keyword">const</span> res4 = <span class="keyword">await</span> <span class="title function_">walk</span>(<span class="number">700</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res4);</span><br><span class="line"> <span class="keyword">const</span> res5 = <span class="keyword">await</span> <span class="title function_">walk</span>(<span class="number">400</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res5);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"finished"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">go</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// you walked for 500ms </span></span><br><span class="line"><span class="comment">// you walked for 900ms </span></span><br><span class="line"><span class="comment">// you walked for 600ms </span></span><br><span class="line"><span class="comment">// you walked for 700ms </span></span><br><span class="line"><span class="comment">// uncaught exception: the value is too small</span></span><br></pre></td></tr></table></figure><p>让我们来分解一下上述代码都做了什么:</p><ul><li>创建一个异步函数需要在 function 前面添加 async 关键词</li><li>这个关键词会告诉 Javascript 返回一个 Promise</li><li>如果指定 async 函数返回一个非 Promise 的值,那么这个值将会被包含在 Promise 中然后被返回</li><li>顾名思义, await 会告诉 Javascript 等待 promise 返回结果</li></ul><h5 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h5><p>通常在 promise 中,我们使用 <code>.catch()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">asyncFunc</span>(<span class="params"></span>) {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">'http:your-url'</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">asyncFunc</span>();</span><br><span class="line"><span class="comment">// TypeError: failed to fetch</span></span><br></pre></td></tr></table></figure><h3 id="ES2018-新特性"><a href="#ES2018-新特性" class="headerlink" title="ES2018 新特性"></a>ES2018 新特性</h3><h4 id="对象扩展运算符"><a href="#对象扩展运算符" class="headerlink" title="对象扩展运算符"></a>对象扩展运算符</h4><p>还记得 ES6 中我们可以使用扩展运算符来做什么吗:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> veggie = [<span class="string">"tomato"</span>, <span class="string">"cucumber"</span>, <span class="string">"beans"</span>];</span><br><span class="line"><span class="keyword">const</span> meat = [<span class="string">"pork"</span>, <span class="string">"beef"</span>, <span class="string">"chicken"</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> menu = [...veggie, <span class="string">"pasta"</span>, ...meat];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(menu);</span><br><span class="line"><span class="comment">// Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]</span></span><br></pre></td></tr></table></figure><p>现在,扩展运算符同样适用于对象:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> myObj = {</span><br><span class="line"> <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">b</span>: <span class="number">3</span>,</span><br><span class="line"> <span class="attr">c</span>: <span class="number">5</span>,</span><br><span class="line"> <span class="attr">d</span>: <span class="number">8</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// we use the rest operator to grab everything else left in the object.</span></span><br><span class="line"><span class="keyword">let</span> { a, b, ...z } = myObj;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(z); <span class="comment">// {c: 5, d: 8}</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// using the spread syntax we cloned our Object</span></span><br><span class="line"><span class="keyword">let</span> clone = { ...myObj };</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(clone);</span><br><span class="line"><span class="comment">// {a: 1, b: 3, c: 5, d: 8}</span></span><br><span class="line">myObj.<span class="property">e</span> = <span class="number">15</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(clone)</span><br><span class="line"><span class="comment">// {a: 1, b: 3, c: 5, d: 8}</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(myObj)</span><br><span class="line"><span class="comment">// {a: 1, b: 3, c: 5, d: 8, e: 15}</span></span><br></pre></td></tr></table></figure><p>使用扩展运算符,我们可以轻松的复制对象(浅复制)。</p><h4 id="异步的迭代"><a href="#异步的迭代" class="headerlink" title="异步的迭代"></a>异步的迭代</h4><p>使用异步的迭代,我们可以异步的遍历数据。<br><a href="https://github.com/tc39/proposal-async-iteration">引自文档</a></p><blockquote><p>异步迭代器很像迭代器,只不过迭代器的 next 方法返回一对 { value, done }</p></blockquote><p>为此,我们将使用一个 <code>for-await-of</code> 循环,它将迭代转换成 Promise。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> iterables = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">for</span> <span class="keyword">await</span> (<span class="keyword">const</span> value <span class="keyword">of</span> iterables) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(value);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">test</span>();</span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>在执行过程中,<code>[Symbol.asyncIterator]()</code> 方法将会创造一个异步的迭代器,每次访问序列中的下一个值时,我们都会隐式地等待迭代器方法返回 Promise。</p><h4 id="Promise-prototype-finally"><a href="#Promise-prototype-finally" class="headerlink" title="Promise.prototype.finally()"></a>Promise.prototype.finally()</h4><p>引自 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally">MDN</a>:</p><blockquote><p>finally() 方法返回一个 Promise。在 Promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myPromise = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="title function_">resolve</span>();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">myPromise</span><br><span class="line"> .<span class="title function_">then</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'still working'</span>);</span><br><span class="line"> })</span><br><span class="line"> .<span class="title function_">catch</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'there was an error'</span>);</span><br><span class="line"> })</span><br><span class="line"> .<span class="title function_">finally</span>(<span class="function">()=></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Done!'</span>);</span><br><span class="line"> })</span><br></pre></td></tr></table></figure><p><code>.finally()</code> 同样会返回一个 promise,所以我们可以继续链式调用 <code>then</code> 和 <code>catch</code> 方法,但是它们是基于之前的 promise 进行调用的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myPromise = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="title function_">resolve</span>();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">myPromise</span><br><span class="line"> .<span class="title function_">then</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'still working'</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'still working'</span>;</span><br><span class="line"> })</span><br><span class="line"> .<span class="title function_">finally</span>(<span class="function">()=></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Done!'</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'Done!'</span>;</span><br><span class="line"> })</span><br><span class="line"> .<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"> })</span><br><span class="line"><span class="comment">// still working</span></span><br><span class="line"><span class="comment">// Done!</span></span><br><span class="line"><span class="comment">// still working</span></span><br></pre></td></tr></table></figure><p>从上边代码可以看到 <code>finally</code> 后边的 <code>then</code> 返回的值是由第一个 <code>then</code> 创建的,而不是 <code>finally</code>。</p><h4 id="正则表达式的新特性"><a href="#正则表达式的新特性" class="headerlink" title="正则表达式的新特性"></a>正则表达式的新特性</h4><p>在新版的 ECMA 中,共更新了 4 个关于正则的特性。</p><ul><li>正则表达式的 s (doAll) 标志</li><li>正则表达式捕获组命名</li><li>正则表达式反向断言 (Lookbehind Assertions)</li><li>unicode 字符转义 (Unicode property escapes)</li></ul><h5 id="s-doAll-标志"><a href="#s-doAll-标志" class="headerlink" title="s (doAll) 标志"></a>s (doAll) 标志</h5><blockquote><p>引自<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll">MDN</a></p></blockquote><p><code>dotAll</code> 属性表明是否在正则表达式中一起使用 “<code>s</code>“ 修饰符(引入 /s 修饰符,使得<code>.</code>可以匹配任意单个字符,包括换行符和回车符)</p><figure class="highlight javascript"><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">/foo.<span class="property">bar</span>/s.<span class="title function_">test</span>(<span class="string">'foo\nbar'</span>);</span><br><span class="line"><span class="comment">// true</span></span><br></pre></td></tr></table></figure><h5 id="捕获组命名"><a href="#捕获组命名" class="headerlink" title="捕获组命名"></a>捕获组命名</h5><blockquote><p>想要引用正则匹配到的某一部分字符串可以为捕获组编号。每个捕获组的数字都是唯一的,可以对应的数字引用它们,但是这使正则表达式难以阅读和维护。例如 <code>/(\d{4})-(\d{2})-(\d{2})/</code> 匹配一个日期,但如果不看上下文的代码,就无法确定哪一组对应于月份,哪一组是一天。当然,如果哪一天需要交换日期和月份的顺序,那么对应的组引用也需要更新。现在,可以使用 <code>(?<name>...)</code> 来为捕获组命名,以表示任何标识符名称。重写上述例子:<code>/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u</code> 每一个命名都是唯一且遵循 ECMA 命名规范的。命名的组可以通过匹配结果的 <code>result</code> 属性来访问。对组的数字引用也会被建立,就像未命名的组一样。看下边几个例子:</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> re = <span class="regexp">/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u</span>;</span><br><span class="line"><span class="keyword">let</span> result = re.<span class="title function_">exec</span>(<span class="string">'2015-01-02'</span>);</span><br><span class="line"><span class="comment">// result.groups.year === '2015';</span></span><br><span class="line"><span class="comment">// result.groups.month === '01';</span></span><br><span class="line"><span class="comment">// result.groups.day === '02';</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// result[0] === '2015-01-02';</span></span><br><span class="line"><span class="comment">// result[1] === '2015';</span></span><br><span class="line"><span class="comment">// result[2] === '01';</span></span><br><span class="line"><span class="comment">// result[3] === '02';</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> { <span class="attr">groups</span>: { one, two } } = <span class="regexp">/^(?<one>.*):(?<two>.*)$/u</span>.<span class="title function_">exec</span>(<span class="string">'foo:bar'</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`one: <span class="subst">${one}</span>, two: <span class="subst">${two}</span>`</span>); </span><br><span class="line"><span class="comment">// one: foo, two: bar</span></span><br></pre></td></tr></table></figure><h5 id="反向断言"><a href="#反向断言" class="headerlink" title="反向断言"></a>反向断言</h5><blockquote><p>使用反向断言可以确保匹配之前或者之后没有其他匹配。反向断言的语法表示为 <code>(?<=...)</code> 。<br>例如:匹配一个美元数值且不包含美元符号可以这样写 <code>/(?<=$)\d+(\.\d*)?/</code>,这个表达式会匹配 <code>$10.53</code> 并返回 <code>10.53</code>,而并不会匹配 <code>€10.53</code>。而 <code>(?<!...)</code> 匹配的规则正相反,它会匹配不存在表达式中的匹配项,例如 <code>/(?<!$)\d+(?:\.\d*)/</code> 不会匹配 <code>$10.53</code>,但是会匹配 <code>€10.53</code>。</p></blockquote><h5 id="Unicode-字符转义"><a href="#Unicode-字符转义" class="headerlink" title="Unicode 字符转义"></a>Unicode 字符转义</h5><blockquote><p>Unicode 字符转义是一种新的转义序列,<code>u</code> 作为字符转义的标志, <code>\p{...}</code> 和 <code>\P{...}</code> 用来添加转义符。有了这个特性,匹配 Unicode 字符可以这样写:</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regexGreekSymbol = <span class="regexp">/\p{Script=Greek}/u</span>;</span><br><span class="line">regexGreekSymbol.<span class="title function_">test</span>(<span class="string">'π'</span>);</span><br><span class="line"><span class="comment">// true</span></span><br></pre></td></tr></table></figure><h4 id="解除模板字符限制"><a href="#解除模板字符限制" class="headerlink" title="解除模板字符限制"></a>解除模板字符限制</h4><p>当使用 Tagged 模板字符串时,转义字符的限制被移除了(<a href="https://tc39.github.io/proposal-template-literal-revision/#sec-template-literals">阅读更多</a>)</p><h3 id="ES2019-新特性"><a href="#ES2019-新特性" class="headerlink" title="ES2019 新特性"></a>ES2019 新特性</h3><h4 id="Array-prototype-flat-Array-prototype-flatMap"><a href="#Array-prototype-flat-Array-prototype-flatMap" class="headerlink" title="Array.prototype.flat() / Array.prototype.flatMap()"></a>Array.prototype.flat() / Array.prototype.flatMap()</h4><p><code>Array.prototype.flat()</code> 会递归地展平一个数组并作为新值返回,它接受一个表示递归深度的值,未传值则默认深度为1。可以用 <code>Infinity</code> 去展平所有嵌套的数组。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> letters = [<span class="string">'a'</span>, <span class="string">'b'</span>, [<span class="string">'c'</span>, <span class="string">'d'</span>, [<span class="string">'e'</span>, <span class="string">'f'</span>]]];</span><br><span class="line"><span class="comment">// default depth of 1</span></span><br><span class="line">letters.<span class="title function_">flat</span>();</span><br><span class="line"><span class="comment">// ['a', 'b', 'c', 'd', ['e', 'f']]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// depth of 2</span></span><br><span class="line">letters.<span class="title function_">flat</span>(<span class="number">2</span>);</span><br><span class="line"><span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f']</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// which is the same as executing flat with depth of 1 twice</span></span><br><span class="line">letters.<span class="title function_">flat</span>().<span class="title function_">flat</span>();</span><br><span class="line"><span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f']</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Flattens recursively until the array contains no nested arrays</span></span><br><span class="line">letters.<span class="title function_">flat</span>(<span class="title class_">Infinity</span>)</span><br><span class="line"><span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f']</span></span><br></pre></td></tr></table></figure><p><code>Array.prototype.flatMap()</code> 与深度值为1的 flat 几乎相同,但它并非仅仅展平数组。 flatMap 接收一个处理函数,使用 <code>flatMap()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> greeting = [<span class="string">"Greetings from"</span>, <span class="string">" "</span>, <span class="string">"Vietnam"</span>];</span><br><span class="line">greeting.<span class="title function_">flatMap</span>(<span class="function"><span class="params">x</span> =></span> x.<span class="title function_">split</span>(<span class="string">" "</span>))</span><br><span class="line"><span class="comment">// ["Greetings", "from", "", "", "Vietnam"]</span></span><br></pre></td></tr></table></figure><p>这有点类似于 <code>map()</code> 方法,只不过多了一次展平操作。</p><h4 id="Object-fromEntries"><a href="#Object-fromEntries" class="headerlink" title="Object.fromEntries()"></a>Object.fromEntries()</h4><p>Object.fromEntries() 将一组键值对转换成对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> keyValueArray = [</span><br><span class="line"> [<span class="string">'key1'</span>, <span class="string">'value1'</span>],</span><br><span class="line"> [<span class="string">'key2'</span>, <span class="string">'value2'</span>]</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = <span class="title class_">Object</span>.<span class="title function_">fromEntries</span>(keyValueArray)</span><br><span class="line"><span class="comment">// {key1: "value1", key2: "value2"}</span></span><br></pre></td></tr></table></figure><p>我们可以将任何可迭代的值作为 <code>Object.entries()</code> 方法的参数,不论它是一个 <code>Array</code> 还是 <code>Map</code>,或是其他实现了迭代协议的值。</p><p>注:可迭代协议( Iteration Protocols )是 ES2015 提出的,通常通过常量 <code>Symbol.iterator</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> someString = <span class="string">"hi"</span>;</span><br><span class="line"><span class="keyword">typeof</span> someString[<span class="title class_">Symbol</span>.<span class="property">iterator</span>]; <span class="comment">// "function"</span></span><br><span class="line"><span class="keyword">var</span> iterator = someString[<span class="title class_">Symbol</span>.<span class="property">iterator</span>]();</span><br><span class="line"></span><br><span class="line">iterator + <span class="string">""</span>; <span class="comment">// "[object String Iterator]"</span></span><br><span class="line">iterator.<span class="title function_">next</span>() <span class="comment">// { value: "h", done: false }</span></span><br><span class="line">iterator.<span class="title function_">next</span>(); <span class="comment">// { value: "i", done: false }</span></span><br><span class="line">iterator.<span class="title function_">next</span>(); <span class="comment">// { value: undefined, done: true }</span></span><br></pre></td></tr></table></figure><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols">阅读更多关于迭代协议的内容</a></p><h4 id="String-prototype-trimStart-trimEnd"><a href="#String-prototype-trimStart-trimEnd" class="headerlink" title="String.prototype.trimStart() / .trimEnd()"></a>String.prototype.trimStart() / .trimEnd()</h4><p><code>String.prototype.trimStart()</code> 移除字符串前面的空白符,<code>String.prototype.trimEnd()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> str = <span class="string">" this string has a lot of whitespace "</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="property">length</span>;</span><br><span class="line"><span class="comment">// 42</span></span><br><span class="line"></span><br><span class="line">str = str.<span class="title function_">trimStart</span>();</span><br><span class="line"><span class="comment">// "this string has a lot of whitespace "</span></span><br><span class="line">str.<span class="property">length</span>;</span><br><span class="line"><span class="comment">// 38</span></span><br><span class="line"></span><br><span class="line">str = str.<span class="title function_">trimEnd</span>();</span><br><span class="line"><span class="comment">// "this string has a lot of whitespace"</span></span><br><span class="line">str.<span class="property">length</span>;</span><br><span class="line"><span class="comment">// 35</span></span><br></pre></td></tr></table></figure><p>也可以使用 <code>.trimStart()</code> 和 <code>trimEnd()</code> 的别名: <code>.trimLeft()</code> 和 <code>.trimRight()</code> 。</p><h4 id="可选的-catch-捕获参数"><a href="#可选的-catch-捕获参数" class="headerlink" title="可选的 catch 捕获参数"></a>可选的 catch 捕获参数</h4><p>在 ES2019 之前,你必须为 catch 捕获传递一个表示异常的变量,现在这个变量不是必要的了。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Before</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> ...</span><br><span class="line">} <span class="keyword">catch</span>(error) {</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES2019</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> ...</span><br><span class="line">} <span class="keyword">catch</span> {</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这在你想忽略错误参数的时候很有用。</p><h4 id="Function-ptototype-toString"><a href="#Function-ptototype-toString" class="headerlink" title="Function.ptototype.toString()"></a>Function.ptototype.toString()</h4><p><code>.toString()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">a, b</span>) {</span><br><span class="line"> <span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sum.<span class="title function_">toString</span>());</span><br><span class="line"><span class="comment">// function sum(a, b) {</span></span><br><span class="line"><span class="comment">// return a + b;</span></span><br><span class="line"><span class="comment">// }</span></span><br></pre></td></tr></table></figure><p>注释也会被包含其中:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">a, b</span>) {</span><br><span class="line"> <span class="comment">// perform a sum</span></span><br><span class="line"> <span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sum.<span class="title function_">toString</span>());</span><br><span class="line"><span class="comment">// function sum(a, b) {</span></span><br><span class="line"><span class="comment">// // perform a sum</span></span><br><span class="line"><span class="comment">// return a + b;</span></span><br><span class="line"><span class="comment">// }</span></span><br></pre></td></tr></table></figure><h4 id="Symbol-prototype-description"><a href="#Symbol-prototype-description" class="headerlink" title="Symbol.prototype.description"></a>Symbol.prototype.description</h4><p><code>.description</code> 返回 <code>Symbol</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> me = <span class="title class_">Symbol</span>(<span class="string">"Alberto"</span>);</span><br><span class="line">me.<span class="property">description</span>;</span><br><span class="line"><span class="comment">// "Alberto"</span></span><br><span class="line"></span><br><span class="line">me.<span class="title function_">toString</span>()</span><br><span class="line"><span class="comment">// "Symbol(Alberto)"</span></span><br></pre></td></tr></table></figure><h3 id="ES2020-特性"><a href="#ES2020-特性" class="headerlink" title="ES2020 特性"></a>ES2020 特性</h3><h4 id="BigInt-类型"><a href="#BigInt-类型" class="headerlink" title="BigInt 类型"></a>BigInt 类型</h4><p>BigInt 是 JavaScript 第七个原始类型,它允许开发者操作非常大的整型。<br>数字类型可以处理 <code>2 ** 53 - 1</code> 即 <code>9007199254740991</code> 以内的数。可以通过常量 <code>MAX_SAFE_INTEGER</code> 来访问这个值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span>; <span class="comment">// 9007199254740991</span></span><br></pre></td></tr></table></figure><p>顾名思义,若操作的 number 值超过最大值时,运行结果就会变的奇怪。使用 <code>BigInt</code> 类型则没有明确的界限,因为它的界限取决于运行设备的内存。<br>定义 <code>BigInt</code> 类型,你即可以通过给 <code>BigInt()</code> 构造函数传递一个字符串值来创建,也可以像平常一样使用字面量语法来创建,但是要在尾部加上一个字符 <code>n</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myBigInt = <span class="title class_">BigInt</span>(<span class="string">"999999999999999999999999999999"</span>);</span><br><span class="line"><span class="keyword">const</span> mySecondBigInt = <span class="number">999999999999999999999999999999n</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> myBigInt; <span class="comment">// "bigint"</span></span><br></pre></td></tr></table></figure><p>注意,<code>BigInt</code> 类型与常规类型的数字并不是完全兼容的,这意味这你确定最好仅在操作比较大的数据时使用它。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bigInt = <span class="number">1n</span>; <span class="comment">// small number, but still of BigInt type</span></span><br><span class="line"><span class="keyword">const</span> num = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">num === bigInt; <span class="comment">// false -> they aren't strictly equal</span></span><br><span class="line">num == bigInt; <span class="comment">// true</span></span><br><span class="line">num >= bigInt; <span class="comment">// true -> they can be compared</span></span><br><span class="line">num + bigInt; <span class="comment">// error -> they can't operate with one another</span></span><br></pre></td></tr></table></figure><p>总之,使用 JS 做比较复杂的数学运算时 <code>BigInt</code> 是个不错的选择。它在替换专门用于处理大量数字的库方面表现良好。现在至少在整型方向有所进展,而目前我们对 <code>BigDecimal</code> 的提案了解的还很少。</p><h4 id="动态导入(Dynamic-imports)"><a href="#动态导入(Dynamic-imports)" class="headerlink" title="动态导入(Dynamic imports)"></a>动态导入(Dynamic imports)</h4><p>动态导入,允许在浏览器端动态地加载代码模块。使用 <code>import()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span>(<span class="string">"module.js"</span>).<span class="title function_">then</span>(<span class="function">(<span class="params"><span class="variable language_">module</span></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="comment">// or</span></span><br><span class="line"><span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> <span class="variable language_">module</span> = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">"module.js"</span>);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><code>import()</code> 返回一个 promise,resolve 中会返回代码模块加载后的内容。可以使用 ES6 的 <code>.then()</code> 方法或者 <code>async/await</code> 来处理加载结果。</p><h4 id="空值合并操作符(-)"><a href="#空值合并操作符(-)" class="headerlink" title="空值合并操作符(??)"></a>空值合并操作符(??)</h4><p>空值合并操作符(??)是一个新的 JS 运算符,当所访问的值是 null 或者 undefined 时,它会提供一个默认值。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> basicValue = <span class="string">"test"</span>;</span><br><span class="line"><span class="keyword">const</span> nullishValue = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> firstExample = basicValue ?? <span class="string">"example"</span>; <span class="comment">// "test"</span></span><br><span class="line"><span class="keyword">const</span> secondExample = nullishValue ?? <span class="string">"example"</span>; <span class="comment">// "example"</span></span><br></pre></td></tr></table></figure><p>但是这跟 逻辑或(||)有什么区别呢?当第一个数是虚值 (在 Boolean 上下文中认定为 false 的值),如 <code>false</code>, <code>0</code>, 或者<code>""</code>,以及空值 <code>null</code> 和 <code>undefined</code>,那么 逻辑或 将会使用第二个操作数。而空值合并操作符仅仅是在第一个值为空值而不是虚值的时候才会使用第二个操作数。如果你的代码可以接受除了 <code>null</code> 和 <code>undefined</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> falseValue = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">const</span> zeroValue = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">const</span> emptyValue = <span class="string">""</span>;</span><br><span class="line"><span class="keyword">const</span> nullishValue = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> firstExampleOR = falseValue || <span class="string">"example"</span>; <span class="comment">// "example"</span></span><br><span class="line"><span class="keyword">const</span> secondExampleOR = zeroValue || <span class="string">"example"</span>; <span class="comment">// "example"</span></span><br><span class="line"><span class="keyword">const</span> thirdExampleOR = emptyValue || <span class="string">"example"</span>; <span class="comment">// "example"</span></span><br><span class="line"><span class="keyword">const</span> forthExampleOR = nullish || <span class="string">"example"</span>; <span class="comment">// "example"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> firstExample = falseValue ?? <span class="string">"example"</span>; <span class="comment">// false</span></span><br><span class="line"><span class="keyword">const</span> secondExample = zeroValue ?? <span class="string">"example"</span>; <span class="comment">// 0</span></span><br><span class="line"><span class="keyword">const</span> thirdExample = emptyValue ?? <span class="string">"example"</span>; <span class="comment">// ""</span></span><br><span class="line"><span class="keyword">const</span> forthExample = nullish ?? <span class="string">"example"</span>; <span class="comment">// "example"</span></span><br></pre></td></tr></table></figure><h4 id="可选链(-)"><a href="#可选链(-)" class="headerlink" title="可选链(?.)"></a>可选链(?.)</h4><p>与空值合并操作符类似,只不过可选链是处理 Object 中 <code>null</code> 和 <code>undefined</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = {</span><br><span class="line"> <span class="attr">prop</span>: {</span><br><span class="line"> <span class="attr">subProp</span>: {</span><br><span class="line"> <span class="attr">value</span>: <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><br><span class="line">obj.<span class="property">prop</span>.<span class="property">subProp</span>.<span class="property">value</span>; <span class="comment">// 1</span></span><br><span class="line">obj.<span class="property">prop</span>.<span class="property">secondSubProp</span>.<span class="property">value</span>; <span class="comment">// error</span></span><br><span class="line"></span><br><span class="line">obj?.<span class="property">prop</span>?.<span class="property">subProp</span>?.<span class="property">value</span>; <span class="comment">// 1</span></span><br><span class="line">obj?.<span class="property">prop</span>?.<span class="property">secondSubProp</span>?.<span class="property">value</span>; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p>当然,这只是一个语法糖,但也是一个很受欢迎的补充。记住不要在代码里到处使用这些操作符,他们虽然用起来方便,但从性能角度来说,它比普通的 <code>.</code> 开销要大。而且,若是代码是经过 Babel 和 TypeScript 转义的,则更要谨慎使用。</p><h4 id="GlobalThis"><a href="#GlobalThis" class="headerlink" title="GlobalThis"></a>GlobalThis</h4><p>由于 JavaScript 的代码可以运行在多个不同的环境,例如 浏览器、Node.js、Web Worker 等,要实现这种交叉兼容性绝非易事,globalThis 的出现方便了这些操作。<br><code>globalThis</code> 是一个新的全局属性,通常它引用的是当前环境下的全局对象。就像是 <code>self</code> 对于 Web Workers,<code>window</code> 对于浏览器,<code>global</code> 对于 Node.js,以及其他实现了ES2020标准的运行环境。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Hacky globalThis polyfill you had to use pre-ES2020</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getGlobal</span> = (<span class="params"></span>) => {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> self !== <span class="string">"undefined"</span>) {</span><br><span class="line"> <span class="keyword">return</span> self;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="variable language_">window</span> !== <span class="string">"undefined"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="variable language_">window</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="variable language_">global</span> !== <span class="string">"undefined"</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="variable language_">global</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">"Couldn't detect global"</span>);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="title function_">getGlobal</span>() === globalThis; <span class="comment">// true (for browser, Web Worker and Node.js)</span></span><br><span class="line">globalThis === <span class="variable language_">window</span>; <span class="comment">// true (if you're in browser)</span></span><br></pre></td></tr></table></figure><h4 id="Promise-allSettled"><a href="#Promise-allSettled" class="headerlink" title="Promise.allSettled()"></a>Promise.allSettled()</h4><p>这个新增的方法看起来有点像 <code>Promise.all()</code>。<br><code>Promise.all()</code> 的参数中的 promise 若有一个失败,则此实例回调失败。而 <code>Promise.allSettled()</code>不论成功或者失败,都会返回处理结束后的对象数组。</p><h4 id="String-matchAll"><a href="#String-matchAll" class="headerlink" title="String.matchAll()"></a>String.matchAll()</h4><p>如果你之前使用正则,那么相比于在 <code>while</code> 循环中使用 <code>RegExp.exec()</code> 并开启标志 <code>g</code> 来匹配,<code>String.matchAll()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regexp = <span class="regexp">/t(e)(st(\d?))/g</span>;</span><br><span class="line"><span class="keyword">const</span> str = <span class="string">"test1test2"</span>;</span><br><span class="line"><span class="keyword">const</span> resultsArr = [...str.<span class="title function_">matchAll</span>(regexp)]; <span class="comment">// convert iterator to an array</span></span><br><span class="line"></span><br><span class="line">resultsArr[<span class="number">0</span>]; <span class="comment">// ["test1", "e", "st1", "1"]</span></span><br><span class="line">resultsArr[<span class="number">1</span>]; <span class="comment">// ["test2", "e", "st2", "2"]</span></span><br></pre></td></tr></table></figure><h3 id="原文链接"><a href="#原文链接" class="headerlink" title="原文链接"></a>原文链接</h3><ul><li><a href="https://inspiredwebdev.com/everything-from-es-2016-to-es-2019">everything-from-es-2016-to-es-2019</a></li><li><a href="https://areknawo.com/ecmascript-2020-biggest-new-features/">ecmascript-2020-biggest-new-features</a></li></ul>]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> JavaScript </tag>
<tag> 翻译 </tag>
</tags>
</entry>
<entry>
<title>PWA-Service Worker 小结(二)实践</title>
<link href="/2020/01/02/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%BA%8C%EF%BC%89%E5%AE%9E%E8%B7%B5/"/>
<url>/2020/01/02/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%BA%8C%EF%BC%89%E5%AE%9E%E8%B7%B5/</url>
<content type="html"><![CDATA[<p>Service Worker 的初衷是极致优化用户体验,带来丝滑般流畅的离线应用。但同时也可以用作站点缓存使用。它本身类似于一个介于浏览器和服务端之间的网络代理,可以拦截请求并操作响应内容。功能强大,但由于兼容性问题,更适合用作渐进增强来使用。</p><span id="more"></span><h3 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h3><ul><li>Service Worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本,它有自己独立的注册文件;它是 Web Worker 的一种,不能够直接操作 DOM;</li><li>出于安全问题考虑,它只能在 HTTPS 域名下或者 localhost 本地运行;</li><li>可以通过 postMessage 接口传递数据给其他 JS 文件;</li><li>Service Worker 中运行的代码不会被阻塞,也不会阻塞其他页面的 JS 文件中的代码;</li><li>每个 Service Worker(注册文件)都有自己的作用域,它只会处理自己作用域下的请求,而 Service Worker 的存放位置就是它的最大作用域;</li><li>缓存的资源存储在 Cache Storage 中,缓存不会过期,但是浏览器对每个网站的 Cache Storage 的大小有硬性限制,所以需要清理不必要的缓存;</li></ul><h3 id="二、Service-Worker-的生命周期"><a href="#二、Service-Worker-的生命周期" class="headerlink" title="二、Service Worker 的生命周期"></a>二、Service Worker 的生命周期</h3><ol><li>注册 Service worker,在网页上生效;</li><li>安装成功,激活 或者 安装失败(下次加载会尝试重新安装);</li><li>激活后,在 sw 的作用域下作用所有的页面,首次注册 sw 不会生效,下次加载页面才会生效;已经注册的 sw 不会重复注册;不会因为页面的关闭而被销毁;</li><li>sw 作用页面后,处理 fetch(网络请求)和 postMessage(页面消息)事件 或者 被终止(节省内存)。</li></ol><img src="/2020/01/02/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%BA%8C%EF%BC%89%E5%AE%9E%E8%B7%B5/Service-Worker-Lifecycle.png" class="" title="Service Worker Lifecycle"><h3 id="三、Service-Worker-安装注册"><a href="#三、Service-Worker-安装注册" class="headerlink" title="三、Service Worker 安装注册"></a>三、Service Worker 安装注册</h3><h4 id="注册文件"><a href="#注册文件" class="headerlink" title="注册文件"></a>注册文件</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> <span class="variable language_">window</span>.<span class="property">navigator</span>) {</span><br><span class="line"> navigator.<span class="property">serviceWorker</span>.<span class="title function_">register</span>(<span class="string">'./sw.js'</span>, { <span class="attr">scope</span>: <span class="string">'./'</span> })</span><br><span class="line"> .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">reg</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'success'</span>, reg);</span><br><span class="line"> })</span><br><span class="line"> .<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params">err</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'fail'</span>, err);</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="注销文件"><a href="#注销文件" class="headerlink" title="注销文件"></a>注销文件</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> <span class="variable language_">window</span>.<span class="property">navigator</span>) {</span><br><span class="line"> navigator.<span class="property">serviceWorker</span>.<span class="property">getRegistrations</span>.<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">registrations</span>) {</span><br><span class="line"> <span class="comment">//returns installed service workers</span></span><br><span class="line"> <span class="keyword">if</span> (registrations.<span class="property">length</span>) {</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> registration <span class="keyword">of</span> registrations) {</span><br><span class="line"> registration.<span class="title function_">unregister</span>().<span class="title function_">then</span>(<span class="function"><span class="params">ret</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(</span><br><span class="line"> <span class="string">'Unregister Service Worker['</span> +</span><br><span class="line"> settings.<span class="property">serviceWorkerUrl</span> +</span><br><span class="line"> <span class="string">']: '</span> + ret</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>register 方法接受两个参数,第一个是 service worker 文件的路径,第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 <strong>scope</strong> 属性。</p><h4 id="拓展-Service-Worker-作用域"><a href="#拓展-Service-Worker-作用域" class="headerlink" title="拓展 Service Worker 作用域"></a>拓展 Service Worker 作用域</h4><p>scope的默认值为 <code>./</code>(注意,这里所有的相对路径不是相对于页面,而是相对于sw.js脚本的),因此,<code>navigator.serviceWorker.register('/static/home/js/sw.js')</code>代码中的 scope 实际上是<code>/static/home/js</code>,Service Worker也就注册在了<code>/static/home/js</code>路径下,显然无法在<code>/home</code>下生效。</p><p>可以通过添加 <code>Service-Worker-Allowed</code> 响应头的方式来扩展 service worker 的作用域:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// express 扩展 service worker scope</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">serveStatic</span>(<span class="string">`<span class="subst">${sourceRoot}</span>/home`</span>, {</span><br><span class="line"> <span class="attr">maxAge</span>: <span class="number">0</span>,</span><br><span class="line"> <span class="attr">setHeaders</span>: <span class="keyword">function</span> (<span class="params">res, path, stat</span>) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/\/sw\/.+\.js/</span>.<span class="title function_">test</span>(path)) {</span><br><span class="line"> res.<span class="title function_">set</span>({</span><br><span class="line"> <span class="string">'Content-Type'</span>: <span class="string">'application/javascript'</span>,</span><br><span class="line"> <span class="string">'Service-Worker-Allowed'</span>: <span class="string">`/<span class="subst">${sourceRoot}</span>//home`</span>,</span><br><span class="line"> <span class="string">'Cache-control'</span>: <span class="string">'no-store'</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><h4 id="打包工具生成静态资源注册文件"><a href="#打包工具生成静态资源注册文件" class="headerlink" title="打包工具生成静态资源注册文件"></a>打包工具生成静态资源注册文件</h4><p>自己本地调试,可以一个个写进 Service Worker 的注册文件里调试;实际开发中可以借助 gulp / webpack 等打包工具等生成站点静态文件的 sw 注册文件;<br>以 gulp 为例,使用 <a href="https://github.com/GoogleChromeLabs/sw-precache"><code>sw-precache</code></a> 插件生成注册文件:</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></pre></td><td class="code"><pre><span class="line">gulp.<span class="title function_">task</span>(<span class="string">'generate-service-worker'</span>, <span class="keyword">function</span>(<span class="params">callback</span>) {</span><br><span class="line"> swPrecache.<span class="title function_">write</span>(<span class="string">'./service-worker.js'</span>, {</span><br><span class="line"> <span class="attr">staticFileGlobs</span>: [<span class="string">'./build/public'</span> + <span class="string">'/**/*.{js,css,png,jpg,webp,gif,svg,eot,ttf,woff}'</span>],</span><br><span class="line"> <span class="attr">stripPrefix</span>: <span class="string">'./build'</span></span><br><span class="line"> }, callback);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="四、Service-Worker-js-注意事项"><a href="#四、Service-Worker-js-注意事项" class="headerlink" title="四、Service Worker.js 注意事项"></a>四、Service Worker.js 注意事项</h3><ol><li><strong>不要给 service-worker.js 设置不同的名字</strong><br>实际开发过程中,为了避免静态资源缓存,通常的做法是在打包压缩静态资源的时候,在文件名后边加上 MD5 后缀,让浏览器认为这是一个新文件从而重新发起请求,但是这种做法在 service-worker.js 上是不可取的;<br>第一种情况:如果缓存了 html 文件,service-worker.js 的文件因为是在 html 中引入的,所以更改 service-worker.js 的名字并不会更新。<br>第二种情况:只缓存了css,js 文件,未缓存 html 文件;页面引入了新的 service-worker.js ,但是旧版本的 service-worker.js 还在使用中,会导致页面状态有问题。</li><li><strong>不要给 service-worker.js 设置缓存</strong><br>理由和第一点类似,也是为了防止在浏览器需要请求新版本的 sw 时,因为缓存的干扰而无法实现。毕竟我们不能要求用户去清除缓存。因此给 sw 及相关的 JS (例如 sw-register.js,如果独立出来的话)设置 Cache-control: no-store 是比较安全的。</li></ol><h3 id="五、遇到的问题"><a href="#五、遇到的问题" class="headerlink" title="五、遇到的问题"></a>五、遇到的问题</h3><ol><li><strong>接收不到浏览器的fetch事件:</strong><br>原因:静态资源缓存:页面路径不能大于 Service worker 的 scope (<a href="https://juejin.im/post/5b06a7b3f265da0dd8567513#heading-8">详情</a>)</li><li><strong><code>public/*</code> 无法匹配public路径下的所有文件, addCaches 时只能写fileName?</strong><br>原因:service worker 没有通配符 * 这个概念,<code>/sw-test/</code> 这个 path 只是让 sw 寻找缓存时的一个入口,用以区分各个路径的缓存(<a href="https://stackoverflow.com/questions/46830493/is-there-any-way-to-cache-all-files-of-defined-folder-path-in-service-worker">详情</a>);<br>解决方案:service-worker.js 使用官方的 <code>sw-precache</code> 插件生成(<a href="https://stackoverflow.com/questions/46208326/for-serviceworker-cache-addall-how-do-the-urls-work/46213137#46213137">详情</a>);</li><li><strong>如果 service worker 缓存的了全部的 js 和 img 会不会导致 cacheStorage 很占用用户的系统空间?</strong><br>不会,cacheStorage 的值不是无限大的。虽然各个浏览器分配给各站点的 cacheStorrage 的值不一样,同时也受用户设备空间影响。</li></ol><h3 id="落地情况"><a href="#落地情况" class="headerlink" title="落地情况"></a>落地情况</h3><p>还是觉得 Service Worker 最适合在 SPA、文档类应用的等场景使用,才能把离线缓存的优势发挥出来。比如 <a href="https://cn.vuejs.org/">Vue</a> 的官网。<hr/><br><em>2019.4.23</em><br>未落地。主要原因有两点: </p><ol><li>工作中想要使用 Service worker 提供离线缓存服务的是一个负责 APP 内嵌页面的 H5 站点,HTML都是动态渲染的,活动数据是实时性较强,缓存数据意义不大;</li><li>这个站点的页面入口都是几乎都是单独的活动页,没有一个统一 sw 注册的入口;</li></ol><hr/><p><em>2020.3.16</em><br>重新看这篇文章的时候,如果在几个主要的活动入口页引入 sw 的注册文件,那么这几个长期的活动就可以应用 sw 缓存了,但这并没有覆盖全站,所以依然不是好的解决方案。</p><p><em>2024.5.9</em><br>查了下现公司的使用方式,在主页注册,pv/uv 高的页面会使用 Service Worker 拦截请求将响应缓存到 indexDB 中。</p><h3 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h3><p>这部分总结摘录自这篇文章:<a href="https://juejin.im/post/5d26aec1f265da1ba56b47ea#heading-6">Service Worker 从入门到出门</a></p><ul><li>网站功能趋于稳定:频繁迭代的网站似乎不方便加 Service Worker。</li><li>网站需要拥有大量用户:管理后台、OA系统等场景似乎不是很有必要加 Service Worker。</li><li>网站真的在追求用户体验:优先保证网站其他功能正常运行,在此基础上引入 SW 来优化加载体验。</li><li>网站用户体验关乎用户留存:体验优先于功能。</li></ul><p>简单总结:Service Worker 在实际应用前,应考虑成本和收益,不要为了用技术而用技术。</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><ul><li><a href="https://juejin.im/post/5b06a7b3f265da0dd8567513#heading-1">Service Worker ——这应该是一个挺全面的整理</a></li><li><a href="https://www.jianshu.com/p/7eae75f46467">【PWA学习与实践】(9)生产环境中PWA实践的问题与解决方案</a></li><li><a href="https://zhuanlan.zhihu.com/p/51118741">谨慎处理 Service Worker 的更新</a></li><li><a href="https://www.sohu.com/a/197477344_463987">使用 Service Worker 做一个 PWA 离线网页应用</a></li></ul>]]></content>
<categories>
<category> 前端 </category>
<category> PWA </category>
</categories>
<tags>
<tag> Service Worker </tag>
</tags>
</entry>
<entry>
<title>2019年终总结</title>
<link href="/2019/12/31/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2019/12/31/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>2019年终总结(✖️)<br>2020待做清单(✔️)</p><span id="more"></span><ul><li>一月:工作</li><li>二月:回家过年</li><li>三月:PWA 离线缓存初步探索</li><li>四月:多页应用 Service Worker 合适的落地方案持续寻找中(暂缓);前端性能指标 Performance 采集上报</li><li>五月:前端性能指标尝试用 Prometheus 监控系统记录并用 Grafana 做可视化,尝试结果并不好;其他工作中过了一遍 RabbitMQ 事件队列收发流程</li><li>六月:Elasticsearch 日志监控系统初探,目的是根据日志中的某些关键词,发出微信、邮件告警通知</li><li>七月:elk 告警通知进行落地测试调优,微信和邮箱刚开始差点被通知给炸没了…</li><li>八月:告警通知发现了很多不规范的日志,以及某些机器大清早的刷接口…持续使用中…但怎样区分业务的错误日志和代码的错误日志一直是一个问题…</li><li>九月:学习计算机网络相关知识</li><li>十月:Flink 实时计算性能指标探索</li><li>十一月:网络工程师考试</li><li>十二月:灌水</li></ul><p>今年是工作上探索新技术比较多的一年,前端的、不止前端的…虽然最终落地实践并产生结果的并不多,但是多种类型和方向的尝试让我开拓了不少眼界。纵观全年技术探索,leader 在带我们建立 FE 团队的性能监控和异常监控系统方向上做努力。19年没搞完的总结,就是20年的todolist…<br>希望 2020 年的我对技术研究能上升个层次,不仅仅是学习新技术、了解新技术带来什么好处,还要能想到引入新技术带来的一系列后续的优化和落地。</p><p>其他方面,就是缺少的计科知识,出来混,总要还的。报了个网工的考试督促自己系统学习下计科网络相关的知识,然后用实力证明忘的比学的快。</p><p><strong>二月份</strong> 回家过年无聊的时候下了个游戏叫《守望先锋》,从人机模式简单->困难,到快速游戏,再到竞技比赛:体验团队合作游戏的乐趣 + 1,一言不合口吐芬芳 + 10086 。概括起来就是,如果这局赢了,那是队友真强;如果这局输了,那是我正常发挥。不知不觉,这游戏已经玩了一年了…<br><strong>六月底</strong> 终于不堪某水果的电池续航和微信卡顿问题转战 HUAWEI 阵营,不得不说,真香…,但是安卓9下的抓包真是让我脑壳疼了好几天…<br><strong>八月份</strong> gxTodo 总是莫名其妙的卡死崩溃,改用【滴答清单】了。印象笔记也是真的好用…<br><strong>十月份</strong> 找房换房。没想到第一次坐在电动车后面吹着微风晒着暖阳周游北京,居然是自如管家驮着我。带我看房的自如管家是位体型娇小的南方妹子,我站在她面前就像是一个五大三粗的钢铁硬汉…怪不好意思的…<br><strong>十一月份</strong> 软考北京考点在房山…(╯°□°)╯︵┻━┻)</p><p>19年还算充实,虽然相比于18年更宅了一点…最大的希望就是在 2020 年懒癌和拖延症能治好,找到比较感兴趣的东西。还有,别迟到。</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>PWA-Service Worker 小结(一)各类缓存对比</title>
<link href="/2019/12/26/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89%E5%90%84%E7%B1%BB%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94/"/>
<url>/2019/12/26/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89%E5%90%84%E7%B1%BB%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94/</url>
<content type="html"><![CDATA[<p>年底了,总结一下上半年探索的 PWA 的离线缓存技术。顺带总结了一下前端全流程每一步中都可能遇到的缓存,大部分都是概念、名词的理解和说明。涉及到的缓存有:HTTP 缓存、Manifest 缓存、CDN 缓存、Nginx 服务器缓存、Service Worker 缓存。</p><span id="more"></span><p>缓存的好处:<br>存储频繁访问的数据,降低服务器压力;<br>减少网络延迟,加快页面打开速度;</p><h3 id="一、HTTP-缓存"><a href="#一、HTTP-缓存" class="headerlink" title="一、HTTP 缓存"></a>一、HTTP 缓存</h3><h4 id="浏览器缓存机制:"><a href="#浏览器缓存机制:" class="headerlink" title="浏览器缓存机制:"></a><a href="https://www.cnblogs.com/slly/p/6732749.html">浏览器缓存机制</a>:</h4><ol><li><a href="https://segmentfault.com/a/1190000011286027">在未设置相应头缓存字段的时候,只有用户点击“回退”按钮的时候,页面才会从缓存中读取</a>;</li><li><strong>过期机制</strong> :与服务器协商获取。对于浏览器来说,如何缓存一个资源是服务器端制定的策略,服务器对每个资源的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ">HTTP 响应头设置属性和值</a>,自己只负责执行。常用的为以下几种:<ul><li>Expires: 设置过期时间(单位日期),某日期之前都不再询问;浏览器再次命中这个资源,直至XXX时间前都不会发起 HTTP 请求,而是直接从缓存(在硬盘中)读取。<ul><li>如:200 (from cache) 这种缓存速度最快。</li></ul></li><li>Last-Modified: 设置资源上次修改时间(单位日期),每次请求命中资源,都去询问资源是否过期;通过这种缓存方式,无论资源是否发生变更,都会发生至少一来一去的 HTTP 头传输和接收,速度比不上 Expires;<ul><li>如:304,若文件发生变更,则返回200。</li></ul></li><li>Cache-Control:<ul><li>max-age=<seconds> 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒);标准中规定 max-age 的值最大不能超过一年,且以秒为单位,所以值为 31536000;</li><li>no-cache 字面意义“不缓存”。实际机制是对资源仍使用缓存,但每次使用前必须(MUST)向服务器对缓存资源进行验证;</li><li>no-store 不使用任何缓存;</li></ul></li></ul></li><li><strong>验证机制</strong> :服务器返回资源的时候有时会在头信息中携带 __Etag(Entity Tag)__,它可作为浏览器再次请求过程的校验标识。如发现校验标识不匹配,说明资源已经修改或过期,浏览器需要重新获取资源内容。<br>ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 ETag / If-None-Match 值来判断是否命中缓存。在精准度上,Etag 优于 Last-Modified。因为 Etag 是按照内容为资源增加标识,而 Last-Modified 是根据文件最后修改时间判断。</li></ol><img src="/2019/12/26/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89%E5%90%84%E7%B1%BB%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94/%E5%8D%8F%E5%95%86%E7%BC%93%E5%AD%98%E5%91%BD%E4%B8%AD%E8%BF%87%E7%A8%8B.png" class="" title="协商缓存命中过程"><h4 id="常用的缓存策略:"><a href="#常用的缓存策略:" class="headerlink" title="常用的缓存策略:"></a>常用的缓存策略:</h4><ul><li>对于动态生成的 HTML 页面使用 HTTP 头: Cache-Control : no-cache;</li><li>对于静态 HTML 页面使用 HTTP 头: Last-Modified;</li><li>其他所有文件类型都设置 Cache-Control 头,并且在文件内容有所修改都时候修改文件名。</li></ul><h4 id="如何更新文件:"><a href="#如何更新文件:" class="headerlink" title="如何更新文件:"></a>如何更新文件:</h4><p>按照 HTTP 规范,如果修改了请求资源的 Query String,就应该被视为一个新的文件。但是遇到运营商劫持时,会忽略 Query String,遇到这种情况只能修改文件名。</p><h4 id="疑问:"><a href="#疑问:" class="headerlink" title="疑问:"></a>疑问:</h4><p>给 HTML 都设置了 Cache-Control: no-cache; 对 CSS 和 JS都用了 gulp 进行了打包编译处理,每次有变化都会变更文件名;那么此种情况下,是否还需要设置 Last-Modified?<br>直接设置 Cache-Control max-age 或者 Expires 难道不会节省更多 HTTP 请求吗?避免服务器为做出应答返回大量 304。</p><h3 id="二、Manifest-缓存"><a href="#二、Manifest-缓存" class="headerlink" title="二、Manifest 缓存"></a>二、<a href="https://segmentfault.com/a/1190000019395237?utm_source=tag-newest">Manifest 缓存</a></h3><p>manifest 在前端含义很多,常见的四个使用场景如下:</p><ol><li>HTML 标签的 manifest 属性,用来离线缓存 HTML 文档以及资源的;<ul><li>如 <html manifest=”xxx”></html>,由于坑太多,现在已经被废弃;</li></ul></li><li>PWA 的 manifest 功能:将 web 应用程序安装到设备的主屏幕;<ul><li>如 <link rel=”manifest” href=”/manifest.json”>;</li><li>在 manifest.json 中配置应用的图标、名称等信息;通过一系列配置,就可以为 Web App 添加一个图标到手机上,点击图标即可打开站点;</li></ul></li><li>webpack 打包时会生成个 manifest.json 的文件,用来分析打包后的文件;</li><li><a href="https://blog.csdn.net/wangjun5159/article/details/79287881">gulp 处理静态资源时,使用 gulp 的 gulp-rev 插件生成 manifest.json,用来记录源文件与处理后的目标文件的对照</a>。</li></ol><h3 id="三、CDN缓存"><a href="#三、CDN缓存" class="headerlink" title="三、CDN缓存"></a>三、CDN缓存</h3><p>即使为各类资源文件设置了 HTTP 头,当用户手动清除缓存 ,或者由于磁盘容量限制,先缓存的文件被挤出磁盘,此时依旧需要请求资源,为了快速响应用户请求,使用 CDN 加速。CDN的分流作用不仅减少了用户的访问延时,也减少了源站的负载。<br>当用户手动清理本地缓存后,将去请求距离最近的 CDN 边缘节点。<br>CDN 边缘节点缓存策略因服务商不同而不同,但一般会遵循 HTTP 标准协议。通过 HTTP 响应头中的 Cache-Control: max-age 的字段来设置CDN边缘节点数据缓存时间,若数据失效,则向源站发出回源请求,拉取最新的数据;当源站内容有更新的时候,源站主动把内容推到CDN节点。</p><p>各家 CDN 缓存参考:<a href="https://segmentfault.com/a/1190000006673084">https://segmentfault.com/a/1190000006673084</a></p><p>CDN 回源原理:<a href="https://www.jianshu.com/p/e7751ecb6f21">https://www.jianshu.com/p/e7751ecb6f21</a></p><h3 id="四、nginx-服务器缓存"><a href="#四、nginx-服务器缓存" class="headerlink" title="四、nginx 服务器缓存"></a>四、nginx 服务器缓存</h3><p><del>这里又牵扯到了两个地方…就像家用路由器和企业级路由器虽然都叫路由器但是功能完全不一样…</del><br>nginx 大名 负载均衡服务器,它是服务器不是服务;CDN 加速是运营商提供的一种服务….,这俩玩意一点关系都没有。如果网站既使用了 CDN 加速,同时又使用了 Nginx 代理,那么 CDN 的位置相比于 Nginx 服务器更靠近用户。</p><img src="/2019/12/26/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89%E5%90%84%E7%B1%BB%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94/CDN&&Nginx.jpg" class="" title="CDN && Nginx"><p>网站管理者可以通过为网站配置 Nginx 服务器来达到负载均衡的目的, Nginx 可以重写静态资源的 HTTP 头的缓存信息等,也可以用 Nginx 搭建自己的 CDN 节点(原理跟运营商 CDN 差不多,都是转发到合适的机器;只不过 CDN 是将静态资源存在运营商的机器上,Nginx 做 CDN 的话就缓存在自己的机器上)。具体选择时可通过银子的多少来判断是选 CDN 加速,还是 Nginx 搭建 CDN。</p><p>综上,当 Nginx 服务器承载“CDN 加速”的功能时,可通过配置 proxy_cache 将文件缓存到本地的一个目录,缓存命中原理当与 CDN 相同;当 Nginx 服务器不充当 CDN,只是重写静态文件的响应头时,此时跟服务器写命令没差,缓存在浏览器中,原理见浏览器缓存命中机制,不再进行赘述。</p><h3 id="五、Service-Worker-缓存"><a href="#五、Service-Worker-缓存" class="headerlink" title="五、Service Worker 缓存"></a>五、Service Worker 缓存</h3><p>Service Worker 是一个位于浏览器和网络之间的客户端代理,可以拦截、处理流经的 HTTP 请求,使开发者可以从缓存中向 Web 应用提供资源。可以把它看成是用户设备中的缓存提供服务器,功能十分强大。它缓存的文件同样存储在客户端(用户设备)中:</p><img src="/2019/12/26/PWA-Service-Worker-%E5%B0%8F%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89%E5%90%84%E7%B1%BB%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94/web%E5%BA%94%E7%94%A8%E7%BC%93%E5%AD%98%E4%BD%8D%E7%BD%AE%E5%9B%BE.png" class="" title="web应用缓存位置图"><p>Service Worker 是 PWA 实现离线应用的核心技术。它可以:</p><ul><li>让网页可以离线访问;</li><li>让网页在弱网情况,使用缓存快速打开应用,提升体验;</li><li>同时在网络正常的情况下走网络缓存减少请求的带宽; </li><li>对不支持的手机没有影响;</li></ul><p><strong>缓存有各自的优先级,当依次查找缓存且都没有命中的时候,才会去请求网络:</strong></p><ol><li>Service Worker</li><li>Memory Cache</li><li>Disk Cache</li><li>网络请求</li></ol><h3 id="参考资料:"><a href="#参考资料:" class="headerlink" title="参考资料:"></a>参考资料:</h3><ul><li>《web全栈工程师的自我修养》</li><li><a href="https://segmentfault.com/a/1190000011286027">由memoryCache和diskCache产生的浏览器缓存机制的思考</a></li><li><a href="https://segmentfault.com/a/1190000008956069">HTTP强缓存和协商缓存</a></li><li><a href="https://www.jianshu.com/p/b5c805f4e8d1">Etag和Last-Modified</a></li><li><a href="https://segmentfault.com/a/1190000019395237?utm_source=tag-newest">傻傻分不清的Manifest</a></li><li><a href="https://blog.csdn.net/wangjun5159/article/details/79287881">定制修改gulp-rev返回的rev-manifest.json文件</a></li><li><a href="https://segmentfault.com/a/1190000006673084">从HTTP响应头看各家CDN缓存技术</a></li><li><a href="https://www.jianshu.com/p/e7751ecb6f21">简述回源原理和CDN常见多级缓存</a></li><li><a href="https://zhuanlan.zhihu.com/p/96934736">渐进式网页应用(PWA)介绍</a></li></ul>]]></content>
<categories>
<category> 前端 </category>
<category> PWA </category>
</categories>
<tags>
<tag> Service Worker </tag>
</tags>
</entry>
<entry>
<title>Flink 初探</title>
<link href="/2019/11/14/Flink-%E5%88%9D%E6%8E%A2/"/>
<url>/2019/11/14/Flink-%E5%88%9D%E6%8E%A2/</url>
<content type="html"><![CDATA[<p>Apache Flink 是一个分布式处理引擎,在有界或无界数据流上进行有状态的计算。工作时偶然接触到一点点,有些概念虽然有点抽象,但是思路却值得借鉴。本文记录用 Flink 实时求均值、水印生成、以及迟到的数据元触发计算更新等等,是一篇纯探索性文章。<del>用笔记形式记录,以便忘记。</del></p><span id="more"></span><h3 id="Flink-中文官网"><a href="#Flink-中文官网" class="headerlink" title="Flink 中文官网"></a>Flink 中文官网</h3><p><a href="https://flink.apachecn.org/docs/1.7-SNAPSHOT/#/">https://flink.apachecn.org/docs/1.7-SNAPSHOT/#/</a></p><h3 id="一、Flink-简介"><a href="#一、Flink-简介" class="headerlink" title="一、Flink 简介"></a>一、Flink 简介</h3><p>Flink 是一个针对流数据和批数据的分布式处理引擎,代码主要是由 Java 实现,部分代码是 Scala。它可以处理有界的批量数据集、也可以处理无界的实时数据集。对 Flink 而言,其主要处理的场景就是流数据。<br/></p><h3 id="二、流处理和批处理的区别"><a href="#二、流处理和批处理的区别" class="headerlink" title="二、流处理和批处理的区别"></a>二、流处理和批处理的区别</h3><p><strong>批处理</strong> 特点:离线、单次处理的数据量大、处理速度慢、非实时计算。常见的批处理就是数据库深夜定时跑任务,因为批量计算会占用大量资源。<br><strong>流处理</strong> 特点:在线,单次处理数据量小、处理速度快、实时计算。常见的应用场景就是监控、统计、实时推荐等。</p><h3 id="三、学习目标"><a href="#三、学习目标" class="headerlink" title="三、学习目标"></a>三、学习目标</h3><p>用 Flink 消费已有数据源,实时计算数据均值,并允许数据元延迟到来时,重新触发计算。</p><h3 id="四、涉及到的名词概念"><a href="#四、涉及到的名词概念" class="headerlink" title="四、涉及到的名词概念"></a>四、涉及到的名词概念</h3><ol><li><strong>窗口</strong> (Windows):对某段数据流进行统计,即统计区间;Windows 可以是时间驱动的(例如:每30秒)或数据驱动(例如:每100个数据元)。</li><li><strong>时间</strong> (Time):程序中引用的时间;Flink 支持三种时间:事件时间、摄取时间和处理时间。</li><li><strong>算子</strong> (Operator):Flink 内部提供的时间/数据流/数据元的处理函数。</li><li><strong>时间戳</strong> (TimeStamp)/<strong>水印</strong> (WaterMark):使用数据源的时间或者系统时间为到来的数据元加上时间戳;数据流加上水印标记,为了等下个数据元到来时知道该数据元是否应该被包含在当前次计算中。<br><strong>注:Watermark 是随数据产生的,窗口时间现在处于什么位置看 Watermark,只有新产生的一条数据超出窗口长度,这个窗口才会触发计算。(当使用事件时间窗口时,可能会发生数据元迟到的情况,则必须为数据流设置时间戳和水印)</strong></li></ol><h4 id="允许迟到-allowedLateness"><a href="#允许迟到-allowedLateness" class="headerlink" title="允许迟到 allowedLateness"></a>允许迟到 allowedLateness</h4><p>只要应该属于此窗口的第一个数据元到达,就会创建一个窗口,当时间(事件或处理时间)超过其结束时间戳加上用户指定 allowed lateness 时,窗口将被完全删除。<br><strong>allowedLateness 用来设置窗口销毁时间</strong> ,而 waterMark 是用来设置窗口激活时间。当时延迟时间超过 allowedLateness 设置的时间,这个计算窗口就会被销毁,开始下一个窗口,即使被销毁的窗口还没有触发计算。</p><h4 id="窗口函数"><a href="#窗口函数" class="headerlink" title="窗口函数"></a>窗口函数</h4><p>Flink 的窗口函数会暴露出数据流不同状态时的处理函数,具体的高级操作或者运算例如聚合、求均值等函数需要我们自己去实现。<br>例如聚合窗口 <code>stream.aggregate</code> 的参数 AggregateFunction <IN, ACC, OUT>,具有三种的类型:输入类型(IN)、累加器类型(ACC)和输出类型(OUT)。<br>使用 AggregateFunction 求均值(示例代码来自<a href="https://flink.apachecn.org/docs/1.7-SNAPSHOT/#/27?id=window-functions">官网</a>):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">AverageAggregate</span></span><br><span class="line"><span class="keyword">implements</span> <span class="title class_">AggregateFunction</span><Tuple2<String, Long>, Tuple2<Long, Long>, Double> {</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Tuple2<Long, Long> <span class="title function_">createAccumulator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Tuple2</span><>(<span class="number">0L</span>, <span class="number">0L</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Tuple2<Long, Long> <span class="title function_">add</span><span class="params">(Tuple2<String, Long> value, Tuple2<Long, Long> accumulator)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Tuple2</span><>(accumulator.f0 + value.f1, accumulator.f1 + <span class="number">1L</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Double <span class="title function_">getResult</span><span class="params">(Tuple2<Long, Long> accumulator)</span> {</span><br><span class="line"> <span class="keyword">return</span> ((<span class="type">double</span>) accumulator.f0) / accumulator.f1;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Tuple2<Long, Long> <span class="title function_">merge</span><span class="params">(Tuple2<Long, Long> a, Tuple2<Long, Long> b)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Tuple2</span><>(a.f0 + b.f0, a.f1 + b.f1);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">DataStream<Tuple2<String, Long>> input = ...;</span><br><span class="line"></span><br><span class="line">input</span><br><span class="line"> .keyBy(<key selector>)</span><br><span class="line"> .window(<window assigner>)</span><br><span class="line"> .aggregate(<span class="keyword">new</span> <span class="title class_">AverageAggregate</span>());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="五、遇到的问题"><a href="#五、遇到的问题" class="headerlink" title="五、遇到的问题"></a>五、遇到的问题</h3><ul><li>数据流过滤后,只剩下被过滤的数据:<ul><li><strong>SingleOutputStreamOperator</strong> 旁路分支:这个分支用来获取被过滤掉的数据,并不是过滤后的数据。</li></ul></li><li>给数据流设置时间戳之后,迟到的数据没有被抛弃:<ul><li><strong>stream.assignTimestampsAndWatermarks</strong> 定期生成水印:最简单的特殊情况是给定源任务看到的时间戳按升序发生的情况。在这种情况下,当前时间戳始终可以充当水印,因为没有更早的时间戳会到达。且生成的时间戳会覆盖事件原有的,若存在迟到的数据元,用这个方法,则数据不会被抛弃。</li><li><strong>BoundedOutOfOrdernessTimestampExtractor</strong> :Flink 提供此参数为固定数量的迟到者分配时间戳和水印。若有数据元可能迟到的场景,请应用此方法。</li></ul></li><li><a href="https://stackoverflow.com/questions/50114412/flink-watermark-and-triggers-late-elements-not-discarded-on-event-time">设置的水印时间戳,超时告警,但是数据没有被丢弃?</a></li><li><a href="https://developer.aliyun.com/ask/128431?spm=a2c6h.13159736">最新记录没有被统计,只有下一条数据写入时,之前的数据才会被触发统计?</a></li></ul><h3 id="六、数据下沉-Data-Sink"><a href="#六、数据下沉-Data-Sink" class="headerlink" title="六、数据下沉 Data Sink"></a>六、数据下沉 Data Sink</h3><p>Flink 可以自己指定数据源连接器,以及数据下沉(接收)目标。从 Flink 官网上来看连接器支持 Kalfa、Elasticsearch、HDFS、RabbitMQ 等等,公司已有 RabbitMQ 数据源,使用 RabbitMQ sink 接收数时,注意事件消费者不要和事件生产者的队列名不要相同,否则会报错。</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><ul><li><a href="http://www.54tianzhisheng.cn/">http://www.54tianzhisheng.cn/</a></li><li><a href="https://bbs.csdn.net/topics/392567642?list=70723145">Flink 水印机制到底怎么回事</a></li><li><a href="https://www.cnblogs.com/starzy/p/11439997.html">Flink 水印机制</a></li><li><a href="https://blog.csdn.net/aA518189/article/details/85233247">Flink实战–如何使用水印</a></li><li><a href="https://www.cnblogs.com/jiang-it/p/9280946.html">Flink Window 的 Timestamps/Watermarks 和 allowedLateness 的区别</a></li><li><a href="http://wuchong.me/blog/2018/11/07/use-flink-calculate-hot-items/">Flink 零基础实战教程:如何计算实时商品</a></li><li><a href="https://cloud.tencent.com/developer/article/1419588">《从0到1学习Flink》– Flink读取 Kafka 数据写入到 RabbitMQ</a></li></ul>]]></content>
<categories>
<category> 大数据 </category>
</categories>
<tags>
<tag> 大数据 </tag>
<tag> 流处理 </tag>
</tags>
</entry>
<entry>
<title>《计算机网络》- http 部分读书笔记</title>
<link href="/2019/10/22/%E3%80%8A%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E3%80%8B-http-%E9%83%A8%E5%88%86%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<url>/2019/10/22/%E3%80%8A%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E3%80%8B-http-%E9%83%A8%E5%88%86%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<p>《计算机网络(第7版)-谢希仁》http 部分的读书小结和扩展,因为工作中最常打交道的就是这部分了。整本书都很不错,语言通俗易懂;各协议的关系、发展过程以及区别都概括的很好。<br>本文主要概括 HTTP、HTTP1.0、HTTP2.0、HTTPS 的之间的差异。</p><span id="more"></span><h3 id="万维网-WWW"><a href="#万维网-WWW" class="headerlink" title="万维网 WWW"></a>万维网 WWW</h3><p>万维网(World Wide Web)是一个分布式的超媒体系统,是超文本系统的扩充。万维网使用 <strong>统一资源定位符URL(Uniform Resource Locator)</strong> 来标志万维网上的各种文档。</p><h4 id="URL-的格式"><a href="#URL-的格式" class="headerlink" title="URL 的格式"></a>URL 的格式</h4><p>URL 的一般形式由以下四个部分组成:<br> <code><协议>://<主机>:<端口>/<路径></code><br>URL 的<协议>就是指出使用什么协议来获取万维网文档。现在最常用的协议就是 http,其次是 ftp。有些浏览器为方便用户,在输入 URL 时,可以把最前面的“http://”甚至把主机名最前面的“www”省略,然后浏览器替用户把省略的字补上。<br><strong>URL里面的字母不分大小写,但是为了便于阅读,有时故意使用一些大写字母。</strong></p><h4 id="超文本传送协议-HTTP"><a href="#超文本传送协议-HTTP" class="headerlink" title="超文本传送协议 HTTP"></a>超文本传送协议 HTTP</h4><p>HTTP(HyperText Transfer Protocol,超文本传输协议),使用了面向连接的 TCP 作为传输层协议,监听 80 端口,信息是明文传输,其本身是无状态的。</p><ul><li>HTTP1.0(1996)每次请求都会单独建立一个TCP连接,用完关闭;(缺点:每次请求都耗费时间在连接上,<strong>非持续连接</strong> 使服务器开销很重)。</li><li>HTTP1.1(1999)在服务器发送完响应后仍在一段时间内保持这条连接,浏览器和该服务器可以继续在该连接上传送后续的请求报文和响应报文,使用 <strong>持续连接</strong> 。</li><li>HTTP2.0(2015)使用了新的二进制格式、多路复用、以及 header 压缩,性能相对于 HTTP1.x 提升明显。改善了在 HTTP1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞;基于 HTTPS,天生具有安全性,可以避免单纯使用 HTTPS 带来的性能下降。</li></ul><h4 id="影响-HTTP-网络请求的因素"><a href="#影响-HTTP-网络请求的因素" class="headerlink" title="影响 HTTP 网络请求的因素"></a>影响 HTTP 网络请求的因素</h4><p>影响因素主要有两个:带宽和延迟。</p><ul><li><strong>带宽</strong> :在浏览器刚流行的时候,大部分用户是通过拨号来上网,由于受当时的带宽条件的限制,无法使得用户的同时多个请求被处理。同时,当时的服务器的配置也比现在差很多,所以限制每个浏览器的连接数的大小也是有必要的。浏览器默认对同一域下的资源,只保持一定的连接数,阻塞过多的连接,以提高访问速度和解决阻塞问题。不同浏览器的默认值不一样,对于不同的 HTTP 协议其值也不一样。</li></ul><table><thead><tr><th align="center">浏览器</th><th align="center">HTTP 1.1</th><th align="center">HTTP 1.0</th></tr></thead><tbody><tr><td align="center">IE 6、7</td><td align="center">2</td><td align="center">4</td></tr><tr><td align="center">IE 8</td><td align="center">6</td><td align="center">6</td></tr><tr><td align="center">FireFox 2</td><td align="center">2</td><td align="center">8</td></tr><tr><td align="center">FireFox 3</td><td align="center">6</td><td align="center">6</td></tr><tr><td align="center">Safari 3、4</td><td align="center">4</td><td align="center">4</td></tr></tbody></table><p>如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。</p><ul><li><strong>延迟</strong> :<ul><li>浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制(见上表),后续请求就会被阻塞。</li><li>DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。</li><li>建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。</li></ul></li></ul><h4 id="应用层安全协议-HTTPS"><a href="#应用层安全协议-HTTPS" class="headerlink" title="应用层安全协议 HTTPS"></a>应用层安全协议 HTTPS</h4><p>HTTPS 是使用 SSL(Secure Socket Layer,安全套接字层)协议的 HTTP 协议。SSL 作用在 HTTP 和运输层之间,在 TCP 之上建立起一个安全通道,为通过 TCP 传输的应用层数据提供安全保障。<br>HTTPS 监听 TCP 的 443 端口,信息是密文传输。</p>]]></content>
<categories>
<category> 计算机相关知识 </category>
</categories>
<tags>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>z-index 小结</title>
<link href="/2019/08/05/z-index%E5%B0%8F%E7%BB%93/"/>
<url>/2019/08/05/z-index%E5%B0%8F%E7%BB%93/</url>
<content type="html"><![CDATA[<p> 当 z-index 多个规则多个层级共同作用时,展现的效果往往跟自己的想法有很大差异,论 CSS 基本功的重要性。本文总结了 CSS 层叠的特性、基本准则和创建条件,内容大多为张鑫旭大神的《CSS世界》读书小结。</p><span id="more"></span><h3 id="层叠的基本概念"><a href="#层叠的基本概念" class="headerlink" title="层叠的基本概念"></a>层叠的基本概念</h3><ul><li>层叠上下文(stacking context):当前元素所处的层叠规则,即元素所处的 z 轴。一个页面中,层叠上下文不止一个。</li><li>层叠水平(stacking level):同一个层叠上下文中元 素在 z 轴上的显示等级。</li><li>层叠顺序(stacking order):<ul><li>background/border 指在同一层叠上下文元素的边框和背景色。</li><li>inline水平盒子指的是包括inline/inline-block/inline-table元素的“层叠顺序”,它们都是同等级别的。</li><li>内联元素的层叠顺序要比浮动元素和块状元素都高,是因为float元素在起始时是作为布局元素存在的。由于“内容”的重要性远大于“装饰”和“布局”,所以内容元素层叠顺序比较高,详情见下图:</li></ul></li></ul><img src="/2019/08/05/z-index%E5%B0%8F%E7%BB%93/stacking-order.png" class="层叠顺序图"><h3 id="z-index"><a href="#z-index" class="headerlink" title="z-index"></a>z-index</h3><p><strong>z-index 属性只有和定位元素(position 不为 static 的元素)在一起的时候才有作用,可以是正数也可以是负数。在同一层叠上下文中,数值越大层级越高。</strong> 在CSS3中,z-index 已经并非只对定位元素有效,flex 盒子的子元素 也可以设置 z-index 属性。</p><h4 id="层叠准则"><a href="#层叠准则" class="headerlink" title="层叠准则"></a>层叠准则</h4><ol><li>谁大谁上:在同一个层叠上下文领域,具有明显的层叠水平标识的时候,层叠水平值大的那一个覆盖小的那一个,例如 z-index 属性值。</li><li>后来居上:当元素的层叠水平一致、层叠顺序相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素。</li></ol><h4 id="层叠上下文的特性"><a href="#层叠上下文的特性" class="headerlink" title="层叠上下文的特性"></a>层叠上下文的特性</h4><ul><li>层叠上下文的层叠水平要比普通元素高。</li><li>层叠上下文可以阻断元素的混合模式。</li><li>层叠上下文可以嵌套,内部层叠上下文及其所有子元素均受制于外部的“层叠上下文”。</li><li>每个层叠上下文和兄弟元素独立,也就是说,当进行层叠变化或渲染的时候,只需要考虑后代元素。</li><li>每个层叠上下文是自成体系的,当元素发生层叠的时候,整个元素被认为是在父层叠上下文的层叠顺序中。</li></ul><h4 id="页面中的层叠上下文"><a href="#页面中的层叠上下文" class="headerlink" title="页面中的层叠上下文"></a>页面中的层叠上下文</h4><ul><li><strong>根层叠上下文</strong> :页面根元素具有层叠上下文,称为“根层叠上下文”。故页面中所有的元素至少处于一个层叠上下文中。</li><li><strong>定位元素与传统层叠上下文</strong> :对于 position 值为 relative/absolute 以及 Firefox/IE 浏览器(不包括 Chrome 浏览 器)下含有 position:fixed 声明的定位元素,当其 z-index 值不是 auto 的时候,会创建层叠上下文(<strong>z-index 一旦变成数值,即使是 0,也创建一个层叠上下文</strong>)。</li><li><strong>CSS3新属性的层叠上下文</strong> :<ul><li>元素为 flex 布局元素(父元素 display:flex | inline-flex),同时 z-index 值不是 auto;</li><li>元素的 opacity 值不是 1;</li><li>元素的 transform 值不是 none;</li><li>元素 mix-blend-mode 值不是 normal;</li><li>元素的 filter 值不是 none;</li><li>元素的 isolation 值是 isolate;</li><li>元素的 will-change 属性值为上面 2~6 的任意一个(如 will-change:opacity、will-chang:transform 等);</li><li>元素的-webkit-overflow-scrolling 设为 touch;</li></ul></li></ul><h4 id="CSS3-属性与-z-index-的兼容性问题"><a href="#CSS3-属性与-z-index-的兼容性问题" class="headerlink" title="CSS3 属性与 z-index 的兼容性问题"></a>CSS3 属性与 z-index 的兼容性问题</h4><ul><li>Safari 3D变换会忽略 z-index<a href="https://blog.csdn.net/sherry_0706/article/details/52593888">(解决方案)</a></li></ul>]]></content>
<categories>
<category> 前端 </category>
<category> CSS </category>
</categories>
<tags>
<tag> CSS </tag>
</tags>
</entry>
<entry>
<title>Prometheus 监控应用性能</title>
<link href="/2019/07/15/Prometheus%E7%9B%91%E6%8E%A7%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD/"/>
<url>/2019/07/15/Prometheus%E7%9B%91%E6%8E%A7%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD/</url>
<content type="html"><![CDATA[<p>   Prometheus 是一个开源的监控系统。它可以自动化的监听应用各性能指标的变化情况,并发出报警信息。了解它目的,是想把前端页面的性能指标记录到公司的 Prometheus 监控系统上,利用它监听前端页面各类异常。</p><span id="more"></span><h3 id="一、Prometheus-系统简介"><a href="#一、Prometheus-系统简介" class="headerlink" title="一、Prometheus 系统简介"></a>一、Prometheus 系统简介</h3><p><a href="https://prometheus.io/">Prometheus</a> 是一个开源的服务监控系统,社区资源和开发者都很活跃。其主要原理是通过 HTTP 协议从远程的机器收集数据并存储在本地的<a href="https://www.cnblogs.com/aiandbigdata/p/10052335.html">时序数据库</a>上。Prometheus 通过安装在远程机器上的 exporter (数据暴露)插件来收集监控数据。</p><h4 id="Prometheus-特点"><a href="#Prometheus-特点" class="headerlink" title="Prometheus 特点"></a>Prometheus 特点</h4><p>Prometheus 本身也是一个时序数据库,它通过 HTTP 的方式获取时序数据。Prometheus 自身的查询语言 PromQL 可多维度的查询并实时计算指标的值。通过 PromQL 提供的计算方法,可以自定义数据可视化的指标,以及报警临界值。它有四种数据类型,可针对不同场景使用不同数据类型。</p><h4 id="Prometheus-系统的组成部分"><a href="#Prometheus-系统的组成部分" class="headerlink" title="Prometheus 系统的组成部分"></a>Prometheus 系统的组成部分</h4><img src="/2019/07/15/Prometheus%E7%9B%91%E6%8E%A7%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD/architecture.png" class="" title="Prometheus 架构"><p>在监控流程中,主要由三个部分组成:被监控的应用暴露性能指标(exporter),promethues 应用采集性能指标(collector),数据可视化分析界面(web UI)。详情参照下述:</p><ol><li>Prometheus server: 用于抓取数据,并存储到时序数据库;</li><li>Prometheus exporter: 安装在监控目标的上,为 Prometheus server 提供数据抓取的接口;</li><li>Prometheus web UI: 提供数据可视化分析界面;</li><li>Alertmanager: 用于处理警报;</li><li>Pushgateway: 用于 job 推送;</li></ol><h3 id="二、Prometheus-监控流程图"><a href="#二、Prometheus-监控流程图" class="headerlink" title="二、Prometheus 监控流程图"></a>二、Prometheus 监控流程图</h3><img src="/2019/07/15/Prometheus%E7%9B%91%E6%8E%A7%E5%BA%94%E7%94%A8%E6%80%A7%E8%83%BD/flowChart.png" class="" title="Prometheus flow chart"><h4 id="Promethues-server"><a href="#Promethues-server" class="headerlink" title="Promethues server"></a>Promethues server</h4><p>主要负责数据采集和存储,提供 PromQL 查询语言的支持。可以通过 Prometheus 的 <code>.yml</code>文件中的<code>scrape_config</code> <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config">(字段详情)</a>来配置要抓取的应用指标地址。同时,Promethues 也会监控自身的健康情况,默认将指标暴露在自身的 <code>http://localhost:9090/metrics</code>。</p><h4 id="Promethues-exporter"><a href="#Promethues-exporter" class="headerlink" title="Promethues exporter"></a>Promethues exporter</h4><p>参照<a href="https://prometheus.io/docs/instrumenting/clientlibs/">官方推荐的插件列表</a>,由于本次监听的站点是 NodeJS 站点,所以选择 <a href="https://github.com/siimon/prom-client">prom-client</a> 作为 exporter。<br>注意:被监听的应用需要暴露指标接口供 server 抓取。</p><h4 id="Prometheus-web-UI"><a href="#Prometheus-web-UI" class="headerlink" title="Prometheus web UI"></a>Prometheus web UI</h4><p>Grafana 可以连接多种类型的库,选择 Promethues 即可,默认监听 Promethues server 的9090<code>/metrics</code> 路径。</p><h3 id="三、NodeJS-应用性能监控"><a href="#三、NodeJS-应用性能监控" class="headerlink" title="三、NodeJS 应用性能监控"></a>三、NodeJS 应用性能监控</h3><p>使用 <a href="https://github.com/siimon/prom-client">prom-client</a> 和 <a href="https://github.com/SimenB/node-prometheus-gc-stats">node-prometheus-gc-stats</a> 收集 NodeJS 的性能指标。</p><ul><li>prom-client:收集服务端性能指标</li><li>node-prometheus-gc-stats:垃圾回收相关指标统计</li></ul><p>(prom-client 相当于一个exporter,将默认的指标暴露在 /metrics 接口,之后 Promethues service会根据 <code>.yml</code> 配置中的采集时间定期来这个接口采集数据信息,然后 web UI Grafana 再跟 Promethues server 进行同步)</p><h4 id="prom-client-文档"><a href="#prom-client-文档" class="headerlink" title="prom-client 文档"></a>prom-client 文档</h4><p>一共支持四种数据格式:Histogram、Summary、Gauges 、Counters:</p><ul><li><strong>Histogram(柱状图)</strong> 统计数据的分布情况(比如 <code>Http_response_time</code> 的时间分布)</li><li><strong>Summary(摘要)</strong> 主要用于表示一段时间内数据采样结果(请求持续的时间或响应大小)</li><li><strong>Gauges(仪表盘)</strong> 最简单的度量指标,监测瞬间状态(监控硬盘容量或者内存的使用量)</li><li><strong>Counters(计数器)</strong> 从数据量0开始累积计算,在理想状态下只能是永远的增长不会降低</li></ul><p>常用的采集方法:</p><ul><li>collectDefaultMetrics() 返回 Promethues 的默认推荐指标,默认 10s 探测一次</li><li>AggregatorRegsitry 聚合注册器:监听集群的性能指标(主进程和其产生的子进程)<ul><li>clusterMetrics() 返回默认指标</li><li><a href="https://github.com/siimon/prom-client/issues/257">抓取所有进程的 metrics 只能在主进程上抓取,在子进程上获取不到 metrics</a></li></ul></li></ul><p>收集到指标后,就可以利用 PromQL 进行计算了,计算时注意 PromQL <strong>即时向量</strong> 和 <strong>范围向量</strong> 两种向量的区别和转换:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">// 计算每分钟垃圾回收bytes数</span><br><span class="line">delta(nodejs_gc_reclaimed_bytes_total{gctype="Scavenge"}[1m])</span><br><span class="line"></span><br><span class="line">// 计算个页面5min以内的DomReady均值</span><br><span class="line">delta(FE_Timing_Performance_domReady_sum[5m])/delta(FE_Timing_Performance_domReady_count[5m])</span><br></pre></td></tr></table></figure><h4 id="※example-prometheus-nodejs"><a href="#※example-prometheus-nodejs" class="headerlink" title="※example-prometheus-nodejs"></a>※<a href="https://github.com/RisingStack/example-prometheus-nodejs">example-prometheus-nodejs</a></h4><p>   这个 demo 是一个完整的 prom-client + Promethues + grafana 监控示例,有助于理解整个监控流程。</p><h3 id="四、前端异常记录实践结论"><a href="#四、前端异常记录实践结论" class="headerlink" title="四、前端异常记录实践结论"></a>四、前端异常记录实践结论</h3><p><strong>并不推荐使用 Prometheus 系统来记录前端页面性能等信息。</strong></p><ol><li>从指标上来看,应用的基本性能指标:吞吐量、内存使用量、每秒请求数、请求平均耗时等。这些几乎都是“瞬时”值(由于时间窗口小,可看做瞬时值),而前端性能指标并不是“瞬时”,它更偏向于一段时间内的表现情况(时间窗口大)。Prometheus 系统主要用于监听应用的性能,它的数据类型更多是为应用服务。</li></ol><ul><li>应用性能特点:一个应用,多个指标;</li><li><a href="https://kuro-p.github.io/2019/07/11/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7-Performance/#more">前端页面的性能指标</a>特点:一个页面,多个指标。多个页面。</li></ul><ol start="2"><li>从数据类型上来看:<ul><li>Gauges:不可以用来记录前端的性能表现。因为 Gauge 记录的某一刻的瞬时值,如果用来记录时间,则每次数据都会被最后访问的那名用户刷新;</li><li>Counter:计数器虽然可以记录前端某个页面的访问次数,但若页面路由中携带参数,或者结尾带时间戳,则会生成多个重复页面的 Counter,遇到爬虫还会生成大量无用路径,表现并不好;</li><li>Histogram、Summary:可以记录多个页面,多个指标;Histogram 和 Summary 很相似,只不过 Histogram 记录原始值,Summary 记录指标的各个占比。</li></ul></li><li>从可视化 Web UI 上来看:<br>公司 Prometheus 系统默认使用的可视化 UI 是 Grafana。之前尝试用 Histogram 来记录前端各页面的性能表现,在 Grafana 中用折线图可视化数据。一个指标对应一个折线图,但由于页面路由多个,导致各个折线图中折线过多难以分辨;若取所有页面该指标的均值或者最大值来展示,又不知道峰值是哪个页面产生的。</li></ol><p><strong>综上可以看出,Prometheus 可以记录前端性能指标,但是受数据类型制约,它并不是最合适的。</strong></p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="https://prometheus.io/">Prometheus 官网</a></li><li><a href="https://blog.csdn.net/polo2044/article/details/83277299">Prometheus 的数据类型介绍</a></li><li><a href="https://www.colabug.com/227611.html">prom-client 监控示例</a></li><li><a href="https://www.cnblogs.com/aguncn/p/9920545.html">如何区分 Prometheus 中 Histogram 和 Summary 类型的 metrics?</a></li></ul>]]></content>
<categories>
<category> 监控 </category>
</categories>
<tags>
<tag> 性能监控 </tag>
</tags>
</entry>
<entry>
<title>使用 Performance API 进行前端性能监控</title>
<link href="/2019/07/11/%E4%BD%BF%E7%94%A8%20Performance%20APi%20%E8%BF%9B%E8%A1%8C%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7/"/>
<url>/2019/07/11/%E4%BD%BF%E7%94%A8%20Performance%20APi%20%E8%BF%9B%E8%A1%8C%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7/</url>
<content type="html"><![CDATA[<p>  平常只在测试环境测过前端页面性能,到了真实环境用户的手机上,页面性能的具体表现却未曾了解。H5 新增的 Performance API 可以精确的测量网页性能。使开发者可以通过数据上报的方式收集线上 H5 页面的性能表现,以合理优化页面性能短板,提升用户体验。</p><span id="more"></span><h3 id="前端性能监控指标"><a href="#前端性能监控指标" class="headerlink" title="前端性能监控指标"></a>前端性能监控指标</h3><ul><li><strong>白屏时间</strong>: 从打开网站到有内容渲染出来的时间节点</li><li><strong>首屏时间</strong>: 首屏内容渲染完毕的时间节点</li><li><strong>domReady 时间</strong>: 用户可操作的时间节点</li><li><strong>onload 时间</strong>: 总下载时间</li></ul><h3 id="Performance-API-简介"><a href="#Performance-API-简介" class="headerlink" title="Performance API 简介"></a>Performance API 简介</h3><p>  <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Performance">Performace</a>是 HTML5 的新特性之一,该接口会返回当前页面性能相关的信息。Performance 对象一共提供了4个属性:</p><ul><li><strong>navigation</strong>: 包含页面加载、刷新、重定向情况</li><li><strong>timing</strong>: 包含了各种与浏览器性能有关的时间数据</li><li><strong>memory</strong>: 返回JavaScript对内存的占用</li><li><strong>timeOrigin</strong>: 返回性能测量开始时的时间的高精度时间戳</li></ul><p>本文主要讨论 Performance 的 timing 对象以及其他几种统计指标。</p><h4 id="performance-timing"><a href="#performance-timing" class="headerlink" title="performance.timing"></a>performance.timing</h4><p>timing 对象提供了各种与浏览器处理相关的时间数据(<a href="https://segmentfault.com/a/1190000014479800">详细</a>),各时间节点可参照下图: </p><img src="/2019/07/11/%E4%BD%BF%E7%94%A8%20Performance%20APi%20%E8%BF%9B%E8%A1%8C%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7/performance.png" class=""><p>其中常用的几项计算指标如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> timing = performance.<span class="property">timing</span>;</span><br><span class="line"><span class="keyword">var</span> times = {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 请求耗时</span></span><br><span class="line">times.<span class="property">request</span> = timing.<span class="property">responseEnd</span> - timing.<span class="property">requestStart</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">times.<span class="property">ttfb</span> = timing.<span class="property">responseStart</span> - timing.<span class="property">navigationStart</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">times.<span class="property">domReady</span> = timing.<span class="property">domComplete</span> - timing.<span class="property">responseEnd</span> || <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//dom渲染时间</span></span><br><span class="line">times.<span class="property">domRender</span> = timing.<span class="property">domContentLoadedEventEnd</span> - timing.<span class="property">navigationStart</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">times.<span class="property">onload</span> = timing.<span class="property">loadEventEnd</span> - timing.<span class="property">navigationStart</span> || <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// DNS解析时间</span></span><br><span class="line">times.<span class="property">lookupDomain</span> = timing.<span class="property">domainLookupEnd</span> - timing.<span class="property">domainLookupStart</span> || <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP建立时间</span></span><br><span class="line">times.<span class="property">tcp</span> = timing.<span class="property">connectEnd</span> - timing.<span class="property">connectStart</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">times.<span class="property">now</span> = performance.<span class="title function_">now</span>();</span><br></pre></td></tr></table></figure><h3 id="performance-now"><a href="#performance-now" class="headerlink" title="performance.now()"></a>performance.now()</h3><p>返回当前网页从performance.timing.navigationStart到当前时间之间的微秒数</p><h3 id="performance-getEntries"><a href="#performance-getEntries" class="headerlink" title="performance.getEntries()"></a>performance.getEntries()</h3><p>浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。</p><h3 id="数据埋点及上报方式"><a href="#数据埋点及上报方式" class="headerlink" title="数据埋点及上报方式"></a>数据埋点及上报方式</h3><h4 id="利用-标签的-src-属性上报"><a href="#利用-标签的-src-属性上报" class="headerlink" title="利用 <script> 标签的 src 属性上报"></a>利用 <script> 标签的 src 属性上报</h4><p>工作中采用的埋点方式是脚本引入。该脚本负责收集浏览器性能指标信息,并生成一个 <script> 节点,将指标信息拼接成 url param 的形式,通过 <script> 标签的 src 属性发起请求,将数据上报到服务器。</p><h4 id="利用-标签的-src-属性上报-1"><a href="#利用-标签的-src-属性上报-1" class="headerlink" title="利用 <img> 标签的 src 属性上报"></a>利用 <img> 标签的 src 属性上报</h4><p>谷歌和百度的都是用的1x1 像素的透明 gif 图片,其优点如下:</p><ul><li>跨域友好</li><li>执行过程无阻塞</li><li>使用image时,部分浏览器内页面关闭不会影响数据上报</li><li>gif 的最低合法体积最小(最小的 bmp 文件需要74个字节,png 需要67个字节,而合法的 gif,只需要43个字节)</li></ul><h4 id="利用-HTML5-Beacon-API-进行数据上报"><a href="#利用-HTML5-Beacon-API-进行数据上报" class="headerlink" title="利用 HTML5 Beacon API 进行数据上报"></a>利用 HTML5 Beacon API 进行数据上报</h4><p>Beacon API 允许开发者发送少量错误分析和上报的信息,它的特点很明显:</p><ul><li>在空闲的时候异步发送统计,不影响页面诸如 JS、CSS Animation 等执行</li><li>即使页面在 unload 状态下,也会异步发送统计,不影响页面过渡/跳转到下跳页</li><li>可被客户端优化发送,尤其在 Mobile 环境下,可以将 Beacon 请求合并到其他请求上,一同处理</li></ul><h3 id="前端性能监控系统"><a href="#前端性能监控系统" class="headerlink" title="前端性能监控系统"></a>前端性能监控系统</h3><p>在 github 上发现的比较好的工具,可以用来参考:</p><ul><li>数据上报插件: <a href="https://github.com/wangweianger/web-report-sdk">web-report-sdk</a></li><li>前端性能监控UI: <a href="http://hubing.online:8083/#/sys/5cb68708838abf131c718ed1/index">web-monitoring</a></li></ul><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p><a href="https://blog.csdn.net/weixin_42284354/article/details/80416157">前端性能监控-window.performance</a><br><a href="http://javascript.ruanyifeng.com/bom/performance.html">Performance API-ruanyifeng</a><br><a href="https://segmentfault.com/a/1190000014479800">初探Performance API</a><br><a href="https://juejin.im/entry/5a179332f265da431b6ce39c">前端全(无)埋点之页面停留时长统计</a></p>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 性能监控 </tag>
</tags>
</entry>
<entry>
<title>IP地址和子网划分</title>
<link href="/2019/06/02/IP%E5%9C%B0%E5%9D%80%E5%92%8C%E5%AD%90%E7%BD%91%E5%88%92%E5%88%86/"/>
<url>/2019/06/02/IP%E5%9C%B0%E5%9D%80%E5%92%8C%E5%AD%90%E7%BD%91%E5%88%92%E5%88%86/</url>
<content type="html"><![CDATA[<p>计算机知识补全计划:ip地址、子网掩码相关笔记。</p><span id="more"></span><p>MAC地址:决定下跳给哪个设备<br>IP地址:决定数据最终到达的计算机<br>子网掩码:用来判断两台机器的ip地址是否处于同一网段<br><strong><a href="https://www.bilibili.com/video/av17905625">课程链接</a></strong></p><p>一、IP地址<br> IP地址是由32位二进制组成的,写成十进制,每四位以逗号分隔:如<code>192.168.30.10</code>。IP地址分为两部分,一部分是网络部分,另一部分是主机部分;在同一网段的计算机,网络部分一样,主机部分不一样,<code>子网掩码</code>就是用来区分主机与网段的。</p><ol><li>子网掩码<br>两台计算机在通信之前,首先需要判断需要进行通信的设备与当前的设备是否处于同一网段之中:<code>IP地址</code>和<code>子网掩码</code>做<code>与运算</code>得出的结果就是网络部分,网络部分相同则处于同一网段。<br>例如:A计算机想与B计算机通信,首先A将A的子网掩码和A的IP地址进行与运算,再将A的子网掩码和B的IP地址进行运算,若二者结果相同,则处于同一网段。</li><li>IP地址的分类<ul><li>A类:1-127 缺省子网掩码:255.0.0.0</li><li>B类:128-191 缺省子网掩码:255.255.0.0</li><li>C类:192-223 缺省子网掩码:255.255.255.0</li><li>D类(组播):224-239 缺省子网掩码:无</li><li>E类(研究):240-255 缺省子网掩码:无<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[0----------128-----192---224-240-255]</span><br></pre></td></tr></table></figure>在分配IP地址时注意:<br><code>xxx.0.0.0</code>:全0表示这个子网的网络号,不可用;<br><code>xxx.255.255.255</code>:全1表示这个子网的广播地址,代表网段内所有计算机,可跨网段,不可用。(注意,若全为255,则只能发送给本网段的机器,不能跨网段)<br>例如C类地址,能设置的主机号只有2-254,一般路由器的ip地址为该网段内的第一个或者最后一个,避免冲突。</li></ul></li><li>保留地址<ul><li>保留的私网地址(不在公网上,相互之间不能通信(内网)):<ul><li>A 10.0.0.0 – 10.255.255.255</li><li>B 172.16.0.0 – 172.31.255.255</li><li>C 192.168.0.0 – 192.168.255.255</li></ul></li><li>本地环回地址<ul><li>127.0.0.1 本机</li><li>169.254.0.0 断网地址</li><li>224.0.0.1 特殊的组播地址,代表所有主机地址</li></ul></li></ul></li></ol>]]></content>
<categories>
<category> 计算机相关知识 </category>
</categories>
<tags>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>微信授权流程</title>
<link href="/2019/05/30/%E5%BE%AE%E4%BF%A1%E6%8E%88%E6%9D%83%E6%B5%81%E7%A8%8B/"/>
<url>/2019/05/30/%E5%BE%AE%E4%BF%A1%E6%8E%88%E6%9D%83%E6%B5%81%E7%A8%8B/</url>
<content type="html"><![CDATA[<p>在公司制作 H5 页面的时候,有这样一个场景:在微信打开 H5 页面,已经绑定微信的用户直接免密登录,未绑定的用户使用传统账号密码的登录方式。其中免密登录的核心一环就是走一个微信授权流程,原理不难,弄懂它的流程比较重要。</p><span id="more"></span><h3 id="微信网页授权官方文档"><a href="#微信网页授权官方文档" class="headerlink" title="微信网页授权官方文档"></a><a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842">微信网页授权官方文档</a></h3><p>当用户在微信中访问第三方网页的时候,公众号可以通过微信网页授权机制来获取用户基本信息。在授权过程中,<code>openid</code>作为用户的唯一标识,同一个用户不同公众号的<code>openid</code>不同,反之亦然。</p><ul><li>在发起授权前,需要到微信公众平台开发的官网设置授权回调的域名;</li><li>openid: 用户唯一标识;</li><li>code: <code>code</code>作为换取<code>access_token</code>的票据,每次用户授权带上的<code>code</code>将不一样,<code>code</code>只能使用一次,5分钟未被使用自动过期;</li><li>access_token: 网页授权接口调用凭证;</li><li>scope:用户授权的作用域;<ul><li>以<code>snsapi_base</code>为<code>scope</code>发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页(往往是业务页面)。用户无感知。</li><li>以<code>snsapi_userinfo</code>为<code>scope</code>发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。</li></ul></li></ul><h3 id="微信免密登录流程图"><a href="#微信免密登录流程图" class="headerlink" title="微信免密登录流程图"></a>微信免密登录流程图</h3><img src="/2019/05/30/%E5%BE%AE%E4%BF%A1%E6%8E%88%E6%9D%83%E6%B5%81%E7%A8%8B/flowChart.png" class="">]]></content>
<categories>
<category> 微信开发相关 </category>
<category> 其他小结 </category>
</categories>
<tags>
<tag> 微信 </tag>
<tag> 微信授权 </tag>
</tags>
</entry>
<entry>
<title>正则表达式学习笔记</title>
<link href="/2019/04/13/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2019/04/13/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<p>把2017年的笔记整理一下,方便查找。记录了js正则表达式常用的概念、字符、以及方法。</p><span id="more"></span> <h3 id="一、RegExp对象"><a href="#一、RegExp对象" class="headerlink" title="一、RegExp对象"></a>一、RegExp对象</h3><p>JavaScript通过通过内置对象RegExp支持正则表达式,有两种方法实例化RegExp对象:</p><ol><li>字面量:<code>var reg = /文本/g</code></li><li>构造函数:<code>var reg = new RegExp("\\bis\\b\", 'g')</code></li></ol><h3 id="二、修饰符"><a href="#二、修饰符" class="headerlink" title="二、修饰符"></a>二、修饰符</h3><table><thead><tr><th>修饰符</th><th>含义</th></tr></thead><tbody><tr><td>g: glogbal</td><td>全文搜索(默认搜索到第一个匹配停止)</td></tr><tr><td>i: ignore case</td><td>忽略大小写(默认大小写敏感)</td></tr><tr><td>m: multiple lines</td><td>多行搜索</td></tr></tbody></table><h3 id="三、元字符"><a href="#三、元字符" class="headerlink" title="三、元字符"></a>三、<a href="https://www.runoob.com/regexp/regexp-metachar.html">元字符</a></h3><p>如(*+$^.|(){}[])等,指在正则表达式中有特殊含义的非字母字符。一般情况下正则表达式的一个字符对应字符串的一个字符。<br><strong>匹配<code>+</code>等特殊字符,可先转义,再匹配,如<code>string.replace(/[\+]/g, "")</code></strong></p><ol><li>普通类<code>[]</code><br>若要对应多个字符,可用元字符<code>[]</code>来构建一个简单的类,所谓类是指符合某些特性的对象,一个泛指,而不是特指某个字符。 <figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'a1b2c3d4'</span>.<span class="title function_">replace</span>(<span class="regexp">/[abc]/gi</span>, <span class="string">'X'</span>) <span class="comment">//"X1X2X3X4"</span></span><br></pre></td></tr></table></figure></li><li>反向类<code>[^xx]</code><br><code>[^..]</code>使用元字符<code>^</code>创建反向类,即不属于某类的内容。 <figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'a1b2c3d4'</span>.<span class="title function_">replace</span>(<span class="regexp">/[^abc]/gi</span>, <span class="string">'X'</span>) <span class="comment">//"aXbXcXXX"</span></span><br></pre></td></tr></table></figure></li><li>范围类<code>[x-x]</code><br>使用<code>[a-z]</code>来连接两个字符,表示从a到z的任意字符 (包含a,z本身)。在<code>[]</code>组成类的内部可以连写<code>[a-zA-Z]</code></li><li>预定义类<br>正则表达式提供预定义类来匹配常见字符类</li></ol><table><thead><tr><th>字符</th><th>等价类</th><th>含义</th></tr></thead><tbody><tr><td>.</td><td>[^\r\n]</td><td>除回车符和换行符之外的所有字符</td></tr><tr><td>\d</td><td>[0-9]</td><td>数字字符</td></tr><tr><td>\D</td><td>[^0-9]</td><td>非数字字符</td></tr><tr><td>\s</td><td>[\t\n\f\r]</td><td>空白符</td></tr><tr><td>\S</td><td>[^\t\n\f\r]</td><td>非空白符</td></tr><tr><td>\w</td><td>[a-zA-Z_0-9]</td><td>单词字符(字母、数字、下划线)</td></tr><tr><td>\W</td><td>[^a-zA-Z_0-9]</td><td>非单词字符</td></tr><tr><td>5. 边界</td><td></td><td></td></tr><tr><td>常见的边界匹配字符如下</td><td></td><td></td></tr></tbody></table><table><thead><tr><th>字符</th><th>含义</th></tr></thead><tbody><tr><td>^</td><td>以xxx开始(注意<code>^</code>要写在字符前面)</td></tr><tr><td>$</td><td>以xxx结束(注意<code>$</code>要写在字符后面)</td></tr><tr><td>\b</td><td>单词边界</td></tr><tr><td>\B</td><td>非单词边界</td></tr></tbody></table><figure class="highlight javascript"><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="string">'@123@abc@'</span>.<span class="title function_">replace</span>(<span class="string">'^@'</span>, <span class="string">'Q'</span>); <span class="comment">//"Q123@abc@" </span></span><br><span class="line"><span class="string">'@123@abc@'</span>.<span class="title function_">replace</span>(<span class="string">'@$'</span>, <span class="string">'Q'</span>); <span class="comment">//"Q123@abcQ" </span></span><br></pre></td></tr></table></figure><ol start="6"><li>量词<br>匹配连续出现多次的字符串(仅作用于紧挨着它的字符)</li></ol><table><thead><tr><th>字符</th><th>含义</th></tr></thead><tbody><tr><td>?</td><td>出现零或一次(最多出现一次)</td></tr><tr><td>+</td><td>出现一次或多次(至少出现一次)</td></tr><tr><td>*</td><td>出现零次或多次(任意次)</td></tr><tr><td>{n}</td><td>出现n次</td></tr><tr><td>{n,m}</td><td>出现n到m次</td></tr><tr><td>{n,}</td><td>至少出现n次</td></tr><tr><td>{0,m}</td><td>至多出现m次</td></tr></tbody></table><ul><li>贪婪模式<br>尽可能多的匹配,直到匹配失败<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'12345678'</span>.<span class="title function_">replace</span>(<span class="regexp">/\d{3,6}/g</span>,<span class="string">'X'</span>); <span class="comment">//"X78"</span></span><br></pre></td></tr></table></figure></li><li>非贪婪模式(在量词后边加<code>?</code>)<br>尽可能少的匹配,一旦匹配成功,不再继续尝试;匹配前面的子表达式零次或一次,等价于 {0,1}<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'12345678'</span>.<span class="title function_">replace</span>(<span class="regexp">/\d{3,6}?/g</span>, <span class="string">'X'</span>); <span class="comment">//"XX78"</span></span><br></pre></td></tr></table></figure></li></ul><ol start="7"><li>分组<br>使用<code>()</code>可以达到分组的功能,使量词作用于分组<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'a1b2c3d4'</span>.<span class="title function_">replace</span>(<span class="regexp">/([a-z]\d{3})g/</span>, <span class="string">'X'</span>) <span class="comment">//"Xd4"</span></span><br></pre></td></tr></table></figure></li><li>反向引用<br>使用<code>$1、$2、$3...</code>来表示和捕获分组后的内容<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'17741881234'</span>.<span class="title function_">replace</span>(<span class="regexp">/(.{3})(.{4})(.{4})/</span>, <span class="string">'$1****$3'</span>) <span class="comment">//"177****1234"</span></span><br></pre></td></tr></table></figure></li><li>忽略分组<br>不希望捕获某些分组,只需要在分组内加上<code>?:</code>即可</li><li>或<br>使用<code>|</code>可以达到或的效果</li><li>前瞻<br>正则表达式从文本头部向文本尾部开始解析。 <strong>文本尾部的方向,称为“前”,文本头部称为“后”。</strong> 前瞻就是在正则表达式匹配规则的时候,向前检查是否符合断言(条件),后顾/后瞻方向相反。JavaScirpt不支持后顾。<strong>符合/不符合</strong> 特定断言称为 <strong>肯定/正向匹配</strong> 和 __否定/负向匹配__。</li></ol><h3 id="四、RegExp对象方法"><a href="#四、RegExp对象方法" class="headerlink" title="四、RegExp对象方法"></a>四、RegExp对象方法</h3><ul><li><strong>RegExp.prototype.test(str)</strong><br>用于测试字符串参数中是否存在匹配正则表达式的字符串,若存在返回true,否则返回false</li><li><strong>RegExp.prototype.exec(str)</strong><br>使用正则表达式模式对字符串执行搜索,并将更新全局RegExp对象的属性以反映匹配结果。如果没有匹配的文本则返回null,否则返回一个结果数组。</li></ul><h3 id="五、String对象方法"><a href="#五、String对象方法" class="headerlink" title="五、String对象方法"></a>五、String对象方法</h3><ul><li><strong>String.prototype.search(reg)</strong><br>用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。方法返回第一个匹配结果的index,查找不到返回-1。search()方法不执行全局匹配,它将忽略标志g,并且总是从字符串的开始进行检索。</li><li><strong>String.prototype.match(reg)</strong><br>用于检索字符串,以找到一个或者多个与正则表达式匹配的文本。如果匹配到了一个或多个字符串,则返回一个数组,若没有匹配到,则返回null。它不会忽略全局标志g。</li><li><strong>String.prototype.split(reg)</strong><br>使用split方法把字符串分割为字符数组。</li><li><strong>String.prototype.replace(str/reg, str)</strong><br>用于替换字符串中匹配正则表达式或字符串的文本。</li></ul><h3 id="六、常用的正则表达式记录"><a href="#六、常用的正则表达式记录" class="headerlink" title="六、常用的正则表达式记录"></a>六、常用的正则表达式记录</h3><ul><li><p><a href="https://www.cnblogs.com/Kuro-P/p/8971349.html">https://www.cnblogs.com/Kuro-P/p/8971349.html</a></p></li><li><p><a href="https://www.runoob.com/regexp/regexp-metachar.html">常用的元字符</a></p></li></ul>]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> JavaScript </tag>
</tags>
</entry>
<entry>
<title>前端模块化</title>
<link href="/2019/01/03/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/"/>
<url>/2019/01/03/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/</url>
<content type="html"><![CDATA[<p>目前JS模块化规范主要三种:浏览器端的 <strong>AMD</strong> 、<strong>CMD</strong> 规范。经常被 exports、modules.exports、export、require 绕懵,遂来探一探究竟。</p><span id="more"></span><h4 id="AMD规范-requireJS-浏览器端-异步加载模块-提前执行"><a href="#AMD规范-requireJS-浏览器端-异步加载模块-提前执行" class="headerlink" title="AMD规范 (requireJS) 浏览器端 异步加载模块 提前执行"></a>AMD规范 (requireJS) 浏览器端 异步加载模块 提前执行</h4><p>AMD (Asynchronous Module Definition): 在浏览器中使用,并用 <code>define</code> 函数定义模块,用<code>require</code>引入模块;<br>它是 RequireJS 在推广过程中对模块定义的规范化产出,诣在帮开发者解决各个 js 文件的依赖问题,让开发者在页面引入多个 js 时,不必考虑各个 js 的依赖关系。</p><h4 id="CMD规范-SeaJS-浏览器端-异步加载模块-延迟执行"><a href="#CMD规范-SeaJS-浏览器端-异步加载模块-延迟执行" class="headerlink" title="CMD规范 (SeaJS) 浏览器端 异步加载模块 延迟执行"></a>CMD规范 (SeaJS) 浏览器端 异步加载模块 延迟执行</h4><p>CMD (Common Module Definition): 在浏览器端使用,使用 <code>define</code> 函数定义模块,用 <code>module.exports</code> 暴露模块;<br>它 是SeaJS 在推广过程中对模块定义的规范化产出。与 AMD 也都是异步加载模块,只是依赖加载的时间点不一样。相比于 AMD 依赖前置,CDM 加载采用就近原则(个人理解:先依赖,先加载)。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">define</span>(<span class="keyword">function</span>(<span class="params"><span class="built_in">require</span>, <span class="built_in">exports</span>, <span class="variable language_">module</span></span>) {</span><br><span class="line"> <span class="keyword">let</span> val = <span class="string">'module4'</span></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">getVal</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(val)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 引入module2 同步</span></span><br><span class="line"> <span class="keyword">let</span> module2 = <span class="built_in">require</span>(<span class="string">'./module2.js'</span>);</span><br><span class="line"> <span class="title function_">module2</span>()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 异步引入module3</span></span><br><span class="line"> <span class="built_in">require</span>.<span class="title function_">async</span>(<span class="string">'./module3.js'</span>, <span class="keyword">function</span>(<span class="params">module3</span>) {</span><br><span class="line"> module3.<span class="property">module3</span>.<span class="title function_">getData</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="variable language_">module</span>.<span class="property">exports</span> = {getVal}</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="CommonJS-服务端-同步加载模块"><a href="#CommonJS-服务端-同步加载模块" class="headerlink" title="CommonJS 服务端 同步加载模块"></a>CommonJS 服务端 同步加载模块</h4><p>NodeJS 的模块机制使用的就是 commonJS 的规范,因为服务端第三方库大多已存于本地,加载速度较快,使用同步加载比较理想。它使用 <code>module.exports</code> 或者是 <code>exports</code> 来导出,使用 <code>require</code> 引入。</p><h4 id="ES6-的-export-和-import-浏览器端"><a href="#ES6-的-export-和-import-浏览器端" class="headerlink" title="ES6 的 export 和 import 浏览器端"></a>ES6 的 export 和 import 浏览器端</h4><p>ESM (ES Modules) 是 JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用<code>import</code>和<code>export</code>引入和导出模块。</p><h4 id="UMD-通用模块机制"><a href="#UMD-通用模块机制" class="headerlink" title="UMD 通用模块机制"></a>UMD 通用模块机制</h4><p>UMD (Universal Module Definition) 是一个通用模块的机制,它使一个模块能运行在各种环境下,不论是 CommonJS、AMD,还是非模块化的环境。代码实现原理如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> define === <span class="string">'function'</span>) { <span class="comment">// 兼容 requireJS AMD、CMD规范 </span></span><br><span class="line"> <span class="title function_">define</span>(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> moduleName;</span><br><span class="line"> });</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="built_in">exports</span> !== <span class="string">'undefined'</span>) { <span class="comment">// 兼容 webpack 引入方式(commonJS) </span></span><br><span class="line"> <span class="variable language_">module</span>.<span class="property">exports</span> = moduleName; </span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">moduleName</span> = moduleName; <span class="comment">// 普通引入,注册到全局</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>常见于打包/编译工具中:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">webpackConfig</span>(<span class="params">BASE_PATH</span>) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="attr">mode</span>: <span class="string">'development'</span>,</span><br><span class="line"> <span class="attr">entry</span>: {</span><br><span class="line"> <span class="attr">index</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">'../src/index'</span>),</span><br><span class="line"> <span class="attr">preview</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">'../src/view'</span>)</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">output</span>: {</span><br><span class="line"> <span class="attr">libraryTarget</span>: <span class="string">'umd'</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><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><p><a href="https://github.com/ljianshu/Blog/issues/48">前端模块化详解(完整版)</a><br><a href="https://www.cnblogs.com/omelette/p/6652472.html">关于commonjs,AMD,CMD之间的异同</a></p>]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> JavaScript </tag>
</tags>
</entry>
<entry>
<title>2018年终总结</title>
<link href="/2018/12/31/2018%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<url>/2018/12/31/2018%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>2018年仿佛什么都没做,但又仿佛做了些什么;仿佛没有遗憾,但却又心有不甘;以为走到了正确的方向,但“迷茫”二字却困惑了我整整一年。</p><span id="more"></span><ul><li>一月:实习、回校考试</li><li>二月:回家过年</li><li>三月:实习、回校选毕设题目</li><li>四月:实习、学习毕设相关知识、投递简历</li><li>五月:实习、开始码毕设、投递简历、跑面试</li><li>六月:回校答辩、毕业</li><li>七月:转正,回公司工作</li><li>八月:工作、去当了一次漫展NPC</li><li>九月:工作、去了一次上海迪士尼</li><li>十月:工作、找房、换房</li><li>十一月:工作</li><li>十二月:工作</li></ul><p>18年主要完成事件就是这些。<br><strong>四、五月</strong> 大概是最忙的时候,因为要管的事情太多,忙到脚打后脑勺。<br><strong>六月</strong> 是全年最开心的阶段,因为回学校了,有室友和同学在。虽然答辩时被老师问到怀疑人生,但最后老师还是给了高分,借此拿了一次奖学金的我也是受宠若惊,以为毕业前再也没有机会拿到了。除了感谢老师以外,还得感谢公司leader,毕设题目是他建议的。<br><strong>七月</strong> 本决定给自己一周毕业旅行的时间,奈何职业方向和家里人冲突升级。取消了打算已久的假期,回公司了。工位发生很大变化,前端组的大家这次都坐在一起了。<br><strong>八月</strong> 第一次去了漫展,也是第一次当NPC,不过应该也都是最后一次了哈哈。遇见了很好的小伙伴们,临走前,没有张口要联系方式,挺后悔的。<br><strong>九月</strong> 去了趟迪士尼,事实证明,做攻略还是非常有用滴,项目都玩了,喜欢的也几刷了。遗憾的是,时间来不及,没有买到漫威周边。<br><strong>十月</strong> 相对轻松。十一没出去玩,出去看房,找到合适的就换了,室友也换了,承蒙了之前两位姐姐很多照顾,有时会怀念。十月末公司团建,挺好玩的。<br><strong>十一月</strong> 中旬心心念念的 blog 诞生了,虽然不难,但也是历史性的一步!毕竟从去年就开始惦记着…<br><strong>十二月</strong> 成长。双十二的时候,买了新水彩颜料,终于把雄狮换成鲁本斯了。</p><p>18年看似很充实,实际一年到头可以收获的东西却寥寥无几,全年没有明确的方向,只是被时间推着做事。<br>希望19年找到自己的目标和方向。</p>]]></content>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>Linux命令行与shell脚本学习</title>
<link href="/2018/11/30/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8Eshell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0/"/>
<url>/2018/11/30/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8Eshell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0/</url>
<content type="html"><![CDATA[<p>《Linux命令行与shell脚本编程大全》读书小结,熟悉一下常用的命令行操作。书籍比较基础,对熟悉Linux命令行的人来说参考意义不大。主要记录下书中提到的、没提到的常用的命令。</p><span id="more"></span><h3 id="基础操作"><a href="#基础操作" class="headerlink" title="基础操作"></a>基础操作</h3><ul><li>. 代表当前目录</li><li>.. 代表父级目录</li><li>~ 代表根目录 表名当前工作目录位于用户home目录之下</li><li>man <directive> 可查看指令可使用的参数手册</li><li><code>tab</code> 键自动补全文件名</li><li>cd 切换目录</li><li>linux 中的文件路径全部采用正斜线<code>/</code>,windows中的路径都是反斜线<code>\</code>而且带盘符</li><li>ls 列出当前路径下的所有文件<ul><li>-F 在显示子目录的时候在它的文件名之后加上一个斜线(“/”)字符</li><li>-F -R 遍历(递归)出当前目录下的子文件夹的所有内容(可以缩写成 ls -FR )</li><li>-a 列出所有文件,包括隐藏文件</li><li>-l 列出文件的所有信息</li></ul></li><li>pwd 查看当前所在位置的全路径</li><li>sudo 以 root 用户身份运行命令</li></ul><h3 id="文件基础操作"><a href="#文件基础操作" class="headerlink" title="文件基础操作"></a>文件基础操作</h3><ul><li>open <fileName> 用默认程序打开文件<ul><li>open <AppName> –args <参数> 用默认参数打开某个App</li></ul></li><li>touch <fileName> 创建一个文件 (不可在不存在的目录下新建文件)</li><li>mkdir <directory> 创建一个文件夹<ul><li>-p 创建多个层级的文件夹</li></ul></li><li>rmdir <directory> 只删除空目录<ul><li>在非空目录下使用 rm -r 命令</li></ul></li><li>cp <fileName> <targetDirectory/fileName> 复制文件到目标文件夹/文件名<ul><li>-i 强制 shell 询问是否覆盖同名文件</li></ul></li><li>scp <fileName> <root@targetPath> 远程拷贝文件 可以跨服务器</li><li>mv <fileName> <directory/fileName> 用来 移动/重命名 文件<ul><li>-i 强制 shell 询问是否覆盖同名文件</li></ul></li><li>rm <fileName> 删除文件/文件夹中的所有内容<ul><li>-i 强制 shell 询问是否删除文件</li><li>-f 强制删除,没有警告信息也没有声音提示</li><li>-r 递归删除目录及目录内所有文件 </li><li><strong>注意:Linux 中没有回收站或垃圾箱,文件一旦删除,就无法再找回</strong></li></ul></li><li>ls -l <fileName> 查看文件权限</li><li>chmod value <fileName> 更改文件权限<ul><li>权限描述顺序依次是:Owner(User)、Group、Other</li><li>r=读取属性 //值=4</li><li>w=写入属性 //值=2</li><li>x=执行属性 //值=1<img src="/2018/11/30/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8Eshell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0/file-permissions.png" class="" title="文件权限"></li></ul></li><li>chown(选项)(参数) 更改文件夹所有者和所属组<ul><li>chown -R user:group .git 将.git文件夹的权限设置为 group 下的 user</li></ul></li><li>获取文件路径:直接将文件拖入命令行即可</li></ul><h3 id="文件内容操作"><a href="#文件内容操作" class="headerlink" title="文件内容操作"></a>文件内容操作</h3><ul><li>file <fileName/directoryName> 查看文件类型信息</li><li>du <fileName/directoryName> 用来查看文件或目录所占用的磁盘空间的大小<ul><li>-h 以易于阅读的方式展示</li><li>-a 显示目录及其下子目录和文件占用的磁盘空间大小</li><li>-s 只展示当前目录占用磁盘空间大小</li></ul></li><li>cat/more/less <fileName> 查看整个文件内容<ul><li>cat 一次性加载完所有文件内容</li><li>more 一次显示一屏文本</li><li>less 一次显示一屏文本 可以上下页翻建</li></ul></li><li>tail/head <fileName> 查看部分文件内容<ul><li>tail 默认展示文件最后10行的效果<ul><li>-n 2 只显示文件最后两行</li><li>-f 允许其他进程使用该文件时查看该文件的内容,tail会保持活跃状态,并不断显示添加到文件中的内容。(可用来实时监测系统日志)</li></ul></li><li>head 默认展示文件前10行内容<ul><li>不支持 -f 属性</li></ul></li></ul></li><li>grep match_pattern <fileName> 强大的文本搜索工具,可以使用正则表达式搜索文本,并显示出匹配的行数</li><li>sed -i ‘s/被替换的内容/要替换的内容/g’ file -i 表示直接修改并保存<ul><li><a href="https://blog.csdn.net/u010339879/article/details/90107977">使用 sed 命令,报错<code>invalid command code</code></a>,是因为 -i 原地替换是危险行为,需要指明一个备份的扩展名才可以,若给了空的扩展名,则不会备份源文件。</li><li>如 sed -i ‘’ ‘s/被替换的内容/要替换的内容/g’ file</li></ul></li><li>ls -> xxx.txt 将命令输出的内容保存为文件</li></ul><h3 id="监控进程"><a href="#监控进程" class="headerlink" title="监控进程"></a>监控进程</h3><ul><li>ps 显示进程信息(瞬间占用情况)</li><li>top 显示进程信息(实时占用情况)</li><li>lsof 查看进程打开的文件<ul><li>lsof -i:4000 查看4000端口占用情况</li></ul></li><li>kill [PID] 杀死对应进程</li></ul><h3 id="网络情况"><a href="#网络情况" class="headerlink" title="网络情况"></a>网络情况</h3><ul><li>ping <ip> 测试主机之间的连通性(不会自动结束,需要手动 ctrl + c 强制退出)</li><li>dig <url> 域名查询工具,可以用来测试域名系统工作是否正常</li><li>nsloopup <url> 域名查询工具,查询 DNS 相关信息</li></ul><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><h4 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h4><ul><li>printenv/env 默认输出所有环境变量(全局)<ul><li>printenv JAVA_HOME 输出全局设置的JAVA SDK位置</li><li>env $JAVA_HOME </li><li>echo $JAVA_HOME</li></ul></li><li>echo $variableName 输出变量 ($用来表名它是个变量)</li><li>set 输出所有环境变量(全局和局部)</li><li>$HOME 表示的用户的主目录,与波浪线<code>~</code>作用一样</li></ul><h4 id="普通变量"><a href="#普通变量" class="headerlink" title="普通变量"></a>普通变量</h4><p>声明时直接声明即可使用 <code>variable=XXX</code>,变量名区分大小写,但需要注意的是 <strong>赋值时,变量名、等号和值之间没有空格</strong> 否则会报错 <code>command not found</code>。<br>常用的书写习惯是 <strong>所有的环境变量名均使用大写字母,若是自己创建的局部变量或是shell脚本,则用小写字母,变量名区分大小写。</strong></p><h3 id="vim-操作"><a href="#vim-操作" class="headerlink" title="vim 操作"></a>vim 操作</h3><ul><li>vim <fileName> 以 vim 编辑器的方式查看当前文件</li><li>按 <code>I</code> 对文件进行 INSERT 操作</li><li>按 <code>esc</code> 退出当前编辑模式</li><li>输入 <code>:</code> 切换到底线命令模式,可以在最底行输入其他命令</li><li>输入 <code>wq</code> ,保存并退出;输入 <code>!q</code>,不保存直接退出</li><li>输入 <code>ggdG</code>,删除当前全部内容;<code>gg</code> 为跳转到文件首行;<code>dG</code>为删除光标所在行以及其下所有行的内容</li><li>.swp 文件: 非正常关闭的 vim 编辑器会生成一个 .swp 文件</li></ul><h3 id="杂项"><a href="#杂项" class="headerlink" title="杂项"></a>杂项</h3><h4 id="大小写转换"><a href="#大小写转换" class="headerlink" title="大小写转换"></a>大小写转换</h4><ul><li>echo $VAR_NAME | tr ‘[:upper:]’ ‘[:lower:]’</li><li>echo $VAR_NAME | tr ‘[A-Z]’ ‘[a-z]’</li></ul><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul><li>alias 可用来查看当前可用的别名(内建命令)<ul><li>alias 新的命令=’原命令 -选项/参数’ 用来定义命令别名</li></ul></li><li>sh <fileName.sh> 执行shell文件</li><li>.xxxrc 可以看做是xxx启动运行时的配置文件<ul><li>例如 .zshrc 就是 zsh 运行前要执行配置文件</li></ul></li><li>source <fileName> 或者 . <fileName> (bash内部命令) 加载文件</li><li>文件\包查找<ul><li>which <fileName> 查找该包编译器所在位置</li><li>whereis <fileName> 搜索更大范围的系统目录并输出所有包含的路径</li><li>find <fileName> 查找系统是否安装了某个软件包</li></ul></li></ul><h3 id="代理"><a href="#代理" class="headerlink" title="代理"></a>代理</h3><ul><li><a href="https://www.jianshu.com/p/c99373ad37f7">参考</a></li><li>若想要在当前终端中生效,直接输入 <code>export http_proxy='http://ip_address:port'</code> 即可,注意 ip 和端口号是本机的 ip + port;</li><li>想要持久化全局生效的话,可以在 .zhsrc 中配置上述命令</li></ul><h3 id="常用的配置文件地址"><a href="#常用的配置文件地址" class="headerlink" title="常用的配置文件地址"></a>常用的配置文件地址</h3><ul><li>Host 文件 /etc/hosts</li><li>配置的 SSH Key: cat ~/.ssh/id_rsa.pub</li></ul><h3 id="常见文件颜色"><a href="#常见文件颜色" class="headerlink" title="常见文件颜色"></a>常见文件颜色</h3><ul><li>白色:表示普通文件</li><li>蓝色:表示目录</li><li>绿色:表示可执行文件</li><li>红色:表示压缩文件</li><li>浅蓝色:链接文件</li><li>红色闪烁:表示链接的文件有问题</li><li>黄色:表示设备文件</li><li>灰色:表示其他文件</li></ul><h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><ul><li>homebrew 包管理器<ul><li>brew install <packageName> 安装插件</li><li>brew list 查看电脑安装了哪些插件</li><li>注:每次下载包之前都会进行 brew 更新检查,速度很慢,按一次 <code>Ctrl+C</code> 跳过更新</li><li>官网上的 github 源安装总是会 443 connect timeout,推荐使用国内的镜像安装:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"</span><br></pre></td></tr></table></figure></li></ul></li><li>wget 下载网页常用的工具</li><li>curl 是用来请求 Web 服务器的命令行工具,类似于 POSTMAN,它的名字就是客户端(client)的 URL 工具的意思。curl 支持的通信协议有 FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP 和 RTSP。curl 还支持 SSL 认证、HTTP POST、HTTP PUT、FTP上传, HTTP form based upload、proxies、HTTP/2、cookies、用户名+密码认证(Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos)、file transfer resume、proxy tunneling。<ul><li>curl <url> 直接返回 url 请求结果<ul><li><code>-A/--user-agent <string\></code> 设置用户代理发送给服务器</li><li><code>-D/--dump-header <file\></code> 把 header 信息写入到该文件中</li><li><code>-O/--output <file\></code> 把输出写到该文件中</li><li><code>-#/--progress-bar</code> 进度条显示当前传送状态</li><li><a href="https://www.amd5.cn/atang_4752.html">详细信息</a></li></ul></li><li>需要注意的是,做了反爬措施的网站在直接 curl 请求的时候,结果可能不如预期</li></ul></li><li>tree 以树状图形式展示目录及其子文件<ul><li>tree <directory> -J 以 json 形式展示文件</li></ul></li><li>tig 将 git 命令行可视化</li></ul><p><strong>其他参考:</strong></p><ul><li><strong><a href="http://man.linuxde.net/">Linux命令大全</a></strong></li><li><strong><a href="http://blog.chinaunix.net/uid-27164517-id-3299073.html">Linux命令英文缩写的含义</a></strong></li><li><strong><a href="http://c.biancheng.net/shell/base/">Shell基础</a></strong></li></ul>]]></content>
<categories>
<category> 计算机相关知识 </category>
</categories>
<tags>
<tag> Linux&shell </tag>
</tags>
</entry>
<entry>
<title>同时使用两个账号分别操作 github 和 gitlab</title>
<link href="/2018/11/17/%E5%90%8C%E6%97%B6%E4%BD%BF%E7%94%A8%E4%B8%A4%E4%B8%AA%E8%B4%A6%E5%8F%B7%E5%88%86%E5%88%AB%E6%93%8D%E4%BD%9CGithub%E5%92%8CGitlab/"/>
<url>/2018/11/17/%E5%90%8C%E6%97%B6%E4%BD%BF%E7%94%A8%E4%B8%A4%E4%B8%AA%E8%B4%A6%E5%8F%B7%E5%88%86%E5%88%AB%E6%93%8D%E4%BD%9CGithub%E5%92%8CGitlab/</url>
<content type="html"><![CDATA[<p>思路就是通过 SSH config 中为不同的域名指定不同的 SSH key,之后再使用 <code>git config -- local</code> 将 github repository 设置成自己的 github 用户账号。</p><span id="more"></span><h3 id="一、生成SSH秘钥"><a href="#一、生成SSH秘钥" class="headerlink" title="一、生成SSH秘钥"></a>一、生成SSH秘钥</h3><p>分别生成 github、gitlab 所需密钥:</p><ul><li>使用 <code>ssh-keygen -t rsa -C "邮箱地址"</code> 生成两份密钥对</li><li>分别命名为 <code>id_rsa、id_rsa.pub</code> 和 <code>github_rsa、github_rsa.pub</code></li><li>生成密钥的过程中,命令行提示输入 passphrase,用作每次进行 ssh 连接时的确认密码(电脑和账号这里都是个人使用所以直接按回车设置为空就可以)</li><li>将两份公钥 <code>id_rsa.pub</code>、<code>github_rsa.pub</code> 分别上传至 gitlab、github</li><li>由于 ssh 连接默认查找的都是私钥路径为 ~/.ssh/id_rsa,所以需要为 github 手动指明所需私钥 github_rsa,否则会报错 Permission denied (publickey) </li><li>在 <code>~/.ssh</code>下创建一个 config 文件,添加配置:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">Host github.com</span><br><span class="line"> Hostname ssh.github.com</span><br><span class="line"> Port 443</span><br><span class="line"> User git</span><br><span class="line"> IdentityFile ~/.ssh/github_rsa</span><br></pre></td></tr></table></figure><em>注意:若为 github 中配置了两个同名 ssh,那么 config 中谁在前谁生效</em></li></ul><h3 id="二、测试连接"><a href="#二、测试连接" class="headerlink" title="二、测试连接"></a>二、测试连接</h3><p>运行<code>ssh -T github</code> 命令测试是否配置成功。</p><img src="/2018/11/17/%E5%90%8C%E6%97%B6%E4%BD%BF%E7%94%A8%E4%B8%A4%E4%B8%AA%E8%B4%A6%E5%8F%B7%E5%88%86%E5%88%AB%E6%93%8D%E4%BD%9CGithub%E5%92%8CGitlab/test-ssh-connect.png" class=""><p>如果能看到一些 Welcome 信息,说明是 OK 的。</p><h3 id="三、配置-git-库账号"><a href="#三、配置-git-库账号" class="headerlink" title="三、配置 git 库账号"></a>三、配置 git 库账号</h3><p>为了使 github / gitlab 知道提交的用户是谁,需要对账户名进行配置。由于全局配置是公司的账号,所以只需要对自己想要进行操作的 github 库进行本地配置即可。</p><figure class="highlight plaintext"><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">git config --local user.name 'username' # github账号名称</span><br><span class="line">git config --local user.email '[email protected]' # github账号邮箱</span><br></pre></td></tr></table></figure><p>或者直接 init 一个 git 库,配置后 github 的代码都在这个仓库下拉取。</p>]]></content>
<categories>
<category> git </category>
</categories>
<tags>
<tag> git </tag>
</tags>
</entry>
<entry>
<title>Hexo 基础使用</title>
<link href="/2018/11/17/Hexo-%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/"/>
<url>/2018/11/17/Hexo-%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<p>记录一下 hexo 基础用法。</p><span id="more"></span><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>node 环境下,全局安装 hexo-cli</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-cli -g</span><br></pre></td></tr></table></figure><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>进入到一个放置 blog 的 <strong>空文件夹</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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">hexo init </span><br><span class="line">hexo generate</span><br><span class="line">hexo server # 默认4000端口</span><br><span class="line">hexo s -p 4001 # 在自定义端口启动</span><br></pre></td></tr></table></figure><p>浏览器输入 localhost:4000,出现 blog 界面</p><h3 id="换主题"><a href="#换主题" class="headerlink" title="换主题"></a>换主题</h3><p>Hexo 官网提供了一些主题 <a href="https://hexo.io/themes/">https://hexo.io/themes/ </a> </p><ul><li>git clone 主题到 blog 项目 <code>/themes</code> 文件夹下,将全局 _config.yml 中的 theme 名字更改为 clone 下来的主题文件夹的名字</li><li>主题中有可供选择的几套样式,更改主题 _config.yml 里的 scheme 属性 </li><li>设置代码高亮样式 更改主题 _config.yml 里的 hightlight_theme 属性</li><li>切换 Hexo 语言 修改全局 _config.yml 里的 language 属性,值为 zh-Hans (Hexo 3+) / zh-CN(Hexo 4+) 即为简体中文(默认为英文)</li><li>(更换完主题,需要重启&重新编译应用,方能生效)</li><li><strong>由于主题也是一个 git 仓库,下载后记得删除 .git 文件,否则主题文件是无法提交的</strong></li><li>主题更新:<ul><li>由于先前已经删除了主题目录下的 <code>.git</code> 文件夹,所以无法通过 <code>git pull</code> 来更新。每次更新需要将新的代码 clone 到 <code>/theme</code> 文件夹中,再手动迁移,比较麻烦,建议有大版本时再更新…</li></ul></li></ul><h3 id="生成文章"><a href="#生成文章" class="headerlink" title="生成文章"></a>生成文章</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">hexo new <span class="string">"postName"</span> <span class="comment"># /source/_post/[postName]/ & /source/_post/[postName].md</span></span><br><span class="line">hexo new page <span class="string">"pageName"</span> <span class="comment"># /source/[pageName]/index/ & /source/[pageName]/index.md</span></span><br><span class="line">hexo generate <span class="comment"># /source/.md -> /public/.html</span></span><br><span class="line">hexo server </span><br><span class="line">hexo deploy <span class="comment"># 将 /public 目录部署到 GitHub</span></span><br></pre></td></tr></table></figure><h3 id="删除文章"><a href="#删除文章" class="headerlink" title="删除文章"></a>删除文章</h3><figure class="highlight sh"><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">hexo clean <span class="comment"># delete /public</span></span><br><span class="line">hexo generate <span class="comment"># regenerate /public</span></span><br><span class="line">hexo deploy</span><br></pre></td></tr></table></figure><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul><li><strong>插入本地图片</strong><br>每次<code>hexo new 'postName'</code>时,都会创建一个与文章名相同的文件夹,将文章所需资源放入该文件夹里,引用时使用 <code>{% asset_img [文件名] %}</code> 即可。</li><li><strong>页面增加“阅读更多”按钮</strong><br>在 .md 文件中增加 <code><!--more--></code> 注释,如果想自动添加“阅读更多”按钮,可在主题下的 <code>_config.yml</code> 中将 <code>auto_excerpt</code> 下的 <code>enable</code> 设置为 <code>true</code>。</li></ul><h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><ul><li><a href="https://github.com/hexojs/hexo-deployer-git">hexo-deployer-git</a> 一键部署到 GitPage</li><li><a href="https://github.com/mythsman/hexo-douban">hexo-douban</a> 爬取豆瓣个人条目相关信息</li><li><a href="https://github.com/wzpan/hexo-generator-search">hexo-generator-search</a> 全文搜索功能</li></ul><h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><p><code>hexo d</code> 部署前,需要安装<code>npm install hexo-deployer-git --save</code>。<br>修改全局 <code>_config.yml</code> 中的配置:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">deploy:</span><br><span class="line"> type: git</span><br><span class="line"> repo: <repository url></span><br><span class="line"> branch: [branch]</span><br><span class="line"> message: [message]</span><br><span class="line"> name: [git user] </span><br><span class="line"> email: [git email]</span><br><span class="line"> extend_dirs: [extend directory] # 其他要部署的目录</span><br><span class="line"> ignore_hidden: true # 忽略隐藏文件</span><br><span class="line"> ignore_pattern: regexp # 忽略正则匹配的隐藏文件</span><br></pre></td></tr></table></figure><p>之后,只需要 <code>hexo d -g</code> 一条命令就可以生成和部署了。关于 hexo-deployer-git 这个插件的参数 <a href="https://hexo.io/zh-cn/docs/deployment.html">hexo 官方文档</a> 介绍的并不全面,建议去 <a href="https://github.com/hexojs/hexo-deployer-git">hexo-deployer-git 官方文档</a> 查看相关配置参数。</p><p><strong>注意:</strong> </p><ul><li>默认部署,只将生成的 HTML 相关文件(<code>/public</code>文件夹)推送到 github</li><li>若想把本地的生成器项目相关文件也推送到 github,则要配置 <code>extend_dirs: /</code></li><li>message、name、email 的内容要用引号括起来</li><li>name、email 的配置信息用来覆盖全局的 git config 中的配置,更改这两项后,需要删除根目录下的 <code>.deploy_git</code> ,部署时才会生效</li><li>master 只能放 <code>/public</code> 下的文件,将项目所有文件放到 master 分支下,会导致页面 build 失败。若想将本地代码全部提交,可部署在其他分支(在 <code>_config.yml</code> 中增加其他分支配置信息,详情参考文档)</li><li>避免提交 node_modules,需在项目下新建<code>.gitignore</code>文件(为什么不使用 extend_dirs ?因为需要添加的文件夹太多…)</li><li>若遇见 <code>Error: EACCES: permission denied, unlink /XXX</code> 相关的错误,大部分是由没权限引起的,使用 <code>sudo chown -R </code>whoami<code>:staff /blog 目录</code> 即可</li></ul><h3 id="自动化部署"><a href="#自动化部署" class="headerlink" title="自动化部署"></a>自动化部署</h3><ol><li>使用 github action 脚本自动部署 hexo pages 到目标分支。<br>当前开发时的源代码在 develop 分支上,静态文件会部署到 master 分支上。 原理其实就是利用 github action events 触发编译操作,在 github 提供的机器上编译后发布到 master 分支。这样就省去了在本地编译的麻烦。<br>需要注意的是,如果使用 ssh 方式部署,那么需要将 ssh 公钥、秘钥上传到项目中,否则机器没权限提交代码。</li></ol><p>部署配置过程参考:<a href="https://juejin.cn/post/7014675289728876574">https://juejin.cn/post/7014675289728876574</a></p><ol start="2"><li>部署后文章的更新时间异常</li></ol><ul><li><strong>文章更新时间错误</strong> :github action 默认时区是 UCT 时间 08:00,比北京时间快 8 个小时。因此运行环境要改为北京时间。(PS:中国一共<a href="https://zhuanlan.zhihu.com/p/450867597">五个时区</a>,命名是历史原因,北京所在的时区名字就是 Asia/Shanghai)<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">name: Deploy CI</span><br><span class="line">on:</span><br><span class="line"> push</span><br><span class="line">env:</span><br><span class="line"> TZ: Asia/Shanghai</span><br></pre></td></tr></table></figure></li><li>所有文章的更新时间都被更新成最新时间:git 在推送更新时,并不保存文件的访问时间、修改时间等信息,故默认取了 github 推送时间,需要将 文件的 update 时间更改为单个文件在 github 上的推送时间。相关文章↓:<ul><li><a href="https://cloud.tencent.com/developer/article/2298026">hugo 博客 github action 部署后文章更新时间异常修复</a></li><li><a href="https://mrseawave.github.io/blogs/articles/2021/01/07/ci-hexo-update-time/">修复 CI 构建博客造成的更新时间错误</a></li></ul></li></ul><h3 id="搜索功能"><a href="#搜索功能" class="headerlink" title="搜索功能"></a>搜索功能</h3><p>全局安装插件 <code>npm install hexo-generator-search --save</code><br>修改全局 <code>_config.yml</code>中的配置:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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">search:</span><br><span class="line"> path: search.xml</span><br><span class="line"> field: post</span><br><span class="line"> content: true</span><br></pre></td></tr></table></figure><p>修改主题 <code>themes/next/_config.yml</code> 中的配置:</p><figure class="highlight plaintext"><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">local_search:</span><br><span class="line"> enable: true</span><br><span class="line"> trigger: auto</span><br></pre></td></tr></table></figure><p>生效:<code>hexo clean</code>、<code>hexo g</code>、<code>hexo s</code></p><h3 id="图片放大查看功能"><a href="#图片放大查看功能" class="headerlink" title="图片放大查看功能"></a>图片放大查看功能</h3><p>在 <code>themes/next/_config.yml</code> 文件中,将 <code>fancybox</code> 或者 <code>mediumzoom</code> 置为 true。 </p><h3 id="Hexo-目录解析"><a href="#Hexo-目录解析" class="headerlink" title="Hexo 目录解析"></a>Hexo 目录解析</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">├── node_modules # 依赖包-安装插件及所需nodejs模块。</span><br><span class="line">├── public # 最终网页信息。即存放通过 markdown 渲染出来的 html文件。</span><br><span class="line">├── scaffolds # 模板文件夹。即新建文章时,根据 scaffold 生成文件。</span><br><span class="line">├── source # 资源文件夹。即存放用户资源。</span><br><span class="line">| └── _posts # 博客文章目录。</span><br><span class="line">└── themes #存放主题。Hexo根据主题生成静态页面。</span><br><span class="line">├── _config.yml #网站的全局配置信息。标题、网站名称等。</span><br><span class="line">├── db.json:# source 解析所得到的缓存文件。</span><br><span class="line">├── package.json # 应用程序信息。即配置Hexo运行需要js包。</span><br></pre></td></tr></table></figure><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p><a href="https://cherryblog.site/Use-Gitpagehexo-to-develop-their-own-blog.html">利用 hexo + Gitpage 开发自己的博客</a><br><a href="https://www.jianshu.com/p/a938da5ddb5d">hexo 浅析原理</a></p>]]></content>
<categories>
<category> 其他小结 </category>
</categories>
<tags>
<tag> Hexo </tag>
</tags>
</entry>
<entry>
<title>《看见》-柴静</title>
<link href="/2018/09/07/%E3%80%8A%E7%9C%8B%E8%A7%81%E3%80%8B-%E6%9F%B4%E9%9D%99/"/>
<url>/2018/09/07/%E3%80%8A%E7%9C%8B%E8%A7%81%E3%80%8B-%E6%9F%B4%E9%9D%99/</url>
<content type="html"><![CDATA[<p>无意间逛知乎的时候发现的书籍片段,留下了很深的印象。从记者的视角看到平日里生活中接触不到的社会另一面,别有一番感触。不愧是著名记者,文笔犀利,干练不拖沓。值得一读的好书:★★★★★</p><span id="more"></span><h3 id="第二章-那个温热的跳动就是活着"><a href="#第二章-那个温热的跳动就是活着" class="headerlink" title="第二章 那个温热的跳动就是活着"></a>第二章 那个温热的跳动就是活着</h3><p>我对非典的印象还是停留在小学时候,有那么一段时间,教室里每天清晨和下午都要喷洒消毒水,学校的走廊里弥漫着一股医院的味道。那时还小,只知道这是在预防“非典”,但它到底是什么,我并不知道。</p><blockquote><p>这就是我之前听说的天井。四周楼群间的一块空地,一 个楼与楼之间的天井,加个盖,就成了个完全封闭的空间, 成了输液室,发热的病人都集中到这里来输液。二十七张床 几乎完全挨在一起,中间只有一只拳头的距离。白天也完全靠灯光,没有通风,没有窗,只有一个中央空调的排气口, 这个排气口把病菌传到各处。<br>病历胡乱地堆在桌上,像小山一样,已经发黄发脆。我 犹豫了一秒钟。朱继红几乎是凄然地一笑,说:“我来吧。” 病例被翻开,上面写的都是“肺炎”。他指给我看墙上的黑 板,上面写了二十二个人的名字,其中十九个后面都用白粉 笔写着:肺炎、肺炎、肺炎……<br>“实际上都是 SARS。”他说。</p></blockquote><blockquote><p>一个卫生系统的官员在这里感染,回家又把妻子儿子感染了,想尽办法要住院,只能找到一个床位,夫妇俩让儿子住了进去。两口子发烧得浑身透湿,站不住,只能顫抖着坐在 小板凳上输液。再后来连板凳都坐不住了。孩子痊愈的时候, 父母已经去世。</p></blockquote><p>明明只是在描述,却让人觉得无比震撼。</p>]]></content>
<categories>
<category> 闲暇读物 </category>
</categories>
<tags>
<tag> 读书小结 </tag>
</tags>
</entry>
<entry>
<title>markdown 语法小记</title>
<link href="/2018/09/05/MarkDown%E8%AF%AD%E6%B3%95%E5%B0%8F%E8%AE%B0/"/>
<url>/2018/09/05/MarkDown%E8%AF%AD%E6%B3%95%E5%B0%8F%E8%AE%B0/</url>
<content type="html"><![CDATA[<p>第一个 hexo-next 主题的 blog,主要记录下 markdown 语法</p><span id="more"></span><h3 id="测试文本样式"><a href="#测试文本样式" class="headerlink" title="测试文本样式"></a>测试文本样式</h3><h4 id="测试加粗样式"><a href="#测试加粗样式" class="headerlink" title="测试加粗样式"></a>测试加粗样式</h4><p><strong>加粗</strong></p><h4 id="测试斜体样式"><a href="#测试斜体样式" class="headerlink" title="测试斜体样式"></a>测试斜体样式</h4><p><em>斜体</em></p><h4 id="测试删除线样式"><a href="#测试删除线样式" class="headerlink" title="测试删除线样式"></a>测试删除线样式</h4><p><del>删除线</del></p><h4 id="测试引用样式"><a href="#测试引用样式" class="headerlink" title="测试引用样式"></a>测试引用样式</h4><blockquote><p>山穷水尽疑无路,柳暗花明又一村</p></blockquote><h3 id="测试代码样式"><a href="#测试代码样式" class="headerlink" title="测试代码样式"></a>测试代码样式</h3><h4 id="测试指定代码语言代码样式"><a href="#测试指定代码语言代码样式" class="headerlink" title="测试指定代码语言代码样式"></a>测试指定代码语言代码样式</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> FE_developer = {</span><br><span class="line"><span class="attr">name</span>: <span class="string">'Kuro'</span>,</span><br><span class="line"><span class="attr">age</span>: <span class="string">'22'</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'info'</span>, FE_developer);</span><br></pre></td></tr></table></figure><h4 id="测试单行代码样式"><a href="#测试单行代码样式" class="headerlink" title="测试单行代码样式"></a>测试单行代码样式</h4><p>在 JS 中我们常用 <code>console.log()</code> 来输出调试信息。</p><h4 id="测试代码块样式"><a href="#测试代码块样式" class="headerlink" title="测试代码块样式"></a>测试代码块样式</h4><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">function</span> <span class="title function_">test</span>(<span class="params">a, b</span>){</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(a + b);</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="variable language_">arguments</span>.<span class="property">callee</span>, <span class="number">500</span>);</span><br><span class="line"> }, <span class="number">500</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="测试连接样式"><a href="#测试连接样式" class="headerlink" title="测试连接样式"></a>测试连接样式</h3><p>百度一下:<a href="https://www.baidu.com/">Baidu</a></p><h3 id="测试首行缩进样式"><a href="#测试首行缩进样式" class="headerlink" title="测试首行缩进样式"></a>测试首行缩进样式</h3><p>  markdown 语法主要考虑的是英文,中文缩进需要依赖 HTML 的空格符号</p><figure class="highlight plaintext"><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">半角空格: &nbsp;</span><br><span class="line">全角空格:&emsp;</span><br></pre></td></tr></table></figure><h3 id="测试表格样式"><a href="#测试表格样式" class="headerlink" title="测试表格样式"></a>测试表格样式</h3><table><thead><tr><th>左对齐</th><th align="center">居中对齐</th><th align="right">右对齐</th></tr></thead><tbody><tr><td>Harry Potter</td><td align="center">Gryffindor</td><td align="right">90</td></tr><tr><td>Hermione Granger</td><td align="center">Gryffindor</td><td align="right">100</td></tr><tr><td>Draco Malfoy</td><td align="center">Slytherin</td><td align="right">90</td></tr></tbody></table><p>表格使用 <code>|</code> 来分隔不同的单元格,使用 <code>-</code> 来分隔表头和其他行。<br><strong>注意:表格前若有文本,需要空一行才能正常显示</strong></p><h3 id="测试插入图片"><a href="#测试插入图片" class="headerlink" title="测试插入图片"></a>测试插入图片</h3><p>来自百度图片: </p><img src="https://ts1.cn.mm.bing.net/th/id/R-C.b2e807d164d8843a80d4e43d6d2cd14e?rik=vBI7UrFJE09fIQ&riu=http%3a%2f%2fimg95.699pic.com%2felement%2f40114%2f3510.png_860.png&ehk=VVAaXYYnxQaqoogiFcOBZsxixfBN1ZsVpGOfySvfy3Y%3d&risl=&pid=ImgRaw&r=0" width="200" alt="西瓜"/><h3 id="测试列表"><a href="#测试列表" class="headerlink" title="测试列表"></a>测试列表</h3><p>git 常用语法</p><ul><li>git status</li><li>git add .</li><li>git commit -m”XXX”</li></ul><ul><li>git stash</li><li>git list</li><li>git stash apply stash@{n}</li></ul><ul><li>git diff</li><li>git reset –hard</li></ul><ol><li>列表内容<ul><li>列表嵌套第一条</li><li>列表嵌套第二条</li></ul></li><li>列表内容</li><li>列表内容</li></ol><h3 id="测试复选框样式"><a href="#测试复选框样式" class="headerlink" title="测试复选框样式"></a>测试复选框样式</h3><ul><li><input checked="" disabled="" type="checkbox"> 选项一</li><li><input disabled="" type="checkbox"> 选项二</li><li><input disabled="" type="checkbox"> 选项三</li></ul><h3 id="测试流程图样式"><a href="#测试流程图样式" class="headerlink" title="测试流程图样式"></a>测试流程图样式</h3><div id="flowchart-0" class="flow-chart"></div><h3 id="其他注意事项"><a href="#其他注意事项" class="headerlink" title="其他注意事项"></a>其他注意事项</h3><ul><li>在 markdown 中直接使用尖括号<code><something></code>会被文本默认为HTML标签语句而不予显示。<ul><li>使用转义字符<code>&lt;</code>代替<code><</code>,用<code>&gt;</code>代替<code>></code></li><li>或者左闭合的尖括号前加一个转义符号<code>\</code>,例如:“\<something>”<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/flowchart/1.6.5/flowchart.min.js"></script><textarea id="flowchart-0-code" style="display: none">st=>start: 开始e=>end: 结束io1=>inputoutput: 输入聚类类数kop1=>operation: 筛选初始质心op2=>operation: 计算样本点到各个质心之间的距离并将其归到距离其最近的质心所在簇中op3=>operation: 计算各簇均值,生成新的质心c1=>condition: 新旧质心距离小于阈值io2=>inputoutput: 输出聚类结果st->io1->op1->op2->op3->c1c1(no)->op2c1(yes)->io2->e</textarea><textarea id="flowchart-0-options" style="display: none">{"scale":1,"line-width":2,"line-length":50,"text-margin":10,"font-size":12}</textarea><script> var code = document.getElementById("flowchart-0-code").value; var options = JSON.parse(decodeURIComponent(document.getElementById("flowchart-0-options").value)); var diagram = flowchart.parse(code); diagram.drawSVG("flowchart-0", options);</script></li></ul></li></ul>]]></content>
<categories>
<category> 其他小结 </category>
</categories>
<tags>
<tag> markdown </tag>
</tags>
</entry>
</search>