-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
946 lines (946 loc) · 396 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[SSL/TLS协议简述]]></title>
<url>%2F2018%2F04%2F10%2Fcomputer-SSL%26TLS%E5%8D%8F%E8%AE%AE%2F</url>
<content type="text"><![CDATA[互联网的通信安全(https协议)是建立在SSL/TLS协议之上… 本文主要回答两大问题:①: 传统 http 协议存在哪些问题②: SSL/TLS 协议是如何解决这些问题的 传统 http 协议存在哪些问题不使用 SSL/TLS 的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。 窃听风险(eavesdropping):第三方可以获知通信内容; 篡改风险(tampering):第三方可以修改通信内容; 冒充风险(pretending):第三方可以冒充他人身份参与通信。 SSL/TLS 协议是如何解决这些问题的窃听风险要解决窃听风险的问题,就必须对信息进行加密处理。而目前对信息的加密主要有两大类:对称加密与非对称加密(又称:公钥加密)。 简单理解这两种加密类型就是对称加密的加密与解密用的是同一套密文,而公钥加密的加密用“公钥”,解密用“私钥”。tip: 有时我们也会用私钥加密,然后将加密后的信息传递给拥有公钥的对方,以证明:我是我。(这一过程俗称数字签名,你的私钥就相当于你的指纹,一旦你用自己的私钥对文件进行加密,也就意味只该文件上面有了你的指纹,任何人进行篡改都会被发现) https 协议在进行信息传递的过程中就是通过公钥加密(目前简单这样理解)来保证传输的信息即使被人截获,对方也不能知道信息的具体内容。如此这般便解决了被窃听的隐患。 篡改风险其实理解了前面讲的窃听风险的规避原理,我们便自然知道在客户端与服务器端正常通信的过程中,第三方无法知道双方信息的解密私钥,也无法知道双方信息的加密公钥。所以一旦第三方对加密后的信息进行篡改,真正的接收信息方也就无法通过自己的私钥来对信息进行解密,也就进一步知道信息被篡改了。 上面讲的是在双方在建立信任之后的防篡改机制。双方在建立信任之前,客户端向服务器端索要并验证公钥(该公钥由服务器给出)。此时,客户端如何认定这个公钥就是由目标服务器给出的呢? 解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。(数字证书是权威机构 CA 给各家服务器发的“良民证”) 冒充风险冒充风险其实有着两重含义:① 服务器被冒充; ② 客户端被冒充。下面让老夫来唠一唠: 服务器为什么不会被冒充?那就要请出上文提到的数字证书。服务器在表明自己身份的时候是需要出示自己的“良民证”的,而伪装者是无法拥有与之匹配的“良民证”的。 客户端为什么不会被冒充?客户端在得到服务器给出的公钥之后,用这个公钥加密自己生成的一个随机码,最后发送出去。此时,这个随机码就是:天不知,地不知,客户端知,服务器知。有了这个随机码,客户端与服务器就如同确认过眼神,双方都认定对方是对的人。第三者根本没有假扮成原配插足的可能性。 SSL/TLS 协议的握手过程SSL/TLS 协议的握手过程简单地讲就是客户端与服务器确认眼神的过程。“握手阶段”涉及四次通信,我们一个个来看。需要注意的是,”握手阶段”的所有通信都是明文的。 客户端发出请求我是谁,我具备什么样的能力。 支持的协议版本,比如TLS 1.0版; 一个客户端生成的随机数,稍后用于生成”对话密钥”; 支持的加密方法,比如RSA公钥加密; 支持的压缩方法。 服务器回应确认双方的密码本,并出示自己的“良民证”。 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信; 一个服务器生成的随机数,稍后用于生成”对话密钥”; 确认使用的加密方法,比如RSA公钥加密; 服务器证书。 客户端回应用对方给的密码本加密“本人是谁”的信息。 一个随机数。该随机数用服务器公钥加密,防止被窃听; 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送; 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。 服务器的最后回应根据前三次会话产生的三个密语,生成一个“会话密钥”,也就是对的眼神。 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送; 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。 更多的事会话密码与公钥加密有什么关系?首先了解一点,公钥加密是计算量很大的加密方式,而且数据量越大,计算量越大。如果对整个信息都进行公钥加密的话,会消耗双方大量的性能。所以实际上的做法是:消息通过对称加密来加密,而对称加密的解密密钥是通过非对称加密来加密。如此这般,方能兼顾安全与性能。]]></content>
<categories>
<category>计算机</category>
<category>互联网协议</category>
</categories>
<tags>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[http协议缓存]]></title>
<url>%2F2018%2F04%2F09%2Fcomputer-http%E5%8D%8F%E8%AE%AE%E7%BC%93%E5%AD%98%2F</url>
<content type="text"><![CDATA[在用户体验变得越来越重要的今天,充分利用缓存无疑是提升用户体验的一个重点… 先上两张图浏览器第一次请求: 浏览器再次请求: 强缓存缓存可以简单的划分成两种类型:强缓存(200 from cache)与协商缓存(304)。强缓存在将检测到缓存还未失效时,不发出 http 请求,而是直接得到 200 状态码。 ExpiresExpires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。 Cache-controlCache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。tip: 很多时候我们会使用”Cache-Control: true”与”Max-age: xx秒”来设置有效时间段。 协商缓存协商缓存(304)时,浏览器会向服务端发起http请求,然后服务端告诉浏览器文件未改变,让浏览器使用本地缓存对于协商缓存,使用Ctrl + F5强制刷新可以使得缓存无效。但是对于强缓存,在未过期时,必须更新资源路径才能发起新的请求(更改了路径相当于是另一个资源了,这也是前端工程化中常用到的技巧)。 Last-Modified / If-Modified-SincLast-Modified/If-Modified-Since要配合Cache-Control使用。Last-Modified 是服务器给出的资源最后修改的时间点,浏览器再次请求这个资源时会通过 If-Modified-Since 携带这个时间点,然会服务器会判断这个文件是否被修改过而给出相应的 200 或 304 状态码。 Etag / If-None-MatchEtag/If-None-Match也要配合Cache-Control使用。 Etag 是服务器生成的资源唯一标识(Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的)。浏览器再次请求这个资源时会通过 If-None-Match 携带着这标识,余者与 Last-Modified 流程相似。 用户行为与缓存 用户操作 Expires / Cache-Control Last-Modified / Etag 地址栏回车 有效 有效 页面链接跳转 有效 有效 新开窗口 有效 有效 前进后退 有效 有效 F5刷新 无效(max-age=0) 有效 Ctrl+F5刷新 无效(CC=no-catche) 无效(请求头丢弃该选项)]]></content>
<categories>
<category>计算机</category>
<category>互联网协议</category>
</categories>
<tags>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[http协议入门]]></title>
<url>%2F2018%2F04%2F04%2Fcomputer-http%E5%8D%8F%E8%AE%AE%E5%85%A5%E9%97%A8%2F</url>
<content type="text"><![CDATA[http协议是浏览器与服务器通信的协议,也是前端工程师最熟悉的一个应用层协议… URLHTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息。 http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name一个完整的URL包含:协议名、域名(或主机名)、端口、虚拟目录、文件名、参数、锚。 tip: URI强调怎样来标识一个资源、URL强调怎样来获取这个资源。 Request一个完整的request包含:请求行、请求头部、空行、请求体。 请求行GET /562f25980001b1b106000338.jpg HTTP/1.1请求行回答了三个问题:我要通过什么样的方式?去到什么地方?我是谁? 请求头部(用来说明服务器要使用的附加信息)常用请求头部: Accept: 接收类型,表示浏览器支持的MIME类型 Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收 Content-Type:客户端发送出去实体内容的类型 Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache If-Modified-Since:对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中 Expires:缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间(过期时间) Max-age:代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中(有效时间段) If-None-Match:对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中 Cookie: 有cookie并且同域访问时会自动带上 Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive Host:请求的服务器URL Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私 Referer:该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段) User-Agent:用户客户端的一些必要信息,如UA头部等 tip: 组合拳: If-Modified-Since与服务器给的Last-Modified If-None-Match与服务器给的ETag(Last-Modified是服务器给的一个时间戳,而ETag相当于是给了一个唯一ID) Expires与Max-age前者指的是服务器给出的过期时间点,后者指定的是在某个时间段内缓存有效 Expires与Max-age是强缓存,而If-Modified-Since与If-None-Match则属于弱缓存。 空行即使没有请求主体,也必须有空行。 请求体请求体并不是一个请求报文所必需的,它是用来承载数据的。 ResponseResponse也由四个部分组成,分别是:状态行、消息报头、空行和响应体。 响应行响应行告诉我们的是发生了什么?由HTTP协议版本号, 状态码, 消息短语 三部分组成。最常用的状态吗: 200——表明该请求被成功地完成,所请求的资源发送回客户端 304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存 400——客户端请求有错(譬如可以是安全模块拦截) 401——请求未经授权 403——禁止访问(譬如可以是未登录时禁止) 404——资源未找到 500——服务器内部错误 503——服务不可用 相应头常用的响应头: Access-Control-Allow-Headers: 服务器端允许的请求Headers Access-Control-Allow-Methods: 服务器端允许的请求方法 Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*) Content-Type:服务端返回的实体内容的类型 Date:数据从服务器发送的时间 Cache-Control:告诉浏览器或其他客户,什么环境可以安全的缓存文档 Last-Modified:请求资源的最后修改时间 Expires:应该在什么时候认为文档已经过期,从而不再缓存它 Max-age:客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效 ETag:请求变量的实体标签的当前值 Set-Cookie:设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端 Keep-Alive:如果客户端有keep-alive,服务端也会有响应(如timeout=38) Server:服务器的一些相关信息 空行消息报头后面的空行是必须的。 响应体服务器返回给客户端的文本信息。 HTTP请求方法GET: 请求指定的页面信息,并返回实体主体;HEAD: 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头;POST: 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改;PUT: 从客户端向服务器传送的数据取代指定的文档的内容;DELETE: 请求服务器删除指定的页面;CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器;OPTIONS: 允许客户端查看服务器的性能;TRACE: 回显服务器收到的请求,主要用于测试或诊断。 HTTP 请求/响应的步骤 客户端连接到Web服务器一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。 发送HTTP请求通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。 服务器接受请求并返回HTTP响应Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。 释放连接TCP连接若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;]]></content>
<categories>
<category>计算机</category>
<category>互联网协议</category>
</categories>
<tags>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[TCP协议入门]]></title>
<url>%2F2018%2F04%2F04%2Fcomputer-TCP%E5%8D%8F%E8%AE%AE%E5%85%A5%E9%97%A8%2F</url>
<content type="text"><![CDATA[TCP 是以太网协议和 IP 协议的上层协议,也是应用层协议的下层协议… 以太网协议解决的是相同子网络内的计算机与计算机连接,IP协议解决的是不同子网络之间的连接,而TCP协议解决的则是应用程序与应用程序之间的连接。 TCP数据包的编号(SEQ)一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。 发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。 TCP数据包的组装收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。 对于应用程序来说,不用关心数据通信的细节。除非线路异常,收到的总是完整的数据。应用程序需要的数据放在 TCP 数据包里面,有自己的格式(比如 HTTP 协议)。 操作系统不会去处理 TCP 数据包里面的数据。一旦组装好 TCP 数据包,就把它们转交给应用程序。TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。 总结:操作系统组装数据包、应用程序得到TCP的数据包体(如http请求) TCP协议的三次握手 由图可知,TCP协议的连接需要三次握手。那么接下来解释两个问题:① 三次握手具体在干什么?② 直觉上来讲两次握手即可,为什么需要三次握手? 拿我们平时与人打招呼为例(问题一):A: 吃了么?B: 吃了,你上洗手间了么?C: 上了。 上面是一个成功的TCP协议的内容,但也会存在异常情况,如:A: 吃了么?B: 上过洗手间了。或者:A: 吃了么?B: 吃了,你上洗手间了么?C: 我也吃了。 tip: 吃饭与上洗手间其实就是双方发送给对方的一个随机码,用来判断对方是不是答非所问。 拿古人写书信来说(问题二):A: 写信问 “吃了么?”(由于交通不便,这封信过了两年才送到B的手里。而在此期间,A早已忘了这茬儿)B: 回信说 “吃了,你上洗手间了么?”A: 这B有病啊,写的什么破信。从此与有病的B断了来往 总结: 如果只有两次通信的话,小B就只能陷入到无果的等待中。 四次挥手(客户端与服务端都可以主动发起关闭请求)客户端:我要跟你分手,你的东西我都给你了(潜台词是“我的东西你还没有给我”)服务端:我知道你想跟我分手,你还有一些东西在我这里,我正在打包,你注意听快递电话服务器:包裹我已寄出,从此你我一别两宽,各生欢喜客户端:hehe…]]></content>
<categories>
<category>计算机</category>
<category>互联网协议</category>
</categories>
<tags>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[互联网协议入门(二)]]></title>
<url>%2F2018%2F04%2F02%2Fcomputer-%E4%BA%92%E8%81%94%E7%BD%91%E5%8D%8F%E8%AE%AE%E5%85%A5%E9%97%A8(2)%2F</url>
<content type="text"><![CDATA[前篇提到互联网协议是怎样从硬件往上一层一层包装数据,这篇主要讲讲从用户角度看互联网协议充当的角色… 前文从宏观上讲了互联网协议的分层,以及每一层的角色。但遗留了一个小问题。下面我们来讲讲这个问题,以及从用户的角度如何理解互联网协议。 遗留问题当发送者与接收者不在同一个子网络时,如何得到接收者的mac地址呢?上图中,1号电脑要向4号电脑发送一个数据包。它先判断4号电脑是否在同一个子网络,结果发现不是(后文介绍判断方法),于是就把这个数据包发到网关A。网关A通过路由协议,发现4号电脑位于子网络B,又把数据包发给网关B,网关B再转发到4号电脑。 上网必备的四个参数这四个参数就相当于互联网世界的身份证。有了这个身份证,我们才能给别人发消息,也才能让别人找到我。这个身份证包含的信息有:DNS的IP地址、网关的IP地址、本机的IP地址、子网的掩码(可以理解为省市区)。 如果我们给每一台计算机配置上述四个参数(即静态IP上网)会存在两个问题:① 这般配置对于普通用户来说太过专业,让人望而生畏;② 这几个参数一旦被一个人占用,那么其他人便不能使用。 动态IP地址基于静态IP的缺陷,大多数人使用的还是动态IP地址的方式上网。而动态地给计算机分配IP地址,需要用到DHCP协议(一种应用层协议,建立在UDP协议之上)。 这个协议规定,每一个子网络中,有一台计算机负责管理本网络的所有IP地址,它叫做”DHCP服务器”。新的计算机加入网络,必须向”DHCP服务器”发送一个”DHCP请求”数据包,申请IP地址和相关的网络参数。 从互联网协议的角度来看我们访问网页的过程 根据网址查询IP地址:已知DNS服务器为8.8.8.8,于是我们向这个地址发送一个DNS数据包(53端口)。然后,DNS服务器做出响应,告诉我们Google的IP地址是172.194.72.105。于是,我们知道了对方的IP地址。 判断本机与对方的IP地址是否在同一个子网络:根据子网掩码(子网掩码在何时拿到的?)分别与本机IP、对方IP做ADN运算,最终判断本机与对方的IP地址是否在同一个子网络 将http数据包的全部内容嵌入到TCP协议的数据包中(TCP协议的数据包实际上含有多个标头与该http请求的全部内容) TCP协议:TCP数据包需要设置端口,接收方(Google)的HTTP端口默认是80,发送方(本机)的端口是一个随机生成的1024-65535之间的整数,假定为51775。 IP协议然后,TCP数据包再嵌入IP数据包。IP数据包需要设置双方的IP地址,这是已知的,发送方是192.168.1.100(本机),接收方是172.194.72.105(Google)。 以太网协议最后,IP数据包嵌入以太网数据包。以太网数据包需要设置双方的MAC地址,发送方为本机的网卡MAC地址,接收方为网关192.168.1.1的MAC地址(通过ARP协议得到)。 服务器响应经过多个网关的转发,Google的服务器172.194.72.105,收到了这四个以太网数据包。根据IP标头的序号,Google将多个包拼起来,取出完整的TCP数据包,然后读出里面的”HTTP请求”,接着做出”HTTP响应”,再用TCP协议发回来。本机收到HTTP响应以后,就可以将网页显示出来,完成一次网络通信。 参考文章:互联网协议入门(二)]]></content>
<categories>
<category>计算机</category>
<category>互联网协议</category>
</categories>
<tags>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[互联网协议入门(一)]]></title>
<url>%2F2018%2F04%2F02%2Fcomputer-%E4%BA%92%E8%81%94%E7%BD%91%E5%8D%8F%E8%AE%AE%E5%85%A5%E9%97%A8(1)%2F</url>
<content type="text"><![CDATA[互联网协议解决的问题是:两台计算机是如何进行通讯的… 概述互联网协议解决的问题是:两台计算机是如何进行通讯的。互联网的核心是一系列协议。它们对电脑如何连接和组网,做出了详尽的规定。理解了这些协议,就理解了互联网的原理。 互联网的分层互联网可以分为好几层。越往下,越是接近硬件;越往上,越贴近用户。为方便理解,这里将互联网分成5层:每一层都是为了完成特定的功能。而完成特定的功能,需要大家共同遵守一些普世的规则。这些规则就是互联网协议。 实体层首先我们需要通过光缆、电缆、双绞线、无线电波等方式将单个电脑接入到整个互联网中。实体层就是把电脑连接起来的物理手段。它主要规定了网络的一些电气特性,作用是负责传送0和1的电信号。 链接层实体层传送的只是一些0和1的电信号,而单纯的0和1是没有意义的,必须规定相应的解读方式:多少个电信号算一组?每个信号位有何意义? 以太网协议以太网规定:一组电信号为一帧,每一帧都包含一个标头(head)与数据实体(data)。标头包含一些说明数据,如发送者、接受者、数据类型等等。tips: 一帧可容纳的数据有限。所以数据量过大时,需要分成多个帧来传输。 mac地址前面提到以太网的标头中包含发送者与接受者信息。那发送者与接受着到底是用什么来表示呢?答案就是mac地址(全世界独一无二的网卡地址) 广播定义地址只是第一步,发送者会将要发送的信息广播出去。接受者会解析以太网标头中的接受者的mac地址,并将之与自身网卡的mac地址进行比较来判断自己是不是要接收这条信息。tip: 以太网的广播方式只能广播给同一个子网络中的计算机。这里留下一个疑问,发送者如何知道对方的mac地址呢? 网络层前面提到,广播的方式只能广播给同一子网络中的计算机。而完整的互联网是无数的子网络所组成的一个巨大的网格。所以在真实的互联网的世界中,我们应当是先确定目标计算机所在的子网络,然后通过以太网协议标头中的mac地址信息来匹配子网络中具体的某一台目标计算机。那么问题来了:如何确定目标计算机所在的子网络呢?网络层便是用来确定目标计算机在哪个子网络的协议。它引进了一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做”网络地址”,简称”网址”。 IP协议规定网络地址的协议,叫做IP协议。它所定义的地址,就被称为IP地址。目前,广泛采用的是IP协议第四版,简称IPv4。这个版本规定,网络地址由32个二进制位组成。习惯上,我们将网址分成4段。所以网址的范围为0.0.0.0至255.255.255.255。 如何根据IP地址判断两台计算机是否在同一子网络中呢?我们并不知道一个IP地址的前面多少位是它的网络部分(网络部分相同即同一子网络),所以单纯通过IP地址是无法判断两个计算机是否在同一子网络的。 前面讲到单纯通过IP地址是无法判断两个计算机是否在同一子网络中的。此时需要配合另外一个网络参数子网掩码。总的来说,子网掩码就是告诉了我们哪几位是网络部分,哪几位是主机部分。 所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.254.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。 将IP地址与子网掩码进行AND运算,就能最终知道目标计算机在哪个子网络。 总结一下,IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。 IP数据包根据IP协议发送的数据,就叫做IP数据包。不难想象,其中标头部分必定包括IP地址信息。“标头”部分主要包括版本、长度、IP地址等信息,”数据”部分则是IP数据包的具体内容。它放进以太网数据包后,以太网数据包就变成了下面这样(以太网数据包整体作为IP数据包的数据部分存在)。 ARP协议要精准找到发送目标,我们需要两个信息:IP地址与mac地址。IP地址是已知的,mac地址却不得而知。所以我们需要一种机制,能够由IP地址得到mac地址。以下我们分成两种情况:① 发送者与接收者不在同一子网络(那么事实上没有办法得到对方的MAC地址,只能把数据包传送到两个子网络连接处的”网关”(gateway),让网关去处理);② 发送者与接收者在同一子网络,此时可以通过ARP协议来获取对方的mac地址(具体如何实现,暂且不表) 传输层有了MAC地址和IP地址,我们已经可以在互联网上任意两台主机上建立通信。但是每个主机都会有多个程序运行,如何建立两台主机中程序到程序的通信呢?“传输层”的功能,就是建立”端口到端口”的通信。相比之下,”网络层”的功能是建立”主机到主机”的通信。只要确定主机和端口,我们就能实现程序之间的交流。因此,Unix系统就把主机+端口,叫做”套接字”(socket)。有了它,就可以进行网络应用程序开发了。 UDP协议现在,我们必须在数据包中加入端口信息,这就需要新的协议。最简单的实现叫做UDP协议,它的格式几乎就是在数据前面,加上端口号。 TCP协议UDP协议的优点是比较简单,容易实现,但是缺点是可靠性较差,一旦数据包发出,无法知道对方是否收到。 为了解决这个问题,提高网络可靠性,TCP协议就诞生了。这个协议非常复杂,但可以近似认为,它就是有确认机制的UDP协议,每发出一个数据包都要求确认。如果有一个数据包遗失,就收不到确认,发出方就知道有必要重发这个数据包了。 因此,TCP协议能够确保数据不会遗失。它的缺点是过程复杂、实现困难、消耗较多的资源。 应用层应用程序收到”传输层”的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。(如http协议)“应用层”的作用,就是规定应用程序的数据格式。这是最高的一层,直接面对用户。它的数据就放在TCP数据包的”数据”部分。因此,现在的以太网的数据包就变成下面这样。 参考文章:互联网协议入门(一)]]></content>
<categories>
<category>计算机</category>
<category>互联网协议</category>
</categories>
<tags>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浏览器中的线程与进程]]></title>
<url>%2F2018%2F03%2F24%2Fothers-%E4%BB%8E%E8%BF%9B%E7%A8%8B%E5%88%B0%E7%BA%BF%E7%A8%8B%2F</url>
<content type="text"><![CDATA[我们常常说JS是单线程的,但是这个JS单线程在整个浏览器的协作体系中处于什么样的位置?从线程角度看待为何css前置,JS后置以及JS的异步通知机制是怎样的。或许你能从本文中窥到一丝端倪… 进程与线程的概念进程:进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位);线程:线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。 tips:①、我们常说的单线程和多线程都是指的在一个进程内;②、进程与进程之间保持相互独立,并行不悖. 浏览器的多进程浏览器包含哪些进程 Browser进程:浏览器的主进程(负责协调、主控),只有一个; 第三方插件进程:每种类型的插件对应一个进程; GPU进程:最多一个,用于3D绘制等; 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为页面渲染,脚本执行,事件处理等; tips:浏览器每打开一个网页就相当于新增一个进程。 浏览器内核的渲染进程(多线程)浏览器内核的常驻线程 GUI渲染线程 JS引擎线程 事件触发线程 定时触发器线程 异步http请求线程 tips:GUI线程与JS引擎线程是互斥的,这也是我们常常将css前置,js后置的原因。 浏览器的渲染流程浏览器器内核拿到内容后,渲染大概可以划分成以下几个步骤: 解析html建立dom树; 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树); 布局render树(Layout/reflow),负责各元素尺寸、位置的计算; 绘制render树(paint),绘制页面像素信息; 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。 默认复合图层与复合图层普通文档流就是一个默认复合图层。(定位虽然会脱离文档流,但依然是在默认的复合图层中)开启硬件加速的原理其实就是让浏览器再创建一个复合图层(不同的复合图层之间是相互独立地渲染,故而新建一个复合图层单独进行动画等处理就会提升性能)。 复合图层中的任何一点发生变化,GPU都会重新绘制整个复合图层。定位虽然能脱离普通文档流,但是他的变化还是会导致他所在的复合图层重新渲染。 硬件加速开启硬件加速的条件: 3D或透视变换 对元素的opcity作动画 视频、webGL、flash等 如果这个元素添加了硬件加速,并且index层级比较低,那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),会默认变为复合层渲染,如果处理不当会极大的影响性能 tips:①、动画时的复合图层是临时的,只在动画进行时创建;②、上面提到的第四点隐藏着一个巨坑,即如果一个元素启用了硬件加速,并且他的z-index值比较小,那么他的兄弟元素也会被放入硬件加速的复合层中。而硬件加速的复合层中的元素过多,会导致内存与GPU吃紧,页面也不会如预期那般加速。所以在使用硬件加速的时候应给该元素设置一个较大的z-index,明确告诉浏览器这一层是独立出去的。 JS事件环机制首先明确三个线程:JS引擎线程、事件触发线程、定时器触发线程。 主线程运行时会产生执行栈; 栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕); 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调; 如此循环。 一些细节:调用setTimeout后,是如何等待特定时间后才添加到事件队列中的?这个计时并不是由JS引擎来记录,而是定时器触发线程到点后将时间推入事件队列中。 setTimeout与setIntervalsetTimeout与setInterval实现定时功能是有差别的。setInterval是由定时器触发线程精准定时推送到任务队列中,如果执行栈运行时间过长,就会导致任务积压(小于间隔时间连续执行)。setTimeout虽然会有超时存在,但好在不会对性能造成太大的压力。 macrotask和microtask在ES6的Promise与nodejs的process.nextTick出现之前,是没有微任务队列一说的。下面先来唠一唠macrotask。JS一开始就是在执行宏任务队列中的代码,遇到定时任务等就会将这些回调推入下一个宏任务队列。而两个宏任务队列之间会对页面进行重渲染。而微任务队列则会紧跟着当前宏任务队列后面执行(在页面重渲染之前,也是下一个宏任务队列执行之前)。 tips:不管是宏任务队列还是微任务队列,里面保存的都只是要处理的事件。真正执行这些任务的是执行栈从事件队列中获取一个回调放入执行栈中执行。]]></content>
<categories>
<category>其他</category>
<category>线程与进程</category>
</categories>
<tags>
<tag>周边</tag>
</tags>
</entry>
<entry>
<title><![CDATA[nginx静态伺服与反向代理]]></title>
<url>%2F2018%2F01%2F04%2Fserver-nginx%E9%9D%99%E6%80%81%E4%BC%BA%E6%9C%8D%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%2F</url>
<content type="text"><![CDATA[Nginx 是一个很强大的高性能Web和反向代理服务器… Nginx 是一个很强大的高性能Web和反向代理服务器。下面,我们将借助 nginx 实现前后端分离。 前期准备 下载 nginx 安装包(windows 下就是一个压缩包),解压到任意位置都行; 切换命令行至 nginx.exe 目录下,启动 nginx,start nginx; 浏览器打开 localhost,如果看到 nginx 的启动页,证明启动成功。 前后端分离问题当下的开发模式是后端提供数据接口,前端进行数据展示。前后端之间的桥梁就是ajax.前端开发通常是借助于一个 node 服务器,我们假设它的主机名加端口是:http://localhost:8080.后端接口通常是在 tomcat 服务器上,这里我们使用 koa + koa-router 创建后台接口,地址为:http://localhost:3001.此时,前端通过 ajax 调用后台接口时,由于浏览器的同源策略会请求失败。 解决思路使用 nginx 作为前后端中间的反向代理服务器。前端不是直接发送请求给后端,而是发给 nginx 服务器。nginx 服务器再去请求真正的接口地址(浏览器有跨域限制,服务器没有)。nginx 服务器获取到接口数据之后再将数据发送给前端(同时要设置add_header 'Access-Control-Allow-Origin' '*';)。而上述转发的过程,对于前后端而言是没有感知的,它们并不知道 nginx 这个中间人干了什么,这就是反向代理的好处。 相关代码实现前端前端借助 axios 这个库来发送 ajax 请求:123456789101112/* http://localhost:8080 */// nginxServer 是 nginx 服务器监听的端口号const nginxServer = 'http://localhost:8880';axios.get(`${nginxServer}/apis/hehe?name=tom&age=22`) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); 后端后端是有 koa + koa-router 提供接口:12345678910// 相关代码router.get('/apis/hehe', async(ctx, next) => { // 声明返回的数据类型为json ctx.set('Content-Type', 'application/json'); ctx.body = JSON.stringify({ code: 0, // 通过 ctx.query 拿到请求地址里的参数 msg: `欢迎:${ctx.query.name}` })}) nginx 配置我们约定好接口的命名都要以”/apis”开头,nginx 会拦截以”/apis”开头的请求,认为这样的请求就是在请求后台接口,并将其转发到后端服务器上。123456789101112131415161718192021222324server { listen 8880; # 默认是80端口,这里我改成了8880 server_name localhost; # 反向代理 location /apis { # 重写请求路径(端口号后面的,查询参数前面的部分) rewrite ^/(.*)$ /$1 break; # 请求真正的后台服务器 proxy_pass http://localhost:3001; # 添加跨域访问头(前端请求 nginx 服务器实际上也是跨域) add_header 'Access-Control-Allow-Origin' '*'; } # 代理到本地文件目录,这里我们可以指向前端打包后的 dist 目录 location ~ \.(html|js|css|png|gif)$ { root E:\workspace\wxApp\dist; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; }} nginx 更多能力开启 gzip 压缩12345678910111213# 默认 gzip on; 是被注释掉的gzip on; # 超过5k才进行压缩 gzip_min_length 5k; gzip_buffers 4 16k; #gzip_http_version 1.0; # 压缩等级,等级越高,耗时越长,要权衡 gzip_comp_level 2; # 要压缩文件的MIME类型 gzip_types text/plain application/x-javascript application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_vary off; # IE6以下就不用压缩了 gzip_disable "MSIE [1-6]\.";]]></content>
<categories>
<category>后端</category>
<category>nginx</category>
</categories>
<tags>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[koa中的mvc分层]]></title>
<url>%2F2018%2F01%2F04%2Fnode-koa%E5%88%86%E5%B1%82%2F</url>
<content type="text"><![CDATA[在koa中实现mvc分层… app.js(koa入口)koa入口文件主要做三件事:创建服务、装载middleware、将路由控制权交给router.js模块。123456789101112const Koa = require('koa');const router = require('./router/router.js');const middleware = require('./middleware/index');const app = new Koa();middleware(app); // 给koa实例增加某些超能力router(app); // 路由处理app.listen(3000, () => { console.log('server is running at http://localhost:3000');}) route(路由)router模块相当于现实生活中的交警,指挥不同的请求流入不同的通道(即控制器)。123456789101112131415const router = require('koa-router')();const HomeController = require('../controller/home');module.exports = app => { router.get('/', HomeController.index) router.get('/home', HomeController.home) router.get('/user', HomeController.user) router.get('/user/:id', HomeController.homeParams) router.post('/user/register', HomeController.register) router.all('*', async(ctx, next) => { ctx.body = `<h1>404 NOT FOUND<h1>` }) app.use(router.routes());} controller(控制器)控制器是路由下面的一员大将,他能够掌控一些大方向。但是涉及到一些琐碎的(如业务逻辑)事情,他会将这些事情委派给他的手下——service.1234567891011121314151617181920212223242526const HomeService = require('../service/home');// 简单来看,他仅仅是暴露了一堆函数module.exports = { index: async(ctx, next) => { ctx.body = `<h1>index page</h1>` }, home: async(ctx, next) => { ctx.body = `<h1>HOME page</h1>` }, user: async(ctx, next) => { ctx.body = ` <h1> <form action="/user/register" method="post"> <input name="name" type="text" placeholder="用户名"/><br /> <input name="password" type="password" placeholder="密码"/><br /> <button type="submit">gogo</button> </form> </h1> ` }, homeParams: async(ctx, next) => { ctx.body = `<h1>您的id是:${ctx.params.id}</h1>` }, register: HomeService.register, // 注册任务,委派给service} service(服务)service用来处理一些琐碎的的活儿(涉及到具体业务的)。123456789101112/* 他也只是暴露了一堆的处理函数 */module.exports = { register: async(ctx, next) => { console.log(ctx.request.body); let { name, password } = ctx.request.body; if(name === 'hehe' && password === '123') { ctx.body = `<h1>欢迎${name},登录成功!</h1>` }else { ctx.body = `<h1>帐号信息有误!</h1>` } },} middleware(中间件)中间件的出现是为了给koa实例增加一些能力,比如:静态文件呈现、解析post数据、直接给前端发送json对象…123456789101112/* middleware/index.js */const path = require('path');const staticFiles = require('koa-static');const bodyParser = require('koa-bodyparser');const miSend = require('./mi-send/index'); // 自定义中间件module.exports = app => { app.use(staticFiles(path.resolve(__dirname, '../public'))); app.use(bodyParser()); app.use(miSend());} 1234567891011/* middleware/mi-send/index.js */module.exports = () => { function render(json) { this.set('Content-Type', 'application/json'); this.body = JSON.stringify(json); } return async(ctx, next) => { ctx.send = render.bind(ctx); await next(); }}]]></content>
<categories>
<category>后端</category>
<category>node</category>
<category>koa</category>
</categories>
<tags>
<tag>koa</tag>
</tags>
</entry>
<entry>
<title><![CDATA[再谈vuex之模块化]]></title>
<url>%2F2017%2F10%2F18%2FJS-vue-vuex%20module%2F</url>
<content type="text"><![CDATA[很多人都知道,使用redux时的模块化是很方便的。利用不同的reducer函数管理不同模块中的状态。vuex也是支持模块化的,今天来侃侃… 前导 模块包含什么 模块如何组织 模块如何访问外部信息 组件中如何操作模块 模块是什么不同于redux中的模块,vuex中的模块相当于是一个小的store,包含state、mutations、actions、getters。 12345678910111213141516171819202122const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... }}const moduleB = { state: { ... }, mutations: { ... }, actions: { ... }}const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB }})store.state.a // -> moduleA 的状态store.state.b // -> moduleB 的状态 模块的局部状态模块内的mutations12345678const moduleA = { mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }} 模块内的gettersgetters: { // 接收到的第一个参数是模块局部的state doubleCount (state) { return state.count * 2 } } /* 如果getters需要用到模块外部的state数据,就要接受后面的参数 */ getters: { // 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来 sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } 模块内的actions对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState. actions: { // 将state, commit, rootState解构出来 incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } 命名空间默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。但我们通常是希望每个模块拥有各自的命名空间,以免“误伤”。 给予模块各自不同的命名空间你可以通过添加 namespaced: true 的方式使其成为命名空间模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。 const store = new Vuex.Store({ modules: { account: { /* 开启该模块自己的命名空间 */ namespaced: true, // ... } } }) 在命名空间模块内访问全局内容如果你希望使用全局 state 和 getter,rootState 和 rootGetter 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。 getters getters: { // 在这个模块的 getter 中,`getters` 被局部化了 // 你可以使用 getter 的第四个参数来调用 `rootGetters` someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' }, someOtherGetter: state => { ... } }, actions actions: { // 在这个模块中, dispatch 和 commit 也被局部化了 // 他们可以接受 `root` 属性以访问根 dispatch 或 commit someAction ({ dispatch, commit, getters, rootGetters }) { getters.someGetter // -> 'foo/someGetter' rootGetters.someGetter // -> 'someGetter' dispatch('someOtherAction') // -> 'foo/someOtherAction' dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' commit('someMutation') // -> 'foo/someMutation' commit('someMutation', null, { root: true }) // -> 'someMutation' }, someOtherAction (ctx, payload) { ... } } 组件内使用绑定函数当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定命名空间模块时,写起来可能比较繁琐: /* 原始写法 */ computed: { ...mapState({ a: state => state.some.nested.module.a, b: state => state.some.nested.module.b }) }, methods: { ...mapActions([ 'some/nested/module/foo', 'some/nested/module/bar' ]) } 对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为: /* 推荐写法 */ computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b }) }, methods: { ...mapActions('some/nested/module', [ 'foo', 'bar' ]) } 还可以更精简一点: /* 鼎力推荐 */ import { createNamespacedHelpers } from 'vuex' const { mapState, mapActions } = createNamespacedHelpers('some/nested/module') // 使用 createNamespacedHelpers 对组件的侵入最小 export default { computed: { // 在 `some/nested/module` 中查找 ...mapState({ a: state => state.a, b: state => state.b }) }, methods: { // 在 `some/nested/module` 中查找 ...mapActions([ 'foo', 'bar' ]) } } 写在最后虽然vuex赋予我们在模块内部访问全局的信息,但是不推荐这般使用。这样会使得模块与模块之间相互耦合,不利于复用。如果你发现自己不得不从模块内部访问全局的信息,那你就该思考现在的模块划分是不是有问题,有没有更好的模块划分方式。]]></content>
<categories>
<category>前端</category>
<category>vue</category>
</categories>
<tags>
<tag>vue</tag>
<tag>vuex</tag>
<tag>状态管理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[react ssr入门]]></title>
<url>%2F2017%2F10%2F17%2FJS-react-next%2F</url>
<content type="text"><![CDATA[react搭配nextjs走马观花… 因果angular、react、vue等现代前端框架带领前端进入spa(单页面应用)时代。从此前端可以自己操控路由,页面与页面之间不再是一个个孤立的网页,而是一个完整的应用。但是但页面应用的盛行也带来了一些问题:①、不利于seo,所有内容都是通过后台异步获取,搜索引擎抓取内容的时候是抓不到有意义的内容的。(据说谷歌等搜索引擎已经能够通过ajax抓取有意义的内容了,但目前的百度无疑是不行的);②、首屏加载时间过长。主要原因是我们打包出来的bundle文件过大,即使经过路由组件拆分,依旧不能达到用户的预期。 如何解决spa带来的副作用,针对react的ssr框架,我们请出今天的主角nextjs. 入门安装依赖12# c创建项目目录并安装以下依赖npm install --save next react react-dom 定义命令运行npm run dev将会在本机的3000端口呈现nextjs渲染出来的内容 12345678// 在package.json中定义一下命令{ "scripts": { "dev": "next", "build": "next build", "start": "next start" }} 在项目中增加两个文件夹:pages、static。pages文件夹中的组件路径映射到URL对应目录上,static文件夹存放静态资源。 页面之间的导航Link组件我们可以通过nextjs提供的Link组件进行页面之间的跳转。 123456789101112// pages/index.jsimport Link from 'next/link'const Index = () = ( <div> <Link href="/about"> <a>About Page</a> </Link> </div>)export default Index 给链接添加样式Link组件是一个抽象的高阶组件,所以给它加样式是不起作用的。 123<Link href="/about"> <a style={{ fontSize: 20 }}>About Page</a></Link> 共享组件共享组件存放的路径是可以自定义的,事实上,除了pages以及static文件夹,其他文件夹名称都可以自定义。 12345678910111213141516171819/* components/MyLayout.js */import Header from './Header'// 页面布局的样式定义在Layout组件中const layoutStyle = { margin: 20, padding: 20, border: '1px solid #DDD'}const Layout = (props) => ( <div style={layoutStyle}> <Header /> {/* 相当于一个插槽 */} {props.children} </div>)export default Layout 传递消息,创建动态页面很多时候,我们是根据不同的信息动态地渲染不同内容的页面,如给页面传入不同的ID从而渲染不同的详情内容。 传递参数1234567891011121314151617181920212223/* pages/index.js */import Layout from '../components/MyLayout.js'import Link from 'next/link'const PostLink = (props) => ( <li> {/* 将title属性值拼接到要跳转的URL地址后面,实际上就是通过URL传参 */} <Link href={`/post?title=${props.title}`}> <a>{props.title}</a> </Link> </li>)export default () => ( <Layout> <h1>My Blog</h1> <ul> <PostLink title="Hello Next.js"/> <PostLink title="Learn Next.js is awesome"/> <PostLink title="Deploy apps with Zeit"/> </ul> </Layout>) 接受参数123456789101112131415161718/* pages/post.js */import Layout from '../components/MyLayout.js'const Content = (props) => ( <div> {/* 通过props下的url对象拿到URL上的查询字符串 */} <h1>{props.url.query.title}</h1> <p>This is the blog post content.</p> </div>)// props中的url对象默认只暴露给了根组件export default (props) => ( <Layout> {/* 将url传给子组件 */} <Content url={props.url} /> </Layout>) 此时,在浏览器地址栏中输入: “http://localhost:3000/post?title=Hello%20Next.js" 就可以看到效果了。 路由掩码路由掩码的作用实际上就是将真实丑陋的URL地址掩盖起来,呈现给用户一个清爽的URL。就像上节的栗子:我们的真实URL地址是 “http://localhost:3000/post?title=Hello%20Next.js" ,但是我们希望呈递给用户的URL是 “http://localhost:3000/p/hello-nextjs" 。 Link的as属性12345678const PostLink = (props) => ( <li> {/* href代表的是真实的URL地址,as代表的是我们呈递给用户看的假URL */} <Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}> <a>{props.title}</a> </Link> </li>) 整完之后,浏览器中的前进后退都是可以实现的,但是如果在 “http://localhost:3000/p/hello-nextjs" 时,直接刷新页面就会报页面404。接下来我们来看看如何通过nodejs修复这个bug。 服务器端配合安装express 12# 这里我们使用express来处理npm install --save express 创建服务 1234567891011121314151617181920212223242526272829303132/* server.js */const express = require('express')const next = require('next')const dev = process.env.NODE_ENV !== 'production'const app = next({ dev })const handle = app.getRequestHandler()app.prepare().then(() => { const server = express() // 关键在此处 server.get('/p/:id', (req, res) => { const actualPage = '/post' const queryParams = { title: req.params.id } app.render(req, res, actualPage, queryParams) }) server.get('*', (req, res) => { return handle(req, res) }) server.listen(3000, (err) => { if (err) throw err console.log('> Ready on http://localhost:3000') })}).catch((ex) => { console.error(ex.stack) process.exit(1)}) 更新启动命令123456/* package.js */{ "scripts": { "dev": "node server.js" }} 页面加载前获取初始数据直接刷新页面获取值有时候我们在页面初始化时需要先从后台服务器中获取数据。此时我们就要祭出getInitialProps与async了。 1234567891011121314151617181920212223242526272829303132import Layout from '../components/MyLayout.js'import Link from 'next/link'import fetch from 'isomorphic-unfetch'const Index = (props) => ( <Layout> <h1>Batman TV Shows</h1> <ul> {props.shows.map(({show}) => ( <li key={show.id}> <Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}> <a>{show.name}</a> </Link> </li> ))} </ul> </Layout>)/* 关键在此处 */Index.getInitialProps = async function() { const res = await fetch('http://api.tvmaze.com/search/shows?q=batman') const data = await res.json() console.log(`Show data fetched. Count: ${data.length}`) return { shows: data }}export default Index 这段打印的信息只会在服务器端打印出来,浏览器控制来不会有这段信息。理当如此! 从其他页面跳转后获取初始数据123456789101112131415161718192021222324/* pages/post.js */import Layout from '../components/MyLayout.js'import fetch from 'isomorphic-unfetch'const Post = (props) => ( <Layout> <h1>{props.show.name}</h1> <p>{props.show.summary.replace(/<[/]?p>/g, '')}</p> <img src={props.show.image.medium}/> </Layout>)// 通过context拿到ID值,根据ID异步获取数据,这个过程发生在浏览器中Post.getInitialProps = async function (context) { const { id } = context.query const res = await fetch(`http://api.tvmaze.com/shows/${id}`) const show = await res.json() console.log(`Fetched show: ${show.name}`) return { show }}export default Post 通过其他页面跳转过来的是通过浏览器异步地获取数据,就像从前一样。 部署将nextjs项目部署到服务器上需要解决两个问题:①、项目的启动要随之操作系统的启动而启动;②、将80端口代理到nextjs开启的端口上。 使用PM2来管理我们的Next.js进程 启动next.js进程 12345# 自定义Express服务器# https://github.com/zeit/next.js/tree/master/examples/custom-server-expressNODE_ENV=production pm2 start ./server.js --interpreter ./node_modules/.bin/babel-node --watch src --name next-blog# 默认Next.js内置的方式NODE_ENV=production pm2 start npm --name "next-blog" -- start 保存启动信息 1pm2 save 创建系统启动服务 12# 以Ubuntu 16.04为例, 它会创建一个名为pm2-www.service的SYSTEMD服务.pm2 startup 查看PM2管理的Next.js应用程序状态 1systemctl status pm2-www.service 代理端口 指定Next.js应用运行的端口 1234/* 首先配置 package.json */"scripts": { "start": "next start -p $PORT"} 1PORT=8000 yarn start 使用Ngninx反向代理 123456789101112131415# 也可以不直接指定端口, 让Next.js 应用程序在Nginx反向代理后面跑.location / { # default port, could be changed if you use next with custom server proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; # if you have try_files like this, remove it from our block # otherwise next app will not work properly # try_files $uri $uri/ =404;}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>js</tag>
<tag>react</tag>
<tag>ssr</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Rxjs操作符]]></title>
<url>%2F2017%2F10%2F07%2FJS-Rxjs%E6%93%8D%E4%BD%9C%E7%AC%A6%2F</url>
<content type="text"><![CDATA[在响应式编程的世界里,我们需要做的最多的便是处理应用在不同的携带数据以及逻辑的时间流中的变化规则,而这恰恰是Rxjs操作符在很大程度是帮我们简化和梳理的… 常见创建类操作符创建类操作符可以说是连接传统数据与Rxjs的桥梁。其主要作用是将原始的数据类型转化为Observable类型。 from可以把数组、Promise、以及Iterable转化为Observable. 12/* 如果是由Array转变为Observable,Array中的数据将会在一瞬间发射完成,没有时间间隔 */Rx.Observable.from(xx: Array|Promise|Iterable) fromEvent将事件转化为Observable. 12/* 很多事件是一个无尽的事件流,所以永远不能激发completed状态 */Rx.Observable.fromEvent(btn, 'eventName') of接受一系列数据,并将它们发射出去。 bindCallback把回调API转化为返回Observable的函数。注意这里返回的还是一个函数,调用该函数才能得到Observable对象。 12345678910// 这是一个接受回调的APIfunction hehe(x, callback) { setTimeout(() => { callback(x) }, 1000)}// 注意函数的参数'haha'是如何传递的Rx.Observable.bindCallback(hehe)('haha') .subscribe(v => console.log(v))// 一秒后输出:haha bindNodeCallback就像是 bindCallback, 但是回调函数必须形如 callback(error, result)这样。 12Rx.Observable.bindNodeCallback(fs.readFile)('./roadNames.txt', 'utf8'); .subscribe(x => console.log(x), e => console.error(e)); 注意:如果回调函数能接受多个参数,则这些参数将会放到数组中被发射出去。 interval接受一个时间间隔,然后不停地从零开始累加发射数据。 12/* 拿到什么就发射什么,不管里边是对象还是函数(如果是函数的调用则返回函数调用后的结果) */Rx.Observable.of(2, 8, [5,54], fn()) 常见转换操作符map用法非常灵活,许多其他的转换操作符都可以使用map来实现。 12Rx.Observable.from([1,2,3,4,5]) .map(v => v * 10) mapTo有时候我们不关心Observable具体发射了什么数据,只需知道有一个数据被发射了就使用mapTo。它永远返回一个定值。 12Rx.Observable.from([1,2,3,4,5]) .mapTo(10) pluck我们经常进行这样的操作,从一个大对象中取出其中的某个特定的字段。此时用到pluck. 123Observable.pluck('data', 'id')// 等价于下面这种写法Observable.map(res => res.data.id) 常见累积操作符许多时候,我们在监听到当前值时,还需要知道Observable之前发射的值。此时就需要用到累积操作符了。 scanscan操作符的用法类似数组的reduce方法。 12345// scan不会改变流中数据的个数,只是让所有之前的数据参与当前的运算Rx.Observable.from([1,2,3,4,5]) .scan(((prev, curr) => prev + curr), 0) .subscribe(v => console.log(v))// 1, 3, 6, 10, 15 reduce12345// reduce操作符之后返回一个数据,并且这个数据只有在该数据流的状态变成completed时才会发射出Rx.Observable.from([1,2,3,4,5]) .reduce(((prev, curr) => prev + curr), 0) .subscribe(v => console.log(v))// 15 过滤类操作符过滤类操作符用来剔除我们不想关心的数据,例如输入框中为空以及长度小于2的值。 filter1234Rx.Observable.from([1,2,3,4,5]) .filter(v => v % 2) .subscribe(v => console.log(v))// 1, 3, 5 taketake表示只取流中的前几个数据。 1234Rx.Observable.interval(500) .take(3) .subscribe(v => console.log(v))// 0, 1, 2 first/last见名知意,无需多做解释。 123456789101112/* first */Rx.Observable.interval(500) .first() .subscribe(v => console.log(v))// 0/* last */Rx.Observable.interval(500) .take(3) .last() .subscribe(v => console.log(v))// 2 skip跳过前面的多少个值。 1234Rx.Observable.interval(500) .skip(3) .subscribe(v => console.log(v))// 3, 4, 5, ... skipWhile忽略源Observable开头的一系列值,直到有一项符合条件,才开始从源Observable的该值开始,开始发射值。 1234Rx.Observable.interval(100) .skipWhile(v => v < 3) .subscribe(v => console.log(v))// 3, 4, 5, ... debounce(对比sample)debounce接受一个函数作为参数,而且该函数返回一个Observable流。将第二个流的数据发射的间隔时间当作第一个流数据发射的等待时间,只有在这个时间间隔内没有新的值发射时,数据才会最终被发射。(本质上是一种延时动作) 1234/* 只有在按钮被点击时才从第一个流中取出一个最新值 */Rx.Observable.interval(100) .debounce(() => Rx.Observable.fromEvent(btn, 'click')) .subscribe(v => console.log(v)) debounceTimedebounceTime接受一个毫秒数作为参数,其作用是如果在一个值发射过后的指定时间间隔内没有新的值发射,该值才发射,否则舍弃掉之前的值。(本质上是一种延时动作)实际场景可以是,用户在输入搜索关键字时,只有停止输入了一个时间间隔,我们才拿到关键字匹配与之相关的内容。 1234567Rx.Observable.fromEvent(input, 'keyup') .debounceTime(1000) .subscribe(v => console.log(v))// 等同于Rx.Observable.fromEvent(input, 'keyup') .debounce(() => Rx.Observable.interval(1000)) .subscribe(v => console.log(v)) sample接受一个Observable作为它的定时发射时间(debounce是接受一个函数,该函数返回Observable)。本质是是一种定时动作。 1234/* 该示例如果改成debounce则永远也不会输出值 */Rx.Observable.interval(100) .sample(Rx.Observable.interval(1000)) .subscribe(v => console.log(v)) sampleTime接受一个毫秒数最为它的定时发射时间,本质是是一种定时动作。 123Rx.Observable.interval(100) .sample(1000) .subscribe(v => console.log(v)) 去重复值 distinct / distinctUntilChanged / distinctKeyUntilChangeddistinct:流中不存在重复值。distinctUntilChanged:相邻两个值不等。distinctKeyUntilChanged:去除连续项中,拥有相同给予key值的value的项。 123456789let items = [ { age: 4, name: 'Foo'}, { age: 7, name: 'Bar'}, { age: 5, name: 'Foo'}, { age: 6, name: 'Foo'}]Rx.Observable.of( ...items ) .distinctUntilKeyChanged('name') .subscribe( x => console.log( x )) 合并类操作符将多个流合并为一个流,可以理解为流的加法运算。(如果是流中包含流,需用到高阶操作符,可以理解为流的乘法运算) merge接受多个流作为参数,将多个流打平成一个流。 concat接受两个流作为参数,将两个流拼接起来(前面的流必须是有穷的流才有意义)。 startWith在流的前面增加一个值。使用场景:给流赋初始值。 combineLastest接受两个流作为参数。取出两个流中的最新值,合并成一个流(任何一个流的改变多会引起流的重新组合)。 withLatestFrom与combineLastest类似,不同之处在于这里有主流的概念,主流发射新值时才从另一个流中取出最新值进行组合,并且返回的是数组。 12length$.withLatestFrom(width$) .subscribe(v => console.log(v)) zip接受两个流作为参数。一一对应,讲究辈分相同,而不在乎实际年纪(与combineLastest对比)。 高阶操作符何为流中流? 点击事件是个流,点击事件后要调后台接口又是一个流。这就是典型的流中流的场景。高阶操作符是用来处理流中流的情况(即流的乘法)。 mergeMap所有主流下的支流全部保留,拍平。 switchMap发射最新主流下的支流。一个典型的场景是:用户输入关键字,同时调接口匹配关键字相关内容。当关键字发生改变时,我们已经不关心后台匹配的上一个关键字的返回结果了。此时,就该是switchMap大放异彩了。]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>JS</tag>
<tag>Rxjs</tag>
</tags>
</entry>
<entry>
<title><![CDATA[拥抱未来的web布局————grid]]></title>
<url>%2F2017%2F10%2F07%2FCSS-grid%2F</url>
<content type="text"><![CDATA[一直以来,许多css框架都在帮我们实现一个栅格系统来帮助我们布局页面,当grid标准以一种更优雅的方式来帮我们实现栅格系统… 容器属性声明123.box { display: grid | inline-grid | subgrid;} 分地1234567891011121314151617.box { /* 可以在每个长度数值两边加上网格线的别名 */ grid-template-columns: 40px 50px auto 50px 40px; grid-template-rows: 25% 100px auto;}/* 使用repeat简化代码 */.box { grid-template-columns: repeat(3, 20px [col-start]) 5%;}/* 等价于 */.box { grid-template-columns: 20px [col-start] 20px [col-start] 20px [col-start] 5%;}/* fr是一个特殊的单位,它可以类似于设定flex-grow时,给网格容器中的自由空间设置为一个份数,举个例子,下面的例子将把网格容器的每个子项设置为三分之一 */.box { grid-template-columns: 1fr 50px 1fr 1fr;} 网格间距1234.box { grid-column-gap: 10px; grid-row-gap: 10px;} 内容相对于子项的布局实际上在使用中可能出现这种情况:网格总大小比它的网格容器的容量小,导致这个问题的原因可能是所有网格子项都使用了固定值,比如px。justify-content属性定义了网格和网格容器列轴对齐方式(和align-content相反,它是和行轴对齐)。 12345678/* 水平方向 */.box { justify-content: start | end | center | stretch | space-around | space-between | space-evenly;}/* 垂直方向 */.box { align-content: start | end | center | stretch | space-around | space-between | space-evenly;} 子项相对于其父元素的布局12345678/* justify-items定义了网格子项的内容和列轴对齐方式,即水平方向上的对齐方式。 */.box { justify-items: start | end | center | stretch(默认);}/* 类似于justify-items,align-items定义了网格子项的内容和行轴对齐方式,即垂直方向上的对齐方式。 */.box { align-items: start | end | center | stretch(默认);} 子项属性子项占地子项占地有两种方式:第一种方式是命名标记(我自己的表达),意思就是给子项取独一无二的名字(如grid-area: nav),然后在容器元素的单元格中打上不同子项的烙印,拥有相同标记的单元格自然隶属于同一个子项;第二种方式是边界标记(我自己的表达),同样是在单元格被划分好之后,子项通过划定横轴的跨度与纵轴跨度可以确定一个矩形区域,这个区域便是子项的地盘。 命名标记1234567891011121314151617181920212223242526/* 父元素中 */.grid { display: grid; grid-template-columns: 100px 100px 100px 100px 100px; grid-template-rows: 100px 100px 100px 100px; grid-template-areas: 'title title title title aside' 'nav main main main aside' 'nav main main main aside' 'footer footer footer footer footer';/* 子项中 */.item1 { grid-area: title;}.item2 { grid-area: nav;}.item3 { grid-area: main;}.item4 { grid-area: aside;}.item5 { grid-area: footer;} 边界标记12345678910111213141516171819202122232425.container { display: grid; /* 这里使用另一种方式来划分单元格 */ /*grid-template-columns: 60px 60px 60px 60px 60px; grid-template-rows: 30px 30px;*/ grid-auto-columns: 300px 300px 300px 300px; grid-auto-rows: 300px; /* 这个属性用来定义没有明确定义边界的子项是横排还是竖排 */ grid-auto-flow: row;}.item-a{ /* 这里的数字是网格线默认的名字,你也可以自定义网格线名字 */ /* 当你的跨度仅为一个单元格时,可以只声明起始线, */ /* 等同于grid-column: 1 / 2; */ grid-column: 1; grid-row: 1 / 3;}.item-f{ grid-column: 2 / 4; grid-row: 1 / 3;}.item-c{ grid-column: 4; grid-row: 1 / 3;} 子项内部的布局justify-content与align-content是在父元素上宏观调控其下每个子元素中内容的布局方式,justify-items是精细化的针对每个子元素进行设定。 1234.item-1 { justify-self: start | end | center | stretch; align-self: start | end | center | stretch;} 更多简写grid-column与grid-row表示grid-column-start + grid-column-end,和grid-row-start + grid-row-end的简写形式。 123456789101112.item-c { /* 终止网格线可以用span来表示跨多少单元格 */ grid-column: 3 / span 2; grid-row: third-line / 4;}/* 等同于 */.item-c { grid-column-start: 3; grid-column-end: span 2; grid-row-start: third-line; grid-row-end: 4;} grid-area调用grid-template-areas属性创建的模板。同时,这个属性也可以是grid-row-start+ grid-column-start + grid-row-end+ grid-column-end的缩写(推荐写法)。 1234.item { /* <name>是显示网格使用的值,搭配父容器的grid-template-areas属性来使用 */ grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;} 注意:它的参数的顺序为: / / / 。 最佳实践(个人认为)12345678910111213141516171819202122/* 父容器划分网格(不推荐简写) */.container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px;}/* 子项占地推荐简写 */.item-1 { grid-area: 1 / 1 / 2 / 3;}.item-2 { grid-area: 1 / 3 / 3 / 4;}.item-3 { grid-area: 3 / 2 / 3 / 4;}.item-4 { grid-area: 2 / 1 / 4 / 2;}.item-5 { grid-area: 2 / 2 / 3 / 3;}]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
</categories>
<tags>
<tag>css</tag>
</tags>
</entry>
<entry>
<title><![CDATA[跨域]]></title>
<url>%2F2017%2F09%2F14%2FJS-cross%20domain%2F</url>
<content type="text"><![CDATA[偷得半日闲,那就唠一唠JS跨域那些事儿… 什么是跨域从一个域名的网页请求另一个域名的资源过程中,协议,域名,端口有任何一个的不同,就被当作是跨域。 跨域的实现方式JSONPJSONP跨域的的原理是利用script标签的src属性请求资源不受同源策略的影响。传入callback名,后端传回来的数据就形似函数的调用。下面直接上代码。浏览器端端:1234567891011<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参并指定回调执行函数为cbName script.src = 'http://www.domain2.com:8080/login?user=admin&callback=cbName'; document.head.appendChild(script); // 回调执行函数 function cbName(res) { alert(JSON.stringify(res)); } </script> node服务器端:12345678910111213var querystring = require('querystring');var http = require('http');var server = http.createServer();server.on('request', function(req, res) { var params = qs.parse(req.url.split('?')[1]); // 得到回调函数名 var fn = params.callback; // jsonp返回设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end();});server.listen('8080'); 跨域资源共享(CORS)普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置。带cookie请求:前后端都需要设置字段,另外需注意:所带cookie为跨域请求接口所在域的cookie,而非当前页。浏览器端:1234567891011var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容// 前端设置是否带cookiexhr.withCredentials = true;xhr.open('post', 'http://www.domain2.com:8080/login', true);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.send('user=admin');xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); }}; node服务器端1234567// ...res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie 'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口) 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取cookie});// ... ServerProxy服务器代理其原理是利用自建的服务器作为中转站,帮助浏览器请求不同域的资源。node实现:1234567891011121314151617181920212223const url = require('url')const http = require('http')const https = require('https')const server = http.createServer((req,res)=>{ const path = url.parse(req.url).path.slice(1) if(path === 'topics'){ // node请求真正需要的资源 https.get('https://cnodejs.org/api/v1/topics', (resp) => { let data = '' resp.on('data',chunk=>{ data += chunk }) resp.on('end',()=>{ res.writeHead(200,{ 'Content-Type':'application/json;charset=utf-8' }) // 获取到的数据返回给浏览器端 res.end(data) }) }) }}).listen(3000,'127.0.0.1') postMessagepostMessage 主要包含两个 API:1). 消息监听:onmessage2). 消息发送:postMessage 监听发送过来的消息12345678function onMessage(e) { console.log(e, e.data); // 消息来源安全验证 if(e.origin !="http://lzw.me") { return false; } // 消息处理...window.addEventListener('message', onMessage, false); 向其他窗体发送消息123// 首先获取要传送消息的窗体对象(如iframe),然后向该对象发送信息var iframeWin = document.getElementsByTagName('iframe')[0].contentWindow;iframeWin.postMessage('hello world!', "*"); nginx反向代理接口跨域通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。 nginx配置12345678910111213server { listen 81; # 主机名不同 server_name www.domain1.com; # 域名相同 location / { proxy_pass http://www.domain2.com:8080; #反向代理(目标服务器) proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; }} 浏览器端123456var xhr = new XMLHttpRequest();// 前端开关:浏览器是否读写cookiexhr.withCredentials = true;// 访问nginx中的代理服务器xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);xhr.send(); node后台12345678910111213var http = require('http');var server = http.createServer();var qs = require('querystring');server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前台写cookie res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取 }); res.write(JSON.stringify(params)); res.end();});server.listen('8080');]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>跨域</category>
</categories>
<tags>
<tag>跨域</tag>
</tags>
</entry>
<entry>
<title><![CDATA[VScode上手]]></title>
<url>%2F2017%2F08%2F24%2Ftools-VScode%20%E4%B8%8A%E6%89%8B%2F</url>
<content type="text"><![CDATA[从webstorm到sublime,再到atom。一路上兜兜转转,直到昨天晚上开始了与VScode的第一次邂逅… 渊源还记得上手的第一个编辑器是webstorm,第一次知道代码还可以写得这么流畅。但是很快就觉得它太大了,让我的机器不堪重负。后来知道有一个叫做sublime的轻量级编辑器,各种配置都是通过代码(而不是点选操作)来控制,可以安装各种插件,感觉是高手装逼专用。然后兴致勃勃地捣鼓各种插件。sublime算是用的事件比较长的一款编辑器,但就像小两口一样,日子过久了自然就会出现各种小矛盾。本身底子有点薄弱以及日益庞大的插件群便是我与sublime矛盾的根源了。与atom的交集是始于它的美貌,结束于它的空有其表(个人认为)。与webstorm差不多的体积,功能的集成程度却差得太远。没换电脑之前估计还hold不住它。对VScode的第一印象不算太好。首先它的默认界面就给人一种古板、老旧的感觉。其次这货是微软出的,印象就更差了。但VScode确实超出我的预期。开源免费、对中文友好、功能集成度高、运行流畅…值得称道的东西太多。 使用技巧控制面板按Ctrl + shift + p或者F1打开控制面板。 修改默认快捷键按F1打开控制面板,输入“keyboard shortcut file”,参照默认快捷键设置来自定义快捷键。 开启terminal按Ctrl + shift + oem_3(反引号),Ctrl + k清屏操作。 markdown预览界面右上角的有小图标,可以在右侧开启markdown预览。 拆分编辑器右上角类似翻开书本的小图标可以拆分编辑器,Ctrl + \快捷键也是可以的。 关于gitVScode已经集成Git管理,这一点大赞。开启源代码管理界面,可以清楚地看到你所作的修改。提交代码只需要在消息框中输入提交日志,按Ctrl + Enter提交。左下角有图标显示本地与远程代码的状态,点击该图标就可以拉取或是提交代码到远端。 必备插件备注信息:8d32546b11e0fa881ddee3928583e95b6c1e8221 自动生成文档注释vscode-fileheader 安装vscode-fileheader插件; 在用户配置json文件中添加"fileheader.Author": "yesixuan", "fileheader.LastModifiedBy": "xuan",; 生成文档注释:ctrl+alt+i。 vscode-icons话不多说,直接上插件vscode-icons。 Debugger for Chrome让vscode映射chrome的debug功能,静态页面都可以用vscode来打断点调试。(这个有待后续研究) VUE插件vetur:语法高亮、智能感知、Emmet等;VueHelper:snippet代码片段。 Syncing备份Syncing插件用来备份编辑器所有配置数据(包括插件)。上传配置信息:打开控制面板,依次输入upload、token,Gist ID给个空的让其随机生成即可。下载配置信息:打开控制面板,依次输入download、token,选择一个Gist ID。 常用快捷键 移动行上下:Alt + up/down 删除行:Ctrl + Shift + K 缩进:Ctrl + ] / [ 或 Tab / Shift + Tab 替代鼠标滚轮:Ctrl + Up / Down 整页地翻:Alt + PageUp / PageDown 折叠展开区块代码:Ctrl + Shift + [ / ] 跳转文件:Ctrl + P 切换最近打开:Ctrl + Tab 多选 / 跳选:Ctrl + D / K 从光标处扩展选中全行:Shift + Alt + right 选择所有出现在当前选中的词汇-操作:Ctrl + F2 侧边栏显示隐藏:Ctrl + B 选中行(可以连续选):Ctrl + I]]></content>
<categories>
<category>开发工具</category>
</categories>
<tags>
<tag>VScode</tag>
</tags>
</entry>
<entry>
<title><![CDATA[细说图片懒加载]]></title>
<url>%2F2017%2F08%2F22%2FJS-optimize%20%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD%2F</url>
<content type="text"><![CDATA[好吧,拾人牙慧的一些优化措施,让老夫再来唠叨一番… 实现图片懒加载的原理标签有一个属性是src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有src属性,就不会发送请求。我们先不给设置src,把图片真正的URL放在另一个属性data-src中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到src中。 判断元素是否在可视区方法一①. 通过document.documentElement.clientHeight获取屏幕可视窗口高度。②. 通过element.offsetTop获取元素相对于文档顶部的距离。③. 通过document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离。然后判断②-③<①是否成立,如果成立,元素就在可视区域内。 方法二通过getBoundingClientRect()方法来获取元素的大小以及位置。这个方法返回一个名为ClientRect的DOMRect对象,包含了top、right、botton、left、width、height这些值。我们可以通过el.getBoundingClientRect().top来获取元素到可视区顶部的距离。也就是说,在el.getBoundingClientRect().top<=clientHeight时,图片是在可视区域内的。1234567function isInSight(el) { const bound = el.getBoundingClientRect(); const clientHeight = window.innerHeight; // 如果只考虑向下滚动加载 // 这里加100是为了提前一点点加载 return bound.top <= clientHeight + 100;} 方法三(IntersectionObserver)1234567var io = new IntersectionObserver(callback, option);// 开始观察io.observe(document.getElementById('example'));// 停止观察io.unobserve(element);// 关闭观察器io.disconnect(); callback的参数是一个数组,每个数组都是一个IntersectionObserverEntry对象,包括以下属性: time: 可见性发生变化的时间,单位为毫秒; rootBounds: 与getBoundingClientRect()方法的返回值一样; boundingClientRect: 目标元素的矩形区域的信息; intersectionRect: 目标元素与视口(或根元素)的交叉区域的信息; intersectionRatio: 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0; target: 被观察的目标元素,是一个 DOM 节点对象。由上可知:当intersectionRatio > 0 && intersectionRatio <= 1即在可视区域内。 加载图片123456789101112131415function checkImgs() { const imgs = document.querySelectorAll('.my-photo'); Array.from(imgs).forEach(el => { if (isInSight(el)) { loadImg(el); } })}// 加载图片逻辑function loadImg(el) { if (!el.src) { const source = el.dataset.src; el.src = source; }} 这里应该是有一个优化的地方,设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。 函数节流基本步骤: 获取第一次触发事件的时间戳; 获取第二次触发事件的时间戳; 时间差如果大于某个阈值就执行事件,然后重置第一个时间。 1234567891011121314151617function throttle(fn, mustRun = 500) { const timer = null; let previous = null; return function() { const now = new Date(); const context = this; const args = arguments; if (!previous){ previous = now; } const remaining = now - previous; if (mustRun && remaining >= mustRun) { fn.apply(context, args); previous = now; } }} 最后一种的实现(比较新的浏览器支持)1234567891011121314151617181920function checkImgs() { const imgs = Array.from(document.querySelectorAll(".my-photo")); imgs.forEach(item => io.observe(item));}function loadImg(el) { if (!el.src) { const source = el.dataset.src; el.src = source; }}const io = new IntersectionObserver(ioes => { ioes.forEach(ioe => { const el = ioe.target; const intersectionRatio = ioe.intersectionRatio; if (intersectionRatio > 0 && intersectionRatio <= 1) { loadImg(el); } el.onload = el.onerror = () => io.unobserve(el); });});]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>优化</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS常用方法(四)]]></title>
<url>%2F2017%2F08%2F22%2FJS-common%20function(4)%2F</url>
<content type="text"><![CDATA[整理一下从别处看到的js常用函数参考… 字符串相关字符串过滤1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556//过滤字符串(html标签,表情,特殊字符,)//字符串,替换内容(special-特殊字符,html-html标签,emjoy-emjoy表情,word-小写字母,WORD-大写字母,number-数字,chinese-中文),要替换成什么,默认'',保留哪些特殊字符//如果需要过滤多种字符,type参数使用','分割//如下栗子,意思就是过滤字符串的html标签,大写字母,中文,特殊字符,全部替换成*,但是保留特殊字符'%','?',除了这两个,其他特殊字符全部清除//var str='asd 654a大蠢sasdasdASDQWEXZC6d5#%^*^&*^%^&*$\\"\'#@!()*/-())_\'":"{}?<div></div><img src=""/>啊实打实大蠢猪自行车这些课程';// ecDo.filterStr(str,'html,WORD,chinese,special','*','%?')//"asd 654a**sasdasd*********6d5#%^*^&*^%^&*$\"'#@!()*/-())_'":"{}?*****************"function filterStr(str, type, restr, spstr) { var typeArr = type.split(','), _str = str; for (var i = 0, len = typeArr.length; i < len; i++) { if (typeArr[i] === 'special') { var pattern, regText = '$()[]{}?\|^*+./\"\'+'; if (spstr) { var _spstr = spstr.split(""), _regText = "[^0-9A-Za-z\\s"; for (var i = 0, len = _spstr.length; i < len; i++) { if (regText.indexOf(_spstr[i]) === -1) { _regText += _spstr[i]; } else { _regText += '\\' + _spstr[i]; } } _regText += ']' pattern = new RegExp(_regText, 'g'); } else { pattern = new RegExp("[^0-9A-Za-z\\s]", 'g') } } var _restr = restr || ''; switch (typeArr[i]) { case 'special': _str = _str.replace(pattern, _restr); break; case 'html': _str = _str.replace(/<\/?[^>]*>/g, _restr); break; case 'emjoy': _str = _str.replace(/[^\u4e00-\u9fa5|\u0000-\u00ff|\u3002|\uFF1F|\uFF01|\uff0c|\u3001|\uff1b|\uff1a|\u3008-\u300f|\u2018|\u2019|\u201c|\u201d|\uff08|\uff09|\u2014|\u2026|\u2013|\uff0e]/g, _restr); break; case 'word': _str = _str.replace(/[a-z]/g, _restr); break; case 'WORD': _str = _str.replace(/[A-Z]/g, _restr); break; case 'number': _str = _str.replace(/[0-9]/g, _restr); break; case 'chinese': _str = _str.replace(/[\u4E00-\u9FA5]/g, _restr); break; } } return _str;} 创建正则字符1234567891011121314//创建正则字符,一般是为搜索或者高亮操作//createKeyExp(['我','谁'])//'(我|谁)'function createKeyExp(strArr) { var str = ""; for (var i = 0; i < strArr.length; i++) { if (i != strArr.length - 1) { str = str + strArr[i] + "|"; } else { str = str + strArr[i]; } } return "(" + str + ")";} 关键字加标签1234567891011121314151617181920//简单关键字加标签(多个关键词用空格隔开)//ecDo.findKey('守侯我oaks接到了来自下次你离开快乐吉祥留在开城侯','守侯 开','i')//"<i>守侯</i>我oaks接到了来自下次你离<i>开</i>快乐吉祥留在<i>开</i>城侯"//加完了标签,对i怎么设置样式就靠大家了!function findKey(str, key, el) { var arr = null, regStr = null, content = null, Reg = null, _el = el || 'span'; arr = key.split(/\s+/); //alert(regStr); // 如:(前端|过来) regStr = this.createKeyExp(arr); content = str; //alert(Reg);// /如:(前端|过来)/g Reg = new RegExp(regStr, "g"); content = content; //过滤html标签 替换标签,往关键字前后加上标签 return content.replace(/<\/?[^>]*>/g, '').replace(Reg, "<" + _el + ">$1</" + _el + ">");} 数组相关对象数组排序1234567891011121314151617//对象数组的排序//var arr=[{a:1,b:2,c:9},{a:2,b:3,c:5},{a:5,b:9},{a:4,b:2,c:5},{a:4,b:5,c:7}]//arraySort(arr2,'a,b') a是第一排序条件,b是第二排序条件//[{a:1,b:2,c:9},{a:2,b:3,c:5},{a:4,b:2,c:5},{a:4,b:5,c:7},{a:5,b:9}]function arraySort(arr, sortText) { if (!sortText) { return arr } var _sortText = sortText.split(',').reverse(), _arr = arr.slice(0); for (var i = 0, len = _sortText.length; i < len; i++) { _arr.sort(function(n1, n2) { return n1[_sortText[i]] - n2[_sortText[i]] }) } return _arr;} DOM操作预加载图片1234567891011//图片没加载出来时用一张图片(loading图片)代替,一般和图片懒加载一起使用function aftLoadImg(obj, url, cb) { var oImg = new Image(), _this = this; oImg.src = url; oImg.onload = function() { obj.src = oImg.src; if (cb && _this.istype(cb, 'function')) { cb(obj); } }} 图片滚动懒加载1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556//图片滚动懒加载//@className {string} 要遍历图片的类名//@num {number} 距离多少的时候开始加载 默认 0//比如,一张图片距离文档顶部3000,num参数设置200,那么在页面滚动到2800的时候,图片加载。不传num参数就滚动,num默认是0,页面滚动到3000就加载//html代码//<p><img data-src="lawyerOtherImg.jpg" class="load-img" width='528' height='304' /></p>//<p><img data-src="lawyerOtherImg.jpg" class="load-img" width='528' height='304' /></p>//<p><img data-src="lawyerOtherImg.jpg" class="load-img" width='528' height='304' /></p>....//data-src储存src的数据,到需要加载的时候把data-src的值赋值给src属性,图片就会加载。//详细可以查看testLoadImg.html//window.onload = function() {// ecDo.loadImg('load-img',100);// window.onscroll = function() {// ecDo.loadImg('load-img',100);// }//}function loadImg(className, num) { var _className = className || 'ec-load-img', _num = num || 0, _this = this; var oImgLoad = document.getElementsByClassName(_className); for (var i = 0, len = oImgLoad.length; i < len; i++) { if (document.documentElement.clientHeight + document.body.scrollTop > oImgLoad[i].offsetTop - _num && !oImgLoad[i].isLoad) { //记录图片是否已经加载 oImgLoad[i].isLoad = true; //设置过渡,当图片下来的时候有一个图片透明度变化 oImgLoad[i].style.cssText = "transition: ''; opacity: 0;" if (oImgLoad[i].dataset) { this.aftLoadImg(oImgLoad[i], oImgLoad[i].dataset.src, function(o) { setTimeout(function() { if (o.isLoad) { _this.removeClass(o, _className); o.style.cssText = ""; } }, 1000) }); } else { this.aftLoadImg(oImgLoad[i], oImgLoad[i].getAttribute("data-src"), function(o) { setTimeout(function() { if (o.isLoad) { _this.removeClass(o, _className); o.style.cssText = ""; } }, 1000) }); } (function(i) { setTimeout(function() { oImgLoad[i].style.cssText = "transition:all 1s; opacity: 1;"; }, 16) })(i); } }} 其它做操封装AJAX1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/* * @param {string}obj.type http连接的方式,包括POST和GET两种方式 * @param {string}obj.url 发送请求的url * @param {boolean}obj.async 是否为异步请求,true为异步的,false为同步的 * @param {object}obj.data 发送的参数,格式为对象类型 * @param {function}obj.success ajax发送并接收成功调用的回调函数 * @param {function}obj.error ajax发送失败或者接收失败调用的回调函数 */// ecDo.ajax({// type:'get',// url:'xxx',// data:{// id:'111'// },// success:function(res){// console.log(res)// }// })function ajax(obj) { obj = obj || {}; obj.type = obj.type.toUpperCase() || 'POST'; obj.url = obj.url || ''; obj.async = obj.async || true; obj.data = obj.data || null; obj.success = obj.success || function() {}; obj.error = obj.error || function() {}; var xmlHttp = null; if (XMLHttpRequest) { xmlHttp = new XMLHttpRequest(); } else { xmlHttp = new ActiveXObject('Microsoft.XMLHTTP'); } var params = []; for (var key in obj.data) { params.push(key + '=' + obj.data[key]); } var postData = params.join('&'); if (obj.type.toUpperCase() === 'POST') { xmlHttp.open(obj.type, obj.url, obj.async); xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'); xmlHttp.send(postData); } else if (obj.type.toUpperCase() === 'GET') { xmlHttp.open(obj.type, obj.url + '?' + postData, obj.async); xmlHttp.send(null); } xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { obj.success(xmlHttp.responseText); } else { obj.error(xmlHttp.responseText); } };} 手机类型判断1234567891011121314151617//手机类型判断//browserInfo('android')//false(在浏览器iphone6模拟器的调试)function browserInfo(type) { switch (type) { case 'android': return navigator.userAgent.toLowerCase().indexOf('android') !== -1 case 'iphone': return navigator.userAgent.toLowerCase().indexOf('iphone') !== -1 case 'ipad': return navigator.userAgent.toLowerCase().indexOf('ipad') !== -1 case 'weixin': return navigator.userAgent.toLowerCase().indexOf('MicroMessenger') !== -1 default: return navigator.userAgent.toLowerCase() }}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>常用函数</category>
</categories>
<tags>
<tag>js</tag>
<tag>常用方法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[koa入门]]></title>
<url>%2F2017%2F08%2F14%2Fnode-koa%E5%85%A5%E9%97%A8%2F</url>
<content type="text"><![CDATA[koa是由Express原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的Web框架。… 基本用法搭建最简单的服务123const Koa = require('koa')const app = new Koa()app.listen(3000) Context对象Koa提供一个Context对象,表示一次对话的上下文(包括HTTP和HTTP)。通过加工这个对象,就可以控制返回给用户的内容。常用对象:Context.response.body:将你想返回给前端的数据赋值给该对象。 Content-TypeKoa返回的mine类型是是text/plain,如果想返回其他类型的内容,可以先用ctx.request.accepts判断一下,客户端希望接受什么数据(根据 HTTP Request 的Accept字段),然后使用ctx.response.type指定返回类型。 路由通过ctx.request.path可以获取用户请求的路径,由此实现简单的路由。但如果你不是想做一个玩具的话,你是不会用这种方式来实现路由的。一般来说,我们会借助koa-route模块来实现路由。 koa-route模块12345678910const route = require('koa-route')const about = ctx => { ctx.response.type = 'html' ctx.response.body = '<a href="/">Index Page</a>'}const main = ctx => { ctx.response.body = 'Hello World'}app.use(route.get('/', main))app.use(route.get('/about', about)) 静态服务express自身具备提供静态服务的功能,但是到了koa,这个功能被剥离出去,通过koa-static来实现。12345const path = require('path')const serve = require('koa-static')// 将指定的目录静态呈递出去const main = serve(path.join(__dirname))app.use(main) 重定向ctx.response.redirect()方法可以发出一个302跳转,将用户导向另一个路由。123456const redirect = ctx => { // 这个方法有点像乾坤大挪移 ctx.response.redirect('/') ctx.response.body = '<a href="/">Index Page</a>'}app.use(route.get('/redirect', redirect)) 中间件中间件大概是koa中,最核心也是最重要的概念了。 中间件栈多个中间件串联起来就形成了中间件栈。最先添加的中间件最先入栈,一旦在中间件中调用next(),那么代码的执行环境会从当前中间件跳到其栈结构上面的中间件中。到了栈结构顶部的中间件时,顶层的中间件开始出栈,最终回到栈结构底部。这就类似于我们向天空抛出一颗石子,石子先上升,后掉落的过程。 异步中间件异步中间件需要将中间件写成async函数。1234const main = async function (ctx, next) { ctx.response.type = 'html' ctx.response.body = await fs.readFile('./demos/template.html', 'utf8')} 中间件的合成koa-compose模块可以将多个中件合成为一个。12345678910const compose = require('koa-compose')const logger = (ctx, next) => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`) next()}const main = ctx => { ctx.response.body = 'Hello World'}const middlewares = compose([logger, main])app.use(middlewares) 错误处理500错误123const main = ctx => { ctx.throw(500)} 404错误如果将ctx.response.status设置成404,就相当于ctx.throw(404),返回404错误。1234const main = ctx => { ctx.response.status = 404 ctx.response.body = 'Page Not Found'} 抛出错误为了方便处理错误,最好使用try…catch将其捕获。但是,为每个中间件都写try…catch太麻烦,我们可以让最里层的中间件,负责所有中间件的错误处理。123456789101112131415const handler = async (ctx, next) => { try { await next() } catch (err) { ctx.response.status = err.statusCode || err.status || 500 ctx.response.body = { message: err.message } }}const main = ctx => { ctx.throw(500)}app.use(handler)app.use(main) error事件的监听运行过程中一旦出错,Koa会触发一个error事件。监听这个事件,也可以处理错误。123456const main = ctx => { ctx.throw(500)}app.on('error', (err, ctx) => console.error('server error', err)) 释放error事件需要注意的是,如果错误被try…catch捕获,就不会触发error事件。这时,必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效。1234567891011121314151617const handler = async (ctx, next) => { try { await next() } catch (err) { ctx.response.status = err.statusCode || err.status || 500 ctx.response.type = 'html' ctx.response.body = '<p>Something wrong, please contact administrator.</p>' ctx.app.emit('error', err, ctx) }}const main = ctx => { ctx.throw(500)}app.on('error', function(err) { console.log('logging error ', err.message) console.log(err)}) Web App的功能Cookiesctx.cookies用来读写Cookie。1234567const main = function(ctx) { // 读cookies const n = Number(ctx.cookies.get('view') || 0) + 1 // 写cookies ctx.cookies.set('view', n) ctx.response.body = n + ' views'} 表单koa-body模块可以用来从POST请求的数据体里面提取键值对。1234567const koaBody = require('koa-body')const main = async function(ctx) { const body = ctx.request.body if (!body.name) ctx.throw(400, '.name required') ctx.body = { name: body.name }}app.use(koaBody()) 文件上传koa-body模块还可以用来处理文件上传。123456789101112131415161718const os = require('os')const path = require('path')const koaBody = require('koa-body')const main = async function(ctx) { const tmpdir = os.tmpdir() const filePaths = [] const files = ctx.request.body.files || {} for (let key in files) { const file = files[key] const filePath = path.join(tmpdir, file.name) const reader = fs.createReadStream(file.path) const writer = fs.createWriteStream(filePath) reader.pipe(writer) filePaths.push(filePath) } ctx.body = filePaths}app.use(koaBody({ multipart: true })) 最后本文大量参考阮一峰的网络日志。原文链接]]></content>
<categories>
<category>后端</category>
<category>node</category>
<category>koa</category>
</categories>
<tags>
<tag>koa</tag>
</tags>
</entry>
<entry>
<title><![CDATA[IO流]]></title>
<url>%2F2017%2F08%2F13%2Fjava-IO%E6%B5%81%2F</url>
<content type="text"><![CDATA[有了IO流,我们才拥有了对硬盘上的文件进行读写的功能。当然,这只是一个开始… File类概览创建一个代表一个文件或者文件夹对象,也就是你要操作的目标。123456// 创建一个文件对象(不管是不是真实存在)File file = new File("F:\\a.txt"); // Linux里面是一个正斜杠(windows也是支持的)// 判断文件对象是否存在file.exists();// 获取系统的目录分隔符File.separator(); 构造方法123456// 指定文件或文件夹,创建文件对象File(String pathname);// 将路径拆开来(应用场景:先要操作父文件对象,再操作子文件)File(File parent, String child);// 没太多意义,纯粹拆开路径File(String parent, String child); 注意:使用相对路径时,当前路径指的是项目根目录。 File类中常用方法创建12345678910File file = new File("F:\\a.txt");File dir = new File("F:\\a");// 基于文件对象创建一个文件(会抛出异常,返回Boolean)file.createNewFile();// 基于文件对象创建一个单级文件夹dir.mkdir();// 创建一个多级文件夹dir.mkdirs();// 重命名(包含剪切)file.renemeTo(destFile); 删除1234// 删除文件或空文件夹delete();// 在虚拟机终止时,删除此抽象路径下的文件或目录,保证程序异常时创建的临时文件也可以删除deleteOnExit() 判断12345exists();isFile();isDirectory();isHidden();isAbsolute(); // 判断该路径是否为绝对路径 获取1234567891011121314151617getName();getPath();getAbsolutePath();// 获取文件大小(字节数),如果文件不存在或者是文件夹则返回0Llength();getParent();lastModified();// 列出系统根目录static File[] listRoots();// 返回指定目录中的子文件或子目录list();// 返回指定目录中符合过滤条件的子文件或子目录list(Filename filter);// 列出指定目录下的文件或目录对象(File实例)listFiles();// 返回指定目录下符合过滤条件的子文件或子目录listFiles(Filename filter); 实例场景:分开列出目录中所有子文件名与子目录名。12345678910111213141516171819202122public static viod listAllFilesAndDirs(String path) { File dir = new File(path); File[] name = dir.listFiles(); List<File> fileList = new ArrayList<File>(); List<File> dirList = new ArrayList<File>(); for(int i = 0; i < name.length; i++) { File file = names[i]; if(file.isFile) { fileList.add(file); }else if(file.isDirectory) { dirList.add(file); } } System.out.println("子文件:"); for(int i = 0; i < fileList.size(); i++) { System.out.println("\t" + fileList[i].getName()); } System.out.println("子目录:"); for(int i = 0; i < dirList.size(); i++) { System.out.println("\t" + dirList[i].getName()); }} 字节流上面我们已经知道File对象封装的是文件或者路径属性,但是不包含向(从)文件读(写)数据的方法,此时就该是IO流粉墨登场的时候了。 输入流read()这种方式效率底下,不推荐使用。12345678910/* 一次读取一个字节,读到文末返回-1 */private viod showContent(String path) throws IOException { // 打开流管道 FileInputStream fis = new FileInputStream(path); int len; while(len = fis.read() != -1) { System.out.println((char)len); } fis.close();} read(byte[] b)使用read()的时候,流需要读一次就处理一次。read(byte[] b)可以将读到的数据装入字节数组中,一次性地操作数组,可以提高效率。read()就类似你从井里一次舀一杯水送到房间里给别人喝;read(byte[] b)就相当于你从井里一次舀一杯水先放到桶子里,等舀满一桶水再将这桶水运到房间里给别人喝。123456789private viod showContent(String path) throws IOException { FileInputStream fis = new FileInputStream(path); byte[] byt = new byte[1024]; int len = 0; while((len = fis.read(byt))! = -1) { // 字节数组里面存的是二进制数据,要将之解析出来需要借助字符串的构造方法 System.out.println(new String(byt, 0, len)); }} read(byte[] b, int off, int len)其实就是把数组的一部分当作流的容器来使用。告诉容器从什么地方开始装多少。 输出流输出流基本就是用来写入数据的。 write(int b)write(int b)一次写出一个字节。虽然write(int b)接收的是int类型参数,但是write(int b)的常规协定是:向输入流写入一个字节,要写入的字节是参数的低八位,高24个高位将将被忽略。 write(byte[] b)write(byte[] b)相比write(int b)效率更高。原因就不多说了。我们只需要将字符串转化为字节数组,然后通过该方法就可以一次写入多个字节。创建输出字节流对象时,如果不想覆盖已存在内容,而是追加内容只需如此这般:new FileOutputStream(path, true)123456private static void writeTxtFile(String path) throwss IOException { FileOutputStream fos = new FileOutputStream(path); byte[] byt = "要插入的数据".getBytes(); fos.write(byt); fos.close();} 字节流文件拷贝1234567891011121314public static copyFile(String srcPath, String destPath) throwss IOException { // 打开输入输出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath); // 读取和写入信息 int len = 0; byte[] byt = [1024]; while((len = fis.read(byt)) != -1) { fos.write(byt, 0, length); } // 关闭流 fis.close(); fos.close();} 字节流的异常处理上述例子中,我们使用了抛出异常的方式来处理异常。但实际上,这样做是不合适的。例如第一个通道关闭出错会导致第二个通道无法关闭。正确的做法是使用try--catch--finelly语句块来处理异常。 字节缓冲流字节缓冲流内部有一个缓冲区,其实内部也是封装了字节数组,默认的字节是8192。缓冲区输入流与缓冲区输出流要配合使用。首先缓冲区输入流会将读取到的数据写入缓冲区,当缓冲区满时,或者调用flush()方法,缓冲输出流会将数据写出。12345678910111213141516public static viod copyFile(String srcPath, String destPath) throws IOException { // 打开输入流,输出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath); // 使用缓冲流 BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); // 读取和写入信息 int len = 0; while((len = bis.read()) != -1) { bos.write(len); } // 关闭流 bis.close(); bos.close();} 字符流(FileReader & FileWwiter)字节流以字节为单位,而字符流是以字符为单位。更方便地处理文本文档。 常见的码表计算机不管是存文本文件还是二进制文件本质上都是存的二进制数据。如果是文本文件的话,计算机需要一套规则才能将二进制的数据翻译成我们能看懂的字符。这套规则就是指的码表。 ASCII:美国标准信息交换码。一个字符占一个字节,用一个字节的7位可以表示 ISO8859-1:拉丁码表。欧洲码表,用一个字节的8位表示 GBG2312: 英文占一个字节,中文占两个字节 GBK:中国的中文码表升级,融合了更多的中文文字符号 Unicode:国际标准码规范。所有文字都用两个字节来表示(java就是用的这个) UTF-8:英文一个字节,中文三个字节 Reader方法int reader();读取一个字符。返回的是读到的那个字符。如果读到流的末尾则返回-1;int read(char[]);将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里装的元素的个数。读到流的末尾则返回-1;close();使用完毕后,进行资源的释放;12345678public static void readFileByReader(String path) throws Exception { Reader reader = new FileReader(path); int len = 0; while((len = reader.read()) != -1) { System.out.println((char)len); } reader.close();} Writerwrite(ch)将一个字符写入到流中。write(char[])将一个字符数组写入到流中。write(String)将字符串写入到流中。flush()刷新流,将流中的数据刷新到目的地中,流还存在。close()关闭资源。关闭前会先调用flush(),刷新流中数据到目的地,然后流关闭。123456789public static void writeToFile(String path) throws Exception { // 这里的第二个参数表示是否为追加 Write write = new FileWiter(path, true); write.write('中'); write.write("世界".toCharArray()); write.write("中国"); // 这里没有调用flush()是因为close()会调用flush() write.close();} 用字符流拷贝文本文件1234567891011public static void copyFile(String path1, String path2) throws Exception { Reader reader = new FileReader(path1); Writer writer = new FileWiter(path2); int ch = -1; char[] arr = new char[1024]; while((ch = reader.reader(arr)) != -1) { writer.writer(arr, 0, ch); } reader.close(); writer.close();} 字符流的缓冲区缓冲的存在是为了增强流的功能而存在,所以在建立缓冲区对象时,先要有流对象存在。缓冲区的出现提高了对流的操作效率。原理就是将数组进行封装。123456789101112131415161718192021private static void copyFile(File srcFile, File destFile) throws IOException { // 创建输入输出流 FileReader fr = new FileReader(srcFile); FileWriter fw = new FileWriter(destFile); // 创建字符输出流 BufferedReader br = new BufferedReader(fr); BufferedWriter bw = new BufferedWriter(fw); // 拷贝(可以一行一行地读) String line = null; while((line = br.readLine()) != null) { // 一次写出一行 bw.write(line); // 刷新缓冲 bw.flush(); // 换行,readLine方法默认没有换行,需要手动换行 bw.newLine(); } // 关闭流 br.close(); bw.close();}]]></content>
<categories>
<category>后端</category>
<category>java</category>
<category>基础</category>
</categories>
<tags>
<tag>java</tag>
<tag>IO流</tag>
</tags>
</entry>
<entry>
<title><![CDATA[集合]]></title>
<url>%2F2017%2F08%2F11%2Fjava-collection%2F</url>
<content type="text"><![CDATA[之前一直很费解java中那么严格的数组怎么能满足开发中的各种需求,也一直为javascript那么灵活的数据类型而庆幸。直到了解了集合这个东西的时候才知道很多东西是想当然了… 概览Collection是与数组一个阶层的数据类型。不同的是,数组是定长并且数据类型固定。而Collection就没有这样的限制。Collection的体系如下:—-| Collection 单列集合类的根接口  —-| List 描述可以有重复元素的集合    —-| ArrayList 实现了List接口的实现类,特点是查询快,增删慢    —-| LinkedList 链表存储,特点是查询慢,增删快  —-| Set接口 不可以有重复元素的集合    —-| HashSet hash表存储,新增时,先判断hashcode,hashcode相同再调用equals方法判断    —-| TreeSet 二叉树结构存储,会对集合内的元素进行排序 collection常用方法 增加 add(E e) addAll(Collection c) 删除 clear() :清空集合元素 remove(Object 0) :删除某元素(返回boolean) removeAll(Collection c) :删除两个集合中的交集元素(返回boolean) retainAll(Collection c) :保留两个集合中的交集元素(返回boolean) 查看 contains(Object o) containsAll(Collection c) isEmpty() size() 迭代 toArray() :将集合中的元素存储到对象数组中返回 iterator() :返回一个实现了Iterator接口的实现类对象 Iterator接口当获取到集合中的迭代器的时候,那么迭代器对象就会有一个游标指向了集合中的第一个元素。常用方法:hasNext()、next()、remove()。remove()移除迭代器最后一次返回的元素(即最后一个next()返回的)。12345678// Collection是接口,所以创建的时候都是创建它的实现类对象Collection c = new ArrayList();// 获取实现了Iterator接口的实现类对象Iterator it = c.iterator();// 遍历while(it.hasNext()) { System.out.println(it.next());} 注意 要以对象的形式查看集合元素,需要重写集合元素中的toString(); ArrayList实现的contains()判断的依据实际上是调用了每个每个元素的equals()。equals()默认比较内存地址,很多时候,我们需要重写元素的equals(); isEmpty()判断集合中如果有null,则判定结果也是非空的; hasNext()实际上是问当前游标有没有指向一个元素; next()首先返回当前游标指向的元素,然后游标向下移动一个单位; List实现了List接口类的添加元素都是有序的,并且可以重复。有序:在集合中所谓的“有序”不是指自然顺序,而是指添加进去的顺序与存储的顺序一致。 List接口特有方法 增加 add(int index, E element) addAll(int index, Collection c) 删除 remove(int index) 修改 set(int index, E element) 获取(类似字符串方法) get(int index) indexOf(Object o) :类似js,如果不存在则返回-1 lastIndexOf(Object o) subList(int fromIndex, int toIndex) :返回Lint接口的实现类对象SubList,而不是List 迭代 listIterator()listIterator()特有的方法:hasPrevious()(是否有上一个元素,注意区别hasnext())、previous()、add(E e)、set(E e)。add(E e)表示将元素添加到当前游标指向的位置上。set(E e)替换最后一次返回出来的元素,包括previous()和next()。总结:List接口下的方法大都是操作索引的(传入索引或返回索引),这一是它有序的前提下造成的现象。四种遍历方式:① toArray(); ② for循环配合get; ③ 遍历器顺序; ④ 遍历器逆序。集合对象的add()添加至集合的尾部,而List实现类对象的add()添加至游标指向的索引。在遍历过程中,使用集合对象的add()会报错,而List实现类对象的add()会跳过新添加的元素继续遍历。注意:迭代器在迭代过程中(指在两个及以上的next()中间),不能使用集合对象改变集合的元素个数。 ArrayListArrayList无参构造方法默认容量为10。如果不够,则自动增加为原来的1.5倍。它的底层是使用了Object数组来存储元素的。ArrayList的特有方法: ensureCapacity(int mainCapacity)(设置长度)、trimToSize()(设置为实际长度)。两者都不常用。ArrayList查询快,增删慢的原因是:Object数组中,元素与元素的内存地址是连续的(查询快);增删过程中会判断原数组长度是否足够,如果不够,则对数组进行拷贝,这一点很费时。 LinkedListLinkedList的存储方式是链表式存储。类似现实生活中的链条,一环扣着一环。上个元素保存着下个元素的内存地址。正因如此,LinkedList查询慢,增删快。 LinkedList特有方法LinkedList特有方法都是跟首尾有关的:addFirst(E e)、addLast(E e)、getFirst()、getLast()、removeFirst()、removeLast() Set实现了Set接口的集合类具备的特点:无序、元素不可重复。 HashSetHashSet底层是使用hashTable来实现的。HashSet判断重复元素的判断过程是首先使用hashCode()判断,如果hashcode相同再调用equals(),如果hashCode相同,而equals不等时,两个元素将存储在同一个单元格(桶式结构,一个单元格可以看作一个桶)中。所以很多时候,我们要利用HashSet来去重的话,需要重写hashcode()。 TreeSetTreeSet内部使用红黑树结构存储,具有自动排序功能。TreeSet默认是按照元素的自然顺序进行排序的,但是如果元素不具备自然顺序则需要我们为元素所属类实现Comparable接口的compareTo的方法或者是在创建TreeSet对象时传入比较器对象。123456789101112131415161718192021222324/* 方法一,实现Comparable接口的compareTo方法 */class Emp implements Comparable { // ... @override public int compareTo(Object o) { Emp e = (Emp)o; return this.salary - e.salary; }}/* 方法二,创建TreeSet对象时传入比较器对象 */// 自定义比较器类class AgeComparator implements Comparator { @override public int compare (Object o1, Object o2) { Emp e1 = (Emp)o1; Emp e2 = (Emp)o2; return e1.age - e2.age; }}// ...// 传入TreeSet构造方法AgeComparator ageComparator = new AgeComparator();TreeSet tree = new TreeSet(ageComparator); 当TreeSet对象必备上述两种比较规则的话,将会优先使用传入比较器的方式。这里推荐使用传入比较器方式,复用性与优先级都比较高。 泛型严格来说,泛型并不属于collection这个体系。但听说collection搭配泛型口感会更佳。这里也一并来唠唠。12// <String>相当于给list这个元素贴上了一个标签,只能用来存放String类型的元素ArrayList<String> list = new ArrayList<String>(); // 标准写法是两边都写泛型,但只写一边也没问题 泛型的好处: 把运行时出现的问题提前至编译时; 避免无意义的强制类型转换; 自定义泛型自定义泛型可以理解为是一个数据类型的变量或者是一个数据类型的占位符。函数自定义泛型的格式:修饰符 <声明自定义泛型> 返回值类型 函数名 (形参列表…) {}在调用函数时才确定自定义泛型所代表的数据类型。1234// 如果传入的是基础数据类型,则返回的该数据类型的包装类public static <T> print(T o) { return o} 基础数据类型对应的包装类型:Integer、Float、Double、Character、Boolean、Byte、Short、Long。 自定义泛型类:在类名后面加上泛型,则该类变为泛型类。类中的方法就不用重复地声明泛型了。这里泛型的确定时机是在创建对象是确定的(如果在创建泛型类对象时,没有传入泛型类型,则默认为Object类型)。注意:类上的泛型是不能够给静态方法使用的,此时只能在静态方法上声明泛型。 泛型接口:与泛型类类似,泛型声明在接口名后面。在实现该接口时确定泛型类型(如果此时还不能确定泛型类型,就需要将该实现类声明为同名接口)。 泛型的上下限泛型的上下限实际上就是将泛型的指代范围缩小。1234// 接收Integer以及它的父类public static void print(Collection<? super Integer>) {}// 接受Number以及它的子类public static void show(Collection<? extend Number>) {}]]></content>
<categories>
<category>后端</category>
<category>java</category>
<category>基础</category>
</categories>
<tags>
<tag>java</tag>
<tag>collection</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java中的字符串]]></title>
<url>%2F2017%2F08%2F11%2Fjava-%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%85%B3%2F</url>
<content type="text"><![CDATA[java开篇之作,来聊一聊java中的字符串相关… 字符串比较创建过程字符串的常用创建方式有两种。一种是字面量的方式创建,另一种是使用new关键字创建。第一种创建方式,jvm会首先在字符串常量池中检查是否存在该字符串。如果不存在,则在字符串常量池中创建字符串对象,返回其内存地址;如果存在,则直接返回该对象的内存地址。第二种创建方式,jvm也会首先在字符串常量池中检查是否已经存在,如果已存在,则将其拷贝到堆内存中,并返回堆内存中的内存地址。如果不存在,就会在堆内存中创建字符串对象,返回其内存地址。 比较符使用==比较时,比较的是内存地址。使用equals()比较时,比的是字符串的内容(字符串类重写了继承自Object的equals())。 总结 使用字面量方式创建的字符串对象在字符串常量池中 使用new关键字创建的对象在堆内存中 字符串之间的比较使用equals()就好 比较一个变量是否为某个字符串时,使用字符串.equals(变量)来判断,避免报错 String类的方法构造方法1234567891011121314151617/* 使用字节数组构建字符串(使用平台默认编码规则) */byte[] buf = {97, 98, 99};String str = String(buf); // [abc]// 指定码表String(byte[] bytes, Charset charset);// 指定开始位置使用的位置索引值与使用的字符个数(最后还可以加一个参数,用来指定码表)String(byte[] bytes, int offset, int length);/* 使用字符数组构建一个字符串 */char[] arr = {'a', 'b', 'c'};str = new String(arr);// 指定片段构造String(char[] value, int offset, int count);/* 使用int类型数组构建字符串(类byte数组) */int[] arr2 = {65, 66, 67};str = new String(arr2, 0, 3); 获取方法12345678// 获取字符串长度int length();// 获取特定位置的字符char charAt(int index);// 获取特定字符的位置(从头检索)int indexOf(String str);// 获取特定字符的位置(从屁股检索)int lastIndexOf(int ch) 判断方法12345678910// 判断是否以指定字符结束boolean endsWidth(String str);// 判断是否为空字符串boolean isEmpty();// 是否包含指定序列boolean contains(CharSequences);// 是否相等boolean equals(String str);// 忽略大小写比较boolean equalsIgnoreCase(String anotherString); 转换方法1234// 将字符数组转换为字符(可以指定开始位置与长度)String(char[] value);// 将字符串转换为字符数组(字符串反转时要用到)char[] toCharArray(); 其他方法123456String replace(char oldChar, char newChar); // 替换String[] split(String regex); // 切割,返回数组String substring(int beginIndex, int endIndex); // 跟js中的同名方法一致String toUpperCase();String toLowerCase();String trim(); // 去空格 StringBuffer与StringBuilder由于String是不可变的,所以在需要频繁改变字符串对象的应用中,需要使用可变的字符串缓冲区类。默认缓冲区的容量是16。StringBuffer与StringBuilder的用法类似,区别在于StringBuilder是线程不安全的,但是效率要高。 常用方法1234567891011121314151617181920/* 添加方法 */StringBuffer("jack"); // 在创建对象的时候赋值append(); // 在缓冲区尾部添加新的文本对象insert(); // 在指定的下标位置添加新的文本对象/* 查看 */toString(); // 返回这个容器的字符串indexOf(String str);/* 修改 */substring(int start); // 从开始位置截取字符串replace(int start, int end, String str);setCharAt(int index, char ch); // 指定索引位置替换一个字符/* 删除 */delete (int start, int end);deleteCharAt(int index);/* 反序 */reverse();]]></content>
<categories>
<category>后端</category>
<category>java</category>
<category>基础</category>
</categories>
<tags>
<tag>字符串</tag>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[async/await 更好的异步解决方案]]></title>
<url>%2F2017%2F08%2F01%2FJS-async%20await%2F</url>
<content type="text"><![CDATA[异步的解决方案有很多种。包括最传统的回调,后来蓬勃发展的Promise,到了今时今日,JS大爆发的的时代。让我们看看现代的异步解决方案… 整体感知async/await提供给我们一种同步的方式来编写异步代码。如果去掉await关键字,下面这段异步代码就跟我们常见的同步代码别无二致了。1234567891011121314151617// 定义一个返回Promise对象的函数function fn() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(30) }, 1000) })}// 然后利用async/await来完成代码const foo = async() => { const t = await fn() console.log(t) console.log('next code')}foo()// 30// next code 更进一步前世————Generator函数async/await的前世是ES6提供的Generator函数。那么下面来瞧一瞧Generator函数是个什么鬼。1234567function* gen(x) { var y = yield x + 2 return y}var g = gen(1)g.next() // { value: 3, done: false }g.next() // { value: undefined, done: true } 简单粗暴地说,Generator函数就是一种可以暂停执行的函数。我们在调用Generator函数时,会返回一个内部指针(即遍历器)。继续调用这个内部指针的next方法才会执行Generator函数体中的语句,然后遇到以yield关键字则会暂停执行,知道再一次调用next方法。调用遍历器的next方法会返回形如{value: xx, done: bool}的对象。value接收yield之后的值,done表示函数是否执行完毕。遍历器的next方法可以接收外部传入的参数,该参数将会被当作上一个yield的值参与运算。下面看看如何用Generator函数来实现异步操作:12345678910111213141516var fetch = require('node-fetch')function* gen(){ var url = 'https://api.github.com/users/github' var result = yield fetch(url) console.log(result.bio)}/* 执行这段代码如下 */var g = gen();// result得到的是一个Promise对象var result = g.next();result.value.then(function(data){ return data.json();}).then(function(data){ // 得到异步返回的数据之后调用下一个next,并且将数据传进next方法 g.next(data);}); async/await为什么要用asyncasync函数是什么?一句话,它就是Generator函数的语法糖。上面我们利用Generator函数封装了异步操作,但是那种写法比较别扭。首先,我们要将异步操作用Promise封装起来,其次,当异步完成之后进行下一个操作时,需要手动地调用next方法。async函数的出现,就弥补了我们提到的不足:12345678910async function getStockPriceByName(name) { // 正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。 var symbol = await getStockSymbol(name) var stockPrice = await getStockPrice(symbol) return stockPrice}// async函数返回一个Promise对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。getStockPriceByName('goog').then(function (result) { console.log(result)}) 不让前面的错误影响后面的操作有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。1234567891011async function f() { try { await Promise.reject('出错了') } catch(e) { } // 即使await后面的语句报错,下面这个await还是会执行 return await Promise.resolve('hello world')}f().then(v => console.log(v))// hello world 错误处理await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。1234567891011121314async function myFunction() { try { await somethingThatReturnsAPromise() } catch (err) { console.log(err) }}// 另一种写法async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err) });} 并发的异步上面的写法都是处理继发状况,下面来说说并发异步的处理:1234567// 写法一let [foo, bar] = await Promise.all([getFoo(), getBar()])// 写法二let fooPromise = getFoo()let barPromise = getBar()let foo = await fooPromiselet bar = await barPromise]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>js</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用redux-saga中间件处理redux中的异步]]></title>
<url>%2F2017%2F07%2F29%2FJS-react%20redux-saga%E4%B8%AD%E9%97%B4%E4%BB%B6%2F</url>
<content type="text"><![CDATA[记得某一天,第一次看到有人说到redux-saga。他说:用了redux-saga之后就再也回不去redux-thunk了… 整体感知使用redux-saga封装异步操作12345678910111213141516import { call, put } from 'redux-saga/effects'import { takeEvery } from 'redux-saga'// 这个函数就封装了我们的异步操作,每一个yield都要等上一个yield完成之后再执行function* fetchData(action) { try { // 这里其实是用声明式的方式调用Api.fetchUser方法,并传入参数 const data = yield call(Api.fetchUser, action.payload.url); yield put({type: "FETCH_SUCCEEDED", data}); } catch (error) { yield put({type: "FETCH_FAILED", error}); }}// 监听INCREMENT_ASYNC action的调用,然后调用fetchData这个封装了异步的generate函数export function* watchFetchData() { yield* takeEvery('FETCH_REQUESTED', fetchData)} 将watchFetchData这个Saga连接至Store12345import { fetchData, watchFetchData } from './sagas'const store = createStore( reducer, applyMiddleware(createSagaMiddleware(watchFetchData))) 细节展示Saga辅助函数takeEvery是最常见的,它提供了类似redux-thunk的行为;takeLatest则只执行最后一次fetchData1234567import { takeEvery } from 'redux-saga'function* watchFetchData() { // 只要监听到FETCH_REQUESTED这个action被派发,就一定会调用fetchData。不管上一次的fetchData有没有完成 yield* takeEvery('FETCH_REQUESTED', fetchData) // 可以作为节流函数使用,还可以避免上一次输入已经清空,但结果还是返回了上次的输入得到的结果 yield* takeLatest('FETCH_REQUESTED', fetchData)} 声明式Effects在 redux-saga 的世界里,Sagas 都用 Generator 函数实现。我们从 Generator 里 yield 纯 JavaScript 对象以表达 Saga 逻辑。 我们称呼那些对象为 Effect。Effect 是一个简单的对象,这个对象包含了一些给 middleware 解释执行的信息。 你可以把 Effect 看作是发送给 middleware 的指令以执行某些操作(调用某些异步函数,发起一个 action 到 store)。 call为什么我们使用call声明式地调用一个函数而不是用“函数()”的方式?这是为了方便我们做断言测试。call 同样支持调用对象方法,你可以使用以下形式,为调用的函数提供一个 this 上下文: 12345678yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...)``` #### applyapply 提供了另外一种调用的方式: ```jsyield apply(obj, obj.method, [arg1, arg2, ...]) cpscall 和 apply 非常适合返回 Promise 结果的函数。另外一个函数 cps 可以用来处理 Node 风格的函数 (例如,fn(…args, callback) 中的 callback 是 (error, result) => () 这样的形式,cps 表示的是延续传递风格(Continuation Passing Style))。12import { cps } from 'redux-saga'const content = yield cps(readFile, '/path/to/file') put同样的,如果我们想在获取到异步数据之后派发一个action也不能直接dispatch({ type: 'PRODUCTS_RECEIVED', products })而是要用yield put({ type: 'PRODUCTS_RECEIVED', products })这样声明式的调用以便测试。 错误处理我们可以使用熟悉的 try/catch 语法在 Saga 中捕获错误。1234567891011import Api from './path/to/api'import { call, put } from 'redux-saga/effects'function* fetchProducts() { try { const products = yield call(Api.fetch, '/products') yield put({ type: 'PRODUCTS_RECEIVED', products }) } catch(error) { yield put({ type: 'PRODUCTS_REQUEST_FAILED', error }) }} 当然了,你并不一定得在 try/catch 区块中处理错误,你也可以让你的 API 服务返回一个正常的含有错误标识的值。例如, 你可以捕捉 Promise 的拒绝操作,并将它们映射到一个错误字段对象。1234567891011121314import Api from './path/to/api'import { take, put } from 'redux-saga/effects'function fetchProductsApi() { return Api.fetch('/products') .then(response => {response}) .catch(error => {error})}function* fetchProducts() { const { response, error } = yield call(fetchProductsApi) if(response) yield put({ type: 'PRODUCTS_RECEIVED', products: response }) else yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })} 高级(官方中文文档)监听未来的action之前我们有用takeEvery来对每一个相同的action进行无差别对待,但其实我们也有take可以对每一次的action进行细微调控。比如说,我们可以在观察到用户完成了3个任务之后,派发一个向用户表示祝贺的action。 同时执行多个任务yield 指令可以很简单的将异步控制流以同步的写法表现出来,但与此同时我们将也会需要同时执行多个任务,我们不能直接这样写:12345678910// 错误写法,effects 将按照顺序执行const users = yield call(fetch, '/users'), repos = yield call(fetch, '/repos') import { call } from 'redux-saga/effects'// 正确写法, effects 将会同步执行const [users, repos] = yield [ call(fetch, '/users'), call(fetch, '/repos')] 当我们需要 yield 一个包含 effects 的数组, generator 会被阻塞直到所有的 effects 都执行完毕,或者当一个 effect 被拒绝 (就像 Promise.all 的行为)。 同时启动多个任务,择其优者取值有时候我们同时启动多个任务,但又不想等待所有任务完成,我们只希望拿到 胜利者:即第一个被 resolve(或 reject)的任务。 race Effect 提供了一个方法,在多个 Effects 之间触发一个竞赛(race)。下面的示例演示了触发一个远程的获取请求,并且限制了 1 秒内响应,否则作超时处理。1234567891011import { race, take, put } from 'redux-saga/effects'function* fetchPostsWithTimeout() { const {posts, timeout} = yield race({ posts : call(fetchApi, '/posts'), timeout : call(delay, 1000) }) if(posts) put({type: 'POSTS_RECEIVED', posts}) else put({type: 'TIMEOUT_ERROR'})} race 的另一个有用的功能是,它会自动取消那些失败的 Effects。具体实例参看官方文档]]></content>
<categories>
<category>前端</category>
<category>react</category>
</categories>
<tags>
<tag>js</tag>
<tag>redux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[immutable在redux中的应用]]></title>
<url>%2F2017%2F07%2F29%2FJS-react-immutable%E5%9C%A8react%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%2F</url>
<content type="text"><![CDATA[immutable类型数据对于状态庞大的系统而言是非常有用的一种数据类型,它对于追踪应用状态、减少不必要的试图渲染有着非同寻常的意义… 一览整合各个子的reducerredux官方提供的combineReducers只支持原生JS的形式,所以这里要用redux-immutable提供的combineReducers来代替。1234567import {combineReducers} from 'redux-immutable'import dish from './dish'import menu from './menu'import cart from './cart'// 整合各个子的reducerconst rootReducer = combineReducers({dish, menu, cart})export default rootReducer reducer中的initialState肯定也需要初始化成immutable类型12345678// Map里面传的是一个原生JS对象,初始化reducer中的initialState时建议用Map方法而不是fromJS方法,效率更高const initialState = Immutable.Map({})export default function menu(state = initialState, action) { switch (action.type) { case SET_ERROR: return state.set('isError', true) }} 将immutable的数据映射到组件的props中state成为了immutable类型,所以mapStateToProps也要改写成immutable的取值方法。123456function mapStateToProps(state) { return { menuList: state.getIn(['dish', 'list']), //使用get或者getIn来获取state中的变量 CartList: state.getIn(['dish', 'cartList']) }} immutable数据类型检验这就需要我们 import 专门针对immutable类型进行校验的库:react-immutable-proptypes,使用方法基本上和普通的PropTypes一致:12345678910111213propTypes: { oldListTypeChecker: React.PropTypes.instanceOf(Immutable.List), anotherWay: ImmutablePropTypes.list, requiredList: ImmutablePropTypes.list.isRequired, mapsToo: ImmutablePropTypes.map, evenIterable: ImmutablePropTypes.iterable}// 与此同时,产生defaultProps的地方应该为:fromJS({ prop1: xxx, prop2: xxx, prop3: xxx}).toObject() 装饰shouldComponentUpdate减少页面无意义渲染的次数是immutable提升react效率的重中之重。12345678910111213import pureRender from "pure-render-immutable-decorator"// @pureRender会帮你实现shouldComponentUpdate。如果在这个钩子中还有自己的逻辑的话,请参看官方文档@pureRenderclass List extends Component { constructor(props, context) { super(props, context) } render() { return ( <div> </div> ) }} 上面用到decorator,在js的babel loader里面,新增plugins: [‘transform-decorators-legacy’]。这个模块下面还有一个不用immutable的可以对原生JS对象进行深比较的模块(pure-render-deepCompare-decorator)。 进阶immutable.js使用过程中的一些注意点 fromJS和toJS会深度转换数据,随之带来的开销较大,尽可能避免使用,单层数据转换使用Map()和List() js是弱类型,但Map类型的key必须是string!(也就是我们取值是要用get(‘1’)而不是get(1)) 所有针对immutable变量的增删改必须左边有赋值,因为所有操作都不会改变原来的值,只是生成一个新的变量 获取深层深套对象的值时不需要做每一层级的判空(JS中如果不判空会报错,immutable中只会给undefined) immutable对象直接可以转JSON.stringify(),不需要显式手动调用toJS()转原生 判断对象是否是空可以直接用size 调试过程中要看一个immutable变量中真实的值,可以chrome中加断点,在console中使用.toJS()方法来查看 高阶组件封装对于使用immutable.js的项目,在应用公共组件的时候,由于公共组件的内部实现一定是原生JS数据,所以我们只能传递原生JS数据到公共组件,但是如果转换成了原生JS数据,就又会出现”React.addons.PureRenderMixin提供的shouldComponentUpdate()是浅比较”问题,对此可以使用下面的高阶组件进行封装。12345678910111213141516171819202122232425262728/* 定义高阶组件 */import {React} from 'base'// 通过Immutable.is 封装过的 shouldComponentUpdateimport {shouldComponentUpdate} from '../immutable-pure-render-decorator'export default ComposedComponent => { return class extends React.Component { constructor(props) { super(props); this.shouldComponentUpdate = shouldComponentUpdate.bind(this) } render() { const props = this.props.toJS ? this.props.toJS() : this.props return <ComposedComponent { ...this.props} { ...props} /> } }}/* 使用高阶组件 */import highComponent from '../../../../widgets/libs/utils/highComponent'// 公共组件import Dialog from '@alife/dialog'function mapStateToProps(state) {}function mapDispatchToProps(dispatch) {}// 通过高阶组件封装export default connect(mapStateToProps, mapDispatchToProps)(highComponent(Dialog)) immutable常用API12345678910111213141516171819202122232425262728293031323334//Map() 原生object转Map对象 (只会转换第一层,注意和fromJS区别)immutable.Map({name:'danny', age:18})//List() 原生array转List对象 (只会转换第一层,注意和fromJS区别)immutable.List([1,2,3,4,5])//fromJS() 原生js转immutable对象 (深度转换,会将内部嵌套的对象和数组全部转成immutable)immutable.fromJS([1,2,3,4,5]) //将原生array --> Listimmutable.fromJS({name:'danny', age:18}) //将原生object --> Map//toJS() immutable对象转原生js (深度转换,会将内部嵌套的Map和List全部转换成原生js)immutableData.toJS();//查看List或者map大小 immutableData.size 或者 immutableData.count()// is() 判断两个immutable对象是否相等immutable.is(imA, imB);//merge() 对象合并var imA = immutable.fromJS({a:1,b:2});var imA = immutable.fromJS({c:3});var imC = imA.merge(imB);console.log(imC.toJS()) //{a:1,b:2,c:3}//增删改查(所有操作都会返回新的值,不会修改原来值)var immutableData = immutable.fromJS({ a:1, b:2, c:{ d:3 }});var data1 = immutableData.get('a') // data1 = 1 var data2 = immutableData.getIn(['c', 'd']) // data2 = 3 getIn用于深层结构访问var data3 = immutableData.set('a' , 2); // data3中的 a = 2var data4 = immutableData.setIn(['c', 'd'], 4); //data4中的 d = 4var data5 = immutableData.update('a',function(x){return x+4}) //data5中的 a = 5var data6 = immutableData.updateIn(['c', 'd'],function(x){return x+4}) //data6中的 d = 7var data7 = immutableData.delete('a') //data7中的 a 不存在var data8 = immutableData.deleteIn(['c', 'd']) //data8中的 d 不存在 参考资料如何用React+Redux+ImmutableJS进行SPA开发React移动web极致优化immutable.js 在React、Redux中的实践以及常用API简介Immutable.js 以及在 react+redux 项目中的实践]]></content>
<categories>
<category>前端</category>
<category>react</category>
</categories>
<tags>
<tag>js</tag>
<tag>react</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS实现继承的几种方式]]></title>
<url>%2F2017%2F07%2F28%2FJS-the%20way%20to%20inherit%2F</url>
<content type="text"><![CDATA[多年以来,JS开发者一直期待JS能够实现像java那样的继承… 原型链继承原型链继承基本思想就是让一个原型对象指向另一个类型的实例。1234567891011121314151617181920212223242526272829303132333435363738394041424344// 父类构造函数function SuperType() { this.prop = true}// 父类原型对象SuperType.prototype.getSuperValue = function () { return this.prop}// 子类构造函数function SubType() { this.subprop = false}// 实现继承SubType.prototype = new SuperType()// 子类原型对象的自有方法SubType.prototype.getSubValue = function () { return this.subprop}// 创建子类实例var instance = new SubType()console.log(instance.getSuperValue()) // true``` 小蝌蚪找妈妈:子类实例-->子类构造函数-->子类原型对象-->父类实例-->父类构造函数-->父类原型对象。 注意点:给子类原型对象添加自有方法时只能用打点的方式添加,而不能用对象字面亮的方式添加。不然会切断子类原型与父类实例的联系,也就不能认贼做父了。 缺陷:所有子类实例都将共享父类中的属性,而当这个属性值是引用类型值时,一处改变将影响每一处。## 构造函数继承此方法为了解决原型中包含引用类型值所带来的问题。 这种方法的思想就是在子类构造函数的内部调用父类构造函数,可以借助apply()和call()方法来改变对象的执行上下文。 ```jsfunction SuperType() { this.colors = ['red', 'blue', 'green']}function SubType() { // 继承SuperType SuperType.call(this)}var instance1 = new SubType()var instance2 = new SubType()instance1.colors.push('black')console.log(instance1.colors) // ["red", "blue", "green", "black"]console.log(instance2.colors) // ["red", "blue", "green"] 缺陷:如果仅仅借助构造函数,方法都在构造函数中定义,因此函数无法达到复用。 组合继承(原型链+构造函数)使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。12345678910111213141516171819202122232425262728293031323334// 父类function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green']}// 父类原型对象SuperType.prototype.sayName = function () { console.log(this.name)}// 子类function SubType(name, job) { // 继承属性 SuperType.call(this, name) // 子有属性 this.job = job}/* 实现继承 */// 子类原型与父类实例勾搭SubType.prototype = new SuperType()// 子类原型的构造函数与父类构造函数勾搭SubType.prototype.constructor = SuperType// 子类原型的自有方法SubType.prototype.sayJob = function() { console.log(this.job)}var instance1 = new SubType('Jiang', 'student')instance1.colors.push('black')console.log(instance1.colors) //["red", "blue", "green", "black"]instance1.sayName() // 'Jiang'instance1.sayJob() // 'student'var instance2 = new SubType('J', 'doctor')console.log(instance2.colors) // //["red", "blue", "green"]instance2.sayName() // 'J'instance2.sayJob() // 'doctor' 寄生继承的函数封装12345function inheritPrototype(subType, superType) { var prototype = Object.create(superType.prototype) prototype.constructor = subType subType.prototype = prototype} 该函数实现了寄生组合继承的最简单形式。这个函数接受两个参数,一个子类,一个父类。第一步创建父类原型的副本,第二步将创建的副本添加constructor属性,第三部将子类的原型指向这个副本。 ES6的 Object.setPrototypeOf 方法实现继承123// 继承Object.setPrototypeOf(SubType.prototype, SuperType.prototype)console.log(SubType.prototype.constructor === SubType) // true]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>js</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS常用方法(三)]]></title>
<url>%2F2017%2F07%2F27%2FJS-common%20function(3)%2F</url>
<content type="text"><![CDATA[整理一下从别处看到的js常用函数参考(dom操作)… 基础函数判断JS数据类型12345678910111213141516171819202122232425262728function istype(o, type) { //全部小写 var _type = type.toLowerCase(); switch (_type) { case 'string': return Object.prototype.toString.call(o) === '[object String]'; case 'number': return Object.prototype.toString.call(o) === '[object Number]'; case 'boolean': return Object.prototype.toString.call(o) === '[object Boolean]'; case 'undefined': return Object.prototype.toString.call(o) === '[object Undefined]'; case 'null': return Object.prototype.toString.call(o) === '[object Null]'; case 'function': return Object.prototype.toString.call(o) === '[object Function]'; case 'array': return Object.prototype.toString.call(o) === '[object Array]'; case 'object': return Object.prototype.toString.call(o) === '[object Object]'; case 'nan': return isNaN(o); case 'elements': return Object.prototype.toString.call(o).indexOf('HTML') !== -1 default: return Object.prototype.toString.call(o) }} 基础DOM操作检测对象是否有某个类名12345678function hasClass(obj, classStr) { if (obj.className && this.trim(obj.className, 1) !== "") { var arr = obj.className.split(/\s+/); //这个正则表达式是因为class可以有多个,判断是否包含 return (arr.indexOf(classStr) == -1) ? false : true; } else { return false; }} 添加类名12345678910111213function addClass(obj, classStr) { if ((this.istype(obj, 'array') || this.istype(obj, 'elements')) && obj.length >= 1) { for (var i = 0, len = obj.length; i < len; i++) { if (!this.hasClass(obj[i], classStr)) { obj[i].className += " " + classStr; } } } else { if (!this.hasClass(obj, classStr)) { obj.className += " " + classStr; } }} 删除类名123456789101112131415function removeClass(obj, classStr) { if ((this.istype(obj, 'array') || this.istype(obj, 'elements')) && obj.length > 1) { for (var i = 0, len = obj.length; i < len; i++) { if (this.hasClass(obj[i], classStr)) { var reg = new RegExp('(\\s|^)' + classStr + '(\\s|$)'); obj[i].className = obj[i].className.replace(reg, ''); } } } else { if (this.hasClass(obj, classStr)) { var reg = new RegExp('(\\s|^)' + classStr + '(\\s|$)'); obj.className = obj.className.replace(reg, ''); } }} 替换类名(“被替换的类名”,”替换的类名”)1234function replaceClass(obj, newName, oldName) { removeClass(obj, oldName); addClass(obj, newName);} 设置样式12345function css(obj, json) { for (var attr in json) { obj.style[attr] = json[attr]; }} 其他操作cookie相关12345678910111213141516171819202122// cookie// 设置cookiefunction setCookie(name, value, iDay) { var oDate = new Date(); oDate.setDate(oDate.getDate() + iDay); document.cookie = name + '=' + value + ';expires=' + oDate;}// 获取cookiefunction getCookie(name) { var arr = document.cookie.split('; '); for (var i = 0; i < arr.length; i++) { var arr2 = arr[i].split('='); if (arr2[0] == name) { return arr2[1]; } } return '';}// 删除cookiefunction removeCookie(name) { setCookie(name, 1, -1);} 清除对象中值为空的属性1234567891011// filterParams({a:"",b:null,c:"010",d:123})// Object {c: "010", d: 123}function filterParams(obj) { let _newPar = {}; for (let key in obj) { if ((obj[key] === 0 || obj[key]) && obj[key].toString().replace(/(^\s*)|(\s*$)/g, '') !== '') { _newPar[key] = obj[key]; } } return _newPar;} 现金额大写转换函数123456789101112131415161718192021222324252627282930// upDigit(1682)// "人民币壹仟陆佰捌拾贰元整"// upDigit(-1693)// "欠人民币壹仟陆佰玖拾叁元整"function upDigit(n) { var fraction = ['角', '分', '厘']; var digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; var unit = [ ['元', '万', '亿'], ['', '拾', '佰', '仟'] ]; var head = n < 0 ? '欠人民币' : '人民币'; n = Math.abs(n); var s = ''; for (var i = 0; i < fraction.length; i++) { s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, ''); } s = s || '整'; n = Math.floor(n); for (var i = 0; i < unit[0].length && n > 0; i++) { var p = ''; for (var j = 0; j < unit[1].length && n > 0; j++) { p = digit[n % 10] + unit[1][j] + p; n = Math.floor(n / 10); } s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s; //s = p + unit[0][i] + s; } return head + s.replace(/(零.)*零元/, '元').replace(/(零.)+/g, '零').replace(/^整$/, '零元整');} 获取,设置url参数1234567891011121314151617181920212223242526272829303132// 获取url参数// getUrlPrmt('segmentfault.com/write?draftId=122000011938')// Object{draftId: "122000011938"}function getUrlPrmt(url) { url = url ? url : window.location.href; let _pa = url.substring(url.indexOf('?') + 1), _arrS = _pa.split('&'), _rs = {}; for (let i = 0, _len = _arrS.length; i < _len; i++) { let pos = _arrS[i].indexOf('='); if (pos == -1) { continue; } let name = _arrS[i].substring(0, pos), value = window.decodeURIComponent(_arrS[i].substring(pos + 1)); _rs[name] = value; } return _rs;}// 设置url参数// setUrlPrmt({'a':1,'b':2})// a=1&b=2function setUrlPrmt(obj) { let _rs = []; for (let p in obj) { if (obj[p] != null && obj[p] != '') { _rs.push(p + '=' + obj[p]) } } return _rs.join('&');} 随机码123function randomWord(count) { return Math.random().toString(count).substring(2);} 随机返回一个范围的数字1234567891011121314151617function randomNumber(n1, n2) { // randomNumber(5,10) // 返回5-10的随机整数,包括5,10 if (arguments.length === 2) { return Math.round(n1 + Math.random() * (n2 - n1)); } // randomNumber(10) // 返回0-10的随机整数,包括0,10 else if (arguments.length === 1) { return Math.round(Math.random() * n1) } // randomNumber() // 返回0-255的随机整数,包括0,255 else { return Math.round(Math.random() * 255) }} 随机产生颜色12345678910111213function randomColor() { // randomNumber是上面定义的函数 // 写法1 return 'rgb(' + randomNumber(255) + ',' + randomNumber(255) + ',' + randomNumber(255) + ')'; // 写法2 return '#' + Math.random().toString(16).substring(2).substr(0, 6); // 写法3 var color = '#'; for (var i = 0; i < 6; i++) { color += '0123456789abcdef' [randomNumber(15)]; } return color;} 到某个时间的倒计时12345678910111213141516171819// 到某一个时间的倒计时// getEndTime('2017/7/22 16:0:0')// "剩余时间6天 2小时 28 分钟20 秒"function getEndTime(endTime) { var startDate = new Date(); //开始时间,当前时间 var endDate = new Date(endTime); //结束时间,需传入时间参数 var t = endDate.getTime() - startDate.getTime(); //时间差的毫秒数 var d = 0, h = 0, m = 0, s = 0; if (t >= 0) { d = Math.floor(t / 1000 / 3600 / 24); h = Math.floor(t / 1000 / 60 / 60 % 24); m = Math.floor(t / 1000 / 60 % 60); s = Math.floor(t / 1000 % 60); } return "剩余时间" + d + "天 " + h + "小时 " + m + " 分钟" + s + " 秒";} 适配rem123456789101112131415161718192021222324252627function getFontSize() { var doc = document, win = window; var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function() { var clientWidth = docEl.clientWidth; if (!clientWidth) return; // 如果屏幕大于750(750是根据我效果图设置的,具体数值参考效果图),就设置clientWidth=750,防止font-size会超过100px if (clientWidth > 750) { clientWidth = 750 } // 设置根元素font-size大小 docEl.style.fontSize = 100 * (clientWidth / 750) + 'px'; }; // 屏幕大小改变,或者横竖屏切换时,触发函数 win.addEventListener(resizeEvt, recalc, false); // 文档加载完成时,触发函数 doc.addEventListener('DOMContentLoaded', recalc, false);}// 使用方式很简单,比如效果图上,有张图片。宽高都是100px;// 样式写法就是img { width: 1 rem; height: 1 rem;}// 这样的设置,比如在屏幕宽度大于等于750px设备上,1rem=100px;图片显示就是宽高都是100px// 比如在iphone6(屏幕宽度:375)上,375/750*100=50px;就是1rem=50px;图片显示就是宽高都是50px;]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>常用函数</category>
</categories>
<tags>
<tag>js</tag>
<tag>常用方法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS常用方法(二)]]></title>
<url>%2F2017%2F07%2F27%2FJS-common%20function(2)%2F</url>
<content type="text"><![CDATA[整理一下从别处看到的js常用函数参考… 字符串的相关操作去除字符串空格12345678910//去除空格 type 1-所有空格 2-前后空格 3-前空格 4-后空格function trim(str,type){ switch (type){ case 1:return str.replace(/\s+/g,""); case 2:return str.replace(/(^\s*)|(\s*$)/g, ""); case 3:return str.replace(/(^\s*)/g, ""); case 4:return str.replace(/(\s*$)/g, ""); default:return str; }} 字母大小写切换123456789101112131415161718192021222324252627282930313233343536373839404142/*type1:首字母大写 2:首页母小写3:大小写转换4:全部大写5:全部小写 **/function changeCase(str, type) { function ToggleCase(str) { var itemText = "" str.split("").forEach( function(item) { if (/^([a-z]+)/.test(item)) { itemText += item.toUpperCase(); } else if (/^([A-Z]+)/.test(item)) { itemText += item.toLowerCase(); } else { itemText += item; } }); return itemText; } switch (type) { case 1: return str.replace(/\b\w+\b/g, function(word) { return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase(); }); case 2: return str.replace(/\b\w+\b/g, function(word) { return word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase(); }); case 3: return ToggleCase(str); case 4: return str.toUpperCase(); case 5: return str.toLowerCase(); default: return str; }} 字符串替换12345// 字符串替换(字符串,要替换的字符,替换成什么)function replaceAll(str, AFindText, ARepText) { raRegExp = new RegExp(AFindText, "g"); return str.replace(raRegExp, ARepText);} 将某些特定字符替换为’*’12345678910111213141516171819202122232425262728293031323334353637383940// replaceStr(字符串,字符格式, 替换方式,替换的字符(默认*))function replaceStr(str, regArr, type, ARepText) { var regtext = '', Reg = null, replaceText = ARepText || '*'; // replaceStr('18819322663',[3,5,3],0) // 188*****663 // repeatStr是在上面定义过的(字符串循环复制),大家注意哦 if (regArr.length === 3 && type === 0) { regtext = '(\\w{' + regArr[0] + '})\\w{' + regArr[1] + '}(\\w{' + regArr[2] + '})' Reg = new RegExp(regtext); var replaceCount = repeatStr(replaceText, regArr[1]); return str.replace(Reg, '$1' + replaceCount + '$2') } // replaceStr('asdasdasdaa',[3,5,3],1) // ***asdas*** else if (regArr.length === 3 && type === 1) { regtext = '\\w{' + regArr[0] + '}(\\w{' + regArr[1] + '})\\w{' + regArr[2] + '}' Reg = new RegExp(regtext); var replaceCount1 = repeatSte(replaceText, regArr[0]); var replaceCount2 = repeatSte(replaceText, regArr[2]); return str.replace(Reg, replaceCount1 + '$1' + replaceCount2) } // replaceStr('1asd88465asdwqe3',[5],0) // *****8465asdwqe3 else if (regArr.length === 1 && type == 0) { regtext = '(^\\w{' + regArr[0] + '})' Reg = new RegExp(regtext); var replaceCount = repeatSte(replaceText, regArr[0]); return str.replace(Reg, replaceCount) } // replaceStr('1asd88465asdwqe3',[5],1,'+') // "1asd88465as+++++" else if (regArr.length === 1 && type == 1) { regtext = '(\\w{' + regArr[0] + '}$)' Reg = new RegExp(regtext); var replaceCount = repeatSte(replaceText, regArr[0]); return str.replace(Reg, replaceCount) }} 检测字符串12345678910111213141516171819202122232425// checkType('165226226326','phone')// false// 大家可以根据需要扩展function checkType(str, type) { switch (type) { case 'email': return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str); case 'phone': return /^1[3|4|5|7|8][0-9]{9}$/.test(str); case 'tel': return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str); case 'number': return /^[0-9]$/.test(str); case 'english': return /^[a-zA-Z]+$/.test(str); case 'chinese': return /^[\u4E00-\u9FA5]+$/.test(str); case 'lower': return /^[a-z]+$/.test(str); case 'upper': return /^[A-Z]+$/.test(str); default: return true; }} 检测密码强度123456789101112131415161718192021// checkPwd('12asdASAD')// 3(强度等级为3)function checkPwd(str) { var nowLv = 0; if (str.length < 6) { return nowLv }; if (/[0-9]/.test(str)) { nowLv++ }; if (/[a-z]/.test(str)) { nowLv++ }; if (/[A-Z]/.test(str)) { nowLv++ }; if (/[\.|-|_]/.test(str)) { nowLv++ }; return nowLv;} 生成随机码12345678910111213// count取值范围0-36// randomNumber(10)// "2584316588472575"// randomNumber(14)// "9b405070dd00122640c192caab84537"// Math.random().toString(36).substring(2);// "83vhdx10rmjkyb9"function randomNumber(count){ return Math.random().toString(count).substring(2);} 查找特定字符串出现的次数123456function countStr (str,strSplit){ return str.split(strSplit).length-1}var strTest = 'sad44654blog5a1sd67as9dablog4s5d16zxc4sdweasjkblogwqepaskdkblogahseiuadbhjcibloguyeajzxkcabloguyiwezxc967'// countStr(strTest,'blog')// 6 数组操作数组顺序打乱123function upsetArr(arr){ return arr.sort(function(){ return Math.random() - 0.5});} 数组最大值最小值1234567// 这一块的封装,主要是针对数字类型的数组function maxArr(arr){ return Math.max.apply(null,arr);}function minArr(arr){ return Math.min.apply(null,arr);} 数组求和,平均值123456789101112131415// 这一块的封装,主要是针对数字类型的数组// 求和function sumArr(arr) { var sumText = 0; for (var i = 0, len = arr.length; i < len; i++) { sumText += arr[i]; } return sumText}// 平均值,小数点可能会有很多位,这里不做处理,处理了使用就不灵活了!function covArr(arr) { var sumText = sumArr(arr); var covText = sumText / length; return covText} 从数组中随机获取元素123function randomOne(arr) { return arr[Math.floor(Math.random() * arr.length)];} 返回数组(字符串)一个元素出现的次数12345678910111213// getEleCount('asd56+asdasdwqe','a')// 3// getEleCount([1,2,3,4,5,66,77,22,55,22],22)// 2function getEleCount(obj, ele) { var num = 0; for (var i = 0, len = obj.length; i < len; i++) { if (ele == obj[i]) { num++; } } return num;} 返回数组(字符串)出现最多的几次元素和出现次数12345678910111213141516171819202122232425262728293031// arr, rank->长度,默认为数组长度,ranktype,排序方式,默认降序function getCount(arr, rank, ranktype) { var obj = {}, k, arr1 = [] // 记录每一元素出现的次数 for (var i = 0, len = arr.length; i < len; i++) { k = arr[i]; if (obj[k]) { obj[k]++; } else { obj[k] = 1; } } // 保存结果{el-'元素',count-出现次数} for (var o in obj) { arr1.push({ el: o, count: obj[o] }); } // 排序(降序) arr1.sort(function(n1, n2) { return n2.count - n1.count }); // 如果ranktype为1,则为升序,反转数组 if (ranktype === 1) { arr1 = arr1.reverse(); } var rank1 = rank || arr1.length; return arr1.slice(0, rank1);}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>常用函数</category>
</categories>
<tags>
<tag>js</tag>
<tag>常用方法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[webpack2优化]]></title>
<url>%2F2017%2F07%2F26%2Fnode-better%20webpack%2F</url>
<content type="text"><![CDATA[webpack是现代前端开发中最常用的的构建打包工具之一,或许连“之一”都可以去掉。它的构建打包速度将直接影响我们的开发效率… 优化输出压缩csscss-loader在webpack2里默认是没有开启压缩的,最后生成的css文件里面有很多空格。通过配置css-loader?minimize参数可以开启压缩输出最小的css。css的压缩实际是通过cssnano实现的。 tree-shakingtree-shaking是指借助es6 import export语法静态性的特点来删掉export但是没有import过的东西。让tree-shaking工作需要注意以下几点: 配置babel让它在编译转化ES6代码时不把ES6的模块语法转化成commonJS的模块语法 12345678"preset": [ [ "es2015", { "modules": false } ]] 大多数npm包的代码是ES5的,但是也有一部分库(redux,react-router等等)开始支持tree-shaking。这些库既包含ES5又包含ES6模块化语法。如redux库中的package.json中会有这两个配置: 12"main": "lib/index.js","jsnext:main": "es/index.js" 我们需要让webpack去到es目录读取代码。为此,我们需要这样配置:12345module.exports = { resolve: { mainFields: ['jsnext:main','main'], }} 这样可以让webpack先使用jsnext:main查找,在没有时使用main指定的路径。 优化UglifyJsPluginwebpack –optimize-minimize选项会开启UglifyJsPlugin来压缩输出的js,但默认的UglifyJsPlugin的配置还是保留了注释和空格,要覆盖默认配置,要这样写:12345678910111213141516new UglifyJsPlugin({ // 最紧凑输出 beautify: fase, // 删除注释 comments: fase, compress: { // 在UglifyJs删除没有用到代码是不输出警告 warning: false, // 删除所有‘console’语句 drop_console: true, // 内嵌定义了但是只用到一次的变量 collapse_vars: true, // 提取出现多次但是没有定义成变量去引用的静态值 reduce_vars: true }}) 定义环境变量NODE_ENV=production很多库(比如react)有部分代码是这样的:123if(process.env.NODE_ENV!=='production') { // 开发环境才需要用到的代码} 在环境变量NODE_ENV等于production的时候UglifyJs会删掉这块代码。 使用CommonsChunkPlugin抽取公共代码CommonsChunkPlugin可以提取出多个代码快都依赖的模块形成一个单独的模块。要发挥CommonsChunkPlugin的威力,还要配合浏览器的缓存机制。 生产环境按照文件内容md5打hashwebpack编译在生产环境出来的js、css等资源应该放到CDN上,再根据文件内容的md5命名文件,利用缓存机制,用户只需加载第一次。如此配置便好:123456{ output: { publicPath: CDN_URL, filename: '[name]_[chunkhash].js', }} 配合CommonsChunkPlugin还可以这样玩:123456789import 'react'import 'react-dom'// .../* webpack配置 */{ entry: { vendor: './path/to/vendor.js', }} 更快的构建缩小文件搜索范围 排除node_modules文件夹 12345module.exports = { resolve: { modules: [path.resolve(__dirname,'node_modules')] }} 简化正则表达式(当项目只有js文件是就不要写成/.jsx?$/) 只对项目目录下的代码进行babel编译12345{ test: /\.js$/, loader: 'babel-loader', include: path.resolve(__dirname,'src')} 开启babel-loader缓存babel编译过程很耗时,好在babel-loader提供缓存编译结果选项,在重启webpack时,不需要重新编译而是复用缓存结果。打开babel-loader缓存配置如下:12345678module.exports = { module: { loaders: [{ test: /.js$/, loader: 'babel-loader?cacheDirectory', }] }} 使用noParsemodule.noParse可以配置哪些文件脱离webpack的解析(如jquery等)。12345module.exports = { module: { noParse: /node_modules\/(jquery|chart)\.js/ }} 其他 happypack DllPlugin]]></content>
<categories>
<category>前端</category>
<category>webpack</category>
</categories>
<tags>
<tag>webpack</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS3样式书写规范]]></title>
<url>%2F2017%2F07%2F26%2FCSS-writing%20criterion%2F</url>
<content type="text"><![CDATA[css一直是一门被人诟病的语言。有人说它甚至不能算是一门语言。正因如此,规范的css才更显得至关重要… 编码设置采用UTF-8编码,在css代码头部使用:1@charset "UTF-8" 注意:必须要定义在CSS文件所有的字符前面(包括注释),编码申明才会生效。 命名空间规范 布局:以g为命名空间。例如:g-wrap、g-header、g-content 状态:以s为命名空间,表示动态的、具有交互性质的状态。例如:s-current、s-selected 工具:以u为命名空间,表示不耦合业务逻辑的、可复用的工具。例如:u-clearfix、u-ellipsis 组件:以m为命名空间,表示可复用、移植的组件模块。例如:j-request、j-open 样式属性顺序按照功能来对属性进行分组:position model –> box model –> typographic –> visual。 如果包含content属性,应放在最前面 position model布局方式、位置。相关属性:position、top、z-index、display、float box model盒模型。相关属性:width、padding、margin、border、overflow typographic文本排版。相关属性:font、line-height、text-align、word-wrap visual视觉外观。相关属性:color、background、list-style、transform、animation css方言的使用建议嵌套层级规定嵌套层级不建议超过3层。 共用类的使用方法共用类使用%xxx定义,@extend引用,如:1234567%clearfix { overflow: auto; zoom: 1;}.g-header { @extend %clearfix;} 这样编译出来的文件中会将公共样式放到一个类中,不会造成冗余代码。]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
</categories>
<tags>
<tag>CSS</tag>
<tag>规范</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mysql基本操作]]></title>
<url>%2F2017%2F07%2F19%2Fdatabase-mysql%2F</url>
<content type="text"><![CDATA[直到闯到数据库这一关,才堪堪有一种任督二脉初通的感觉。当然,数据库的话题博大精深,这里只是最基本的操作… 准备工作 安装mysql客户端 安装时选择server only就行(我们只需要它的服务端) 展示高级选项,设置密码 安装Navicat for MYSQL客户端 用navicat连接mysql(使用localhost就行) 基本SQL语句增INSERT INTO 表 (字段列表) VALUES (值列表)INSERT INTO user_table (ID, username, password) VALUES(0, ‘ye’, ‘741258’) 删DELETE FROM 表 WHERE 条件 改UPTATE 表 SET 字段=值,字段=值 WHERE ID=xxUPDATE user_table SET password=123 WHERE ID=xx 查SELECT 什么 FROM 表SELECT * FROM user_table [WHERE ID=XX] // 中括号中的条件是可选的SELECT ID,username FROM user_table // 指定要查询的特定字段 SQL子句WHERE 条件WHERE name=’xuan’ // <、>、<=、>=..WHERE age>18 AND score<60 // 还有OR ORDER 排序ORDER BY age ASC/DESC // ASC升序、DESC降序ORDER BY price ASC, sales DESC // 首先按价格升序排,价格相同就按销量降序排 GROUP 聚类(合并相同的,只保留第一条)COUNT 计数SELECT * FROM student_table GROUP BY class // 班级相同的数据只保留第一条(一般来说不会单纯地用他来去重)SELECT class,COUNT(class) FROM student_table GROUP BY class // 只保留班级字段,以及每个班级多少人SELECT class,AVG(score) FROM student_table GROUP BY class // 拿到每个班的平均分(后面还可以加order by AVG(score) desc) LIMIT 限制输出LIMIT 起点, 数量 // 含头的LIMIT (n-1) * 每页条数, 每页条数 子句的顺序WHERE GROUP ORDER LIMIT // 筛选 合并 排序 限制 函数 COUNT MIN MAX AVG SUM使用Navicat客户端备份恢复数据库备份:右键要备份的数据库或表,选择转储SQL文件。恢复:备份的SQL文件不会帮你创建数据库,所以你需要自己创建数据库,然后右键数据库名。]]></content>
<categories>
<category>后台</category>
<category>数据库</category>
</categories>
<tags>
<tag>database</tag>
</tags>
</entry>
<entry>
<title><![CDATA[express]]></title>
<url>%2F2017%2F07%2F19%2Fnodejs-express%2F</url>
<content type="text"><![CDATA[曾经听说过这样一个说法:jQuery之于JS就好似express之于nodejs。如此这般,大家应该知道express的江湖地位了… 在我看来,express最亮眼的便是它的中间件。通过各种中间件,我们对服务中最重要的两个参数req、res进行加工,加工过后的req、res就有我们经常需要用到的数据以及更加好用的方法。 一览12345678910111213141516171819202122232425262728293031const express = require('express')const cookieParser = require('cookie-parser')const cookieSession = require('cookie-session')const bodyParser = require('body-parser')const multer = require('multer')const consolidate = require('consolidate')var server = express()server.listen(4001)// 1. 解析cookieserver.use(cookieParser('nsdojgf45642norjes'))// 2. 使用sessionserver.use(cookieSession({ name: 'xuan_sess', keys: ['shd','dsr156er','nherisg','bdsrgfij'], maxAge: 1000*3600*20}))// 3. post数据server.use(bodyParser.urlencoded({extended: false}))server.use(multer({dest: './www/upload'}).any())// 4. 模板引擎server.set('view engine', 'html') // 要输出什么server.set('views', './views') // 指定模板目录server.engine('html', consolidate.ejs) // 指定引擎// 6. 接口处理server.get('/index', (req, res, next) => { res.render('1.ejs', {name: 'yesixuan'})})// 5. 静态数据server.use(express.static('./www')) 解析数据解析get数据express原生解析出get参数,并将其挂在req的query属性上。12345// ...server.get('/user', (req, res) => {// req.query,直接可以拿到查询参数 console.log(req.query)}) 解析post数据解析post数据需要借助body-parser提供的中间件。它能帮助我们解析出post数据,并将其挂在req的body属性上。12345const bodyParser = require('body-parser')// ...server.use(bodyParser.urlencoded({extended: false})) // 取消扩展模式,免得它老是发出警告// ...console.log(req.body) // post数据 解析上传的二进制数据解析上传的文件需要借助multer模块。它解析出来的数据挂在req.files上。1234567891011121314151617const fs = require('fs')const pathLib = require('path')const multer = require('multer')// ...server.use(multer({dest: './www/upload'}).any()) // 指定文件上传的路径,不让buffer数据大量占用内存// .../* 使用pathLib.parse('路径带文件名')可以解析更多信息,也可以拿到文件后缀名 */var newName = req.files[0].path + pathLib.extname(req.files[0].originalname)// 文件重命名,这里只是改了后缀名fs.rename(req.files[0].path, newName, err => { if(err) res.send('上传失败!') else res.send('ok!')})// ...console.log(req.files) // 对象数组,每个元素包含该文件的各种信息 cookie && sessioncookiecookie是在浏览器保存一些数据,每次请求都会带过来。只能存4k的数据。使用cookie时要注意两点:① 空间小,精打细算;② 使用时要校验cookie是否被篡改。1234567891011const cookieParser = require('cookie-parser')// ... 使用中间件,这里可以添加密钥作为中间件的参数server.use(cookieParser())// ... 写入cookie/* 指定只有在‘/aaa’下才有cookie,保质期为一个月,是否需要签名 */res.cookie(res.cookie('user', 'xuan', {path: '/aaa', maxAge: 30*24*3600*1000, signed: true}))// ... 读取cookies/* 读取,子级目录可以读根级。即树枝可以去寻根。例如/aaa/bbb可以读/aaa下的cookie */console.log(req.cookies)// ... 删除cookieres.clearCookie('user') 给cookie签个名。(签名虽然不能加密数据,但是可以保证数据一旦被篡改,我能够知道)12345// ...server.use(cookieParser('shdgopedfgh')) // 整个密钥res.cookie('user', 'xuan', {signed: true})console.log(req.cookies) // 没有签过名的cookieconsole.log(req.signedCookies) // 签过名的cookie sessioncookie没太多必要加密,真正机密的东西往session中放就好了。一定要用的话,cookie-encrypter。下面上session。1234567891011121314151617181920const cookieSession = require('cookie-session')// ...// 这个要在cookieParser之下server.use(cookieSession({ keys: ['aaa', 'bbb', 'ccc'], // 这个数组越长,越是安全 name: 'sess', // 如果没这个参数,默认就是在cookie中的键名就是session maxAge: 1000*3600*2 // 两小时未操作,自动注销}))// ... 使用sessionserver.use('/', (req, res) => { if(req.session['count'] == null) { req.session['count'] = 1 }else { req.session['count']++ } console.log(req.session['count']) res.send('ok')})// ... 删除sessiondelete req.session // session是存储在服务器上的数据,所以我们可以使用JS原生的方法来删除session 后台模版虽然后台模版渲染是与现代的开发方式背道而驰的,现在大家比较推崇的是后台提供数据,前端获取数据,渲染模版。但是我在使用后台模版的时候,竟然找到了像撸react时相似的感觉。估计很多前端渲染模版的灵感也是来自后台模版吧。 模版渲染1234567// consolidate帮我们整合了各种后台模版,甚至包括reactconst consolidate = require('consolidate')server.set('view engine', 'html') // 要输出什么server.set('views', './templates') // 指定模板目录server.engine('html', consolidate.ejs) // 指定引擎// ... {}里面传入模版中需要的参数res.render('index.ejs', {}) jade语法 属性,使用小括号:img(src=”./xx.jpg”,alt=”xxx”) 内容,空格往标签后面写:a 链接 style属性的对象写法:div(style={width:’200px’,…}) class的数组写法:div(class=[‘aa’,…]) 写class与id可以使用类似emmet写法 标签后面加上&attributes,可以用对象方式写多个属性 在内容前加‘|’,表示原样输出内容(script标签里的多行代码) 在标签后加‘.’,表示里面的内容原样输出 include可以引入一个外部文件,include a.js(引入的内容还是嵌在页面中的) 使用变量:#{变量},变量定义在renderFile(,{},)方法的‘{}’中(还可以写表达式) 可以写两个class属性,jade自会处理好 以‘-’开头的,解析为js。前面一行加了‘-’,下面与它平级或下级的都不用加‘-’ span #{name}与span=name,两者等价 不让变量里面的尖括号被转义,在‘=’前面加‘!’ 原来的switch-case变成case-when结构。 ejs语法 变量:<%= name %> js语法:<% 脚本 %> 引入外部文件的内容:<% include 路径 %> include不是原生js的语法,所以使用include时,要单独起一行用“<% %>”包裹起来 express与数据库连接mysql1234567891011121314151617181920const mysql = require('mysql')// 使用连接池而不是创建连接,以提高性能(会创建多个链接,以保持跟数据库的持久通话)const db = mysql.createPool({ host: 'localhost', user: 'root', password: '123456', database: 'blog' // ... 默认是3306端口的话,就不用在此处配置})// ... 增删改查(查询语句中使用反引号是极好的)db.query('select ID,title,summery from article_table', (err, data) => { if(err) { // 这里的链式操作也是推荐写法 res.status(500).send('database err!').end() }else { // 将文章数据挂到res上,传递到下一层去。 res.articles = data next() }}) 连接mongodb123456789101112131415161718const MongoClient = require('mongodb').MongoClientconst databaseUrl = 'mongodb://localhost:27017/xuan'// ...MongoClient.connect(databaseUrl, (err, db) => { if(err) { res.status(500).send('database err!').end() return } res.send('数据库连接成功!') db.collection('teacher').insert({'name':'Mary'}, (err, result) => { if(err) { res.status(500).send('插入数据失败!').end() return } res.status(200).send('数据插入成功!').end() db.close() })})]]></content>
<categories>
<category>后台</category>
<category>nodejs</category>
</categories>
<tags>
<tag>nodejs</tag>
<tag>express</tag>
</tags>
</entry>
<entry>
<title><![CDATA[原生nodejs创建服务]]></title>
<url>%2F2017%2F07%2F19%2Fnodejs-createServer%2F</url>
<content type="text"><![CDATA[虽然现实开发中不会有人使用原生nodejs提供服务。但原生毕竟是基石,还是有必要温故一下的… 一览最简单的创建服务1234567891011121314151617const http = require('http')const urlLib = require('url')const querystring = require('querystring')http.createServer((req, res) => { // 判断是get还是post请求 if(req.url.indexOf('?') == -1) { var get = querystring(req.url, true) }else { var str = '' req.on('data', data => { str += data }) req.on('end', () => { var post = querystring(str) }) }}).listen(4001) 提供静态资源这只是个简陋的静态资源服务1234567891011121314const http = require('http')const fs = require('fs')let server = http.createServer((req, res) => { let fileName = './www' + req.url fs.readFile(fileName, (err, data) => { if (err) { res.write('404') } else { res.write(data) } // 读文件是异步的,所以end该放在这里 res.end() })}).listen('4001') 常用模块url模块(解析GET参数)1234567const urlLib = require('url')// ...req.on('end', () => { var obj = urlLib.parse(req.url, true) const url = obj.pathname const GET = obj.query // 重要的查询参数}) querystring(解析POST数据)12345678const querystring = require('querystring')// ...req.on('data', data => { str += data})req.on('end', () => { const POST = querystring.parse(str)} 比较完整的服务12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364const http = require('http')const fs = require('fs')const querystring = require('querystring')const urlLib = require('url')var users = {} // 存用户数据http.createServer((req, res) => { // 解析数据 var str = '' req.on('data', data => { str += data }) req.on('end', () => { var obj = urlLib.parse(req.url, true) // console.log(obj) const url = obj.pathname const GET = obj.query const POST = querystring.parse(str) // 区分接口和文件 if (!url) { return } if (url === '/user') { // 接口 switch (GET.act) { case 'reg': // 检查用户是否存在 if (users[GET.user]) { res.write('{"ok":false,"msg":"用户名已存在"}') // 插入数据 } else { users[GET.user] = GET.pass res.write('{"ok":true,"msg":"注册成功"}') } break; case 'login': // 检查用户名是否存在 if (users[GET.name] == null) { res.write('{"ok":false,"msg":"用户不存在"}') // 检查密码是否正确 } else if (users[GET.name] != GET.pass) { res.write('{"ok":false,"msg":"密码有误"}') } else { res.write('{"ok":true,"msg":"登录成功"}') } break; default: res.write('{"ok":false,"msg":"未知的act"}') } res.end() } else { // 当文件来对待 // 读取文件 var file_name = './www' + url fs.readFile(file_name, (err, data) => { if (err) { res.write('404') } else { res.write(data) } res.end() }) } })}).listen(4001)]]></content>
<categories>
<category>后台</category>
<category>nodejs</category>
</categories>
<tags>
<tag>nodejs</tag>
<tag>server</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CommonJs规范以及nodejs的包管理工具]]></title>
<url>%2F2017%2F07%2F19%2Fnodejs-module%2F</url>
<content type="text"><![CDATA[借助nodejs的生态开发也有不断的一段时间了,常用的一直是那几个常用的命令。如今,是时候好好理一理了… CommonJs规范一览module代表当前的模块,而module上的exports属性是这个模块对外暴露对象的接口。require其实就是导入某个模块的exports属性1234/* 导出模块 */module.exports = {}/* 导入模块 */const xx = require('xx') 特点 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存 模块加载的顺序,按照其在代码中出现的顺序(这也是我们一般将导入模块的操作放到最顶部的原因) module.exports与exports为了方便,Node为每个模块提供一个exports变量,指向module.exports。等同在每个模块头部,有一行这样的命令:var exports = module.exports。这样就有两个值得我们注意的点了: 我们可以在exports变量上添加属性,但是不能直接给exports变量赋值 1234/* 正确做法 */exports.exp = function() {}/* 错误的做法 */exports = function exp() {} 在给exports添加属性后不能给module.exports重新赋值 123exports.exp = function() {}// 实际对外输出的就是module.exports属性,这样就切断了module.exports与exports的联系module.exports = 'Hello world' requirerequire的常规用法这里就不再多说了,这里只说以前忽略的点。目录加载规则:有些时候,我们在require的参数中只写目录而不写文件名。在没有特殊指定的情况下,node会加载该目录下的index.js或是index.node文件。当然我们也可以指定目录与文件名:12345// package.json{ "name" : "some-library", "main" : "./lib/some-library.js"} 模块加载机制CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 npm常用命令 npm init:创建package.json文件 npm login:登录npm npm whoami:告诉你当前登录用户是谁 npm publish:发布自己的npm包 npm unpublish –force:删除自己的npm包(每个版本都得删) npm config list:查看配置信息 踩过的坑之前发布不成功,报了403的错误。解决方案是要讲淘宝的源切换会npm官方的源。然后还要重新登录。某些模块被墙的解决方案(以node-sass为例):1234npm install --save node-sass --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/dist --sass-binary-site=http://npm.taobao.org/mirrors/node-sass# --registry=https://registry.npm.taobao.org 淘宝npm包镜像# --disturl=https://npm.taobao.org/dist 淘宝node源码镜像,一些二进制包编译时用# --sass-binary-site=http://npm.taobao.org/mirrors/node-sass 这个才是node-sass镜像 yarnyarn安装模块的速度着实把我给惊艳到了,这里必须要说道说道。 常用命令 yarn yarn init yarn add:默认添加到开发依赖 yarn upgrade yarn add –offline:指定离线安装(当前最新版本好像不太需要显式指定) yarn add –dev:添加到开发依赖 yarn remove yarn publish yarn config set 淘宝源 yarn global add:全局安装(yarn不推荐滥用全局安装) yarn self-update yarn run:运行package.json中的脚本]]></content>
<categories>
<category>后台</category>
<category>nodejs</category>
</categories>
<tags>
<tag>nodejs</tag>
<tag>module</tag>
<tag>npm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS常用方法(一)]]></title>
<url>%2F2017%2F07%2F11%2FJS-common%20function(1)%2F</url>
<content type="text"><![CDATA[观察者模式是一种非常经典的设计模式,本文是JS方式的实现… 自定义事件对象1234567891011121314151617181920212223242526272829303132333435363738394041// 用于创建事件对象的类function Person(name) { this.name = name // 私有属性,用于管理事件名称对应的回调列表 this._events = {}}/* 如果有这个事件名,则将传进来的回调放至回调数组尾部;如果没有,则创建 */Person.prototype.on = function(eventName, callback) { if(this._events[eventName]) { this._events[eventName].push(callback) }else { this._events[eventName] = [callback] }}/* 拿到事件名称对应的回调列表,依次执行这些回调 */Person.prototype.emit = function(eventName) { // 拿到除事件名以外的其他参数,传至每一个回调 var args = Array.prototype.slice.call(arguments, 1) var callbacks = this._events[eventName] var self = this callbacks.forEach(function(callback) { callback.apply(self, args) })}/* 解除订阅,这里的回调需要是引用外部的函数,而不是直接写函数体 */Person.prototype.off = function(eventName, callback) { this._events[eventName] = this._events[eventName].filter(function(item) { return item != callback })}var girl = new Person()function test(he) { console.log('记得撩起',he)}girl.on('待你长发及腰', function(he) { console.log('娶你可好?',he)})girl.on('待你长发及腰', test)girl.emit('待你长发及腰','hehe')girl.off('待你长发及腰', test)girl.emit('待你长发及腰','hehe') 节流函数12345678910111213function debounce(func, delay) { var timer = null return function() { // 返回的函数中还是能够拿到调用者的this var context = this // 函数参数虽然没有显式地声明出来,但是可以通过arguments拿到,apply中也是可以直接传入arguments的 var args = arguments clearTimeout(timer) timer = setTimeout(function() { func.apply(context, args) }, delay || 200) }} 防抖函数12345678910function throttle(func, delay) { var last = 0 return function() { var curr = +new Date() if(curr - last > delay) { func.apply(this, arguments) last = curr } }} 封装jsonp12345678910111213141516171819202122232425262728function jsonp(config) { var options = config || {} var callbackName = ('jsonp_' + Math.random()).replace(".", "") var oHead = document.getElementByTagName('head')[0] var oScript = document.creatElement('script') oHead.appendChild(oScript) window[callbackName] = function(json) { oHead.removeChild(oScript) clearTimeout(oScript.timer) window[callbackName] = null options.success && options.success(json) } oScript.src = option.url + '?' + callbackName if(options.time) { oScript.timer = setTimeout(function() { window[callbackName] = null oHead.removeChild(oScript) options.fail && options.fail({message: '超时!'}) }, options.time) }}/* 使用 */jsonp({ url: '/b.com/b.json', time: 5000, success: function(res) {}, fail: function() {}}) 封装ajax123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051// json对象转成URLfunction json2url(json) { var arr = [] for (var name in json) { arr.push(name + '=' + json[name]) } return arr.join('&')}function ajax(json) { json = json || {} if (!json.url) return json.data = json.data || {} json.type = json.type || 'get' var timer = null if (window.XMLHttpRequest) { var oAjax = new XMLHttpRequest() } else { var oAjax = new ActiveXObject('Microsoft.XMLHTTP') } switch (json.type) { case 'get': oAjax.open('GET', json.url + '?' + json2url(json.data), true) oAjax.send() break case 'post': oAjax.open('POST', json.url, true) oAjax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') oAjax.send(json2url(json.data)) break } oAjax.onreadystatechange = function() { if (oAjax.readyState == 4) { clearTimeout(timer) if (oAjax.status >= 200 && oAjax.status < 300 || oAjax.status == 304) { json.success && json.success(oAjax.responseText) } else { json.error && json.error(oAjax.status) } } }}// ... 使用ajax({ url: '/user', data: {}, type: 'get', success: function(res) {}, error: function() {}}) 深克隆对象123456789101112function deepCopy(p, c) { var c = c || {} for (var i in p) { if (typeof p[i] === 'object') { // 判断是否为广义对象 c[i] = (p[i].constructor === Array) ? [] : {} // 判断是否为狭义对象 deepCopy(p[i], c[i]) } else { c[i] = p[i] } } return c}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>常用函数</category>
</categories>
<tags>
<tag>js</tag>
<tag>常用方法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[巴金走后]]></title>
<url>%2F2017%2F07%2F03%2Fmood-2017073%2F</url>
<content type="text"><![CDATA[忘了有多久没有煽情了。巴金走后,姑且记录一下心情… 五月初,巴金来到深圳;七月初,巴金回家… 在得知巴金要来深圳之前,我的心情是有些小小期待的。毕竟同窗四年不容易,四年里不在同一个宿舍却相交甚欢更不容易,而四年中的三年中的多数夜晚相约去操场跑步健身的更是难上加难。这样的小伙伴,我有两个。巴金就是其中之一。 两个月的时间,几乎都习惯了巴金那傻傻的笑脸时不时地挤进你的视野。然后,有一天巴金突然说可能过几天就走了。然后我忽然有些慌了,于是急急忙忙的去订电影票。但种种原因,巴金终究是没有与我们一起去看电影。 有人说:我们总以为我们与别人见面的次数是在做加法,每见面一次就累加一次。其实人与人之间的见面次数是减法。见一次,少一次。 忘了从什么时候开始,最初的讨好、迁就慢慢地变成了漠视与据理力争(也许并没有据理)。甚至有一段时间,几乎是一开口就要争论不休。好在我这个人还是有那么一点点优点或者说是怯懦。那就是风平浪静之后能够切换到上帝视角去看一看问题。人与人之间的矛盾,几乎没有什么是完全一方的责任。当时的我深陷了盒子当中。身处盒子当中,看待事物自然是偏颇的。当我想明白这一点的时候,我决心从盒子中跳脱出来。但明白要这样做是一回事,真正能做到又是另外一回事了。你说服不了当时的自己,但你可以让时间慢慢抚平你那颗偏执委屈的心。然后再跳出盒子。很庆幸我当时就是那样做的。如果让我被困在盒子中直到现在,那么缺憾会打过现在无数倍。 想来实在惭愧。巴金来这么久,出门最多的活动仅仅是周末出去打羽毛球,而吃的最多的饭,是在网上点的快餐。总以为之后还有很多时间可以去做一些地主该做的事儿,却不知道人生处处都是戛然而止,让你猝不及防。 if("1110"!==prompt("请输入文章密码!")){ alert("密码错误,回到首页"); window.history.back(); }]]></content>
<categories>
<category>随笔</category>
<category>情感</category>
</categories>
<tags>
<tag>情感</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Rxjs入门]]></title>
<url>%2F2017%2F06%2F28%2FJS-Rxjs%2F</url>
<content type="text"><![CDATA[初识Rxjs是在angular2的学习过程中。那时的理解起来还是非常晦涩,现在也不能说理解有多深,姑且写下来吧… 概况Rxjs是响应式编程思想在JS中的一种实现。那么Rxjs到底是个什么东西呢?装逼地讲,Rxjs是一种融合了函数式编程、观察者模式的以操作流为核心的一种编程思想。Rxjs的适用场景为异步数据流。针对异步的处理是采用观察者模式。针对数据流,我们使用函数式编程中的状态集中管理的理念。简单说说对面向对象编程与函数式编程的理解吧:面向对象就是对数据的封装,将具有相关性的数据和方法封装到一个个对象中,以便管理;函数式编程实际上就是对数据操作的封装,我们只关心数据的输入与输出,至于输入的数据经过了一些什么样的操作最终变成输出的数据,我们是不必在乎的。 核心概念一览ObservableObservable是事件流的源,相当于观察者模式中的被观察者。可以发射一个个的事件、数据等流。 OperatorOperator是Observable的操作符。体现了函数式编程和迭代器模式的思想。通过各种转变,将Observable流转变为新的Observable流。 Observer对Observable对象发出的每个事件进行响应。 subscribe()subscribe()方法用来订阅Observable流。该方法会接受一个observe作为参数,每当observable完成并发送一个事件时,该事件就会被observer所捕获,进入到observer对应的回调函数中。 SubscriptionObservable对象被订阅后返回的Subscription实例(可取消订阅)。 SubjectEventEmitter的等价数据结构,可以当做Observable被监听,也可以作为Observer发送新的事件。 一个完整的Rx流创建Observable对象将一系列的异步事件封装到Observable流里边。常用的Observable对象创建方式除了new Observable()还有实用的Observable.fromEvent()和Observable.create()。Observable.fromEvent():1234567891011121314 let button = document.querySelector('button') Rx.Observable.fromEvent(button, 'click') // 返回一个Observable对象 .subscribe(() => console.log('Clicked!'))``` Observable.create(): ```js Rx.Observable.create(observer => { getData(data => { observer.next(data) observer.complete() }) }) .subscribe(data => { doSomething(data) }) 对Observable流进行各种花式操作map变换操作符只返回我们所关心的数据12345observable.map(res => { return res.data}).subscribe(data => { doSomething(data)}) filter过滤操作符过滤掉一些无用数据1234/* 值为true时,才输出该值 */observable.filter(res => { return !!res.data && res.status == 200}) forkJoin组合操作符某些场景是需要等到两个操作都完成之后才进行的12345Rx.Observable.forkJoin(getFirstDatas, getSecondDatas).subscribe(datas => { // datas[0]是getFirstDatas的数据 // datas[1]是getSecondDatas的数据}) concatMap组合操作符某次数据请求依赖前一次请求的结果123456789101112131415161718let getFirstDatas = Rx.Observable.create(observer => { // next可以执行一个异步操作,也可以在异步操作后next出异步操作的结果 observer.next(getFirstData()) observer.complete()})let createSecondDatas = function(firstData) { return Rx.Observable.create(observer => { getSecondData(firstData, secondData) => { observer.next(secondData) observer.complete() } })}getFirstDatas.concatMap(fristData => { return createSecondDatas(firstData)}).subscribe(secondData => { doSomething(secondData)}) 工具操作符 timeout(): 超过指定的时间没有拿到数据就抛出异常 debounceTime(): 防止抖动 switchMap(): 保证前端拿到的数据是有序的12// ....switchMap(event => getRecommend(event.target.value)) 参考文档RxJS中文文档]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>JS</tag>
<tag>编程思想</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ps小结]]></title>
<url>%2F2017%2F06%2F25%2Ftools-ps-summarize%2F</url>
<content type="text"><![CDATA[将纸质的笔记搬运到线上来,备忘… 基础操作 自由变换:Ctrl+T,按住shift等比缩放,右键可旋转 复制选区:Ctrl+C 复制图层:Ctrl+J 取消选取:Ctrl+D 贴入功能:菜单->编辑->贴入 调节阴影:图层下边的fx 反选:shift+F7 着色:Alt+Del(前景色)、Ctrl+Del(背景色) 隐藏右边工具:shift+Tab 向下合并图层:Ctrl+E 将路径变选取:Ctrl+Enter 选取变路径:路径下边的环状图形 路径:按住Alt,再单击空白处结束路径、shift正交、右键描边 以中心点等比缩放:shift+Alt 重复上一次的自由变换:Ctrl+Alt+T 选区之后羽化:选择->修改->调整羽化和平滑度 将文字属性变为图层属性:右键,文字栅格化 动图:窗口->动画->n个图,存储为web和设备所用格式 切图使用技巧从目标图层中拿切图 快速定位到目标图层 选择“选择工具” 工具栏中勾选“自动选择”,后面选择“图层” 鼠标点击目标图标 右键目标图层选择“转换为智能对象”(转换为智能对象是为了在图片放大的情况下截取图片也不至于导致像素丢失) 截取目标图标,放入新图中 选区工具选择目标图标 “Ctrl + C”复制选取 “Ctrl + N”新建画布(新建画布时注意背景色要选择透明,至于宽高不需要手动设置,PS会根据之前选区的大小设定新建画布的尺寸) “Ctrl + V”将选区内容粘贴到新建画布中 “Ctrl+Shift+Alt+S”保存为“png-24”类型的图片 使用切片工具快速从原图中切出所有的选区这一步可以在上一步得到目标图标之后进行进一步处理。 从左侧工具栏中找到切片工具 类似矩形选框那样划分区域,不同的是切片工具可以拉出很多个矩形选框 “Ctrl+Shift+Alt+S”保存为“png-24”类型的图片 “切片”选项中选择“所有用户切片”(重要),最终生成的一堆图标将放到一个文件夹下]]></content>
<categories>
<category>工具</category>
<category>ps</category>
</categories>
<tags>
<tag>ps</tag>
</tags>
</entry>
<entry>
<title><![CDATA[react-router4笔记]]></title>
<url>%2F2017%2F06%2F19%2FJS-reactRouter4%2F</url>
<content type="text"><![CDATA[前两天在写react项目的时候发现在路由上面走了很多的弯路。正所谓磨刀不误砍柴工,还是先系统地学习一下吧… 重要API一览路由容器组件 BrowserRouter: 浏览器自带的API,restful风格(需要后台做相应的调整); HashRouter: 使用hash方式进行路由; MemoryRouter: 在内存中管理history,地址栏不会变化。在reactNative中使用。 Route标签 该标签有三种渲染方式component、render、children(绝大多数情况使用component组件就好了); 三种渲染方式都会得到三个属性match、history、location; 渲染组件时,route props跟着一起渲染; children方式渲染会不管地址栏是否匹配都渲染一些内容,在这里加动画一时很常见的做法。 Link标签 to: 后面可以接字符串,也可以跟对象(对象可以是动态地添加搜索的信息); replace: 当设置为true时,点击链接后将使用新地址替换掉访问历史记录里面的原地址。 NavLink标签 是的一个特定版本, 会在匹配上当前URL的时候会给已经渲染的元素添加样式参数; activeClassName,当地址匹配时添加相应class; activeStyle,当地址匹配时添加相应style; exact,当地址完全匹配时,才生效; isActive,添加额外逻辑判断是否生效。 Prompt标签 when: when的属性值为true时启用防止转换; message: 后面可以跟简单的提示语,也可以跟函数,函数是有默认参数的。 Redirect标签 可以写在的render属性里面,也可以跟平级; to: 依旧是可以跟字符串或对象; push: 添加该属性时,地址不会被覆盖,而是添加一条新纪录; from: 重定向,与平级时。 match params: 通过解析URL中动态的部分获得的键值对; isExact: 当为true时,整个URL都需要匹配; path: 在需要嵌套的时候用到; url: 在需要嵌套的时候会用到; 获取方式: 以this.props.match方式。 12345678import { BrowserRouter as Router, // 或者是HashRouter、MemoryRouter Route, // 这是基本的路由块 Link, // 这是a标签 Switch // 这是监听空路由的 Redirect // 这是重定向 Prompt // 防止转换 } from 'react-router-dom' 权限控制利用组件内的Redirect标签。123456789101112const PrivateRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={props => ( fakeAuth.isAuthenticated ? ( <Component {...props}/> ) : ( <Redirect to={{ pathname: '/login', state: { from: props.location } }}/> ) )}/>) 阻止离开当前路由在组件内部添加Prompt标签来进行权限控制。123456<Prompt when={isBlocking} message={location => ( `你真的要跳转到 ${location.pathname}么?` )}/> 过渡动画样式分别定义:.example-enter、.example-enter.example-enter-active、.example-leave、.example-leave.example-leave-active。实例12345678910111213<ReactCSSTransitionGroup transitionName="fade" transitionEnterTimeout={300} transitionLeaveTimeout={300}> <!-- 这里和使用 ReactCSSTransitionGroup 没有区别,唯一需要注意的是要把你的地址(location)传入「Route」里使它可以在动画切换的时候匹配之前的地址。 --> <Route location={location} key={location.key} path="/:h/:s/:l" component={HSL} /></ReactCSSTransitionGroup> 按需加载官方方法借助bundle-loader实现按需加载。新建一个bundle.js文件:12345678910111213141516171819202122232425262728293031import React, { Component } from 'react'export default class Bundle extends React.Component { state = { // short for "module" but that's a keyword in js, so "mod" mod: null } componentWillMount() { this.load(this.props) } componentWillReceiveProps(nextProps) { if (nextProps.load !== this.props.load) { this.load(nextProps) } } load(props) { this.setState({ mod: null }) props.load((mod) => { this.setState({ // handle both es imports and cjs mod: mod.default ? mod.default : mod }) }) } render() { if (!this.state.mod) return false return this.props.children(this.state.mod) }} 在入口处使用按需加载:1234567891011121314151617181920// bundle模型用来异步加载组件import Bundle from './bundle.js';// 引入单个页面(包括嵌套的子页面)// 同步引入import Index from './app/index.js';// 异步引入import ListContainer from 'bundle-loader?lazy&name=app-[name]!./app/list.js';const List = () => ( <Bundle load={ListContainer}> {(List) => <List />} </Bundle>)<HashRouter> <Router basename="/"> <div> <Route exact path="/" component={Index} /> <Route path="/list" component={List} /> </div> </Router></HashRouter> webpack.config.js文件配置:12345output: { path: path.resolve(__dirname, './output'), filename: '[name].[chunkhash:8].bundle.js', chunkFilename: '[name]-[id].[chunkhash:8].bundle.js',}, 个人觉得更好用的写法123456789import Loadable from 'react-loadable'import Loading from './my-loading-component'Loadable({ loader: () => import(`views/AsyncView`), // 如果没有loading动画,就返回null LoadingComponent: () => null, // 如果有loading动画,则如下 loading: Loading}) webpack配置:123456789// 添加插件babel-plugin-import-inspector{ "plugins": [ ["import-inspector", { "serverSideRequirePath": true, "webpackRequireWeakId": true, }] ]}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>js</tag>
<tag>router</tag>
<tag>react</tag>
</tags>
</entry>
<entry>
<title><![CDATA[immutable在react中的应用]]></title>
<url>%2F2017%2F06%2F16%2FJS-react-immutable%2F</url>
<content type="text"><![CDATA[江湖传言,immutable-js能够对react应用的性能提升良多。那么就下来就是填坑经历了参考 官方文档… 为什么使用immutable使用immutable创建的数据集合一旦被创建就无法被改变,每次对这个数据集合进行修改都会返回新的值。这点特性对redux这样的偏函数式框架非常重要。immutable相似的数据集合能够共享他们之间相同的节点,这对于节省内存是非常有好处的。 immutable的边界性问题 在React视图里,props其实就来自于Redux维护的全局的state的,所以props中的每一项一定是immutable的; 在React视图里,组件自己维护的局部state如果是用来提交到store的,必须为immutable的,否则不强制; 从视图层向同步和异步action发送的数据(A/B),必须是immutable; Action提交给reducer的数据(C/D),必须是immutable的; reducer处理后所得state(E)当然一定是immutable的。 除了向服务端发送数据请求的时候,其他位置,不允许出现toJS的代码。而接收到服务端的数据后,在流转入全局state之前,统一转化为immutable数据。一句话总结就是在redux中流转的节点,包适流入到action的数据、action流入reducer、reducer流出的数据。而在视图中流转的数据则要看情况而定。 集成immutable到流程中reducers我们用redux-immutable提供的combineReducers来处理,他可以将immutable类型的全局state进行分而治之。1234567import { combineReducers } from 'redux-immutable'const rootReducer = combineReducers({ routing: routingReducer, a: immutableReducer, b: immutableReducer})export default rootReducer initialState12const initialState = Immutable.Map()const store = createStore(rootReducer, initialState) mapStateToProps12345const mapStateToProps = (state) => ({ pause: state.get('pause'), music: state.get('music')})export default connect(mapStateToProps)(App) immutable基础API更多原生js转换为immutableData12Immutable.fromJS([1,2]) // immutable的 listImmutable.fromJS({a: 1}) // immutable的 map 从immutableData回到JavaScript对象1immutableData.toJS() 判断两个immutable数据是否一致1Immutable.is(immutableA, immutableB) 判断是不是map或List12Immutable.Map.isMap(x)Immutable.Map.isList(x) 对象合并(注意是同个类型)1let immutableMaB = immutableMapA.merge(immutableMaC) Map的增删查改查12immutableData.get('a') // {a:1} 得到1。immutableData.getIn(['a', 'b']) // {a:{b:2}} 得到2。访问深层次的key 增和改(注意不会改变原来的值,返回新的值)1234immutableData.set('a', 2) // {a:1} 得到1。immutableData.setIn(['a', 'b'], 3)immutableData.update('a',function(x){return x+1})immutableData.updateIn(['a', 'b'],function(x){return x+1}) 删12immutableData.delete('a')immutableData.deleteIn(['a', 'b']) List的增删查改如同Map,不过参数变为数字索引。1immutableList.set(1, 2)]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>js</tag>
<tag>react</tag>
<tag>性能</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS的数组方法]]></title>
<url>%2F2017%2F06%2F08%2FJS-array%2F</url>
<content type="text"><![CDATA[数组是我们最常操作的JS对象之一,熟练使用各种JS方法,可以让我们的代码敲起来更爽… 数组基本方法join()将数组以指定的字符连接起来输出字符串12// 不改变原数组var newArr = arr.join('-') push()和pop()在数组的屁股上做操作,改变原数组。 shift() 和 unshift()与上面一组方法类似,只是改为在数组的头部做操作。 sort()不传参数的情况下,按照元素的编码值排序。同时可以接受一个函数作为参数来自定义排序规则。123456function compare(pre,next) { // 返回值如果为负数,则两者不调换位置 return(pre-next)}// sort方法会改变原数组arr.sort(compare) reverse()这个没什么好说的,将数组反转。该方法会改变原数组。 concat()将两个数组拼接为一个数组。该方法不改变原数组,返回新数组。 slice()该方法接收起始位置和终止位置(含头不含尾)。字符串也有这样的方法(类似字符串的substring()方法类似)。不改变原数组,返回新数组。 splice()这个方法是数组中的瑞士军刀。它接受多个参数,顺序依次为:起始位置,要删除多少项,要插入的元素…该数组会改变原数组,但它同时也返回被删除的项组成的数组。1234var arr = [2,1,6,5]var newArr = arr.splice(1,1,8)console.log(arr) // [2, 8, 6, 5]console.log(newArr) // [1] indexOf()和lastIndexOf()检索数组中是否包含某值,返回检索到值的位置下标。 ES5数组方法forEach()对数组for循环的封装,接收一个函数作为参数,该函数默认有三个参数:当前遍历的这一项,当前遍历项的索引值,遍历的数组对象。 map()意为“映射”,即对数组的每一项进行指定的处理,最终得到一个新的数组(一定要接住这个新的数组)。 filter()接收一个函数,用来过滤掉数组中某些不符合要求的项。12345var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];var arr2 = arr.filter(function(item, index) { return index % 3 === 0 || item >= 8;});console.log(arr2); // [1, 4, 7, 8, 9, 10] every()和some()every(): 判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true。some(): 判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。 reduce()和reduceRight()这两个方法都会实现迭代数组的所有项,然后构建一个最终返回的值。reduce()方法从数组的第一项开始,逐个遍历到最后。这两个方法都接收两个参数:一个函数,给定迭代的初始值。这个函数默认接受四个参数:前一次处理得到的值,遍历的当前项,当前项的索引,迭代的数组对象。123456var values = [1,2,3,4,5];// 每处理一项的返回值将作为下一项的prev值var sum = values.reduceRight(function(prev, cur, index, array){ return prev + cur;},10); // 这个10将作为第一项的prev值console.log(sum); //25 ES6数组方法Array.from()Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。1234567let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3}let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] Array.of()Array.of方法用于将一组值,转换为数组。 find()和findIndex()数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。 entries(),keys()和values()ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。123456789for(let index of ['a', 'b'].keys()) { console.log(index);}for(let elem of ['a', 'b'].values()) { console.log(elem);}for(let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem);} includes()该方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似(比indexOf()更强)。 扩展的数组方法数组的去重方法filter():1234567function unique(arr) { return ( arr.filter((item,index,arr) => { return arr.indexOf(item) == index }) )} hash方法12345678910function unique(arr) { var hash = {},result = [] arr.forEach((item) => { if(!hash[item]) { hash[item] = 1 result.push(item) } }) return result} splice()1234567891011/* 两层循环 */function unique(arr) { arr.forEach((item, index) => { for(let i += index; i < arr.length; i++) { if(item === arr[i]) { arr.splice(i--, 1) } } }) return arr} sort()加filter()123456function unique(arr) { // arr.concat()得到新的数组 return arr.concat().sort().filter(function(item, index, arr) { return !index || item != arr[index - 1]; });} ES6的Set以及Array.from方法123function unique(arr) { return Array.from(new Set(arr));} 数组判断123456789// 自带的isArray方法var array = []Array.isArray(array) // true// 利用instanceof运算符array instanceof Array;//true// 利用toString的返回值function isArray(o) { return Object.prototype.toString.call(o) === '[object Array]';}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>JS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[angular4表单]]></title>
<url>%2F2017%2F06%2F05%2FJS-angular-form%2F</url>
<content type="text"><![CDATA[angular4提供了两种方式来处理表单。模板式表单适合处理比较简单和常规的表单;响应式表单适合应付比较复杂的表单… 模板式表单麻雀一览12345678910<!-- 表单元素要被包裹在ngForm属性里边,通过模版局部变量,可以拿到表单中的所有数据 --><form #myForm="ngForm" (ngSubmit)="onSubmit(myForm.value)"> <!-- ngModel作为单独的属性放进去,name的属性值将作为表单对象的键名 --> <input ngModel name="userName" type="text"> <!-- 给表单中的元素分组,即给表单对象加一层 --> <div ngModelGroup="pwds"> <input ngModel type="password" name="pwd"> <input ngModel type="password" name="confirmPwd"> </div></form> 响应式表单名词解释FormControl: 表单模版的基本单位FormGroup: 整个表单或者表单的一个固定的子集FormArray: 多个同类型的数据组成的数组 代码实例12345678910111213141516171819formModel: FormGroup// 单独的inputuserName: FormControl = new FormControl()constructor() { this.formModel: FormGroup = new FormGroup({ dateRange: FormGroup = new FormGroup({ from: new FormControl(), to: new FormControl() }), emails: FormArray = new FormArray([ new FormControl() ]) })}// 增加email输入框addEmail() { let emails = this.fromModel.get('emails') as FormArray emails.push(new FormControl())} 123456789101112// 使用formBuilder来简化代码('[]'里面可以有三个元素,分别为默认值、校验、异步校验)constructor(fb: formBuilder) { this.fromModel = fb.group({ dateRange: FormGroup = fb.group({ from: [""], to: [""] }), emails: FormArray = fb.rray([ new FormControl() ]) },{}) // 最后的对象里面定义表单组整体的校验规则} 12345678910111213<form [formGroup]="formModel" (ngSubmit)="onSubmit()"> <div formGroupName="dateRange"> <input type="date" formControlName="from"> <input type="date" formControlName="to"> </div> <ul formArrayName="emails"> <li *ngFor="let e of this.fromModel.get('emails').controls; let i=index;"> <input [formControlName]="i" type="email"> </li> </ul></form><!-- fromControl不能在formGroup中使用,要拎出来单独用(在里面的话要用fromControlName="xx") --><input [FormControl]="userName" type="text" name="" value=""> 表单校验angular自带校验器不用定义,直接可用:required、minlength、maxlength、pattern12345678910constructor(fb: formBuilder) { this.fromModel = fb.group({ userName: ['',[Validators.required,Validators.minLength(6)]] })}onSubmit() { let isValid: boolean = this.formModel.get('userName').Validators // 这里拿到的是一个对象(没有错误时返回的null) let errors: any = this.formModel.get('userName').errors} 自定义校验123456789101112131415161718192021constructor(fb: formBuilder) { this.fromModel = fb.group({ userName: ['',[Validators.required,Validators.minLength(6)]], mobile: ['',[this.mobileValidator]], passwordsGroup: fb.group({ password: [''], confirmPwd: [''] },{validator: this.equalValidator}) })}mobileValidator(control: FormControl):any { var myreg = /^(((13[0-9]){1}) | (15[0-9]{1}) | (18[0-9]{1})) + \d{8})$/ let valid = myreg.test(control.value) return valid ? null : {mobile: true}}equalValidator(group: FormGroup):any { let password: FormControl = group.get('password') as FormControl let confirmPwd: FormControl = group.get('confirmPwd') as FormControl let valid: boolean = (password.value === confirmPwd.value) return valid ? null : {equal: true}} 表单状态字段touched和untouched: 有没有获取过焦点pristine和dirty: 有值被改变,就会变为dirtypending: 正处于异步校验的状态123456<!-- 校验不通过时显示的提示信息 --> <div [hidden]="formModel.get('userName').valid || formModel.get('userName').untouched">校验不通过!</div> <!-- 注意用error的话要取反的 --> <div [hidden]="!formModel.hasError('required','userName') || formModel.get('userName').pristine">必填项!</div> <!-- 异步校验提示信息 --> <div [hidden]="!formModel.get('mobile').pending">正在校验手机号合法性!</div> 表单元素的动态class初始状态:ng-untouched ng-pristine ng-invalid其他状态:ng-dirty ng-touched ng-valid注意:这些class都是全局的,自定义的时候要防止误伤。12<!-- 验证不通过的表单元素加红边框 --><input [class.hasError]="formModel.get('userName').invalid && fromModel.get('userName').touched"/>]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>angular</category>
</categories>
<tags>
<tag>js</tag>
<tag>angular</tag>
<tag>表单</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS中的字符串]]></title>
<url>%2F2017%2F06%2F05%2FJS-string%2F</url>
<content type="text"><![CDATA[拒绝字符串中的各种模糊的方法… 字符串的转换1234567var num= 19 // 19// 方法一var myStr = num.toString() // "19"// 方法二var myStr = String(num) // "19"// 方法三(这个我喜欢)var myStr = "" +num // "19" 字符串分割(split)12345var myStr = "I,Love,You,Do,you,love,me"// 以","为分割点,将字符串分割为多个片段组成的数组var substrArray = myStr .split(",") // ["I", "Love", "You", "Do", "you", "love", "me"];// 加上第二个参数,指定切割后数组的最大长度var arrayLimited = myStr .split(",", 3) // ["I", "Love", "You"]; 查询子字符串123456var myStr = "I,Love,you,Do,you,love,me"// indexOf()如果匹配成功,则返回这个匹配上的位置,匹配不成功返回-1var index = myStr.indexOf("you") // 7 ,基于0开始,找不到返回-1// 从字符串的末尾开始查找var index = myStr.lastIndexOf("you") // 14/* 以上两个函数同样接收第二个可选的参数,表示开始查找的位置。 */ 字符串替换1234var myStr = "I,love,you,Do,you,love,me";var replacedStr = myStr.replace("love","hate") // "I,hate,you,Do,you,love,me"/* 默认只替换第一次查找到的,想要全局替换,需要置上正则全局标识 */var replacedStr = myStr.replace(/love/g,"hate") //"I,hate,you,Do,you,hate,me" 查找给定位置的字符或其字符编码值1234var myStr = "I,love,you,Do,you,love,me"var theChar = myStr.charAt(8) // "o",同样从0开始/* 查找对应位置的字符编码值 */var theChar = myStr.charCodeAt(8) // 111 字符串连接12345/* 除了使用"+",JS还提供了专门的函数来处理 */var str1 = "I,love,you!"var str2 = "Do,you,love,me?"// concat()函数可以有多个参数,传递多个字符串,拼接多个字符串var str = str1.concat(str2) // "I,love,you!Do,you,love,me?" 字符串切割和提取1234567var myStr = "I,love,you,Do,you,love,me"// slice()含头不含尾var subStr = myStr.slice(1,5) // ",lov"// substring()跟上面效果一样var subStr = myStr.substring(1,5) // ",lov"// 从哪个位置开始截取,最多截取多少个var subStr = myStr.substr(1,5) // ",love" 字符串大小写转换123var myStr = "I,love,you,Do,you,love,me"var lowCaseStr = myStr.toLowerCase() // "i,love,you,do,you,love,me";var upCaseStr = myStr.toUpperCase() // "I,LOVE,YOU,DO,YOU,LOVE,ME" 字符串匹配字符串方法match()12345var myStr = "I,love,you,Do,you,love,me"var pattern = /love/var result = myStr.match(pattern) // ["love"]console.log(result .index) // 2console.log(result.input ) // I,love,you,Do,you,love,me 正则方法exec()123456var myStr = "I,love,you,Do,you,love,me"var pattern = /love//* 其实就是将match()方法反过来用 */var result = pattern .exec(myStr) // ["love"]console.log(result .index) // 2console.log(result.input ) // I,love,you,Do,you,love,me 对于以上两个方法,匹配的结果都是返回第一个匹配成功的字符串,如果匹配失败则返回null。 search()1234var myStr = "I,love,you,Do,you,love,me";var pattern = /love/;/* 返回查到的匹配的下标,如果匹配失败则返回-1 */var result = myStr.search(pattern) // 2]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>js</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[angular4生命周期]]></title>
<url>%2F2017%2F06%2F05%2FJS-angular-lifeCycle%2F</url>
<content type="text"><![CDATA[angular4官方提供了许多的生命周期钩子函数。在合适的钩子函数中做合适的事儿… 写在前头angular4的生命周期钩子是其官方提供的接口。所以当我们要定义这些钩子函数时需要在模块中引入相应的接口,然后在组件的类中实现该接口。钩子函数的名字是在接口的前面加上on。 数据准备阶段ngOnInit当Angular初始化完成数据绑定的输入属性后,用来初始化指令或者组件。 ngOnChanges 当Angular设置了一个被绑定的输入属性(输入属性)后触发。该回调方法会收到一个包含当前值和原值的changes对象; 当输入属性值发生变化,但是视图没有响应时,需要用到这个钩子; 父组件初始化子组件的输入属性时会被调用一次; 构造函数阶段输入属性为空,ngOnInit()时才会有值; 子组件的输入属性(基础数据类型)变化时触发这个钩子(引用类型不会触发); 虽然引用类型不会触发钩子,但是angular的变更监测机制会检测到,并且同步数据;ngDoCheck用来检测所有变化(无论是Angular本身能检测还是无法检测的),并作出相应行动。在每次执行“变更检测”时被调用。注意:点击、调后台接口、表单处理、定时器都会触发这个钩子,所以我们在实现这个接口时必须要非常高效。视图准备阶段ngAfterContentInit当Angular把外来内容投影进自己的视图之后调用。ngAfterContentChecked当Angular检查完那些投影到自己视图中的外来内容的数据绑定之后调用。ngAfterViewInit在Angular创建完组件的视图后调用。ngAfterViewChecked在Angular检查完组件视图中的绑定后调用。 afterViewInit()与afterViewChecked() 子组件试图初始化并且试图变更监测完之后,父组件再进行试图初始化和试图变更监测; 父组件通过模版本地变量调用子组件中的方法时被调用; 调用子组件方法时也许不会触发视图初始化,但肯定会调用检测钩子; 不能在这两个钩子中改变视图中绑定的东西(可以通过定时器来解决)afterContentInit()与afterContentChecked() 与上面两个钩子类似,一个是说的整个视图,一个是说的投影内容; 在父组件的子组件标签中间可以定义要投影的内容(用class来区分多个),子组件用[ngCntent]来占位; 投影内容只能绑定父组件中的值; 调用顺序是父组件投影内容–>子组件初始化–>父组件初始化; 在这个钩子里还可以改变模版中绑定的内容,因为此时视图还为初始化]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>angular</category>
</categories>
<tags>
<tag>js</tag>
<tag>angular</tag>
<tag>生命周期</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mock数据]]></title>
<url>%2F2017%2F05%2F31%2FJS-mock%2F</url>
<content type="text"><![CDATA[花了差不多半天的时间来探索在vue中mock数据的最佳方案。总结一下… 前言本文以vue-cli命令行工具生成的webpack项目为例,实现在前后端分离的情况下前段工程师模拟后台接口的方案。 最粗暴的mockwebpack内部其实是启动了一个基于express的小型服务器。而express提供了一个很简单的api来将某个目录里面的资源静态呈递出去。所以,我们的思路很简单,创建一个文件夹存放一对json数据;用express的static()方法将这个文件夹静态呈递。关键代码如下:123/* build/dev-server.js */// 以mock打头的http请求将去到指定的文件夹里面寻找相对应的资源,这里我们存放json文件即可(严格的json格式)app.use('/mock',express.static('./src/mock-data')) 使用node的第三方模块写接口使用上面的方法太过死板,而且只能使用get请求。作为一个有追求的前端渣渣,我们还得继续完善。首先,我们使用express(webpack本身依赖express,所以不需要安装其他模块)模拟后台接口:123456789101112/* mock/server.js */ let express = require('express') let app = express(); app.get('/api',(req,res) => { res.send('success!') }) // 这个js暴露了一个json对象 var homeAdData = require('./home/ad.js'); app.get('/api/homead',(req,res) => { res.send(homeAdData) }) app.listen(3002) 大家可以看到这里我们监听的是本机的3002端口,而vue项目默认是在8080端口启动的(这两个端口不能相同,原因不解释)。webpack-dev-server官方提供了一个转发接口的配置项。1234567891011/* config/index.js */// 将URL后面以api打头的接口转发到指定的域名上proxyTable: { '/api': { target: 'http://localhost:3002', changeOrigin: true, // 这里表示允许跨域 pathRewrite: { '^/api': '/api' } }} 至此,我们开发之前都要敲两个命令来分别启动项目和我们写的接口,还可以更加简单123/* build/dev-server.js */// 将这个express服务器跑起来require('../mock/server.js');]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>mock</category>
</categories>
<tags>
<tag>js</tag>
<tag>mock</tag>
</tags>
</entry>
<entry>
<title><![CDATA[angular4依赖注入]]></title>
<url>%2F2017%2F05%2F29%2FJS-anjular-DI%2F</url>
<content type="text"><![CDATA[整理一下angular4中的依赖注入设计模式的相关知识… 感性认知为什么要用它依赖注入是为了解决我们在一个组件中要使用某个类的方法时,需要先实例化这个类,这使得这个组件和那个类紧密地耦合在一起的问题。 依赖注入是什么依赖注入是为了解决上述问题的一种设计模式。它是“控制反转”(将实例化这个类的控制权交给外部,从而实现类与组件的解耦)的一体两面。控制反转是目的,依赖注入是手段。 怎么用我们在组件中需要某个服务的实例时,不需要手动地创建这个实例。只需要在构造函数的参数中指定这个实例的变量,以及这个实例的类。然后angular会根据这个类的名字找到providers属性里边指定的同名provide,再找到它对应的useClass,最终自动创建这个服务的实例。代码一览:123456789101112// 在模块中声明依赖注入的服务@NgModule({ // 当这两个属性值同名时,可以简写为:ProductService providers: [{provide: ProductService, useClass: AnotherProductService}]})// 组件类中export class ProductComponent { product: Product constructor(productService: ProductService) { // 这一步将ProductService实例化后的对象赋值给了productService变量 this.product = productService.getProduct() // 这里调用了ProductService实例的方法 }} angular中如何实现依赖注入两个主要概念注入器你需要谁的实例,直接在构造函数的参数里指定变量和类型就好了。1constructor(private productService: ProductService) { ... } 提供器angular需要知道怎样来实例化这个服务12345678// 两个属性同名providers: [ProductService]// 完整写法providers: [{provide: ProductService, useClass: ProductService}]// 不同命写法providers: [{provide: ProductService, useClass: AnotherProductService}]// 用工厂方式创建这个实例providers: [{provide: ProductService, useFactory: ()=>{...}}] 实际上手123456789101112131415161718192021// shared/product.service.tsimport { Injectable } from '@angular/core'// 这里加上注入器,可以在这个服务中注入其他服务@Injectable()export class ProductService { constructor() {} // 定义getProduct方法返回Product类型的对象 getProduct(): Product { ... // 这里可以做很多操作,如查询数据库等,这里简化 return new Product(0,"iphone8",...) }}// 之所以要暴露这个类,是因为在组件中实例化这个类时,需要指定该实例的类型export class Product { constructor( // 定义Product类中的属性 public id: number, public title: string, ... ) {}}// ProductComponent中,略... 作用域 在模块中提供的服务,作用域是它下边的所有组件(绝大多数情况); 在组件中提供的服务,只能在该组件中使用(@Component是@Injectable的实例。所以,组件的注入写在@Component下就好了); 如果在模块中和组件中提供了同名的服务,那么模块中的服务将被组件中的同名服务所覆盖]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>angular</category>
</categories>
<tags>
<tag>js</tag>
<tag>angular</tag>
<tag>依赖注入</tag>
</tags>
</entry>
<entry>
<title><![CDATA[angular4路由]]></title>
<url>%2F2017%2F05%2F28%2FJS-angular-router%2F</url>
<content type="text"><![CDATA[听说angular4整了一个超级牛叉的路由模块… 路由概念一览Routes路由配置,保存着哪个URL对应展示哪个组件,以及在哪个RouterOutlet中显示组件。 RouterOutlet在html中标记路由内容呈现位置的占位符指令。 Router负责在运行时执行路由的对象,可以通过调用其navigate()和navigateByUrl()方法来导航到一个指定的路由。 RouterLink在html中声明路由导航用的指令。 ActivetedRouter当前激活的路由对象,保存着当前路由的信息,如路由地址,路由参数等。 实际上手创建带有路由模块的项目架构1ng new projectName -routing 路由的配置模块123456789101112131415161718192021222324252627// app-routing.module.tsimport { NgModule } from '@angular/core'import { Routes,RouterModule } from '@angular/router'... // 这里还要导入路由里面切换的组件const routes: Routes = [ { path: '', redirectTo: '/home', // 注意:这里的home是有'/'的 pathMatch: 'full' }, { path: 'home', // path里边不能用'/'开头 component: homeComponent, children: [] }, { path: '**', component: Code404Component }]@NgModule({ imports: [RouterModule.forRoot(routes)], // 在主模块中使用该路由配置就用forRoot,否则forChild exports: [RouterModule], providers: []})export class AppRoutingModule { } // 这个模块将要在主模块的imports选项中导入 路由跳转声明式12<!-- 注意点:路径必须用'/开头(导航到跟路由)',并且要用'[]'包裹起来 --><a [routerLink]="['/',params]"></a> 命令式1234567import { Router } from "@angular/router"// 在构造函数中注入Router对象constructor(private router: Router) { }// 点击跳转的函数toSomePage() { this.router.navigate(['/somePage']) // 这里同样可以传参} 路由传参查询参数/product?id=1&name=2 —> ActivetedRoute.queryParams[id];代码:12<!-- id即是要传的参数(这个id将以?的方式拼接,形如:...product?id=1) --><a [routerLink]="['/product']" [queryParams]="{id:1}">商品详情</a> 12345678// 组件类中import { ActivetedRoute } from '@angular/router'private productId: number // 定义productId来接受传过来的参数constructor(private routeInfo: ActivetedRoute) { } // 将ActivetedRoute注入进来,让别人来使用它实例的方法ngOnInit() { // 这里的snapshot是生成一次快照,如果组件没被创建,只是参数变化,这个productId不会跟着变化(监听这个参数可以实现)。 this.productId = this.routeInfo.snapshot.queryParams["id"]} 路径传参{path:/product/:id} —> /product/1 —> ActivetedRoute.params[id];代码:12// 路由配置中{path: product/:id,component:xxComponent} 12<!-- 用navigate导航也是一样的 --><a [routerLink]="['/product', 1]">商品详情</a> 123456789...ngOnInit() { this.productId = this.routeInfo.snapshot.params["id"] // 用参数订阅的方式响应productId的变化 this.routeInfo.params.subscribe((params: Params) => { // RxJs的语法 this.productId = params["id"] })}... 配置传参 {path:/product,component:xxComponent,data:[{isProd:true}]} —> ActivetedRoute.data[0][isProd]; 辅助路由12<router-outlet></router-outlet><router-outlet name="aux"></router-outlet> 123// 这里配置哪些组件能够显示到‘aux’这个插座上,只要有outlet属性,就说明这是辅助路由的配置{path: 'xxx', component: XxxComponent, outlet: 'aux'}{path: 'yyy', component: YyyComponent, outlet: 'aux'} 12345<!-- 这里的outlets只是控制辅助路由的切换(独立变化,与主路由链接无关)(辅助路由的URL是用'()'包起来的) --><a [routerLink]="['/home',{outlets: {aux: 'xxx'}}]">Xxx</a><a [routerLink]="['/product',{outlets: {aux: 'yyy'}}]">Yyy</a><!-- 这里是点击切换到开始聊天路由,顺便将主路由切到home路径上去 --><a [routerLink]="[{primary: 'home', outlets: {aux: 'chat'}}]">开始聊天</a> 路由守卫CanActivate场景:用户未登陆不允许进入该路由页面12345678910111213141516171819// guard/login.guard.tsimport { CanActivate } from '@angular/router'export class LoginGuard implements CanActivate { canActivate() { // 这个方法要反回一个布尔值,根据这个布尔值看你是否有权限进到这个路由 let loggedIn: boolean = Match.random() < 0.5 // 用随机数的方式来模拟看用户是否登陆 if(!loggedIn) { alert('未登录,不能进入该页面') } return loggedIn }}// 路由配置信息里面加入一个属性import { LoginGuard } from "./guard/login.guard"{path: 'product/:id',component:ProductComponent,...,canActivatave:[LoginGuard]}// 在providers属性中注入LoginGuard以实例化@NgModule({ ... providers: [LoginGuard]}) CanDeactivate场景:用户要离开时,提醒他保存表单信息12345678910111213141516// guard/unsaved.guard.tsimport { CanDeactivate } from '@angular/router'import { ProductComponent } from '../product/product.component' // 引入你要保护的组件export class UnsavedGuard implements CanDeactivate<ProductComponent> { // 这里要指定一个泛型指定你要保护谁 canActivate(component: ProductComponent) { // 这里我们可以拿到受保护的组件,判断它是否具备离开的条件 return window.confirm('你还没有保存,确定要离开吗?') }}// 路由配置信息里面加入一个属性import { UnsavedGuard } from "./guard/unsaved.guard"{path: 'product/:id',component:ProductComponent,...,canDeactivatave:[UnsavedGuard]}// 在providers属性中注入LoginGuard以实例化@NgModule({ ... providers: [LoginGuard,UnsavedGuard]}) Resolve在进入路由页面之前,预先从服务器上获取到想要的数据。而后带这这些数据进入路由页面,用户体验更佳1// 太难了...]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>angular</category>
</categories>
<tags>
<tag>js</tag>
<tag>angular</tag>
<tag>router</tag>
</tags>
</entry>
<entry>
<title><![CDATA[angular4搭建竞拍网站(一)]]></title>
<url>%2F2017%2F05%2F27%2FJS-angular-suction(1)%2F</url>
<content type="text"><![CDATA[四天的小长假,这是第一天。上午玩了一下react写大众点评app,下午转战angular4。连我自己都有点佩服我自己的忍耐力了… 使用angular4的准备工作 angular-cli命令行工具 1npm i -g @angular/cli sublime安装type-script插件 创建项目骨架 1ng new progectName ng server启动项目,监听4200端口 ng generate component component-name创建一个完整组件(这里的完整组件指的是组件控制器、HTML片段、样式文件、测试文件。并且这些组件会自动注入到启动模块中去,而后这些组件可以相互引用了) angular4的语法规则angular4组件的元素 必备元素:装饰器(@Component)、模版(template)、控制器(也就是写在装饰器下面的类) 可选的输入对象:输入属性(@Input)、提供器(providers,用来作依赖注入)、生命周期钩子(Lifecycle Hooks) 可选输出对象:生命周期钩子(Lifecycle Hooks)、样式表(styles,可以是多个)、动画(Animations)、输出属性(@Output) 组件控制器实例12345678910111213141516171819202122232425262728import { Component, OnInit } from '@angular/core';@Component({ selector: 'app-product', // 使用的时候写成这样即可<app-product></app-product> templateUrl: './product.component.html', styleUrls: ['./product.component.css']})export class ProductComponent implements OnInit { @Input() // 输入属性吗,值由父组件标签中的属性传递过来 private stars: boolean[]; // 这个数组元素由基本数据类型布尔值组成的 private products: Array<Product>; // 这个Array是由自己创建的类的实例组成的 private imgSrc = "http://placehold.it/320x150"; // 这是图片占位符的写法(与angular无关) constructor() { } ngOnInit() { this.products = [ new Product(1,"第1个商品",1.99,3.5,"这是一个商品的描述1",["电子产品1","通讯设备1"]), ... ] }}export class Product { // 这个类是用来描述一个商品的信息的 constructor( public id: number, ... public categories: Array<string> ) { ... }} 模块实例12345678910111213141516171819202122import { BrowserModule } from '@angular/platform-browser';import { NgModule } from '@angular/core';import { FormsModule } from '@angular/forms';import { HttpModule } from '@angular/http';import { AppComponent } from './app.component';import { NavbarComponent } from './navbar/navbar.component';...@NgModule({ declarations: [ // 这个元数据里只能声明组件、指令和管道 AppComponent, NavbarComponent, ... ], imports: [ // 声明当前模块依赖的其他模块,引入了这些模块,你就可以使用这些模块提供的组件、指令和服务 BrowserModule, FormsModule, HttpModule ], providers: [], // 声明模块中提供了哪些服务 bootstrap: [AppComponent] // 声明模块的主组件是谁})export class AppModule { } 动态属性绑定1234 <img [src]="动态值" alt=""> <!-- 当布尔值为真的时候,active的class才会被添加,而且之前的container不会被覆盖 --><div class="container" [class.active]="布尔值"></div> <!-- 使用[class]会覆盖之前的类,使用[ngClass]="{类名: 表达式(返回布尔值)}"不会覆盖之前的类。(样式绑定与其相似) --> 循环语法12 <!-- 几个注意点:*、let不能丢;of(不是in) --><span *ngFor="let item of items"></span> 星级评价 拿到评分数值,如果大于5,要做转换; 生成含五个元素的布尔值类型的数组。true代表添加空星样式(默认都是实星样式); 用*ngFor动态生成5颗星。在循环内使用[class.空星样式]=”item” 关键代码1234 let arr = [];for(let i = 1; i <= 5; i++) { arr.push(星级数 < i)}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>angular</category>
</categories>
<tags>
<tag>js</tag>
<tag>angular</tag>
</tags>
</entry>
<entry>
<title><![CDATA[React webapp(二)]]></title>
<url>%2F2017%2F05%2F24%2FJS-react-webapp(2)%2F</url>
<content type="text"><![CDATA[写这篇博客的时候,刚刚将这个webapp的首页完成… 智能组件与木偶组件我们将react项目中的组件分为智能组件和木偶组件两类,每个组件拥有一个文件夹,文件夹里面包含index.jsx、style.less(它的子组件在它的下级目录)。 智能组件我们把智能组件放到containers文件夹下,containers文件夹下的一级目录放的是每个路由分页面。智能组件主要是作为一个容器,负责给木偶组件传递方法和数据。 木偶组件木偶组件位于components文件夹下。它的作用主要是页面的展示,它是消费父组件(智能组件)传递给他的方法和数据。 字体图标制作在线制作字体图标 使用生成的css文件放在’static/css’文件夹下,字体文件放在’static/fonts’文件夹下(相对路径不要搞错)。 postcss上代码:1234// 引入样式文件import styles from './style.less'// 使用<div className={styles["content-title"]}></div> fetch的使用mock接口写接口12345678910111213// mock/server.js var app = require('koa')(); var router = require('koa-router')(); //首页广告 var homeAdData = require('./home/ad.js'); // 这里其实是拿到一个json router.get('/api/homead',function *(next){ this.body = homeAdData; }) // ...后面可以接着写 //开启服务 app.use(router.routes()) .use(router.allowedMethods()); app.listen(3002); 定义json12345678910111213// mock/home/ad.jsmodule.exports = [ { title: '暑假5折', img: 'http://images2015.cnblogs.com/blog/138012/201610/138012-20161016191639092-2000037796.png', link: 'http://www.imooc.com/wap/index' }, { title: '特价出国', img: 'http://images2015.cnblogs.com/blog/138012/201610/138012-20161016191648124-298129318.png', link: 'http://www.imooc.com/wap/index' }] 使用123456789101112131415161718192021222324import AdData from '../../../../mock/home/ad'import {getAdData} from '../../../fetch/home/home'componentDidMount() { // 第一步,调接口 const result = getAdData(); // 第二步,拿到结果,如果成功,将字符串转成JSON对象 result.then(res => { if(res.ok) { return res.json() }else { return AdData // 如果调接口拿数据出错,则直接返回这个import进来的数据 } }).then(json => { // 将转换后的数据跟UI组件的显示数据对接起来 const data = json if(data.length) { this.setState({ data: data }) } }).catch(err => { console.log(err.message) })} 滚动事件中的性能优化123456789101112131415161718192021222324252627componentDidMount() { const wrapper = this.refs.wrapper const loadMoreFn = this.props.loadMoreFn // 这里是滚动事件被触发之后的回调函数 function callback() { const top = wrapper.getBoundingClientRect().top const windowHeight = window.screen.height if(top && top < windowHeight) { loadMoreFn() // 从父组件传过来的加载更多数据的方法 } } /* * 1. 外部定义变量用来存储定时器 * 2. 在滚动事件里面判断那个变量有没有存定时器 * 3. 定时器搞起来,用之前的变量存起来 */ let timeAction window.addEventListener('scroll',() => { if(this.props.isLoadingMore) { return } if(timeAction) { clearTimeout(timeAction) } timeAction = setTimeout(callback,50) })}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>react</tag>
</tags>
</entry>
<entry>
<title><![CDATA[React webapp(一)]]></title>
<url>%2F2017%2F05%2F20%2FJS-react-webapp(1)%2F</url>
<content type="text"><![CDATA[额,五月二十号这么特殊的一天。我还是写一篇技术博客吧… webpack自动打开浏览器12345678910var OpenBrowserPlugin = require('open-browser-webpack-plugin');// 插件配置plugins: [ ... // 打开浏览器 new OpenBrowserPlugin({ url: 'http://localhost:8080' }), ...], 自动添加CSS兼容属性123postcss: [ require('autoprefixer') //调用autoprefixer插件,例如 display: flex], 将第三方依赖库打包在一起123456789101112131415161718192021entry: { app: path.resolve(__dirname, 'app/index.jsx'), // 将 第三方依赖 单独打包 vendor: [ 'react', 'react-dom', ... ] }, output: { path: __dirname + "/build", filename: "[name].[chunkhash:8].js", publicPath: '/' }, plugins: [ ... new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[name].[chunkhash:8].js' }), ] 提高组件利用率12345plugins: [ ... // 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID new webpack.optimize.OccurenceOrderPlugin(),] 分离CSS文件123456var ExtractTextPlugin = require('extract-text-webpack-plugin');plugins: [ ... // 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID new ExtractTextPlugin('[name].[chunkhash:8].css'),] mock使用koa写接口12345678910111213141516// mock/server.js(使用nodejs启动这个koa服务)var app = require('koa')();var router = require('koa-router')();router.get('/', function *(next) { this.body = 'hello koa !'});router.get('/api', function *(next) { this.body = 'test data'});router.get('/api/1', function *(next) { this.body = 'test data 1'});app.use(router.routes()) .use(router.allowedMethods());app.listen(3000); 设置代理12345678910devServer: { proxy: { // 凡是 `/api` 开头的 http 请求,都会被代理到 localhost:3000 上,由 koa 提供 mock 数据。 '/api': { target: 'http://localhost:3000', secure: false } }, ...} localStorage123456789101112131415161718192021222324252627// util/localStorage.jsexport default { getItem: function (key) { let value try { value = localStorage.getItem(key) } catch (ex) { // 开发环境下提示error if (__DEV__) { console.error('localStorage.getItem报错, ', ex.message) } } finally { return value } }, setItem: function (key, value) { try { // ios safari 无痕模式下,直接使用 localStorage.setItem 会报错 localStorage.setItem(key, value) } catch (ex) { // 开发环境下提示 error if (__DEV__) { console.error('localStorage.setItem报错, ', ex.message) } } }} loading在系统还没准备好的时候展示一些其他的组件:123456789101112// 当我们的app准备好了再改变initDone的值,重新渲染组件render() { return ( <div> { this.state.initDone ?this.props.children :<p>Loding...</p> } </div> )}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>react</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS小技巧]]></title>
<url>%2F2017%2F05%2F17%2FCSS-tips%2F</url>
<content type="text"><![CDATA[一些CSS的小技巧往往能够发挥出巨大的能量。本文转载至前端开发者应该知道的CSS小技巧… 使用:not()去除导航上不需要的边框1234/* 只保留导航列表最后一项的右边框 */.nav li:not(:last-child) { border-right: 1px solid #666; } 为body添加行高1234/* 这种方式下,文本元素可以很容易从body继承 */body { line-height: 1;} 垂直居中任何元素1234567891011html, body { height: 100%; margin: 0;}body { -webkit-align-items: center; -ms-flex-align: center; align-items: center; display: -webkit-flex; display: flex;} 逗号分离的列表1234/* 使用伪类:not() ,这样最后一个元素不会被添加逗号 */ul > li:not(:last-child)::after { content: ",";} 使用负nth-child选择元素1234/* 选择1到3的元素并显示 */li:not(:nth-child(-n+3)){ display: none;} 使用SVG图标SVG对所有分辨率类型具有良好的伸缩性,IE9以上的所有浏览器都支持。1234/* 如果你使用SVG图标按钮,同时SVG加载失败,下面能帮助你保持可访问性 */.no-svg .icon-only:after { content: attr(aria-label);} 文本显示优化12345html { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;} 继承box-sizing1234567/* 从html继承box-sizing */html { box-sizing: border-box;}, :before, *:after { box-sizing: inherit;} 表格单元格等宽1234/* 无痛表格布局 */.calendar { table-layout: fixed;}]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
<category>skill</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS中函数的生命周期]]></title>
<url>%2F2017%2F05%2F17%2FJS-function%2F</url>
<content type="text"><![CDATA[本文意在将JS中函数从生到死的讲清楚。嗯,让后人能知晓JS函数的生平。是非功过,便听凭后人吧… 通俗地讲函数的创建当函数被声明的时候,它的作用域链就已经被确定了。此时将保存作用域链到[[scope]]中(这也是函数能记住自己出生环境的原因)。 函数的调用 创建自己的执行上下文。 复制[[scope]]属性,创建作用域链。 用arguments创建活动对象,初始化变量。 将活动对象压入作用域顶端。 函数代码块消费作用域链(AO)中的值。 函数的终结(本人臆断) 执行上下文出栈。 销毁未被引用的变量。 专业地讲上代码123456var scope = "global scope";function checkscope(){ var scope2 = 'local scope'; return scope2;}checkscope(); 执行过程 checkscope函数被创建,保存作用域链到[[scope]]。 123checkscope.[[scope]] = [ globalContext.VO]; 执行checkscope函数,创建checkscope函数执行上下文,checkscope函数执行上下文被压入执行上下文栈。 1234ECStack = [ checkscopeContext, globalContext]; checkscope函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链。 123checkscopeContext = { Scope: checkscope.[[scope]],} 第二步:用arguments创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明。 12345678checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }} 第三步:将活动对象压入checkscope作用域链顶端。 123456789checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]]} 准备工作做完,开始执行函数,随着函数的执行,修改AO的属性值。]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>JS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS3变形]]></title>
<url>%2F2017%2F05%2F16%2FCSS-transform%2F</url>
<content type="text"><![CDATA[CSS3的转换是能够启用浏览器硬件加速的一个属性,它也能代替我们常用的一些css属性。但人总有惰性,不愿意舍弃自己熟悉的东西而去使用一个不太熟的事物… 前言通过CSS3转换,我们能够对元素进行移动、缩放、转动、拉长或拉伸。transform属性指一组转换函数,transform-origin属性(常用百分比来表示)定元素的中心点在哪里,新增加了第三个数transform-origin-z,控制元素三维空间中心点。transform-style的值设置为preserve-3d,建立一个3D渲染环境。 2D转换概述2D转换的属性名为transform,使用方法为transform:method(value)。2D转换方法有translate、scale、rotate、skew、matrix,还有基于这些分支出的translateX、scaleY等。 translate(x,y)12345.demo { /* 向右移50px,向下移100px */ /* 分支属性即在方法名后面加大写的“X”或“Y” */ transform:translate(50px,100px);} rotate()1234.demo { /* 顺时针旋转30度 */ transform: rotate(30deg);} scale(x,y)scale方法的参数表示缩放的倍数。当参数值为负值的时候,就出现里翻转现象(展开收起中的上下箭头就可以用这种方式来做)。1234.demo { /* 该元素将会沿纵轴翻转 */ transform:scale(-1,1);} skew()首先纠正一个误区:参数是坐标轴旋转度数。参考坐标系x轴向右正,y向下正。旋转x轴时候,平行于y轴的直线方向不变,依旧水平,但逐渐变短,平行于x的直线跟随x轴转动依旧平行于x轴,但是逐渐变长。反之亦然。 3D转换创建3D模型 模式:transform-style: presserve-3d 视距:perspective: 800 背面可见:backface-visibility: visibility(默认) | hidden translate3d()类似2D中的位移,只是加了一个z轴方向(Z轴上的位移效果类似2D中的缩放)。分写属性:translateX()… rotate3d()这个用分写属性比较多。 rotateX(a)等同于rotate3d(1,0,0,a) rotateY(a)等同于rotate3d(0,1,0,a) rotateZ(a)等同于rotate3d(0,0,1,a)]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
<category>transform</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SASS的基本用法]]></title>
<url>%2F2017%2F05%2F16%2FCSS-scss%2F</url>
<content type="text"><![CDATA[SCSS是目前最流行的一门CSS方言。没办法,只能耍耍了… 基本用法变量12345678910/* 定义变量 */$blue : #1875e7; div { color : $blue; }/* 如果变量要用在字符串中,需要用“#{}”包裹起来。类似ES6的语法 */$side : left; .rounded { border-#{$side}-radius: 5px;} 计算功能123456/* 类似cals计算,单位可以混用 */body { margin: (14px/2); top: 50px + 100px; right: $var * 10%;} 嵌套123456/* 这种在小组件中再好不过了 */div { hi { color:red; }} 属性嵌套123456p { /* 注意:border后面有冒号 */ border: { color: red; }} 在嵌套的代码块内,可以使用&引用父元素。比如a:hover伪类,可以写成:123a { &:hover { color: #ffb3ff; }} 注释 标准的CSS注释 /* comment */ ,会保留到编译后的文件。 单行注释 // comment,只保留在SASS源文件中,编译后被省略。 /*! comment */ 表示这是”重要注释”。即使是压缩模式编译,也会保留这行注释,通常可以用于声明版权信息。 代码的重用继承SASS允许一个选择器,继承另一个选择器。比如,现有class1:12345678.class1 { border: 1px solid #ddd;}/* class2要继承class1,就要使用@extend命令 */.class2 { @extend .class1; font-size:120%;} MixinMixin有点像C语言的宏(macro),是可以重用的代码块。123456789/* 使用@mixin命令,定义一个代码块(可以指定参数和缺省值) */@mixin left($value: 10px) { float: left; margin-right: $value; }/* 使用@include命令,调用这个mixin */div { @include left(20px); } 下面是一个mixin的实例,用来生成浏览器前缀:12345678@mixin rounded($vert, $horz, $radius: 10px) { border-#{$vert}-#{$horz}-radius: $radius; -moz-border-radius-#{$vert}#{$horz}: $radius; -webkit-border-#{$vert}-#{$horz}-radius: $radius;}/* 调用 */#navbar li { @include rounded(top, left); }#footer { @include rounded(top, left, 5px); } 颜色函数SASS提供了一些内置的颜色函数,以便生成系列颜色。 lighten(#cc3, 10%) // #d6d65c darken(#cc3, 10%) // #a3a329 grayscale(#cc3) // #808080 complement(#cc3) // #33c 插入文件123@import "path/filename.scss";/* 如果插入的是.css文件,则等同于css的import命令 */@import "foo.css"; 高级用法条件语句12345@if lightness($color) > 30% { background-color: #000;} @else { background-color: #fff;} 循环语句123456789101112131415161718/* for循环 */@for $i from 1 to 10 { .border-#{$i} { border: #{$i}px solid blue; }}/* while循环 */$i: 6; @while $i > 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }/* each命令,作用与for类似 */@each $member in a, b, c, d { .#{$member} { background-image: url("/image/#{$member}.jpg"); } } 自定义函数1234567@function double($n) { @return $n * 2; }/* 调用 */ #sidebar { width: double(5px); }]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
<category>sass</category>
</categories>
<tags>
<tag>sass</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Flex布局]]></title>
<url>%2F2017%2F05%2F16%2FCSS-flexBox%2F</url>
<content type="text"><![CDATA[Flex布局是必然会代替现行Div+CSS布局的一种布局方式。在现代浏览器中基本都支持… 基本概念采用Flex布局的元素,称为Flex容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称”项目”。容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。 指定容器为Flex布局123456/*设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。*/.box{ display: flex; display: inline-flex; display: -webkit-flex; /* Safari */} 容器的属性flex-direction属性flex-direction属性决定主轴的方向(即项目的排列方向)。1234.box { /*默认是row*/ flex-direction: column-reverse | column | row | row-reverse;} flex-wrap属性默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。123.box{ flex-wrap: nowrap(不换行,默认) | wrap(换行) | wrap-reverse(换行反向);} flex-flowflex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。 justify-content属性justify-content属性定义了项目在主轴上的对齐方式。123.box { justify-content: flex-start(默认) | flex-end | center | space-between | space-around;} align-items属性align-items属性定义项目在交叉轴上如何对齐。123.box { align-items: flex-start(默认) | flex-end | center | baseline | stretch;} align-content属性align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。(子项目占据超过一行)123.box { align-content: flex-start | flex-end | center | space-between | space-around | stretch;} flex-start:与交叉轴的起点对齐。 flex-end:与交叉轴的终点对齐。 center:与交叉轴的中点对齐。 space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。 space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。 stretch(默认值):轴线占满整个交叉轴。 项目的属性order属性order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。 flex-grow属性flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。 flex-shrink属性flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。 flex-basis属性flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。123.item { flex-basis: <length> | auto; /* default auto */} 它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。 flex属性flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。123.item { flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]} 该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。 align-self属性align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。123.item { align-self: auto | flex-start | flex-end | center | baseline | stretch;} 最佳实践 声明flex容器; 将项目排列方向与是否折行这两个属性合并到flex-flow中写; 定义所有子项目在容器中的横向排布以及纵向排布(只有项目超过一行,声明纵向布局才有意义); 通过定义align-items属性来声明每个项目在各自所属的那行空间的纵向排布; 定义项目的flex属性来确定项目在空间多余、空间缺乏和自身的原始宽度; 定义align-self属性,其功效类似容器中的align-items属性。只不过前者作用于所有项目,后者针对当前子项目(如果需要某个子项的行为与与其一行的兄弟不同的话)。 123456789101112131415.container{ display: flex; /* 方向可选值:row(默认) | column-reverse | column | row | row-reverse */ flex-flow: row no-wrap; /* 可选值为:flex-start(默认) | flex-end | center | space-between | space-around */ justify-content: space-around; align-content: space-around; /* 可选值为:flex-start(默认) | flex-end | center | baseline | stretch */ align-items: flex-start;}.item{ /* 扩展默认值为0(不扩展),收缩默认值为1(收缩),宽度默认值为auto */ flex: 1 0 20%; align-self: auto | flex-start | flex-end | center | baseline | stretch;}]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
</categories>
<tags>
<tag>flex</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS基础样式库]]></title>
<url>%2F2017%2F05%2F15%2FCSS-fundationStyle%2F</url>
<content type="text"><![CDATA[本文列举了一些我们常常需要用到的css基础样式库。这样既保证代码的精简,又保证开发的效率。我个人是极喜欢的… 前言国内一位大神通读Normalize.css源码,并且将Normalize.css进行了更细粒度的划分放入Koala这个项目中。非常符合我的心意。感谢@Alsiso。原文地址 样式重置方案normal.cssNormalize.css不仅统一了样式,还保留元素的可辨识性,这是我们应该继承和发扬的优点,normal.css也会参考借鉴Normalize.css所有优势,不过Normalize.css更简洁,让开发者更容易上手,并且对它进行瘦身,比如移除一些不会用到的元素标签hgroup,将一些元素进行分模块管理,比如html5.css,form.css等,方便按需求灵活引用。 调整内容 字体约定62.5%,方便单位转换 统一元素的内外边距 设置全局字体,修复表单元素不继承父级font的问题 添加链接基本样式 移除列表元素的默认标识 移除元素默认边框 移除链接默认的下划线 移除单元格间距让其边重合 修复th不继承text-align,默认左对齐 重置标题,采用自定义 把所有斜体标签默认扶正 统一引用标记 统一上标和下标 HTML5元素html5.csshtml5.css主要是用于解决html5新元素在旧浏览器中的不识别,并且修复一些元素存在的隐性问题。 调整内容 修复HTML5新元素不能正确显示的问题 修复progress元素的对其问题 修复没有controls属性的audio显示出来 修复hidden属性不起作用的问题 修复svg元素overflow不正常的问题 统一mark标签的样式 修复拖动元素添加拖动的光标 表单元素form.cssform.css修复表单元素在不同浏览器下的默认样式,尤其是IE低浏览器版本下的一些怪异问题;并且还修复了一些表单显示状态,致力于提升用户体验。button按钮在网页中是最常用的基础元件,但是不同浏览器下按钮的默认样式千奇百怪,而且表现形式过于单一,所以考虑在form.css里内置了一套按钮组件,提供几种表现场景,并且可以和下面要介绍的iconfont.css搭配使用。 调整内容(form.css) 统一fieldset元素的显示样式 修复’legend’元素的若干问题 修复表单元素字体与字号不继承的问题 统一表单元素的垂直对其方式 统一表单元素的overflow属性为visible 重置按钮禁用时光标样式 修复checkbox,radio的属性box-sizing: border-box; 统一button,input内边距和内边框 统一select的样式 修复textarea只能为纵向拉伸 调整内容(ui-btn按钮组件包含的内容) 初始化默认按钮样式,增加不同状态下的效果 提供多种场景按钮,如主要,警告与错误 提供可定制的大小按钮,如较小,更小,一般,较大,更大 提供组合式按钮 支持iconfont.css,搭配图标按钮使用 使用示例1<button type="button" class="ui-btn">默认</button> 栅格系统grid.css借鉴了Bootstrap的一套响应式流式栅格布局系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。对栅格系统的样式命名进行重新组织,简化和移除工具代码,只保留核心布局样式。 调整内容 .ui-grid-container(固定宽度)和.ui-grid-fluid(100%宽度)用于包裹.ui-row .ui-grid-container(固定宽度)通过媒体查询来实现响应式宽度 .ui-row用于包裹一组.ui-col-1-.ui-col-12列 xs,sm,lg通过媒体查询来实现响应式 .ui-col-xs-* 超小屏幕 手机 (<768px) .ui-col-sm-* 小屏幕 平板 (≥768px) .ui-col-* (默认)中等屏幕 桌面显示器 (≥992px) .ui-col-lg-* 大屏幕 大桌面显示器 (≥1200px) 支持列嵌套,必须包裹在.ui-row行中 简化代码,不支持列偏移,列排序 使用示例123456<div class="ui-grid-fluid"> <div class="ui-row"> <div class="ui-col-8">.ui-col-8</div> <div class="ui-col-4">.ui-col-8</div> </div></div> 辅助工具utils.css提供最常用的功能类class,命名使用fn-前缀来进行区别表示,如果在项目中能够灵活复用这些类,那将大大提升开发效率。 调整内容 浮动元素与清楚浮动元素 垂直与水平滚动 元素显示类型 元素与文本隐藏 文本不换行 文本强制换行 文本溢出显示省略号 文本左右对齐 文本垂直对齐 常用符号(关闭,箭头,下三角等) 自适应两端对齐 未知高度垂直居中 列表平铺 使用示例12<!-- 文字溢出显示省略号 --><div class="fn-text-ellipsis">文字那是相当的长</div> 字体图标iconfont.cssiconfont都已经很熟悉了,是一种把图标放入自定义字体中,然后使用字体图标来替代普通图标的技术,使用起来方便灵活。 包含内容 提供网页中66个最常用的字体图标 可搭配form.css按钮组件使用 使用示例1<button class="ui-btn"><b class="iconfont">&#126;</b>提交</button> 动画库animate.css这个在动画与过度一文中有详细讲到,此处暂且不提。 打印print.css可以优化打印出的网页更适合浏览,并且加快打印速度,节省打印机耗材。 调整的内容 修复打印时的背景和文字颜色 删除所有的阴影效果 标注超链接,并显示URL链接]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
<category>CSS库</category>
</categories>
<tags>
<tag>CSS库</tag>
</tags>
</entry>
<entry>
<title><![CDATA[动画与过渡]]></title>
<url>%2F2017%2F05%2F15%2FCSS-css3-animation%2F</url>
<content type="text"><![CDATA[如果说css里面有什么是最好玩的,那我觉得毫无疑问该是css里边的动画了… 关键帧动画定义@keyframes123456@keyframes 动画名称{ /* 时间点可以用百分比来表示 */ 时间点 {元素状态} 时间点 {元素状态} …} 使用关键帧动画123div{ animation: 动画名称 持续时间 ...;} CSS3动画属性 animation:所有动画属性的简写属性,除了animation-play-state属性(推荐使用这种属性简写方式)。 animation-name:规定@keyframes动画的名称。 animation-duration:规定动画完成一个周期所花费的秒或毫秒。默认是0。 animation-timing-function:规定动画的速度曲线。默认是”ease”。 animation-delay:规定动画何时开始。默认是0。 animation-iteration-count:规定动画被播放的次数。默认是1。 animation-direction:规定动画是否在下一周期逆向地播放。默认是”normal”。 animation-play-state:规定动画是否正在运行或暂停。默认是”running”。 animation-fill-mode:规定对象动画时间之外的状态。 过渡概述过渡可以让一个元素从一种状态变平滑过渡为为另一种状态。这有点像声明式,我们只需要声明元素的初始状态和结束状态,至于这两个状态的过程,我们不必关心。 场景 通过与用户的交互来改变元素状态,如:hover、:link等。 通过JS的方式来改变元素状态,如:$(.dome).addClass(‘className’)。 定义和用法123/* 这里使用的是简写属性 *//* 多个属性要写的话,用逗号隔开 */transition: property duration timing-function delay; animate.css安装1npm install animate.css --save 引入1<link rel="stylesheet" href="animate.min.css"> 使用 添加通用类名:animated(必选)、infinite(可选)。 添加动画类名:查看不同类名效果。]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
<category>css3</category>
</categories>
<tags>
<tag>动画</tag>
<tag>过渡</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS选择器]]></title>
<url>%2F2017%2F05%2F15%2FCSS-selector%2F</url>
<content type="text"><![CDATA[高级的css选择器虽说效率不怎么高,但是确实很强大。在一些不用太过在意性能的情况下用起来还是很爽的… 属性选择器示例:123p[name='test'] { //} 伪选择器nth-child示例:123ul li:nth-child(1) { //} nth-child()括号里面可以是数字、n、odd、even,个人感觉还是n最强大。还有两个与之相关的选择器:first-child和last-child。 nth-of-type:nth-of-type类似于:nth-child,不同的是他只计算选择器中指定的那个元素,其实我们前面的实例都是指定了具体的元素,这个选择器主要对用来定位元素中包含了好多不同类型的元素是很有用处,比如说,我们div.demo下有好多p元素,li元素,img元素等,但我只需要选择p元素,并让他每隔一个p元素就有不同的样式,那我们就可以简单的写成:1.demo p:nth-of-type(even) {background-color: lime;} 动态伪类动态伪类,因为这些伪类并不存在于HTML中,而只有当用户和网站交互的时候才能体现出来,动态伪类包含两种,第一种是我们在链接中常看到的锚点伪类,如”:link”,”:visited”;另外一种被称作用户行为伪类,如“:hover”,”:active”和”:focus”。示例如下:1234.demo a:link {color:gray;}/*链接没有被访问时前景色为灰色*/.demo a:visited{color:yellow;}/*链接被访问过后前景色为黄色*/.demo a:hover{color:green;}/*鼠标悬浮在链接上时前景色为绿色*/.demo a:active{color:blue;}/*鼠标点中激活链接那一下前景色为蓝色*/ UI元素状态伪类:checked、:unchecked、:disabled、:enabled这几个选择器在我们作表单验证时改变表单样式时特别有用。 否定选择器示例:1input:not([type="submit"]) {border: 1px solid red;} 伪元素::first-line选择元素的第一行。比如说改变每个段落的第一行文本的样式,我们就可以使用这个:1p::first-line {font-weight:bold;} ::first-letter选择文本块的第一个字母。比如说首字下沉:1p::first-letter {font-size: 56px;float:left;margin-right:3px;} ::before和::after这两个主要用来给元素的前面或后面插入内容,这两个常用”content”配合使用。比如清除浮动:123456789.clearfix:before,.clearfix:after { content: "."; display: block; height: 0; visibility: hidden;}.clearfix:after {clear: both;}.clearfix {zoom: 1;}]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
<category>selector</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS长度描述]]></title>
<url>%2F2017%2F05%2F15%2FCSS-css3(1)%2F</url>
<content type="text"><![CDATA[多久没有好好整理css知识了,这篇css3作为css的开山大篇,我希望这是一个好的开始… cals()概述cals()是作为长度单位的属性值来使用的。主要用来在css中计算长度属性。 示例12/*用100%减去左右两个20px的padding和2px的border*/width:calc(100% - (20px + 2px) * 2); 要点总结 兼容性:在IE9+、FF4.0+、Chrome19+、Safari6+支持较好,移动端支持不理想; 表达式支持加、减、乘、除运算,同时也支持单位的混合使用(%、px、em等); 表达式中有“+”,“-”运算符的,前后必须要有空格。 长度单位em说明相对长度单位。相对于当前对象内文本的字体尺寸(建议全部都用rem来代替)。 使用场景 整体页面尺寸随着字体尺寸的变化而变化。 给页面设置整体的左右边距,个人感觉比用百分比要好些。 ex说明相对长度单位。相对于字符“x”的高度。通常为字体高度的一半。 使用场景 当我们需要设置一段文字,超过两行就省略后面的字符,这时候用ex限制容器元素的高度。 rem说明相对长度单位。相对于根元素(即html元素)font-size计算值的倍数(与em的差别只在它们的参照尺寸不一样)。 使用场景建议用rem来代替em。 vm说明相对于视口的宽度。视口被均分为100单位的vw。 区别百分比两者相对的对象不一样。百分比相对窗口(不含滚动条),vm相对视口(含滚动条)。 使用场景按需加载,滚动条由无变有时会有页面的跳动问题,解决方案如下:1234567.wrap-outer {margin-left: calc(100vw - 100%);}/* 或者 */.wrap-outer {padding-left: calc(100vw - 100%);}]]></content>
<categories>
<category>前端</category>
<category>CSS</category>
</categories>
<tags>
<tag>css</tag>
</tags>
</entry>
<entry>
<title><![CDATA[react-router]]></title>
<url>%2F2017%2F05%2F14%2FJS-react-reactRouter%2F</url>
<content type="text"><![CDATA[前几天苦修react大法,然后从github上拉了一个简单的react项目吗,准备挑个‘软柿子’捏一捏。结果一看人家代码,发现还有很多陌生的面孔,甚至连路由都是一知半解的… 路由的定义基本定义123456789import { Router, Route, hashHistory } from 'react-router';// 路由本质上也是一个组件,这些组件在这里是作为根级组件来使用的。当然,我们也可以将它放在一个容器组件里面。任何时候都不要忘记,路由也是一个组件。render(( <Router history={hashHistory}> <Route path="/" component={App}/> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Router>), document.getElementById('app')); 嵌套路由12345678910111213141516<Router history={hashHistory}> <Route path="/" component={App}> // 下面的子组件要被通过"his.props.children"来呈现 <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Route></Router>// App组件要写成下面的样子。export default React.createClass({ render() { return <div> // 子组件将被呈现在此处 {this.props.children} </div> }}) 将路由配置拆分子路由也可以不写在Router组件里面,单独传入Router组件的routes属性:123456let routes = <Route path="/" component={App}> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/></Route>;<Router routes={routes} history={browserHistory}/> path属性Route组件的path属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件。1234567<Route path="inbox" component={Inbox}> <Route path="messages/:id" component={Message} /></Route>// 与下面这种等价<Route component={Inbox}> // 这里省略了path属性,所以不管怎么匹配都会加载Inbox组件 <Route path="inbox/messages/:id" component={Message} /></Route> 通配符 paramName:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出。 ()()表示URL的这个部分是可选的。 ``匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式。 匹配任意字符,直到下一个/、?、#为止。匹配方式是贪婪模式。 注:path属性也可以使用相对路径(不以/开头),匹配时就会相对于父组件的路径,可以参考上一节的例子。嵌套路由如果想摆脱这个规则,可以使用绝对路由。 IndexRoute组件首先看这段代码:123456789101112131415// 这里访问根路径"/",不会加载任何子组件。<Router> <Route path="/" component={App}> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route></Router>// 下面是解决方案,访问根路径"/",会默认加载Home子组件。(这也是项目中推荐使用的一种方式)<Router> <Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route></Router> Redirect组件<Redirect>组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由。1234<Route path="inbox" component={Inbox}> /* 从 /inbox/messages/:id 跳转到 /messages/:id */ <Redirect from="messages/:id" to="/messages/:id" /></Route> IndexRedirect组件IndexRedirect组件用于访问根路由的时候,将用户重定向到某个子组件。123456// 这跟IndexRoute组件是有区别的<Route path="/" component={App}> <IndexRedirect to="/welcome" /> <Route path="welcome" component={Welcome} /> <Route path="about" component={About} /></Route> 路由跳转Link组件12345<li><Link to="/about">About</Link></li><Link to="/about" activeStyle={{color: 'red'}}>About</Link><Link to="/about" activeClassName="active">About</Link> 在Router组件之外,导航到路由页面12import { browserHistory } from 'react-router';browserHistory.push('/some/path'); IndexLink如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件。这是因为对于根路由来说,activeStyle和activeClassName会失效,或者说总是生效,因为/会匹配任何子路由。而IndexLink组件会使用路径的精确匹配。12345678// 根路由只会在精确匹配时,才具有activeClassName。<IndexLink to="/" activeClassName="active"> Home</IndexLink>// 使用Link组件的onlyActiveOnIndex属性,也能达到同样效果。<Link to="/" activeClassName="active" onlyActiveOnIndex={true}> Home</Link> 周边histroy属性 browserHistory :以hash的方式在URL上面呈递。 hashHistory :调用浏览器的historyAPI。但是,这种情况需要对服务器改造。 createMemoryHistory :主要用于服务器渲染。它创建一个内存中的history对象,不与浏览器URL互动。 JS跳转 browserHistory.push(): 123import { browserHistory } from 'react-router'const path = `/repos/${userName}/${repo}`browserHistory.push(path) 使用context对象 12345678910export default React.createClass({ // ask for `router` from context contextTypes: { router: React.PropTypes.object }, handleSubmit(event) { // ... this.context.router.push(path) },}) 路由钩子每个路由都有Enter和Leave钩子,用户进入或离开该路由时触发。下面是一个例子,使用onEnter钩子替代组件:12345678<Route path="inbox" component={Inbox}> <Route path="messages/:id" onEnter={ ({params}, replace) => replace(`/messages/${params.id}`) } /></Route> onEnter钩子还可以用来做认证:1234567891011const requireAuth = (nextState, replace) => { if (!auth.isAdmin()) { // Redirect to Home page if not an Admin replace({ pathname: '/' }) }}export const AdminRoutes = () => { return ( <Route path="/admin" component={Admin} onEnter={requireAuth} /> )}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>router</tag>
<tag>react</tag>
</tags>
</entry>
<entry>
<title><![CDATA[fetch]]></title>
<url>%2F2017%2F05%2F10%2Fstandard-fetch%2F</url>
<content type="text"><![CDATA[认识到fetch是一次很偶然的时机。那是在阮一峰讲解redux中的异步操作中使用到了fetch,当时我很费解,这货怎么能直接使用then()方法呢… 简介fetch是w3c推出来的用来取代ajax的一种标准。 fetch的使用一般构造请求的方法使用 fetch 的构造函数请求数据后,返回一个 Promise 对象,处理即可。1234fetch(URL) .then(function(response){ // do something...}) fetch构成函数的其他选项1234567891011121314151617var myHeaders = new Headers();myHeaders.append("Content-Type", "text/plain");myHeaders.append("Content-Length", content.length.toString());myHeaders.append("X-Custom-Header", "ProcessThisImmediately");myHeaders.append("Cache-Control", "no-cache"); // 每次 API 的请求我们想不受缓存的影响var myInit = { method: 'GET', // 这里切换请求方式 headers: myHeaders, mode: 'cors', cache: 'default'};fetch(URL, myInit).then(function(response){ // do something...}) 返回的数据结构常用的数据有: Response.status也就是StatusCode,如成功就是200。 Response.statusText是StatusCode的描述文本,如成功就是OK。 Response.ok一个Boolean类型的值,判断是否正常返回,也就是StatusCode为200-299。 Body参数因为在Request和Response中都包含Body的实现,所以包含以下类型:ArrayBuffer、 ArrayBufferView (Uint8Array and friends)、 Blob/File、 string、 URLSearchParams、 FormData。在fetch中实现了对应的方法,并返回的都是Promise类型:arrayBuffer()、 blob()、 json()、 text()、 formData() 兼容性问题原生支持率并不高,幸运的是,引入下面这些polyfill后可以完美支持 IE8+ : 由于IE8是ES3,需要引入ES5的polyfill: es5-shim, es5-sham 引入Promise的polyfill: es6-promise 引入fetch探测库:fetch-detector 引入fetch的polyfill: fetch-ie8 可选:如果你还使用了jsonp,引入 fetch-jsonp 可选:开启Babel的runtime模式,现在就使用async/await]]></content>
<categories>
<category>前端</category>
<category>fetch</category>
</categories>
<tags>
<tag>fetch</tag>
</tags>
</entry>
<entry>
<title><![CDATA[VUE入门]]></title>
<url>%2F2017%2F05%2F09%2FJS-vue-fundation%2F</url>
<content type="text"><![CDATA[貌似最近耍react和redux耍得比较多,vue已经有了要淡忘的趋势。所以,赶紧的,vue耍起来… 前言本文旨在提醒自己在vue中容易混淆和淡忘的点。所以此处略过vue的基础知识。 Vue实例Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀$,以便与代理的data属性区分。 插值语法添加含标签的插值双大括号会将数据解释为纯文本,而非HTML。为了输出真正的HTML,你需要使用v-html指令。 给标签属性绑定动态值差值不能在标签里面使用,要在标签里面绑定动态值,需要使用v-bind指令。1<div v-bind:id="动态ID"></div> 指令指令的:指令通过后面的:来接收参数(最常用的便是动态绑定标签属性)。 指令的.指令的.(修饰符)用于指出一个指令应该以某种特殊的方式绑定。1<form v-on:submit.prevent="onSubmit"></form> 过滤器(管道)过滤器允许用在两个地方:mustache插值和v-bind表达式。它只适合处理文本转换,更加复杂的转换需要用到计算属性。 改变样式通过class改变样式使用对象的形式绑定class123456789// 这个对象可以直接写在ng-bind:class中,也可以写在data属性中,但是,放在计算属性中无疑是最强大的computed: { classObject: function () { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal', } }} 使用数组的形式绑定class1234<!-- 数组里面使用三元表达式 --><div v-bind:class="[isActive ? activeClass : '', errorClass]"><!-- 数组里面使用对象方式 --><div v-bind:class="[{ active: isActive }, errorClass]"> 通过内联样式改变样式对象语法常常结合返回对象的计算属性使用。v-bind:style的数组语法可以将多个样式对象应用到一个元素上。 列表渲染列表渲染中的参数12345678<!-- 循环数组 --><div v-for="(item, index) in items"> {{ index }} - {{ index }}</div><!-- 循环对象 --><div v-for="(value, key, index) in object"> {{ index }} - {{ key }} - {{ value }}</div> 循环渲染大段内容只需在这段内容的最外层包一层<template> ~ </template>标签即可。 整数迭代 v-for1<span v-for="n in 10">{{ n }}</span> 显示过滤/排序结果1234<!-- evenNumbers是计算属性,这个属性过滤了某些值 --><li v-for="n in evenNumbers">{{ n }}</li><!-- even()这个方法过滤了某些值 --><li v-for="n in even(numbers)">{{ n }}</li> 事件处理在事件处理函数中访问原生DOM对象有时也需要在内联语句处理器中访问原生DOM事件。可以用特殊变量$event把它传入处理事件的回调函数。 事件修饰符直接上代码:1234567891011121314<!-- 阻止单击事件冒泡 --><a v-on:click.stop="doThis"></a><!-- 提交事件不再重载页面 --><form v-on:submit.prevent="onSubmit"></form><!-- 修饰符可以串联 --><a v-on:click.stop.prevent="doThat"></a><!-- 只有修饰符 --><form v-on:submit.prevent></form><!-- 添加事件侦听器时使用事件捕获模式 --><div v-on:click.capture="doThis">...</div><!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 --><div v-on:click.self="doThat">...</div><!-- 点击事件将只会触发一次 --><a v-on:click.once="doThis"></a> 按键修饰符123<!-- 根据键值调用 vm.submit() --><input @keyup.enter="submit"><!-- 这是一些别名.enter.tab.delete.esc.space.up.down.left.right --> 表单修饰符123456<!-- 在 "change" 而不是 "input" 事件中更新 --><input v-model.lazy="msg" ><!-- 将用户输入的值变为数字类型 --><input v-model.number="age" type="number"><!-- 过滤用户输入的前后空格 --><input v-model.trim="msg"> 组件全局注册1234// 定义组件时推荐使用‘-’命名Vue.component('my-component', { // 选项}) 局部注册12345678910var Child = { template: '<div>A custom component!</div>'}new Vue({ // ... components: { // <my-component> 将只在父模板可用 'my-component': Child }}) data必须是函数123456// 每创建一个组件实例就返回新的data,不然会影响其他实例data: function () { return { counter: 0 }} 使用Prop传递数据1234567Vue.component('child', { // 声明 props(这种方式与angular2蛮像的) props: ['message'], // 就像 data 一样,prop 可以用在模板内 // 同样也可以在 vm 实例中像 “this.message” 这样使用 template: '<span>{{ message }}</span>'}) Prop验证12345678910111213141516171819202122232425262728293031Vue.component('example', { props: { // 基础类型检测 (`null` 意思是任何类型都可以) propA: Number, // 多种类型 propB: [String, Number], // 必传且是字符串 propC: { type: String, required: true }, // 数字,有默认值 propD: { type: Number, default: 100 }, // 数组/对象的默认值应当由一个工厂函数返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { return value > 10 } } }}) 使用v-on绑定自定义事件(子向父传参) 使用$on(eventName)监听事件。 使用$emit(eventName)触发事件。 单个Slot在父组件中传入一堆内容,这堆内容将会在子组件中的标签位置上显示。这样可以让子组件呈现更多样化的展示。 具名Slot1234<!-- 这个h1标签将会被插入到name属性为header的slot标签上 --><h1 slot="header">这里可能是一个页面标题</h1><!-- 这个定义在自组件上 --><slot name="header"></slot> keep-alive如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数。 作用域插槽123456789101112131415161718192021<!-- 子组件 --><div class="child"> <slot text="hello from child"></slot></div><!-- 父组件 --><div class="parent"> <child> <!-- 这个props可以接收子组件的prop对象 --> <template scope="props"> <span>hello from parent</span> <span>{{ props.text }}</span> </template> </child></div><!-- 渲染之后得到 --><div class="parent"> <div class="child"> <span>hello from parent</span> <span>hello from child</span> </div></div>]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>VUE</category>
</categories>
<tags>
<tag>VUE</tag>
</tags>
</entry>
<entry>
<title><![CDATA[react入门]]></title>
<url>%2F2017%2F05%2F09%2FJS-react%2F</url>
<content type="text"><![CDATA[想对react下手已经不是一天两天的事儿了,今日便将心一横… react的两个核心方法1234567891011// 渲染方法ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root'));// 定义组件类(ES6的写法)class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; }} JSX语法JSX语法支持html与js混着写。遇见<解析为html,遇见{解析为javascript。注: 如果{}里面放了javascript数组,JSX会将该数组展开渲染。 给标签设置动态属性值,只需要将属性值用{}包起来。 在JSX里面class要变为className。 组件类组件类的render方法12345678// render方法里面返回一些标签render() { return ( <div> <h1>Hello, world!</h1> </div> );} 组件类的propsreact里面的props是只读的,由父组件传给子组件。子组件通过this.props.xx来得到props。初始化默认的props:12// 这个写在组件类的外面组件类名.defaultProps = { initialCount: 0 }; 组件类的state修改state要用this.setState({comment: 'Hello'});(这种方式会自动触发视图渲染)。 组件类的构造函数12345// 构造函数可以用来初始化props的值constructor(props) { super(props); // 调一次父类的构造函数 this.state = {date: new Date()};} 组件的生命周期 三种状态Mount、Update、Unmount。 两种特殊状态的处理函数componentWillReceiveProps(object nextProps)(已加载组件收到新的参数时调用);shouldComponentUpdate(object nextProps, object nextState)(组件判断是否重新渲染时调用)。 事件处理 在ES6写法的组件类中,方法的绑定需要用bind()方法绑定this。 给事件处理函数传递额外参数的方式:bind(this, arg1, arg2, ...)。 使用原生事件的时候注意在componentWillUnmount解除绑定 removeEventListener。 渲染列表善用数组的map()方法12345const numbers = [1, 2, 3, 4, 5];// map()的第二个参数可以用来作为key的属性值const listItems = numbers.map((number,index) => <li>{number}</li>); 列表中的key属性如果循环列表时没有给列表的每一项添加key属性,react会有警告。加上key属性有利于提高react的性能。注:key并不要求在全局独一无二。 表单获取输入框、文本域、下拉选项中的值123handleChange(event) { this.setState({value: event.target.value});} 表单默认值使用defaultValue和defaultChecked代替value和checked。 处理多个表单元素1234567891011121314handleInputChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; // 这里的name就是每个表单元素的name属性 const name = target.name; this.setState({ [name]: value }); // 等价与这种写法 var partialState = {}; partialState[name] = value; this.setState(partialState);}]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>react</tag>
</tags>
</entry>
<entry>
<title><![CDATA[npm]]></title>
<url>%2F2017%2F05%2F08%2Ftools-node-npm%2F</url>
<content type="text"><![CDATA[本文旨在拾起一些实用但是容易被忽略的npm小知识… 安装制定版本的包1$ npm install <packageName>@1.7.13 更新包1$ npm update <packageName> 清空本地的缓存包1$ npm cache clean 从本地缓存中现在包12# 意味着9999999分钟之后才上网下载包$ npm install --cache-min 9999999 <package-name> 针对国内的特殊网络环境123// 修改node安装目录的npmrc配置文件,添加如下代码registry = https://registry.npm.taobao.orgsass_binary_site=https://npm.taobao.org/mirrors/node-sass/]]></content>
<categories>
<category>开发工具</category>
<category>npm</category>
</categories>
<tags>
<tag>npm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[redux]]></title>
<url>%2F2017%2F05%2F08%2FJS-react-redux%2F</url>
<content type="text"><![CDATA[又是一个无味的星期一。看了一上午的redux相关知识,每次去看,几乎每次都有新的收获。这让我既是开心又是无奈… redux核心整体感知用我自己浅陋的理解来叙一叙吧。redux是一个专门用来管理应用状态的库。它的核心是store,围绕着store有state(一个大对象,包含所有状态数据),action(描述一个动作),reducer(根据不同类型的action来得到新的state)。它的工作流程是:①store.dispatch(‘ACTION1’)–>②自动触发reducer函数,返回新的state–>③render函数订阅state的变化。 store创建store时传入的参数有reducer函数,初始状态值,中间件。store主要提供了一些函数:123456// 获取应用的状态数据store.getState()// 派发一个动作,从而触发reducerstore.dispatch()// 参数里面放state改变后的操作,如重新渲染视图store.subscribe() actionaction是一个普通的javascript对象,它必须包含type属性来区分不同的action。我们一般不会用字面量的方式去创建action,而是通过一个action生成函数来创建action。这个actionCreator函数可以做一些比较猥琐的事儿。这是后话,暂且不提。 reducerreducer是一个纯函数。里面是判断action的类型,根据原始state和传入的action生成新的state的具体操作。 reducer的拆分当state特别大时,我们需要对reducer进行拆分(拆分的是state的直系属性)。1234567891011121314151617import { combineReducers } from 'redux';const totalReducer = combineReducers({ stateA : reducerA, stateB : reducerB, stateC : reducerC})// 等同于/*function totalReducer(state = {}, action) { return { stateA: reducerA(state.a, action), stateB: reducerB(state.b, action), stateC: reducerC(state.c, action) }}*/export default totalReducer; redux中间件为什么需要redux的中间件redux中间件是用来增强store.dispatch()的。原始的dispatch方法只能接收一个action对象作为参数。但是使用了中间件之后,dispatch方法可以接收函数、promise对象等作为参数。 中间件的用法12345678import { applyMiddleware, createStore } from 'redux';import createLogger from 'redux-logger';const logger = createLogger();const store = createStore(reducer,applyMiddleware(logger)); redux-thunk中间件Action Creator的写法如下:12345678910111213// fetchPosts函数返回了一个函数const fetchPosts = postTitle => // 这个就是返回的函数 (dispatch) => { // dispatch一个同步的action,表示操作开始 dispatch(requestPosts(postTitle)); // 使用fetch发送异步请求(目测fetch里面可以直接使用then,而且每一次用到fetch时,都是return打头阵), return fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) // 派发结束action .then(json => dispatch(receivePosts(postTitle, json))); }; }; redux-thunk中间件的用法:①在创建store的时候传入applyMiddleware(thunk)–>②派发动作store.dispatch(fetchPosts('reactjs'))(dispatch之后还可以继续.then()) redux-promise中间件redux-promise中间件使dispatch支持promise对象作为参数。 Action Creator的写法一:12345678const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => { type: 'FETCH_POSTS', payload: response.json() });}); Action Creator的写法二:123456dispatch(createAction( 'FETCH_POSTS', // 这个action的payload为promise对象 fetch(`/some/API/${postTitle}.json`) .then(response => response.json()))); react-reduxUI组件(木偶组件)负责渲染页面,数据全部由props提供,没有自己的state。 容器组件由react-redux生成,有自己的状态,管理数据和业务逻辑。 connect方法用于根据UI组件生成容器组件。12345import { connect } from 'react-redux'const VisibleTodoList = connect( mapStateToProps, // 建立state到props的映射 mapDispatchToProps // 建立dispatch到props的映射)(TodoList) // TodoList为UI组件 mapStateToProps()123456const mapStateToProps = (state) => { return { // todos就是UI组件里面的同名参数,getVisibleTodos根据state算出todos的值 todos: getVisibleTodos(state.todos, state.visibilityFilter) }} 补充:mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。 mapDispatchToProps()写法一(函数形式)12345678910111213const mapDispatchToProps = ( dispatch, ownProps // 容器组件的props对象) => { return { onClick: () => { // onClick为UI组件里的同名参数 dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } };} 写法二(对象形式)1234567// 返回的 Action 会由 Redux 自动发出const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter };} 组件在根组件外面包一层,使得所有子组件通过this.context得到store对象。 一个真实项目中的应用入口文件12345678910111213141516// index.jsximport {render} from 'react-dom'import {Provider} from 'react-redux'// 这里是将store封装了一层,调用configureStore()得到storeimport configureStore from './store/configureStore'...const store = configureStore()// 这里用了react-router 4.x 版本render( <Provider store={store}> <HashRouter basename="/"> <AppContainer /> </HashRouter> </Provider>, document.body.appendChild(document.createElement('div'))) 定义action的名字12// app/contants/userinfo.jsexport const USERINFO_UPDATE = "USERINFO_UPDATE"; // 后面还有许多 定义action生成函数12345678// app/actions/userinfo.jsimport * as userTypes from '../constants/userinfo'export function update(data){ return { type:userTypes.USERINFO_UPDATE, data }} 定义reducers12345678910// app/reducers/userinfo.jsimport * as userTypes from '../constants/userinfo'export default function userinfo(state={},action){ switch(action.type) { case userTypes.USERINFO_UPDATE: return action.data; default: return state; }} 生成store1234567891011// app/store/configureStore.jsimport { createStore } from 'redux'import rootReducer from '../reducers'export default function configureStore(initialState){ const store = createStore(rootReducer,initialState, // 目测这里是用来配合浏览器的redux开发者工具的 window.devToolsExtension ? window.devToolsExtension() : undefined ) return store} 配合react-redux12345678910111213141516171819202122232425// app/appContainer.jsx,导入action生成函数import { bindActionCreators } from 'redux'import { connect } from 'react-redux'import * as userInfoActionsFromOtherFiles from './actions/userinfo.js' // 在这个根组件中,只需要拿到action生成函数就好了// 调用redux中的action生成函数componentDidMount(){ this.props.userInfoActions.update({ cityName:cityName })}// 将action生成函数与redux里的state数据映射到组件的props上function mapStateToProps(state){ return { ... }}function mapDispatchToProps(dispatch){ return { userInfoActions:bindActionCreators(userInfoActionsFromOtherFiles,dispatch) }}export default connect( mapStateToProps, mapDispatchToProps)(AppContainer) redux亲手填坑redux相关目录|redux—-|constants——–|module1.js(action名常量)——–|…—-|actions——–|module1.js(生成action对象的工厂函数)——–|…—-|reducers——–|module1.js——–|…——–|index.js(合并各个reducer)—-|index.js(创建store,传入reducer) constants12// 定义action的名字(module1.js)export const ADD = "ADD"; actions1234567import * as module1Types from '../constants/module1.js'export function add(data){ return { type:module1Types.ADD, data }} reducers1234567891011/* module1.js */import * as module1Types from '../constants/module1.js'// 建议在每个reducer中传入这个模块相对应的那部分state,state的拆分对应reducer的拆分export default function app(state=25, action){ switch(action.type) { case module1Types.ADD: return state + 1; default: return state; }} 1234567/* index.js */import {combineReducers} from 'redux'import module1 from './module1.js'export default combineReducers({ // 这里实际上是将全局的state的module1部分交给名字叫module1的reducer来处理 module1}) 最外层定义store的index.js12345678910import { createStore } from 'redux'import rootReducer from './reducers'// 如果初始化的state已经在reducer时被传入,那么这里的initialState是没必要传入的export default function store(initialState){ const store = createStore(rootReducer,initialState, // 这里用来开启浏览器redux的debugger模式的 window.devToolsExtension ? window.devToolsExtension() : undefined ) return store} 在react上注册store12345678910import { Provider } from 'react-redux';import createStore from './store';// 调用最外层定义store的index.js定义的方法生成storeconst store = createStore();// ...<Provider store={store}> <div className="container"> ... </div></Provider> 在组件中与store交互123456789101112131415161718192021222324252627import { connect } from 'react-redux'import { bindActionCreators } from 'redux'// 这里引入的action将来要映射到该组件的props上import * as module1Actions from '../store/actions/module1.js'/* 组件类start */// ...<h3>{this.props.count}</h3><button onClick={this.clickHandle.bind(this)}>点我</button>// ...clickHandle() { this.props.module1ActionList.add({"hehe": 0}); // 这里传入的数据没意义,只是说明通过这种方式传参}/* 组件类end */function mapStateToProps(state) { return { count: state.module1 }}function mapDispatchToProps(dispatch) { return { module1ActionList: bindActionCreators(module1Actions,dispatch) }}export default connect( mapStateToProps, mapDispatchToProps)(Hehe)]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>react</category>
</categories>
<tags>
<tag>redux</tag>
<tag>应用状态</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS 基础进阶(一)]]></title>
<url>%2F2017%2F05%2F04%2FJS-foundation(1)%2F</url>
<content type="text"><![CDATA[本节通过了解JS中变量在计算机中的内存中存储方式来解释JS中的一些难题,按值传递和引用传递… 栈与堆JS中没有严格区分栈内存与堆内存。 栈栈的存取方式就像是往乒乓球盒子里面存放乒乓球。先进后出,后进先出。 堆堆内存的存取方式就像是从书架上拿书。我们只需要知道某本书的名字就可以从书架上准确地拿到该书。 JS中的基础数据类型与引用数据类型的存储变量对象(变量对象其实也是存在堆内存中的,但它比较特殊,所以把它单独提出来) 基础数据类型是统一存储在一个变量对象上的。var a = 1;相当于:变量对象.a = 1; 复制变量对象中的变量时会为新变量分配一个新值,这个值与之前的值没有关系 引用数据类型 引用数据都是存在堆内存中的 一个引用数据的内存由三个部分构成:变量对象中存储变量名与内存地址(内存指针)、堆内存中存储键值对、内存地址指向堆内存中的一个引用类型数据 复制一个引用类型的数据其实是给了新变量一个新的内存地址(这个过程发生在变量对象中)。这个新的内存地址与原来的内存地址指向的是同一个引用类型数据 内存空间管理JS的内存生命周期123var a = 20; // 在内存中给数值变量分配空间alert(a + 100); // 使用内存a = null; // 使用完毕之后,释放内存空间 给a赋值null的目的是让a原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。 在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了。因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量,以确保性能问题。 执行上下文what 执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。 types 全局环境:JS代码执行首先进入全局环境。这个环境最先入栈,最后出栈。 函数环境:当函数被调用执行时,会进入当前函数中执行代码(不是定义时,是被调用时)。 eval环境:现在基本不用,所以不用考虑。 characteristic 单线程 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待。 全局的上下文只有一个,它在浏览器窗口关闭时出栈。 函数的执行上下文没有个数限制。 每次某个函数被调用,就会用一个新的执行上下文为其创建。即使是调用的它自身。 执行上下文与闭包 它的执行过程是:全局环境入栈-->外层函数入栈-->外层函数出栈-->内层函数入栈-->内层函数出栈-->全局环境出栈。内层函数会记住外层函数的变量对象,从而实现闭包的效果。 变量对象 当一个函数被调用时,就会创建一个新的执行上下文。一个的执行上下文的生命周期分为创建阶段和执行阶段。创建阶段:生成变量对象-->建立作用域链-->确定this的指向;执行阶段:变量赋值-->函数引用-->执行其他代码。 变量对象(Variable Object)的创建过程 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。 注:未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。变量对象和活动对象其实是同一个对象,只是处于执行上下文的不同生命周期。 作用域链按照我的理解,作用域链是由多个变量对象所组成的一个数组。数组的第一个元素是当前执行上下文的变量对象,最后一个元素是全局环境的变量对象(window对象下的变量)。作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。 JS中的this this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。 函数中的this在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。 使用call,apply显示指定thisJavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。下面举一个利用call,apply实现继承的方式:1234567891011121314151617181920// 定义父级的构造函数var Person = function(name, age) { this.name = name; this.age = age; this.gender = ['man', 'woman'];}// 定义子类的构造函数var Student = function(name, age, high) { // use call Person.call(this, name, age); this.high = high;}// Student的构造函数等同于下var Student = function(name, age, high) { this.name = name; this.age = age; this.gender = ['man', 'woman']; // Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承 this.high = high;} 注:匿名函数经常会导致this丢失,可以用bind方法来解决。 构造函数与原型方法上的thisnew一个对象的过程: 创建一个新的对象。 将构造函数的this指向这个新对象。 指向构造函数的代码,为这个对象添加属性,方法等。返回新对象。原型方法上的this原型方法上的this就好理解多了。谁调用的,this就指向谁。]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>js</tag>
<tag>堆栈</tag>
<tag>内存</tag>
</tags>
</entry>
<entry>
<title><![CDATA[vuex]]></title>
<url>%2F2017%2F04%2F26%2FJS-vue-vuex%2F</url>
<content type="text"><![CDATA[凌晨四点多惊醒,杂事纷呈,再难入眠。浑浑噩噩坐上地铁,坐地铁的近一个小时时间看了CSDN的老师讲vuex的视频,确实解惑不少… statewhat?state是整个应用的数据中心,管理整个应用的状态。也称为状态对象。 定义一般是定义在store/index.js中,就是一个大的对象(或者单独来一个state.js引进来)。1234567const state = { count: 0, history: []}const store = new Vuex.Store({ state}) 使用 直接在组件模板里:。 在计算属性里面使用: 12345computed: { count () { return this.$store.state.count }} 最高级的用法: 12345computed:{ ...mapState([ count ])} mutationswhat?我把它理解为直接操作状态对象的操作中心。 定义一般是定义在store/mutations.js中,里面暴露了多个函数来改变状态对象。1234export const increment = state => { state.count++ state.history.push('increment')} 使用12345mothods: { ...mapMutations([ increment ])} getterswhat?getter可以看作是state的计算属性,即通过状态对象衍生出来的数据。从它的名字为getter可以看出,通过读取state得到衍生值。 定义一般是定义在store/getters.js中,里面暴露了许多值,这些值都是通过函数返回值赋值(通过这种方式可以避免在组件内直接使用mapState这种局限性较大方式)。1export const count = state => state.count 使用12345computed: { ...mapGetters([ count ])} actionswhat?action用来派发mutation,一个action中可以触发多个mutation,在不同的时间不同的条件下触发不同的mutation(异步操作在此处执行)。 定义一般是定义在store/actions.js中,里面暴露了很多方法。方法里面触发mutation(函数默认的第一个参数是context,下例使用了对象的结构)。1234567891011export const incrementIfOdd = ({ commit, state }) => { if ((state.count + 1) % 2 === 0) { commit('increment') }}export const incrementAsync = ({ commit }) => { setTimeout(() => { commit('increment') }, 1000)} 使用123456mothods: { ...mapActions([ incrementIfOdd, incrementAsync ])} modulewhat?用来将上述种种进行模块化。 定义1234567891011const moduleA = { state, getters, actions, mutations}export default new Vuex.Store({ modules: { moduleA }}) 使用实际上就是在state后面多加了一层moduleA(我们那些可爱的映射函数都没法用了)。]]></content>
<categories>
<category>前端</category>
<category>JS</category>
<category>vue</category>
</categories>
<tags>
<tag>vue</tag>
<tag>vuex</tag>
<tag>状态管理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS 拾遗(二)]]></title>
<url>%2F2017%2F04%2F25%2FJS-gleaning(2)%2F</url>
<content type="text"><![CDATA[重读阮一峰 《JavaScript 标准参考教程》一书。有沧海拾遗的感觉,偶有收获,记录下来,以便之后查阅… JS语法变量提升 只有用var声明变量才会变量提升 要规避这种特性,可以直接写a = 0,或者用 winsow.a = 0 这种形式 用function关键字声明的函数会将整个函数提升到顶部,用 var foo = function () {} 可以只提升foo变量的声明,但函数体还是会留在原地,这样可以避免一些错误 switch-case语法 每一个case语句后面都要加break,不然程序还是会往下走 switch-case语法使用的是严格相等的模式 数组 数组即对象,不过是拥有有序的键名(‘0’,’1’,…) 本质上,数组的下标是字符串。只不过我们写成数字下标时,JS解析器会对数字进行隐式转换 数组与狭义对象的差别之一是数组的length属性是动态变化的。清空数组的一种方式是arr.length = 0 给数组添加一些不符合数组规范的属性不会影响它的length属性 类数组对象也有length属性,但是该属性不是动态的 强制类型转换 Number()是比parseInt()更加严格的转换 String()可以用来区分狭义对象和数组 Boolean() 错误处理 error对象使用new Error()构造函数,并传入错误信息message 三个属性:name、message、stack throw用于中断程序执行,抛出意外或错误。接收表达式作为参数,可以抛出各种类型的值 闭包 作用:①外部读内部的变量 ②闭包的变量不会被回收,可以用来存储变量 ③封装对象的私有属性和方法 外部函数的内部环境会一直存在,闭包可以看作是函数内部作用域的一个接口 外部函数每一次执行都会形成新的闭包,该闭包又保留外层函数的内部变量,所以内存消耗挺大,要慎用 IIFE function关键字既可以作为语句,也可以作为表达式 ()()是为了让JS解释器将function解析成表达式,后面的小括号是用来传参的。 还有其他形式的如:(function(){}())、~function(){}()... 狭义对象 对象的键名为字符串。我们用对象字面量的方式定义对象,键名可以省略引号 Object.keys(obj)方法返回包含所有键名的数组(该方法可以用来判断一个对象是否为空) delete obj.xx 删除对象的某个属性 'xx' in obj 判断对象是否有某个属性(继承过来的属性也被包含) obj.hasOwnProperty('xx') 判断对象是否有某个属性(不包含继承过来的属性也)]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>js</tag>
<tag>JS 拾遗</tag>
</tags>
</entry>
<entry>
<title><![CDATA[常用git命令]]></title>
<url>%2F2017%2F04%2F22%2Finstrument-git-directive%2F</url>
<content type="text"><![CDATA[听说git可能是目前世界上最好的代码管理工具,下面整理下我迄今为止用到的git命令。不全,但极为实用… Git的几个概念 Workspace 工作区 Index/Stage 暂存区 Repository 本地仓储 Remote 远程仓储 我最常用的git命令 创建本地仓储 1git init 添加所有文件到git的跟踪列表(除去被.gitignore过滤掉的文件) 12// 或者git add .git add --all 提交代码到本地仓储 1git commit -m '提交日志' 将本地仓储与远程仓储建立联系 12// origin后面跟的就是你的远程仓储地址git remote add origin https://github.com/yesixuan/uhealth.git 将本地代码同步到远程仓储 1git push -u origin master 将远程仓储的代码拉到本地 12// 后面可以指定你想要存放的文件夹名git clone https://github.com/yesixuan/uhealth.git 将远程仓储的代码同步到本地 1git pull origin master 分支操作 创建并且切换到该分支 1git checkout -b [branch-name] 切换至主分支 1git checkout master 删除某分支 1git branch -d [branch-name] 提交分支到远程仓库 1git push -u origin [branch-name] 删除远程分支 1git push origin --delete [branch-name] 合并分支 12// 一旦出现冲突,需要手动解决冲突。解决完后,还要add一下git merge [branch-name] 其他重要的git命令 查看本地仓储的状态 1git status 对比代码的差异 1git diff 查看代码提交的日志 1git log 恢复到之前的某个版本 12// 哈希值可以在log中看git reset --hard 哈希值的前六位 下载远程仓储的所有变动 1git fetch [remote] 同步本地代码到远程仓储的指定分支 1git pull [remote] [branch] 同步所有分支到远程仓储 1git push [remote] --all]]></content>
<categories>
<category>工具</category>
<category>git</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS 拾遗(一)]]></title>
<url>%2F2017%2F04%2F21%2FJS-gleaning(1)%2F</url>
<content type="text"><![CDATA[JS数据类型 分为三大类:原始型、合成型、特殊型 合成型包含狭义对象、数组、函数 JS数据类型的判断 typeof 运算符检测对象、数组、null都是返回的obj obj.prototype.toString.call(arg) === '[object Array]'可以区别对象与数组 String() 强制类型转换可以区别对象与数组 JSON.stringify() 可判断一个对象是不是空对象 JS函数 常规函数会申明提前,var出来的函数不会 函数a、b都是在外部申明,如果调用a时传入b,b用了a里面的变量,此时会报错 函数参数如果是原始类型,则传值方式为按值传递,可以用window属性的方式来绕过 arguments有一个callee属性,指向它对应的原函数 JS中,一切皆对象。运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境) bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数]]></content>
<categories>
<category>前端</category>
<category>JS</category>
</categories>
<tags>
<tag>js</tag>
<tag>JS 拾遗</tag>
</tags>
</entry>
<entry>
<title><![CDATA[正则表达式(基础篇)]]></title>
<url>%2F2017%2F04%2F21%2Fregular-expression%2F</url>
<content type="text"><![CDATA[正则表达式,学了好多次了,但是没有经过大量的实践,总是易忘,唉… 通用正则语法类 [abc]表示匹配a或者b或者c的字符 [^abc]表示匹配不是a或者b或者c的字符,^仅在[]中取反,在其他情况下表示边界 范围类[a-z]在中括号里面的-表示范围,如需匹配短横线,则需在后面再加-,如[0-9-] 预定义类:. 匹配除了换行符以外的任何字符 \d 匹配数字 \s 匹配字母,数字,下划线 \w 匹配空格(字母如果变大写则表示取反) 边界 ^ $ 为起始边界 \b 单词边界 \B 非单词边界 量词 ? 匹配0个或1个 + 匹配 >=1 个 * 匹配任意多个 {n,m} a为最少,b为最多。若为{n},则为精确匹配;若为{n,},则匹配大于n个 匹配模式 默认的匹配模式是贪婪模式(即尽可能多的匹配) 若想将贪婪模式改成“廉洁模式(自创)”,只需在量词后面加上“ ? ” 默认情况下,量词制作用于紧挨着它的字符。如果想要破,可以用 () 来进行分组 用 () 来进行分组后的分组内容可以用 $1,$2,... 来获取 JS中的正则正则对象 正则对象的属性有 g,i,m,lastIndex test 方法,返回布尔值 exec 方法,匹配不成功返回 null,否则返回结果数组,数组的第一个元素表示匹配的文本,第二个元素是与正则对象的第一个子表达式相匹配的文本(如果有),以此类推 与正则相关的字符串方法 search 方法,返回第一个匹配结果的index,否则返回null(它不会执行全局匹配) match 方法,如果不加‘ g ’,则返回null或数组,数组存放了与它找到的匹配文本有关的信息,第一个元素是匹配的文本,其余元素存放子表达式匹配的内容]]></content>
<categories>
<category>前端</category>
<category>正则表达式</category>
</categories>
<tags>
<tag>前端</tag>
<tag>正则表达式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端面试]]></title>
<url>%2F2017%2F04%2F20%2FFront-end-Interview%2F</url>
<content type="text"><![CDATA[本文转载 (博客:http://segmentfault.com/u/trigkit4) 收集整理。 一些开放性题目1.自我介绍:除了基本个人信息以外,面试官更想听的是你与众不同的地方和你的优势。 2.项目介绍 3.如何看待前端开发? 4.平时是如何学习前端开发的? 5.未来三到五年的规划是怎样的? position的值, relative和absolute分别是相对于谁进行定位的? absolute :生成绝对定位的元素, 相对于最近一级的 定位不是 static 的父元素来进行定位。 fixed (老IE不支持)生成绝对定位的元素,通常相对于浏览器窗口或 frame 进行定位。 relative 生成相对定位的元素,相对于其在普通流中的位置进行定位。 static 默认值。没有定位,元素出现在正常的流中 sticky 生成粘性定位的元素,容器的位置根据正常文档流计算得出 如何解决跨域问题 JSONP: 原理是:动态插入script标签,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。 由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。 优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。 JSONP:json+padding(内填充),顾名思义,就是把JSON填充到一个盒子里 12345678910111213141516171819<script> function createJs(sUrl){ var oScript = document.createElement('script'); oScript.type = 'text/javascript'; oScript.src = sUrl; document.getElementsByTagName('head')[0].appendChild(oScript); } createJs('jsonp.js'); box({ 'name': 'test' }); function box(json){ alert(json.name); }</script> CORS 服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。 通过修改document.domain来跨子域 将子域和主域的document.domain设为同一个主域.前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域 主域相同的使用document.domain 使用window.name来进行跨域 window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的 使用HTML5中新引进的window.postMessage方法来跨域传送数据 还有flash、在服务器上设置代理页面等跨域方式。个人认为window.name的方法既不复杂,也能兼容到几乎所有浏览器,这真是极好的一种跨域方法。 XML和JSON的区别?123456789101112131415(1).数据体积方面。JSON相对于XML来讲,数据的体积小,传递的速度更快些。(2).数据交互方面。JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互。(3).数据描述方面。JSON对数据的描述性比XML较差。(4).传输速度方面。JSON的速度要远远快于XML。 谈谈你对webpack的看法WebPack 是一个模块打包工具,你可以使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTML、Javascript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack有对应的模块加载器。webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。 webpack的两大特色: 1.code splitting(可以自动完成) 2.loader 可以处理各种类型的静态文件,并且支持串联操作 webpack 是以commonJS的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。 webpack具有requireJs和browserify的功能,但仍有很多自己的新特性: 1234567891011121314151. 对 CommonJS 、 AMD 、ES6的语法做了兼容2. 对js、css、图片等资源文件都支持打包3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持4. 有独立的配置文件webpack.config.js5. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间6. 支持 SourceUrls 和 SourceMaps,易于调试7. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活8.webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快 说说TCP传输的三次握手四次挥手策略 为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。用TCP协议把数据包送出去后,TCP不会对传送 后的情况置之不理,它一定会向对方确认是否成功送达。握手过程中使用了TCP的标志:SYN和ACK。 发送端首先发送一个带SYN标志的数据包给对方。接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。若在握手过程中某个阶段莫名中断,TCP协议会再次以相同的顺序发送相同的数据包。 断开一个TCP连接则需要“四次握手”: 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。 TCP和UDP的区别TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来 UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去! UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。 说说你对作用域链的理解作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。 创建ajax过程1234567891011(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.(3)设置响应HTTP请求状态变化的函数.(4)发送HTTP请求.(5)获取异步调用返回的数据.(6)使用JavaScript和DOM实现局部刷新. 渐进增强和优雅降级渐进增强 :针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。 优雅降级 :一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。 常见web安全及防护原理 sql注入原理 就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。 总的来说有以下几点: 123456781.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。 XSS原理及防范 Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意 html标签或者javascript代码。比如:攻击者在论坛中放一个 看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单, 当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。 XSS防范方法 首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag 弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击。 首先,避免直接在cookie 中泄露用户隐私,例如email、密码等等。 其次,通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。 如果网站不需要再浏览器端对cookie 进行操作,可以在Set-Cookie 末尾加上HttpOnly 来防止javascript 代码直接获取cookie 。 尽量采用POST 而非GET 提交表单 XSS与CSRF有什么区别吗? XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。 要完成一次CSRF攻击,受害者必须依次完成两个步骤: 123登录受信任网站A,并在本地生成Cookie。在不登出A的情况下,访问危险网站B。 CSRF的防御 服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。 通过验证码的方法 Web Worker 和webSocket worker主线程: 12345671.通过 worker = new Worker( url ) 加载一个JS文件来创建一个worker,同时返回一个worker实例。2.通过worker.postMessage( data ) 方法来向worker发送数据。3.绑定worker.onmessage方法来接收worker发送过来的数据。4.可以使用 worker.terminate() 来终止一个worker的执行。 WebSocket是Web应用程序的传输协议,它提供了双向的,按序到达的数据流。他是一个HTML5协议,WebSocket的连接是持久的,他通过在客户端和服务器之间保持双工连接,服务器的更新可以被及时推送给客户端,而不需要客户端以一定时间间隔去轮询。 HTTP和HTTPSHTTP协议通常承载于TCP协议之上,在HTTP和TCP之间添加一个安全协议层(SSL或TSL),这个时候,就成了我们常说的HTTPS。 默认HTTP的端口号为80,HTTPS的端口号为443。 为什么HTTPS安全因为网络请求需要中间有很多的服务器路由器的转发。中间的节点都可能篡改信息,而如果使用HTTPS,密钥在你和终点站才有。https之所以比http安全,是因为他利用ssl/tls协议传输。它包含证书,卸载,流量转发,负载均衡,页面适配,浏览器适配,refer传递等。保障了传输过程的安全性 对前端模块化的认识 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。 CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 AMD 是提前执行,CMD 是延迟执行。 AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的。 CMD模块方式 12345define(function(require, exports, module) { // 模块代码}); Javascript垃圾回收方法 标记清除(mark and sweep) 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了 引用计数(reference counting) 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。 在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,也就是说只要涉及BOM及DOM就会出现循环引用问题。 你觉得前端工程的价值体现在哪为简化用户使用提供技术支持(交互部分) 为多个浏览器兼容性提供支持 为提高用户浏览速度(浏览器性能)提供支持 为跨平台或者其他基于webkit或其他渲染引擎的应用提供支持 为展示数据提供支持(数据接口) 谈谈性能优化问题代码层面:避免使用css表达式,避免使用高级选择器,通配选择器。 缓存利用:缓存Ajax,使用CDN,使用外部js和css文件以便缓存,添加Expires头,服务端配置Etag,减少DNS查找等 请求数量:合并样式和脚本,使用css图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载。 请求带宽:压缩文件,开启GZIP, 代码层面的优化 用hash-table来优化查找 少用全局变量 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能 用setTimeout来避免页面失去响应 缓存DOM节点查找的结果 避免使用CSS Expression 避免全局查询 避免使用with(with会创建自己的作用域,会增加作用域链长度) 多个变量声明合并 避免图片和iFrame等的空Src。空Src会重新加载当前页面,影响速度和效率 尽量避免写在HTML标签中写Style属性 移动端性能优化 尽量使用css3动画,开启硬件加速。 适当使用touch事件代替click事件。 避免使用css3渐变阴影效果。 可以用transform: translateZ(0)来开启硬件加速。 不滥用Float。Float在渲染时计算量比较大,尽量减少使用 不滥用Web字体。Web字体需要下载,解析,重绘当前页面,尽量减少使用。 合理使用requestAnimationFrame动画代替setTimeout CSS中的属性(CSS3 transitions、CSS3 3D transforms、Opacity、Canvas、WebGL、Video)会触发GPU渲染,请合理使用。过渡使用会引发手机过耗电增加 PC端的在移动端同样适用 相关阅读:如何做到一秒渲染一个移动页面 什么是Etag?当发送一个服务器请求时,浏览器首先会进行缓存过期判断。浏览器根据缓存过期时间判断缓存文件是否过期。 情景一:若没有过期,则不向服务器发送请求,直接使用缓存中的结果,此时我们在浏览器控制台中可以看到 200 OK(from cache) ,此时的情况就是完全使用缓存,浏览器和服务器没有任何交互的。 情景二:若已过期,则向服务器发送请求,此时请求中会带上①中设置的文件修改时间,和Etag 然后,进行资源更新判断。服务器根据浏览器传过来的文件修改时间,判断自浏览器上一次请求之后,文件是不是没有被修改过;根据Etag,判断文件内容自上一次请求之后,有没有发生变化 情形一:若两种判断的结论都是文件没有被修改过,则服务器就不给浏览器发index.html的内容了,直接告诉它,文件没有被修改过,你用你那边的缓存吧—— 304 Not Modified,此时浏览器就会从本地缓存中获取index.html的内容。此时的情况叫协议缓存,浏览器和服务器之间有一次请求交互。 情形二:若修改时间和文件内容判断有任意一个没有通过,则服务器会受理此次请求,之后的操作同① ① 只有get请求会被缓存,post请求不会 Expires和Cache-ControlExpires要求客户端和服务端的时钟严格同步。HTTP1.1引入Cache-Control来克服Expires头的限制。如果max-age和Expires同时出现,则max-age有更高的优先级。 123456789Cache-Control: no-cache, private, max-age=0ETag: abcdeExpires: Thu, 15 Apr 2014 20:00:00 GMTPragma: privateLast-Modified: $now // RFC1123 format ETag应用:Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match。请求一个文件的流程可能如下: ====第一次请求=== 1.客户端发起 HTTP GET 请求一个文件; 2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).状态码200 ====第二次请求=== 客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d0448402.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办 答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后, 服务器才能返回304.(不要陷入到底使用谁的问题怪圈) 为什么使用Etag请求头? Etag 主要为了解决 Last-Modified 无法解决的一些问题。 栈和队列的区别?栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。 队列先进先出,栈先进后出。 栈只允许在表尾一端进行插入和删除,而队列只允许在表尾一端进行插入,在表头一端进行删除 栈和堆的区别?栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。 堆(数据结构):堆可以被看成是一棵树,如:堆排序; 栈(数据结构):一种先进后出的数据结构。 快速 排序的思想并实现一个快排?“快速排序”的思想很简单,整个排序过程只需要三步: (1)在数据集之中,找一个基准点 (2)建立两个数组,分别存储左边和右边的数组 (3)利用递归进行下次比较 12345678910111213141516171819202122232425262728<script type="text/javascript"> function quickSort(arr){ if(arr.length<=1){ return arr;//如果数组只有一个数,就直接返回; } var num = Math.floor(arr.length/2);//找到中间数的索引值,如果是浮点数,则向下取整 var numValue = arr.splice(num,1);//找到中间数的值 var left = []; var right = []; for(var i=0;i<arr.length;i++){ if(arr[i]<numValue){ left.push(arr[i]);//基准点的左边的数传到左边数组 } else{ right.push(arr[i]);//基准点的右边的数传到右边数组 } } return quickSort(left).concat([numValue],quickSort(right));//递归不断重复比较 } alert(quickSort([32,45,37,16,2,87]));//弹出“2,16,32,37,45,87”</script> 你觉得jQuery或zepto源码有哪些写的好的地方(答案仅供参考) jquery源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入window对象参数,可以使window对象作为局部变量使用,好处是当jquery中访问window对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问window对象。同样,传入undefined参数,可以缩短查找undefined时的作用域链。 12345678910111213(function( window, undefined ) { //用一个函数域包起来,就是所谓的沙箱 //在这里边var定义的变量,属于这个函数域内的局部变量,避免污染全局 //把当前沙箱需要的外部变量通过函数参数引入进来 //只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数 window.jQuery = window.$ = jQuery;})( window ); jquery将一些原型属性和方法封装在了jquery.prototype中,为了缩短名称,又赋值给了jquery.fn,这是很形象的写法。 有一些数组或对象的方法经常能使用到,jQuery将其保存为局部变量以提高访问速度。 jquery实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率。 ES6的了解新增模板字符串(为JavaScript提供了简单的字符串插值功能)、箭头函数(操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs。)、for-of(用来遍历数据—例如数组中的值。)arguments对象可被不定参数和默认参数完美代替。ES6将promise对象纳入规范,提供了原生的Promise对象。增加了let和const命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。。还有就是引入module模块的概念 js继承方式及其优缺点 原型链继承的缺点 一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。 借用构造函数(类式继承) 借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。所以我们需要原型链+借用构造函数的模式,这种模式称为组合继承 组合式继承 组合式继承是比较常用的一种继承方法,其背后的思路是 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。 具体请看:JavaScript继承方式详解 关于Http 2.0 你知道多少?HTTP/2引入了“服务端推(server push)”的概念,它允许服务端在客户端需要数据之前就主动地将数据发送到客户端缓存中,从而提高性能。 HTTP/2提供更多的加密支持 HTTP/2使用多路技术,允许多个消息在一个连接上同时交差。 它增加了头压缩(header compression),因此即使非常小的请求,其请求和响应的header都只会占用很小比例的带宽。 defer和async defer并行加载js文件,会按照页面上script标签的顺序执行async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行 谈谈浮动和清除浮动浮动的框可以向左或向右移动,直到他的外边缘碰到包含框或另一个浮动框的边框为止。由于浮动框不在文档的普通流中,所以文档的普通流的块框表现得就像浮动框不存在一样。浮动的块框会漂浮在文档普通流的块框上。 如何评价AngularJS和BackboneJSbackbone具有依赖性,依赖underscore.js。Backbone + Underscore + jQuery(or Zepto) 就比一个AngularJS 多出了2 次HTTP请求. Backbone的Model没有与UI视图数据绑定,而是需要在View中自行操作DOM来更新或读取UI数据。AngularJS与此相反,Model直接与UI视图绑定,Model与UI视图的关系,通过directive封装,AngularJS内置的通用directive,就能实现大部分操作了,也就是说,基本不必关心Model与UI视图的关系,直接操作Model就行了,UI视图自动更新。 AngularJS的directive,你输入特定数据,他就能输出相应UI视图。是一个比较完善的前端MVW框架,包含模板,数据双向绑定,路由,模块化,服务,依赖注入等所有功能,模板功能强大丰富,并且是声明式的,自带了丰富的 Angular 指令。 用过哪些设计模式? 工厂模式: 主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置防止代码重复。 工厂模式解决了重复实例化的问题 ,但还有一个问题,那就是识别问题,因为根本无法 搞清楚他们到底是哪个对象的实例。 function createObject(name,age,profession){//集中实例化的函数var obj = new Object(); obj.name = name; obj.age = age; obj.profession = profession; obj.move = function () { return this.name + ' at ' + this.age + ' engaged in ' + this.profession; }; return obj; } var test1 = createObject('trigkit4',22,'programmer');//第一个实例var test2 = createObject('mike',25,'engineer');//第二个实例 构造函数模式 使用构造函数的方法 ,即解决了重复实例化的问题 ,又解决了对象识别的问题,该模式与工厂模式的不同之处在于: 1.构造函数方法没有显示的创建对象 (new Object()); 2.直接将属性和方法赋值给 this 对象; 3.没有 renturn 语句。 说说你对闭包的理解使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 具体请看:详解js闭包 请你谈谈Cookie的弊端cookie虽然在持久保存客户端数据提供了方便,分担了服务器存储的负担,但还是有很多局限性的。 第一:每个特定的域名下最多生成20个cookie 1.IE6或更低版本最多20个cookie 2.IE7和之后的版本最后可以有50个cookie。 3.Firefox最多50个cookie 4.chrome和Safari没有做硬性限制 IE和Opera 会清理近期最少使用的cookie,Firefox会随机清理cookie。 cookie的最大大约为4096字节,为了兼容性,一般不能超过4095字节。 IE 提供了一种存储可以持久化用户数据,叫做userdata,从IE5.0就开始支持。每个数据最多128K,每个域名下最多1M。这个持久化数据放在缓存中,如果缓存没有清理,那么会一直存在。 优点:极高的扩展性和可用性 1.通过良好的编程,控制保存在cookie中的session对象的大小。 2.通过加密和安全传输技术(SSL),减少cookie被破解的可能性。 3.只在cookie中存放不敏感数据,即使被盗也不会有重大损失。 4.控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。 缺点: 1.`Cookie`数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉. 2.安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。 3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。 浏览器本地存储在较高版本的浏览器中,js提供了sessionStorage和globalStorage。在HTML5中提供了localStorage来取代globalStorage。 html5中的Web Storage包括了两种存储方式:sessionStorage和localStorage。 sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。 而localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。 web storage和cookie的区别Web Storage的概念和cookie相似,区别是它是为了更大容量存储设计的。Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。 除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像cookie需要前端开发者自己封装setCookie,getCookie。 但是cookie也是不可以或缺的:cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生 浏览器的支持除了IE7及以下不支持外,其他标准浏览器都完全支持(ie及FF需在web服务器里运行),值得一提的是IE总是办好事,例如IE7、IE6中的userData其实就是javascript本地存储的解决方案。通过简单的代码封装可以统一到所有的浏览器都支持web storage。 localStorage和sessionStorage都具有相同的操作方法,例如setItem、getItem和removeItem等 cookie 和session 的区别:1、cookie数据存放在客户的浏览器上,session数据放在服务器上。 2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗 考虑到安全应当使用session。 3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能 考虑到减轻服务器性能方面,应当使用COOKIE。 4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。 5、所以个人建议: 将登陆信息等重要信息存放为SESSION 其他信息如果需要保留,可以放在COOKIE中 display:none和visibility:hidden的区别? display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。 visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。 CSS中link 和@import的区别是? (1) link属于HTML标签,而@import是CSS提供的; (2) 页面被加载的时,link会同时被加载,而@import被引用的CSS会等到引用它的CSS文件被加载完再加载; (3) import只在IE5以上才能识别,而link是HTML标签,无兼容问题; (4) link方式的样式的权重 高于@import的权重. position:absolute和float属性的异同 共同点:对内联元素设置float和absolute属性,可以让元素脱离文档流,并且可以设置其宽高。 不同点:float仍会占据位置,absolute会覆盖文档流中的其他元素。 介绍一下box-sizing属性? box-sizing属性主要用来控制元素的盒模型的解析模式。默认值是content-box。 content-box:让元素维持W3C的标准盒模型。元素的宽度/高度由border + padding + content的宽度/高度决定,设置width/height属性指的是content部分的宽/高 border-box:让元素维持IE传统盒模型(IE6以下版本和IE6~7的怪异模式)。设置width/height属性指的是border + padding + content 标准浏览器下,按照W3C规范对盒模型解析,一旦修改了元素的边框或内距,就会影响元素的盒子尺寸,就不得不重新计算元素的盒子尺寸,从而影响整个页面的布局。 CSS 选择符有哪些?哪些属性可以继承?优先级算法如何计算? CSS3新增伪类有那些? 12345678910111213141516171.id选择器( # myid)2.类选择器(.myclassname)3.标签选择器(div, h1, p)4.相邻选择器(h1 + p)5.子选择器(ul > li)6.后代选择器(li a)7.通配符选择器( * )8.属性选择器(a[rel = "external"])9.伪类选择器(a: hover, li:nth-child) 优先级为: !important > id > class > tag important 比 内联优先级高,但内联比 id 要高 CSS3新增伪类举例: 12345678910111213p:first-of-type 选择属于其父元素的首个 <p> 元素的每个 <p> 元素。p:last-of-type 选择属于其父元素的最后 <p> 元素的每个 <p> 元素。p:only-of-type 选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。p:only-child 选择属于其父元素的唯一子元素的每个 <p> 元素。p:nth-child(2) 选择属于其父元素的第二个子元素的每个 <p> 元素。:enabled :disabled 控制表单控件的禁用状态。:checked 单选框或复选框被选中。 CSS3有哪些新特性? CSS3实现圆角(border-radius),阴影(box-shadow), 对文字加特效(text-shadow、),线性渐变(gradient),旋转(transform) transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px) skew(-9deg,0deg);//旋转,缩放,定位,倾斜 增加了更多的CSS选择器 多背景 rgba 在CSS3中唯一引入的伪元素是::selection. 媒体查询,多栏布局 border-image CSS3中新增了一种盒模型计算方式:box-sizing。盒模型默认的值是content-box, 新增的值是padding-box和border-box,几种盒模型计算元素宽高的区别如下: content-box(默认)布局所占宽度Width: 1Width = width + padding-left + padding-right + border-left + border-right 布局所占高度Height: 1Height = height + padding-top + padding-bottom + border-top + border-bottom padding-box布局所占宽度Width: 1Width = width(包含padding-left + padding-right) + border-top + border-bottom 布局所占高度Height: 1Height = height(包含padding-top + padding-bottom) + border-top + border-bottom border-box布局所占宽度Width: 1Width = width(包含padding-left + padding-right + border-left + border-right) 布局所占高度Height: 1Height = height(包含padding-top + padding-bottom + border-top + border-bottom) 对BFC规范的理解? BFC,块级格式化上下文,一个创建了新的BFC的盒子是独立布局的,盒子里面的子元素的样式不会影响到外面的元素。在同一个BFC中的两个毗邻的块级盒在垂直方向(和布局方向有关系)的margin会发生折叠。 (W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行布局,以及与其他元素的关系和相互作用。 说说你对语义化的理解?1,去掉或者丢失样式的时候能够让页面呈现出清晰的结构 2,有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重; 3,方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)以意义的方式来渲染网页; 4,便于团队开发和维护,语义化更具可读性,是下一步吧网页的重要动向,遵循W3C标准的团队都遵循这个标准,可以减少差异化。 Doctype作用? 严格模式与混杂模式如何区分?它们有何意义?1)、<!DOCTYPE> 声明位于文档中的最前面,处于 <html> 标签之前。告知浏览器以何种模式来渲染文档。 2)、严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。 3)、在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站点无法工作。 4)、DOCTYPE不存在或格式不正确会导致文档以混杂模式呈现。 你知道多少种Doctype文档类型? 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。 HTML与XHTML——二者有什么区别区别: 1.所有的标记都必须要有一个相应的结束标记 2.所有标签的元素和属性的名字都必须使用小写 3.所有的XML标记都必须合理嵌套 4.所有的属性必须用引号""括起来 5.把所有<和&特殊符号用编码表示 6.给所有属性赋一个值 7.不要在注释内容中使“--” 8.图片必须有说明文字 常见兼容性问题?png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8.也可以引用一段脚本处理. 浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。 IE6双边距bug:块属性标签float后,又有横行的margin情况下,在ie6显示margin比设置的大。 浮动ie产生的双倍距离(IE6双边距问题:在IE6下,如果对元素设置了浮动,同时又设置了margin-left或margin-right,margin值会加倍。) #box{ float:left; width:10px; margin:0 0 0 100px;} 这种情况之下IE会产生20px的距离,解决方案是在float的标签样式控制中加入 _display:inline;将其转化为行内属性。(_这个符号只有ie6会识别) 渐进识别的方式,从总体中逐渐排除局部。 首先,巧妙的使用“\9”这一标记,将IE游览器从所有情况中分离出来。 接着,再次使用“+”将IE8和IE7、IE6分离开来,这样IE8已经独立识别。 css .bb{ background-color:#f1ee18;/*所有识别*/ .background-color:#00deff\9; /*IE6、7、8识别*/ +background-color:#a200ff;/*IE6、7识别*/ _background-color:#1e0bd1;/*IE6识别*/ } 怪异模式问题:漏写DTD声明,Firefox仍然会按照标准模式来解析网页,但在IE中会触发 怪异模式。为避免怪异模式给我们带来不必要的麻烦,最好养成书写DTD声明的好习惯。现在 可以使用[html5](http://www.w3.org/TR/html5/single-page.html)推荐的写法:`<doctype html>` 上下margin重合问题 ie和ff都存在,相邻的两个div的margin-left和margin-right不会重合,但是margin-top和margin-bottom却会发生重合。 解决方法,养成良好的代码编写习惯,同时采用margin-top或者同时采用margin-bottom。 解释下浮动和它的工作原理?清除浮动的技巧浮动元素脱离文档流,不占据空间。浮动元素碰到包含它的边框或者浮动元素的边框停留。 1.使用空标签清除浮动。 这种方法是在所有浮动标签后面添加一个空标签 定义css clear:both. 弊端就是增加了无意义标签。 2.使用overflow。 给包含浮动元素的父标签添加css属性 overflow:auto; zoom:1; zoom:1用于兼容IE6。 3.使用after伪对象清除浮动。 该方法只适用于非IE浏览器。具体写法可参照以下示例。使用中需注意以下几点。一、该方法中必须为需要清除浮动元素的伪对象中设置 height:0,否则该元素会比实际高出若干像素; 浮动元素引起的问题和解决办法?浮动元素引起的问题: (1)父元素的高度无法被撑开,影响与父元素同级的元素 (2)与浮动元素同级的非浮动元素(内联元素)会跟随其后 (3)若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构 解决方法: 使用CSS中的clear:both;属性来清除元素的浮动可解决2、3问题,对于问题1,添加如下样式,给父元素添加clearfix样式: 123.clearfix:after{content: ".";display: block;height: 0;clear: both;visibility: hidden;}.clearfix{display: inline-block;} /* for IE/Mac */ 清除浮动的几种方法: 123456789101112131415161718192021221,额外标签法,<div style="clear:both;"></div>(缺点:不过这个办法会增加额外的标签使HTML结构看起来不够简洁。)2,使用after伪类#parent:after{ content:"."; height:0; visibility:hidden; display:block; clear:both; }3,浮动外部元素4,设置overflow为hidden或者auto DOM操作——怎样添加、移除、移动、复制、创建和查找节点。 1)创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节点 2)添加、移除、替换、插入 appendChild() removeChild() replaceChild() insertBefore() //并没有insertAfter() 3)查找 getElementsByTagName() //通过标签名称 getElementsByName() //通过元素的Name属性的值(IE容错能力较强, 会得到一个数组,其中包括id等于name值的) getElementById() //通过元素Id,唯一性 html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加。 拖拽释放(Drag and drop) API 语义化更好的内容标签(header,nav,footer,aside,article,section) 音频、视频API(audio,video) 画布(Canvas) API 地理(Geolocation) API 本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失; sessionStorage 的数据在浏览器关闭后自动删除 表单控件,calendar、date、time、email、url、search 新的技术webworker, websocket, Geolocation 移除的元素 纯表现的元素:basefont,big,center,font, s,strike,tt,u; 对可用性产生负面影响的元素:frame,frameset,noframes; 支持HTML5新标签: 1234567891011121314IE8/IE7/IE6支持通过document.createElement方法产生的标签,可以利用这一特性让这些浏览器支持HTML5新标签,当然最好的方式是直接使用成熟的框架、使用最多的是html5shim框架 <!--[if lt IE 9]> <script> src="http://html5shim.googlecode.com/svn/trunk/html5.js"</script> <![endif]-->如何区分: DOCTYPE声明\新增的结构元素\功能元素 如何实现浏览器内多个标签页之间的通信?1调用localstorge、cookies等本地存储方式 什么是 FOUC(无样式内容闪烁)?你如何来避免 FOUC?1234567 FOUC - Flash Of Unstyled Content 文档样式闪烁 <style type="text/css" media="all">@import "../fouc.css";</style>而引用CSS文件的@import就是造成这个问题的罪魁祸首。IE会先加载整个HTML文档的DOM,然后再去导入外部的CSS文件,因此,在页面DOM加载完成到CSS导入完成中间会有一段时间页面上的内容是没有样式的,这段时间的长短跟网速,电脑速度都有关系。 解决方法简单的出奇,只要在<head>之间加入一个<link>或者<script>元素就可以了。 null和undefined的区别?null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。 当声明的变量还未被初始化时,变量的默认值为undefined。 null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。 undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是: (1)变量被声明了,但没有赋值时,就等于undefined。 (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时,默认返回undefined。 null表示”没有对象”,即该处不应该有值。典型用法是: (1) 作为函数的参数,表示该函数的参数不是对象。 (2) 作为对象原型链的终点。 new操作符具体干了什么呢? 1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。 2、属性和方法被加入到 this 引用的对象中。 3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。 var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj); js延迟加载的方式有哪些?defer和async、动态创建DOM方式(创建script,插入到DOM中,加载完毕后callBack)、按需异步载入js call() 和 apply() 的区别和作用?作用:动态改变某个类的某个方法的运行环境(执行上下文)。 区别参见:JavaScript学习总结(四)function函数部分 哪些操作会造成内存泄漏?内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。 setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环) 详见:详解js变量、作用域及内存 列举IE 与其他浏览器不一样的特性? IE支持currentStyle,FIrefox使用getComputStyle IE 使用innerText,Firefox使用textContent 滤镜方面:IE:filter:alpha(opacity= num);Firefox:-moz-opacity:num 事件方面:IE:attachEvent:火狐是addEventListener 鼠标位置:IE是event.clientX;火狐是event.pageX IE使用event.srcElement;Firefox使用event.target IE中消除list的原点仅需margin:0即可达到最终效果;FIrefox需要设置margin:0;padding:0以及list-style:none CSS圆角:ie7以下不支持圆角 WEB应用从服务器主动推送Data到客户端有那些方式?Javascript数据推送 Commet:基于HTTP长连接的服务器推送技术 基于WebSocket的推送方案 SSE(Server-Send Event):服务器推送数据新方式 对前端界面工程师这个职位是怎么样理解的?它的前景会怎么样?前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近。 1、实现界面交互 2、提升用户体验 3、有了Node.js,前端可以实现服务端的一些事情 前端是最贴近用户的程序员,前端的能力就是能让产品从 90分进化到 100 分,甚至更好, 参与项目,快速高质量完成实现效果图,精确到1px; 与团队成员,UI设计,产品经理的沟通; 做好的页面结构,页面重构和用户体验; 处理hack,兼容、写出优美的代码格式; 针对服务器的优化、拥抱最新前端技术。 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么? 分为4个步骤: (1),当发送一个URL请求时,不管这个URL是Web页面的URL还是Web页面上每个资源的URL,浏览器都会开启一个线程来处理这个请求,同时在远程DNS服务器上启动一个DNS查询。这能使浏览器获得请求对应的IP地址。 (2), 浏览器与远程`Web`服务器通过`TCP`三次握手协商来建立一个`TCP/IP`连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。 (3),一旦`TCP/IP`连接建立,浏览器会通过该连接向远程服务器发送`HTTP`的`GET`请求。远程服务器找到资源并使用HTTP响应返回该资源,值为200的HTTP响应状态表示一个正确的响应。 (4),此时,`Web`服务器提供资源服务,客户端开始下载资源。 请求返回后,便进入了我们关注的前端模块 简单来说,浏览器会解析`HTML`生成`DOM Tree`,其次会根据CSS生成CSS Rule Tree,而`javascript`又可以根据`DOM API`操作`DOM` 详情:从输入 URL 到浏览器接收的过程中发生了什么事情? javascript对象的几种创建方式1,工厂模式 2,构造函数模式 3,原型模式 4,混合构造函数和原型模式 5,动态原型模式 6,寄生构造函数模式 7,稳妥构造函数模式 javascript继承的6种方法1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 详情:JavaScript继承方式详解 创建ajax的过程1234567891011121314151617181920212223242526(1)创建`XMLHttpRequest`对象,也就是创建一个异步调用对象.(2)创建一个新的`HTTP`请求,并指定该`HTTP`请求的方法、`URL`及验证信息.(3)设置响应`HTTP`请求状态变化的函数.(4)发送`HTTP`请求.(5)获取异步调用返回的数据.(6)使用JavaScript和DOM实现局部刷新.var xmlHttp = new XMLHttpRequest();xmlHttp.open('GET','demo.php','true');xmlHttp.send()xmlHttp.onreadystatechange = function(){ if(xmlHttp.readyState === 4 & xmlHttp.status === 200){ }} 详情:JavaScript学习总结(七)Ajax和Http状态字 异步加载和延迟加载1.异步加载的方案: 动态插入script标签 2.通过ajax去获取js代码,然后通过eval执行 3.script标签上添加defer或者async属性 4.创建并插入iframe,让它异步执行js 5.延迟加载:有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的。 ie各版本和chrome可以并行下载多少个资源IE6 两个并发,iE7升级之后的6个并发,之后版本也是6个 Firefox,chrome也是6个 ####Flash、Ajax各自的优缺点,在使用中如何取舍? Flash适合处理多媒体、矢量图形、访问机器;对CSS、处理文本上不足,不容易被搜索。 -Ajax对CSS、文本支持很好,支持搜索;多媒体、矢量图形、机器访问不足。 共同点:与服务器的无刷新传递消息、用户离线和在线状态、操作DOM 请解释一下 JavaScript 的同源策略。概念:同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。 这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。 指一段脚本只能读取来自同一来源的窗口和文档的属性。 为什么要有同源限制? 我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。 缺点: 现在网站的JS 都会进行压缩,一些文件用了严格模式,而另一些没有。这时这些本来是严格模式的文件,被 merge 后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节。 GET和POST的区别,何时使用POST? GET:一般用于信息获取,使用URL传递参数,对所发送信息的数量也有限制,一般在2000个字符 POST:一般用于修改服务器上的资源,对所发送的信息没有限制。 GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值, 也就是说Get是通过地址栏来传值,而Post是通过提交表单来传值。 然而,在以下情况中,请使用 POST 请求: 无法使用缓存文件(更新服务器上的文件或数据库) 向服务器发送大量数据(POST 没有数据量限制) 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠 事件、IE与火狐的事件机制有什么区别? 如何阻止冒泡?1. 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。 2. 事件处理机制:IE是事件冒泡、firefox同时支持两种事件模型,也就是:捕获型事件和冒泡型事件。; 3. `ev.stopPropagation()`;注意旧ie的方法 `ev.cancelBubble = true`; ajax的缺点和在IE下的问题?详情请见:JavaScript学习总结(七)Ajax和Http状态字 ajax的缺点 1、ajax不支持浏览器back按钮。 2、安全问题 AJAX暴露了与服务器交互的细节。 3、对搜索引擎的支持比较弱。 4、破坏了程序的异常机制。 5、不容易调试。 IE缓存问题 在IE浏览器下,如果请求的方法是GET,并且请求的URL不变,那么这个请求的结果就会被缓存。解决这个问题的办法可以通过实时改变请求的URL,只要URL改变,就不会被缓存,可以通过在URL末尾添加上随机的时间戳参数('t'= + new Date().getTime()) 或者: 1open('GET','demo.php?rand=+Math.random()',true);// Ajax请求的页面历史记录状态问题 可以通过锚点来记录状态,location.hash。让浏览器记录Ajax请求时页面状态的变化。 还可以通过HTML5的history.pushState,来实现浏览器地址栏的无刷新改变 谈谈你对重构的理解网站重构:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。 对于传统的网站来说重构通常是: 表格(table)布局改为DIV+CSS 使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的) 对于移动平台的优化 针对于SEO进行优化 深层次的网站重构应该考虑的方面 减少代码间的耦合 让代码保持弹性 严格按规范编写代码 设计可扩展的API 代替旧有的框架、语言(如VB) 增强用户体验 通常来说对于速度的优化也包含在重构中 压缩JS、CSS、image等前端资源(通常是由服务器来解决) 程序的性能优化(如数据读写) 采用CDN来加速资源加载 对于JS DOM的优化 HTTP服务器的文件缓存 HTTP状态码1234567891011121314151617181920212223242526272829100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息200 OK 正常返回信息201 Created 请求成功并且服务器创建了新的资源202 Accepted 服务器已接受请求,但尚未处理301 Moved Permanently 请求的网页已永久移动到新位置。302 Found 临时性重定向。303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。304 Not Modified 自从上次请求后,请求的网页未修改过。400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。401 Unauthorized 请求未授权。403 Forbidden 禁止访问。404 Not Found 找不到如何与 URI 相匹配的资源。500 Internal Server Error 最常见的服务器端错误。503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。 说说你对Promise的理解依照 Promise/A+ 的定义,Promise 有四种状态: pending: 初始状态, 非 fulfilled 或 rejected. fulfilled: 成功的操作. rejected: 失败的操作. settled: Promise已被fulfilled或rejected,且不是pending 另外, fulfilled 与 rejected 一起合称 settled。 Promise 对象用来进行延迟(deferred) 和异步(asynchronous ) 计算。 Promise 的构造函数 构造一个 Promise,最基本的用法如下: 123456789101112var promise = new Promise(function(resolve, reject) { if (...) { // succeed resolve(result); } else { // fails reject(Error(errMessage)); }}); Promise 实例拥有 then 方法(具有 then 方法的对象,通常被称为 thenable)。它的使用方法如下: 1promise.then(onFulfilled, onRejected) 接收两个函数作为参数,一个在 fulfilled 的时候被调用,一个在 rejected 的时候被调用,接收参数就是 future,onFulfilled 对应 resolve, onRejected 对应 reject。 说说你对前端架构师的理解负责前端团队的管理及与其他团队的协调工作,提升团队成员能力和整体效率;带领团队完成研发工具及平台前端部分的设计、研发和维护;带领团队进行前端领域前沿技术研究及新技术调研,保证团队的技术领先负责前端开发规范制定、功能模块化设计、公共组件搭建等工作,并组织培训。 实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制123456789101112Object.prototype.clone = function(){ var o = this.constructor === Array ? [] : {}; for(var e in this){ o[e] = typeof this[e] === "object" ? this[e].clone() : this[e]; } return o;} 说说严格模式的限制严格模式主要有以下限制: 变量必须声明后再使用 函数的参数不能有同名属性,否则报错 不能使用with语句 不能对只读属性赋值,否则报错 不能使用前缀0表示八进制数,否则报错 不能删除不可删除的属性,否则报错 不能删除变量delete prop,会报错,只能删除属性delete global[prop] eval不会在它的外层作用域引入变量 eval和arguments不能被重新赋值 arguments不会自动反映函数参数的变化 不能使用arguments.callee 不能使用arguments.caller 禁止this指向全局对象 不能使用fn.caller和fn.arguments获取函数调用的堆栈 增加了保留字(比如protected、static和interface) 设立”严格模式”的目的,主要有以下几个: 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为; 消除代码运行的一些不安全之处,保证代码运行的安全; 提高编译器效率,增加运行速度; 为未来新版本的Javascript做好铺垫。 注:经过测试IE6,7,8,9均不支持严格模式。 如何删除一个cookie 1.将时间设为当前时间往前一点。 123var date = new Date();date.setDate(date.getDate() - 1);//真正的删除 setDate()方法用于设置一个月的某一天。 2.expires的设置 1document.cookie = 'user='+ encodeURIComponent('name') + ';expires = ' + new Date(0) <strong>,<em>和<b>,<i>标签1234567<strong> 标签和 <em> 标签一样,用于强调文本,但它强调的程度更强一些。em 是 斜体强调标签,更强烈强调,表示内容的强调点。相当于html元素中的 <i>...</i>;< b > < i >是视觉要素,分别表示无意义的加粗,无意义的斜体。em 和 strong 是表达要素(phrase elements)。 说说你对AMD和Commonjs的理解CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。 AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的。 详情:也谈webpack及其开发模式 document.write()的用法document.write()方法可以用在两个方面:页面载入过程中用实时脚本创建页面内容,以及用延时脚本创建本窗口或新窗口的内容。 document.write只能重绘整个页面。innerHTML可以重绘页面的一部分 编写一个方法 求一个字符串的字节长度假设:一个英文字符占用一个字节,一个中文字符占用两个字节 1234567891011121314151617 function GetBytes(str){ var len = str.length; var bytes = len; for(var i=0; i<len; i++){ if (str.charCodeAt(i) > 255) bytes++; } return bytes; }alert(GetBytes("你好,as")); git fetch和git pull的区别123git pull:相当于是从远程获取最新版本并merge到本地git fetch:相当于是从远程获取最新版本到本地,不会自动merge 说说你对MVC和MVVM的理解 MVC View 传送指令到 Controller Controller 完成业务逻辑后,要求 Model 改变状态 Model 将新的数据发送到 View,用户得到反馈 所有通信都是单向的。 Angular它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。 组成部分Model、View、ViewModel View:UI界面 ViewModel:它是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model; Model:数据访问层 请解释什么是事件代理事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能。 attribute和property的区别是什么?attribute是dom元素在文档中作为html标签拥有的属性; property就是dom元素在js中作为对象拥有的属性。 所以: 对于html的标准属性来说,attribute和property是同步的,是会自动更新的, 但是对于自定义的属性来说,他们是不同步的, 说说网络分层里七层模型是哪七层 应用层:应用层、表示层、会话层(从上往下)(HTTP、FTP、SMTP、DNS) 传输层(TCP和UDP) 网络层(IP) 物理和数据链路层(以太网) 每一层的作用如下: 12345678物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)数据链路层:将比特组装成帧和点到点的传递(帧Frame)网络层:负责数据包从源到宿的传递和网际互连(包PackeT)传输层:提供端到端的可靠报文传递和错误恢复(段Segment)会话层:建立、管理和终止会话(会话协议数据单元SPDU)表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)应用层:允许访问OSI环境的手段(应用协议数据单元APDU) 各种协议 ICMP协议: 因特网控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。TFTP协议: 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。DHCP协议: 动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。 说说mongoDB和MySQL的区别MySQL是传统的关系型数据库,MongoDB则是非关系型数据库 mongodb以BSON结构(二进制)进行存储,对海量数据存储有着很明显的优势。 对比传统关系型数据库,NoSQL有着非常显著的性能和扩展性优势,与关系型数据库相比,MongoDB的优点有:①弱一致性(最终一致),更能保证用户的访问速度:②文档结构的存储方式,能够更便捷的获取数据。 讲讲304缓存的原理服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。 304是HTTP状态码,服务器用来标识这个文件没修改,不返回内容,浏览器在接收到个状态码后,会使用浏览器已缓存的文件 客户端请求一个页面(A)。 服务器返回页面A,并在给A加上一个ETag。 客户端展现该页面,并将页面连同ETag一起缓存。 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。 什么样的前端代码是好的高复用低耦合,这样文件小,好维护,而且好扩展。 代码题 css代码题 js代码题]]></content>
<categories>
<category>前端</category>
<category>面试</category>
</categories>
<tags>
<tag>Interview</tag>
<tag>面试</tag>
</tags>
</entry>
<entry>
<title><![CDATA[我是用hexo搭建个人博客的经历]]></title>
<url>%2F2017%2F04%2F19%2Fhexo%2F</url>
<content type="text"><![CDATA[首先贴出两个网址hexo的使用教程nextT主题的替换教程 下面总结下我自己的填坑经历关于hexo本地操作 npm install -g hexo hexo init hexo generate(hexo g也可以,用来生成静态页面) hexo server (浏览器输入http://localhost:4000)(然后去配置github,再继续第五步) npm install hexo-deployer-git –save (这一步是在配置完github之后) hexo deploygithub配置 建立Repository (名字为yesixuan.github.io) 修改项目配置文件 (根目录下的_config.yml)1234deploy: type: git repo: https://github.com/leopardpan/leopardpan.github.io.git branch: master 部署以及常用操作 hexo clean hexo generate hexo deploy hexo new”postName” #新建文章 hexo new page”pageName” #新建页面 hexo generate #生成静态页面至public目录 hexo server #开启预览访问端口(默认端口4000,’ctrl + c’关闭server) hexo deploy #将.deploy目录部署到GitHub hexo help # 查看帮助 hexo version #查看Hexo的版本 关于更换主题(这里以nextT为例) 下载主题的zip包。(地址:https://github.com/iissnan/hexo-theme-next/releases) 结业后的文件夹改名为next,放入项目的themes目录里面 打开站点配置文件,找到theme字段,并将其值更改为next 。 此时可已在本地测试看看 切换hexo里面的不同主题,在主题配置文件里面找到scheme的配置项,自己加减注释来切换 关于标签与分类 原地址 hexo new page tags (hexo new page categories) 确认站点配置文件里有tag_dir: tags (category_dir: categories) 确认主题配置文件里有tags: /tags (categories: /categories) 编辑站点的source/tags/index.md (略…),添加1234title: tagsdate: 2015-10-20 06:49:50type: "tags"comments: false]]></content>
<categories>
<category>杂项</category>
</categories>
<tags>
<tag>hexo</tag>
<tag>nextT</tag>
<tag>blog</tag>
<tag>静态博客</tag>
</tags>
</entry>
</search>