-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
968 lines (465 loc) · 220 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>如何公证(notarize)你的Electron应用</title>
<link href="/2020/10/15/how-to-notarize-your-electron-app/"/>
<url>/2020/10/15/how-to-notarize-your-electron-app/</url>
<content type="html"><![CDATA[<p>为什么打包后的<code>Electron</code>应用在<code>macOS</code>上会提示无法打开,版本更新之后,接到了部分用户的反馈,提示无法安装我们的<code>App</code>,情况如下:<br><img src="https://i.loli.net/2021/06/06/CdjJLRGEc62qQft.png" alt="未公证"></p><a id="more"></a><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><p>从<code>macOS 10.14.5</code>版本开始,所有<code>macOS</code>程序不仅需要签名,还需要经过苹果公证,当然上架AppStore的应用苹果会进行审核和公证,但那些不上架市场的应用也需要向苹果公证你的应用,才能正常使用,否则<code>GateKeeper</code>会阻止用户打开应用。</p><h3 id="如何公证Electron-App"><a href="#如何公证Electron-App" class="headerlink" title="如何公证Electron App"></a>如何公证Electron App</h3><p>我这里使用的是<a href="mailto:`electron-builder@22.9.1" target="_blank" rel="noopener">`electron-builder@22.9.1</a><code></code><a href="mailto:electron@9.4.1" target="_blank" rel="noopener">electron@9.4.1</a><code>,另外进行苹果公证还需要一个必要的前置条件,那就是需要有</code>Apple Developer ID`也就是苹果开发者账号。已有开发者账号请继续往下看。</p><h4 id="1-构建应用开启强化运行时"><a href="#1-构建应用开启强化运行时" class="headerlink" title="1. 构建应用开启强化运行时"></a>1. 构建应用开启强化运行时</h4><p>苹果在添加公证时,要让你的<code>App</code>有一个强化的运行时,不懂什么意思?其实就是默认给你的应用更少的权限。<br>Electron启用强化运行时,在你的构建配置下,添加<code>"hardenedRuntime": true</code> </p><pre><code class="json">"build": { "mac": { "hardenedRuntime": true }}</code></pre><p>开启该选项时,需要申请一定的权限,macOS下申请权限可使用<code>plist</code>文件,我们项目的该文件参考如下,可直接复制该文件使用,如果你有其他权限需要申请,可在其中添加。</p><pre><code class="xml"><?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <dict> <key>com.apple.security.cs.allow-jit</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> <key>com.apple.security.cs.allow-dyld-environment-variables</key> <key>com.apple.security.cs.disable-library-validation</key> <true/> </dict></plist></code></pre><p>主要申请的权限为第二条,<a href="https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-unsigned-executable-memory" target="_blank" rel="noopener">允许未签名的可执行内存授权</a></p><p>使用<code>plist</code>,修改上面的强化运行时构建代码:</p><pre><code class="json">"mac": { "entitlements": "scripts/entitlements.mac.plist", "entitlementsInherit": "scripts/entitlements.mac.plist"}</code></pre><h4 id="2-苹果开发者账号及完整性检验"><a href="#2-苹果开发者账号及完整性检验" class="headerlink" title="2. 苹果开发者账号及完整性检验"></a>2. 苹果开发者账号及完整性检验</h4><p>需要公证你的应用,必须要有有效的开发者账号才能进行公证。有关开发者账号的申请,证书的创建、导出、删除请自行搜索解决。</p><p><code>electron-builder</code>使用的签名工具<code>electron-osx-sign</code>会验证签名是否成功,在macOS10.14.5之前,签名应用会返回<code>true</code>,而该版本及该版本之后将会返回<code>false</code>,因为虽然应用签名了,但它没有进行公证。所以,我们最好时禁用这个完整性检查,可添加<code>"gatekeeperAssess": false</code>到mac构建下,及完整的mac构建配置至少需要以下内容:</p><pre><code class="json">"mac": { "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "scripts/entitlements.mac.plist", "entitlementsInherit": "scripts/entitlements.mac.plist"}</code></pre><p>另外,如果你需要导出<code>dmg</code>的macOS安装包,并且你的<code>electron-builder</code>版本小于<code>20.43.0</code>,你则需要添加额外的配置:</p><pre><code>"dmg": { "sign": false}</code></pre><p>因为<code>Gatekeeper</code>会对你的程序所以签名内容进行完整性校验,实际上对<code>dmg</code>进行签名和公证仍会出现上诉错误,应该是苹果的bug或有其他原因。所以我们最好使用未签名、未工作的dmg,因为安装程序后,<code>Gatekeeper</code>检验的应用内容,发现经过公证,同样可以通过检查并且使用。</p><h4 id="3-对你的应用进行公证"><a href="#3-对你的应用进行公证" class="headerlink" title="3. 对你的应用进行公证"></a>3. 对你的应用进行公证</h4><p>Electron官方提供了人一个npm包<code>electron-notarize</code>用来对electron程序进行公证,所以我们需要安装一下这个依赖:</p><pre><code class="shell">yarn add electron-notarize --dev// ornpm install electron-notarize --save-dev</code></pre><p>我们需要在应用签名后,打包dmg镜像之前对应用进行公证,<code>electron-builder</code>提供了一个hooks <a href="https://www.electron.build/configuration/configuration#aftersign" target="_blank" rel="noopener">afterSign</a>,该钩子将在签名完成后执行指定的函数或脚本。我们在根目录下新建<code>scripts</code>目录,并且新建<code>notarize.js</code>文件:</p><pre><code class="shell">mdkir scriptscd scriptstouch notarize.js</code></pre><p>代码如下:</p><pre><code>const fs = require('fs')const electronNotarize = require('electron-notarize')module.exports = async (params) => { // params由afterSign传入,可参考上面的链接 const { electronPlatformName, appOutDir } = params // 打包非mac平台无需公证 if (electronPlatformName !== 'darwin') { return; } const appName = params.packager.appInfo.productFilename; const appPath = `${appOutDir}/${appName}.app` if (!fs.existsSync(appPath)) { throw new Error(`Cannot find application at: ${appPath}`); } console.log(`start to notarize application `) try { await electronNotarize.notarize({ appBundleId: 'your app bundle Id', appPath: appPath, appleId: `your apple ID`, appleIdPassword: `your apple id password` }) } catch (error) { console.log(error) } console.log(`notarize app : ${appName} Done.`)}</code></pre><p><code>appleId</code>和<code>appleIdPassword</code>较为敏感,建议不要使用明文,可考虑使用<code>.env</code>或环境变量等方式注入。<code>appleIdPassword</code>建议不要使用常规密码,可生成一个特定的密钥用来公证,<a href="https://appleid.apple.com/" target="_blank" rel="noopener">生成特定程序密钥</a>。</p><p>应用签名后,会自动运行该脚本,最后打包生成<code>dmg</code>镜像,就可以正常安装并通过<code>GateKeeper</code>的检测了。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>该上传到公证过程时间可能较长,熟悉iOS开发者应该比较清楚,国内上传安装包到苹果,可能需要比较长的时间,安心等待即可。不过我尝试过全局科学上网甚至无法上传,使用wifi也会长时间处于等待,并且这个过程看不到进度。我亲试使用手机热点上传会比较快,接受不了长时间等待可以尝试手机热点上传。</p>]]></content>
<categories>
<category> electron </category>
</categories>
<tags>
<tag> electron </tag>
</tags>
</entry>
<entry>
<title>解决下载electron缓慢</title>
<link href="/2019/12/17/solve-download-electron-release-slow/"/>
<url>/2019/12/17/solve-download-electron-release-slow/</url>
<content type="html"><![CDATA[<p>  由于近期在做electron开发,我发现如果需要切换或下载electron版本时会非常慢,有时候甚至会停滞无法下载。之前在medium上看到过类似文章,在国外也存在下载非常缓慢的情况,只有5k/s。因为electron的release都是发布在github上的,我们下载release也是从github上下载的。<br><a id="more"></a></p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p>  如果你访问github足够快,可以考虑手动下载release到<code><USER_HOME>/.electron</code>目录下。<br>  基本上国内的开发者都会使用taobao源下载npm包,当然electron也不例外,淘宝镜像也有对应的electron的release。但我发现使用<code>npm install</code>or<code>yarn install</code>时仍然是从github下载。所以我们需要用到<code>electron-download</code>来下载。</p><pre><code class="shell">yarn add -g electron-downloadornpm install -g electron-download</code></pre><pre><code class="shell">electron-download --mirror=https://npm.taobao.org/mirrors/electron/ --version=5.0.12 --platform=darwin --arch=x64</code></pre><p>  version、platform、arch等参数参考<code>electron-download</code>的说明。<br>  下载之后需要删除<code>node_modules/electron</code>然后重新<code>npm install</code>或<code>yarn install</code>。</p>]]></content>
<categories>
<category> electron </category>
</categories>
<tags>
<tag> electron </tag>
</tags>
</entry>
<entry>
<title>解决github访问慢</title>
<link href="/2019/02/21/solve-github-access-slow/"/>
<url>/2019/02/21/solve-github-access-slow/</url>
<content type="html"><![CDATA[<p>  github在日常开发中会经常使用到,但其服务器部署在国外,我们访问会有较大的延迟(曾经还被墙),有时候即使用代理访问速度同样较慢,严重时甚至无法拉代码。我们可以通过修改<code>host</code>的方式一定程度上的解决该问题。<br><a id="more"></a></p><p>  首先尝试<code>ping</code>以下<code>github.com</code>,如果你的延迟在300ms左右是较为正常的。但大部分人可能会存在<code>ping</code>不通的情况。</p><pre><code class="shell">$ ping github.comPING github.com (13.250.177.223): 56 data bytesRequest timeout for icmp_seq 0Request timeout for icmp_seq 1Request timeout for icmp_seq 2Request timeout for icmp_seq 3Request timeout for icmp_seq 4--- github.com ping statistics ---6 packets transmitted, 0 packets received, 100.0% packet loss</code></pre><p>  经查询该ip位于新加坡,国内访问<code>github.com</code>大部分会走新加坡和上海两个地区。我尝试<code>ping</code>这两个地区的ip发现都<code>ping</code>不通。</p><p>  通过<a href="http://tool.chinaz.com/dns" target="_blank" rel="noopener">站长之家DNS查询工具</a>,可以查询<code>github.com</code>的服务器地址。在国内查询仅能查询到三个地区:新加坡、上海、美国。</p><p>  查询到对应的ip之后,在<code>host</code>文件中添加一条记录即可。ip地址可以选择自己线路最优的,推荐<code>192.30.253.112</code>和<code>192.30.253.113</code>这两个ip段,速度不算最快但经测试是最稳定的。</p><p>  修改<code>host</code>以mac为例:</p><pre><code class="shell">// 1.sudo vim /etc/hosts// 输入密码后继续,按i键入// 添加一条记录192.30.253.112 github.com// 按ESC,键入:wq!</code></pre><p>  通过浏览器访问github.com,速度虽达不到秒开,但较之前,在可以接受的范围之内。此时尝试<code>ping`</code>github.com<code>会发现,能</code>ping`通,延迟300ms+,且丢包率较低。</p>]]></content>
<categories>
<category> 网络 </category>
</categories>
<tags>
<tag> 网络 </tag>
</tags>
</entry>
<entry>
<title>iTerm2下使用命令行代理</title>
<link href="/2019/01/27/item2-user-command-line-proxy/"/>
<url>/2019/01/27/item2-user-command-line-proxy/</url>
<content type="html"><![CDATA[<p>  在使用ss、v2ray等fq时,并不能在命令行进行代理。但我们实际使用时可能会经常在命令行下通过brew安装应用或者clone代码到本地。不使用代理会非常慢,有些时候github完全访问不了,代码都拉不下来。<br>  如果在命令行下使用代理的话,上诉问题能够得到一定程度解决。如果你会搭建软路由的话,直接使用即可,无需额外配置。如果不会或者不想折腾可以直接通过在命令行配置代理的方式实现。<br><a id="more"></a></p><p>  这里以ss为例,在<code>.zshrc</code>中键入以下命令,保存,source执行以下该文件或重启iTerm。</p><pre><code class="shell">// 这里的代理地址需要修改为自己的,一般为http://127.0.0.1// 端口可能不同,需要查看ss设置export proxyon=Socks5://127.0.0.1:1087</code></pre><p>  然后查看代理是否生效:</p><pre><code class="shell">// 输入curl ip.gs// 输出Current IP / 当前 IP: xxx.xxx.xxx.xxxISP / 运营商: xxxxxxxxCity / 城市: xxxxxxCountry / 国家: xxxxIP.GS is now IP.SB, please visit https://ip.sb/ for more information. / IP.GS 已更改为 IP.SB ,请访问 https://ip.sb/ 获取更详细 IP 信息!Please join Telegram group https://t.me/sbfans if you have any issues. / 如有问题,请加入 Telegram 群 https://t.me/sbfans</code></pre><p>  为了方便使用,我们使用函数给它添加上开启提示,以及测试ip。在<code>.zshrc</code>输入以下内容,保存,source执行该文件。</p><pre><code class="shell">proxyon () { export http_proxy=Socks5://127.0.0.1:1086 export https_proxy=Socks5://127.0.0.1:1086 echo "http/https proxy on." curl ip.gs}proxyoff () { unset http_proxy unset https_proxy echo "http/https proxy off." curl ip.gs}</code></pre><p>  使用命令<code>proxyon</code>和<code>proxyoff</code>即可在当前终端开启和关闭代理。</p>]]></content>
<categories>
<category> 网络 </category>
</categories>
<tags>
<tag> proxy </tag>
<tag> network </tag>
</tags>
</entry>
<entry>
<title>如何使用Git进行版本控制管理hexo博客</title>
<link href="/2019/01/12/git-upload-subfolder/"/>
<url>/2019/01/12/git-upload-subfolder/</url>
<content type="html"><![CDATA[<p>  使用hexo或其它静态博客都会有这样的问题:博客的配置文件<code>_config.yml</code>、主题配置等如何保存?我们常用的电脑会存一份,但是把鸡蛋都放在一个篮子里是绝对不妥的,保不准电脑出故障或者其它问题。最稳妥的方式还是使用版本控制,由于个人的配置还是有一些私密性,所以要选择能建私有仓的。2018年1月9号github的私有仓免费了,是个不错的选择,另外gitee也能使用免费私有仓,速度较github要快一些。我之前是放在gitee上的,但github能用私有仓了,我还是选择切换到github上。</p><p>  hexo静态博客的主题多数会用别人开发的,克隆开发者的仓库,修改配置然后使用。也就是说我们的<code>hexo/</code>文件夹下还有一个版本控制的仓库?如何用版本控制管理呢?</p><a id="more"></a><h4 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h4><p>  首先,hexo的配置我们肯定是要进行版本控制的,但主题有两个问题:</p><blockquote><ol><li>自己没能力修改主题,解决问题,需要开发者修复问题,或者希望开发者提供新功能。</li><li>自己能解决问题,能在开发者的基础上进行二次开发。</li></ol></blockquote><p>  主要针对这两种情况进行说明如何进行版本控制。</p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><h5 id="第一种:不需要对主题进行版本管理,只管理配置文件即可"><a href="#第一种:不需要对主题进行版本管理,只管理配置文件即可" class="headerlink" title="第一种:不需要对主题进行版本管理,只管理配置文件即可"></a>第一种:不需要对主题进行版本管理,只管理配置文件即可</h5><p>  这种情况主题的开发交给开发者来做,自己只需定时<code>pull</code>一下代码即可,但推荐将配置文件保存一份,重命名放在<code>hexo/</code>下,与hexo的配置一同进行版本控制。步骤:</p><blockquote><ol><li>在github或gitee上创建一个私有仓</li><li>在hexo文件夹下执行<code>git init</code></li><li>添加远程仓库地址,<code>git remote add github repository_path</code>(github是远程仓库别名,可以另取,repository_path是远程仓库地址)</li><li>先拉一下远程仓库的代码,<code>git fetch github</code></li><li>将自己主题的配置复制一份,重命名放到<code>hexo/</code>下</li><li>填写<code>.gitignore</code>忽略一些不需要进行管理的文件或文件夹,下面是我的配置</li><li>将自己的代码提交并push到github上即可</li></ol></blockquote><pre><code class="shell">.DS_StoreThumbs.dbdb.json*.lognode_modules/public/.deploy*/</code></pre><h5 id="第二种:会对主题进行二次开发,要对主题进行版本控制"><a href="#第二种:会对主题进行二次开发,要对主题进行版本控制" class="headerlink" title="第二种:会对主题进行二次开发,要对主题进行版本控制"></a>第二种:会对主题进行二次开发,要对主题进行版本控制</h5><p>  因为主题仓库有自己的版本控制,不过我们也不需要,之前查了下,好像submodule也不满足需求。反正我们也不需要使用原来的版本控制,那我们直接删掉<code>.git</code>在本地看看,git有没有对主题文件夹进行管理,通常第一次对hexo文件夹下的内容进行版本控制,删掉<code>.git</code>就能使git对其进行版本控制,如果已经进行过版本控制或添加过submoudle,即使删掉<code>.git</code>也不会生效,原因是git在本地会有缓存,需要删除本地缓存才能使git重新对其进行版本控制。步骤:</p><blockquote><p> 1.2.3.4同上</p><ol start="5"><li>先删掉主题目录下的<code>.git</code>和<code>.gitignore</code>,本地使用<code>git status</code>看git有没有对主题进行版本控制,如果没有,可能是我上诉的两种情况。<code>git rm -r --cached themes/theme_name</code>theme_name替换为自己的主题名,再用<code>git status</code>查看一下,正常情况git就会管理主题文件夹了。</li><li>7.同上</li></ol></blockquote><h4 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h4><p>  当初从wordpress切换到hexo还是挺值的,速度大大提升了,也不用操心服务器、数据库什么的,而且还可以结合自己的能力进行二次开发。当初git用的不熟,还是存在很多问题,折腾了很久。写这篇博客才了解了git本地缓存的问题。</p>]]></content>
<categories>
<category> git </category>
</categories>
<tags>
<tag> git </tag>
</tags>
</entry>
<entry>
<title>2018年度总结</title>
<link href="/2019/01/06/2018-summary/"/>
<url>/2019/01/06/2018-summary/</url>
<content type="html"><![CDATA[<p>  2018年就这么过去了,在这里补上一下过去一年的总结,感觉还是发生了很多事情,希望能通过总结记录一下,鞭策自己新的一年要做出改变,顺便也立立flag。</p><a id="more"></a><h4 id="过去一年"><a href="#过去一年" class="headerlink" title="过去一年"></a>过去一年</h4><p>  1-2月份,在实验室给导师做项目,早9晚9,周末开始有一天休息到后面没有,这时候学校也很冷清,因为12月底就已经考完试放假回家了。那段时间最多的感觉就是背痛,但所幸有些提升。记忆最深刻的就是每天快接近9点的时候去实验室路过图书馆有一个学姐每天准时会在那里背书,十分佩服,那时候考研刚结束,几乎提前一年就准备了。而此时同学已经有投实习简历的了。最后,在过年前几天回到了家。</p><p>  开学新学期伊使,我都没有多少打算去找实习,总给自己找借口,自己还要多准备一下或是我哥淘宝店那边需要帮忙,就这样欺骗自己。4月底左右才准备好自己的简历,开始投一投,但是黄金时期已过,一二线的公司实习基本已经招够了。一直都没有收到面试邀请,趁着五一和室友一起出去玩了一次,也算我第一次旅游吧,虽然累,但是忘记了很多烦恼。回学校之后就开始积极的找实习,还是受挫不少,但是从面试中也学到了很多东西。虽然成都有家公司给了offer,最后给拒了,感觉去了可能对自己没有提升,因为就是做一些H5的活动页。期间也有几家感觉稳了最后挂掉了,中间心态也有变化,所幸7月份的时候飞鱼给了offer跟之前面试的同事交流的也还不错,就打算实习了。</p><p>  实习期做了三个项目,因为人员预算有限,大部分时间都只有我一个人负责前端开发。刚好两个月上线了区块链社区<a href="https://www.liank.me" target="_blank" rel="noopener">链客</a>,没有推广,也没做SEO,所以很少有人关注。期间花了一些时间做了一款应用分发内测平台,公司有部分团队使用了,感觉还是很有成就感,上线出了问题也会积极的处理。后面参与了游戏持续集成系统的前端开发,因为东西很多,开始还觉得自己可能不能胜任。所以花了两天时间对整个项目结构进行了设计,以保证开发阶段少出问题,事实上也有一定作用,但是后来东西越来越多,一个人开发也略感孤独,很多地方也不知道合不合理。最后在12月底,搞定了流水线配置的几个问题,最后跑通了,成就感也很足,虽然没有机会看到它成型。</p><p>  在飞鱼的这段时间过的还是比较开心的,结识的同事,虽然前端方面基本都靠我自己摸索,这个过程也明显感到比较孤独。但部门老大和另外两个同事也教会我很多东西,拓展我的信息量,如何解决问题,以及各种方便开发的工具、chrome拓展。总体来说,在飞鱼收获还是蛮大的,但在前端方面确实还是需要有人指引或探讨更好。所以,这阶段一直打算找一个有成熟的前端团队的公司,但面过头条和阿里之后,由于一直处于快速开发,没有太多时间,就放弃了。</p><p>  总的来说,这一年蛮多遗憾的,后悔没有早点准备找实习,错过了机会,秋招由于签了三方和工作也放弃了很多机会。上半年打球运动较多,身体还可以。出来实习,很多事操心,公司伙食又比较好,导致肚子胖了一小圈,总说着要减掉,却也连续做几天仰卧起坐就放弃了,给自己定的隔几天打一次球也没有实现。</p><h4 id="新一年"><a href="#新一年" class="headerlink" title="新一年"></a>新一年</h4><p>  首先毕设肯定要好好做,然后在毕业前和室友旅行一次,拍点值得纪念的校园生活,好好回学校打两个月篮球。另外,好好沉淀一下,多学点东西。多运动一下,保证身体健康,减掉自己的小肚子。</p>]]></content>
<categories>
<category> 总结 </category>
</categories>
<tags>
<tag> 年度总结 </tag>
</tags>
</entry>
<entry>
<title>hexo博客自定义域名添加HTTPS</title>
<link href="/2018/12/31/hexo-enable-https/"/>
<url>/2018/12/31/hexo-enable-https/</url>
<content type="html"><![CDATA[<p>  不知从去年什么时候起,各大网站都上了https。7月底从wordpress转到hexo,静态博客速度确实很快,秒杀wordpress,也不用操心维护,只专注于写作。期间换过一个主题两个主题,都是基于Vue的,简单的配置了https,没有发现任何问题,最近新换了一个主题发现仅有首页是https,文章和其他页都是http,花了点时间解决,分享一下。</p><a id="more"></a><h4 id="HTTP和HTTPS"><a href="#HTTP和HTTPS" class="headerlink" title="HTTP和HTTPS"></a>HTTP和HTTPS</h4><blockquote><p>HTTP:互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。</p><p>HTTPS:以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。</p></blockquote><p><strong>HTTP和HTTPS主要区别:</strong></p><blockquote><ol><li>https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。</li><li>http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。</li><li>http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。</li><li>http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。</li></ol></blockquote><p><strong>为什么要使用HTTPS</strong></p><p>  HTTPS的主要作用有两点:一是保证数据安全,二是确认网站的真实性。现在使用http,chrome会提示不安全,而https则会带一把小绿锁。所以我的博客添加https的作用就是为自己加一个小绿锁:)。</p><h4 id="hexo博客两种添加HTTPS的方式"><a href="#hexo博客两种添加HTTPS的方式" class="headerlink" title="hexo博客两种添加HTTPS的方式"></a>hexo博客两种添加HTTPS的方式</h4><p><strong>Github强制HTTPS</strong></p><p><img src="https://i.loli.net/2019/01/10/5c3756e423ac6.png" alt="https.png"></p><p>  直接在Github pages的配置中勾选Enforce HTTPS选项即可开启HTTPS,虽然也能解决小绿锁的问题,但是却不能用到免费的CDN加速,对于使用Vue/React开发的主题,首屏加载可能较慢。所以采用第二种方案较好。</p><p><strong>CloudFlare</strong></p><p>  注册cloudflare和如何绑定自己的域名就不赘诉了。首先,在DNS添加一条CNAME记录,指向github提供的pages地址。记得使用cloudflare就不要开启github的强制https,否则可能会导致你的博客访问不了。</p><p><img src="https://i.loli.net/2019/01/10/5c375a5080bb2.png" alt="dns.png"></p><p>  然后在Crypto中选择创建一个证书,根据自己需要填写。</p><p><img src="https://i.loli.net/2019/01/10/5c375add948a1.png" alt="ssl.png"></p><p>  创建完成后,再添加一下Page Rules,用户可以免费创建3个Page Rules。</p><p>  第一个让匹配的URL都强制走HTTPS,记得尽量使用通配符<code>*</code>尽可能多的匹配到会用到的URL。我就是因为前面一直用的SPA,一直没发现自己只配了主页。直到最近换了主题才发现。</p><p><img src="https://i.loli.net/2019/01/10/5c375d21e9669.png" alt="pagerules1.png"></p><p>  第二个,可以让CloudFlare的CDN中缓存你的静态也页面。一定程度上能提高访问速度。</p><p><img src="https://i.loli.net/2019/01/10/5c375d21ee0ec.png" alt="pagerules2.png"></p><p>  配置完成后,可能要等一会儿才能生效。</p><h4 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h4><p>  其实这两种开启https的方式使用的CA证书都不是你自己的,都是两个提供商的。不过也有方式可以实现个人网站的证书,一是cloudflare的证书可以升级,5$一个月对于个人用户还是比较贵了。另外,可以使用<strong>Let’s Encrypt</strong>通配符证书。</p>]]></content>
<categories>
<category> 网络 </category>
</categories>
<tags>
<tag> https </tag>
</tags>
</entry>
<entry>
<title>踩坑的问题</title>
<link href="/2018/12/23/vue-deep-and-shallow-copy/"/>
<url>/2018/12/23/vue-deep-and-shallow-copy/</url>
<content type="html"><![CDATA[<p>  这周在完成一个功能之后,回过头去看自己的代码竟然发现自己有点看不懂了,也才完成一周不到的时间。看不懂的原因也很简单,父组件有一个对象数组,传递给其中一个子组件,然后会对组件中的对象修改,而修改的操作是这个子组件的兄弟组件去完成的,修改完成之后会将修改后的结果返回给父组件,然而子组件中的数据也相应的修改了,不明白其原因。想了一会儿才关联到可能是跟浅拷贝有关。<br><a id="more"></a></p><pre><code class="vue">// 父组件<template lang="html"> <div class="parent"> <eldest-son :arr="arr"></eldest-son> <younger-son :arrItem="arrItem" @data-changed="dataChanged"></younger-son> </div></template><script lang="ts"> public arr: Person[] = [ { name: 'bob', age: 18, }, { name: 'jack', age: 14, }, ]; public arrItem: Person = arr[0]; public dataChanged(newPerson: Person): void { this.arrItem = newPerson; }</script></code></pre><p>  问题差不多就是这样,更恰好的是当时的功能正好是这样的需求,就是要修改<code>eldest-son</code>中的数据。写完之后去看觉得通过数据渲染的框架,对待数据会严谨很多,第一时间并没有往浅拷贝上去想这个问题,看了一会儿之后发现是对数组进行操作的,才联想到浅拷贝。</p><p>  在JavaScript中,深、浅拷贝问题只针对复杂对象,array和object。关于深浅拷贝之前已经看过一些文章了,也是一个很简单的问题,就不再赘诉深浅拷贝的原理和实现了,有很多优秀的文章。而恰好我这里的需求就是要通过浅拷贝这种方式实现,不然在子组件中修改后的数据还要通过父组件中转一下。</p><p>  通过这个小问题,再次让我意识到注释的重要性,即使是自己一个人开发项目。有时候解决问题的想法可能是在一瞬间得到的灵感,写完之后虽然觉得实现的简单,如果不加上注释,后面再看代码的时候可能很难想到,就要花更多的时间去读。所以,还是得写一篇文章来警示下自己,有些东西对于自己再简单,尽可能的在实现功能后加上注释,方便别人也方便自己阅读。</p><p>  另外联想到的就是,遇到的一些小问题一定要记录一下,特别是自己做过的一些东西,如果不记一下很快就忘掉了,下次遇到的时候还会再犯错,是很不值得的一件事。现在已经有一点习惯,会在自己遇到一些问题的时候,会用网易云笔记记录下来,希望能继续保持这样的态度。</p><p>  最后感觉自己读书和看文档的专注度不够,只看到了皮毛,学会了使用,在使用的过程中还会踩坑。我觉得这样也并不是一件坏事,但这提醒了我,看完一定要尝试一下,及时发现不足,然后弥补。现在感觉好像好些书我都得再读一遍啊…</p>]]></content>
<categories>
<category> 其他 </category>
</categories>
<tags>
<tag> 踩坑 </tag>
</tags>
</entry>
<entry>
<title>关于安装双通道内存</title>
<link href="/2018/12/15/about-dual-channel-memory/"/>
<url>/2018/12/15/about-dual-channel-memory/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  自己之前的主力机(宏碁的V神系列),上大学的时候买的,在当时算性价比比较高的了,而且杜比音效和屏幕真的秒杀当时同寝室以及隔壁寝室的其他本子。但是买回来用了一段时间发现体验不是很好,就是慢、卡顿,还拿去买笔记本的店让老板给我看下,然后他就重装了个系统(当时还是个萌新),后面回去用了一段时间之后还是老问题,就攒钱加了个固态,简直是质的飞跃,一直到现在。</p><p>  虽然现在没怎么用那个本子了,但是也不舍得送人或者卖掉,偶尔想打打游戏,电脑又很吃力,CPU和显卡(外接显卡)又没法升级,就内存看来比较小了。听说两个4G的双通道要强于单个8G的,又不太懂内存双通道的东西,就买了一个同型号同频率不同牌的内存条升级。<br><a id="more"></a></p><h4 id="关于双通道"><a href="#关于双通道" class="headerlink" title="关于双通道"></a>关于双通道</h4><p>  双通道,就是MCH芯片级里设计两个内存控制器,这两个内存控制器可相互独立工作,每个控制器控制一个内存通道。在这两个内存通道,CPU可分别寻址、读取数据,从而使内存的带宽增加一倍,读取速度也相应增加一倍。</p><h4 id="安装双通道注意事项"><a href="#安装双通道注意事项" class="headerlink" title="安装双通道注意事项"></a>安装双通道注意事项</h4><p>  <strong>主板不支持弹性双通道的情况</strong></p><blockquote><p>  1.相同容量</p><p>  2.相同内存条参数(时序、频率、品牌)</p><p>  3.对称的内存插槽位置</p></blockquote><p>  我买的时候主要考虑容量和频率,感觉品牌对于组双通道并不重要,当然相同的当然更好。一般笔记本都是支持双通道的,特殊的轻薄本、便携本除外。</p><h4 id="弹性双通道"><a href="#弹性双通道" class="headerlink" title="弹性双通道"></a>弹性双通道</h4><p>  <strong>弹性双通道技术拥有以下两种双通道内存工作模式:</strong></p><p>  1.对称双通道工作模式</p><p>  对称双通道工作模式要求两个通道的内存容量相等,该模式下可使用 2个、3个或 4个内存条获得双通道模式,如果使用的内存模块速度不同,内存通道速度取决于系统中安装的速度最慢的内存模块速度。</p><blockquote><ol><li>内存容量对称。对称双通道工作模式不要求两个通道中的内存条数量相等,可由3条内存组成双通道,两个通道的内存总容量相等就可以。例如A通道为2G内存条+2G内存条,B通道为一条4GB内存条,A通道和B通道总容量相等。</li><li>内存模组对称。即分别在相同颜色的插槽中插入相同容量的内存条,内存条数为2或4,该模式下所有的内存都工作在双通道模式下,性能最强。</li></ol></blockquote><p>  2.非对称双通道工作模式</p><p>  在非对称双通道模式下,两个通道的内存容量可以不相等,而组成双通道的内存容量大小取决于容量较小的那个通道。例如A通道有4GB内存条,B 通道有8GB内存条,则A通道中的4GB和B通道中的4GB组成双通道,B通道剩下的4GB内存仍工作于单通道模式下。同样的,两条内存条必须插在相同颜色的插槽中。</p><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><p>  笔记本组双通道相对简单,一般支持双通道的本子只有两个插槽,只支持对称双通道,选用同频率、同型号就可以了,感觉电压和品牌不重要。</p><p>  台式机一般都支持两种双通道工作模式,主板一般用颜色区分单、双通道,将两条规格相同的内存条间隔插入到相同颜色的插槽中,就可以了。</p><h4 id="检测"><a href="#检测" class="headerlink" title="检测"></a>检测</h4><p>  看一般装机的都是使用<code>cpu-z</code>来检测CPU、内存、硬盘等信息,推荐使用该软件测试。</p>]]></content>
<categories>
<category> computer </category>
</categories>
<tags>
<tag> 内存条 </tag>
</tags>
</entry>
<entry>
<title>Vue动态组件</title>
<link href="/2018/12/08/vue-dynamic-components/"/>
<url>/2018/12/08/vue-dynamic-components/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  之前对Vue的了解很局限,感觉上和我实验室老师的Nodom框架很像,也可以说Nodom有一些借鉴Vue的地方,所以当时学习Vue也只是草草的入了个门,然后就从做demo开始学习。最近觉得自己用Vue好像到瓶颈期了,因为觉得Vue的教程是最好的资料,就准备认真的在过一遍Vue的教程。<br><a id="more"></a></p><h4 id="动态组件-amp-异步组件"><a href="#动态组件-amp-异步组件" class="headerlink" title="动态组件&异步组件"></a>动态组件&异步组件</h4><p>  由于项目的问题,要解决从服务端获取表单内容,来动态生成表单,这就有点触及我的知识盲区了。难道要一个一个页面写,这也太傻了吧。了解了之后大概可以分为三种可以使用的方法,动态组件、异步组件、JSX。JSX了解不多,就不深入了。异步组件:</p><pre><code class="vue">Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 向 `resolve` 回调传递组件定义 resolve({ template: '<div>I am async!</div>' }) }, 1000)})</code></pre><p>  通过定义工厂函数来异步解析组件定义,只有组件在需要被渲染的时候才会触发这个工厂函数,而且Vue会将结果缓存起来供未来使用。不适用动态生成多个不同表单的应用场景。</p><p>  动态组件:</p><pre><code class="vue"><component v-bind:is="currentTabComponent"></component></code></pre><p>  使用Vue的is特性来决定要加载的组件,针对使用element-ui、iview等ui框架的话生成表单、验证等要方便的多。这样一来就可以定义一套格式规范,从服务端拿到这些数据然后根据type选择相应组件生成表单,只要把统一的样式定好,就基本可以实现动态生成组件了。以iview为例:</p><pre><code class="vue"><!-- html --><Form> <FormItem> <component :is="componentName"></component> </FormItem></Form>// tspublic componentName: string = 'Input';</code></pre><h4 id="不同表单的数据问题"><a href="#不同表单的数据问题" class="headerlink" title="不同表单的数据问题"></a>不同表单的数据问题</h4><p>  实现生成动态表单的问题已经解决了,但是由于我的需要不仅要生成表单还要拿到相应的数据。</p><p>  经过几天的思考(当然这几天还在做其他的东西),暂时想到了一种解决方案,因为vue的v-model就是拿来做数据的双向绑定的,肯定要围绕这一点来想办法。因为所有生成表单的信息都要来自服务端,那么这些事肯定也要一并从服务端拿了,初步的想法是统一一个字段名来做form的数据对象名,叫<code>plugin-form</code>,然后在每个表单元素中定义一个字段名<code>plugin-form-item</code>,这样通过v-model就可以进行数据的双向绑定,用户进行了输入以后,数据就会绑定到相应字段上,只需遍历整个表单元素,将数据封装成对象就可以了。</p><pre><code class="json">// 服务端数据返回 json{ plugin_name: 'xxx', ··· plugin_form: 'appForm', form_item: [ { type: 'Input', plugin_form_item: 'app_name', ··· }, { type: 'Input', plugin_form_item: 'project_path', ··· }, ]}</code></pre><pre><code class="json">// 通过v-model拿到的数据appForm: { app_name: '用户输入', project_path: '用户输入'}</code></pre><h4 id="提交-amp-数据校验问题"><a href="#提交-amp-数据校验问题" class="headerlink" title="提交&数据校验问题"></a>提交&数据校验问题</h4><p>  因为要涉及很多个不同的表单提交,可以用button提交暂存到localstorage,然后依次取出提交就可以了。关于校验可能要麻烦一些,还没有想好结构,但使用async-validator应该也没有太大问题。</p><h4 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h4><p>  动态组件的意思就像是给组件加了一个v-if,只不过用is特性的方式看起来更优雅一些。想到动态组件,因为Vue官方教程切换tab时用的是动态组件,我一般使用的是router-view,然后上了下思否好像基本都是使用的router-view。</p>]]></content>
<categories>
<category> Vue </category>
</categories>
<tags>
<tag> Vue </tag>
<tag> components </tag>
</tags>
</entry>
<entry>
<title>深入浅出webpack读后感</title>
<link href="/2018/12/01/dive-into-webpack-note/"/>
<url>/2018/12/01/dive-into-webpack-note/</url>
<content type="html"><![CDATA[<p>  《深入浅出webpack》这本书是我在5.6月份的时候买的,现在想来真是太傻<em>了。这么说吧看了下这本书的印刷版次是2018年1月,作者在序中写到写书的时候是v3.8.1,书印刷出版之后,2月份v4.0就正式发布了。自从有了这次教训我发誓再也不会买这类工具书了,或者说前端相关的书也少买了,因为更新速度实在太快了,不过像《JavaScript语言精粹》、《CSS揭秘》这些书还是蛮推荐购买支持一下的,至少读起来不会过时。<br><a id="more"></a><br>  《深入浅出webpack》这本书是我在5.6月份的时候买的,现在想来真是太傻</em>了。这么说吧看了下这本书的印刷版次是2018年1月,作者在序中写到写书的时候是v3.8.1,书印刷出版之后,2月份v4.0就正式发布了。自从有了这次教训我发誓再也不会买这类工具书了,或者说前端相关的书也少买了,因为更新速度实在太快了,不过像《JavaScript语言精粹》、《CSS揭秘》这些书还是蛮推荐购买支持一下的,至少读起来不会过时。</p><p>  先回到书中,这本书作为入门webpack翻阅一下还是有一定的好处,毕竟作者将其踩过的坑告诉了你,让你避免踩同样的坑。第一章入门就大概讲了一下前端的发展和使用的技术,然后简单的提了webpack的核心loader、plugin,做了一个简单的入门的教程。第二章花了很大的篇幅介绍了webpack的entry、output、loader、plugin等具体配置,最后给出了一个总结性的配置。第三章实战就给出了一些实际的应用比如使用ES6、TypeScript、Vue框架、加载图片以及SourceMap等。在第四章优化给出了一些优化的相关内容,第五章最后给出了一些原理,如编写loader、plugin等。</p><p>  最后来总结下这本书,整体的结构设计还可以,但第二章介绍了大量的配置我觉得不太合理,东西太多了,说不定看着看着就放弃了。然后还介绍了如何应用,还介绍了如何编写loader和plugin,我觉得都是比较实在的,符合工具书的实用性。</p><p>  然后再来吐槽一下,首先作者后来把书开源了,但是第一章过后,一翻页就会弹出modal无法翻页页没法关闭,上面写着大大的几个字请购买本书纸质版,我觉得这样的方式和开源思想是有悖的。另外就是书的定价79,一本工具书是否太贵了些,就一《javascript设计模式与实践》这本书来说,写的还可以,评价也很高,书还厚的多,纸张质量也好一些,都能良心的定价59,一本工具书怎么能卖这个价的,还好我买的时候京东有优惠。另外书中那些使用实例我觉得还是以博客的方式呈现要好的多。</p><p>  对于我来说确实让我对webpack的了解更多了一些,不会像刚开始上手vue cli的时候那么手足无措了,很多东西都不知道怎么弄。另外书中还有几处是因为webpack版本的问题的错误,导致运行处问题,希望其他的读者发现配置跑着有问题,检查一两次配置之后,去搜搜是不是相关用法的版本问题可能会少走弯路。</p>]]></content>
<categories>
<category> webpack </category>
</categories>
<tags>
<tag> 读书笔记 </tag>
<tag> webpack </tag>
</tags>
</entry>
<entry>
<title>前端模块化</title>
<link href="/2018/11/25/front-end-modular/"/>
<url>/2018/11/25/front-end-modular/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  模块化是前端开发的一个重要理念/思想,随着web的发展,嵌入网页的代码js代码越来越来,web开发变得越来越复杂,大型的web应用的复杂程度可能超过了桌面程序,所以模块化的思想因此而发展起来。模块就是实现特定功能的相互独立的一组方法。有了模块,就可以更好的管理web应用的逻辑,使用这种方式在不仅能提高代码复用率、可维护性,还能作为开源项目提供给开发者使用。</p><p>  模块化的发展经历了几个阶段,现在逐渐发展成熟,特别是ES6的模块化,引用前端圈的话可以说是王者归来。有了模块,为了开发者能够方便的加载模块,就需要一套编写模块的规范了,比较流行的有CommonJS、AMD、以及ES6模块化规范。我觉得学习的重点主要还是ES6模块化规范,毕竟被誉为终极解决方案、王者归来等等。目前nodejs还在使用CommonJS,但相信不久的将来nodejs也会转而使用ES6模块化规范。<br><a id="more"></a></p><h4 id="CommonJS"><a href="#CommonJS" class="headerlink" title="CommonJS"></a>CommonJS</h4><p>  CommonJS核心思想是通过<code>require</code>方法<strong>同步</strong>加载依赖的其他模块,通过<code>module.exports</code>导出需要暴露的接口和变量。如上面所说,CommonJS的流行是因为nodejs采用了这种方式,CommonJS也被用于web开发中。</p><pre><code class="javascript">// 采用ES5语法// 定义一个math模块var baseCount = 0;function add(a, b) { return a + b;}// 暴露需要导出的接口和变量module.exports = { add: add, baseCount: baseCount}// 引入自定义模块var math = require('./math')// 引入核心模块不需要带路径var http = require('http')</code></pre><p>优点:</p><p>  代码可复用于nodejs环境下,通过npm发布的第三方模块大多都采用CommonJS规范(现在更多的则是ES6模块规范)</p><p>缺点:</p><p>  采用同步加载模块的方式,在服务端很适用,因为模块文件都存在本地,加载快。浏览器就会受限于网络原因,更好的方式是使用异步加载。</p><h4 id="AMD"><a href="#AMD" class="headerlink" title="AMD"></a>AMD</h4><p>  AMD也是一种模块化规范,其采用了异步加载模块的方式,主要是用来解决浏览器环境的模块化问题。</p><pre><code class="javascript">// 定义模块define('module', ['dep'], function(dep) { return exports;});// 导入使用require(['module'], function(module) {});</code></pre><p>优点:</p><p>  在不转换代码的情况下可直接运行于浏览器环境和nodejs环境,可以异步加载多个模块。</p><p>缺点:</p><p>  没有原生支持,需要导入AMD的库才可以使用。</p><h4 id="ES6"><a href="#ES6" class="headerlink" title="ES6"></a>ES6</h4><p>  ES6模块化是国际标准化组织ECMA提出的javascript模块化规范,在语言层面上实现了模块化。浏览器厂商和nodejs都宣布要原生支持该规范。它将逐渐取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。</p><pre><code class="javascript">// 导出export default Router;export default { // ...};export function hello() {};// 导入import { Component, Vue, Prop, ... } from 'vue-property-decorator';import Header from './components/Header.vue';</code></pre><p>  ES6模块化是终极模块化解决方案,但缺点是目前无法直接运行于大部分JavaScript环境,必须通过工具转换成标准的ES5才能正常运行。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>  现在这个阶段,前两者作个了解就可以了,重点还是ES6模块化规范,是未来的趋势,也是模块化的终极解决方案,现在也可以通过工具转换成标准的ES5代码运行。另外,如果看完我的博客觉得还不ok,推荐去看一下黄玄的<a href="http://huangxuan.me/js-module-7day/#/" target="_blank" rel="noopener">JavaScript模块化七日谈</a>。</p>]]></content>
<categories>
<category> 模块化 </category>
</categories>
<tags>
<tag> 模块化 </tag>
</tags>
</entry>
<entry>
<title>node.js+express搭建简单的web服务器</title>
<link href="/2018/11/11/node-express-http-server/"/>
<url>/2018/11/11/node-express-http-server/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  有朋友问我关于nodejs搭建web服务器的问题,我对node的了解也不多,所以简单的学习了解一下,帮朋友解决了一些问题。</p><p>  首先,很多人都在问nodejs适不适合web服务器开发。这个问题知乎上面已经有很多解答了,比较详细点赞较多的一条:<a href="https://www.zhihu.com/question/19653241/answer/15993549" target="_blank" rel="noopener">使用 Node.js 的优势和劣势都有哪些? - FengqiAsia的回答 - 知乎</a>,年代久远不知道是否适用。看了相关的介绍之后,我觉得nodejs是非常适合做web服务器的,网上大多的评价都是可靠性低,单进程、单线程,一个崩掉整个进程就崩掉之类的,异常难以定位。还有可能是从传统语言转过来写js的话,可能在一段时间内很难适应吧。</p><p>  看了下,linkedin、yahoo的服务器就是用node写的,还有之前去了解的unity-cache-server,说明node的优点是毋庸置疑的。可靠性低进程崩掉等缺点其实是都可以通过代码的健壮性来避免的(说着轻巧),另外异常处理相关的也有很多库和进程管理程序监控node的运行。<br><a id="more"></a></p><blockquote><p>作者:尤雨溪</p><p>链接:<a href="https://www.zhihu.com/question/20069184/answer/14385915" target="_blank" rel="noopener">https://www.zhihu.com/question/20069184/answer/14385915</a></p><p>来源:知乎</p><p>异步的思维是js的特点,也是node高并发性能优势的原因之一,你从传统的同步语言过来可能不习惯,但是像我们这种从前端写js过来的人就自然得像说话一样,关键还是适应。熟悉之后可以用async,Promise系 (q, bluebird) 或者 eventproxy 之类的库来改善代码嵌套的问题。</p><p>异常的问题 - Node 核心库的 API 抛异常大致有三种常见情况:<br>\1. 异步回调。按惯例,接收的回调函数第一个参数都是可能出现的异常,没有特殊情况的话你应该把异常按照同样的参数位置一层层传下去,直到最顶层的回调里进行统一处理。<br>\2. 同步版本的api会直接抛异常。所以如果确实无法避免抛错的可能,直接 try catch,要么就避免用同步版本。<br>\3. Stream形态的API,必须在stream对象上添加 error 的侦听函数,不然异常会直接抛出。</p><p>如果出现导致进程中断的异常,说明你的代码有逻辑层面的问题(以上几点没有完全做好),你应该在开发的时候发现并处理这些异常,而不是让它们在部署环境中发生。</p><p>如果你实在避免不了问题发生,你可以用 Node 的 Domain API 来对整块代码的异常进行捕捉。<br>另外可以用进程管理工具比如 forever, pm2 或是 monit 监视应用进程,崩溃后自动重启。</p><p>最后回到你的问题,node是否适合做web开发 - node的独特优势是高并发,高实时性,或者单页富前端的web应用,比如实时聊天,游戏,另外node也是写JSON API的最好选择。</p></blockquote><p>  另外看到了一条关于node的应用场景的回答,比较赞同,找不到链接了。大概是说node的适合 io 密集型的应用,能发挥出很好的性能,而 cpu 密集型的应用可能性能就不是最佳选择。估计这就是unity官方选择nodejs来写cacheserver的原因吧。</p><h4 id="一个简单的web服务器"><a href="#一个简单的web服务器" class="headerlink" title="一个简单的web服务器"></a>一个简单的web服务器</h4><p>  这里用了nodejs的第三方库express,express也有自己的脚手架。新建一个文件夹<code>http-server</code>,cd到该目录下:</p><pre><code class="shell">touch app.js # 新建app.js文件# 然后一顿回车,使用默认就ok,不过author可以填上自己的名字。package name: (http-server)version: (1.0.0)description:entry point: (app.js)test command:git repository:keywords:author: wangxlicense: (ISC)About to write to /Users/wangx/work/nodejs/http-server/package.json:{ "name": "http-server", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "wangx", "license": "ISC"}# 初始化npm init# 安装expressnpm install express --save</code></pre><p>  然后就可以在app.js写相关逻辑:</p><pre><code class="javascript">/** * http服务器 */// 引入expressconst express = require('express');// 创建express服务器const app = express();// get请求app.get('/', function (request, response) { // console.log(request) response.send('get请求成功')});// post请求app.post('/', function (request, response) { response.send('post请求成功')});// 绑定监听端口app.listen(9001);console.log('server启动成功')</code></pre><p>  try it,浏览器中输入<code>localhost:9001</code>就可以看到get请求成功了。简单的http服务是这样了。如果简单的写一个页面测试的话会有跨域问题:</p><pre><code class="javascript">app.all('*', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); next();});</code></pre><blockquote><p>Access-Control-Allow-Origin:允许的域,*为任意的域都可以访问</p><p>Access-Control-Allow-Headers:允许的header类型</p><p>Access-Control-Allow-Methods:允许的请求方法</p></blockquote><p>  路由:针对不同的路由有不同的处理方式</p><pre><code class="javascript">app.get('/home', function (request, response, next) { console.log('获取数据'); next();}, function (request, response) { response.send('get请求成功')});</code></pre><p>  get请求参数:request.query会把请求参数包装成字典对象,可通过点运算符获取请求参数</p><pre><code class="javascript">app.get('/home', function (request, response, next) { console.log(request.query.name); next();}, function (request, response) { response.send(`${request.query.name}请求成功`);});localhost:9001/home?name=wangx // wangx wangx请求成功</code></pre><p>  中间件:发送一个请求给服务器的时候,会被中间件拦截,先由中间件处理,每个中间件都有一个回调函数作为参数,拦截到参数,就会自动执行回调函数。<em>有中间件,会先执行中间件的回调函数,然后才会调用get或者post的回调函数,也就是当监听到请求,先执行中间件,才会到get、post请求。</em></p><pre><code class="javascript">// 中间件// 截取请求、拦截回调server.use('/home', function (request, response, next) { console.log('中间件') console.log(request.query.name) next()});</code></pre><p>  post请求参数:nodejs需要使用body-parse解析post请求参数,采用中间件解析post请求参数。完整代码:</p><pre><code class="javascript">// 引入expressconst express = require('express');// 引入 body-parseconst bodyParse = require('body-parse');// 创建express服务器const app = express();// 解析器const urlencoded = bodyParse.urlencoded({ extends:true });// 中间件const jsonParse = bodyParse.json();// 通过中间件处理,解析请求参数,存放在request.body中server.use('./home', jsonParse);// 处理请求参数app.post('./home', function (request, response) { console.log(request.body); response.send(request.body);});// 解决跨域app.all('*', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); next();});// 绑定监听端口app.listen(9001);console.log('server启动成功')</code></pre><p>  仅作为一个demo,关于node服务器的开发与设计,应该参考一下github上成熟的nodejs项目。</p><p>  <a href="https://github.com/bailicangdu/node-elm" target="_blank" rel="noopener">基于nodejs+mongodb构建的饿了么后台系统</a>,这是一个比较好参考的项目,前后端分离,内容比较完整,可以作为学习参考。</p>]]></content>
<categories>
<category> nodejs </category>
</categories>
<tags>
<tag> nodejs </tag>
<tag> express </tag>
<tag> http_server </tag>
</tags>
</entry>
<entry>
<title>快速生成项目结构图-tree命令的使用</title>
<link href="/2018/11/11/generate-project-structure-diagram/"/>
<url>/2018/11/11/generate-project-structure-diagram/</url>
<content type="html"><![CDATA[<pre><code class="shell">.├── app.js # 入口文件├── bin # 可执行文件│ └── www├── package-lock.json├── package.json # 项目信息及模块依赖├── public # 存放image、css、js等静态资源│ ├── images│ ├── javascripts│ └── stylesheets│ └── style.css├── routes # 路由文件│ ├── index.js│ └── users.js└── views # 视图/模版文件 ├── error.ejs └── index.ejs</code></pre><p>  上面这种树形项目结构图在前端项目介绍中非常常见,之前都以为是通过生成器生成或是手敲的,今天才发现linux下使用tree命令就可以直接生成上面的项目结构图。mark一下。<br><a id="more"></a></p><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><p>  这里就介绍mac的安装方法,通过homebrew安装就可以了。</p><pre><code class="shell">brew install tree# 安装完成之后查看安装成功与否tree --version# tree v1.7.0 (c) 1996 - 2014 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokorotree --help # 查看帮助</code></pre><pre><code class="shell">usage: tree [-acdfghilnpqrstuvxACDFJQNSUX] [-H baseHREF] [-T title ] [-L level [-R]] [-P pattern] [-I pattern] [-o filename] [--version] [--help] [--inodes] [--device] [--noreport] [--nolinks] [--dirsfirst] [--charset charset] [--filelimit[=]#] [--si] [--timefmt[=]<f>] [--sort[=]<name>] [--matchdirs] [--ignore-case] [--] [<directory list>] ------- Listing options ------- -a All files are listed. -d List directories only. -l Follow symbolic links like directories. -f Print the full path prefix for each file. -x Stay on current filesystem only. -L level Descend only level directories deep. -R Rerun tree when max dir level reached. -P pattern List only those files that match the pattern given. -I pattern Do not list files that match the given pattern. --ignore-case Ignore case when pattern matching. --matchdirs Include directory names in -P pattern matching. --noreport Turn off file/directory count at end of tree listing. --charset X Use charset X for terminal/HTML and indentation line output. --filelimit # Do not descend dirs with more than # files in them. --timefmt <f> Print and format time according to the format <f>. -o filename Output to file instead of stdout. -------- File options --------- -q Print non-printable characters as '?'. -N Print non-printable characters as is. -Q Quote filenames with double quotes. -p Print the protections for each file. -u Displays file owner or UID number. -g Displays file group owner or GID number. -s Print the size in bytes of each file. -h Print the size in a more human readable way. --si Like -h, but use in SI units (powers of 1000). -D Print the date of last modification or (-c) status change. -F Appends '/', '=', '*', '@', '|' or '>' as per ls -F. --inodes Print inode number of each file. --device Print device ID number to which each file belongs. ------- Sorting options ------- -v Sort files alphanumerically by version. -t Sort files by last modification time. -c Sort files by last status change time. -U Leave files unsorted. -r Reverse the order of the sort. --dirsfirst List directories before files (-U disables). --sort X Select sort: name,version,size,mtime,ctime. ------- Graphics options ------ -i Don't print indentation lines. -A Print ANSI lines graphic indentation lines. -S Print with CP437 (console) graphics indentation lines. -n Turn colorization off always (-C overrides). -C Turn colorization on always. ------- XML/HTML/JSON options ------- -X Prints out an XML representation of the tree. -J Prints out an JSON representation of the tree. -H baseHREF Prints out HTML format with baseHREF as top directory. -T string Replace the default HTML title and H1 header with string. --nolinks Turn off hyperlinks in HTML output. ---- Miscellaneous options ---- --version Print version and exit. --help Print usage and this help message and exit. -- Options processing terminator.</code></pre><p>  命令太多?了解了一下其实常用的只有下面着三个:</p><pre><code class="shell">-d # 只显示目录-I # 忽略目录或文件tree -I "node_modules|*.png"-f # 显示完整路径和文件</code></pre><p>  上面的项目结构图我是用以下命令生成的:</p><pre><code class="shell">tree -I "node_modules"</code></pre>]]></content>
<categories>
<category> Linux </category>
</categories>
<tags>
<tag> tree </tag>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>TypeScript泛型</title>
<link href="/2018/11/04/typescript-generics/"/>
<url>/2018/11/04/typescript-generics/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  之前上手typescript+vue的时候,vue也没有完全支持typescript,当时看typescript的文档的时候,就大概的过了一遍。当时也不是很清楚如何在vue中使用typescript,更别说泛型了。</p><p>  <em>软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。</em></p><p>  这是typescript官方文档的介绍,个人感觉不是太好理解,个人理解这里的组件更多的是指方法,官方的泛型示例也都是跟方法相关的。<br><a id="more"></a></p><h4 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h4><pre><code class="typescript">// 无返回值function doSomething(obj: any): void { // do something}// 有返回值function getSomething(obj: any): any { return obj;}</code></pre><p>  我在vue中就只使用了这两种,说起来也是尴尬,typescript的真正功力都没发挥出来,把typescript写成了javascript,还多堆了两个关键词的代码。其实之前在写的时候就有发现所有具有返回值的方法都指定any有何意义呢。</p><p>  typescript的基础类型也很多,如果一个方法接受一个参数直接进行返回,如果要用上强类型,是否需要写多个方法,仅仅是指定类型的不同,答案是否定的。一个方法能够接受任意类型的参数并返回相应类型的参数,就是泛型。泛型方法:</p><pre><code class="typescript">function identity<T>(arg: T): T { return T;}</code></pre><p><code>identity</code>方法就是泛型,它接受任意类型的参数,并可以对参数类型进行捕获,并将捕获的类型返回。这样的<code>identity</code>可以适用多个类型。定义了泛型方法之后,有两种方法可以进行使用,一种是传入参数和类型,第二种是使用类型推论:</p><pre><code class="typescript">// 传入参数和类型 T就是stringlet out = identity<string>("string"); // type: string//类型推论 T会根据参数类型推断为numberlet out = identity<number>(10); // type: number</code></pre><p>  使用泛型变量时需要注意,如果不具体指定传入参数的具体类型如数组、对象,就直接使用数组或对象的方法会报错。因为类型变量代表的是任意类型,传入的参数可能是数字、布尔值等,此时我们要使用<code>length</code>查看其长度是不行的,因为都不具有length属性。如果是数组,需要在泛型的基础上指定为数组。</p><pre><code class="typescript">function identity<T>(arg: T[]): T[] { return arg.length; // ok}// orfunction identity<T>(arg: Array<T>): Array<T> { return arg.length; // ok}</code></pre><h4 id="创建泛型"><a href="#创建泛型" class="headerlink" title="创建泛型"></a>创建泛型</h4><p>  在普通方法声明的基础上加一个类型参数,这里的参数名T可以是任意的,而且使用时只要数量对的上就行。</p><pre><code class="typescript">function identity<T>(arg: T): T { // do something}</code></pre><p>  使用对象字面量进行创建,在此基础上也可以抽象成接口:</p><pre><code class="typescript">let myIdentity: {<T>(arg: T): T} = identity;// orinterface GenericFunction { <T>(arg: T): T;}let myIdentity: GenericFunction = identity;</code></pre><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><p>  主要在使用上,如果一个方法需要适配不同类型的传入参数,就可以使用泛型定义方法。这种使用方式就如identity方法一样,根据传入参数不同返回指定类型的参数。</p><p>  另外就是指定请求参数类型。如axios请求:</p><pre><code> http.post<T>(`${url}`, params)</code></pre><p>  还有就是定义请求返回数据的类型,这样我们就可以在ide中使用<code>.</code>运算符拿到response中的属性。</p><pre><code>export interface LoginRequest { username: string; password: string;}export interface LoginPayload { username: string; token: string; ···}export const Login = async (loginParams: LoginRequest): Promise<LoginPayload> => { try { const response = await http.post<LoginRequest>('/login', loginParams); return response.data as LoginPayload; } catch (e) { throw(e); }}</code></pre><p>  代码中定义了两个接口,分别为登陆的请求参数类型,和登陆返回值类型。在return的时候使用as断言。这样在请求得到的返回结果,在ide中就可以使用<code>.</code>得到返回结果中的token等属性,比较方便,不需要将请求结果打印出来,或者查看请求结果。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>   除此之外还有泛型类和类类型,于方法类似,只不过是换成了类的写法,而且不常用。对于学习新的语言或者框架使用粗略的过一遍文档,然后就上手做,肯定开始很多东西都不了解。就像我之前写的,感觉完全没有用上typescript的强类型,把ts写成了js。不过这种学习的方式好的一点是,在了解过后,写了一段时间代码,也踩过一些坑之后,再次学习的话,就有醍醐灌顶、豁然开朗的感觉,记忆也会更加深刻。了解清楚了泛型的使用,现在也可以去更正之前代码的错误,加上泛型或指定其类型。</p>]]></content>
<categories>
<category> TypeScript </category>
</categories>
<tags>
<tag> TypeScript </tag>
<tag> Generics </tag>
</tags>
</entry>
<entry>
<title>css世界读书笔记</title>
<link href="/2018/11/04/css-world-note/"/>
<url>/2018/11/04/css-world-note/</url>
<content type="html"><![CDATA[<p>  一直以来都觉得前端CSS可能是最难学的,emm..最难学好学精的。直到现在写CSS,有时候遇到一些小问题都不知道怎么回事,甚至有时候还会写一些无用的的属性,是在我用了语法检测才发现以前写的部分CSS其实是不正确的。有时候不明白一些属性的原理,你加很多CSS是完全不会生效的。<br><a id="more"></a><br>  遇到CSS问题的时候特别是比较棘手的问题,张鑫旭的博客中几乎都能找到。所以对于张鑫旭在CSS方面的造诣还是比较佩服的,希望能从《CSS世界》这本书中学习到不一样的东西。但是首先先就要吐槽一下,蹩脚的比喻和美式幽默的腔调确实不适合我,如果少一点废话,这本书至少可以省去1/3的篇幅。</p><p>  内容上设计上这本书着重于CSS2,比较贴近实际,CSS3新增了很多内容,也纠正了一些之前设计上的问题。如box-sizing等,但更多的则是CSS动画,使用CSS3动画相较于之前的JS动画,可以大大减少页面的重排和重绘,从而提高网页性能。但在实际开发中更注重于实现而不是动画,个人感觉动画只是点缀,提升用户体验。在实现层CSS2更为重要,学习CSS背后的技术内幕。</p><p>  相较于其他编程语言,CSS毫无逻辑可言,所有的东西都像堆叠起来的一样,所以叫层叠样式表?对于每一条CSS属性都有其作用,如一个span是属于内联元素(inline),是不能设置width和height的,如果使用的display属性设为block、inline-block或其它块元素,就可以为其设置宽高了。例如书中在块级元素提到的清除浮动方法:</p><pre><code class="css">.clear:after { content: ''; display: block; clear: both;}</code></pre><p>在很长的一段时间里我都是使用clear:both,而且并不是使用伪类的方式。除了block还可以使用table、list-item.</p><p>  还有height的问题,经常会遇到设置height: 100%,实际高度永远是0,如果不明白计算方式那么永远都是0。就之前做项目的时候实现一个footer沉底,内容不足铺满一页沉底,超出页面就往下堆叠。其实思路很简单,就是footer绝对定位,bottom为0。但是代码这么写了却永远不生效。因为高度计算不正确,绝对定位就不能成功定位。vue项目中:</p><pre><code class="css">#app { height: 100%;}.footer { position: absolute; bottom: 0; left: 0; height: 60px;}</code></pre><p>在vue项目中一般footer的父元素大都不是app,可能还会有,所以这样做不ok,那么给app和footer之间的元素都加上height: 100%就可以了吗,也不行,其实实际动手试一下,调试选中app如果页面内容不足铺满屏幕,那么app的高度也不是窗口的高度,因为还少了一部分:</p><pre><code class="css">html, body { height: 100%;}</code></pre><p>因为app的父元素html和body并没有达到整个窗口的高度,只是适应了内容的大小。所以要确保高度能够正确计算,另外,父元素auto,子元素100%,是无法计算高度的,因为100% * auto。</p><p>  个人觉得书中3-7章比较有用,涉及到的东西也比较多,关于元素、盒子、流等内容。读完这本书,说实话虽然一直在吐槽书的啰嗦和一些比喻,但确实收获很大。从第3章开始读起来是比较费劲的,很多东西都得边读边思考,还不能跳过一些比喻,跳过感觉往后读就有些吃力。特别是盒尺寸一章,读的令人头大。最后几张读起来就轻松许多,很快就过一遍了。吐槽归吐槽,但是如果有人问我学习CSS的书我还是会推荐这本书,前提是有一定的CSS开发经验,至少得踩过一些坑,读这本书才有用,新手还是上W3C看看基础属性吧。</p><p>  这本书中也给出了很多实际开发中一些问题的解决方案,都是比较好的解决方案,也是大家都比较推崇的。读完之后收获很大,但在盒尺寸和流这一块还需要实际用的时候,再来翻阅一遍。</p>]]></content>
<categories>
<category> css </category>
</categories>
<tags>
<tag> css </tag>
<tag> 读书笔记 </tag>
</tags>
</entry>
<entry>
<title>基于vue-cli3.x和typescript构建前端项目结构设计</title>
<link href="/2018/10/16/vue-cli3-typescript-project-structure/"/>
<url>/2018/10/16/vue-cli3-typescript-project-structure/</url>
<content type="html"><![CDATA[<p>  之前在vue-cli3正式版还没有发布的时候就已经在安利下尝过鲜了,但之前对于vue-cli的设计和typescript等知识了解不多,项目结构设计的不太好。借着做另外一个项目的机会学习了vue-cli3的相关知识,根据自己的理解设计了一个前端项目结构。</p><p>  项目的技术选型用vue+typescript,脚手架使用vue-cli3,UI框架主要在ElementUI和iView之间选择。因为项目是PC中后台类型的,作为对比的就是活跃度、组件库这些,确实ElementUI是饿了么团队做的,然后活跃star这些iView也不少,明显的bug的话ELementUI比iView要多很多,也可能是使用iView的人少一点。但是iView的组件库要优于饿了么,iView是基于ant-design,个人觉得iView的UI优于饿了么。所以最后选择了iView作为UI框架,iView是基于less开发的,所以我也选用了less。这就是大概的技术选型。包管理工具这些根据个人喜好选择,我用的是yarn。<br><a id="more"></a></p><h5 id="创建vue-cli3项目"><a href="#创建vue-cli3项目" class="headerlink" title="创建vue-cli3项目"></a>创建vue-cli3项目</h5><pre><code class="shell"># 没有安装的@vue-cli3的使用下面命令安装npm install -g @vue/cli# ORyarn global add @vue/cli# 安装完之后使用下面的命令检查是否安装正确,出版本号即正确安装vue --version# 使用vue create <project_name>vue create my-project</code></pre><p>  会提示你使用默认配置还是手动设置,通过上下键切换回车选择。接着使用空格进行选择,选上Babel、TypeScript、Router、Vuex、CSS Pr-processors、Linter/Formatter,测试可以不选,回车下一步。</p><p><img src="https://i.loli.net/2018/10/17/5bc69dc6dbf6d.png" alt="step1"></p><p><img src="https://i.loli.net/2018/10/17/5bc69dc73dbb0.png" alt="step2"></p><p><img src="https://i.loli.net/2018/10/17/5bc69dc79e4a8.png" alt="step3"></p><p>  创建好的项目结构:</p><p><img src="https://i.loli.net/2018/10/17/5bc6a69993d88.png" alt="项目结构"></p><p>  vue-cli3的最大特点就是零配置,相关配置都隐藏在@vue/preload-webpack-plugin,默认配置已经可以满足大部分需求了,针对不了解webpack的比较友好。熟悉webpack的也可以进行定制。想要修改或拓展webpack配置可以新增<code>vue.config.js</code>文件进行配置,这部门vue-cli文档很详细。</p><pre><code class="shell"># 在当前项目下查看webpack 详细配置vue inspect</code></pre><h5 id="添加iView、axios"><a href="#添加iView、axios" class="headerlink" title="添加iView、axios"></a>添加iView、axios</h5><p>  iView推出了支持Vue CLI 3的插件,安装插件使用更方便。可以通过命令行安装或使用图形界面安装。注意安装时选择Fully import,因为这样才可以定制主题,选择添加一个less来overwrite主题色。</p><pre><code class="shell"># 命令行安装vue add vue-cli-plugin-iview</code></pre><p>  个人觉得添加插件使用图形界面安装方便,除了可以检查是否能正确安装之外还能看到插件的下载数等信息,图形界面也可以直接创建vue-cli项目。</p><pre><code class="shell"># 打开vue-cli图形界面 vue ui</code></pre><p><img src="https://i.loli.net/2018/10/17/5bc6b382a4f51.png" alt="vue-ui"></p><p>  在搜索插件搜索安装就行了,插件的配置项在安装的时候也已选择。</p><p>  Vue CLI 3默认没有安装axios,需要我们手动安装:</p><pre><code class="shell">yarn add axios</code></pre><p>  iView插件安装好之后,选择添加一个iview-variables.less来覆盖主题色或者其他iView的配置,例如覆盖主题色:</p><pre><code class="less">// Here are the variables to cover, such as:@primary-color: #8c0776;</code></pre><p>  这个less文件需要在入口文件main.ts引入,目前的vuecli3项目这样引入会报error。</p><p><img src="https://i.loli.net/2018/10/17/5bc6d560932c6.png" alt="less error"></p><p>  不过比较人性化的是提示了你如何去解决,可以去这个链接寻找解决方案。我使用的方案是将less版本降到3.x以下,2.7.3。这样就没问题了。</p><h5 id="全局引用less"><a href="#全局引用less" class="headerlink" title="全局引用less"></a>全局引用less</h5><p>  iview-variables.less只能重写iview中定义的less变量,如果需要我们自己定义一些变量类似背景色和其他公用的变量,就需要引一个base.less了,这部分在vue-cli-3的文档中也说明的很详细。我采用的是使用vue-cli-plugin-resources-loader。使用vue ui搜索添加这个插件。安装成功项目目录下会多一个vue.config.js,在里面进行配置就好了,这个插件会给我初始化一些内容,我们只需要引入我们需要的less文件即可。</p><pre><code class="javascript">const path = require('path');module.exports = { pluginOptions: { 'style-resources-loader': { preProcessor: 'less', patterns: [ path.resolve(__dirname, 'src/assets/common/base.less'), ] } }}</code></pre><h5 id="路由设计"><a href="#路由设计" class="headerlink" title="路由设计"></a>路由设计</h5><p>  通过对项目的简单分析设计了路由,包括路由结构,路由懒加载路由前置检查。</p><pre><code class="typescript">import Vue from 'vue';import Router from 'vue-router';import Home from './views/Home.vue';Vue.use(Router);/** * 路由懒加载方法 * @param component_name 路由组件name * @return 返回一个加载当前name组件的匿名函数 */const getComponent = (componentName: string) => () => import(`./views/${componentName}.vue`);const router = new Router({ routes: [ { path: '/', name: 'home', redirect: '/user', component: Home, meta: { auth: true, }, children: [ { path: '/pluginmarket', name: 'plugin_market', component: getComponent('PluginMarket'), }, { path: '/user', name: 'user', component: getComponent('User'), meta: { auth: true, }, }, { path: '/team', name: 'team', component: getComponent('Team'), meta: { auth: true, }, }, { path: '/accountmanagement', name: 'account_management', component: getComponent('AccountManagement'), meta: { auth: true, }, }, ], }, { path: '/login', name: 'login', component: getComponent('Login'), }, { path: '*', name: 'not_found', component: getComponent('404NotFound'), }, ],});/** * 路由拦截 */router.beforeEach((to, from, next) => { // 校验 if (to.meta.auth) { console.log('please login.'); next(); } next();});export default router;</code></pre><h5 id="全局过滤器"><a href="#全局过滤器" class="headerlink" title="全局过滤器"></a>全局过滤器</h5><p>  项目有的地方可能会用到全局过滤器,在src下新建一个filter文件夹,新建filter.ts文件。依次添加全局过滤器,然后在main.ts引入filter.ts,就可以在组件中使用全局过滤器了。比较简单就不贴代码了。</p><h5 id="localstorage管理"><a href="#localstorage管理" class="headerlink" title="localstorage管理"></a>localstorage管理</h5><p>  项目中肯定会涉及到localstorage的使用,如果比较多的话,统一进行管理。在src下新建storage文件夹,新建storage.ts,实现localstorage的一些方法如get、set、remove等。</p><pre><code class="typescript">/** * localStorage */export default class Storage { public static getItem(key: any, defaultValue: any): any { const value = localStorage.getItem(key); if (value) { return JSON.parse(value); } else if (defaultValue) { return defaultValue; } return null; } public static setItem(key: any, value: any): void { localStorage.setItem(key, JSON.stringify(value)); } public static removeItem(key: any): void { localStorage.removeItem(key); }}</code></pre><p>  然后在根据自己需要新建tokenStorage、userStorage等。以tokenStorage和userStorage为例:</p><pre><code class="typescript">/** * tokenStorage */import storage from './storage';const StorageKey = 'Token';export default class TokenStorage { public static getToken(): any { return storage.getItem(StorageKey, ''); } public static setToken(value: any): void { storage.setItem(StorageKey, JSON.stringify(value)); } public static removeItem(): void { storage.removeItem(StorageKey); }}</code></pre><pre><code class="typescript">/** * userStorage */import storage from './storage';const UserInfoKey = 'user_info';export default class UserStorage { public static saveUserInfo(user: any): any { return storage.setItem(UserInfoKey, user); } public static getUserInfo(): any { return storage.getItem(UserInfoKey, ''); }}</code></pre><h5 id="api设计"><a href="#api设计" class="headerlink" title="api设计"></a>api设计</h5><p>  项目必定会设计和后端交互请求接口,在src下新建一个service文件夹来保存api方便管理。以前用ajax请求可以对http请求进行二次封装,我项目中使用的是async函数,虽然会有很多重复代码,但是看起来也会很简洁,初步没有涉及二次封装。关于拦截器的相关内容没有设计进来。</p><p>  结构目录:</p><p><img src="https://i.loli.net/2018/10/17/5bc6e948af72a.png" alt="service结构"></p><pre><code class="typescript">// http.tsimport axios from 'axios';const http = axios.create({ baseURL: process.env.VUE_APP_BASE_URL,});export default http;</code></pre><pre><code class="typescript">// user.tsimport http from '../http';export interface ResponseData { message: string; payload: any;}export const ToolList = async (requestParam: any): Promise<any> => { try { const response = await http.get<ResponseData>('/user', { params: requestParam }); return response.data.payload; } catch (e) { throw e; }};</code></pre><h5 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h5><p>  这个项目结构的设计是根据我个人思路做的,其中有很多问题也是自己踩坑踩过来的。关于http请求拦截和其他一些库的引入等到实际开发的时候才会去做,所以并没有关于这部分的内容。另外,代码都只是简单的写了一部分方法,并没有具体实现某些功能。</p><p>  项目托管在github上:<a href="https://github.com/reeves7/VueCLI3ProjectStructure" target="_blank" rel="noopener">https://github.com/reeves7/VueCLI3ProjectStructure</a></p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> vue </tag>
<tag> vue-cli3 </tag>
<tag> typescript </tag>
</tags>
</entry>
<entry>
<title>原生javascript实现canvas图形验证码</title>
<link href="/2018/10/14/javascript-canvas-verify-code/"/>
<url>/2018/10/14/javascript-canvas-verify-code/</url>
<content type="html"><![CDATA[<p>  感觉自己最近好像很少拿原生js写东西了,自己的js水平也还有待提高。上周做原型设计的时候设计了一个图形验证码,感觉这么小一个东西也没必要去找个库,就打算自己造个小轮子来用了。</p><p>  图形验证码的作用是一种人机识别的手段,目的是为了区分正常人和机器人,所以没必要设计的太麻烦(12306之前的验证码简直!!),所以就采用数字+大小写字母共4个字符进行验证,用canvas进行绘制,如果只是简单这样也太简单了,像接触到的其他网站使用的验证码还有背景色和干扰的线条,基本上这就是实现的思路了。<br><a id="more"></a></p><hr><p>  根据实现的思路,随机颜色的方法、生成随机数的方法肯定是需要的。</p><pre><code>/* * 生成一个随机数 */function randomNum(min, max) { return Math.floor(Math.random() * (max - min) + min);}/* * 生成随机颜色rgb值 */function randomColor(min, max) { const r = randomNum(min, max); const g = randomNum(min, max); const b = randomNum(min, max); return `rgb(${r}, ${g}, ${b})`;}</code></pre><hr><p>  在html我们只要需要提供一个容器就可以了,调用时传容器id,以及图形验证码的size就可以了。</p><pre><code class="html"><div id="verifyContainer"></div></code></pre><p>  获取到容器和size之后,我们得先添加一个canvas,参考了下网上其他验证码的背景色大概都在160-240左右可能这个区间的颜色会偏浅一点。</p><pre><code class="javascript">/* * 向html添加canvas */function appendCanvas(id, width, height) { const container = document.getElementById(id); const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; canvas.style.backgroundColor = randomColor(160, 240); container.appendChild(canvas);}</code></pre><p>  然后再定义两个常量及大小写字符数组和数字数组</p><pre><code class="javascript">const numberArr = "0,1,2,3,4,5,6,7,8,9".split(',');const characterArr = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z".split(',');</code></pre><p>  然后就可以开始绘制验证码了</p><pre><code class="javascript">/** * 绘制验证码 */function drawCharacter(id, width, height) { const canvas = document.getElementById(id).getElementsByTagName('canvas')[0]; if (canvas.getContext) { var ctx = canvas.getContext('2d'); } ctx.textBaseline = 'middle'; ctx.font = '20px SimHei'; // 开始绘制 for (let i = 1; i <= 4; i++) { var character = textArr[randomNum(0, textArr.length)]; // 随机字体颜色 ctx.fillStyle = randomColor(50, 160); ctx.shadowBlur = randomNum(-3, 3); ctx.shadowColor = "rgba(0, 0, 0, 0.3)"; var x = width / 5 * i; var y = height / 2; var deg = randomNum(-30, 30); // 设置旋转角度和坐标原点 ctx.translate(x, y); ctx.rotate(deg * Math.PI / 180); ctx.fillText(character, 0 , 0); // 恢复旋转角度和原点 ctx.rotate(-deg * Math.PI / 180); ctx.translate(-x, -y); }}</code></pre><p>  这样验证码就绘制出来了,但是确实太简单了一点,需要在加一点难度,加一些干扰线和干扰点。干扰线的话影响会较大一点,所以要少一点字符数量为宜,干扰点可以多一些,大概在canvas宽度1/5的样子。</p><pre><code class="javascript">/** * 绘制干扰线 */function interferenceLine(ctx, width, height) { for ( let i= 0; i < 4; i++) { ctx.strokeStyle = randomColor(40, 180); ctx.beginPath(); ctx.moveTo(randomNum(0, width / 2), randomNum(0, height / 2)); ctx.lineTo(randomNum(0, width / 2), randomNum(0, height / 2)); ctx.stroke(); }}/** * 绘制干扰点 */function interferenceDot(ctx, width, height) { for (let i = 0; i < width / 5; i++) { ctx.fillStyle = randomColor(0, 255); ctx.beginPath(); ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI); ctx.fill(); }}</code></pre><hr><p>  总的说来实现的比较粗糙,代码也没有去优化,本来还可以添加一些比较人性化的定制功能的,因为忙着到处看房子去了,没来得及弄,打算后面花时间优化一下。</p><p>  代码和在线演示分享在jsfiddle上,<a href="https://jsfiddle.net/5ws1Lcjf/" target="_blank" rel="noopener">check</a>.</p>]]></content>
<categories>
<category> javascript </category>
</categories>
<tags>
<tag> javascript </tag>
<tag> 验证码 </tag>
</tags>
</entry>
<entry>
<title>使用charles进行APP抓包简单教程</title>
<link href="/2018/10/07/use-charles/"/>
<url>/2018/10/07/use-charles/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>  本来想趁国庆写个小工具来自动预约单车,手动确实太麻烦了,时间掌握不好,车就被别人骑走了。做这个小工具最重要的步骤就是抓到摩拜和哈罗的api,选择了charles这款抓包工具,是挺好用的,不够也需要付费。</p><p>  之前定好的计划一点点被打乱,高速一日游这种就不说了t.t,结果用charles抓包才发现好像别人也不傻,api都是加密的,导致小工具就暂时搁置了。那么就先来记录下如何使用charles抓取app的api吧。<br><a id="more"></a></p><h2 id="PC设置"><a href="#PC设置" class="headerlink" title="PC设置"></a>PC设置</h2><p>  开始之前得保证你下载并安装了charles,有关如何安装就不赘述了,另外得保证wifi环境下。</p><p><img src="https://i.loli.net/2018/10/07/5bba1be006793.png" alt="proxy设置"></p><p>  Step1: 点击proxy settings进行代理设置,填上代理端口号默认是8888,激活http代理。</p><p><img src="https://i.loli.net/2018/10/07/5bba1be032586.png" alt="proxy mobile"></p><p>  Step2: 因为只需要抓手机APP,所以不需要代理pc,将macOS Proxy勾选去掉。</p><h2 id="手机设置"><a href="#手机设置" class="headerlink" title="手机设置"></a>手机设置</h2><p>  手机连上wifi,选择手动代理,然后填上设置信息。</p><p><img src="https://i.loli.net/2018/10/07/5bba1be0639f0.png" alt="mobile setting"></p><p>  服务器主机名填上自己PC的地址,不知道的可以用ifconfig查看,填上端口保存就可以了。记得抓包完过后,将这里的设置还原,否则无法使用wifi正常上网。</p><p>  保存成功连接wifi的时候,电脑就会弹出是否允许代理,点击allow就可以了。</p><h2 id="抓包"><a href="#抓包" class="headerlink" title="抓包"></a>抓包</h2><p>  电脑和手机都配置好后就可以开始抓包了,使用录制或者直接查看记录都是可以的,看个人选择。</p><p>  使用手机打开摩拜或者哈罗,charles就会出现网络请求的数据了。</p><p><img src="https://i.loli.net/2018/10/07/5bba1be0ef44b.png" alt="api"></p><p>  可以点击你想要查看的网络请求,charles会默认把同一域名下的api归到一起,查看比较方便。可以看到摩拜的api都是加密的,哈罗也是,就看不到具体的api信息了。另外如果觉得网络请求太多的话可以使用过滤功能进行过滤。</p><p>  这差不多就是charles简单的抓取一个手机app的api的过程了,更多有关charles的使用还是推荐<a href="https://blog.devtang.com/2015/11/14/charles-introduction/" target="_blank" rel="noopener">charles从入门到精通</a>.</p>]]></content>
<categories>
<category> 抓包 </category>
</categories>
<tags>
<tag> charles </tag>
<tag> 抓包 </tag>
</tags>
</entry>
<entry>
<title>Git权威指南读书笔记</title>
<link href="/2018/10/07/definitity-guide-of-git/"/>
<url>/2018/10/07/definitity-guide-of-git/</url>
<content type="html"><![CDATA[<p>  对于git的接触我也算较晚的,应该是到了大二会有一些团队项目,才开始使用git。也仅仅局限于简单的了解,对于很多原理都不了解,出了问题,就只有找百度。在实验室老师也只推崇svn,当时也是一个接近10人的团队提交代码。大家也都是用的GUI,记得当时三天两头就会出问题。后来因为要找实习就转到git上来了,而且大部分开源项目都发布在github等社区。后来在工作中又出过事故,觉得还是得好好了解下git。在推荐下看了git权威指南这本书。<br><a id="more"></a><br>  这本书怎么说呢?有亮点也有缺点,这是必然的。先说亮点,文章条理和章节设计非常合理,而且在前言里还推荐了不同人群的阅读重点。根据阅读重点,我基本上第一篇就是简单阅读掠过,第二篇就也粗略的过了一下,相比第一篇投入时间的更多一些,第三篇是作者推荐的重点阅读篇,花的时间较多,但是很多东西接触的不多或者甚至没有接触,所以读起来有些晦涩的感觉。结合作者的例子读起来稍好一些,然后作者讲了一些git背后的原理,对于想知其然,知其所以然还是比较好。然后缺点的话,确实有点太啰嗦了,有点影响阅读,比如花了30多页的篇幅去讲在不同操作系统下git的安装,我觉得是没那么必要的。再有就是从“权威指南”这四个字,我觉得定位不那么合理,我认为的权威指南是类似犀牛书那种的,什么操作、命令之类的忘记了,我可以查权威指南解决问题的。总之就是涵盖的东西确实很广,但是定位不够明确,针对的读者用户群较广,就感觉被捆绑消费了,感觉大多数人可能会抱着如何用好git或者了解如果搭建git、迁移到git这两方面或者更多。另外介绍cvs、svn以及其他版本控制系统,个人觉得没什么必要,要么别人是受够了svn转git对svn已经很了解了,要么是没有接触过svn想直接入门git的,所以介绍了只是徒增阅读时间罢了。</p><p>  再来说我读了之后感觉提升还是蛮大的,至少不会再像之前不知道stash,在一个分支下改了一些东西,要切到另一个分支,自己傻傻的将修改的内容全部还原再切换分支,然后又从头来过,简直被自己蠢哭。然后对git的基本操作了解的更多了,对于解决冲突、分支有了进一步的了解。提升的话可能较之前好得多,但还是有些地方理解的不深刻,对本书有了一定了解之后,感觉有些地方还需要花时间精读,另外在使用git中碰到问题回来查印象肯定也会更深刻一些。</p>]]></content>
<categories>
<category> git </category>
</categories>
<tags>
<tag> 读书笔记 </tag>
<tag> git </tag>
</tags>
</entry>
<entry>
<title>unity-cache-server-cache-modules</title>
<link href="/2018/09/28/unity-cache-server-cache-modules/"/>
<url>/2018/09/28/unity-cache-server-cache-modules/</url>
<content type="html"><![CDATA[<h2 id="Cache-Modules"><a href="#Cache-Modules" class="headerlink" title="Cache Modules"></a>Cache Modules</h2><p>  Unity-cache-server v6的缓存服务器支持两种缓存机制:</p><ul><li>cache_fs,基于文件系统的缓存</li><li>完全内存(RAM)支持的缓存</li></ul><p>  cache server默认使用cache_fs即文件系统缓存模式,适用于大多数的应用程序。RAM缓存模式提供最佳性能,但Server需要足够的物理RAM用于缓存,通常需要的内存大小至少为项目Library文件夹的2-3倍。</p><p>  两个模式的配置文件信息都在config/defalult.yml中进行配置。<br><a id="more"></a></p><h2 id="cache-fs"><a href="#cache-fs" class="headerlink" title="cache_fs"></a>cache_fs</h2><p>  一个简单且高效的文件系统缓存模式。</p><p><strong>用法</strong></p><p><code>--cache-module cache_fs</code></p><p><strong>命令</strong></p><table><thead><tr><th style="text-align:center">命令</th><th style="text-align:center">默认值</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center">cachePath</td><td style="text-align:center"><code>.cache_fs</code></td><td style="text-align:center">缓存路径</td></tr><tr><td style="text-align:center">cleanupOptions.expireTimeSpan</td><td style="text-align:center"><code>P30D</code></td><td style="text-align:center"><a href="https://msdn.microsoft.com/en-us/library/se73z7b9(v=vs.110" target="_blank" rel="noopener">ASP.NET</a>.aspx)或<a href="https://en.wikipedia.org/wiki/ISO_8601#Time_intervals" target="_blank" rel="noopener">IOS 8601</a>格式时间跨度。在此时间范围内没有访问过的缓存文件会被清理。相关次序时间语法的更多信息,请参阅<a href="https://momentjs.com/docs/#/durations/" target="_blank" rel="noopener">Moment.js</a>文档。</td></tr><tr><td style="text-align:center">cleanupOptions.maxCacheSize</td><td style="text-align:center">0</td><td style="text-align:center">磁盘缓存的最大size(以字节为单位)。使磁盘利用率低于该阈值,清理脚本会考虑要删除的文件,这些文件是最近最少使用的顺序。将值设置为0来禁用清除功能。</td></tr></tbody></table><p><strong>注意</strong></p><ul><li>cache_fs向后兼容v5.x Cache Server目录</li><li>支持工作线程,使用<code>--workers</code>命令</li><li>运行清理脚本时,<code>expireTimeSpan</code>的值用来确定要删除的文件。如果<code>maxCacheSize</code>指定,则脚本会检查惠存是否超过该值。超过该值脚本将清理最近最少使用的文件,直到缓存不再超过maxCacheSize。</li></ul><h2 id="cache-ram"><a href="#cache-ram" class="headerlink" title="cache_ram"></a>cache_ram</h2><p>  高性能,完全内存的LRU缓存。</p><p><strong>用法</strong></p><p><code>--cache-module cache_ram</code></p><p><strong>命令</strong></p><table><thead><tr><th style="text-align:center">命令</th><th style="text-align:center">默认值</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center">pageSize</td><td style="text-align:center">100000000</td><td style="text-align:center">缓存页面大小(以字节为单位)</td></tr><tr><td style="text-align:center">maxPageCount</td><td style="text-align:center">10</td><td style="text-align:center">在缓存中分配的最大页面数。</td></tr><tr><td style="text-align:center">minFreeBlockSize</td><td style="text-align:center">1024</td><td style="text-align:center">页面内最小分配单元(以字节为单位)。可以给较小的项目指定较低的值。</td></tr><tr><td style="text-align:center">cachePath</td><td style="text-align:center"><code>.cache_ram</code></td><td style="text-align:center">缓存目录的路径,脏内存页面会定期保存到磁盘的此目录下,并在启动时加载。</td></tr><tr><td style="text-align:center">persistence</td><td style="text-align:center">true</td><td style="text-align:center">将页面文件保存和加载到磁盘。如果为false,缓存会在退出时被清理。</td></tr><tr><td style="text-align:center">persistenceOptions.autosave</td><td style="text-align:center">true</td><td style="text-align:center">设置为true时将自动保存更改,false为禁用。</td></tr><tr><td style="text-align:center">persistenceOptions.autosaveInterval</td><td style="text-align:center">10000</td><td style="text-align:center">保存页面更改的频率(以毫秒为单位)</td></tr></tbody></table><p><strong>注意</strong></p><ul><li>不支持工作线程</li></ul><h2 id="对比"><a href="#对比" class="headerlink" title="对比"></a>对比</h2><p>  cache_ram是高性能缓存模式,但是需要内存要大于项目Library文件夹的2-3倍,对于大一点的项目就需要很大的物理内存。而cache_fs在实际测试中,并不逊色于cache_ram模式,当然仅仅是针对我的机器。并且用作server的mac mini还是一块1T的机械硬盘,如果更换上SSD可能会更快一些。所以两种模式,个人更推荐使用cache_fs。</p><p>  作为测试的资源Assets文件夹3.86G,Library文件夹2.65G,机器Server是mac mini,i5、8G内存、1T机械硬盘。客户端是mac mini,i5、16G内存、240GSSD。测试结果仅供参考,因为客户端mini 12年的比较老,跑unity还是有点卡,所以部分记录时间只能用大概范围,因为有时候导入的时候会卡死。</p><table><thead><tr><th style="text-align:center">比较项目</th><th style="text-align:center">cache_fs</th><th style="text-align:center">cache_ram</th></tr></thead><tbody><tr><td style="text-align:center">导入缓存结果(从客户端导本地缓存结果)</td><td style="text-align:center">3min+</td><td style="text-align:center">2min+</td></tr><tr><td style="text-align:center">重新压缩贴图导入server</td><td style="text-align:center">26min+</td><td style="text-align:center">26min+</td></tr><tr><td style="text-align:center">不适用cache server压缩贴图</td><td style="text-align:center">21min+</td><td style="text-align:center">21min+</td></tr><tr><td style="text-align:center">客户端导入server缓存结果</td><td style="text-align:center">117s</td><td style="text-align:center">-</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>  测试结果可能会因为机器的缘故略有不同,但是在对比下,cache_fs是server比较好的选择,虽然两种模式都不需要多好的cpu,但是cache_fs模式只需要一块够大的ssd,通常情况240G的SSD已经完全够用了,价格也不贵。但是使用cache_ram在我测试的情况下并没有好多少,首先门槛就要内存至少是project下library大小的2-3倍。</p><p>  综上,使用cache_fs已经能达到很好的效果了。比不适用cache server快了几乎10倍,而且还是在server使用的是机械硬盘的情况,换上ssd应该会更快一点。</p><p>  另外,关于cache的清理、镜像、高可靠性的相关内容,参考<a href="https://github.com/Unity-Technologies/unity-cache-server" target="_blank" rel="noopener">unity-cache-server</a>官方文档。</p>]]></content>
<categories>
<category> unity </category>
</categories>
<tags>
<tag> cacheserver </tag>
</tags>
</entry>
<entry>
<title>Unity-Cache-Server</title>
<link href="/2018/09/26/unity-cache-server/"/>
<url>/2018/09/26/unity-cache-server/</url>
<content type="html"><![CDATA[<p><em>注:由于没有做过游戏开发,也没有使用过unity,所以有问题的地方欢迎在评论指正</em></p><h2 id="缓存服务器"><a href="#缓存服务器" class="headerlink" title="缓存服务器"></a>缓存服务器</h2><p>  Unity的打包流程中最慢的环节就是贴图的压缩。不同的平台,需要把原始贴图压缩成对应平台的压缩贴图格式。iOS和Android平台对应的格式不同。针对这个问题,Unity官方给出了一个CacheServer的解决方案。CacheServer只是一个文件cache服务器,记录了贴图源文件和转换参数(meta文件)以及转换器版本等信息构成的字符串的md5值作为文件索引。第一个做转换的人,在本地进行压缩后,会把结果传到CacheServer,其他人使用时,就会优先去看有没有人做过同样的工作,如果有就从cache服务器下载资源,没有就会进行压缩,并上传结果到CacheServer。如果源文件、转换参数都完全一样,结果会被缓存。在日常所有贴图都会被压缩过,可以节约压缩贴图的时间,从而减少打包时间。</p><p>  Unity官方在3月发布了Cache Server v6.0.0版本,官方宣称6.0带来了更高的可靠性和性能,以及一系列的新功能。包括新模块化,实现最大的I/O性能,支持高性能的缓存模式等等。<br><a id="more"></a><br>  Unity用户手册中也有关于Cache Server介绍和使用,但截至9.26日,官网的用户手册还停留在2月份的,没有做更新。关于文件更改请仔细阅读官网手册英文原文说明或下方翻译:</p><blockquote><p>关于缓存服务器,Unity具有一个全自动的资源管道。当修改了类似于<code>.psd</code>或<code>.fbx</code>文件的资源后,Unity将检测到更改并自动重新导入更改后的文件。从文件导入的数据之后将由Unity按其内部格式存储。资源管道最重要的部分是“热重载”功能和保证您的所有资源始终同步为所见即所得模式。此功能的实现也是要付出代价的。任何修改的资源必须重新导入。在一个大团队中工作时,获得了最新的管理源代码(Source Control)后,您通常需要等很长一段时间才可将所有其他成员修改或创建的资源重新导入完成。另外,在PC和移动平台之间来回切换工程平台,也将引发重新导入大部分资源的操作。</p><p>在<strong>缓存服务器(Cache Server)</strong>上缓存导入的资源数据可大幅缩短导入资源占用的时间。</p><p>缓存每个导入的资源基于:</p><p>  <em>资源文件本身</em></p><p>  <em>导入设置</em></p><p>  <em>资源导入器版本</em></p><p>  <em>当前平台</em></p><p>如果上述四项任意一项发生变化,资源将重新导入,否则,会从缓存服务器(Cache Server)下载更新后的资源。启用偏好设置(Preferences)中的缓存服务器后,您可以共享多个工程的导入资源。</p><p><strong><em>注意:将缓存服务器设置好后,这个过程将是完全自动的,这意味着无需其他工作流程。它只是缩短了导入工程的时间,无需您进行操作。</em></strong></p></blockquote><h2 id="配置要求"><a href="#配置要求" class="headerlink" title="配置要求"></a>配置要求</h2><p>  只考虑将CacheServer搭在本地环境,cache服务器不同于版本控制,其缓存数据可以随时在本地重建,没必要使用远程服务器。如果存在开发团队分布在各地的情况,选择在每个地方配置一台独立缓存服务器比较好。</p><p>  因为CacheServer只相当于一个文件cache服务器,所以配置并不需要太高。但是为了最佳性能,机器必须要有足够的RAM来存储整个导入的工程文件夹。机器最好使用SSD,保证读写速度。另外CacheServer的CPU使用率非常低。最好使用Linux或Mac OS X系统。Windows 文件系统未对资源缓存服务器 (Asset Cache Server) 存储数据的方式进行特别理想的优化,Windows 上的文件锁定问题可引发问题,但 Linux 或 Mac OS X 上不会出现此类问题。</p><h2 id="用户使用"><a href="#用户使用" class="headerlink" title="用户使用"></a>用户使用</h2><p>  Unity Cache Server 用户配置使用非常简单:</p><p>  1. 在编辑器中选择偏好设置(Preferences)</p><p>  2. 选择Cache Server,Mode选择Remote,输入缓存服务器的IP地址即可</p><p><img src="https://i.loli.net/2018/09/26/5bab3ffe7a483.png" alt="cache server本地配置"></p><p>  如果缓存服务器配置正确,不存在网络问题的话,Check Connection之后就会出现如图所示连接成功。如果失败的话,可能是缓存服务器配置问题。</p><h2 id="管理员配置"><a href="#管理员配置" class="headerlink" title="管理员配置"></a>管理员配置</h2><p>  Cache Server升级到v6版本之后只支持npm方式安装,所以机器需要先安装LTS版本(长期支持版本),目前官方推荐的版本是v8.10.0. 下载地址:<a href="https://nodejs.org/en/download/" target="_blank" rel="noopener">https://nodejs.org/en/download/</a></p><p>  node环境配置好之后,使用npm安装:</p><pre><code class="shell">npm install unity-cache-server -g或npm install github:Unity-Technologies/unity-cache-server -g</code></pre><p>  安装完成之后,命令行输入下面的指令:</p><pre><code class="shell">unity-cache-server</code></pre><p><img src="https://i.loli.net/2018/09/26/5bab4fe1e0d17.png" alt="cache server location"></p><p>  然后unity-cache-server就启动起来了,可以看到默认的缓存路径位于unity-cache-server安装目录下,我们也可以指定缓存路径和端口,缓存模式等。用法附带常用命令表:</p><pre><code class="shell">用法:unity-cache-server [arguments]</code></pre><table><thead><tr><th style="text-align:left">命令</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left">-V, –version</td><td style="text-align:left">查看缓存服务器的版本号</td></tr><tr><td style="text-align:left">-p, –port</td><td style="text-align:left">缓存服务器监听的端口,默认端口是8126。</td></tr><tr><td style="text-align:left">-c, –chache-module [path]</td><td style="text-align:left">缓存模块路径,默认值是’cache’</td></tr><tr><td style="text-align:left">-P, –cache-path [path]</td><td style="text-align:left">缓存路径</td></tr><tr><td style="text-align:left">-l, –log-level \<n></n></td><td style="text-align:left">日志详细程度,等级0(没有提示) - 5(调试模式),默认值为3</td></tr><tr><td style="text-align:left">-w, –workers \<n></n></td><td style="text-align:left">要生成的工作线程数,默认值为0。</td></tr><tr><td style="text-align:left">-h, –help</td><td style="text-align:left">显示使用帮助 </td></tr></tbody></table><p><strong>配置文件</strong></p><p>  配置文件位于unity-cache-server安装目录下config/default.yml,配置系统基于node-config模块,关于如何管理特定环境的配置文件的其他信息,可以参阅node-config配置文档。</p><p>  默认情况,run <code>unity-cache-server</code>使用的是内置的配置文件,当然我们可以修改内置的配置文件。另外也可以将配置信息写入新文件,使用–NODE_CONFIG_DIR命令使用该配置文件。</p><p><strong>配置文件常用命令</strong></p><table><thead><tr><th style="text-align:center">命令</th><th style="text-align:center">默认值</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center">Cache.options.processor.putWhitelist</td><td style="text-align:center">[]</td><td style="text-align:center">允许上传更改的IP地址数组,类型为字符串</td></tr><tr><td style="text-align:center">Server.options.allowIPv6</td><td style="text-align:center">false</td><td style="text-align:center">在IPv4和IPv6上监听客户端连接</td></tr></tbody></table><pre><code class="shell">// cd到unity-cache-server安装目录,如果不清楚安装位置,可以先运行 unity-cache-server,查看pathcd /Users/wangx/.nvm/versions/node/v8.11.3/lib/node_modules/unity-cache-server/vi config/default.yml</code></pre><p><img src="https://i.loli.net/2018/09/27/5bac39daebbd4.png" alt="default.yml"></p><p>  常用只需要修改这两个配置,因为放在本地网络环境,可以不需要修改allowIpv6,只修改putWhitelist即可,注意yml语法,以及putWhitelist值为字符串值,这个是一定要注意的,不然会导致cache server启动成功却不能使用缓存服务。建议修改完之后做一下YAML语法检测,<a href="http://www.yamllint.com/" target="_blank" rel="noopener">YAML在线检测网址</a>。</p><p>  至此,server的配置就基本完成了。然后在启动server,测试是否成功。端口可以使用默认端口,但最好指定一个cache结果缓存目录,方便查看和清理。本文先使用cache_fs,高性能文件系统缓存模式,下一篇文章将对比说明cache_ram模式。</p><pre><code class="shell">// 我这里使用的mac OS X作为server,缓存目录放在用户目录下 unity-cache/unity-cache-server -P /Users/wangx/unity-cache/</code></pre><p>  注意unity-cache-server命令区分大小写,-p和-P代表的命令不一样。</p><p><img src="https://i.loli.net/2018/09/27/5bac4ba3a101c.png" alt="unity-cache-server -P"></p><h2 id="测试使用"><a href="#测试使用" class="headerlink" title="测试使用"></a>测试使用</h2><p>  客户端配置参考上文的用户使用,第一遍导入时,就会压缩一次贴图,并将结果缓存到cache-server。因此第一次压缩贴图的时间并不会缩短。第一次压缩完成后server上指定的缓存目录下就会有缓存结果。</p><p><img src="https://i.loli.net/2018/09/27/5bac4d10c16cf.png" alt="缓存结果"></p><p><img src="https://i.loli.net/2018/09/27/5bac4d10ceba0.png" alt="缓存结果"></p><p>  然后我们再次切换到相同平台的时候,速度会加快很多倍。当然仅限于相同平台,如果不同平台,本机又可以提交修改,然后缓存结果就会被体会,再次切相同平台同样会重新压缩贴图。如前文中的Unity哪些更改会导致资源重新导入。例如:</p><blockquote><p>游戏项目是android平台的,但是unity初次导入项目会默认在PC平台,所以压缩贴图的时候会压缩成PC支持的格式。切到Adroid平台的时候会重新压缩一遍贴图,就会重新导入。所以建议putWhitelist在第一次导入的时候只填写第一个上传项目压缩贴图的IP地址,待所有人都切到相同平台的时候,再加入能提交更改的IP。</p></blockquote><blockquote><p>还有另外一种比较好的方式:</p><p>  在Server上缓存的是Android平台的缓存结果,导入项目如果能直接是在Android平台就会直接下载缓存结果,避免因平台的切换导致部分资源被重新导入。有关如何通过命令行启动Unity并完成平台的切换,请百度或google,“unity如何直接在打开时完成开发平台的切换”。当启动Unity就处于Android平台,就会直接下载Cache Server的缓存结果。</p></blockquote><p>  结果缓存成功,再次切平台的就会从cache server下载缓存,整个过程基本能在1-2分钟内完成,速度提升很多倍。在server的log中可以看到发送缓存记录:</p><p><img src="https://i.loli.net/2018/09/27/5bac52a1d7b30.png" alt="download from cache server"></p><p><img src="https://i.loli.net/2018/09/27/5bac5291133f3.png" alt="log"></p><h2 id="现有项目导入"><a href="#现有项目导入" class="headerlink" title="现有项目导入"></a>现有项目导入</h2><p>  上面的导入缓存配置较为简单,但是如果是已有Unity项目又不想重新做一次导入,可以使用Unity官方提供的这种方式进行导入。前提是进行导入的客户端IP地址需要包含在白名单中,并且项目要具有Library文件夹。</p><blockquote><ol><li><p>将安装目录下Unity文件夹的<code>CacheServerTransactionImporter.cs</code>脚本添加到Unity项目下。对于这部分的工作我不是太清楚,具体如何设置,请参考unity-cache-server官方的说明。<a href="https://github.com/Unity-Technologies/unity-cache-server" target="_blank" rel="noopener">https://github.com/Unity-Technologies/unity-cache-server</a>, Unity project Library Importer.</p></li><li><p>添加成功之后,等待刷新一下,Unity的菜单栏就会出现Cache Server Utilities选项,然后选择导出,Export Transactions.选择导出json文件的路径,保存。</p></li><li><p>在导出结果的客户端执行下面的命令:</p><pre><code class="shell">unity-cache-server-import <path to json file> [server:port]</code></pre><p><em>注:这一步需要导入客户端的地址在配置文件的putWhitelist中,否则会导致写入失败。</em></p><p>如果配置没有问题的话就会看到server log会有资源文件的上传记录,此阶段可以将log-level 设置为4,就能看到上传记录。</p></li></ol></blockquote><p><img src="https://i.loli.net/2018/09/27/5bac898fbf382.png" alt="unity menu"></p><p><img src="https://i.loli.net/2018/09/27/5bac898f72602.png" alt="import shell"></p><p><img src="https://i.loli.net/2018/09/27/5bac899abcda0.png" alt="server import"></p><p>  等待上传完成,server就缓存了客户端已经有的缓存结果了。其他客户端就可以从cache server导入资源。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>  至此,关于unity-cache-server最新版本v6的使用及配置就差不多了。其中可能有一些地方是可以改进,或者可以详细说明的,但我是个门外汉,对很多unity相关的内容都不太清楚。所以还是欢迎指正,另外在使用时有疑问也可以评论或邮件联系。</p>]]></content>
<categories>
<category> unity </category>
</categories>
<tags>
<tag> cacheserver </tag>
</tags>
</entry>
<entry>
<title>原型链与继承-JS高级</title>
<link href="/2018/09/24/the-proptotype-chain-and-inheritance/"/>
<url>/2018/09/24/the-proptotype-chain-and-inheritance/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>  初学javascript的时候对原型链和继承就一知半解,上半年复习的时候感觉十分良好,直到最近又被问道的时候,还是讲不清楚。自己就又看了一遍JS高级程序设计,力求有更深的理解,彻底搞懂。</p><p>  继承是OO语言中的一个最为人津津乐道的概念。许多OO语言都支持两种继承方式:接口继承和 实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其 实现继承主要是依靠原型链来实现的。本段摘自JS高级程序设计。<br><a id="more"></a></p><h2 id="原型链"><a href="#原型链" class="headerlink" title="原型链"></a>原型链</h2><p>  原型和实例关系:</p><blockquote><p>每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个只想原型对象的内部指针。</p></blockquote><p>  如果试图引用对象(instance)的某个属性,首先会在对象内部有没有这个属性,找不到时才会在该对象的原型(prototype)里去寻找这个属性。</p><p>  让一个实例的原型对象指向另一个类型的实例:</p><pre><code class="javascript"> thisConstructor.prototype = otherInstance</code></pre><p>  我们如果要引用thisConstructor构造的实例thisInstance的属性name,</p><blockquote><ol><li><p>先在thisInstance自有属性查找</p></li><li><p>没有找到会在thisInstance.__proto__(thisConstructor.prototype)中找,我们上面将其指向了otherInstance,即我们是在otherInstance中寻找属性name</p></li><li><p>如果otherInstance中还是没有该属性,程序并不会终止,它将会继续向上去找otherInstance.__proto__(otherConstructor.prototype)的属性,一直到Object的原型对象,即顶端。</p><p>查找过程:</p><p>thisInstance >> otherInstance >> otherConstructor.prototype ··· >> Object.prototype</p></li></ol></blockquote><p>  这样一个查找的过程,就像链条一样,就称作原型链,prototype充当着链接的作用。</p><pre><code class="javascript">// 一个简单的例子function Animal() { this.name = 'animal';}function Tiger() { this.age = 'tiger';}Tiger.prototype = new Animal();const instance = new Tiger();console.log(instance.name);// animal</code></pre><p>  了解了原型链,上面的的结果就很明显了,Tiger没有name属性,向上查找到Animal具有name属性,输出。</p><p>  如何判断原型和实例的继承呢?一般使用instanceof或isPrototypeOf:</p><blockquote><ol><li><p>使用instanceof运算符:</p><p>console.log(instance instanceof Object); // true</p><p>console.log(instance instanceof Tiger); // true</p><p>console.log(instance instanceof Animal); // true</p></li></ol></blockquote><p>  由于原型链的关系,instance可以说是Object、Animal、Tiger中的任何一个实例,所以使用instanceof都会返回true。</p><blockquote><ol start="2"><li><p>使用isPrototypeOf()方法:</p><p>console.log(Object.prototype.isPrototypeOf(instance));</p><p>console.log(Animal.prototype.isPrototypeOf(instance));</p><p>console.log(Tiger.prototype.isPrototypeOf(instance));</p></li></ol></blockquote><p>  道理和上面的一样。</p><h2 id="原型链的问题及解决"><a href="#原型链的问题及解决" class="headerlink" title="原型链的问题及解决"></a>原型链的问题及解决</h2><p>  原型链的设计并不是完美的,它也存在着一些问题:</p><blockquote><ol><li>当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享。</li><li>创建子类型时,不能向父类的构造函数中传参。</li></ol></blockquote><p>  针对原型链的不足,也有解决方案。</p><p><strong>经典继承</strong></p><p>  为解决原型链中的这两个问题,开始我们使用一种<strong>借用构造函数</strong>(constructor stealing)的技术。其基本思想是在子类型的构造函数内部调用父类的构造函数。</p><pre><code class="javascript">function Father() { this.childrens = ['tom', 'bob', 'jack','lucy'];}function Son() { // 继承了Father,且向父类传递参数 Father.call(this);}const instance = new Son();instance.childrens.push('reeves');console.log(instance.childrens); // ['tom', 'bob', 'jack','lucy', 'reeves']const otherInstance = new Son();console.log(otherInstance.childrens); // ['tom', 'bob', 'jack','lucy']; 证明引用类型值是独立的</code></pre><p>  这种方式保证原型链中引用类型值独立,同时子类型创建时可以向父类型传递参数。但是如果仅用借用构造函数,将会存在方法都在构造函数中定义,函数服用也就不能使用了。而且父类(Father)中定义的方法对子类(Son)而言也是不可见的。所以很少会单独使用这种技术。</p><p><strong>组合继承</strong></p><p>  也叫做伪经典继承,意思是将原型链和借用构造函数的技术组合。其思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现函数服用,又能保证每个实例都又自己的属性。</p><pre><code class="javascript">function Father(name){ this.name = name; this.childrens = ["tom","bob","jack","lucy"];}Father.prototype.sayName = function(){ console.log(this.name);}function Son(name, age){ //继承实例属性,第一次调用Father() Father.call(this, name); this.age = age;}//继承父类方法,第二次调用Father()Son.prototype = new Father();Son.prototype.sayAge = function(){ console.log(this.age);}var instance1 = new Son("reeves", 18);instance1.childrens.push("reeves");console.log(instance1.childrens); // "tom,bob,jack,black,reeves"instance1.sayName(); // reevesinstance1.sayAge(); // 18var instance1 = new Son("alex",19);console.log(instance1.childrens); // "tom,bob,jack,lucy"instance1.sayName(); // alexinstance1.sayAge(); // 19</code></pre><p>  组合继承避免了原型链和经典继承的缺陷,组合了二者的有点,是js中最常用的继承模式,它支持instanceof和isPropertyOf()识别实例是否为组合继承创建的对象。另外,组合继承实际上调用了两次父类构造函数,造成了不必要的消耗。</p><p><strong>原型继承</strong></p><p>  这个方法是由著名的大师Douglas Crockford与2006年提出的,他的想法是借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。其思路是在object()函数内部,先用一个临时的构造函数,再将传入的对象作为这个构造函数的原型,最后返回了这个临时的新实例。</p><pre><code class="javascript">function object(o){ function F(){} F.prototype = o; return new F();}</code></pre><p>  实际上来说,object()对传入的对象进行了一次浅拷贝。</p><pre><code class="javascript">var person = { friends : ["Van","Louis","Nick"]};var anotherPerson = object(person);anotherPerson.friends.push("Rob");var yetAnotherPerson = object(person);yetAnotherPerson.friends.push("Style");console.log(person.friends);//"Van,Louis,Nick,Rob,Style"</code></pre><p>  可以作为另一个对象基础的是person对象,于是我们把它传入到object()函数中,然后该函数就会返回一个新对象。这个新对象将person作为原型,因此它的原型中就包含引用类型值属性。 这意味着person。friends不仅属于person所有,而且也会被anotherPerson以及yetAnotherPerson共享。</p><p>  ES5中,新增了Object.create()方法规范了原型继承。Object.create()接受两个参数:</p><ul><li>一个用作新对象原型的对象</li><li>(可选)一个为新对象定义额外属性的对象</li></ul><pre><code class="javascript">var person = { friends : ["Van","Louis","Nick"]};var anotherPerson = Object.create(person);anotherPerson.friends.push("Rob");var yetAnotherPerson = Object.create(person);yetAnotherPerson.friends.push("Style");console.log(person.friends);//"Van,Louis,Nick,Rob,Style"</code></pre><p>  <strong>object.create()</strong> 只有一个参数时功能与上述object方法相同,它的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的.以这种方式指定的任何属性都会覆盖原型对象上的同名属性。例如:</p><pre><code class="javascript">var person = { name : "Van"};var anotherPerson = Object.create(person, { name : { value : "Louis" }});console.log(anotherPerson.name);//"Louis"</code></pre><p>  支持Object.create()的浏览器IE9+, Firefox 4+, Safari 5+, Opera 12+ 和 Chrome。需要注意的是,<strong>原型式继承中,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。</strong></p><h2 id="寄生继承"><a href="#寄生继承" class="headerlink" title="寄生继承"></a>寄生继承</h2><blockquote><p>寄生继承思路和构造函数、工厂模式类似,创建一个仅用来封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。</p></blockquote><pre><code>function createAnother(oriObject){ // 通过调用object函数创建一个新对象 var another = object(oriObject); // 通过某种方式增强对象 another.sayHi = function(){ alert("hi"); }; return another;//返回这个对象}</code></pre>]]></content>
<categories>
<category> javascript </category>
</categories>
<tags>
<tag> 原型链 </tag>
<tag> 继承 </tag>
</tags>
</entry>
<entry>
<title>npm包开发</title>
<link href="/2018/09/16/npmpackage/"/>
<url>/2018/09/16/npmpackage/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>  之前做项目的时候遇到了一个npm包在typescript程序中无法使用的问题,添加了@types也无法作用。打算自己学习一下npm包的开发。简单的记录一下npm包开发的过程。<br><a id="more"></a></p><h2 id="必备"><a href="#必备" class="headerlink" title="必备"></a>必备</h2><p>  1.注册一个npm账号,<a href="https://www.npmjs.com/" target="_blank" rel="noopener">https://www.npmjs.com/</a></p><p>  2.安装nodejs</p><p>  3.安装git</p><p>  安装过程就不贴了,都很简单,安装教程也很多。</p><h2 id="创建npm包"><a href="#创建npm包" class="headerlink" title="创建npm包"></a>创建npm包</h2><pre><code>.├── bin //命令配置├── README.md //说明文档├── index.js //主入口├── src //功能文件├── package.json //包信息└── test //测试用例</code></pre><p>  大致的目录结构如上,我们在src下新建一个resize.js,然后写上代码:</p><pre><code class="javascript">const Resize = function() { const sizeName = ['B', 'K', 'M', 'G', 'T']; if (bytes) { const e = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, Math.floor(e))).toFixed(2) + ' ' + sizeName[e]; } else { return ''; }}export default Resize;</code></pre><p>  然后再在index.js中引用它</p><pre><code class="javascript">let reSize = require('./src/resize.js');reSize(18995);</code></pre><p>  然后cd到该文件目录下,使用npm init 创建一个package.json。</p><p><img src="https://i.loli.net/2018/09/17/5b9eed3b8bdf1.png" alt="package.json"></p><h2 id="发布npm包"><a href="#发布npm包" class="headerlink" title="发布npm包"></a>发布npm包</h2><p>  用已经注册好的npm账号添加该项目</p><pre><code>npm adduser</code></pre><p>  根据提示输入账号密码邮箱即可,然后发布。</p><pre><code>npm publish</code></pre><p>  使用该命令发布该npm包,发布完成后,进入npm个人中心就可以看到自己发布的npm包了。发布时出错可能是注册完npm账号未验证,如果遇到该问题,验证下邮箱重新发布即可。</p><p><img src="https://i.loli.net/2018/09/17/5b9eed3b99f63.png" alt="publish"></p><p><img src="https://i.loli.net/2018/09/17/5b9eed3b9fc5b.png" alt="npm"></p><h2 id="获取npm包"><a href="#获取npm包" class="headerlink" title="获取npm包"></a>获取npm包</h2><p>  下面的就很熟悉了,在创建好的目录下,使用npm install <package-name>即可,我们获取一下刚刚创建好的包。</package-name></p><pre><code>npm install file-resize</code></pre><p>  测试一下:</p><pre><code class="javascript">import reSize from 'file-resize';reSize(188567); // 184.14 K</code></pre><h2 id="更新npm包"><a href="#更新npm包" class="headerlink" title="更新npm包"></a>更新npm包</h2><p>  更新和发布一样,都是使用npm publish,但更新必须修改version,否则会报错。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>  学习了一下npm包的创建发布与更新,可以在以后写一些属于自己的npm包。只是简单的学习了一下,并没有系统的去做npm包的开发,npm包开发还包含单元测试等等。</p>]]></content>
<categories>
<category> npm </category>
</categories>
<tags>
<tag> npm </tag>
</tags>
</entry>
<entry>
<title>Vue slot(插槽)</title>
<link href="/2018/09/09/vueslot/"/>
<url>/2018/09/09/vueslot/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  插槽是组件的一部分,当时做项目的时候使用到了插槽,并不是很懂,vue官网也没有说明的很清楚,只是说vue实现了一套内容分发的api,\<slot>元素承载分发内容的出口。也就是说插槽是用来分发内容的。那就举个栗子说明一下:</slot></p><pre><code class="html">// app<component-a> 这是插槽内容</component-a>// component-a html<div> hello,word!</div></code></pre><p>  上面app中放了一个component-a组件,组件的内容是下面的,这样在浏览器中渲染出来的结果是什么呢?结果是只会渲染出组件中的内容。app中组件标签里的内容被抛弃了。我们在组件中加一下slot,会发现之前写在组件内的内容出来了,这就是插槽的作用,可以分发内容。意思就是如果不使用插槽,在组件标签内写的任何内容都是不会生效的。<br><a id="more"></a></p><pre><code class="vue"><body> <div id="app"> <component-a> 这是插槽内容 </component-a> </div></body> <script> Vue.component('component-a',{ template: ` <div>hello,word!<slot></slot></div> ` }); const app = new Vue({ el: '#app', data() { return {}; } }); </script></code></pre><p>  这个插槽也叫单个插槽。</p><h4 id="具名插槽"><a href="#具名插槽" class="headerlink" title="具名插槽"></a>具名插槽</h4><p>  具名插槽就是具有名字的插槽,在组件中给插槽取上名字,然后再组件标签内slot属性填上名字,它就会跟组件内的name一一对应。那么没有名字的就是默认插槽了。</p><pre><code class="vue"><style> .border { border: 1px solid #e7494b; }s</style></head><body> <div id="app"> <component-a> <div> 默认插槽的内容 </div> <template slot="slota"> 插槽a的内容 </template> <template slot="slotb"> 插槽b的内容 </template> </component-a> </div></body> <script> Vue.component('component-a',{ template: ` <div> <h2>具名插槽</h2> <slot></slot> <div class="border"></div> <slot name="slota"></slot> <div class="border"></div> <slot name="slotb"></slot> </div> ` }); const app = new Vue({ el: '#app', data() { return {}; } }); </script></code></pre><h4 id="作用域插槽"><a href="#作用域插槽" class="headerlink" title="作用域插槽"></a>作用域插槽</h4><p>  其实上面两种插槽都很简单,一个就是默认插槽不进行命名,具名插槽就是给个名字,然后标签内的内容就会和组件中的名字一一对应,分发内容。</p><p>  作用域插槽就是可以把组件标签内的数据在组件元素中使用,我也不知道这样解释合不合理,先看个简单的例子把。</p><pre><code class="vue"><body> <div id="app"> <component-a> <template slot-scope="name"> {{ name }} </template> </component-a> </div></body> <script> Vue.component('component-a',{ template: ` <div> <slot say="hello"></slot> </div> ` }); const app = new Vue({ el: '#app', data() { return { }; } }); </script></code></pre><p>  在slot元素上定义一个属性,然后在template添加slot-scope,我们将结果放在标签内展示出来,会看到一个slot属性和值的键值对,这就是作用域插槽。</p><p>  再来看看这个例子:</p><pre><code class="vue"><body> <div id="app"> <component-a :lists="list"> <template slot-scope="name"> {{ name }} </template> </component-a> </div></body> <script> Vue.component('component-a',{ props: ['lists'], template: ` <div> <ol> <li v-for="item in lists"> <slot :data="item"></slot> </li> </ol> </div> ` }); const app = new Vue({ el: '#app', data() { return { list: [ { no: 1, name: '张三' }, { no: 2, name: '李四' }, { no: 3, name: '王五' }, { no: 4, name: '陈六' }, { no: 5, name: '吴七' } ] }; } }); </script></code></pre><p>  如果你用过elementui,就会觉得很熟悉,table就是这样的。</p><p>  作用域插槽还是比较好用的,这篇文章也只是简单的介绍了插槽的使用和给了一些例子来方便说明。</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> slot </tag>
</tags>
</entry>
<entry>
<title>Vue数据单向流动</title>
<link href="/2018/09/02/vue-sigleway-flow/"/>
<url>/2018/09/02/vue-sigleway-flow/</url>
<content type="html"><![CDATA[<h4 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h4><p>  官方说法叫单向数据流,意思是父级prop的更新会向下流动到子组件中,但是反过来是不行的。为的是防止子组件修改父组件的状态。但是实际开发中我们可能会遇到以下两种情况:</p><p>  1. 需要使用并修改父组件传递的数据,但不需要回传给父组件</p><p>  2. 需要使用并修改父组件传递的数据并回传给父组件<br><a id="more"></a></p><h4 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h4><p>  如果我们将父组件prop的数据直接在input中进行了修改,vue在控制台中给出警告,栗子:</p><pre><code class="vue">// html<div id="app"> <p>{{ title }}</p> <conponent-son :title="title"></conponent-son></div><script> Vue.component('conponent-son', { props: ['title'], template: `<div> <input type="text" v-model="title"> </div>` }); const app = new Vue({ el: '#app', data() { return { title: '文章', }; }, mounted() { // 模拟异步加载数据 window.setTimeout(()=>{ this.title = '文章'; },100); } });</script></code></pre><p><img src="https://i.loli.net/2018/09/02/5b8bb4fc4f1e6.png" alt="错误"></p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p><strong>1.使用父组件数据,修改不回传</strong></p><p>  这种情况通常是需要修改已经存在的文章或者其他的个人信息之类的表单数据,通过父组件获取数据,传给子组件,子组件需要进行修改,然后直接请求接口修改信息,不需要回传给父组件再请求接口修改。这种方式的解决方法再vue给出的警告已经说的很清楚了,使用data或者计算方法去代替这种操作。先看第一种data方法:</p><pre><code class="vue">Vue.component('conponent-son', { props: ['title'], data(){ return { currentTitle: this.title }; }, template: `<div> <input type="text" v-model="currentTitle"> </div>` });</code></pre><p>  这种方法主要是通过组件的一个局部变量,将props的数据赋值给局部变量currentTitle,这样就不会存在数据回流的情况了,因为我们是修改的并不是原数据。另外也可以使用计算属性getter&setter,不推荐,毕竟有简单的方法实现了,何必钻牛角尖呢。</p><p><strong>2.使用父组件数据需要回传</strong></p><p>  这种情况呢就跟上面的有一定区别,在于通过父组件获取数据后,传给子组件修改,需要通过父组件请求接口完成数据的更改。所以需要回传,但是这种操作在vue看来是不安全的,因为子组件去修改了父组件的数据状态,有时候我们不得不这样做比如所说的这种情况。栗子:</p><pre><code class="vue"><div id="app"> <p>{{ title }}</p> <conponent-son :title="title" @input-done="receiveInput"></conponent-son></div>Vue.component('conponent-son', { props: ['title'], data() { return { currentTitle: this.title }; }, template: `<div> <input type="text" v-model="currentTitle" @blur="commitInput"> </div>`, methods: { commitInput() { this.$emit('input-done', this.currentTitle); } } }); const app = new Vue({ el: '#app', data() { return { title: '文章', }; }, mounted() { // 模拟异步加载数据 window.setTimeout(() => { this.title = '文章'; },100); }, methods: { receiveInput(newTitle) { this.title = newTitle; } } });</code></pre><p>  验证了之后发现直接修改原数据还是会给warning,所以还是需要用到第一种解决方案来处理一下数据,折中,毕竟对于强迫症来说一直给wraning也挺糟的,虽然用户感觉不到,但我们还是得处理一下。</p><p>  主要就是添加自定义事件input-done,然后在父组件中监听该事件,输入完成,焦点离开后,就触发该提交事件,父组件监听到该事件就会将数据修改为newTitle,这样就完成了提交。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>  数据单向流动就是确保数据的安全性,可以向下但不能(是不建议)向上,因为向上可能会涉及一些问题,所以有时候有需求的时候,麻烦还是得麻烦一下。因为这样做虽然麻烦了,但是避免了修改原数据,保证了数据的安全、可靠,值得推荐使用(我当然不是觉得一堆warning看着难受:)。</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> component </tag>
<tag> 单向流动 </tag>
</tags>
</entry>
<entry>
<title>双向绑定</title>
<link href="/2018/08/26/vuedoublesidedbind/"/>
<url>/2018/08/26/vuedoublesidedbind/</url>
<content type="html"><![CDATA[<h4 id="双向绑定"><a href="#双向绑定" class="headerlink" title="双向绑定"></a>双向绑定</h4><p>  数据的双向绑定可以说是MVVM框架的核心思想,MVVM框架包括三个部分,Model、View和ViewModel,分别指数据、视图、ViewModel可以说是前二者的连接者,二者通过它实现数据的双向绑定。以Vue为例,先看下Vue官网数据绑定的示意图:</p><p><img src="https://i.loli.net/2018/08/26/5b827f69c1a85.png" alt="数据绑定"></p><p>  <em>这张图想表达的是,对象a下的属性b定义了getter、setter对属性进行劫持,当属性值改变时就会notify通知watch对象,而watch对象则会notify到view更新。反之,在视图改变数据时,也会触发订阅者watch,更新数据到data中。这样的model能实时响应view上的数据变化,view实时响应model的数据变化,这样的一个过程就叫数据的双向绑定。</em></p><p>  Vue是通过ES5中Object.defineProperty()这个方法来实现getter、setter对数据进行劫持,所以要运行Vue的运行环境需支持ES5。<br><a id="more"></a></p><h4 id="Object-defineProperty"><a href="#Object-defineProperty" class="headerlink" title="Object.defineProperty()"></a>Object.defineProperty()</h4><p>  这是一个ES5的方法,可以在一个对象上定义一个新属性或用来修改一个已经存在的属性,并返回该对象。目前对象的属性描述符有两种主要形式:此数据描述符和存取描述符。</p><p>  <em>数据描述符是一个拥有一个可写或不可写值的属性</em></p><p>  <em>存取描述符是由一对getter-setter函数功能来描述的属性</em></p><p>  描述符只能是以上两种之一,不可能同时拥有这两种。描述符属性包括:configurable(可配置性,这又为true时才能设置)、Writable(是否可写)、Enumerable(是否可枚举)、get(给属性提供getter)、setter(给属性提供setter)。看下这个例子:</p><pre><code class="javascript"> let obj = { name: 'vue' }; // 数据描述符 Object.defineProperty(obj,"age", { value: 11, writable: true, enumerable: true, configurable: true }); // 数据存取符 let objValue = { name: 'data' }; Object.defineProperty(objValue, "age", { get: () => { console.log('getter'); return objValue; }, set: () => { console.log('setter'); }, enumerable: true, configurable: true }); objValue.age = 18; // setter console.log(objValue.age); // getter</code></pre><p>  前面已经提到了,数据描述符和存取描述符二者只能有一,虽然上诉几种属性二者均有,但是实际上writable不能和get、set同时存在。这就是Object.defineProperty方法的用法。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>  Vue通过Object.defineProperty()方法实现对数据的劫持,给每个实例数据添加了getter、setter,仅仅是这样还不能够实现数据的双向绑定。要实现双向绑定还需要对属性的get、set进行监听,还需要实现notify。也就是需要实现订阅器存放订阅者watcher,它可以将view和model数据联系起来,数据变化触发update更新视图。这就是数据双向绑定的大概思路。</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> 双向绑定 </tag>
</tags>
</entry>
<entry>
<title>Vuex</title>
<link href="/2018/08/19/vuex/"/>
<url>/2018/08/19/vuex/</url>
<content type="html"><![CDATA[<h4 id="Vuex"><a href="#Vuex" class="headerlink" title="Vuex"></a>Vuex</h4><p>  官方说vuex是一个专为vue.js应用程序开发的<strong>状态管理模式</strong>。它采用<strong>集中式</strong>存储管理应用的<strong>所有组件的状态</strong>,并以相应的规则保证状态以一种可预测的方式发生变化。</p><p>  按我的理解就是,Vuex也是用来管理组建之间通信的。组件之间都是独立的,组件之间想实现通信,就要用到之前的文章提到的props选项,自定义事件,以及eventbus,前两种只适用于父子关系,eventbus可以适用所有的组件通信。但是据说不是很推荐,我想可能的原因是在大型项目中,到处引用一个eventbus代码可读性差、可能会命名冲突等,以及数据并不好进行管理。而Vuex就是解决这样的问题的,将组件需要共享的数据提出来,在一定的规则下管理这些数据,在大型项目中看起来就会仅仅有条。当然我也说了,是<strong>大型项目</strong>,一般的小项目还是用前三种方式去做吧,不要为了用vuex而用。<br><a id="more"></a></p><h4 id="使用Vuex"><a href="#使用Vuex" class="headerlink" title="使用Vuex"></a>使用Vuex</h4><p>  先创建一个实例,了解一下vuex:</p><pre><code class="html"><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vuex</title> <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script> <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.js"></script></head><body> <div id="app"></div></body><script> Vue.use(Vuex); let store = new Vuex.Store({ // 存放组件之间共享的数据 state: { name: 'myName' }, // 显示更改state里的数据 mutations: { }, // 获取数据的方法 getters: { }, // 类似mutation,不是直接变更状态,而是提交mutation,还可以包含异步操作 actions: { } }); new Vue ({ el: '#app', data() { return { name: 'null' }; }, store, mounted() { console.log(this.$store); } });</script></html></code></pre><p>  在创建vue实例前需使用vuex,也就是Vue.use(Vuex),使用Vuex.Store({})方法创建一个仓库,当Vue组件从store中读取state选项也就是状态,如果store中状态发生更新时,它会及时响应将数据发给其它组件,如果要直接改变store的状态,就使用使用mutations进行显式的更改。另外四个核心选项在代码中已经注释说明了。我们在控制台中打印了$store,来看看有哪些东西:</p><p><img src="https://i.loli.net/2018/08/19/5b797da78e02d.png" alt="store"></p><p>  一般情况下会在组件的计算属性中来获取state的数据,原因是计算属性会监控数据变化,数据改变就会响应。</p><pre><code class="javascript">// 在上面代码的html中加一个helloword的标签,在注册一个helloword的组件。就可以到浏览器中看到效果了。Vue.component('helloword',{ template:"<div>{{ name }}</div>", computed: { name() { return this.$store.state.name } }, mounted() { console.log(this); } });</code></pre><p>  我们可以在浏览器中看到保存在state中的数据,state就是存放共享数据的地方,getters就是store的计算属性,跟组件的计算属性类似,看一个例子:</p><pre><code class="html"><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vuex</title> <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script> <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.js"></script></head><body> <div id="app"> <helloword></helloword> </div></body><script> Vue.use(Vuex); let store = new Vuex.Store({ // 存放组件之间共享的数据 state: { name: 'myName', age: 18 }, // 显示更改state里的数据 mutations: { }, // 获取数据的方法 getters: { getAge(state) { return state.age; } }, // 类似mutation,不是直接变更状态,而是提交mutation,还可以包含异步操作 actions: { } }); Vue.component('helloword',{ template:"<div><div>{{ name }}</div><div>{{ age }}</div></div>", computed: { name() { return this.$store.state.name }, age() { return this.$store.getters.getAge; } }, mounted() { console.log(this); } }); new Vue ({ el: '#app', data() { return { name: 'null' }; }, store, mounted() { console.log(this.$store); } });</script></html></code></pre><p>  通过在store的getters中定义getAge方法,就可以得到age了。</p><p>  mutations:在vuex中实际改变状态state的唯一方法就是通过commit一个mutation,mutations内的函数接收state作为第一参数,接收payload(载荷)作为第二参数,这个就是记录使用该函数的信息,就是提交了什么更改了什么之类的,所以这个东西真的跟git是比较相似的。例子:</p><pre><code class="html"><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vuex</title> <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script> <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.js"></script></head><body> <div id="app"> <helloword></helloword> </div></body><script> Vue.use(Vuex); let store = new Vuex.Store({ // 存放组件之间共享的数据 state: { name: 'myName', age: 18 }, // 显示更改state里的数据 mutations: { changeAge(state, a) { state.age += a; console.log(state.age); } }, // 获取数据的方法 getters: { getAge(state) { return state.age; } }, // 类似mutation,不是直接变更状态,而是提交mutation,还可以包含异步操作 actions: { } }); Vue.component('helloword',{ template:"<div><div>{{ name }}</div><div @click='changAge'>{{ age }}</div></div>", computed: { name() { return this.$store.state.name }, age() { return this.$store.getters.getAge; } }, mounted() { console.log(this); }, methods: { changAge() { // 组件中提交 this.$store.commit('changeAge', 12); } } }); new Vue ({ el: '#app', data() { return { name: 'null' }; }, store, mounted() { console.log(this.$store); } });</script></html></code></pre><p>  当我们每次点击年龄的时候就会调用组件changeAge的方法,然后在这个方法中我们显示的提交了更改state中的age。当然,mutations只能处理同步方法。所以actions应运而生,js中涉及很多回调,涉及很多异步操作。actions不是直接更改state而是通过提交mutation来更改数据,action可以包含任意的异步操作,ajax、settimeout等。</p><pre><code class="html"><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vuex</title> <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script> <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.js"></script></head><body> <div id="app"> <helloword></helloword> </div></body><script> Vue.use(Vuex); let store = new Vuex.Store({ // 存放组件之间共享的数据 state: { name: 'myName', age: 18 }, // 显示更改state里的数据 mutations: { changeAge(state, a) { state.age += a; console.log(state.age); }, asyncChange(state, data) { state.name += data; console.log(state.name); } }, // 获取数据的方法 getters: { getAge(state) { return state.age; } }, // 类似mutation,不是直接变更状态,而是提交mutation,还可以包含异步操作 actions: { commitChangeName(context, value) { setTimeout(() => { context.commit('asyncChange', value); },1000); } } }); Vue.component('helloword',{ template:` <div> <div @click='alertName'>{{ name }}</div> <div @click='changAge'>{{ age }}</div> </div>`, computed: { name() { return this.$store.state.name }, age() { return this.$store.getters.getAge; } }, mounted() { console.log(this); }, methods: { changAge() { // 组件中提交 this.$store.commit('changeAge', 12); }, alertName() { this.$store.dispatch('commitChangeName', ' is wangx.'); } } }); new Vue ({ el: '#app', data() { return { name: 'null' }; }, store, mounted() { console.log(this.$store); } });</script></html></code></pre><p>  上面的代码就是一个actions的例子,在组件中注册一个方法,alertName,点击myName之后,我们派发事件,触发actions中的commitChangeName,该方法中有一个异步操作,等待1秒后会提交一个mutation,更改state中的name。我们在浏览器中就可以看到效果啦。这就是actions的一个更改state的流程。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>  这是我看了Vuex的官网学习了一下以及网上看的教程,大概了解了如何使用vuex。当然还没有用到项目中去,项目中用也不是不可以,我觉得如果项目比较小,可管理的数据比较少,我们直接用其他的通信方式处理就好了。state就是存放数据的仓库,mutation就是用来显示的更改仓库中的数据,就像仓库管理员一样。getters就像出货的工人,只管往外取,不管如何往仓库装货,当然一般情况,仓库的工人都是需要搬进搬出的。另外actions就像中间人,告诉你管理员怎么改数据,管理员去改。大概理解的vuex就是这样,后续实际使用遇到坑了再来填。</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> 通信 </tag>
<tag> vuex </tag>
</tags>
</entry>
<entry>
<title>图解http读后感</title>
<link href="/2018/08/14/tujiehttp/"/>
<url>/2018/08/14/tujiehttp/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>   去年上计算机网络的时候,没有好好学,很多东西都是懵懵懂懂的,最后草草复习了一下,考过了这门课。但是网络这块的知识在开发中还是蛮重要的,所以就在推荐下买了这本图解http,在这里总结下这本书的内容以及来一篇读后感。<br><a id="more"></a></p><h2 id="内容整理"><a href="#内容整理" class="headerlink" title="内容整理"></a>内容整理</h2><p> <strong>1.了解web及网络基础</strong><br>   web使用HTTP(HyperText Transfer Protocol,超文本传输协议)作为规范,完成客户端到服务端等一系列运作。web是建立在HTTP协议上通信的。<br>   1989.03,http诞生。1990.11,CERN成功研发世界上第一台Web服务器和Web浏览器。1994.12,网景发布Netscape Nacigator 1.0,1995年微软发布IE1.0/2.0。95年起,两家公司爆发浏览器大战,各自拓展HTML,对Web标准视而不见,还多次新增功能而不出文档说明(web开发兼容性的坑就是从这来的T.T)。2000年后,网景没落,Firefox、chrome、opera、Safari于IE抢占市场份额。1996年5月HTTP标准正式公布,至今广泛应用于服务器端。1997年HTTP/1.1公布,作为目前主流的HTTP协议版本。HTTP/2.0正在制定中。</p><p>  TCP/IP是互联网相关的各类协议族的总称。TCP/IP协议族里重要的一点就是分层,按层次分为以下4层:应用层、传输层、网络层和数据链路层。</p><p>  应用层:应用层决定了向用户提供应用服务时通信的活动,协议族内预存了各类通用应用服务,如FTP、DNS,HTTP也处于这一层。</p><p>  传输层:对上层应用层提供处于网络连接中的两台计算机之间的数据传输。传输层有两个协议TCP和UDP。</p><p>  网络层:处理在网络上流动的数据包,数据包是网络传输的最小数据单位,这一层规定了通过怎样的路径到达对方计算机,并把数据包给对方。与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多的选项中选择一条传输路线。</p><p>  数据链路层:处理连接网络的硬件部分,包括操作系统、硬件的设备驱动及光线等物理可见的部分。硬件上的范畴均在链路层的作用范围之内。</p><p>  TCP/IP传输流,通过分层与对方进行通信,发送端应用层自上而下,接收端自下而上,如图:</p><p><img src="https://i.loli.net/2018/08/16/5b755a606febc.png" alt="TCP/IP"></p><p>  与HTTP密切相关的协议:IP、TCP、DNS,IP协议确保正确发送给对方,通过IP地址和MAC地址,IP地址可动态变换,但MAC地址一般不变。TCP协议采用三次握手策略确保数据能传达给目标。如图:</p><p><img src="https://i.loli.net/2018/08/16/5b755c17f1c04.png" alt="三次握手"></p><p>  DNS解析域名,就是把这样的域名blog.wangx.me解析成IP地址1xx.xx.xx.xx3。HTTP通信过程中集中协议的作用如图:</p><p><img src="https://i.loli.net/2018/08/16/5b755cdb200ed.png" alt="http通信过程"></p><p><strong>2.http协议(1.1版本)</strong></p><p>  http协议用于客户端和服务端通信,方式是客户端发送请求,服务器收到请求返回响应结果。http有get、post、put、options等方法。http在1.1版本提出了持久连接(keep-alive),只要任意一端没有明确提出断开连接,就保持TCP连接状态。http使用cookie进行状态管理。</p><p><strong>3.http报文的http信息</strong></p><p>  用于HTTP协议交互的信息称为HTTP报文,报文包括报文首部、空行和主体。通过编码来提升传输速率,报文又分请求报文和响应报文。</p><p>  请求行:包含上面所说的请求方法,请求URI和HTTP版本。</p><p>  状态行:包含表明响应结果的状态码,原因短语和HTTP版本。</p><p>  首部字段:包含表示请求和相应的各种条件和属性的各类首部。</p><p><strong>4.返回结果的HTTP状态码</strong></p><p>  状态码是用来描述客户端向服务器发送请求时返回的请求结果。通过状态码可以知道服务器是正常处理了请求还是出错了。状态码类别:</p><p><img src="https://i.loli.net/2018/08/19/5b78e4e2d0dca.png" alt="状态码类别"></p><p>  常见的状态码:200 OK,表示请求被正常处理了。204 No Content表示已成功处理但没有内容返回。301 Moved Permanently永久重定向。400 Bad Request表明请求报文存在语法错误。401 Unauthorized未获得授权,表明需要认证。403 Forbidden 请求资源被拒绝。404 Not Found表明服务器找不到请求资源。500 Internal Server Error表明请求时服务器发生错误。503 Service Unavailable表明服务器处于超负载或停机维护。</p><p><strong>5.与HTTP协作的Web服务器</strong></p><p>   一台Web服务器可搭建多个域名的Web网站,当两个域名部署在同一个服务器上时,通过DNS解析后会得到一个相同的IP地址,,所以在请求时必须指定URI。代理、网关、隧道,代理是接收客户端请求转发给服务端,再将服务端响应接收转发给客户端。缓存,缓存服务器保存了源服务器的资源副本,在请求时直接向缓存服务器请求,能节省流量和通信时间,达到加速的效果。</p><p><strong>6.HTTP首部</strong></p><p>  HTTP协议的请求和响应报文中必定包含HTTP首部。首部字段分为4种通用首部字段、请求首部字段、响应首部字段、实体首部字段。请求首部字段中User-Agent可以告诉我们请求的客户端的信息,包含设备系统、浏览器版本等信息。Authorization是用来告知服务器用户的认证信息。Cookie用来做用户识别和状态管理。</p><p><strong>7.确保Web安全的HTTPS</strong></p><p>  尽管HTTP具有相当优秀和方便的一面,但是也有它的缺点,使用明文,内容可能会被窃听,不验证通信身份,可能遭遇伪装,无法验证报文完整性,信息可能遭篡改。所以HTTPS=HTTP+加密+认证+完整性保护。HTTPS安全通信的机制:</p><p><img src="https://i.loli.net/2018/08/19/5b7907af31b7b.png" alt="HTTPS通信机制"></p><p><strong>8.认证机制</strong></p><p>  如果只想让特定的人浏览页面,就需要认证功能。FormBase认证即表单认证是我们最常见的,一个网站大多都需要我们进行注册和登陆才能访问一些资源。当然还有BASIC、DIGEST、SSL认证等认证方式。</p><p><strong>9.HTTP新增的功能的协议</strong></p><p>  2010年google发布了SPDY,旨在解决HTTP性能瓶颈,缩短web页面加载时间。使用浏览器进行全双工通信的WebSocket,推送功能由服务器向客户端推送数据,减少通信量。WebSocket通信图示:</p><p><img src="https://i.loli.net/2018/08/19/5b790f850d41e.png" alt="websocket"></p><p><strong>10.构建Web内容的技术</strong></p><p>  这就没什么好说的了,就是HTML、CSS、JS,当然现在构建Web内容的技术当然不止这些了很多方便开发的库、打包工具等等。还提到了数据格式,XML、JSON等。</p><p><strong>11.Web的攻击技术</strong></p><p>  互联网的攻击大多都是冲着Web站点来的,主要是利用HTTP不具备安全功能。对Web应用的攻击模式主要有两种主动攻击和被动攻击,SQL注入、OS命令注入、HTTP首部注入、邮件首部注入、目录遍历攻击。还有就是设计上的缺陷引发的安全漏洞,有强制浏览、不正确错误信息处理、开放重定向等。还有因会话管理疏忽引发的安全漏洞,主要有会话劫持、会话固定攻击等。还有一些其他的安全漏洞,密码破解、点击劫持、DOS攻击、后门程序等等。</p><h2 id="读后感"><a href="#读后感" class="headerlink" title="读后感"></a>读后感</h2><p>  怎么说呢,读完之后对HTTP有了一定的理解,但我觉得还是不够,可能读完这第一遍对我自己在开发中遇到的问题可能印象特别深刻,一些常见的状态码200、301、400、401、404、500等,以及首部字段Authorization、User-Agent、Cookie等。当然还有很多HTTP相关的概念,只是匆匆读过一遍,稍微有了点印象,但不够深刻。另外还有很多没有遇到过的东西,读了之后也有点懵,可能在以后用到了再来读一遍印象会更加的深刻。这本书还是比较好的,推荐阅读。1.2章都是简单的介绍,7.8.9.10.11章也都是简要的进行了介绍,那么主要内容就是3.4.5.6是需要仔细去阅读的。</p>]]></content>
<categories>
<category> 网络 </category>
</categories>
<tags>
<tag> http </tag>
</tags>
</entry>
<entry>
<title>从wordpress转战hexo</title>
<link href="/2018/08/12/firstblood/"/>
<url>/2018/08/12/firstblood/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><hr><p>   之前弄了个腾讯云的学生机,顺带利用学生优惠申请了个域名,不知道干嘛就搭了一个wordpress。当时用wordpress还是蛮流行的,有很多丰富的插件,会一些html也可以自己定制一些东西。wordpress很臃肿,又受限于学生机,首次加载贼慢,还有学生机的内存,之前课程的时候弄ubuntu图形界面,主机崩掉了,丢失了一些数据。最近不知怎么wordpress站点被黑了,每天会发几篇文章。也没有其他恶意行为,找了原因,发现也没完全解决掉。然后就被安利了hexo,了解了下,hexo全部生成静态文件,加载很快而且没有数据库,迁移也方便,也很安全。</p><p>   hexo的搭建还是蛮简单的,但是在这个过程中还是踩了很多坑,就来分享一下遇到的坑。<br><a id="more"></a></p><h2 id="hexo搭建"><a href="#hexo搭建" class="headerlink" title="hexo搭建"></a>hexo搭建</h2><hr><p>   这个过程还是蛮简单的,按照网上普通的配置教程进行配置就行了。就来说我这个过程遇到的坑。</p><p> <strong>1.windows下cmd和gitbash,都无法成功登陆github。</strong><br>   本地搭建好hexo之后往github上部署的时候,我使用的https,这种情况要输入github账号和密码,部署的时候会弹出登陆,确认了很多遍账号密码确实没输错,才觉得可能这里有问题,有很多人都遇到了问题。最后使用ssh成功登陆并部署成功,所以建议是从git拉代码还是使用ssh比较稳妥,当然其他os好像不会存在这个问题。</p><h2 id="使用自己的域名"><a href="#使用自己的域名" class="headerlink" title="使用自己的域名"></a>使用自己的域名</h2><hr><p>   在namesilo购买了一个自己名字简拼的域名,准备把自己博客地址绑定到自己的域名。因为不是很懂,就在网上找教程,都没有讲的很清楚,导致踩了坑,还不知道为什么。</p><p> <strong>1.Nameserver的问题</strong><br>   我用cloudflare做dns解析,但是当时并不知道这个东西,也没有去改namesilo域名下自带的nameserver,就开始用cloudflare做dns解析,这样导致还是用的namesilo的域名解析。<br> <strong>nameserver</strong>: <em>表示解析域名时使用该地址指定的主机为域名服务器,且域名服务器是按照文件顺序来查询的,只有当第一个域名服务器没有响应才会去查找下一个nameserver。</em><br>   这就可以解释开始为什么部署不成功,而且还是链接到namesilo的地址,后面我将cloudflare给我的域名服务器添加到nameserver中,还是不生效,这个问题也就可以解释拉。所以要做DNS解析,先得修改域名的nameserver,当然如果你是使用购买域名的供应商的DNS解析就不用修改了。<br>   DNS解析成功后,在github pages服务中填写自己解析好的域名就可以实现自己域名的访问了。</p><p> <strong>2.使用cloudflare的cdn加速导致站点无法访问</strong><br>   这个问题倒还没研究清楚,是如何产生的,反正我开了cdn加速就会导致站点无法访问,无奈,只能暂时先关闭cdn加速,然后有时间再研究,使用cloudflare的免费cdn为什么会导致站点无法访问。</p><p> <strong>3.给自己网站加小绿锁,配证书</strong><br>   这一步在解决了Nameserver之后,好像就没有什么困难了。将域名的nameserver配置成cloudflare的域名服务器之后,记得在域名overview下点击check,确保域名成功激活。域名为激活状态后,就直接申请一个ssl证书就可以了,最多可以申请15年。最后应该就可以在github pages服务下选择强制使用https就可以了。</p><h2 id="选择一个hexo主题"><a href="#选择一个hexo主题" class="headerlink" title="选择一个hexo主题"></a>选择一个hexo主题</h2><hr><p>   hexo和wordpress一样有很多主题可供选择,我选择的是hexo-theme-lite,比较烦的一点是他文章是全部在首页进行展示的,看着有些乱,准备后面花时间改进下,被吸引的主要是这个高斯模糊的效果很赞。但是我还是遇到坑了。</p><p> <strong>1.删除文章或迁移文章会导致hexo出错,无法生成静态文件也无法部署。</strong><br>   我第一次是删除了原有的一篇helloword文章,然后部署就报错了,当时看是hexo官方一个api库的报错,就以为是自己操作的锅,没办法我就重新来过部署了一遍hexo。然后我将wordpress的文章迁移过来的时候,也报了相同的错误,就觉得这不是偶然,然我去看了主题中引的这个库,防线报错的地方是一个比较低级的错误,一个过滤htmltag的函数,这个函数接收一个字符串,如果字符串非空就过滤htmltag并返回,如果为空返回null,然后在调用这个方法的基础上又使用了substring,顿时就黑人问号脸了…<br>   substring()是js字符串的一个方法,但前面那个方法为空会返回null,null是一个空对象,这里是解析不了的。所以我就修改了这个地方,测试了一下就解决了这个bug。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><hr><p>   还是花了几个晚上的时间来搞这个博客,希望也是一个好的开始。另外还有一些问题可能记不太清楚了。<br>   看了整篇,怎么没有一张图???本来想截图引入的,但是发现好像不是比较好的做法,看到网上建议用七牛云做图床的,准备在搞一搞一起把图片弄上来。</p>]]></content>
<categories>
<category> other </category>
</categories>
<tags>
<tag> hexo </tag>
</tags>
</entry>
<entry>
<title>Vue组件之间通信</title>
<link href="/2018/08/05/vueconponentscommunity/"/>
<url>/2018/08/05/vueconponentscommunity/</url>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>  之前在学校一直在用nodom框架进行开发,对vue的了解也仅仅局限于对比二者的区别,并没有很深入的去了解。而且nodom的用法和vue的比较相似,所以上手vue是比较快的,但是其中很多原理并不熟悉,所以想总结并分享一下学到的东西。 </p><p>  组件化是vue.js或者说现在的mvvm框架中一个重要的思想,也是前端发展的趋势,这其中组件间的通信又是vue数据驱动的灵魂所在。所以在这谈谈Vue2.x的组件通信,这一篇主要聊聊非Vuex的做法,下一篇文章再讲Vuex。Vue2.x已经废弃了$dispatch和$broadcast,nodom中还保留着broadcast的方法…<br><a id="more"></a></p><h4 id="父子组件通信"><a href="#父子组件通信" class="headerlink" title="父子组件通信"></a>父子组件通信</h4><h5 id="1-父子组件,父组件向子组件传递数据"><a href="#1-父子组件,父组件向子组件传递数据" class="headerlink" title="1.父子组件,父组件向子组件传递数据"></a>1.父子组件,父组件向子组件传递数据</h5><p>  父组件向子组件传递数据时,这是比较简单也是比较常见的一种方式,我们首先看看props,prop的值有两种一种是使用数组和对象,静态数据比较简单,作为理解就使用数组为例了,先看下代码: </p><pre><code class="html"><body> <div id="app"> <div class=""> 父组件 </div> <child message="父亲给儿子说的话:xxxxx"></child> </div></body> <script> Vue.component('child',{ props: ['message'], template: '<div>{ { message }}</div>' }); const app = new Vue({ el: '#app' }); </script></html></code></pre><p>  通过在props中声明数据,在子组件中添加message并赋值进行传递,需要注意的是HTML不区分大小写,在dom模板中,驼峰命名的props名称要转为短横线分隔命名。 </p><p>  另外在实际开发中,很少会传递死的数据,大部分都是使用动态数据,此时我们需要使用v-bind指令来绑定父组件数据,当父组件数据发生变化时,也会传递给子组件,代码: </p><pre><code class="html"><div id="app"> <div class=""> 父组件数据 <input type="text" v-model="parentMessage"> </div> child :message="parentMessage"</child> </div></body> <script> Vue.component('child',{ props: ['message'], template: '<div>子组件收到的消息:{ { message }}</div>' }); const app = new Vue({ el: '#app', data: { parentMessage: '' } }); </script></code></pre><p>  当然这种传递方式,不局限于字符串,object也是可以的,也是我们用的比较多的。 </p><p>  另外props传值需要注意,props中声明的数据和子组件自身的data是有一定区别的,props的数据来自于父组件,而data中的数据属于组件本身,作用域也是组件本身,但是这两种数据都可以在template、computed、methods中使用。</p><h5 id="2-父子组件,子组件向父组件传递数据,自定义事件方式"><a href="#2-父子组件,子组件向父组件传递数据,自定义事件方式" class="headerlink" title="2.父子组件,子组件向父组件传递数据,自定义事件方式"></a>2.父子组件,子组件向父组件传递数据,自定义事件方式</h5><p>  子组件向父组件传递数据时,要用到自定义事件的方式。v-on指令除了监听dom事件,还可以用于组件间的自定义事件。子组件用$emit()方法触发事件,父组件用$on()方法进行家庭或者使用v-on进行监听自定义事件。代码: </p><pre><code class="html"><div id="app"> <div class=""> total: { { total }} </div> child @increase="getTotal"</child> </div></body> <script> Vue.component('child',{ props: ['message'], template: ` <div class="child"> button @click="increase"+1</button> </div>`, data() { return { num: 0 }; }, methods: { increase() { this.num ++; this.$emit('increase',this.num); } } }); const app = new Vue({ el: '#app', data: { total: 0 }, methods: { getTotal(total) { this.total = total; } } }); </script></code></pre><p>  例子中,在子组件的increase方法中,$emit()方法的第一个参数是要注册的事件名称,后面的参数是要传递的数据,父组件使用v-on指令监听子组件事件,从而接收数据。 </p><h4 id="任意组件之间的通信-父子组件、兄弟组件、跨级组件"><a href="#任意组件之间的通信-父子组件、兄弟组件、跨级组件" class="headerlink" title="任意组件之间的通信(父子组件、兄弟组件、跨级组件)"></a>任意组件之间的通信(父子组件、兄弟组件、跨级组件)</h4><p>  在vue2.x中,还有一种使用一个空的Vue实例作为中央总线(bus),也就是中介,可以实现任何组件之间的通信。like中介(虽然我挺讨厌中介的..),通信的任何组件之间,只需要通过这个中介,就可以实现通信,代码:</p><pre><code class="html"><div id="app"> 父组件:{ { message }} <component-b></component-b> <component-c></component-c> </div></body> <script> // 创建一个空实例 作为中央总线 const bus = new Vue(); Vue.component('component-b',{ template: <button @click="handleEventB">B组件</button>, data() { return { componentBData: '我是B组件' }; }, methods: { handleEventB() { bus.$emit('b-message', this.componentBData); } } }); Vue.component('component-c',{ template: <button @click="handleEventC">C组件</button>, data() { return { componentCData: '我是C组件' }; }, methods: { handleEventC() { bus.$emit('c-message', this.componentCData); } } }); const app = new Vue({ el: '#app', data() { return { message: '' }; }, // 监听bus实例中的事件。 mounted() { let me = this; bus.$on('c-message', (msg) => { me.message = msg; }); bus.$on('b-message', (msg) => { me.message = msg; }); } }); </script></code></pre><p>  我们创建了一个空的vue实例bus作为中介,然后定义了两个组件b、c,两个按钮,在handle中用bus.$emit注册注册b-message的事件,并传递数据,最后在app中,在生命周期mounted的钩子函数监听这两个事件,点击按钮,就message方法发送出去,在app中就会接收到来自bus的事件,从而在回调中拿到数据。这就是中央总线的方式实现组件通信,当然这种方法不局限于父子组件,可以用于任意的组件通信方式。</p><h4 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h4><p>  还有一种父调用子和子调用父的方法实现父子通信,但是我觉得父子组件用前面提到的两种方法更加可靠,并且父调用子还可以接收,但是子调用父比较不可取,所以在这篇文章中就没有提到。 </p><p>  这篇文章只提到了这几种通信方式,基本可以满足日常开发了,但是Vue2.x还提供了Vuex,把这几种方式和vuex分开,下一篇文章会谈谈Vuex。</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> VUE </tag>
<tag> 组件 </tag>
<tag> 通信 </tag>
</tags>
</entry>
<entry>
<title>CSS图片居中</title>
<link href="/2018/07/27/cssimagecentre/"/>
<url>/2018/07/27/cssimagecentre/</url>
<content type="html"><![CDATA[<h4 id="综述"><a href="#综述" class="headerlink" title="综述"></a>综述</h4><p>  在前端开发中,居中是一个比较常见的问题。通常的居中指的是div块居中,div块居中,在前面我已经有一篇博客谈到了一些。这里主要总结一下图片的居中,居中一般分为水平居中和水平垂直居中。图片水平居中的方法有两种,一是使用margin: 0 auto;,二是使用text-align: center;。水平垂直居中的方案有很多,主要有flex、table、行高、以及张鑫旭推荐的两种方法。<br><a id="more"></a></p><h4 id="水平居中"><a href="#水平居中" class="headerlink" title="水平居中"></a>水平居中</h4><p>  提到的两种方法都很简单,就直接贴代码,不再赘述了。</p><h5 id="margin"><a href="#margin" class="headerlink" title="margin"></a>margin</h5><pre><code class="css">.img-box { text-align: center;}.img-box img { margin: 0 auto;}</code></pre><h5 id="text-align"><a href="#text-align" class="headerlink" title="text-align"></a>text-align</h5><pre><code class="css">.img-box { text-align: center;}.img-box img { display: inline-block;}</code></pre><h4 id="水平垂直居中"><a href="#水平垂直居中" class="headerlink" title="水平垂直居中"></a>水平垂直居中</h4><h5 id="flex"><a href="#flex" class="headerlink" title="flex"></a>flex</h5><p>  相对来说,flex大法是最简单的一种,优点嘛,代码简单不需多余标签同时也好理解,但是缺点嘛…pc只支持到ie10,移动端基本没什么问题,所以如果在不兼容到ie10以下的pc或是移动端开发,flex大法,嗯~真香。</p><pre><code class="css">.img-box { display: flex; align-items: center; justify-content: center;}</code></pre><h5 id="table"><a href="#table" class="headerlink" title="table"></a>table</h5><p>  这种方法主要利用table的垂直居中属性,我们这里使用display: table属性来模拟table的,当然ie6/7是不支持这个属性的,所以我们如果要兼容ie6/7的话要单独做处理。</p><pre><code class="css">.img-box { display: table;}.img-box span { display: table-cell; text-align: center; vertical-align: middle;}.img-box img { display: inline-block;}</code></pre><p>  这种做法呢,会多添加一个无用的标签,将span作为表格td使span水平垂直居中,在用前面提到的text-align方法使图片水平居中就ok了。缺点使会添加一个无用标签以及使用display: table;可能会影响布局。</p><h4 id="行高"><a href="#行高" class="headerlink" title="行高"></a>行高</h4><p>  如果在已知高度的情况下,使用行高的方式可能是最好的方法了,不会有兼容性问题,但前提是必须已知父元素高度。</p><pre><code class="css">.img-box { height: 400px; line-height: 400px; text-align: center;}.img-box img { display: inline-block;}</code></pre><h4 id="透明图片-背景定位"><a href="#透明图片-背景定位" class="headerlink" title="透明图片+背景定位"></a>透明图片+背景定位</h4><p>  这是张鑫旭在博客中写到一种方法,也是比较好的一种方法,也好维护,但是缺点嘛,需要我们准备一张透明的gif图作为背景,这里只是提一下这种方法,个人并不是很推荐。</p><pre><code class="css"><img src="../image/pixel.gif" style="background-image:url(mm1.jpg);" />img { background-position: center;}</code></pre><h4 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h4><p>  只是谈到了几种常见的图片居中方法,张鑫旭博客中还有一个透明图片拉伸对齐水平垂直居中的方法。也是最近在读css世界这本书,觉得自己css确实只处于一个入门的水平,希望读完这本书有一定的提升。 </p><p>  对于图片居中的方法,如果是移动端开发或者不考虑老版本的情况下,用flex就完事儿了。但更多的还是要兼容一下的,所以,在已知高度的情况下使用行高的方法去做,如果还要兼容ie6/7可以去看看淘宝团队是如何使用table方法去做的。张鑫旭的博客中也有提到。最后附上链接:<a href="https://www.zhangxinxu.com/wordpress/2009/08/%E5%A4%A7%E5%B0%8F%E4%B8%8D%E5%9B%BA%E5%AE%9A%E7%9A%84%E5%9B%BE%E7%89%87%E3%80%81%E5%A4%9A%E8%A1%8C%E6%96%87%E5%AD%97%E7%9A%84%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/" target="_blank" rel="noopener">透明图片拉伸对齐实现垂直居中显示</a></p>]]></content>
<categories>
<category> HTML/CSS </category>
</categories>
<tags>
<tag> CSS </tag>
</tags>
</entry>
<entry>
<title>Vue实例生命周期</title>
<link href="/2018/07/07/vuelifecyle/"/>
<url>/2018/07/07/vuelifecyle/</url>
<content type="html"><![CDATA[<h4 id="Vue实例生命周期"><a href="#Vue实例生命周期" class="headerlink" title="Vue实例生命周期"></a>Vue实例生命周期</h4><p>  先来上一张vue官网的生命周期图: </p><p><img src="https://i.loli.net/2018/09/08/5b9380089a9dc.png" alt="生命周期"></p><a id="more"></a><p>一个vue实例在创建的时候会调用上面的钩子函数,vue的生命周期钩子函数有: </p><ul><li><p>beforeCreate</p></li><li><p>created</p></li><li><p>beforeMount</p></li><li><p>mounted</p></li><li><p>beforeUpdate</p></li><li><p>updated</p></li><li><p>beforeDestroy</p></li><li><p>destroyed</p></li></ul><p>  下面来一一讲解vue的生命周期钩子函数: </p><p><strong>beforeCreate:</strong></p><p>  vue实例创建前,el、data、methods都还未生成。 </p><p><strong>created:</strong></p><p>  vue实例创建完成,这一阶段进行<strong>事件初始化,同时观测数据</strong>。注意:此时el还是读不到。 </p><p>  这个阶段如果创建实例的时候没有填写el属性的话,将不会往下执行,直到调用$mount(el),时才会继续向下执行。如上图所示。 </p><p> 另外在实例中如果有template属性,将会把template编译成render函数,如果没有将会把外部的html作为模板编译。</p><p><strong>beforeMount:</strong></p><p>  挂载前,这一阶段$el属性已经添加进来,但是html中还是{ { data }}进行占位,还没有将数据进行挂载,以虚拟dom的形式存在。</p><p><strong>mounted:</strong></p><p>  挂载成功,虚拟dom被替换成真是dom,数据也已经进行挂载。 </p><p><strong>beforeUpdate:</strong></p><p>  监听到数据变化,view并没有重新渲染。 </p><p><strong>updated:</strong></p><p>  data中数据已经改变,触发重新渲染。</p><p><strong>beforeDestory:</strong></p><p>  vue实例仍然存在并且可以使用,解除事件监听和数据绑定等。 </p><p><strong>destoryed:</strong></p><p>  vue实例已经销毁。</p><h2 id="Vue生命周期总结"><a href="#Vue生命周期总结" class="headerlink" title="Vue生命周期总结"></a>Vue生命周期总结</h2><p><strong>beforecreate</strong>: 可以在这加loading事件 </p><p><strong>created</strong> :在这结束loading,还做一些初始化,实现函数自执行 </p><p><strong>mounted</strong>: 在这发起后端请求,拿回数据,配合路由钩子做一些事情 </p><p><strong>beforeDestory</strong>: 你确认删除XX吗? </p><p><strong>destoryed</strong> :当前组件已被删除,清空相关内容</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> 生命周期 </tag>
</tags>
</entry>
<entry>
<title>JavaScript-单例模式</title>
<link href="/2018/07/07/javascriptsingleton/"/>
<url>/2018/07/07/javascriptsingleton/</url>
<content type="html"><![CDATA[<p>JavaScript单例模式 </p><hr><h3 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h3><p>  单例模式时一种较为简单的设计模式,传统的单例模式是指保证一个类只有一个实例,并提供一个访问它的全局访问点。<br><a id="more"></a></p><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>  实现的思路是定义一个变量标志是否已经创建过实例,如果已经有实例,则返回该实例对象,否则创建一个实例并返回。用一个创建登录框的例子来说明单例模式在JavaScript中如何实现: </p><pre><code class="javascript">var loginHtml = "账号:<input type=\"text\" /><br/>密码:<input type=\"password\" />";var createLoginDiv = function(html) { this.html = html; this.init();};createLoginDiv.prototype.init = function() { var div = document.createElement('div'); div.innerHTML = this.html; document.body.appendChild(div);};createLoginDiv.getInstance = (function() { var instance = null; return function(html) { if (instance === null) { instance = new createLoginDiv(html); } return instance; };})();var div1 = createLoginDiv.getInstance(loginHtml);var div2 = createLoginDiv.getInstance("账号密码");console.log(div1 === div2); // true 并且页面只有一个登录框,为第一次创建的。</code></pre><p>  这样实现的单例模式耦合度较低,init只负责创建登录框对象,对于返回现有对象还是创建新对象交给getInstance去做,代码也很清晰。通过这样的方式我们想要在页面上只想要一个登录框时可以得到保证,只有一个登录框,不会重复创建,节约性能。</p><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><h3 id="优点:"><a href="#优点:" class="headerlink" title="优点:"></a>优点:</h3><ul><li><p>单例模式声明一个命名空间,生成一个唯一的全局变量,用对象的方式来进行声明:var single = {…},在多人开发时可以很好的解决命名冲突的问题,方便维护、控制代码。</p></li><li><p>单例只声明一个变量,如果我们在js中写多个方法,就会在window中生成多个变量,会占用更多内存单元,而且全局作用域很广,在众多处理函数中都可能改变,很难定位bug,而单例模式在创建的对象变量中可以更快的找到bug,可以大大减少bug修复时间和系统加载时间。</p></li><li><p>实现同一个功能时,比通过new新创建对象对内存、资源的占用更具有优势。</p></li></ul><h4 id="缺点:"><a href="#缺点:" class="headerlink" title="缺点:"></a>缺点:</h4><ul><li><p>扩展性和灵活性不好,当重写单例对象中的方法会破坏原有的功能,当其中某个功能要改变,其他的不变时,单例模式就不太好处理了。</p></li><li><p>引用问题,当创建一个单例,var a = singleton.getInstance(“single1”),将a赋值给b,即浅拷贝,var b = a,我们知道b相当于一个引用,b和a指向的地址相同,修改b时a也会改变。在一个单例对象创建完成之后是不能随意改的。有局限性。</p></li></ul><h3 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h3><p>  单例模式我们经常用到,在一个js中只创建一个对象,比如在一个demo.js中,我们可以这样 var demo = {},这样暴露出来的命名只有demo一个,可以大大减少对全局变量的污染。这种方法,我们在项目中会经常用到,维护起来也比较方便。 </p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>  最上面的代码实现只是为了说明单例模式是一个什么样的结构,但是在我们常见的项目中,单例模式就是基本是在一个js文件中只包含一个对象,在对象里面添加属性和方法。</p>]]></content>
<categories>
<category> javascript </category>
</categories>
<tags>
<tag> javascript </tag>
<tag> singleton </tag>
</tags>
</entry>
<entry>
<title>【转载】深入理解JavaScript事件循环(Eventloop)和microtask</title>
<link href="/2018/05/27/eventloop/"/>
<url>/2018/05/27/eventloop/</url>
<content type="html"><![CDATA[<p><strong>在做javascript练习题或者面试的时候,总会遇到下面这样的题目:</strong></p><pre><code class="javascript"> console.log("1"); setTimeout(()=>{ console.log("2"); },0); Promise.resolve().then(()=>{ console.log("3"); }); console.log("4"); // 控制台输出1 4 3 2</code></pre><a id="more"></a><p>  想知道为什么是这样的结果,就必须理解javascript的eventloop机制,推荐这位博主DongXu的两篇文章,这里就不转载全文了,推荐大家去他的博客看,还有生动的demo,很容易懂。</p><blockquote><p><a href="http://www.cnblogs.com/dong-xu/p/7000163.html" target="_blank" rel="noopener">深入理解 JavaScript 事件循环(一)— event loop</a> </p><p><a href="http://www.cnblogs.com/dong-xu/p/7000139.html" target="_blank" rel="noopener">[译]深入理解 JavaScript 事件循环(二)— task and microtask</a></p></blockquote>]]></content>
<categories>
<category> javascript </category>
</categories>
<tags>
<tag> eventloop </tag>
<tag> 事件循环 </tag>
</tags>
</entry>
<entry>
<title>HTML语义化&HTML5</title>
<link href="/2018/05/10/htmlyuyihua/"/>
<url>/2018/05/10/htmlyuyihua/</url>
<content type="html"><![CDATA[<h2 id="1、什么是HTML语义化?"><a href="#1、什么是HTML语义化?" class="headerlink" title="1、什么是HTML语义化?"></a>1、什么是HTML语义化?</h2><p>  就我自己理解来说,HTML是超文本标记语言。语义化就是让我们清楚整个页面的结构,不论是从代码还是文本内容,不仅要我们人清楚,也要让机器清楚。纯HTML不带样式的时候,我们也能通过代码知道标签标记的是什么内容。比如H标签,我们知道这是一个标题,p一个段落,table表格,form表单等等。HTML5更加注重语义化,新增了很多语义化的标签,如header、nav、footer、article等。关于语义化一些更多的内容可以看这个<a href="https://www.zhihu.com/question/20455165" title="如何理解 Web 语义化?" target="_blank" rel="noopener">如何理解 Web 语义化?</a><br><a id="more"></a></p><h2 id="2、为什么要语义化?"><a href="#2、为什么要语义化?" class="headerlink" title="2、为什么要语义化?"></a>2、为什么要语义化?</h2><p>  在此之前,我写代码也基本上是全篇用div+css实现整个页面结构的,既然我们使用div+css就可以了,为什么还要使用语义化呢? </p><p>  从实现的效果上看,使用语义化的代码和div+css的代码并没有区别,但是代码不仅是给人看的,机器也要看。从维护上面来说,语义化标签比满篇div更好维护,在页面出问题时,语义化会更加的友好。 </p><p>  语义化的好处在于建立起好的HTML结构,使搜索引擎更容易建立索引、抓取。另外就是结构清晰,便于开发人员维护。(大厂的做法,比如阿里的淘宝在div+css实现之余,也会用一些语义化的标签进行强调) </p><h2 id="3、语义化代码应该注意些什么?"><a href="#3、语义化代码应该注意些什么?" class="headerlink" title="3、语义化代码应该注意些什么?"></a>3、语义化代码应该注意些什么?</h2><ul><li><p>尽可能少的使用无语义的标签div和span;在语义不明显时,既可以使用div或者p时,尽量用p, 因为p在默认情况下有上下间距,对兼容特殊终端有利;</p></li><li><p>不要使用纯样式标签,如:b、font、u等,改用css设置。需要强调的文本,可以包含在strong或者em标签中(浏览器预设样式,能用CSS指定就不用他们),strong默认样式是加粗(不要用b),em是斜体(不用i);</p></li><li><p>使用表格时,标题要用caption,表头用thead,主体部分用tbody包围,尾部用tfoot包围。表头和一般单元格要区分开,表头用th,单元格用td;</p></li><li><p>表单域要用fieldset标签包起来,并用legend标签说明表单的用途;每个input标签对应的说明文本都需要使用label标签,并且通过为input设置id属性,在lable标签中设置for=someld来让说明文本和相对应的input关联起来。</p></li></ul><h2 id="4、常用的一些HTML语义标签"><a href="#4、常用的一些HTML语义标签" class="headerlink" title="4、常用的一些HTML语义标签"></a>4、常用的一些HTML语义标签</h2><ul><li><p><code><h1></code>~<code><h6></code> ,作为标题使用,并且依据重要性递减,<code><h1></code> 是最高的等级。</p></li><li><p><code><p></code>段落标记,知道了 <code><p></code> 作为段落,你就不会再使用 <code><br /></code> 来换行了,而且不需要 <code><br /></code> 来区分段落与段落。<code><p></code> 中的文字会自动换行,而且换行的效果优于 <code><br /></code>。段落与段落之间的空隙也可以利用 CSS 来控制,很容易而且清晰的区分出段落与段落。</p></li><li><p><code><ul></code>、<code><ol></code>、<code><li></code>,<code><ul></code> 无序列表,这个被大家广泛的使用,<code><ol></code> 有序列表不常用。在 Web 标准化过程中,<code><ul></code> 还被更多的用于导航条,本来导航条就是个列表,这样做是完全正确的,而且当你的浏览器不支持 CSS 的时候,导航链接仍然很好使,只是美观方面差了一点而已。</p></li><li><p><code><dl></code>、<code><dt></code>、<code><dd></code>,<code><dl></code> 就是“定义列表”。比如说词典里面的词的解释、定义就可以用这种列表。dl不单独使用,它通常与dt和dd一起使用。dl开启一个定义列表,dt表示要定义的项目名称,dd表示对dt的项目的描述。</p></li><li><p><code><em></code>、<code><strong></code>,<code><em></code> 是用作强调,<code><strong></code> 是用作重点强调。</p></li><li><p><code><table></code>、<code><thead></code>、<code><tbody></code>、<code><td></code>、<code><th></code>、<code><caption></code>, 就是用来做表格不要用来布局</p></li></ul><h2 id="5、HTML5新增语义标签"><a href="#5、HTML5新增语义标签" class="headerlink" title="5、HTML5新增语义标签"></a>5、HTML5新增语义标签</h2><ul><li><p><code>header元素</code>:header 元素代表“网页”或“section”的页眉。</p></li><li><p><code>footer元素</code>:footer元素代表“网页”或“section”的页脚,通常含有该节的一些基本信息,譬如:作者,相关文档链接,版权资料。</p></li><li><p><code>nav元素</code>:nav元素代表页面的导航链接区域。用于定义页面的主要导航部分。</p></li><li><p><code>aside元素</code>:aside元素被包含在article元素中作为主要内容的附属信息部分,其中的内容可以是与当前文章有关的相关资料、标签、名次解释等。(特殊的section)</p></li><li><p><code>section元素</code>:section元素代表文档中的“节”或“段”,“段”可以是指一篇文章里按照主题的分段;“节”可以是指一个页面里的分组。section通常还带标题,虽然html5中section会自动给标题h1-h6降级,但是最好手动给他们降级。</p></li><li><p><code>article元素</code>:article元素最容易跟section和div容易混淆,其实article代表一个在文档,页面或者网站中自成一体的内容,其目的是为了让开发者独立开发或重用。譬如论坛的帖子,博客上的文章,一篇用户的评论,一个互动的widget小工具。(特殊的section)除了它的内容,article会有一个标题(通常会在header里),会有一个footer页脚。</p></li></ul><h2 id="6、关于HTML5"><a href="#6、关于HTML5" class="headerlink" title="6、关于HTML5"></a>6、关于HTML5</h2><p>  HTML5对于用户来说,提高了用户体验,加强了视觉感受。HTML5技术在移动端,能够让应用程序回归到网页,并对网页的功能进行扩展,用户不需要下载客户端或插件就能够观看视频、玩游戏,操作更加简单,用户体验更好。HTML5的视音频新技术解决了移动端苹果和安卓4.0+,对flash的支持问题。在视音频方面,性能表现比flash要更好。网页表现方面,HTML5中的CSS3特效样式、Canvas、webgl的介入,不仅加强了网页的视觉效果,甚至能够使用户在网页当中看到三维立体特效。<br>  对于开发者来说,HTML5技术跨平台,适配多终端。传统移动终端上的Native App,开发者的研发工作必须针对不同的进行,成本相对较高。Native App对于用户还存在着管理成本、存储成本以及性能消耗成本。HTML/JavaScript/所开发的应用只要一次开发就能进入所有进行分发。即使是走传统的App Store应用商店渠道,只需要再将底层用HTML5开发的应用“封装”为App,从时间和上讲远小于跨系统移植。<br>  对于来说,HTML5新增的标签,使更加容易抓取和索引网页,从而驱动网站获得更多的点击流量。也就是上文提到的语义化。</p>]]></content>
<categories>
<category> HTML/CSS </category>
</categories>
</entry>
<entry>
<title>【CSS】css居中方案</title>
<link href="/2018/05/07/csscenter/"/>
<url>/2018/05/07/csscenter/</url>
<content type="html"><![CDATA[<p>  关于一些CSS居中的方案,先说一下水平居中和垂直居中的一般方案.<br><a id="more"></a><br>  水平居中设置:</p><blockquote><ol><li><p>行内元素:给父元素设置text-align:center;</p></li><li><p>定宽块状元素:设置左右margin值为auto</p></li><li><p>不定宽块状元素:</p><ol><li><p>在元素外加入table标签(包括table、tbody、tr、td),在把该元素写在td内,然后设置margin的值为auto,注意是设置table不是该元素。</p></li><li><p>给该元素设置display:inline方法,居中方法和行内一致。</p></li><li><p>父元素设置float:left;position:relative和left:50%,子元素设置position:relative和left:-50%。</p></li></ol></li></ol></blockquote><p>  垂直居中设置:</p><blockquote><ol><li><p>父元素高度确定的单行文本:设置height = line-height;</p></li><li><p>父元素高度确定的多行文本:</p><ol><li><p>插入table(和水平居中一致),设置vertical-align:middle;</p></li><li><p>先设置display:table-cell,在设置vertical-align: middle; </p></li></ol></li></ol></blockquote><p>  下面就提供几种居中的方法:</p><h4 id="1-使用定位:position-absolute-设置left、top、margin-left、margin-top属性。"><a href="#1-使用定位:position-absolute-设置left、top、margin-left、margin-top属性。" class="headerlink" title="1.使用定位:position:absolute;设置left、top、margin-left、margin-top属性。"></a>1.使用定位:position:absolute;设置left、top、margin-left、margin-top属性。</h4><pre><code class="css">.content{ position: absolute; width: 100px; height: 100px; top: 50%; left: 50%; margin-top: -50px; margin-left: -50px;}</code></pre><p>  这种方法好处是浏览器基本都能兼容,不足的地方就是需要我们固定宽高。</p><h4 id="2-使用position-absolute-设置top、bottom、right、left为0,margin-auto"><a href="#2-使用position-absolute-设置top、bottom、right、left为0,margin-auto" class="headerlink" title="2.使用position: absolute;设置top、bottom、right、left为0,margin:auto;"></a>2.使用position: absolute;设置top、bottom、right、left为0,margin:auto;</h4><pre><code class="css">.content { position: absolute; width: 100px; height: 100px; top: 0; bottom: 0; left: 0; right: 0; margin: auto; background-color: blue; }</code></pre><h4 id="3-使用display-table-cell属性使内容垂直居中。"><a href="#3-使用display-table-cell属性使内容垂直居中。" class="headerlink" title="3.使用display:table-cell属性使内容垂直居中。"></a>3.使用display:table-cell属性使内容垂直居中。</h4><pre><code class="css">.content { width: 100px; height: 100px; display: table-cell; vertical-align: middle; text-align: center;}</code></pre><h4 id="4-使用css3的display-webkit-box属性,在设置-webkit-box-pack-center-webkit-box-align-center"><a href="#4-使用css3的display-webkit-box属性,在设置-webkit-box-pack-center-webkit-box-align-center" class="headerlink" title="4.使用css3的display:-webkit-box属性,在设置-webkit-box-pack:center/-webkit-box-align:center;"></a>4.使用css3的display:-webkit-box属性,在设置-webkit-box-pack:center/-webkit-box-align:center;</h4><pre><code class="css">.content { width: 100px; height: 100px; display: -webkit-box; -webkit-box-pack: center; -webkit-box-align: center; background-color: blue;}</code></pre><h4 id="5-使用css3的新属性transform-translate-x-y-属性。"><a href="#5-使用css3的新属性transform-translate-x-y-属性。" class="headerlink" title="5.使用css3的新属性transform:translate(x,y)属性。"></a>5.使用css3的新属性transform:translate(x,y)属性。</h4><pre><code>.content { position: absolute; width: 100px; height: 100px; top: 50%; left: 50%; transform: translate(-50%,-50%); -webkit-transform: translate(-50%,-50%); -moz-transform: translate(-50%,-50%); -ms-transform: translate(-50%,-50%); background-color: blue;}</code></pre><pre><code>这种方法不需要设置固定宽高,移动端使用较多,移动端css3兼容的额比较好。</code></pre><h4 id="6-flex弹性布局。"><a href="#6-flex弹性布局。" class="headerlink" title="6.flex弹性布局。"></a>6.flex弹性布局。</h4><pre><code class="css">.container { display: flex; justify-content: center; align-items: center; width: 300px; height: 300px; background-color: red;}.content { width: 100px; height: 100px; background-color: blue;}</code></pre><p>  兼容性不是太好,ie10以下都挂掉了,而且webkit内核的浏览器必须设置为display: -webkit-flex;</p>]]></content>
<categories>
<category> HTML/CSS </category>
</categories>
<tags>
<tag> css </tag>
<tag> 居中 </tag>
</tags>
</entry>
</search>