-
Notifications
You must be signed in to change notification settings - Fork 2
/
atom.xml
2208 lines (2053 loc) · 497 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Alon's Blog]]></title>
<subtitle><![CDATA[一边翻译一边学习---涂鸦码龙]]></subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://jinlong.github.io/"/>
<updated>2017-05-08T07:01:31.000Z</updated>
<id>http://jinlong.github.io/</id>
<author>
<name><![CDATA[Alon Zhang]]></name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title><![CDATA[浏览器前端优化]]></title>
<link href="http://jinlong.github.io/2017/05/08/optimising-the-front-end-for-the-browser/"/>
<id>http://jinlong.github.io/2017/05/08/optimising-the-front-end-for-the-browser/</id>
<published>2017-05-08T12:33:31.000Z</published>
<updated>2017-05-08T07:01:31.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:<a href="https://hackernoon.com/optimising-the-front-end-for-the-browser-f2f51a29c572" target="_blank" rel="external">Optimising the front end for the browser</a><br>笔记:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
</blockquote>
<p>优化关乎速度和满意度。</p>
<ul>
<li>从用户体验(UX)角度,我们希望前端网页可以快速加载</li>
<li><p>从开发体验(DX)角度,我们希望前端是快速,简洁,规范的</p>
<h1 id="浏览器都做了什么">浏览器都做了什么</h1></li>
</ul>
<p>我们希望浏览器打开一个简单的网页</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="meta"><!DOCTYPE html></span></div><div class="line"><span class="tag"><<span class="name">html</span>></span></div><div class="line"> <span class="tag"><<span class="name">head</span>></span></div><div class="line"> <span class="tag"><<span class="name">title</span>></span>The "Click the button" page<span class="tag"></<span class="name">title</span>></span></div><div class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></div><div class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"styles.css"</span> /></span></div><div class="line"> <span class="tag"></<span class="name">head</span>></span></div><div class="line"> </div><div class="line"> <span class="tag"><<span class="name">body</span>></span></div><div class="line"> <span class="tag"><<span class="name">h1</span>></span></div><div class="line"> Click the button.</div><div class="line"> <span class="tag"></<span class="name">h1</span>></span></div><div class="line"> </div><div class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">"button"</span>></span>Click me<span class="tag"></<span class="name">button</span>></span></div><div class="line"> </div><div class="line"> <span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></div><div class="line"> var button = document.querySelector("button");</div><div class="line"> button.style.fontWeight = "bold";</div><div class="line"> button.addEventListener("click", function () {</div><div class="line"> alert("Well done.");</div><div class="line"> });</div><div class="line"> <span class="tag"></<span class="name">script</span>></span></div><div class="line"> <span class="tag"></<span class="name">body</span>></span></div><div class="line"><span class="tag"></<span class="name">html</span>></span></div></pre></td></tr></table></figure>
<h2 id="浏览器如何渲染网页">浏览器如何渲染网页</h2><ol>
<li>使用 HTML 创建文档对象模型(<strong>DOM</strong>)</li>
<li>使用 CSS 创建 CSS 对象模型(<strong>CSSOM</strong>)</li>
<li>基于 DOM 和 CSSOM 执行脚本(<strong>Scripts</strong>)</li>
<li>合并 DOM 和 CSSOM 形成渲染树(<strong>Render Tree</strong>)</li>
<li>使用渲染树布局(<strong>Layout</strong>)所有元素</li>
<li>渲染(<strong>Paint</strong>)所有元素</li>
</ol>
<p><img src="/image/browser-fe-optimizing/p1.png" alt="附图"></p>
<a id="more"></a>
<h1 id="步骤一_—_HTML">步骤一 — HTML</h1><p>浏览器从上到下读取标签,把他们分解成节点,从而创建 DOM 。</p>
<p><img src="/image/browser-fe-optimizing/p2.png" alt="附图"></p>
<h2 id="HTML_加载优化策略">HTML 加载优化策略</h2><ul>
<li>样式在顶部,脚本在底部</li>
</ul>
<p>总体思路是尽可能早的加载样式,尽可能晚的加载脚本。原因是脚本执行之前,需要 HTML 和 CSS 解析完成,因此,样式尽可能的往顶部放,当底部脚本开始执行之前,样式有足够的时间完成计算。</p>
<p>进一步讲讲如何优化</p>
<ul>
<li>最小化和压缩</li>
</ul>
<p>方法可用于所有内容,包括 HTML,CSS,JavaScript,图片和其它资源。</p>
<p>最小化是移除所有多余的字符,包括空格,注释,多余的分号,等等。</p>
<p>压缩比如 GZip,大大压缩下载文件的大小</p>
<p>两种方法都用的情况下,资源加载量减少了 80% 到 90%。比如:<a href="https://css-tricks.com/the-difference-between-minification-and-gzipping/#article-header-id-2" target="_blank" rel="external">bootstrap 节省了 87% 的流量</a>。</p>
<ul>
<li>无障碍</li>
</ul>
<p>不会提升页面的下载速度,但会大大提升残障人士的满意度。给元素加上 <code>aria</code> 标签,图片提供 <code>alt</code> 文本,<a href="http://www.clarissapeterson.com/2012/11/html5-accessibility/" target="_blank" rel="external">HTML 5 无障碍参见</a>。</p>
<p>使用诸如 <a href="http://wave.webaim.org/" target="_blank" rel="external">WAVE</a> 的工具鉴别哪些地方可以提高可访问性。</p>
<h1 id="步骤二_—_CSS">步骤二 — CSS</h1><p>当浏览器发现任何与节点相关的样式时,比如:外部,内部,或行内样式,立即停止<strong>渲染</strong> DOM ,并利用这些节点创建 CSSOM。这就是 CSS “<strong>渲染阻塞</strong>“ 的由来。这里是不同类型样式的<a href="https://vineetgupta22.wordpress.com/2011/07/09/inline-vs-internal-vs-external-css/" target="_blank" rel="external">优缺点</a>。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">//外部样式</div><div class="line"><span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"styles.css"</span>></span></div><div class="line"></div><div class="line">// 内部样式</div><div class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></div><div class="line"> h1 {</div><div class="line"> font-size: 18px;</div><div class="line"> }</div><div class="line"><span class="tag"></<span class="name">style</span>></span></div><div class="line"></div><div class="line">// 行内样式</div><div class="line"><span class="tag"><<span class="name">button</span> <span class="attr">style</span>=<span class="string">"background-color: blue;"</span>></span>Click me<span class="tag"></<span class="name">button</span>></span></div></pre></td></tr></table></figure>
<p>CSSOM 节点创建与 DOM 节点创建类似,随后,两者合并如下:</p>
<p><img src="/image/browser-fe-optimizing/p3.png" alt="附图"></p>
<p>CSSOM 的构建会阻塞页面的渲染,因此我们想尽早加载样式,</p>
<h2 id="CSS_加载优化策略">CSS 加载优化策略</h2><ul>
<li>使用 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media" target="_blank" rel="external">media 属性</a></li>
</ul>
<p>media 属性指定加载样式的条件,比如:符合最大或最小分辨率?还是面向屏幕阅读器?</p>
<ul>
<li>延迟加载 CSS</li>
</ul>
<p>有些样式,比如:首屏以下的,或者不那么重要的,可以等待首屏最有价值的内容渲染完成再加载,可以使用脚本等待页面加载,然后再插入样式。</p>
<p>这有两个栗子:<a href="https://jakearchibald.com/2016/link-in-body/" target="_blank" rel="external">The future of loading CSS</a>,<a href="https://www.giftofspeed.com/defer-loading-css/" target="_blank" rel="external">Defer load CSS</a></p>
<ul>
<li>只加载需要的样式</li>
</ul>
<p>使用 <a href="https:/giakki/uncss" target="_blank" rel="external">uncss</a> 类似的工具,尽量移除不需要的样式。</p>
<h1 id="步骤三_—_JavaScript">步骤三 — JavaScript</h1><p>浏览器不断构建 DOM / CSSOM 节点,直到发现外部或者行内的脚本。</p>
<p>由于脚本可能需要访问或操作之前的 HTML 或样式,我们必须等待它们构建完成。</p>
<p>因此浏览器必须停止<strong>解析</strong>节点,完成构建 CSSOM,执行脚本,然后再继续。这就是 JavaScript 被称作“<strong>解析器阻塞</strong>”的原因。</p>
<p>脚本只能等到先前的 CSS 节点构建完成。</p>
<p><img src="/image/browser-fe-optimizing/p4.png" alt="附图"></p>
<h2 id="JavaScript_加载优化策略">JavaScript 加载优化策略</h2><ul>
<li>异步加载脚本</li>
</ul>
<p>脚本添加 <code>async</code> 属性,可以通知浏览器不要阻塞其余页面的加载,下载脚本处于较低的优先级。一旦下载完成,就可以执行。</p>
<p><img src="/image/browser-fe-optimizing/p5.png" alt="附图"></p>
<p><code>async</code> 适用于不影响 DOM 或 CSSOM 的脚本,对一些跟我们的代码无关的,不影响用户体验的外部脚本尤其适用,比如:分析统计脚本。</p>
<ul>
<li>延迟加载脚本</li>
</ul>
<p><code>defer</code> 跟 <code>async</code> 非常相似,不会阻塞页面加载,但会等到 HTML 完成解析后再执行。</p>
<p><img src="/image/browser-fe-optimizing/p6.png" alt="附图"></p>
<p>使用 defer 策略的 <a href="https://varvy.com/pagespeed/defer-loading-javascript.html" target="_blank" rel="external">另一个好选择</a>,或者也可以使用 <code>addEventListener</code>,了解更多,参加<a href="http://stackoverflow.com/questions/588040/window-onload-vs-document-onload" target="_blank" rel="external">这里</a>。</p>
<p>不幸的是 <code>async</code> 和 <code>defer</code> 对于行内的脚本不起作用,浏览器默认会编译执行它们。</p>
<ul>
<li>操作之前克隆节点</li>
</ul>
<p>多次操作 DOM 时可以尝试,首先克隆整个 DOM 节点更加高效,操作克隆后的节点,然后替换先前的节点,避免了多次重绘,降低了 CPU 和内存消耗,同时也避免了不必要的页面闪烁。</p>
<p>需要注意,克隆的时候并没有克隆事件监听。</p>
<ul>
<li>Preload/Prefetch/Prerender/Preconnect</li>
</ul>
<p>这些新属性并不是所有的浏览器都支持。了解详情可以看这里:<a href="https://css-tricks.com/prefetching-preloading-prebrowsing/" target="_blank" rel="external">Prefetching, preloading, prebrowsing</a></p>
<h1 id="步骤四_—_渲染树(Render_Tree)">步骤四 — 渲染树(Render Tree)</h1><p>一旦所有节点已被解析,DOM 和 CSSOM 准备合并,浏览器便会构建渲染树。如果我们把节点想象成单词,那么对象模型就是句子,渲染树便是整个页面。</p>
<p><img src="/image/browser-fe-optimizing/p7.png" alt="附图"></p>
<h1 id="步骤五_—_布局(Layout)">步骤五 — 布局(Layout)</h1><p>布局阶段需要确定页面上所有元素的大小和位置。</p>
<p><img src="/image/browser-fe-optimizing/p8.png" alt="附图"></p>
<h1 id="步骤六_—_渲染(Paint)">步骤六 — 渲染(Paint)</h1><p>最终的渲染阶段,会真正地光栅化屏幕上的像素,把页面呈现给用户。</p>
<p><img src="/image/browser-fe-optimizing/p9.png" alt="附图"></p>
<p>整个过程耗时1秒或十分之一秒,我们的任务是让它更快。</p>
<p>如果 JavaScript 事件改变了页面的某部分,便会引起渲染树的重绘,并且迫使布局(Layout)和渲染(Paint)过程再次进行。</p>
<h1 id="浏览器如何发起网络请求">浏览器如何发起网络请求</h1><p>当浏览器请求一个 URL,服务端会响应一些 HTML。</p>
<p>我们需要认识一个新术语,关键渲染路径(Critical Rendering Path (CRP)),就是浏览器渲染页面的步骤数,如下图。</p>
<p><img src="/image/browser-fe-optimizing/p10.png" alt="附图"></p>
<h2 id="关键路径长度">关键路径长度</h2><p>关键渲染路径的度量标准是路径长度。最理想的关键路径长度是1。</p>
<p>如果页面包含一些内部样式和 JavaScript ,关键路径发生以下改变。</p>
<p><img src="/image/browser-fe-optimizing/p11.png" alt="附图"></p>
<p>新增两步,<strong>构建 CSSOM</strong>和<strong>执行脚本</strong>,因为我们的 HTML 有内部样式和脚本需要计算。由于没有外部请求,我们的关键路径长度没变。</p>
<p>但是注意,我们的 HTML 大小增加到了 2kb,某些地方还是受了影响。</p>
<h2 id="关键字节数">关键字节数</h2><p>三个度量标准之二出现了,关键字节数,它用来衡量渲染页面需要传送多少字节数。</p>
<p>如果你认为不需要外部资源,就大错特错了,外部资源可以被缓存。</p>
<p>我们使用一个外部 CSS 文件,一个外部 JavaScript 文件,和一个外部带 <code>async</code> 属性的 JavaScript 文件。关键路径图如下:</p>
<p><img src="/image/browser-fe-optimizing/p12.png" alt="附图"></p>
<p>浏览器请求页面,构建 DOM,发现外部资源后开始下载,CSS 和 JavaScript 有较高的优先级,其它资源次之。</p>
<p><code>styles.css</code> 和 <code>app.js</code> 通过另一个关键路径获取。暂时不获取 <code>analytics.js</code> ,因为加了 <code>async</code> 属性,浏览器将用另一个线程下载它,它处于较低优先级,不会阻塞页面渲染,也不影响关键路径。</p>
<h2 id="关键文件">关键文件</h2><p>最后一个度量标准是关键文件,浏览器渲染页面需要下载的文件总量。以上例子,HTML 文件,CSS 和 JavaScript 文件算关键文件,<code>async</code> 的脚本不算。当然是文件越少越好。</p>
<h2 id="回到关键路径长度">回到关键路径长度</h2><p>以上例子就是最长的渲染路径吗?我认为渲染页面时,我们仅需要下载 HTML,CSS 和 JavaScript 文件,仅通过两次服务器往返就做到了。</p>
<h2 id="HTTP1_文件限制">HTTP1 文件限制</h2><p>我们浏览器的 HTTP1 协议,在同一个域名,同一次,允许下载的文件数有最大限制,范围从 2(老旧的浏览器)到 6(Edge,Chrome)。</p>
<p>各种浏览器请求文件的最大并发数,参见<a href="http://sgdev-blog.blogspot.co.uk/2014/01/maximum-concurrent-connection-to-same.html" target="_blank" rel="external">Maximum concurrent connections to the same domain for browsers</a>。</p>
<p>通过把一些资源存放到影子域名,可以绕过这个限制,以达到最佳优化效果。</p>
<p><strong>注意</strong>:不要把关键的 CSS 放到根域名之外的其他域名,有些场景下会对 DNS 查找和延迟起反作用。</p>
<h2 id="HTTP2">HTTP2</h2><p>如果网站使用了 HTTP2,并且用户的浏览器也兼容,则可以完全避开这个下载限制。</p>
<p><a href="https://tools.keycdn.com/http2-test" target="_blank" rel="external">这里</a>有个 HTTP2 测试网站。</p>
<h2 id="TCP_往返限制">TCP 往返限制</h2><p>每一次服务器往返可以传送的最大数据量是 14kb,包括所有 HTML,CSS 和脚本的网络请求。</p>
<p>如果我们的 HTML,或者积累的资源请求超过 14kb时,需要多做一次服务器往返。</p>
<h2 id="大魔法师">大魔法师</h2><p>我们整个 HTML 页面可以很好的压缩, GZip 可以压缩到 2kb,远低于 14kb 的限制,因此,一次服务器往返就可以搞定。</p>
<p><img src="/image/browser-fe-optimizing/p13.png" alt="附图"></p>
<p><code>关键路径度量: 长度 1,文件数 1,字节数 2kb</code></p>
<p>浏览器发现外部资源(CSS 和 JavaScript)时,发起请求开始下载它们。首要下载的 CSS 文件是 14kb,达到了往返传输的最大限制,因此增加了一条关键路径。</p>
<p><img src="/image/browser-fe-optimizing/p14.png" alt="附图"></p>
<p><code>关键路径度量: 长度 2,文件数 2,字节数 16kb</code></p>
<p>余下的资源低于 14kb,但是总共有 7 个资源,由于网站未启用 HTTP2,我们的 Chrome,每一次往返仅可以下载 6 个文件。</p>
<p><img src="/image/browser-fe-optimizing/p15.png" alt="附图"></p>
<p><code>关键路径度量: 长度 3,文件数 8,字节数 28kb</code></p>
<p>下载完最终文件,并开始渲染 DOM。</p>
<p><img src="/image/browser-fe-optimizing/p16.png" alt="附图"></p>
<p><code>关键路径度量: 长度 4,文件数 9,字节数 30kb</code></p>
<p>基于以上的信息和知识,发起每个连接时,就可以准确地预估页面的性能了。</p>
<h2 id="浏览器网络优化策略">浏览器网络优化策略</h2><ul>
<li>Pagespeed Insights</li>
</ul>
<p>使用 <a href="https://developers.google.com/speed/pagespeed/insights/" target="_blank" rel="external">Insights</a> 鉴别性能问题,Chrome DevTools 也有个 <code>audit</code> 标签。</p>
<ul>
<li>充分利用 Chrome 开发者工具</li>
</ul>
<p><a href="https://developers.google.com/web/tools/chrome-devtools/network-performance/understanding-resource-timing" target="_blank" rel="external">这篇文章</a> 值得一读,帮你理解网络资源</p>
<ul>
<li>在优质的环境里开发,在艰苦的环境里测试</li>
</ul>
<p>开发时大可使用 1Tb SSD,32G 内存的 Macbook Pro ,但是性能测试时还是要到 Chrome 的 <code>network</code> 标签下模拟低带宽的情形,从而获取有价值的信息。</p>
<ul>
<li>合并资源/文件</li>
</ul>
<p>基本上,每接收到一个外部 CSS 和 JavaScript 文件,浏览器都会构建 CSSOM,执行脚本。尽管几个文件可以在一次往返中传送,但也浪费了浏览器的宝贵时间和资源,最好还是合并文件,减少不必要的加载。</p>
<ul>
<li>首屏内容使用内部样式</li>
</ul>
<p>内部 CSS 和 JavaScript 不需要请求外部资源,相反,外部资源又可以被缓存,并保持 DOM 轻量,两者没有非黑即白。</p>
<p>但是一个非常好的论点是首屏关键内容使用内部样式,可以避免请求额外的资源,节省时间做最有意义的渲染。</p>
<ul>
<li><p>最小化/压缩图片</p>
</li>
<li><p>延迟加载图片</p>
</li>
<li><p>异步记载字体</p>
</li>
<li><p>是否真正需要 JavaScript / CSS?</p>
</li>
</ul>
<p>原生 HTML 元素可以实现的行为是否用了脚本?是否有样式或者图标可以行内创建的,不需要内部/外部资源?比如:<a href="https://css-tricks.com/using-svg/#article-header-id-7" target="_blank" rel="external">行内 SVG</a>。</p>
<ul>
<li>CDN</li>
</ul>
<p>可以利用 CDN(内容分发网络)存储资源,它会从离用户最近,延迟最低的位置分发到用户设备,加载时间更快。</p>
<p><strong>延伸阅读</strong></p>
<ul>
<li><a href="https://developers.google.com/web/fundamentals/performance/" target="_blank" rel="external">Google 的优化文档</a></li>
<li><a href="https://hpbn.co/" target="_blank" rel="external">高性能浏览器网络</a></li>
</ul>
<h1 id="综述">综述</h1><p>关键渲染路径是最重要的,它使得网站优化有规律可循。需要关注3个指标:</p>
<p>1—关键字节数<br>2—关键文件数<br>3—关键路径长度</p>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文:<a href="https://hackernoon.com/optimising-the-front-end-for-the-browser-f2f51a29c572">Optimising the front end for the browser</a><br>笔记:<a href="http://weibo.com/newwave">涂鸦码龙</a></p>
</blockquote>
<p>优化关乎速度和满意度。</p>
<ul>
<li>从用户体验(UX)角度,我们希望前端网页可以快速加载</li>
<li><p>从开发体验(DX)角度,我们希望前端是快速,简洁,规范的</p>
<h1 id="浏览器都做了什么">浏览器都做了什么</h1></li>
</ul>
<p>我们希望浏览器打开一个简单的网页</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="meta"><!DOCTYPE html></span></div><div class="line"><span class="tag"><<span class="name">html</span>></span></div><div class="line"> <span class="tag"><<span class="name">head</span>></span></div><div class="line"> <span class="tag"><<span class="name">title</span>></span>The "Click the button" page<span class="tag"></<span class="name">title</span>></span></div><div class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></div><div class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"styles.css"</span> /></span></div><div class="line"> <span class="tag"></<span class="name">head</span>></span></div><div class="line"> </div><div class="line"> <span class="tag"><<span class="name">body</span>></span></div><div class="line"> <span class="tag"><<span class="name">h1</span>></span></div><div class="line"> Click the button.</div><div class="line"> <span class="tag"></<span class="name">h1</span>></span></div><div class="line"> </div><div class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">"button"</span>></span>Click me<span class="tag"></<span class="name">button</span>></span></div><div class="line"> </div><div class="line"> <span class="tag"><<span class="name">script</span>></span><span class="undefined"></div><div class="line"> var button = document.querySelector("button");</div><div class="line"> button.style.fontWeight = "bold";</div><div class="line"> button.addEventListener("click", function () {</div><div class="line"> alert("Well done.");</div><div class="line"> });</div><div class="line"> </span><span class="tag"></<span class="name">script</span>></span></div><div class="line"> <span class="tag"></<span class="name">body</span>></span></div><div class="line"><span class="tag"></<span class="name">html</span>></span></div></pre></td></tr></table></figure>
<h2 id="浏览器如何渲染网页">浏览器如何渲染网页</h2><ol>
<li>使用 HTML 创建文档对象模型(<strong>DOM</strong>)</li>
<li>使用 CSS 创建 CSS 对象模型(<strong>CSSOM</strong>)</li>
<li>基于 DOM 和 CSSOM 执行脚本(<strong>Scripts</strong>)</li>
<li>合并 DOM 和 CSSOM 形成渲染树(<strong>Render Tree</strong>)</li>
<li>使用渲染树布局(<strong>Layout</strong>)所有元素</li>
<li>渲染(<strong>Paint</strong>)所有元素</li>
</ol>
<p><img src="/image/browser-fe-optimizing/p1.png" alt="附图"></p>]]>
</summary>
<category term="前端优化" scheme="http://jinlong.github.io/tags/%E5%89%8D%E7%AB%AF%E4%BC%98%E5%8C%96/"/>
<category term="前端优化" scheme="http://jinlong.github.io/categories/%E5%89%8D%E7%AB%AF%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title><![CDATA[JavaScript 数组方法对比]]></title>
<link href="http://jinlong.github.io/2017/02/04/javascript-array-methods-mutating-vs-non-mutating/"/>
<id>http://jinlong.github.io/2017/02/04/javascript-array-methods-mutating-vs-non-mutating/</id>
<published>2017-02-04T10:17:47.000Z</published>
<updated>2017-02-04T09:29:35.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:“<a href="http://lorenstewart.me/2017/01/22/javascript-array-methods-mutating-vs-non-mutating/" target="_blank" rel="external">JavaScript Array Methods: Mutating vs. Non-Mutating</a>”<br>笔记:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a> </p>
</blockquote>
<p>JavaScript 提供了多种新增,移除,替换数组元素的方法,但是有些会影响原来的数组;有些则不会,它是新建了一个数组。</p>
<p><strong>注意</strong>:区分以下两个方法的不同点:</p>
<ol>
<li><code>array.splice()</code> 影响原来的数组</li>
<li><code>array.slice()</code> 不影响原来的数组</li>
</ol>
<h1 id="I-_新增:影响原数组">I. 新增:影响原数组</h1><p>使用 <code>array.push()</code> 和 <code>array.ushift()</code> 新增元素会影响原来的数组。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingAdd = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line">mutatingAdd.push(<span class="string">'f'</span>); <span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f']</span></div><div class="line">mutatingAdd.unshift(<span class="string">'z'</span>); <span class="comment">// ['z', 'b', 'c', 'd', 'e' 'f']</span></div></pre></td></tr></table></figure>
<a id="more"></a>
<h1 id="II-_新增:不影响原数组">II. 新增:不影响原数组</h1><p>两种方式新增元素不会影响原数组,第一种是 <code>array.concat()</code> 。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> arr1 = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>];</div><div class="line"></div><div class="line"><span class="keyword">const</span> arr2 = arr1.concat(<span class="string">'f'</span>); <span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f'] (注:原文有误,我做了修改 “.” ---> “,”)</span></div><div class="line"><span class="built_in">console</span>.log(arr1); <span class="comment">// ['a', 'b', 'c', 'd', 'e']</span></div></pre></td></tr></table></figure>
<p>第二种方法是使用 JavaScript 的展开(spread)操作符,展开操作符是三个点(…)</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> arr1 = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>];</div><div class="line"></div><div class="line"><span class="keyword">const</span> arr2 = [...arr1, <span class="string">'f'</span>]; <span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f'] </span></div><div class="line"><span class="keyword">const</span> arr3 = [<span class="string">'z'</span>, ...arr1]; <span class="comment">// ['z', 'a', 'b', 'c', 'd', 'e']</span></div></pre></td></tr></table></figure>
<p>展开操作符会复制原来的数组,从原数组取出所有元素,然后存入新的环境。</p>
<h1 id="III-_移除:影响原数组">III. 移除:影响原数组</h1><p>使用 <code>array.pop()</code> 和 <code>array.shift()</code> 移除数组元素时,会影响原来的数组。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingRemove = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line">mutatingRemove.pop(); <span class="comment">// ['a', 'b', 'c', 'd'] </span></div><div class="line">mutatingRemove.shift(); <span class="comment">// ['b', 'c', 'd']</span></div></pre></td></tr></table></figure>
<p><code>array.pop()</code> 和 <code>array.shift()</code> 返回被移除的元素,你可以通过一个变量获取被移除的元素。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingRemove = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>];</div><div class="line"></div><div class="line"><span class="keyword">const</span> returnedValue1 = mutatingRemove.pop(); </div><div class="line"><span class="built_in">console</span>.log(mutatingRemove); <span class="comment">// ['a', 'b', 'c', 'd'] </span></div><div class="line"><span class="built_in">console</span>.log(returnedValue1); <span class="comment">// 'e'</span></div><div class="line"></div><div class="line"><span class="keyword">const</span> returnedValue2 = mutatingRemove.shift(); </div><div class="line"><span class="built_in">console</span>.log(mutatingRemove); <span class="comment">// ['b', 'c', 'd'] </span></div><div class="line"><span class="built_in">console</span>.log(returnedValue2); <span class="comment">// 'a'</span></div></pre></td></tr></table></figure>
<p><code>array.splice()</code> 也可以删除数组的元素。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingRemove = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line">mutatingRemove.splice(<span class="number">0</span>, <span class="number">2</span>); <span class="comment">// ['c', 'd', 'e']</span></div></pre></td></tr></table></figure>
<p>像 <code>array.pop()</code> 和 <code>array.shift()</code> 一样,<code>array.splice()</code> 同样返回移除的元素。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingRemove = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line"><span class="keyword">let</span> returnedItems = mutatingRemove.splice(<span class="number">0</span>, <span class="number">2</span>); </div><div class="line"><span class="built_in">console</span>.log(mutatingRemove); <span class="comment">// ['c', 'd', 'e'] </span></div><div class="line"><span class="built_in">console</span>.log(returnedItems) <span class="comment">// ['a', 'b']</span></div></pre></td></tr></table></figure>
<h1 id="IV-_移除:不影响原数组">IV. 移除:不影响原数组</h1><p>JavaScript 的 <code>array.filter()</code> 方法基于原数组创建一个新数组,新数组仅包含匹配特定条件的元素。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> arr1 = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>];</div><div class="line"></div><div class="line"><span class="keyword">const</span> arr2 = arr1.filter(<span class="function"><span class="params">a</span> =></span> a !== <span class="string">'e'</span>); <span class="comment">// ['a', 'b', 'c', 'd'](注:原文有误,我做了修改) </span></div><div class="line"><span class="comment">// 或者</span></div><div class="line"><span class="keyword">const</span> arr2 = arr1.filter(<span class="function"><span class="params">a</span> =></span> { </div><div class="line"> <span class="keyword">return</span> a !== <span class="string">'e'</span>;</div><div class="line">}); <span class="comment">// ['a', 'b', 'c', 'd'](注:原文有误,我做了修改)</span></div></pre></td></tr></table></figure>
<p>以上代码的条件是“不等于 ‘e’ ”,因此新数组(<code>arr2</code>)里面没有包含 ‘e’。</p>
<hr>
<p><strong>箭头函数的独特性</strong>:</p>
<p><strong>单行箭头函数</strong>,’return’ 关键字是默认自带的,不需要手动书写。</p>
<p>可是,<strong>多行箭头函数</strong>就需要明确地返回一个值。</p>
<hr>
<p>另一种不影响原数组的方式是 <code>array.slice()</code>(不要与 <code>array.splice()</code> 混淆)。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> arr1 = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line"><span class="keyword">const</span> arr2 = arr1.slice(<span class="number">1</span>, <span class="number">5</span>) <span class="comment">// ['b', 'c', 'd', 'e'] </span></div><div class="line"><span class="keyword">const</span> arr3 = arr1.slice(<span class="number">2</span>) <span class="comment">// ['c', 'd', 'e']</span></div></pre></td></tr></table></figure>
<h1 id="V-_替换:影响原数组">V. 替换:影响原数组</h1><p>如果知道替换哪一个元素,可以使用 <code>array.splice()</code> 。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingReplace = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line">mutatingReplace.splice(<span class="number">2</span>, <span class="number">1</span>, <span class="number">30</span>); <span class="comment">// ['a', 'b', 30, 'd', 'e'] </span></div><div class="line"><span class="comment">// 或者</span></div><div class="line">mutatingReplace.splice(<span class="number">2</span>, <span class="number">1</span>, <span class="number">30</span>, <span class="number">31</span>); <span class="comment">// ['a', 'b', 30, 31, 'd', 'e']</span></div></pre></td></tr></table></figure>
<h1 id="VI-_替换:不影响原数组">VI. 替换:不影响原数组</h1><p>可以使用 <code>array.map()</code> 创建一个新数组,并且可以检查每一个元素,根据特定的条件替换它们。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> arr1 = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>] </div><div class="line"><span class="keyword">const</span> arr2 = arr1.map(<span class="function"><span class="params">item</span> =></span> { </div><div class="line"> <span class="keyword">if</span>(item === <span class="string">'c'</span>) {</div><div class="line"> item = <span class="string">'CAT'</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> item;</div><div class="line">}); <span class="comment">// ['a', 'b', 'CAT', 'd', 'e']</span></div></pre></td></tr></table></figure>
<p><strong>使用 <code>array.map()</code> 转换数据</strong></p>
<p> <code>array.map()</code> 是个强力方法,可以用于转换数据,而不污染原先的数据源。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> origArr = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line"><span class="keyword">const</span> transformedArr = origArr.map(<span class="function"><span class="params">n</span> =></span> n + <span class="string">'Hi!'</span>); <span class="comment">// ['aHi!', 'bHi!', 'cHi!', 'dHi!', 'eHi!'] </span></div><div class="line"><span class="built_in">console</span>.log(origArr); <span class="comment">// ['a', 'b', 'c', 'd', 'e']; // 原数组毫发无损</span></div></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文:“<a href="http://lorenstewart.me/2017/01/22/javascript-array-methods-mutating-vs-non-mutating/">JavaScript Array Methods: Mutating vs. Non-Mutating</a>”<br>笔记:<a href="http://weibo.com/newwave">涂鸦码龙</a> </p>
</blockquote>
<p>JavaScript 提供了多种新增,移除,替换数组元素的方法,但是有些会影响原来的数组;有些则不会,它是新建了一个数组。</p>
<p><strong>注意</strong>:区分以下两个方法的不同点:</p>
<ol>
<li><code>array.splice()</code> 影响原来的数组</li>
<li><code>array.slice()</code> 不影响原来的数组</li>
</ol>
<h1 id="I-_新增:影响原数组">I. 新增:影响原数组</h1><p>使用 <code>array.push()</code> 和 <code>array.ushift()</code> 新增元素会影响原来的数组。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> mutatingAdd = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>]; </div><div class="line">mutatingAdd.push(<span class="string">'f'</span>); <span class="comment">// ['a', 'b', 'c', 'd', 'e', 'f']</span></div><div class="line">mutatingAdd.unshift(<span class="string">'z'</span>); <span class="comment">// ['z', 'b', 'c', 'd', 'e' 'f']</span></div></pre></td></tr></table></figure>]]>
</summary>
<category term="Array" scheme="http://jinlong.github.io/tags/Array/"/>
<category term="JavaScript" scheme="http://jinlong.github.io/categories/JavaScript/"/>
</entry>
<entry>
<title><![CDATA[React Native 的组件通信方式]]></title>
<link href="http://jinlong.github.io/2016/12/16/react-native-component-communication/"/>
<id>http://jinlong.github.io/2016/12/16/react-native-component-communication/</id>
<published>2016-12-16T05:11:24.000Z</published>
<updated>2016-12-16T07:27:19.000Z</updated>
<content type="html"><![CDATA[<p>题外话,说几句我对 React 与 React Native 关系的理解:</p>
<ul>
<li>React 主要用于浏览器端实现一些 UI 组件,也可用于服务端渲染。React 可以使用 HTML 提供的标签,也可封装自定义的组件,React 也提供直接操作 DOM 的方法;</li>
<li>React Native 主要用于实现客户端应用(App)的 UI 组件,它只能使用 Facebook 封装的 Native 组件,或者自己封装的 Native 组件。开发中主要借助 JavaScript,基本告别 HTML 和 CSS 了,不过优点是可以用 ES6。</li>
</ul>
<hr>
<p>言归正传,正文开始</p>
<p>之前没搞过 React ,直接开撸的 React Native,使用过程也是各种踩坑填坑,磕磕绊绊,这里简单总结一下我用过的组件通信的几种方式吧。</p>
<h1 id="React_最基础的_props_和_state">React 最基础的 props 和 state</h1><h2 id="组件内部用_state">组件内部用 state</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"></div><div class="line"> <span class="keyword">this</span>.state = {</div><div class="line"> <span class="attr">isOnline</span>: <span class="literal">true</span> <span class="comment">//组件 state</span></div><div class="line"> };</div><div class="line">}</div><div class="line">render() {</div><div class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.state.isOnline){</div><div class="line"> <span class="comment">//...剩余代码</span></div><div class="line"> }</div><div class="line"> <span class="comment">//...剩余代码</span></div><div class="line">}</div></pre></td></tr></table></figure>
<a id="more"></a>
<h2 id="父子组件通信用_props">父子组件通信用 props</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//父组件设置属性参数</span></div><div class="line"><MyComponet isOnline={<span class="literal">true</span>} /></div><div class="line"></div><div class="line"><span class="comment">//子组件</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"></div><div class="line"> <span class="comment">//子组件获取属性</span></div><div class="line"> <span class="keyword">let</span> isOnline = <span class="keyword">this</span>.props.isOnline; </div><div class="line"> }</div><div class="line"> <span class="comment">//...剩余代码</span></div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="子父组件通信也可用_props">子父组件通信也可用 props</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//子组件</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"> }</div><div class="line"> componentDidMount() {</div><div class="line"> <span class="comment">//子组件给父组件的方法传参</span></div><div class="line"> <span class="keyword">this</span>.props.onChange(<span class="string">'newVal'</span>);</div><div class="line"> }</div><div class="line"> render() {</div><div class="line"> <span class="keyword">return</span> (</div><div class="line"> <span class="xml"><span class="tag"><<span class="name">View</span> /></span></span></div><div class="line"> );</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//父组件</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">parentCpt</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"> <span class="keyword">this</span>.state = {</div><div class="line"> <span class="attr">key</span>: <span class="string">'defVal'</span></div><div class="line"> };</div><div class="line"> }</div><div class="line"> <span class="comment">//父组件接受子组件的参数,并改变 state</span></div><div class="line"> handleChange(val) {</div><div class="line"> <span class="keyword">this</span>.setState({</div><div class="line"> <span class="attr">key</span>: val </div><div class="line"> });</div><div class="line"> }</div><div class="line"> render() {</div><div class="line"> <span class="comment">//...剩余代码</span></div><div class="line"> <span class="keyword">return</span> (</div><div class="line"> <span class="xml"><span class="tag"><<span class="name">MyComponent</span> <span class="attr">onChange</span>=<span class="string">{(val)</span> =></span> {this.handleChange(val)}} /></span></div><div class="line"> );</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h1 id="使用_Refs">使用 Refs</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//子组件</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"> }</div><div class="line"> <span class="comment">//开放的实例方法</span></div><div class="line"> doIt() {</div><div class="line"> <span class="comment">//...做点什么</span></div><div class="line"> }</div><div class="line"> render() {</div><div class="line"> <span class="keyword">return</span> (</div><div class="line"> <span class="xml"><span class="tag"><<span class="name">View</span> /></span></span></div><div class="line"> );</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//父组件</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">parentCpt</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"> }</div><div class="line"> render() {</div><div class="line"> <span class="comment">//this.myCpt 保存组件的实例</span></div><div class="line"> <span class="keyword">return</span> (</div><div class="line"> <span class="xml"><span class="tag"><<span class="name">MyComponent</span> <span class="attr">ref</span>=<span class="string">{(c)</span> =></span> {this.myCpt = c;}} /></span></div><div class="line"> );</div><div class="line"> }</div><div class="line"> componentDidMount() {</div><div class="line"> //调用组件的实例方法</div><div class="line"> this.myCpt.doIt();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h1 id="使用_global">使用 global</h1><p><code>global</code> 类似浏览器里的 <code>window</code> 对象,它是全局的,一处定义,所有组件都可以访问,一般用于存储一些全局的配置参数或方法。</p>
<p><strong><em>使用场景:全局参数不想通过 <code>props</code> 层层组件传递,有些组件对此参数并不关心,只有嵌套的某个组件使用</em></strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">global.isOnline = <span class="literal">true</span>;</div></pre></td></tr></table></figure>
<h1 id="使用_RCTDeviceEventEmitter">使用 RCTDeviceEventEmitter</h1><p><code>RCTDeviceEventEmitter</code> 是一种事件机制,<a href="http://facebook.github.io/react-native/releases/0.39/docs/native-modules-android.html#sending-events-to-javascript" target="_blank" rel="external">React Native 的文档</a>只是草草带过,也可以使用 <code>DeviceEventEmitter</code> ,它是把 <code>RCTDeviceEventEmitter</code> 封装了一层,用法略不同。</p>
<p>按文档所言,<code>RCTDeviceEventEmitter</code> 主要用于 Native 发送事件给 JavaScript,实际上也可以用来发送自定义事件。</p>
<p><strong><em>使用场景:多个组件都使用了异步模块,且异步模块之间有顺序依赖时,可以使用。</em></strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//引入模块</span></div><div class="line"><span class="keyword">import</span> RCTDeviceEventEmitter <span class="keyword">from</span> <span class="string">'RCTDeviceEventEmitter'</span>;</div><div class="line"></div><div class="line"><span class="comment">//监听自定义事件</span></div><div class="line">RCTDeviceEventEmitter.addListener(<span class="string">'customEvt'</span>, (o) => {</div><div class="line"> <span class="built_in">console</span>.log(o.data); <span class="comment">//'some data'</span></div><div class="line"></div><div class="line"> <span class="comment">//做点其它的</span></div><div class="line">});</div><div class="line"></div><div class="line"><span class="comment">//发送自定义事件,可传数据</span></div><div class="line">RCTDeviceEventEmitter.emit(<span class="string">'customEvt'</span>, {</div><div class="line"> <span class="attr">data</span>: <span class="string">'some data'</span></div><div class="line">});</div></pre></td></tr></table></figure>
<h1 id="使用_AsyncStorage">使用 <a href="http://facebook.github.io/react-native/releases/0.39/docs/asyncstorage.html" target="_blank" rel="external">AsyncStorage</a></h1><p>这是官方提供的持久缓存的模块,类似浏览器端的 <code>localStorage</code>,用法也很类似,不过比 <code>localStorage</code> 多了不少 API。</p>
<p>使用场景:当然也类似,退出应用需要保存的少量数据,可以存在这里,至于大小限制,<a href="https:/facebook/react-native/issues/3387" target="_blank" rel="external">Android 貌似是 6M</a> 。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> {</div><div class="line"> AsyncStorage</div><div class="line">} <span class="keyword">from</span> <span class="string">'react-native'</span>;</div><div class="line"></div><div class="line"><span class="comment">//设置</span></div><div class="line">AsyncStorage.setItem(<span class="string">'@MySuperStore:key'</span>, <span class="string">'I like to save it.'</span>);</div><div class="line"><span class="comment">//获取</span></div><div class="line">AsyncStorage.getItem(<span class="string">'@MySuperStore:key'</span>)</div></pre></td></tr></table></figure>
<p>综上所述,这是我能想到的组件通信方式,其它想到了再补充吧。</p>
]]></content>
<summary type="html">
<![CDATA[<p>题外话,说几句我对 React 与 React Native 关系的理解:</p>
<ul>
<li>React 主要用于浏览器端实现一些 UI 组件,也可用于服务端渲染。React 可以使用 HTML 提供的标签,也可封装自定义的组件,React 也提供直接操作 DOM 的方法;</li>
<li>React Native 主要用于实现客户端应用(App)的 UI 组件,它只能使用 Facebook 封装的 Native 组件,或者自己封装的 Native 组件。开发中主要借助 JavaScript,基本告别 HTML 和 CSS 了,不过优点是可以用 ES6。</li>
</ul>
<hr>
<p>言归正传,正文开始</p>
<p>之前没搞过 React ,直接开撸的 React Native,使用过程也是各种踩坑填坑,磕磕绊绊,这里简单总结一下我用过的组件通信的几种方式吧。</p>
<h1 id="React_最基础的_props_和_state">React 最基础的 props 和 state</h1><h2 id="组件内部用_state">组件内部用 state</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">constructor</span>(props) {</div><div class="line"> <span class="keyword">super</span>(props);</div><div class="line"></div><div class="line"> <span class="keyword">this</span>.state = {</div><div class="line"> <span class="attr">isOnline</span>: <span class="literal">true</span> <span class="comment">//组件 state</span></div><div class="line"> };</div><div class="line">}</div><div class="line">render() {</div><div class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.state.isOnline){</div><div class="line"> <span class="comment">//...剩余代码</span></div><div class="line"> }</div><div class="line"> <span class="comment">//...剩余代码</span></div><div class="line">}</div></pre></td></tr></table></figure>]]>
</summary>
<category term="React Native" scheme="http://jinlong.github.io/tags/React-Native/"/>
<category term="React Native" scheme="http://jinlong.github.io/categories/React-Native/"/>
</entry>
<entry>
<title><![CDATA[针对易读性的背景滤镜兼容方案(CSS filter 兼容方案)]]></title>
<link href="http://jinlong.github.io/2016/05/15/Filtered-background-with-fallback-for-legibility/"/>
<id>http://jinlong.github.io/2016/05/15/Filtered-background-with-fallback-for-legibility/</id>
<published>2016-05-15T13:47:29.000Z</published>
<updated>2016-05-23T10:08:43.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:<a href="http://codepen.io/tigt/post/blurred-background-image-with-fallback" target="_blank" rel="external">Filtered background with fallback for legibility</a><br>翻译:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
</blockquote>
<p>你知道现在有多火吗?用这种很大的,高质量的,支持 Retina 屏的<a href="https://www.google.com/search?q=blurred+background+web&tbm=isch" target="_blank" rel="external">模糊的 JPEG 图片作为 header 背景</a> :</p>
<p data-height="265" data-theme-id="0" data-slug-hash="RaprZK" data-default-tab="css,result" data-user="tigt" data-embed-version="2" class="codepen">See the Pen <a href="http://codepen.io/tigt/pen/RaprZK/" target="_blank" rel="external">Web site header, circa 2016</a> by Taylor Hunt (<a href="http://codepen.io/tigt" target="_blank" rel="external">@tigt</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>潜在的问题是如果浏览器不支持滤镜 filter,<a href="https://www.nngroup.com/articles/text-over-images/" target="_blank" rel="external">文字将不可读</a> 。这违背了可访问性的原则,再完美的视觉也无济于事。</p>
<a id="more"></a>
<p><img src="/image/Filtered-background/p1.png" alt="附图1"><br>支持和不支持 filter 的效果对比</p>
<p>因此呢,你需要提前准备好处理模糊的图片,可是设计师偏要你实现动态模糊呢,图片不固定,跟 Apple 的效果一样一样的,咋办?如果是用户上传的图片又咋办?嗯,你需要一台图片处理服务器,听起来成本很高。</p>
<p>我碰巧想到一个妙招,让不支持 <code>filter</code> 的浏览器用颜色图层代替,方案并不完美,但是具有高可读性:</p>
<p><img src="/image/Filtered-background/p2.png" alt="附图2"><br>不支持 filter 的效果</p>
<h1 id="基本实现">基本实现</h1><p>我用伪元素作为背景,因为给元素加 <code>filter</code> 比<a href="http://caniuse.com/css-filter-function" target="_blank" rel="external">单纯给背景加 filter</a> <a href="http://caniuse.com/css-filters" target="_blank" rel="external">兼容性更好</a> 。</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="selector-class">.backdrop</span> { <span class="attribute">position</span>: relative }</div><div class="line"><span class="selector-class">.backdrop</span><span class="selector-pseudo">::after</span> {</div><div class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</div><div class="line"></div><div class="line"> <span class="comment">/* 铺满整个父元素 */</span></div><div class="line"> <span class="attribute">position</span>: absolute;</div><div class="line"> <span class="attribute">top</span>: <span class="number">0</span>; <span class="attribute">right</span>: <span class="number">0</span>; <span class="attribute">bottom</span>: <span class="number">0</span>; <span class="attribute">left</span>: <span class="number">0</span>;</div><div class="line"></div><div class="line"> <span class="comment">/* 放到父元素内容的下层 */</span></div><div class="line"> <span class="attribute">z-index</span>: -<span class="number">2</span>;</div><div class="line"></div><div class="line"> <span class="comment">/* 像父元素一样展示背景 */</span></div><div class="line"> <span class="attribute">background</span>: <span class="number">#222</span> <span class="built_in">url</span>(<span class="string">"inspirational-landscape-and/or-laughing-with-salad.jpg"</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<h1 id="加特效">加特效</h1><p>此时,设计师发话了,让背景图模糊一下,再来个轻微变暗效果就更好了。</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/* 不要忘记前缀,Safari >9.1 和所有 Chrome 仍然需要 -webkit- */</span></div><div class="line"><span class="selector-tag">filter</span>: <span class="selector-tag">blur</span>(4<span class="selector-tag">px</span>) <span class="selector-tag">brightness</span>(75%);</div></pre></td></tr></table></figure>
<p>这么做还远远不够,不支持 <code>filter</code> 的浏览器将严重影响阅读。</p>
<h1 id="上妙招">上妙招</h1><p>是否听过 <code>filter</code> 有 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter#opacity%28%29_2" target="_blank" rel="external">opacity() 效果</a> ?对比广泛支持的 <code>opacity</code> 属性,它显得有点鸡肋,但是它的存在才使得兼容方案得以实现:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="selector-class">.backdrop</span><span class="selector-pseudo">::before</span> {</div><div class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</div><div class="line"> <span class="attribute">position</span>: absolute;</div><div class="line"> <span class="attribute">top</span>: <span class="number">0</span>; <span class="attribute">right</span>: <span class="number">0</span>; <span class="attribute">bottom</span>: <span class="number">0</span>; <span class="attribute">left</span>: <span class="number">0</span>;</div><div class="line"></div><div class="line"> <span class="comment">/* 把它放到其它伪元素的上面,但是仍在父元素内容的下面 */</span></div><div class="line"> <span class="attribute">z-index</span>: -<span class="number">1</span>;</div><div class="line"></div><div class="line"> <span class="comment">/* 让遮罩层足够的暗,无论什么背景都可以看清楚上面的文字 */</span></div><div class="line"> <span class="attribute">background</span>: <span class="built_in">rbgba</span>(0,0,0, 0.5);</div><div class="line"></div><div class="line"> <span class="comment">/* 使用滤镜完全隐藏它?? */</span></div><div class="line"> <span class="attribute">filter</span>: <span class="built_in">opacity</span>(0%);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果支持 filter 滤镜,带图片的伪元素是模糊的,微微变暗的,这一黑色的遮罩层是完全透明的。如果不支持滤镜呢,显示效果并不完美,但是不会阻碍任何人阅读上面的文字。</p>
<p>CodePen 的例子在此,希望你喜欢:</p>
<p data-height="265" data-theme-id="0" data-slug-hash="bpqpMP" data-default-tab="css,result" data-user="tigt" data-embed-version="2" class="codepen">See the Pen <a href="http://codepen.io/tigt/pen/bpqpMP/" target="_blank" rel="external">Filtered background with fallback for legibility</a> by Taylor Hunt (<a href="http://codepen.io/tigt" target="_blank" rel="external">@tigt</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>其它 filter 滤镜的使用或许也能受此启发,我把它放到了我的锦囊里。</p>
<h1 id="何不用_@supports_代替?">何不用 @supports 代替?</h1><p>浏览器对 CSS 特性检测(<code>@supports</code>)的<a href="http://caniuse.com/#feat=css-featurequeries" target="_blank" rel="external">支持程度跟 filter 保持一致</a> ,替换以后代码更加清晰明了:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">@<span class="keyword">supports</span> (filter: blur(<span class="number">4px</span>) brightness(<span class="number">75%</span>)) or (-webkit-filter: blur(<span class="number">4px</span>) brightness(<span class="number">75%</span>)) {</div><div class="line"> <span class="comment">/* 浏览器支持 filter 的话... */</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>你完全可以这么写,全看个人喜好。我查阅了 CanIUse 的使用数据,有一些环境仍不支持 <code>@supports</code> :</p>
<ul>
<li>Chrome 18–27</li>
<li>Safari 8.x</li>
<li>UC Browser 9.9(撰写本文时)</li>
</ul>
<p>直到<strong><em>2016年5月</em></strong>,以上浏览器的联合使用份额是 ≈ 美国 3.6%、世界范围 10.5%(感谢大洋彼岸 UC 的盛行)。这些数字会随着时间流逝逐渐减少,到那时,如果想写更加清晰的 CSS 就推荐使用 <code>@supports</code> 了。</p>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文:<a href="http://codepen.io/tigt/post/blurred-background-image-with-fallback">Filtered background with fallback for legibility</a><br>翻译:<a href="http://weibo.com/newwave">涂鸦码龙</a></p>
</blockquote>
<p>你知道现在有多火吗?用这种很大的,高质量的,支持 Retina 屏的<a href="https://www.google.com/search?q=blurred+background+web&tbm=isch">模糊的 JPEG 图片作为 header 背景</a> :</p>
<p data-height="265" data-theme-id="0" data-slug-hash="RaprZK" data-default-tab="css,result" data-user="tigt" data-embed-version="2" class="codepen">See the Pen <a href="http://codepen.io/tigt/pen/RaprZK/">Web site header, circa 2016</a> by Taylor Hunt (<a href="http://codepen.io/tigt">@tigt</a>) on <a href="http://codepen.io">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>潜在的问题是如果浏览器不支持滤镜 filter,<a href="https://www.nngroup.com/articles/text-over-images/">文字将不可读</a> 。这违背了可访问性的原则,再完美的视觉也无济于事。</p>]]>
</summary>
<category term="CSS3" scheme="http://jinlong.github.io/tags/CSS3/"/>
<category term="CSS3" scheme="http://jinlong.github.io/categories/CSS3/"/>
</entry>
<entry>
<title><![CDATA[4类 JavaScript 内存泄漏及如何避免]]></title>
<link href="http://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/"/>
<id>http://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/</id>
<published>2016-05-01T13:16:27.000Z</published>
<updated>2016-05-26T02:08:11.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:<a href="https://auth0.com/blog/2016/01/26/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/" target="_blank" rel="external">4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them</a><br>笔记:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
<p>译者注:本文并没有逐字逐句的翻译,而是把我认为重要的信息做了翻译。如果您的英文熟练,可以直接阅读原文。</p>
</blockquote>
<p>本文将探索常见的客户端 JavaScript 内存泄漏,以及如何使用 Chrome 开发工具发现问题。</p>
<h1 id="简介">简介</h1><p>内存泄漏是每个开发者最终都要面对的问题,它是许多问题的根源:反应迟缓,崩溃,高延迟,以及其他应用问题。</p>
<h2 id="什么是内存泄漏?">什么是内存泄漏?</h2><p>本质上,内存泄漏可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。一些编程语言提供了语言特性,可以帮助开发者做此类事情。另一些则寄希望于开发者对内存是否需要清晰明了。</p>
<h2 id="JavaScript_内存管理">JavaScript 内存管理</h2><p>JavaScript 是一种垃圾回收语言。垃圾回收语言通过周期性地检查先前分配的内存是否可达,帮助开发者管理内存。换言之,垃圾回收语言减轻了“内存仍可用”及“内存仍可达”的问题。两者的区别是微妙而重要的:仅有开发者了解哪些内存在将来仍会使用,而不可达内存通过算法确定和标记,适时被操作系统回收。</p>
<h1 id="JavaScript_内存泄漏">JavaScript 内存泄漏</h1><p>垃圾回收语言的内存泄漏主因是不需要的引用。理解它之前,还需了解垃圾回收语言如何辨别内存的可达与不可达。</p>
<h2 id="Mark-and-sweep">Mark-and-sweep</h2><p>大部分垃圾回收语言用的算法称之为 Mark-and-sweep 。算法由以下几步组成:</p>
<ol>
<li>垃圾回收器创建了一个“roots”列表。Roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);</li>
<li>所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。</li>
<li>所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。</li>
</ol>
<p>现代的垃圾回收器改良了算法,但是本质是相同的:可达内存被标记,其余的被当作垃圾回收。</p>
<p>不需要的引用是指开发者明知内存引用不再需要,却由于某些原因,它仍被留在激活的 root 树中。在 JavaScript 中,不需要的引用是保留在代码中的变量,它不再需要,却指向一块本该被释放的内存。有些人认为这是开发者的错误。</p>
<p>为了理解 JavaScript 中最常见的内存泄漏,我们需要了解哪种方式的引用容易被遗忘。</p>
<a id="more"></a>
<h1 id="三种类型的常见_JavaScript_内存泄漏">三种类型的常见 JavaScript 内存泄漏</h1><h2 id="1:意外的全局变量">1:意外的全局变量</h2><p>JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 <code>window</code> 。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">arg</span>) </span>{</div><div class="line"> bar = <span class="string">"this is a hidden global variable"</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>真相是:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">arg</span>) </span>{</div><div class="line"> <span class="built_in">window</span>.bar = <span class="string">"this is an explicit global variable"</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>函数 <code>foo</code> 内部忘记使用 <code>var</code> ,意外创建了一个全局变量。此例泄漏了一个简单的字符串,无伤大雅,但是有更糟的情况。</p>
<p>另一种意外的全局变量可能由 <code>this</code> 创建:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.variable = <span class="string">"potential accidental global"</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// Foo 调用自己,this 指向了全局对象(window)</span></div><div class="line"><span class="comment">// 而不是 undefined</span></div><div class="line">foo();</div></pre></td></tr></table></figure>
<blockquote>
<p>在 JavaScript 文件头部加上 <code>'use strict'</code>,可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。</p>
</blockquote>
<p><strong>全局变量注意事项</strong></p>
<p>尽管我们讨论了一些意外的全局变量,但是仍有一些明确的全局变量产生的垃圾。它们被定义为不可回收(除非定义为空或重新分配)。尤其当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。与全局变量相关的增加内存消耗的一个主因是缓存。缓存数据是为了重用,缓存必须有一个大小上限才有用。高内存消耗导致缓存突破上限,因为缓存内容无法被回收。</p>
<h2 id="2:被遗忘的计时器或回调函数">2:被遗忘的计时器或回调函数</h2><p>在 JavaScript 中使用 <code>setInterval</code> 非常平常。一段常见的代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> someResource = getData();</div><div class="line">setInterval(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">var</span> node = <span class="built_in">document</span>.getElementById(<span class="string">'Node'</span>);</div><div class="line"> <span class="keyword">if</span>(node) {</div><div class="line"> <span class="comment">// 处理 node 和 someResource</span></div><div class="line"> node.innerHTML = <span class="built_in">JSON</span>.stringify(someResource));</div><div class="line"> }</div><div class="line">}, <span class="number">1000</span>);</div></pre></td></tr></table></figure>
<p>此例说明了什么:与节点或数据关联的计时器不再需要,<code>node</code> 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,<code>someResource</code> 如果存储了大量的数据,也是无法被回收的。</p>
<p>对于观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的。</p>
<p>观察者代码示例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> element = <span class="built_in">document</span>.getElementById(<span class="string">'button'</span>);</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">onClick</span>(<span class="params">event</span>) </span>{</div><div class="line"> element.innerHTML = <span class="string">'text'</span>;</div><div class="line">}</div><div class="line"></div><div class="line">element.addEventListener(<span class="string">'click'</span>, onClick);</div></pre></td></tr></table></figure>
<p><strong>对象观察者和循环引用注意事项</strong></p>
<p>老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。如今,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经可以正确检测和处理循环引用了。换言之,回收节点内存时,不必非要调用 <code>removeEventListener</code> 了。</p>
<h2 id="3:脱离_DOM_的引用">3:脱离 DOM 的引用</h2><p>有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> elements = {</div><div class="line"> <span class="attr">button</span>: <span class="built_in">document</span>.getElementById(<span class="string">'button'</span>),</div><div class="line"> <span class="attr">image</span>: <span class="built_in">document</span>.getElementById(<span class="string">'image'</span>),</div><div class="line"> <span class="attr">text</span>: <span class="built_in">document</span>.getElementById(<span class="string">'text'</span>)</div><div class="line">};</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">doStuff</span>(<span class="params"></span>) </span>{</div><div class="line"> image.src = <span class="string">'http://some.url/image'</span>;</div><div class="line"> button.click();</div><div class="line"> <span class="built_in">console</span>.log(text.innerHTML);</div><div class="line"> <span class="comment">// 更多逻辑</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeButton</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="comment">// 按钮是 body 的后代元素</span></div><div class="line"> <span class="built_in">document</span>.body.removeChild(<span class="built_in">document</span>.getElementById(<span class="string">'button'</span>));</div><div class="line"></div><div class="line"> <span class="comment">// 此时,仍旧存在一个全局的 #button 的引用</span></div><div class="line"> <span class="comment">// elements 字典。button 元素仍旧在内存中,不能被 GC 回收。</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个 <code><td></code> 的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <code><td></code> 以外的其它节点。实际情况并非如此:此 <code><td></code> 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 <code><td></code> 的引用,导致整个表格仍待在内存中。保存 DOM 元素引用的时候,要小心谨慎。</p>
<h2 id="4:闭包">4:闭包</h2><p>闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量。</p>
<p>代码示例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> theThing = <span class="literal">null</span>;</div><div class="line"><span class="keyword">var</span> replaceThing = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">var</span> originalThing = theThing;</div><div class="line"> <span class="keyword">var</span> unused = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">if</span> (originalThing)</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">"hi"</span>);</div><div class="line"> };</div><div class="line"></div><div class="line"> theThing = {</div><div class="line"> <span class="attr">longStr</span>: <span class="keyword">new</span> <span class="built_in">Array</span>(<span class="number">1000000</span>).join(<span class="string">'*'</span>),</div><div class="line"> <span class="attr">someMethod</span>: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(someMessage);</div><div class="line"> }</div><div class="line"> };</div><div class="line">};</div><div class="line"></div><div class="line">setInterval(replaceThing, <span class="number">1000</span>);</div></pre></td></tr></table></figure>
<p>代码片段做了一件事情:每次调用 <code>replaceThing</code> <code>,theThing</code> 得到一个包含一个大数组和一个新闭包(<code>someMethod</code>)的新对象。同时,变量 <code>unused</code> 是一个引用 <code>originalThing</code> 的闭包(先前的 <code>replaceThing</code> 又调用了 <code>theThing</code> )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。<code>someMethod</code> 可以通过 <code>theThing</code> 使用,<code>someMethod</code> 与 <code>unused</code> 分享闭包作用域,尽管 <code>unused</code> 从未使用,它引用的 <code>originalThing</code> 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄漏。</p>
<blockquote>
<p><a href="http://info.meteor.com/blog/an-interesting-kind-of-javascript-memory-leak" target="_blank" rel="external">Meteor 的博文</a> 解释了如何修复此种问题。在 <code>replaceThing</code> 的最后添加 <code>originalThing = null</code> 。</p>
</blockquote>
<h1 id="Chrome_内存剖析工具概览">Chrome 内存剖析工具概览</h1><p>Chrome 提供了一套很棒的检测 JavaScript 内存占用的工具。与内存相关的两个重要的工具:<code>timeline</code> 和 <code>profiles</code>。</p>
<h2 id="Timeline">Timeline</h2><p><img src="/image/4-Types-of-Memory-Leaks/p1.png" alt="附图1"></p>
<p>timeline 可以检测代码中不需要的内存。在此截图中,我们可以看到潜在的泄漏对象稳定的增长,数据采集快结束时,内存占用明显高于采集初期,Node(节点)的总量也很高。种种迹象表明,代码中存在 DOM 节点泄漏的情况。</p>
<h2 id="Profiles">Profiles</h2><p><img src="/image/4-Types-of-Memory-Leaks/p2.png" alt="附图2"></p>
<p>Profiles 是你可以花费大量时间关注的工具,它可以保存快照,对比 JavaScript 代码内存使用的不同快照,也可以记录时间分配。每一次结果包含不同类型的列表,与内存泄漏相关的有 summary(概要) 列表和 comparison(对照) 列表。</p>
<p>summary(概要) 列表展示了不同类型对象的分配及合计大小:shallow size(特定类型的所有对象的总大小),retained size(shallow size 加上其它与此关联的对象大小)。它还提供了一个概念,一个对象与关联的 GC root 的距离。</p>
<p>对比不同的快照的 comparison list 可以发现内存泄漏。</p>
<h1 id="实例:使用_Chrome_发现内存泄漏">实例:使用 Chrome 发现内存泄漏</h1><p>实质上有两种类型的泄漏:周期性的内存增长导致的泄漏,以及偶现的内存泄漏。显而易见,周期性的内存泄漏很容易发现;偶现的泄漏比较棘手,一般容易被忽视,偶尔发生一次可能被认为是优化问题,周期性发生的则被认为是必须解决的 bug。</p>
<p>以 <a href="https://developer.chrome.com/devtools/docs/demos/memory/example1" target="_blank" rel="external">Chrome 文档</a>中的代码为例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> x = [];</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">createSomeNodes</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">var</span> div,</div><div class="line"> i = <span class="number">100</span>,</div><div class="line"> frag = <span class="built_in">document</span>.createDocumentFragment();</div><div class="line"></div><div class="line"> <span class="keyword">for</span> (;i > <span class="number">0</span>; i--) {</div><div class="line"> div = <span class="built_in">document</span>.createElement(<span class="string">"div"</span>);</div><div class="line"> div.appendChild(<span class="built_in">document</span>.createTextNode(i + <span class="string">" - "</span>+ <span class="keyword">new</span> <span class="built_in">Date</span>().toTimeString()));</div><div class="line"> frag.appendChild(div);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="built_in">document</span>.getElementById(<span class="string">"nodes"</span>).appendChild(frag);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">grow</span>(<span class="params"></span>) </span>{</div><div class="line"> x.push(<span class="keyword">new</span> <span class="built_in">Array</span>(<span class="number">1000000</span>).join(<span class="string">'x'</span>));</div><div class="line"> createSomeNodes();</div><div class="line"> setTimeout(grow,<span class="number">1000</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>当 <code>grow</code> 执行的时候,开始创建 div 节点并插入到 DOM 中,并且给全局变量分配一个巨大的数组。通过以上提到的工具可以检测到内存稳定上升。</p>
<h2 id="找出周期性增长的内存">找出周期性增长的内存</h2><p>timeline 标签擅长做这些。在 Chrome 中<a href="https://developer.chrome.com/devtools/docs/demos/memory/example1" target="_blank" rel="external">打开例子</a>,打开 Dev Tools ,切换到 timeline,勾选 memory 并点击记录按钮,然后点击页面上的 <code>The Button</code> 按钮。过一阵停止记录看结果:</p>
<p><img src="/image/4-Types-of-Memory-Leaks/p3.png" alt="附图3"></p>
<p>两种迹象显示出现了内存泄漏,图中的 Nodes(绿线)和 JS heap(蓝线)。Nodes 稳定增长,并未下降,这是个显著的信号。</p>
<p>JS heap 的内存占用也是稳定增长。由于垃圾收集器的影响,并不那么容易发现。图中显示内存占用忽涨忽跌,实际上每一次下跌之后,JS heap 的大小都比原先大了。换言之,尽管垃圾收集器不断的收集内存,内存还是周期性的泄漏了。</p>
<p>确定存在内存泄漏之后,我们找找根源所在。</p>
<h2 id="保存两个快照">保存两个快照</h2><p>切换到 Chrome Dev Tools 的 profiles 标签,刷新页面,等页面刷新完成之后,点击 Take Heap Snapshot 保存快照作为基准。而后再次点击 <code>The Button</code> 按钮,等数秒以后,保存第二个快照。</p>
<p><img src="/image/4-Types-of-Memory-Leaks/p4.png" alt="附图4"></p>
<p>筛选菜单选择 Summary,右侧选择 Objects allocated between Snapshot 1 and Snapshot 2,或者筛选菜单选择 Comparison ,然后可以看到一个对比列表。</p>
<p>此例很容易找到内存泄漏,看下 <code>(string)</code> 的 <code>Size Delta</code> Constructor,8MB,58个新对象。新对象被分配,但是没有释放,占用了8MB。</p>
<p>如果展开 <code>(string)</code> Constructor,会看到许多单独的内存分配。选择某一个单独的分配,下面的 retainers 会吸引我们的注意。</p>
<p><img src="/image/4-Types-of-Memory-Leaks/p5.png" alt="附图5"></p>
<p>我们已选择的分配是数组的一部分,数组关联到 <code>window</code> 对象的 <code>x</code> 变量。这里展示了从巨大对象到无法回收的 root(<code>window</code>)的完整路径。我们已经找到了潜在的泄漏以及它的出处。</p>
<p>我们的例子还算简单,只泄漏了少量的 DOM 节点,利用以上提到的快照很容易发现。对于更大型的网站,Chrome 还提供了 Record Heap Allocations 功能。</p>
<h2 id="Record_heap_allocations_找内存泄漏">Record heap allocations 找内存泄漏</h2><p>回到 Chrome Dev Tools 的 profiles 标签,点击 Record Heap Allocations。工具运行的时候,注意顶部的蓝条,代表了内存分配,每一秒有大量的内存分配。运行几秒以后停止。</p>
<p><img src="/image/4-Types-of-Memory-Leaks/p6.png" alt="附图6"></p>
<p>上图中可以看到工具的杀手锏:选择某一条时间线,可以看到这个时间段的内存分配情况。尽可能选择接近峰值的时间线,下面的列表仅显示了三种 constructor:其一是泄漏最严重的<code>(string)</code>,下一个是关联的 DOM 分配,最后一个是 <code>Text</code> constructor(DOM 叶子节点包含的文本)。</p>
<p>从列表中选择一个 <code>HTMLDivElement</code> constructor,然后选择 <code>Allocation stack</code>。</p>
<p><img src="/image/4-Types-of-Memory-Leaks/p7.png" alt="附图7"></p>
<p>现在知道元素被分配到哪里了吧(<code>grow</code> -> <code>createSomeNodes</code>),仔细观察一下图中的时间线,发现 <code>HTMLDivElement</code> constructor 调用了许多次,意味着内存一直被占用,无法被 GC 回收,我们知道了这些对象被分配的确切位置(<code>createSomeNodes</code>)。回到代码本身,探讨下如何修复内存泄漏吧。</p>
<h2 id="另一个有用的特性">另一个有用的特性</h2><p>在 heap allocations 的结果区域,选择 Allocation。</p>
<p><img src="/image/4-Types-of-Memory-Leaks/p8.png" alt="附图8"></p>
<p>这个视图呈现了内存分配相关的功能列表,我们立刻看到了 <code>grow</code> 和 <code>createSomeNodes</code>。当选择 <code>grow</code> 时,看看相关的 object constructor,清楚地看到 <code>(string)</code>, <code>HTMLDivElement</code> 和 <code>Text</code> 泄漏了。</p>
<p>结合以上提到的工具,可以轻松找到内存泄漏。</p>
<h1 id="延伸阅读">延伸阅读</h1><ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management" target="_blank" rel="external">Memory Management - Mozilla Developer Network</a></li>
<li><a href="http://javascript.crockford.com/memory/leak.html" target="_blank" rel="external">JScript Memory Leaks - Douglas Crockford (old, in relation to Internet Explorer 6 leaks)</a></li>
<li><a href="https://developer.chrome.com/devtools/docs/javascript-memory-profiling" target="_blank" rel="external">JavaScript Memory Profiling - Chrome Developer Docs</a></li>
<li><a href="https://developers.google.com/web/tools/chrome-devtools/profile/memory-problems/memory-diagnosis" target="_blank" rel="external">Memory Diagnosis - Google Developers</a></li>
<li><a href="http://info.meteor.com/blog/an-interesting-kind-of-javascript-memory-leak" target="_blank" rel="external">An Interesting Kind of JavaScript Memory Leak - Meteor blog</a></li>
<li><a href="http://mrale.ph/blog/2012/09/23/grokking-v8-closures-for-fun.html" target="_blank" rel="external">Grokking V8 closures</a></li>
</ul>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文:<a href="https://auth0.com/blog/2016/01/26/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/">4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them</a><br>笔记:<a href="http://weibo.com/newwave">涂鸦码龙</a></p>
<p>译者注:本文并没有逐字逐句的翻译,而是把我认为重要的信息做了翻译。如果您的英文熟练,可以直接阅读原文。</p>
</blockquote>
<p>本文将探索常见的客户端 JavaScript 内存泄漏,以及如何使用 Chrome 开发工具发现问题。</p>
<h1 id="简介">简介</h1><p>内存泄漏是每个开发者最终都要面对的问题,它是许多问题的根源:反应迟缓,崩溃,高延迟,以及其他应用问题。</p>
<h2 id="什么是内存泄漏?">什么是内存泄漏?</h2><p>本质上,内存泄漏可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。一些编程语言提供了语言特性,可以帮助开发者做此类事情。另一些则寄希望于开发者对内存是否需要清晰明了。</p>
<h2 id="JavaScript_内存管理">JavaScript 内存管理</h2><p>JavaScript 是一种垃圾回收语言。垃圾回收语言通过周期性地检查先前分配的内存是否可达,帮助开发者管理内存。换言之,垃圾回收语言减轻了“内存仍可用”及“内存仍可达”的问题。两者的区别是微妙而重要的:仅有开发者了解哪些内存在将来仍会使用,而不可达内存通过算法确定和标记,适时被操作系统回收。</p>
<h1 id="JavaScript_内存泄漏">JavaScript 内存泄漏</h1><p>垃圾回收语言的内存泄漏主因是不需要的引用。理解它之前,还需了解垃圾回收语言如何辨别内存的可达与不可达。</p>
<h2 id="Mark-and-sweep">Mark-and-sweep</h2><p>大部分垃圾回收语言用的算法称之为 Mark-and-sweep 。算法由以下几步组成:</p>
<ol>
<li>垃圾回收器创建了一个“roots”列表。Roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);</li>
<li>所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。</li>
<li>所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。</li>
</ol>
<p>现代的垃圾回收器改良了算法,但是本质是相同的:可达内存被标记,其余的被当作垃圾回收。</p>
<p>不需要的引用是指开发者明知内存引用不再需要,却由于某些原因,它仍被留在激活的 root 树中。在 JavaScript 中,不需要的引用是保留在代码中的变量,它不再需要,却指向一块本该被释放的内存。有些人认为这是开发者的错误。</p>
<p>为了理解 JavaScript 中最常见的内存泄漏,我们需要了解哪种方式的引用容易被遗忘。</p>]]>
</summary>
<category term="内存泄漏" scheme="http://jinlong.github.io/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"/>
<category term="JavaScript" scheme="http://jinlong.github.io/categories/JavaScript/"/>
</entry>
<entry>
<title><![CDATA[实例解析防抖动(Debouncing)和节流阀(Throttling)]]></title>
<link href="http://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/"/>
<id>http://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/</id>
<published>2016-04-24T14:30:00.000Z</published>
<updated>2016-04-24T15:03:12.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:<a href="https://css-tricks.com/debouncing-throttling-explained-examples/" target="_blank" rel="external">Debouncing and Throttling Explained Through Examples</a><br>笔记:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
</blockquote>
<p>防抖(<strong>Debounce</strong>)和节流(<strong>throttle</strong>)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。</p>
<p>当我们给 DOM 绑定事件的时候,加了防抖和节流的函数变得特别有用。为什么呢?因为我们在事件和函数执行之间加了一个控制层。记住,我们是无法控制 DOM 事件触发频率的。</p>
<p>看下滚动事件的例子:</p>
<p data-height="268" data-theme-id="0" data-slug-hash="PZOZgB" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/PZOZgB/" target="_blank" rel="external">Scroll events counter</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>当使用触控板,滚动滚轮,或者拖拽滚动条的时候,一秒可以轻松触发30次事件。经我的测试,在智能手机上,慢慢滚动一下,一秒可以触发事件100次之多。这么高的执行频率,你的滚动回调函数压力大吗?</p>
<p>早在2011年,Twitter 网站抛出了一个问题:向下滚动 Twitter 信息流的时候,变得很慢,很迟钝。John Resig 发表了<a href="http://ejohn.org/blog/learning-from-twitter" target="_blank" rel="external">一篇博客解释这个问题</a>,文中解释到直接给 <code>scroll</code> 事件关联昂贵的函数,是多么糟糕的主意。</p>
<p>John(5年前)建议的解决方案是,在 <code>onScroll</code> 事件外部,每 250ms 循环执行一次。简单的技巧,避免了影响用户体验。</p>
<p>现如今,有一些稍微高端的方式处理事件。我来结合用例介绍下 Debounce,Throttle 和 requestAnimationFrame 吧。</p>
<a id="more"></a>
<h1 id="防抖动(Debounce)">防抖动(Debounce)</h1><p>防抖技术可以把多个顺序地调用合并成一次。</p>
<p><img src="/image/debounce-throttle/p1.png" alt="附图1"></p>
<p>假想一下,你在电梯中,门快要关了,突然有人准备上来。电梯并没有改变楼层,而是再次打开梯门。电梯延迟了改变楼层的功能,但是优化了资源。</p>
<p>在顶部按钮上点击或移动鼠标试一下:</p>
<p data-height="268" data-theme-id="0" data-slug-hash="KVxGqN" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/KVxGqN/" target="_blank" rel="external">Debounce. Trailing</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>你可以看到连续快速的事件是如何被一个 debounce 事件替代的。但是如果事件触发的时间间隔过长,debounce 则不会生效。</p>
<h2 id="前缘(或者“immediate”)">前缘(或者“immediate”)</h2><p>你会发现,直到事件停止快速执行以后,debounce 事件才会触发相应功能。为何不立即触发呢?那样的话就跟原本的非 debounce 处理无异了。</p>
<p>直到两次快速调用之间的停顿结束,事件才会再次触发。</p>
<p>这是带 <code>leading</code> 标记的例子:</p>
<p><img src="/image/debounce-throttle/p2.png" alt="附图1"><br><em>前缘 debounce 的例子</em></p>
<p>在 underscore.js 中,选项叫 <code>immediate</code> ,而不是 <code>leading</code>:</p>
<p data-height="268" data-theme-id="0" data-slug-hash="GZWqNV" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/GZWqNV/" target="_blank" rel="external">Debounce. Leading</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<h2 id="Debounce_实现">Debounce 实现</h2><p>我首次看到 debounce 的 JavaScript 实现是在 2009 年的 <a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/" target="_blank" rel="external">John Hann 的博文</a>。</p>
<p>不久后,Ben Alman 做了个 <a href="http://benalman.com/projects/jquery-throttle-debounce-plugin/" target="_blank" rel="external">jQuery 插件</a>(不再维护),一年后 Jeremy Ashkenas 把它<a href="https:/jashkenas/underscore/commit/9e3e067f5025dbe5e93ed784f93b233882ca0ffe" target="_blank" rel="external">加入了 underscore.js</a>。而后加入了 Lodash 。</p>
<p data-height="268" data-theme-id="0" data-slug-hash="YwNwgy" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/YwNwgy/" target="_blank" rel="external">New example</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>Lodash 给 <code>_.debounce</code> 和 <code>_.throttle</code> 添加了<a href="https://lodash.com/docs#debounce" target="_blank" rel="external">不少特性</a>。之前的 <code>immediate</code> 被 <code>leading</code>(最前面) 和 <code>trailing</code>(最后面) 选项取代。你可以选一种,或者都选,默认只有 <code>trailing</code> 启用。</p>
<p>新的 <code>maxWait</code> 选项(仅 Lodash 有)本文未提及,但是也很有用。事实上,throttle 方法是用 <code>_.debounce</code> 加 <code>maxWait</code> 实现的,你可以看 <a href="https:/lodash/lodash/blob/4.7.0/lodash.js#L9840" target="_blank" rel="external">lodash 源码</a>。</p>
<h2 id="Debounce_实例">Debounce 实例</h2><p><strong>调整大小的例子</strong></p>
<p>调整桌面浏览器窗口大小的时候,会触发很多次 <code>resize</code> 事件。</p>
<p>看下面 demo:</p>
<p data-height="268" data-theme-id="0" data-slug-hash="XXPjpd" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/XXPjpd/" target="_blank" rel="external">Debounce Resize Event Example</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>如你所见,我们为 resize 事件使用了默认的 <code>trailing</code> 选项,因为我们只关心用户停止调整大小后的最终值。</p>
<p><strong>基于 AJAX 请求的自动完成功能,通过 keypress 触发</strong></p>
<p>为什么用户还在输入的时候,每隔50ms就向服务器发送一次 AJAX 请求?<code>_.debounce</code> 可以帮忙,当用户停止输入的时候,再发送请求。</p>
<p>此处也不需要 <code>leading</code> 标记,我们想等最后一个字符输完。</p>
<p data-height="268" data-theme-id="0" data-slug-hash="mVGVOL" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/mVGVOL/" target="_blank" rel="external">Debouncing keystrokes Example</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>相似的使用场景还有,直到用户输完,才验证输入的正确性,显示错误信息。</p>
<h1 id="如何使用_debounce_和_throttle_以及常见的坑">如何使用 debounce 和 throttle 以及常见的坑</h1><p>自己造一个 debounce / throttle 的轮子看起来多么诱人,或者随便找个博文复制过来。<strong>我是建议直接使用 underscore 或 Lodash</strong> 。如果仅需要 <code>_.debounce</code> 和 <code>_.throttle</code> 方法,可以使用 Lodash 的自定义构建工具,生成一个 2KB 的压缩库。使用以下的简单命令即可:</p>
<figure class="highlight stata"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">npm i -<span class="keyword">g</span> lodash-<span class="keyword">cli</span></div><div class="line">lodash-<span class="keyword">cli</span> <span class="keyword">include</span>=debounce,throttle</div></pre></td></tr></table></figure>
<p>常见的坑是,不止一次地调用 <code>_.debounce</code> 方法:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 错误</span></div><div class="line">$(<span class="built_in">window</span>).on(<span class="string">'scroll'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> _.debounce(doSomething, <span class="number">300</span>); </div><div class="line">});</div><div class="line"></div><div class="line"><span class="comment">// 正确</span></div><div class="line">$(<span class="built_in">window</span>).on(<span class="string">'scroll'</span>, _.debounce(doSomething, <span class="number">200</span>));</div></pre></td></tr></table></figure>
<p>debounce 方法保存到一个变量以后,就可以用它的私有方法 <code>debounced_version.cancel()</code>,lodash 和 underscore.js 都有效。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> debounced_version = _.debounce(doSomething, <span class="number">200</span>);</div><div class="line">$(<span class="built_in">window</span>).on(<span class="string">'scroll'</span>, debounced_version);</div><div class="line"></div><div class="line"><span class="comment">// 如果需要的话</span></div><div class="line">debounced_version.cancel();</div></pre></td></tr></table></figure>
<h1 id="Throttle(节流阀)">Throttle(节流阀)</h1><p>使用 _.throttle 的时候,只允许一个函数在 X 毫秒内执行一次。</p>
<p>跟 debounce 主要的不同在于,throttle 保证 X 毫秒内至少执行一次。</p>
<h2 id="节流阀实例">节流阀实例</h2><p><strong>无限滚动</strong></p>
<p>用户向下滚动无限滚动页面,需要检查滚动位置距底部多远,如果邻近底部了,我们可以发 AJAX 请求获取更多的数据插入到页面中。</p>
<p>我们心爱的 <code>_.debounce</code> 就不适用了,只有当用户停止滚动的时候它才会触发。只要用户滚动至邻近底部时,我们就想获取内容。</p>
<p>使用 <code>_.throttle</code> 可以保证我们不断检查距离底部有多远。</p>
<p data-height="268" data-theme-id="0" data-slug-hash="eJLMxa" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/eJLMxa/" target="_blank" rel="external">Infinite scrolling throttled</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<h1 id="requestAnimationFrame(rAF)">requestAnimationFrame(rAF)</h1><p><code>requestAnimationFrame</code> 是另一种限速执行的方式。</p>
<p>跟 <code>_.throttle(dosomething, 16)</code> 等价。它是高保真的,如果追求更好的精确度的话,可以用浏览器原生的 API 。</p>
<p>可以使用 rAF API 替换 throttle 方法,考虑一下优缺点:</p>
<h2 id="优点">优点</h2><ul>
<li>动画保持 60fps(每一帧 16 ms),浏览器内部决定渲染的最佳时机</li>
<li>简洁标准的 API,后期维护成本低</li>
</ul>
<h2 id="缺点">缺点</h2><ul>
<li>动画的开始/取消需要开发者自己控制,不像 ‘.debounce’ 或 ‘.throttle’由函数内部处理。</li>
<li>浏览器标签未激活时,一切都不会执行。</li>
<li>尽管所有的现代浏览器<a href="http://caniuse.com/#feat=requestanimationframe" target="_blank" rel="external">都支持 rAF</a> ,IE9,Opera Mini 和 老的 Android 还是<a href="http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/" target="_blank" rel="external">需要打补丁</a>。</li>
<li>Node.js 不支持,无法在服务器端用于文件系统事件。</li>
</ul>
<p>根据经验,如果 JavaScript 方法需要绘制或者直接改变属性,我会选择 <code>requestAnimationFrame</code>,只要涉及到重新计算元素位置,就可以使用它。</p>
<p>涉及到 AJAX 请求,添加/移除 class (可以触发 CSS 动画),我会选择 <code>_.debounce</code> 或者 <code>_.throttle</code> ,可以设置更低的执行频率(例子中的200ms 换成16ms)。</p>
<h2 id="rAF_实例">rAF 实例</h2><p>灵感来自于 <a href="http://www.html5rocks.com/en/tutorials/speed/animations/" target="_blank" rel="external">Paul Lewis 的文章</a>,我将用 requestAnimationFrame 控制 scroll 。</p>
<p>16ms 的 <code>_.throttle</code> 拿来做对比,性能相仿,用于更复杂的场景时,rAF 可能效果更佳。</p>
<p data-height="268" data-theme-id="0" data-slug-hash="pgOKKw" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/pgOKKw/" target="_blank" rel="external">Scroll comparison requestAnimationFrame vs throttle</a> by Corbacho (<a href="http://codepen.io/dcorb" target="_blank" rel="external">@dcorb</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>headroom.js 是个更<a href="https:/WickyNilliams/headroom.js/blob/3282c23bc69b14f21bfbaf66704fa37b58e3241d/src/Debouncer.js" target="_blank" rel="external">高级的例子</a>。</p>
<h1 id="结论">结论</h1><p>使用 debounce,throttle 和 <code>requestAnimationFrame</code> 都可以优化事件处理,三者各不相同,又相辅相成。</p>
<p>总之:</p>
<ul>
<li><p><strong>debounce</strong>:把触发非常频繁的事件(比如按键)合并成一次执行。</p>
</li>
<li><p><strong>throttle</strong>:保证每 X 毫秒恒定的执行次数,比如每200ms检查下滚动位置,并触发 CSS 动画。</p>
</li>
<li><p><strong>requestAnimationFrame</strong>:可替代 throttle ,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。注意:IE9 不支持。</p>
</li>
</ul>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文:<a href="https://css-tricks.com/debouncing-throttling-explained-examples/">Debouncing and Throttling Explained Through Examples</a><br>笔记:<a href="http://weibo.com/newwave">涂鸦码龙</a></p>
</blockquote>
<p>防抖(<strong>Debounce</strong>)和节流(<strong>throttle</strong>)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。</p>
<p>当我们给 DOM 绑定事件的时候,加了防抖和节流的函数变得特别有用。为什么呢?因为我们在事件和函数执行之间加了一个控制层。记住,我们是无法控制 DOM 事件触发频率的。</p>
<p>看下滚动事件的例子:</p>
<p data-height="268" data-theme-id="0" data-slug-hash="PZOZgB" data-default-tab="result" data-user="dcorb" class="codepen">See the Pen <a href="http://codepen.io/dcorb/pen/PZOZgB/">Scroll events counter</a> by Corbacho (<a href="http://codepen.io/dcorb">@dcorb</a>) on <a href="http://codepen.io">CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>当使用触控板,滚动滚轮,或者拖拽滚动条的时候,一秒可以轻松触发30次事件。经我的测试,在智能手机上,慢慢滚动一下,一秒可以触发事件100次之多。这么高的执行频率,你的滚动回调函数压力大吗?</p>
<p>早在2011年,Twitter 网站抛出了一个问题:向下滚动 Twitter 信息流的时候,变得很慢,很迟钝。John Resig 发表了<a href="http://ejohn.org/blog/learning-from-twitter">一篇博客解释这个问题</a>,文中解释到直接给 <code>scroll</code> 事件关联昂贵的函数,是多么糟糕的主意。</p>
<p>John(5年前)建议的解决方案是,在 <code>onScroll</code> 事件外部,每 250ms 循环执行一次。简单的技巧,避免了影响用户体验。</p>
<p>现如今,有一些稍微高端的方式处理事件。我来结合用例介绍下 Debounce,Throttle 和 requestAnimationFrame 吧。</p>]]>
</summary>
<category term="Debounce" scheme="http://jinlong.github.io/tags/Debounce/"/>
<category term="Throttle" scheme="http://jinlong.github.io/tags/Throttle/"/>
<category term="JavaScript" scheme="http://jinlong.github.io/categories/JavaScript/"/>
</entry>
<entry>
<title><![CDATA[你好 ES2015]]></title>
<link href="http://jinlong.github.io/2016/04/09/Say-Hello-To-ES2015/"/>
<id>http://jinlong.github.io/2016/04/09/Say-Hello-To-ES2015/</id>
<published>2016-04-09T03:22:43.000Z</published>
<updated>2016-04-09T04:59:20.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文: <a href="https://tutor.mantrajs.com/say-hello-to-ES2015/introduction" target="_blank" rel="external">Say Hello To ES2015</a><br>笔记:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
</blockquote>
<h1 id="介绍_ES2015">介绍 ES2015</h1><p>ES2015 是新版的 JavaScript,Node.js 已经完全支持,浏览器端可以用 <a href="https://babeljs.io/" target="_blank" rel="external">Babel</a> 库编译。</p>
<p>运行本文的示例代码,可以用 <a href="https://jsbin.com/defeba/edit?js,console" target="_blank" rel="external">JSBin 环境</a>,也可以结合原文中的测试题检测学习效果。</p>
<h1 id="使用_let_和_const">使用 let 和 const</h1><p>ES2015 可以用 <code>const</code> 或 <code>let</code> 替换 <code>var</code> ,它们定义了块级作用域变量。</p>
<p>示例代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> lc=<span class="number">0</span>; lc < <span class="number">10</span>; lc++) {</div><div class="line"> <span class="keyword">let</span> value = lc;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(value); <span class="comment">//抛出错误</span></div></pre></td></tr></table></figure>
<p>变量 <code>value</code> 只能在 <code>for</code> 循环中使用。</p>
<p><code>const</code> 跟 <code>let</code> 很像,但是它定义的变量值无法改变。</p>
<p>示例代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> user = {<span class="attr">name</span>: <span class="string">"Arunoda"</span>};</div><div class="line">user.name = <span class="string">"Susiripala"</span>;</div><div class="line"><span class="built_in">console</span>.log(user.name);</div></pre></td></tr></table></figure>
<p>改变的是变量 <code>user</code> 内部的属性,并没有改变 <code>user</code> 本身。</p>
<p>许多人更喜欢用 <code>const</code> 代替 <code>let</code> 。</p>
<h1 id="使用箭头函数整理你的代码">使用箭头函数整理你的代码</h1><p>熟悉的方式:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyBy10 = numbers.map(<span class="function"><span class="keyword">function</span>(<span class="params">a</span>) </span>{</div><div class="line"> <span class="keyword">return</span> a * <span class="number">10</span>;</div><div class="line">});</div><div class="line"><span class="built_in">console</span>.log(multiplyBy10);</div></pre></td></tr></table></figure>
<p>使用箭头函数以后:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyBy10 = numbers.map(<span class="function"><span class="params">a</span> =></span> a * <span class="number">10</span>);</div><div class="line"><span class="built_in">console</span>.log(multiplyBy10);</div></pre></td></tr></table></figure>
<p>如果方法接受不止1个参数,可以这么写:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyByIndex = numbers.map(<span class="function">(<span class="params">a, i</span>) =></span> a * i);</div><div class="line"><span class="built_in">console</span>.log(multiplyByIndex);</div></pre></td></tr></table></figure>
<p>箭头函数返回一个对象的话,需要加圆括号:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyBy10 = numbers.map(<span class="function"><span class="params">a</span> =></span> ({<span class="attr">res</span>: a * <span class="number">10</span>}));</div><div class="line"><span class="built_in">console</span>.log(multiplyBy10);</div></pre></td></tr></table></figure>
<a id="more"></a>
<h1 id="不再使用“self=this”">不再使用“self=this”</h1><p>以前的代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Clock</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.currentTime = <span class="keyword">new</span> <span class="built_in">Date</span>();</div><div class="line">}</div><div class="line"></div><div class="line">Clock.prototype.start = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>;</div><div class="line"> setInterval(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> self.currentTime = <span class="keyword">new</span> <span class="built_in">Date</span>();</div><div class="line"> }, <span class="number">1000</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以用箭头函数代替 <code>self=this</code>:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Clock</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.currentTime = <span class="keyword">new</span> <span class="built_in">Date</span>();</div><div class="line">}</div><div class="line"></div><div class="line">Clock.prototype.start = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> setInterval(<span class="function"><span class="params">()</span> =></span> {</div><div class="line"> <span class="keyword">this</span>.currentTime = <span class="keyword">new</span> <span class="built_in">Date</span>();</div><div class="line"> }, <span class="number">1000</span>);</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>setInterval</code> 里面用了箭头函数,它携带了 <code>start</code> 方法的上下文<code>(this)</code>。</p>
<p><strong>使用箭头函数要多加小心,并不是随处可用的,箭头函数会携带函数定义时的上下文。</strong></p>
<p>正如这个例子:<a href="https://jsbin.com/zuseqap/edit?js,console" target="_blank" rel="external">https://jsbin.com/zuseqap/edit?js,console</a> </p>
<h1 id="改良的对象字面量">改良的对象字面量</h1><p>在对象里面定义一个方法,可以这么写:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> user = {</div><div class="line"> getName() {</div><div class="line"> <span class="keyword">return</span> <span class="string">'Arunoda'</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(user.getName());</div></pre></td></tr></table></figure>
<p>不必每次都写 <code>function</code> 关键字。</p>
<p>这是最酷的特性,你会喜欢的:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> name = <span class="string">'Arunoda'</span>;</div><div class="line"><span class="keyword">const</span> age = <span class="number">80</span>;</div><div class="line"></div><div class="line"><span class="keyword">const</span> user = {name, age};</div></pre></td></tr></table></figure>
<p>瞅瞅多简单,并不用这么啰嗦:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> name = <span class="string">'Arunoda'</span>;</div><div class="line"><span class="keyword">const</span> age = <span class="number">80</span>;</div><div class="line"></div><div class="line"><span class="keyword">const</span> user = {</div><div class="line"> <span class="attr">name</span>: name,</div><div class="line"> <span class="attr">age</span>: age</div><div class="line">};</div></pre></td></tr></table></figure>
<h1 id="解构对象">解构对象</h1><p>很容易地提取 <code>user</code> 对象的 <code>name</code> 和 <code>age</code> 字段:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> user = {</div><div class="line"> <span class="attr">name</span>: <span class="string">'Arunoda'</span>,</div><div class="line"> <span class="attr">age</span>: <span class="number">80</span>,</div><div class="line"> <span class="attr">city</span>: <span class="string">'Colombo'</span></div><div class="line">};</div><div class="line"></div><div class="line"><span class="keyword">const</span> {name, age} = user;</div><div class="line"><span class="built_in">console</span>.log(name, age);</div></pre></td></tr></table></figure>
<p>对于函数相当有用,上代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">printName</span>(<span class="params">{name}</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Name is: '</span> + name);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> user = {</div><div class="line"> <span class="attr">name</span>: <span class="string">'Arunoda'</span>,</div><div class="line"> <span class="attr">age</span>: <span class="number">80</span>,</div><div class="line"> <span class="attr">city</span>: <span class="string">'Colombo'</span></div><div class="line">};</div><div class="line">printName(user);</div></pre></td></tr></table></figure>
<p>不仅简化了代码,而且可以自描述。看到函数第一行时,我们便会明白使用传入对象的哪个字段。</p>
<p>可以定义传入对象的默认值。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">printUser</span>(<span class="params">{name, age = <span class="number">20</span>}</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Name is: '</span> + name + <span class="string">' Age: '</span> + age);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>像传入对象一样,同样可以从传入的数组中解构值:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">printUser</span>(<span class="params">[name, age = <span class="number">20</span>]</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Name is: '</span> + name + <span class="string">' Age: '</span> + age);</div><div class="line">}</div><div class="line"></div><div class="line">printUser([<span class="string">"Arunoda"</span>, <span class="number">80</span>]);</div></pre></td></tr></table></figure>
<h1 id="前所未见的方式传递(spread)数组">前所未见的方式传递(spread)数组</h1><p>以前的代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">a, b</span>) </span>{</div><div class="line"> <span class="keyword">return</span> a + b;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">sumAndLog</span>(<span class="params">a, b</span>) </span>{</div><div class="line"> <span class="keyword">var</span> result = sum(a, b);</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Result is: '</span> + result);</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line">sumAndLog(<span class="number">10</span>, <span class="number">20</span>);</div></pre></td></tr></table></figure>
<p>ES2015 代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">a, b</span>) </span>{</div><div class="line"> <span class="keyword">return</span> a + b;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">sumAndLog</span>(<span class="params">...args</span>) </span>{</div><div class="line"> <span class="keyword">const</span> result = sum(...args);</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Result is: '</span> + result);</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line">sumAndLog(<span class="number">10</span>, <span class="number">20</span>);</div></pre></td></tr></table></figure>
<p>在 <code>sumAndLog</code> 方法中使用 <code>spread</code> 操作符(<code>...</code>),可以很简单地把所有参数存入 <code>args</code> 变量,然后再用 <code>spread</code> 操作符把 <code>args</code> 传入 <code>sum</code> 方法。</p>
<p>再看以下例子:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">printTeam</span>(<span class="params">leader, ...others</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Leader: '</span> + leader + <span class="string">' - Others: '</span> + others);</div><div class="line">}</div><div class="line"></div><div class="line">printTeam(<span class="string">'Arunoda'</span>, <span class="string">'John'</span>, <span class="string">'Singh'</span>);</div><div class="line"><span class="comment">//输出结果:"Leader: Arunoda - Others: John,Singh"</span></div></pre></td></tr></table></figure>
<h1 id="克隆、合并对象">克隆、合并对象</h1><p>以往都是用 <a href="http://underscorejs.org/" target="_blank" rel="external">underscore</a> 或者 <a href="https://lodash.com/" target="_blank" rel="external">lodash</a>,克隆、合并对象:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> user = {<span class="attr">name</span>: <span class="string">"Arunoda"</span>};</div><div class="line"><span class="keyword">var</span> newUser = _.clone(user);</div><div class="line"><span class="keyword">var</span> withAge = _.extend(user, {<span class="attr">age</span>: <span class="number">20</span>});</div><div class="line"><span class="keyword">var</span> newUserVersion = _.defaults({<span class="attr">age</span>: <span class="number">80</span>}, user);</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(newUser, withAge, newUserVersion);</div></pre></td></tr></table></figure>
<p>ES2015 不需要任何工具库,轻松实现以上功能。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> user = {<span class="attr">name</span>: <span class="string">"Arunoda"</span>};</div><div class="line"><span class="keyword">const</span> newUser = {...user};</div><div class="line"><span class="keyword">const</span> withAge = {...user, <span class="attr">age</span>: <span class="number">20</span>};</div><div class="line"><span class="keyword">const</span> newUserVersion = {<span class="attr">age</span>: <span class="number">80</span>, ...user};</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(newUser, withAge, newUserVersion);</div></pre></td></tr></table></figure>
<p>看以下例子:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> user = {</div><div class="line"> <span class="attr">name</span>: <span class="string">'Arunoda'</span>,</div><div class="line"> <span class="attr">emails</span>: [<span class="string">'[email protected]'</span>]</div><div class="line">};</div><div class="line"></div><div class="line"><span class="keyword">const</span> newUser = {...user};</div><div class="line">newUser.emails.push(<span class="string">'[email protected]'</span>);</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(user.emails);</div><div class="line"><span class="comment">//输出结果:["[email protected]", "[email protected]"]</span></div></pre></td></tr></table></figure>
<p>尽管我们克隆了对象,但不是深度克隆,只克隆了顶层字段,emails 数组字段使用的仍是同一个。</p>
<p><strong>往数组里添加元素</strong></p>
<p>跟对象类似,我们同样可以克隆数组:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> marks = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>];</div><div class="line"><span class="keyword">const</span> newMarks = [...marks, <span class="number">40</span>];</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(marks, newMarks);</div></pre></td></tr></table></figure>
<h1 id="JavaScript_不变性(Immutability)">JavaScript 不变性(Immutability)</h1><p>这些日子,JavaScript 也兴起函数式编程的概念。因此,我们可以尝试写写纯函数。</p>
<blockquote>
<p>纯函数:一个函数接收一些值,并且返回一些值,但是通过参数接收到的值不会被改变。 同样的输入总是返回同样的值。<br>random() 就不是一个纯函数,任何可以修改全局状态的函数都不能称之为纯。</p>
</blockquote>
<p>用 <code>spread</code> 操作符可以轻松实现。</p>
<p>用于对象:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">addMarks</span>(<span class="params">user, marks</span>) </span>{</div><div class="line"> <span class="keyword">return</span> {</div><div class="line"> ...user,</div><div class="line"> marks</div><div class="line"> };</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> user = {<span class="attr">username</span>: <span class="string">'arunoda'</span>};</div><div class="line"><span class="keyword">const</span> userWithMarks = addMarks(user, <span class="number">80</span>);</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(user, userWithMarks);</div></pre></td></tr></table></figure>
<p>用于数组:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">addUser</span>(<span class="params">users, username</span>) </span>{</div><div class="line"> <span class="keyword">const</span> user = {username};</div><div class="line"> <span class="keyword">return</span> [</div><div class="line"> ...users,</div><div class="line"> user</div><div class="line"> ];</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> user = {<span class="attr">username</span>: <span class="string">'arunoda'</span>};</div><div class="line"><span class="keyword">const</span> users = [user];</div><div class="line"><span class="keyword">const</span> newUsers = addUser(users, <span class="string">'john'</span>);</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(users, newUsers);</div></pre></td></tr></table></figure>
<h1 id="以_Python_方式合并字符串">以 Python 方式合并字符串</h1><p>合并字符串通常很烦,可以用 + 操作符,或者类似 <code>underscore</code> 的模板。</p>
<p>ES2015 的模板字符串,非常简单,看例子:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> name = <span class="string">"Arunoda"</span>;</div><div class="line"><span class="keyword">const</span> welcome = <span class="string">`Hello <span class="subst">${name}</span>, Good Morning!`</span>;</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(welcome);</div></pre></td></tr></table></figure>
<p>注意 <strong>“`”</strong> 的使用。</p>
<h2 id="多行字符串">多行字符串</h2><p>既然支持模板字符串,多行字符串也不在话下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> message = <span class="string">`</span></div><div class="line"> # Title</div><div class="line"></div><div class="line"> This is a multi line string as markdown.</div><div class="line"> It's pretty nice.</div><div class="line">`;</div><div class="line"><span class="built_in">console</span>.log(message);</div></pre></td></tr></table></figure>
<p>没有模板字符串的话,是这个样子的:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> message = <span class="string">"\n # Title\n\n This is a multi line string as markdown.\n It's pretty nice.\n"</span>;</div><div class="line"><span class="built_in">console</span>.log(message);</div></pre></td></tr></table></figure>
<h1 id="像_Java_一样写_Class">像 Java 一样写 Class</h1><p>JavaScript 并不是真正的面向对象语言,但是可以用函数和原型模拟类。ES2015 可以写真正原生的类了。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(type, number) {</div><div class="line"> <span class="keyword">this</span>.type = type;</div><div class="line"> <span class="keyword">this</span>.number = number;</div><div class="line"> }</div><div class="line"></div><div class="line"> display() {</div><div class="line"> <span class="keyword">return</span> <span class="string">`Number: <span class="subst">${<span class="keyword">this</span>.number}</span>`</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> v1 = <span class="keyword">new</span> Vehicle(<span class="string">'Car'</span>, <span class="string">'GH-2343'</span>);</div><div class="line"><span class="built_in">console</span>.log(v1.display());</div></pre></td></tr></table></figure>
<p>继承一个类:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(type, number) {</div><div class="line"> <span class="keyword">this</span>.type = type;</div><div class="line"> <span class="keyword">this</span>.number = number;</div><div class="line"> }</div><div class="line"></div><div class="line"> display() {</div><div class="line"> <span class="keyword">return</span> <span class="string">`Number: <span class="subst">${<span class="keyword">this</span>.number}</span>`</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Car</span> <span class="keyword">extends</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(number) {</div><div class="line"> <span class="keyword">super</span>(<span class="string">'Car'</span>, number);</div><div class="line"> }</div><div class="line"></div><div class="line"> display() {</div><div class="line"> <span class="keyword">const</span> value = <span class="keyword">super</span>.display();</div><div class="line"> <span class="keyword">return</span> <span class="string">`Car <span class="subst">${value}</span>`</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> v1 = <span class="keyword">new</span> Car(<span class="string">'GH-2343'</span>);</div><div class="line"><span class="built_in">console</span>.log(v1.display());</div></pre></td></tr></table></figure>
<p>小汽车继承了车辆:</p>
<ul>
<li>在 Car constructor 内部调用了 <code>super constructor</code> (Vehicle 的 constructor)。</li>
<li>Car 的 display() 方法内部,调用了 <code>super.display()</code> 。这里展示了子类如何继承方法。</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(type, number) {</div><div class="line"> <span class="keyword">this</span>.type = type;</div><div class="line"> <span class="keyword">this</span>.number = number;</div><div class="line"> }</div><div class="line"></div><div class="line"> display() {</div><div class="line"> <span class="keyword">return</span> <span class="string">`Number: <span class="subst">${<span class="keyword">this</span>.number}</span>`</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Car</span> <span class="keyword">extends</span> <span class="title">Vehicle</span> </span>{</div><div class="line"> display() {</div><div class="line"> <span class="keyword">const</span> value = <span class="keyword">super</span>.display();</div><div class="line"> <span class="keyword">return</span> <span class="string">`Car <span class="subst">${value}</span>`</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> v1 = <span class="keyword">new</span> Car(<span class="string">'GH-2343'</span>);</div><div class="line"><span class="built_in">console</span>.log(v1.display());</div></pre></td></tr></table></figure>
<p>Car 类没有实现 constructor 的话,它会用 Vehicle 的 constructor 。</p>
<h1 id="ES2015_模块系统">ES2015 模块系统</h1><p>ES2015 的模块系统很像 CommonJS 模块系统(或者 Node.js 的模块系统),但是有一点主要的区别:</p>
<blockquote>
<p>所有的模块导入应该是静态的,无法在运行时导入模块。编译时间应该完成导入(或者最好在解释 JavaScript 期间完成)。<br>以下代码在 ES2015 模块里无法使用:</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">let</span> router;</div><div class="line"></div><div class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="built_in">window</span> === <span class="string">'function'</span>) {</div><div class="line"> router = <span class="keyword">import</span> <span class="string">'./client-router'</span>;</div><div class="line">} <span class="keyword">else</span> {</div><div class="line"> router = <span class="keyword">import</span> <span class="string">'./server-router'</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="命名导出函数">命名导出函数</h2><p>定义一个简单的导出函数 <code>sum</code> :</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">a, b</span>) </span>{</div><div class="line"> <span class="keyword">return</span> a + b;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>然后导入它:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> {sum} <span class="keyword">from</span> <span class="string">'./lib/math'</span>;</div><div class="line"></div><div class="line"><span class="keyword">const</span> total = sum(<span class="number">10</span>, <span class="number">20</span>);</div><div class="line"><span class="built_in">console</span>.log(total);</div></pre></td></tr></table></figure>
<p>导入多个函数:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> {sum, multiply}</div></pre></td></tr></table></figure>
<p>像函数一样,可以导出任何类型的变量,包括类。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">export</span> <span class="keyword">const</span> BASE = <span class="number">10</span>;</div><div class="line"><span class="keyword">export</span> <span class="keyword">let</span> name = <span class="string">'Arunoda'</span>;</div><div class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Vehicle</span> </span>{};</div></pre></td></tr></table></figure>
<h2 id="默认导出">默认导出</h2><p>有时需要导出一个独立的模块,叫做默认导出。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> </span>{</div><div class="line"> <span class="keyword">constructor</span>(type, number) {</div><div class="line"> <span class="keyword">this</span>.type = type;</div><div class="line"> <span class="keyword">this</span>.number = number;</div><div class="line"> }</div><div class="line"></div><div class="line"> display() {</div><div class="line"> <span class="keyword">return</span> <span class="string">`Number: <span class="subst">${<span class="keyword">this</span>.number}</span>`</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以这么导入:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> Vehicle <span class="keyword">from</span> <span class="string">'./lib/vehicle'</span>;</div><div class="line"></div><div class="line"><span class="keyword">const</span> v1 = <span class="keyword">new</span> Vehicle(<span class="string">'Car'</span>, <span class="string">'GH-3355'</span>);</div><div class="line"><span class="built_in">console</span>.log(v1.display());</div></pre></td></tr></table></figure>
<p>如果再导出一个 <code>print</code> 函数,这么写:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> Vehicle, {print} <span class="keyword">from</span> <span class="string">'./lib/vehicle'</span>;</div></pre></td></tr></table></figure>
<p>这便是我们如何在同一模块导入 “默认导出” 和 “命名导出” 的方式。</p>
<p>重命名导入,一次导入所有命名导出,以及更多知识,参见 <a href="https://developer.mozilla.org/en/docs/web/javascript/reference/statements/import" target="_blank" rel="external">MDN 文档</a>。</p>
<h1 id="在实际的项目中使用_ES2015?">在实际的项目中使用 ES2015?</h1><p>至今还没有浏览器完全实现 ES2015 的所有规范。因此,无法直接在浏览器里使用 ES2015 。</p>
<p>那么我们该怎么做?</p>
<p>欢迎来到 transpiling 的世界。</p>
<p>现在可以按 ES2015 写代码,然后使用一个工具把它转换成 ES5,最有名的一个 transpiler 便是 <a href="https://babeljs.io/" target="_blank" rel="external">Babel</a>。</p>
<p>设置 Babel 并没那么简单,需要一定的经验,这是一些新手包,拿去用吧。</p>
<ul>
<li>在 Node.js 中使用 <a href="https:/kadirahq/node-base" target="_blank" rel="external">node-base</a> </li>
<li>在 NPM 模块中使用 <a href="https:/kadirahq/npm-base" target="_blank" rel="external">npm-base</a> </li>
<li>在 React app 中使用 <a href="https:/gaearon/react-hot-boilerplate" target="_blank" rel="external">react-hot-boilerplate</a></li>
</ul>
<blockquote>
<p>此外,可以使用 <a href="https://www.meteor.com/" target="_blank" rel="external">Meteor</a>,默认支持 ES2015 了。</p>
</blockquote>
<h1 id="深入研究_ES2015">深入研究 ES2015</h1><p>深入研究,可以参考以下链接:</p>
<ul>
<li><a href="https://babeljs.io/docs/learn-es2015/" target="_blank" rel="external">Babel’s Learn ES2015 Guide</a></li>
<li><a href="https:/ericdouglas/ES6-Learning" target="_blank" rel="external">Massive List of ES2015 Learning Resources</a></li>
</ul>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文: <a href="https://tutor.mantrajs.com/say-hello-to-ES2015/introduction">Say Hello To ES2015</a><br>笔记:<a href="http://weibo.com/newwave">涂鸦码龙</a></p>
</blockquote>
<h1 id="介绍_ES2015">介绍 ES2015</h1><p>ES2015 是新版的 JavaScript,Node.js 已经完全支持,浏览器端可以用 <a href="https://babeljs.io/">Babel</a> 库编译。</p>
<p>运行本文的示例代码,可以用 <a href="https://jsbin.com/defeba/edit?js,console">JSBin 环境</a>,也可以结合原文中的测试题检测学习效果。</p>
<h1 id="使用_let_和_const">使用 let 和 const</h1><p>ES2015 可以用 <code>const</code> 或 <code>let</code> 替换 <code>var</code> ,它们定义了块级作用域变量。</p>
<p>示例代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> lc=<span class="number">0</span>; lc < <span class="number">10</span>; lc++) {</div><div class="line"> <span class="keyword">let</span> value = lc;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(value); <span class="comment">//抛出错误</span></div></pre></td></tr></table></figure>
<p>变量 <code>value</code> 只能在 <code>for</code> 循环中使用。</p>
<p><code>const</code> 跟 <code>let</code> 很像,但是它定义的变量值无法改变。</p>
<p>示例代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> user = {<span class="attr">name</span>: <span class="string">"Arunoda"</span>};</div><div class="line">user.name = <span class="string">"Susiripala"</span>;</div><div class="line"><span class="built_in">console</span>.log(user.name);</div></pre></td></tr></table></figure>
<p>改变的是变量 <code>user</code> 内部的属性,并没有改变 <code>user</code> 本身。</p>
<p>许多人更喜欢用 <code>const</code> 代替 <code>let</code> 。</p>
<h1 id="使用箭头函数整理你的代码">使用箭头函数整理你的代码</h1><p>熟悉的方式:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyBy10 = numbers.map(<span class="function"><span class="keyword">function</span>(<span class="params">a</span>) </span>{</div><div class="line"> <span class="keyword">return</span> a * <span class="number">10</span>;</div><div class="line">});</div><div class="line"><span class="built_in">console</span>.log(multiplyBy10);</div></pre></td></tr></table></figure>
<p>使用箭头函数以后:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyBy10 = numbers.map(<span class="function"><span class="params">a</span> =></span> a * <span class="number">10</span>);</div><div class="line"><span class="built_in">console</span>.log(multiplyBy10);</div></pre></td></tr></table></figure>
<p>如果方法接受不止1个参数,可以这么写:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyByIndex = numbers.map(<span class="function">(<span class="params">a, i</span>) =></span> a * i);</div><div class="line"><span class="built_in">console</span>.log(multiplyByIndex);</div></pre></td></tr></table></figure>
<p>箭头函数返回一个对象的话,需要加圆括号:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> numbers = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>];</div><div class="line"><span class="keyword">const</span> multiplyBy10 = numbers.map(<span class="function"><span class="params">a</span> =></span> ({<span class="attr">res</span>: a * <span class="number">10</span>}));</div><div class="line"><span class="built_in">console</span>.log(multiplyBy10);</div></pre></td></tr></table></figure>]]>
</summary>
<category term="JavaScript" scheme="http://jinlong.github.io/tags/JavaScript/"/>
<category term="ES6" scheme="http://jinlong.github.io/tags/ES6/"/>
<category term="ES2015" scheme="http://jinlong.github.io/tags/ES2015/"/>
<category term="JavaScript" scheme="http://jinlong.github.io/categories/JavaScript/"/>
</entry>
<entry>
<title><![CDATA[flex-grow 不易理解,难道不是吗?]]></title>
<link href="http://jinlong.github.io/2016/02/04/flex-grow-is-weird/"/>
<id>http://jinlong.github.io/2016/02/04/flex-grow-is-weird/</id>
<published>2016-02-04T14:40:39.000Z</published>
<updated>2016-02-04T15:22:12.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:<a href="https://css-tricks.com/flex-grow-is-weird/" target="_blank" rel="external">‘flex-grow’ is weird. Or is it?</a><br>翻译:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
</blockquote>
<p>当我刚接触 <code>flex-grow</code> 时,为了探寻它的工作原理,做了一个<a href="http://codepen.io/matuzo/pen/zvXQyj" target="_blank" rel="external">简单的例子</a>。</p>
<p>我以为理解的挺透彻了,但是当我把它应用到同事的网站上时,效果跟我想象的完全不同。无论怎么改,布局都无法像我的demo那样展示。这时我才意识到,我并没有完全掌握 <code>flex-grow</code> 。</p>
<h1 id="flex-grow_为何不正常">flex-grow 为何不正常</h1><p>在我深入剖析 <code>flex-grow</code> 的功能之前,我想解释一下我起初犯了什么错。</p>
<p>我认为所有 flex 元素的 <code>flex-grow</code> 如果设置为 <code>1</code> ,它们将一样宽。如果某一项的 <code>flex-grow</code> 设置为 <code>2</code> ,它将是其它元素的二倍宽。</p>
<p>一切听起来顺理成章。我<a href="http://codepen.io/matuzo/pen/zvXQyj" target="_blank" rel="external">上面的例子</a> 貌似也印证了这点。父元素是900px宽,<code>flex-grow: 2</code> 的 section 元素计算后是600px宽,<code>flex-grow: 1</code> 的 aside 元素计算后是300px宽。</p>
<p>如你所见,它在这个例子中展现的近乎完美,可是在真实的例子中却不尽人意,即使我们用了完全相同的 CSS。事实证明,问题不在 CSS,而在于内容(或者说缺乏内容)。我的测试用例只用了两个空元素,无法展示这个属性最重要的细节。</p>
<h1 id="flex-grow_到底如何工作">flex-grow 到底如何工作</h1><p>啰嗦了半天,我终于要解释 <code>flex-grow</code> 没有尽如人意的原因了。</p>
<p>为了阐明原因,我又搞了个<a href="http://codepen.io/matuzo/pen/ZQEWjg" target="_blank" rel="external">栗子</a> ,所有的设置跟<a href="http://codepen.io/matuzo/pen/zvXQyj" target="_blank" rel="external">第一个栗子</a> 完全一致,只不过 section 和 aside 元素不再是空的。看吧,两个元素的比例不再是 2 : 1,flex-grow 为 1 的元素的确比 flex-grow 为 2 的元素宽不少呐。</p>
<a id="more"></a>
<h2 id="解析">解析</h2><p>如果给父元素设置了 <code>display: flex;</code> ,子元素仅仅是水平排列,没有其它效果了。如果没有足够的空间,它们会收缩一些尺寸。另一方面,如果有足够的空间,它们也不会扩展,因为 Flexbox 希望我们自己定义扩展多少。<strong><em>flex-grow</em></strong> 恰恰用来定义剩余空间如何分配,每一项分享多大宽度。</p>
<p>换言之:</p>
<blockquote>
<p>flex 容器为它的子元素分配剩余空间(它们的扩展系数是成比例的),从而填满整个容器,或者收缩元素(它们的收缩系数也是成比例的),从而阻止溢出。</p>
</blockquote>
<p><a href="https://drafts.csswg.org/css-flexbox/#flexibility" target="_blank" rel="external">https://drafts.csswg.org/css-flexbox/#flexibility</a></p>
<h2 id="证明">证明</h2><p>如果我们可视化一把,概念就很清晰明了了。</p>
<p>首先,我们给父元素设置了 <code>display: flex</code>,然后它的子元素成了 flex 元素,一个挨一个的横向排列。 </p>
<p><img src="/image/flex-grow-weird/pic1.png" alt="附图1"></p>
<p>下一步,我们要决定每个元素能获得多少剩余空间。先前的例子中,第一个元素获得了 2/3 的剩余空间(<code>flex-grow: 2</code>),第二个元素获得了 1/3 的剩余空间(<code>flex-grow: 1</code>)。想知道 flex-grow 总共的值是多少,看看剩余空间被分成了几份吧。</p>
<p><img src="/image/flex-grow-weird/pic2.png" alt="附图2"></p>
<p>最终我们得出了可分配的块数,根据 <code>flex-grow</code> 的值,每个元素可以获得适当的块数。</p>
<p><img src="/image/flex-grow-weird/pic3.png" alt="附图3"></p>
<h2 id="计算结果">计算结果</h2><p>理论和视觉展示都不错,让我们为<a href="http://codepen.io/matuzo/pen/ZQEWjg" target="_blank" rel="external">上面例子</a> 做点算术吧。</p>
<p>我们需要4组数字:父容器宽度, section 和 aside 元素的初始宽度,以及 flex-grow 的总值。</p>
<p>父容器宽:<strong>900px</strong><br>section 宽:<strong>99px</strong><br>aside 宽:<strong>623px</strong><br>flex-grow 总值:<strong>3</strong></p>
<p><strong>1. 首先计算剩余空间</strong></p>
<p>父容器宽减去每一个子元素的初始宽度。</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">900 </span>- <span class="number">99</span> - <span class="number">623</span> = <span class="number">178</span></div></pre></td></tr></table></figure>
<p>父容器宽 - section 宽 - aside 宽 = 剩余空间</p>
<p><strong>2. 然后计算 flex-grow 的1份是多宽</strong></p>
<p>既然有了剩余空间,我们还需要确定把它切成几份。重要的是,我们不按元素的个数切分剩余空间,而是按 flex-grow 总值,所以这里是 3 (<code>flex-grow: 2</code> + <code>flex-grow: 1</code>)</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">178 </span>/ <span class="number">3</span> = <span class="number">59.33</span></div></pre></td></tr></table></figure>
<p>剩余空间 / flex-grow 总值 = “1份 flex-grow 的宽”</p>
<p><strong>3. 最终所有的元素瓜分剩余空间</strong></p>
<p>依据它们的 flex-grow 值,section 需要 2 份(2 <em> 59.33),aside 需要 1 份(1</em> 59.33)。这个数字再与每个元素的初始宽度相加。</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">99 </span>+ (<span class="number">2</span> * <span class="number">59.33</span>) = <span class="number">217.66</span> (≈<span class="number">218</span>px)</div></pre></td></tr></table></figure>
<p>初始 section 宽度 + (section 的 flex-grow 值 * “1 份 flex-grow 的宽”) = 新的宽度</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">623 </span>+ (<span class="number">1</span> * <span class="number">59.33</span>) = <span class="number">682.33</span> (≈<span class="number">682</span>px)</div></pre></td></tr></table></figure>
<p>初始 aside 宽度 + (aside 的 flex-grow 值 * “1 份 flex-grow 的宽”) = 新的宽度</p>
<p>so easy,不是吗?</p>
<h1 id="好吧,那为什么第一个例子正常呢?">好吧,那为什么第一个例子正常呢?</h1><p>我们按已有的公式,算下<a href="http://codepen.io/matuzo/pen/zvXQyj" target="_blank" rel="external">第一个例子</a> 。</p>
<p>父容器宽:<strong>900px</strong><br>section 宽:<strong>0px</strong><br>aside 宽:<strong>0px</strong><br>flex-grow 总值:<strong>3</strong></p>
<p><strong>1. 计算剩余空间</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">900 </span>- <span class="number">0</span> - <span class="number">0</span> = <span class="number">900</span></div></pre></td></tr></table></figure>
<p><strong>2. 计算 flex-grow 的1份是多宽</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">900 </span>/ <span class="number">3</span> = <span class="number">300</span></div></pre></td></tr></table></figure>
<p><strong>3. 分配剩余空间</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">0 </span>+ (<span class="number">2</span> * <span class="number">300</span>) = <span class="number">600</span> </div><div class="line"><span class="symbol">0 </span>+ (<span class="number">1</span> * <span class="number">300</span>) = <span class="number">300</span></div></pre></td></tr></table></figure>
<p>如果每个元素的宽是 0,剩余空间等于父容器的宽度,因此,看起来像是 <code>flex-grow</code> 按比例划分了父容器的宽度。</p>
<h1 id="flex-grow_和_flex-basis">flex-grow 和 flex-basis</h1><p>快速回顾一下:剩余空间被 <code>flex-grow</code> 的总值划分,由此产生的商,乘以各自的 <code>flex-grow</code> 值,结果再加上每个元素的初始宽度。</p>
<p>但是如果没有剩余空间或者不想依赖元素的初始宽度,我们可以设置它吗?还能用 <code>flex-grow</code> 吗?</p>
<p>当然可以。有个 <code>flex-basis</code> 属性,它可以定义元素的初始宽度。如果 <code>flex-basis</code> 和 <code>flex-grow</code> 一起设置,宽度的计算方式得变一下。</p>
<blockquote>
<p><code><‘flex-basis’></code> :按 flex 因子分配剩余空间之前,每个 flex 元素的最初主要尺寸。</p>
</blockquote>
<p><a href="https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis" target="_blank" rel="external">https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis</a> </p>
<p>如果给某个元素设置了 <code>flex-basis</code> 属性,我们计算的时候就不能再用元素本身的初始宽度了,而要用 <code>flex-basis</code> 属性的值。</p>
<p>我调整了一下<a href="http://codepen.io/matuzo/pen/GoRqOP" target="_blank" rel="external">先前的例子</a> ,给每个元素加了 <code>flex-basis</code> 属性。</p>
<p>父容器宽:<strong>900px</strong><br>section 宽:<strong>400px</strong>(flex-basis 值)<br>aside 宽:<strong>200px</strong>(flex-basis 值)<br>flex-grow 总值:<strong>3</strong></p>
<p><strong>1. 计算剩余空间</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">900 </span>- <span class="number">400</span> - <span class="number">200</span> = <span class="number">300</span></div></pre></td></tr></table></figure>
<p><strong>2. 计算 flex-grow 的1份是多宽</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">300 </span>/ <span class="number">3</span> = <span class="number">100</span></div></pre></td></tr></table></figure>
<p><strong>3. 分配剩余空间</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="symbol">400 </span>+ (<span class="number">2</span> * <span class="number">100</span>) = <span class="number">600</span> </div><div class="line"><span class="symbol">200 </span>+ (<span class="number">1</span> * <span class="number">100</span>) = <span class="number">300</span></div></pre></td></tr></table></figure>
<p>仅仅是为了完整性,我才用到 px 值,用<a href="http://codepen.io/matuzo/pen/pgobag" target="_blank" rel="external">百分比的话当然也没问题</a> 。</p>
<h1 id="与盒模型结合">与盒模型结合</h1><p>为了覆盖所有情况,如果我们加上 padding 和 margin 看看会发生什么,<a href="http://codepen.io/matuzo/pen/Qybgee" target="_blank" rel="external">啥也没发生</a> ,第一步计算的时候,只要减去 margin 就好了。</p>
<p>唯一需要注意的是,使用 <code>box-sizing</code> 的话, <code>flex-basis</code> 跟 <code>width</code> 属性表现相似。<a href="http://codepen.io/matuzo/pen/wMaeVO" target="_blank" rel="external">如果 box-sizing 属性改变</a>,计算结果也会变化。如果 <code>box-sizing</code> 设置为 <code>border-box</code> ,计算时只需用到 <code>flex-basis</code> 和 <code>margin</code> 值,因为 <code>padding</code> 已经包含到宽度里面了。</p>
<h1 id="更多实用的例子">更多实用的例子</h1><p>好吧,算术是玩够了。我再展示一些项目中合理使用 <code>flex-grow</code> 的例子吧。</p>
<h2 id="不限宽度:[_x_]%">不限宽度:[ x ]%</h2><p>实际应用中,剩余空间是自动分配的,如果想让子元素填满父容器的话,我们没必要再考虑宽度值。</p>
<p></p><p data-height="268" data-theme-id="0" data-slug-hash="QyWEBb" data-default-tab="result" data-user="matuzo" class="codepen">See the Pen <a href="http://codepen.io/matuzo/pen/QyWEBb/" target="_blank" rel="external">flex-grow</a> by Manuel Matuzovic (<a href="http://codepen.io/matuzo" target="_blank" rel="external">@matuzo</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p><p></p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<h2 id="固定宽度的_“圣杯”_3列流式布局">固定宽度的 “圣杯” 3列流式布局</h2><p>固定加自适应宽度的混合布局,可以用浮动实现,但是既不简单直观,又不灵活。Flexbox 实现的话,加点儿 flex-grow 和 flex-basis 魔法,简直小菜一碟。</p>
<p></p><p data-height="268" data-theme-id="0" data-slug-hash="jbRjMG" data-default-tab="result" data-user="matuzo" class="codepen">See the Pen <a href="http://codepen.io/matuzo/pen/jbRjMG/" target="_blank" rel="external">Layout using fluid and fixed widths</a> by Manuel Matuzovic (<a href="http://codepen.io/matuzo" target="_blank" rel="external">@matuzo</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p><p></p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<h2 id="使用任何元素填满剩余空间">使用任何元素填满剩余空间</h2><p>比如,一个 label 元素后面紧跟着输入框,想让输入框填满剩余空间,不再需要丑陋的 hacks 。</p>
<p></p><p data-height="268" data-theme-id="0" data-slug-hash="eJYdWV" data-default-tab="result" data-user="matuzo" class="codepen">See the Pen <a href="http://codepen.io/matuzo/pen/eJYdWV/" target="_blank" rel="external">Filling up the remaining space in a form</a> by Manuel Matuzovic (<a href="http://codepen.io/matuzo" target="_blank" rel="external">@matuzo</a>) on <a href="http://codepen.io" target="_blank" rel="external">CodePen</a>.</p><p></p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
<p>Philip Waltons 的 <a href="https://philipwalton.github.io/solved-by-flexbox/" target="_blank" rel="external">Solved by Flexbox</a> 一文可以找到更多示例。</p>
<h1 id="听听标准规范怎么说">听听标准规范怎么说</h1><p>根据标准,使用 <code>flex</code> 的简写,比直接用 <code>flex-grow</code> 更好。</p>
<blockquote>
<p>Authors are encouraged to control flexibility using the flex shorthand rather than flex-grow directly, as the shorthand correctly resets any unspecified components to accommodate common uses.</p>
</blockquote>
<p><a href="https://drafts.csswg.org/css-flexbox/#flex-grow-property" target="_blank" rel="external">https://drafts.csswg.org/css-flexbox/#flex-grow-property</a> </p>
<p>但是小心!如果仅仅使用 <code>flex: 1;</code> ,以上某些例子可能无法正常展示。</p>
<p>我们的例子要用 <code>flex</code> 的话,应该这么定义:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">flex: 2 1 auto; /* (<span class="tag"><<span class="name">flex-grow</span>></span> | <span class="tag"><<span class="name">flex-shrink</span>></span> | <span class="tag"><<span class="name">flex-basis</span>></span>) */</div></pre></td></tr></table></figure>
<h1 id="深入学习_Flexbox">深入学习 Flexbox</h1><p>如果你想深入学习 Flexbox,可以看看这些不错的资源:</p>
<ul>
<li><a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/" target="_blank" rel="external">A Complete Guide to Flexbox</a> by Chris Coyier</li>
<li><a href="https://chriswrightdesign.com/experiments/flexbox-adventures/" target="_blank" rel="external">Flexbox adventures</a> by Chris Wright</li>
<li><a href="http://flexboxfroggy.com/" target="_blank" rel="external">Flexbox Froggy</a> by Thomas Park</li>
<li><a href="http://flexbox.io/" target="_blank" rel="external">What the Flexbox?</a> by Wes Bos</li>
<li><a href="https://cvan.io/flexboxin5/" target="_blank" rel="external">flexboxin5</a> </li>
<li><a href="http://madebymike.com.au/demos/flexbox-tester/" target="_blank" rel="external">Flexbox Tester</a> by Mike Riethmuller</li>
</ul>
<h1 id="总结经验教训">总结经验教训</h1><p><code>flex-grow</code> 不易理解吗?也不全是。我们只需理解它如何工作,它做了什么。如果一个元素设置 <code>flex-grow</code> 为 <code>3</code> ,并不代表它是 <code>flex-grow</code> 为 <code>1</code> 的元素的3倍大,准确含义是:它的初始宽度可以增加的像素值是另一个元素的3倍。</p>
<p>我通过<a href="http://codepen.io/matuzo/pen/zvXQyj" target="_blank" rel="external">两个空元素</a> 测试 flex-grow ,得到的结论跟<a href="http://codepen.io/matuzo/pen/ZQEWjg" target="_blank" rel="external">真实情况</a> 完全不符。应该在尽可能真实的环境中验证新事物,这样才能得到最切合实际的结论。</p>
]]></content>
<summary type="html">
<![CDATA[<blockquote>
<p>原文:<a href="https://css-tricks.com/flex-grow-is-weird/">‘flex-grow’ is weird. Or is it?</a><br>翻译:<a href="http://weibo.com/newwave">涂鸦码龙</a></p>
</blockquote>
<p>当我刚接触 <code>flex-grow</code> 时,为了探寻它的工作原理,做了一个<a href="http://codepen.io/matuzo/pen/zvXQyj">简单的例子</a>。</p>
<p>我以为理解的挺透彻了,但是当我把它应用到同事的网站上时,效果跟我想象的完全不同。无论怎么改,布局都无法像我的demo那样展示。这时我才意识到,我并没有完全掌握 <code>flex-grow</code> 。</p>
<h1 id="flex-grow_为何不正常">flex-grow 为何不正常</h1><p>在我深入剖析 <code>flex-grow</code> 的功能之前,我想解释一下我起初犯了什么错。</p>
<p>我认为所有 flex 元素的 <code>flex-grow</code> 如果设置为 <code>1</code> ,它们将一样宽。如果某一项的 <code>flex-grow</code> 设置为 <code>2</code> ,它将是其它元素的二倍宽。</p>
<p>一切听起来顺理成章。我<a href="http://codepen.io/matuzo/pen/zvXQyj">上面的例子</a> 貌似也印证了这点。父元素是900px宽,<code>flex-grow: 2</code> 的 section 元素计算后是600px宽,<code>flex-grow: 1</code> 的 aside 元素计算后是300px宽。</p>
<p>如你所见,它在这个例子中展现的近乎完美,可是在真实的例子中却不尽人意,即使我们用了完全相同的 CSS。事实证明,问题不在 CSS,而在于内容(或者说缺乏内容)。我的测试用例只用了两个空元素,无法展示这个属性最重要的细节。</p>
<h1 id="flex-grow_到底如何工作">flex-grow 到底如何工作</h1><p>啰嗦了半天,我终于要解释 <code>flex-grow</code> 没有尽如人意的原因了。</p>
<p>为了阐明原因,我又搞了个<a href="http://codepen.io/matuzo/pen/ZQEWjg">栗子</a> ,所有的设置跟<a href="http://codepen.io/matuzo/pen/zvXQyj">第一个栗子</a> 完全一致,只不过 section 和 aside 元素不再是空的。看吧,两个元素的比例不再是 2 : 1,flex-grow 为 1 的元素的确比 flex-grow 为 2 的元素宽不少呐。</p>]]>
</summary>
<category term="flexbox" scheme="http://jinlong.github.io/tags/flexbox/"/>
<category term="CSS" scheme="http://jinlong.github.io/categories/CSS/"/>
</entry>
<entry>
<title><![CDATA[CSS分层动画可以让元素沿弧形路径运动]]></title>
<link href="http://jinlong.github.io/2016/01/14/moving-along-a-curved-path-in-css-with-layered-animation/"/>
<id>http://jinlong.github.io/2016/01/14/moving-along-a-curved-path-in-css-with-layered-animation/</id>
<published>2016-01-14T14:48:56.000Z</published>
<updated>2016-02-04T14:42:48.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>原文:<a href="http://tobiasahlin.com/blog/curved-path-animations-in-css/" target="_blank" rel="external">Moving along a curved path in CSS with layered animation</a><br>翻译:<a href="http://weibo.com/newwave" target="_blank" rel="external">涂鸦码龙</a></p>
<p>译者注:部分代码示例在原文中可以看效果(作者写在博文里面了…),我偷懒把它做成Gif图了。</p>
</blockquote>
<p>CSS 的 animations (动画) 和 transitions(变换)擅于实现从点 A 到点 B 的直线运动,运动轨迹是直线路径。给一个元素添加了 <code>animation</code> 或者 <code>transition</code> 以后,无论你如何调整<a href="https://developer.mozilla.org/en/docs/Web/CSS/timing-function" target="_blank" rel="external">贝塞尔曲线</a>,都无法让它沿着弧形路径运动。你可以通过自定义 timing function 属性,做出弹动的效果,但是它沿着 X 和 Y 轴相对移动的值永远是相同的。</p>
<p>与其使用 JavaScript 实现外观自然的运动,不如尝试用这种简单的方式:分层动画,绕过已有的限制。通过使用两个或多个元素实现动画效果,我们可以更加细粒度地控制某个元素的路径,沿着 X 轴运动使用一种 timing function ,沿着 Y 轴运动使用另一种 timing function 。</p>
<h1 id="问题所在">问题所在</h1><p><img src="/image/layered-anim/exp1.gif" alt="附图1"></p>
<p>当我们深入探讨解决方案之前,看看到底问题在哪。CSS <code>animations</code> 和 <code>transitions</code> 限制我们只能沿直线路径运动。元素总是沿着点 A 到点 B 的最短路径运动,如果我们另辟蹊径,告诉 CSS 沿着“更好的路径”,而不是“最短路径”运动呢?</p>
<p>用 CSS (开启硬件加速)实现两点之间的运动,最直截了当的方式是使用 <code>transform</code> 的 <code>translate</code> 在一定时间内移动某个元素。这就产生了直线运动。在 <code>@keyframes</code> 中,我们打算在 (0,0) 和 (100,-100) 间来回运动,见上图例子:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">@<span class="keyword">keyframes</span> straightLine {</div><div class="line"> 50% {</div><div class="line"> <span class="attribute">transform</span>: <span class="built_in">translate3D</span>(100px, -100px, 0);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="selector-class">.dot</span> {</div><div class="line"> <span class="attribute">animation</span>: straightLine <span class="number">2.5s</span> infinite linear;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这些看起来并不难懂,但我们稍等片刻,思考一下我们需要的解决方案,拆分开来的动画,视觉上长什么样子呢。</p>
<p><code>0%</code> 时,元素从 (0,0) 出发,<code>50%</code> 时,我们用了 <code>translate3D(100px, -100px, 0)</code> 把它移动到 (100,-100),然后原路返回。换句话说,我们把元素向右移动了 <code>100px</code>,向上移动了 <code>100px</code>,两个方向联合作用使元素沿着一个角度运动。</p>
<a id="more"></a>
<p><img src="/image/layered-anim/exp2.gif" alt="附图2"></p>