-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1162 lines (831 loc) · 180 KB
/
index.html
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
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"yoursite.com","root":"/","scheme":"Muse","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"appID":"45UGH4CJ52","apiKey":"c88fb708849de8d55c0523c1f9addc08","indexName":"garannodou","hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
</script>
<meta name="description" content="Do you best, and God will do the lest.">
<meta property="og:type" content="website">
<meta property="og:title" content="Veda">
<meta property="og:url" content="http://yoursite.com/index.html">
<meta property="og:site_name" content="Veda">
<meta property="og:description" content="Do you best, and God will do the lest.">
<meta property="og:locale">
<meta property="article:author" content="张煜航">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="http://yoursite.com/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false,
lang : 'zh-Hans'
};
</script>
<title>Veda</title>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="Toggle navigation bar">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<h1 class="site-title">Veda</h1>
<span class="logo-line-after"><i></i></span>
</a>
<p class="site-subtitle" itemprop="description">A collection of my tranlation and a few of mind。</p>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="main-menu menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-home fa-fw"></i>Home</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>Archives</a>
</li>
</ul>
</nav>
</div>
</header>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content index posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2023/02/02/%E8%8B%A5%E5%B9%B2%E5%B9%B4%E5%90%8E%E7%9A%84%E8%AF%88%E5%B0%B8/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2023/02/02/%E8%8B%A5%E5%B9%B2%E5%B9%B4%E5%90%8E%E7%9A%84%E8%AF%88%E5%B0%B8/" class="post-title-link" itemprop="url">若干年后的诈尸</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2023-02-02 04:48:31 / Modified: 05:02:08" itemprop="dateCreated datePublished" datetime="2023-02-02T04:48:31+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>半夜学习线代,心血来潮的把当初的 gh_page 捞出来看了一下,真不知道我当初为啥要用 submodule 关联。</p>
<p>不过仔细一想当年没 monorepo 的解决方案,貌似一下就都说的通了。</p>
<p>最新的 hexo-next 的主题发觉真的不喜欢。</p>
<p>先随便整一下,改天要不还是迁移到 jekyll 吧。</p>
<p>不知道为啥 apple m2 下的 ruby 的安装各种报错,不知不觉又熬夜了,昨天回家的路上貌似看到她了。</p>
<p>明天继续线代的学习吧,感谢晓询的“你一直写前端不焦虑吗”。</p>
<p>话说当年忽悠我妹去学数学真的是太好了。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2017/03/22/Nothing/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2017/03/22/Nothing/" class="post-title-link" itemprop="url">一些需要思考的事情</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2017-03-22 00:00:00" itemprop="dateCreated datePublished" datetime="2017-03-22T00:00:00+08:00">2017-03-22</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2023-02-02 04:30:17" itemprop="dateModified" datetime="2023-02-02T04:30:17+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<blockquote>
<ol>
<li>你觉得有意义事情</li>
<li>有让你去做的更好的余地</li>
<li>在一个很好的老大手下做,有权利去让一些事情做得更好</li>
</ol>
</blockquote>
<p>现阶段我所应该去追求的。 – From MIKE</p>
<blockquote>
<ol>
<li>潜力,很强的学习能力</li>
<li>目前能力,扎实的基础和不错的知识面</li>
<li>特质,比如代码洁癖,像素控,产品意识等</li>
</ol>
</blockquote>
<p>这是假如我想进入蚂蚁的团队,我所要做到的。 – From PETER</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2017/03/06/JavaScript%20Errors%20and%20Stack%20Traces%20in%20Depth/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2017/03/06/JavaScript%20Errors%20and%20Stack%20Traces%20in%20Depth/" class="post-title-link" itemprop="url">【翻译】深入了解JavaScript错误和堆栈追踪</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2017-03-06 00:00:00" itemprop="dateCreated datePublished" datetime="2017-03-06T00:00:00+08:00">2017-03-06</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2023-02-02 04:30:17" itemprop="dateModified" datetime="2023-02-02T04:30:17+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>原文地址:<a target="_blank" rel="noopener" href="http://lucasfcosta.com/2017/02/17/JavaScript-Errors-and-Stack-Traces.html">http://lucasfcosta.com/2017/02/17/JavaScript-Errors-and-Stack-Traces.html</a></p>
<p>(。・∀・)ノ゙嗨,大家好!鉴于我几个星期没有写些什么关于JavaScript的东西了,是时候让我们回到正轨了。</p>
<p>这一次,我们将会来探讨一下 errors 和 stack traces,并且熟练的掌握它们。</p>
<p>有些时候人们的确不太注意这些细节,但是这些细节知识在当你写一个库,并且需要测试和调错时会非常有用。举个例子,这周在 Chai 时,我们有一个很棒的pull-request,关于如何提升我们在堆栈追踪的处理能力上,从而能够使我们的用户能够在 assert 测试失败时,能够获得更多的信息。</p>
<p>熟练的操控堆栈追踪能偶让你清理掉一些不必要的干扰信息,从而能够关注于真正的问题上。此外,当你理解什么是错误及其属性,你会感到更有信心利用它。</p>
<p><strong>这篇博文在开头可能看起来太浅显了,但是当我们开始操作堆栈追踪时,它变得相当复杂,因此在我们进入那个章节之前,请确保您对以前的内容有了很好的理解。</strong></p>
<h3 id="调用堆栈是如何工作的"><a href="#调用堆栈是如何工作的" class="headerlink" title="调用堆栈是如何工作的"></a>调用堆栈是如何工作的</h3><p>在我们讨论errors之前,我们必须理解调用堆栈是如何工作的。(的确)这很单调,不过在深入之前理解这些是很有必要的。如果你已经知道了这些,请随意跳过这节。</p>
<p><strong>当一个方法被调用时,它会被push到栈顶。在它执行完成后,它会从栈顶被移除。</strong></p>
<p>这种数据结构有趣的地方在于 <strong>最后进来的元素会最先出去</strong>。同样这被称作 LIFO (后入先出) 原则。</p>
<p>给你看另一个例子,假设你有如下代码:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'c'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">b</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'b'</span>);</span><br><span class="line"> <span class="title function_">c</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'a'</span>);</span><br><span class="line"> <span class="title function_">b</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">a</span>();</span><br></pre></td></tr></table></figure>
<p>在上面的例子中,当运行方法a时,它被添加到我们栈的顶部。然后,当方法 b 在方法 a 内被调用时,它也被添加到了栈顶。同样的事也发生在方法 c 在方法 b 内被调用时。</p>
<p>当运行方法 c 时,我们的堆栈追踪内顺序包含 a, b, c 三个方法。</p>
<p>一旦方法 c 结束运行,它从栈顶被移除,控制权重新交回给方法 b 。当方法 b 完成时,它也从栈顶被移除,现在控制权被交回到了方法 a 手中。最终,当方法 a 结束运行后,它同样也从栈顶被移除。</p>
<p>为了更好的演示这些行为,我们将会使用<code>console.trace()</code>方法。它能够在控制台种将当前的堆栈信息打印出来。同样,你应该从上到下来阅读这些信息。仔细想想下面每一行代码被调用时都发生了什么。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'c'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">trace</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">b</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'b'</span>);</span><br><span class="line"> <span class="title function_">c</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'a'</span>);</span><br><span class="line"> <span class="title function_">b</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">a</span>();</span><br></pre></td></tr></table></figure>
<p>当代码在 node REPL 运行时,我们得到下面一些信息。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Trace</span></span><br><span class="line"> at c (<span class="attr">repl</span>:<span class="number">3</span>:<span class="number">9</span>)</span><br><span class="line"> at b (<span class="attr">repl</span>:<span class="number">3</span>:<span class="number">1</span>)</span><br><span class="line"> at a (<span class="attr">repl</span>:<span class="number">3</span>:<span class="number">1</span>)</span><br><span class="line"> at <span class="attr">repl</span>:<span class="number">1</span>:<span class="number">1</span> <span class="comment">// <-- 这个指针下面的东西都是Nodejs的内部实现,无视就好</span></span><br><span class="line"> at realRunInThisContextScript (vm.<span class="property">js</span>:<span class="number">22</span>:<span class="number">35</span>)</span><br><span class="line"> at sigintHandlersWrap (vm.<span class="property">js</span>:<span class="number">98</span>:<span class="number">12</span>)</span><br><span class="line"> at <span class="title class_">ContextifyScript</span>.<span class="property">Script</span>.<span class="property">runInThisContext</span> (vm.<span class="property">js</span>:<span class="number">24</span>:<span class="number">12</span>)</span><br><span class="line"> at <span class="title class_">REPLServer</span>.<span class="property">defaultEval</span> (repl.<span class="property">js</span>:<span class="number">313</span>:<span class="number">29</span>)</span><br><span class="line"> at bound (domain.<span class="property">js</span>:<span class="number">280</span>:<span class="number">14</span>)</span><br><span class="line"> at <span class="title class_">REPLServer</span>.<span class="property">runBound</span> [<span class="keyword">as</span> <span class="built_in">eval</span>] (domain.<span class="property">js</span>:<span class="number">293</span>:<span class="number">12</span>)</span><br></pre></td></tr></table></figure>
<p>简单的讲:你调用了一个东西,它被压入栈顶。当它完成了它就被弹出。就是这么简单。</p>
<h3 id="错误对象和错误处理"><a href="#错误对象和错误处理" class="headerlink" title="错误对象和错误处理"></a>错误对象和错误处理</h3><p>当错误发生时,通常一个 <code>Error</code> 对象被抛出。<code>Error</code> 对象同样也被当作原型来使用,来拓展或创建自己的错误。</p>
<p><code>Error.prototype</code> 对象通常包含下面属性:</p>
<ul>
<li><code>constructor</code> - 构造函数负责这个实例的原型。</li>
<li><code>message</code> - 一条错误信息。</li>
<li><code>name</code> - 错误的名称</li>
</ul>
<p>上述这些是标准的属性,有些时候不同的环境会有它们自己特定参数。在一些环境下,比如 Node, Firefox, Chrome, Edge, IE 10+, Opera 和 Safari 6+,我们甚至会有 <code>stack</code> 参数,它包含了一个错误的堆栈追踪信息。</p>
<p><strong>一个错误的堆栈追踪信息包含所有到它自身的结构函数为止的栈帧信息</strong>。</p>
<p>如果你希望了解更多的<code>Error</code>对象的参数,我非常推荐你去看看<a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/prototype">MDN上的这篇文章</a>.</p>
<p>为了抛出一个错误你必须使用<code>throw</code> 关键词。为了<code>catch</code> 一个被抛出的错误,你必须用<code>try catch</code>将那些可能会抛出错误的代码包裹起来。Catch 同样可以接收一个被抛出的错误作为参数。</p>
<p>如同在 java 种发生的一样, JavaScript 同样允许你在<code>try/catch</code>之后添加一个 <code>finally</code> 区块而不需要去关系 <code>try</code>区块内是否发生了错误。使用 <code>finally</code> 来做好一些善后工作,而不用关心你的操作是否正常工作。</p>
<p>到目前为止的所有东西对于大多数人而言都很基础,所有让我们来看一些不太注意的细节。(译者: indeed 😭)</p>
<p>你可以使用 <code>try</code>区块而不在后面带上 <code>catch</code>区块,但是这时必须带上 <code>finally</code>。这意味着你可以使用三种不同的try表达式结构:</p>
<ul>
<li><code>try...catch</code></li>
<li><code>try...finally</code></li>
<li><code>try...catch...finally</code></li>
</ul>
<p>Try表达式能够签到在其他的 <code>try</code> 表达式内,比如:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">'Nested error.'</span>); <span class="comment">// 这里抛出的错误会被他自身的catch子句所捕获</span></span><br><span class="line"> } <span class="keyword">catch</span> (nestedErr) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Nested catch'</span>); <span class="comment">// This runs</span></span><br><span class="line"> }</span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'This will not run.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你同样可以将 <code>try</code>嵌入 <code>catch</code> 和 <code>finally</code> 区块内:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">'First error'</span>);</span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'First catch running'</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">'Second error'</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (nestedErr) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Second catch running.'</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">try {</span><br><span class="line"> console.log('The try block is running...');</span><br><span class="line">} finally {</span><br><span class="line"> try {</span><br><span class="line"> throw new Error('Error inside finally.');</span><br><span class="line"> } catch (err) {</span><br><span class="line"> console.log('Caught an error inside the finally block.');</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>同样重要的是,<strong>你要知道throw 同样可以抛出非 <code>Error</code> 对象</strong>。尽管这看起来很cool,但是实际上真的不好,特别是那些需要在开发时使用其他库的开发者们,他们不得不去处理别人的代码,因为这之前并没有标准,你永远不会知道用户会给你什么东西。你不能信任他们而单纯的只是抛出一个<code>Error</code> 对象,因为他们可能选择不这么做,取而代之,而是抛出一个字符串或者数字。这使得你在处理堆栈追踪和其他一些有价值的元数据时变得困难。</p>
<p>假设你有如下代码:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">runWithoutThrowing</span>(<span class="params">func</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="title function_">func</span>();</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'There was an error, but I will not throw it.'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'The error\'s message was: '</span> + e.<span class="property">message</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">funcThatThrowsError</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">'I am a TypeError.'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">runWithoutThrowing</span>(funcThatThrowsError);</span><br></pre></td></tr></table></figure>
<p>当使用者传递一个含有错误抛出的方法到你的 <code>runWithoutThrowing</code>函数时,一切都正常工作。但是如果他们抛了一个 <code>String</code> 给你时,那你就有麻烦了:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">runWithoutThrowing</span>(<span class="params">func</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="title function_">func</span>();</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'There was an error, but I will not throw it.'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'The error\'s message was: '</span> + e.<span class="property">message</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">funcThatThrowsString</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="string">'I am a String.'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">runWithoutThrowing</span>(funcThatThrowsString);</span><br></pre></td></tr></table></figure>
<p>现在你的第二行 <code>console.log</code> 将告诉你 error 的 message 是 <code>undefined</code> 。这看起来在当前似乎不是很重要,不过如果你需要确认<code>Error</code> 对象内存在的一个特定的属性后者需要从用一种方法上处理 <code>Error </code> 特定属性时(比如 <a target="_blank" rel="noopener" href="https://github.com/chaijs/chai/blob/a7e1200db4c144263599e5dd7a3f7d1893467160/lib/chai/core/assertions.js#L1506">Chai’s<code>throws</code> 断言文档</a>),你需要做更多的工作。</p>
<p>同样的,当抛出值不是 <code>Error</code> 对象时,你不需要去访问其他重要的数据,比如它的<code>stack</code>,一个在一些环境中 <code>Error</code> 对象所包含的字段。</p>
<p>错误同样可以被当作其他(一般)的对象来使用,你并不一定要把他们抛出。这就是为什么它们经常被当初回调函数的第一个参数的原因。比如,在 <code>fs.readdir</code> 方法种:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">readdir</span>(<span class="string">'/example/i-do-not-exist'</span>, <span class="keyword">function</span> <span class="title function_">callback</span>(<span class="params">err, dirs</span>) {</span><br><span class="line"> <span class="keyword">if</span> (err <span class="keyword">instanceof</span> <span class="title class_">Error</span>) {</span><br><span class="line"> <span class="comment">// `readdir` 将会抛出一个错误,因为这个文件根本不存在 </span></span><br><span class="line"> <span class="comment">// 现在我们能够使用回调函数中的错误对象了</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Error Message:'</span> + err.<span class="property">message</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'See? We can use Errors without using try statements.'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(dirs);</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>最后但并非不重要, <code>Error</code> 对象在 promise reject 时被使用。这使得控制promise的rejections变得容易:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) {</span><br><span class="line"> <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">'The promise was rejected.'</span>));</span><br><span class="line">}).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'I am an error.'</span>);</span><br><span class="line">}).<span class="title function_">catch</span>(<span class="keyword">function</span>(<span class="params">err</span>) {</span><br><span class="line"> <span class="keyword">if</span> (err <span class="keyword">instanceof</span> <span class="title class_">Error</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'The promise was rejected with an error.'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Error Message:'</span> + err.<span class="property">message</span>);</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<h3 id="操作堆栈追踪"><a href="#操作堆栈追踪" class="headerlink" title="操作堆栈追踪"></a>操作堆栈追踪</h3><p>现在就是你所期待的部分了:如何去操作堆栈追踪信息。</p>
<p>这个章节只针对一些支持 <code>Error.captureStackTrace</code> 的特殊环境,比如 NodeJS。</p>
<p>这个 <code>Error.captureStackTrace</code> 方法将一个 <code>object</code> 作为它的一个参数,一个可选的 <code>function</code> 作为它的第二个参数。这个 captureStackTrace 做的呢就是捕获当前的堆栈信息(废话)并且在一个大的对象中创建一个 <code>stack</code> 参数来保存它。如果提供了第二个参数,这个被传递的方法将会被认为是调用堆栈的重点。因此堆栈跟踪将仅显示在调用此函数之前发生的调用。</p>
<p>让我们给一些例子来让这一切变得更清晰。首先,我们将会捕获当前的堆栈信息,并且将它保存在一个普通的对象中。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myObj = {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) {</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">b</span>(<span class="params"></span>) { </span><br><span class="line"> <span class="comment">// 这里将会讲当前的堆栈信息储存到 myObj 中</span></span><br><span class="line"> <span class="title class_">Error</span>.<span class="title function_">captureStackTrace</span>(myObj);</span><br><span class="line"> <span class="title function_">c</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">b</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 首先我们会调用这些方法</span></span><br><span class="line"><span class="title function_">a</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在让我们看看什么堆栈信息被存入了 myObj.stack</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(myObj.<span class="property">stack</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这将会在控制台中打印出如下信息:</span></span><br><span class="line"><span class="comment">// at b (repl:3:7) <-- 因为它在B内被调用,所以B是堆栈中的最后一个条目</span></span><br><span class="line"><span class="comment">// at a (repl:2:1)</span></span><br><span class="line"><span class="comment">// at repl:1:1 <-- 下面是 node 的内部实现</span></span><br><span class="line"><span class="comment">// at realRunInThisContextScript (vm.js:22:35)</span></span><br><span class="line"><span class="comment">// at sigintHandlersWrap (vm.js:98:12)</span></span><br><span class="line"><span class="comment">// at ContextifyScript.Script.runInThisContext (vm.js:24:12)</span></span><br><span class="line"><span class="comment">// at REPLServer.defaultEval (repl.js:313:29)</span></span><br><span class="line"><span class="comment">// at bound (domain.js:280:14)</span></span><br><span class="line"><span class="comment">// at REPLServer.runBound [as eval] (domain.js:293:12)</span></span><br><span class="line"><span class="comment">// at REPLServer.onLine (repl.js:513:10)</span></span><br></pre></td></tr></table></figure>
<p>正如你在上述例子中看到的,我们首先调用了 <code>a</code> (被压入了栈内)然后在 <code>a</code> 内调用了 <code>b</code> (被 push 在 <code>a</code> 上面)。然后,在 <code>b</code> 内,我们捕获到了当前的堆栈信息,并且存入了 <code>myObj</code>。 这就是为什么我们在控制台中只获得了 <code>a</code> 和 <code>b</code>。 </p>
<p>现在,让我们传递一个方法作为第二个参数给<code>Error.captureStackTrace</code> 方法,来看会发生什么:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myObj = {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">d</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// 这里我们将会储存当前的堆栈信息到 myObj 中</span></span><br><span class="line"> <span class="comment">// 这一次我么将会隐藏 `b` 之后以及它自身的栈帧 </span></span><br><span class="line"> <span class="title class_">Error</span>.<span class="title function_">captureStackTrace</span>(myObj, b);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">d</span>(); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">b</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">c</span>(); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">b</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 首先我们会调用这些方法</span></span><br><span class="line"><span class="title function_">a</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在让我们看看什么堆栈信息被存入了 myObj.stack</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(myObj.<span class="property">stack</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这将会在控制台中打印出如下信息:</span></span><br><span class="line"><span class="comment">// at a (repl:2:1) <-- 如你所见在这里我们只能获得 `b` 之前的被调用的栈帧</span></span><br><span class="line"><span class="comment">// at repl:1:1 <-- 下面是 node 的内部实现</span></span><br><span class="line"><span class="comment">// at realRunInThisContextScript (vm.js:22:35)</span></span><br><span class="line"><span class="comment">// at sigintHandlersWrap (vm.js:98:12)</span></span><br><span class="line"><span class="comment">// at ContextifyScript.Script.runInThisContext (vm.js:24:12)</span></span><br><span class="line"><span class="comment">// at REPLServer.defaultEval (repl.js:313:29)</span></span><br><span class="line"><span class="comment">// at bound (domain.js:280:14)</span></span><br><span class="line"><span class="comment">// at REPLServer.runBound [as eval] (domain.js:293:12)</span></span><br><span class="line"><span class="comment">// at REPLServer.onLine (repl.js:513:10)</span></span><br><span class="line"><span class="comment">// at emitOne (events.js:101:20)</span></span><br></pre></td></tr></table></figure>
<p>当我们传递 <code>b</code> 给<code>Error.captureStackTrace</code> 函数时,它隐藏了 <code>b</code> 本身以及在它之上的所有栈帧。这就是为什么我们在堆栈追踪中只看到了<code>a</code>。</p>
<p>现在你或许会问你自己: “为什么这东西有用?”。这个东西在当你试图对非你的用户隐藏内部实现细节时非常有用。在 Chai 内,举个例子, 们使用它来避免向我们的用户显示与我们实现检查和断言自身的方式无关的细节。</p>
<h3 id="真实环境中的堆栈追踪操作"><a href="#真实环境中的堆栈追踪操作" class="headerlink" title="真实环境中的堆栈追踪操作"></a>真实环境中的堆栈追踪操作</h3><p>正如我在上一个小节提到的,Chai 使用堆栈操作技术来使得堆栈追踪与我们的用户(的操作)更加关联。下面是我们如何做的。</p>
<p>首先,让我们看一看当断言失败时, <code>AssertionError</code> 构造函数会抛出什么:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// `ssfi` 代表 “start stack function”. 它指向堆栈追踪中删除不相关帧的起点</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">AssertionError</span> (message, _props, ssf) {</span><br><span class="line"> <span class="keyword">var</span> extend = <span class="title function_">exclude</span>(<span class="string">'name'</span>, <span class="string">'message'</span>, <span class="string">'stack'</span>, <span class="string">'constructor'</span>, <span class="string">'toJSON'</span>)</span><br><span class="line"> , props = <span class="title function_">extend</span>(_props || {});</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 默认值</span></span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">message</span> = message || <span class="string">'Unspecified AssertionError'</span>;</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">showDiff</span> = <span class="literal">false</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 从参数中拷贝</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> props) {</span><br><span class="line"> <span class="variable language_">this</span>[key] = props[key];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这里就是与我们相关的部分:</span></span><br><span class="line"> <span class="comment">// 如果一个start stack function 被提供了,我们捕获了当前堆栈的追踪信息,并且将其传递给了 `captureStackTrace` 方法,那样我们移除在这个之后的栈帧了。</span></span><br><span class="line"> ssf = ssf || <span class="variable language_">arguments</span>.<span class="property">callee</span>;</span><br><span class="line"> <span class="keyword">if</span> (ssf && <span class="title class_">Error</span>.<span class="property">captureStackTrace</span>) {</span><br><span class="line"> <span class="title class_">Error</span>.<span class="title function_">captureStackTrace</span>(<span class="variable language_">this</span>, ssf);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果没有提供 start stack function 我们就用原来的 stack 属性。</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>();</span><br><span class="line"> } <span class="keyword">catch</span>(e) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">stack</span> = e.<span class="property">stack</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如你所见,在上面的代码中我们使用 <code>Error.captureStackTrace</code> 来捕获堆栈信息,并且将其储存在我们所生成的 <code>AssertionError</code> 实例中,(当它存在时)我们传递了一个 start stack function 给它来将不相干的栈帧从栈列内移除。这些仅仅展示了Chai的内部实现细节并且在最后污染了栈列。</p>
<p>现在让我们看看现在由 <a target="_blank" rel="noopener" href="https://github.com/meeber">@meeber</a>在 <a target="_blank" rel="noopener" href="https://github.com/chaijs/chai/pull/922">这个碉堡的PR内</a>的代码是怎么写的.</p>
<p>在我们看下面的代码之前,我必须告诉你 <code>addChainableMethod</code> 方法做了什么。它将传递给它的可链接方法添加到断言,并且还使用包含断言的方法标记断言本身。它以 <code>ssfi</code> 作为名称保存(代表了起始栈方法指示器)。这基本上意味着当前断言将是堆栈中的最后一帧,因此我们不会在堆栈中显示Chai中的任何进一步的内部方法。我避免添加整个代码,因为它有很多东西,而且有点棘手,但如果你想读它,<a target="_blank" rel="noopener" href="https://github.com/meeber/chai/blob/42ff3c012b8a5978e7381b17d712521299ced341/lib/chai/utils/addChainableMethod.js">这里是它的链接</a>.。</p>
<p>在下面的代码中,我们有一个 <code>lengthOf</code> 断言的逻辑,它检查对象是否具有一个明确的 <code>长度</code>。我们希望我们的用户像这么用它:<code>expect(['foo', 'bar']).to.have.lengthOf(2)</code></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">assertLength</span> (n, msg) {</span><br><span class="line"> <span class="keyword">if</span> (msg) <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'message'</span>, msg);</span><br><span class="line"> <span class="keyword">var</span> obj = <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'object'</span>)</span><br><span class="line"> , ssfi = <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'ssfi'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 注意这一行</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Assertion</span>(obj, msg, ssfi, <span class="literal">true</span>).<span class="property">to</span>.<span class="property">have</span>.<span class="title function_">property</span>(<span class="string">'length'</span>);</span><br><span class="line"> <span class="keyword">var</span> len = obj.<span class="property">length</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这一行也同样相关</span></span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">assert</span>(</span><br><span class="line"> len == n</span><br><span class="line"> , <span class="string">'expected #{this} to have a length of #{exp} but got #{act}'</span></span><br><span class="line"> , <span class="string">'expected #{this} to not have a length of #{act}'</span></span><br><span class="line"> , n</span><br><span class="line"> , len</span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title class_">Assertion</span>.<span class="title function_">addChainableMethod</span>(<span class="string">'lengthOf'</span>, assertLength, assertLengthChain);</span><br></pre></td></tr></table></figure>
<p>在上面的代码中,我突出强调了与我们现在相关的代码段。我们先来看看 <code>this.assert</code> 的调用。</p>
<p>下面是 <code>this.assert</code> 方法的代码:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Assertion</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">assert</span> = <span class="keyword">function</span> (<span class="params">expr, msg, negateMsg, expected, _actual, showDiff</span>) {</span><br><span class="line"> <span class="keyword">var</span> ok = util.<span class="title function_">test</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>);</span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">false</span> !== showDiff) showDiff = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">undefined</span> === expected && <span class="literal">undefined</span> === _actual) showDiff = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">true</span> !== config.<span class="property">showDiff</span>) showDiff = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!ok) {</span><br><span class="line"> msg = util.<span class="title function_">getMessage</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>);</span><br><span class="line"> <span class="keyword">var</span> actual = util.<span class="title function_">getActual</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里是我们所要关注的行</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AssertionError</span>(msg, {</span><br><span class="line"> <span class="attr">actual</span>: actual</span><br><span class="line"> , <span class="attr">expected</span>: expected</span><br><span class="line"> , <span class="attr">showDiff</span>: showDiff</span><br><span class="line"> }, (config.<span class="property">includeStack</span>) ? <span class="variable language_">this</span>.<span class="property">assert</span> : <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'ssfi'</span>));</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>基本上,<code>assert</code>方法负责检查是否通过了布尔表达式的断言。如果没有,我们必须实例化一个<code>AssertionError</code>。请注意,当实例化这个新的<code>AssertionError</code>时,我们也向其传递一个堆栈跟踪功能指示符(<code>ssfi</code>)。如果配置标志<code>includeStack</code>被打开,我们通过将<code>this.assert</code>本身传递给它来显示整个堆栈跟踪,这真的是堆栈中的最后一帧。但是,如果<code>includeStack</code>配置标志被启用,我们必须从堆栈跟踪中隐藏更多的内部实现细节,所以我们使用什么存储到<code>ssfi</code>标志。</p>
<p>现在,我们来谈谈另一个相关的行:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Assertion</span>(obj, msg, ssfi, <span class="literal">true</span>).<span class="property">to</span>.<span class="property">have</span>.<span class="title function_">property</span>(<span class="string">'length'</span>);</span><br></pre></td></tr></table></figure>
<p>正如你可以看到的,我们在创建我们的嵌套断言时传递了我们从<code>ssfi</code>标志获得的内容。这意味着当创建新的断言时,它将使用此函数作为从堆栈跟踪中删除无用框架的起点。顺便说一下,这是<code>Assertion</code>构造函数:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Assertion</span> (obj, msg, ssfi, lockSsfi) {</span><br><span class="line"> <span class="comment">// This is the line that matters to us</span></span><br><span class="line"> <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'ssfi'</span>, ssfi || <span class="title class_">Assertion</span>);</span><br><span class="line"> <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'lockSsfi'</span>, lockSsfi);</span><br><span class="line"> <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'object'</span>, obj);</span><br><span class="line"> <span class="title function_">flag</span>(<span class="variable language_">this</span>, <span class="string">'message'</span>, msg);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> util.<span class="title function_">proxify</span>(<span class="variable language_">this</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你可以记住从我对<code>addChainableMethod</code>的说法,它设置<code>ssfi</code>标志与自己的包装方法,这意味着这是堆栈跟踪中最低的内部帧,所以我们可以删除所有上面的帧。</p>
<p>通过将<code>ssfi</code>传递给嵌套断言,它只检查我们的对象是否具有属性长度,我们避免重置我们将用作起点指示符的帧,然后在堆栈中使得之前的<code>addChainableMethod</code>保持可见。</p>
<p>这可能看起来有点复杂,所以让我们回顾一下Chai发生的事情,我们想从堆栈中删除无用的帧:</p>
<ol>
<li>当我们运行断言时,我们设置自己的方法作为删除堆栈中的下一个帧的参考</li>
<li>断言运行,如果它失败,我们删除我们存储的引用后的所有内部帧</li>
<li>如果我们有嵌套断言,我们仍然必须使用当前的断言包装方法作为删除堆栈中的下一个帧的参考点,所以我们将当前的<code>ssfi</code>(启动堆栈函数指示符)传递给我们正在创建的断言,以便它可以保留它</li>
</ol>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2017/02/13/React%20Fiber%20Architecture/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2017/02/13/React%20Fiber%20Architecture/" class="post-title-link" itemprop="url">【翻译】React Fiber架构</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2017-02-13 12:21:33" itemprop="dateCreated datePublished" datetime="2017-02-13T12:21:33+08:00">2017-02-13</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2023-02-02 04:30:17" itemprop="dateModified" datetime="2023-02-02T04:30:17+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>原文链接: <a target="_blank" rel="noopener" href="https://github.com/acdlite/react-fiber-architecture">https://github.com/acdlite/react-fiber-architecture</a></p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>React Fiber 是一个正在进行的的React核心算法的重新实现,它是过去两年react团队的研究的顶峰。</p>
<p>React Fiber 的目标是为了增强react在动画,布局和手势等领域的适应性,它的头号特性就是增量渲染:一种将渲染任务切割成多个小块并分布到复数个帧中。</p>
<p>其他的关键特性包括,在新的更新来临时,暂停,退出和复用任务,为不同类型的更新设置优先级,和新的并发原函数。</p>
<h3 id="关于本文档"><a href="#关于本文档" class="headerlink" title="关于本文档"></a>关于本文档</h3><p>Fiber 引入了几个新的概念,它们并不能简单的通过代码来理解。这篇文档最初是我在跟进react项目的Fiber实现时整理的一些笔记引入,随着积累,我意识到它对其他人或许会是有用的资源。</p>
<p>我尝试着用简单易懂的语言来讲述,并通过界定术语来避免行话。如果可能的话,我也会引用大量的外部链接。</p>
<p>请注意我并不在react团队,而且并不能代表官方发言。这并不是一片官方文档,不过我请求了react团队的一些人来验证了文章的准确性。</p>
<p>要记住,这是一个正在进行中的工作。Fiber是一个进行中的项目,在它完成之前可能会颠覆性的重构。同样的,进行中,也是我为这篇文档定义的理念。任何改进或者建议都非常欢迎。</p>
<p>我的目标是在你读完了这篇文档后,你会对Fiber足够认知去理解<a target="_blank" rel="noopener" href="https://github.com/facebook/react/commits/master/src/renderers/shared/fiber">它的实现</a>,并且最终能够反哺给react。</p>
<h3 id="必要的知识"><a href="#必要的知识" class="headerlink" title="必要的知识"></a>必要的知识</h3><p>我强烈建议你先熟悉下面的资源来继续你的阅读:</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://facebook.github.io/react/blog/2015/12/18/react-components-elements-and-instances.html">React Components, Elements, and Instances</a> - “组件”是一个经常提起的术语,对其有一个坚实的认知是至关重要的。</li>
<li><a target="_blank" rel="noopener" href="https://facebook.github.io/react/docs/reconciliation.html">Reconciliation</a> - React 协调算法的高阶阐述</li>
<li><a target="_blank" rel="noopener" href="https://github.com/reactjs/react-basic">React Basic Theoretical Concepts</a> - 不带实现细节的react概念模型的说明。部分内容或许不会再阅读的初期产生意义。但是没关系,它会随着进展而变得更有价值。</li>
<li><a target="_blank" rel="noopener" href="https://facebook.github.io/react/contributing/design-principles.html">React Design Principles</a> - 特别注意文中的“调度“部分,它花了很大的功夫来阐述为什么要有React Fiber。</li>
</ul>
<h2 id="回顾"><a href="#回顾" class="headerlink" title="回顾"></a>回顾</h2><p>请在继续前确认你以对上述知识有所了解。</p>
<p>在我们深入到新的内容前,让我们先回顾一些概念。</p>
<h3 id="什么是协调算法?"><a href="#什么是协调算法?" class="headerlink" title="什么是协调算法?"></a>什么是协调算法?</h3><h4 id="协调算法"><a href="#协调算法" class="headerlink" title="协调算法"></a>协调算法</h4><p> 一个在react中比较一棵树与另一棵树来找出哪些部分需要被改变的算法。</p>
<h4 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h4><p> 渲染React应用时发生的数据变化。通常是“setState”引发的。最终的结果就是重新渲染。</p>
<p>React API 的核心思想是让更新能够触发整个应用的重新渲染。它允许开发者进行声明式的开发,而不用担心应用在一个状态转移到另一个状态时的性能表现(A到B, B到C, C到A,等等)。</p>
<p>事实上,重新渲染整个应用只适用于一些小型的应用。在现实中的应用,(重新渲染)代价是高昂的。React在这基础上做了一系列优化,使其在对整个页面重新渲染时依然能够保证很好的性能。这些优化都是 <strong>协调算法</strong> 的一部分。</p>
<p>协调算法是一种隐藏在一个被广泛认知的概念“虚拟DOM”之下的算法。一个高阶的阐述如下:当你渲染一个react应用时,一棵用于描述整个应用的树被生成并保存在内存中。然后这棵树被刷新到正式的渲染环境中 - 举个例子,在浏览器应用中,它被转成一系列的DOM操作。当应用更新时(通常是通过 setState),一棵新的树被生成。这棵新的树与之前的树的差异决定了使用哪些更新操作来重新渲染整个应用。</p>
<p>尽管Fiber是一个协调算法的推倒重写,但是它与<a target="_blank" rel="noopener" href="https://facebook.github.io/react/docs/reconciliation.html">React官方文档中描述的高阶算法</a>大致相同。其关键点在于:</p>
<ul>
<li>不同的组件类型被用以生成不同的树。比起比较两者的区别,react采用了替换掉整个旧树。</li>
<li>通过key来进行diff比较。key必须“稳定,可预测且独一无二”。</li>
</ul>
<h3 id="协调算法vs渲染"><a href="#协调算法vs渲染" class="headerlink" title="协调算法vs渲染"></a>协调算法vs渲染</h3><p>DOM只是React可以采用的一种渲染环境,其他的主要环境还有原生的IOS 和 android 视图,通过React Native.(这既是为什么“虚拟DOM”其实有一点用词不当)。</p>
<p>React支持那么多渲染环境的原因是React在设计时就将协调算法和渲染拆分成了不同的部分。协调算法用以计算一棵树的哪些部分被改变了。渲染器使用这些信息来实际更新应用。</p>
<p>这个分离意味着 React DOM 和 React Native 能使用它们各自的渲染器的前提下共享React核心提供的协调算法,</p>
<p>Fiber重新实现了协调器。渲染并不是Fiber需要考虑的,不过渲染器需要调整以适应新的架构。</p>
<h3 id="调度"><a href="#调度" class="headerlink" title="调度"></a>调度</h3><h4 id="调度-1"><a href="#调度-1" class="headerlink" title="调度"></a>调度</h4><p> 决定任务什么时候被执行的过程</p>
<h4 id="任务"><a href="#任务" class="headerlink" title="任务"></a>任务</h4><p> 任何计算必须被执行。”任务”通常是更新的结果(如 <code>setState</code>)</p>
<p>React 的<a target="_blank" rel="noopener" href="https://facebook.github.io/react/contributing/design-principles.html#scheduling">设计原则</a>中对于这部分讲的十分不错,我贴在这里了:</p>
<blockquote>
<p>在当前的实现中,react在一个tick中历遍并调用了整棵树的渲染函数。但在未来,它有可能会延迟部分更新来避免丢帧。</p>
<p>这是react设计的一个常见的主题。一些流行的库在实现时采用了一种”push“的方法,当新的数据准备好时触发执行运算。然而,React依然使用了”pull”的方式,计算可以被延迟到必须执行的时候。</p>
<p>React不是一个通用的数据处理库,而是一个构建用户交互界面应用的库。我们认为(在交互界面应用中)知道哪些东西该立即关联,哪些则不必是有着独一无二的地位的。</p>
<p>如果有些东西超出了屏幕,我们可以延迟相关逻辑的执行。如果数据来的比帧绘制快,我们可以合并数据并批量更新。我们可以优先处理来自用户的交互(比如按钮点击出发的动画),而那些不是非常重要的后台任务(比如渲染来自网络的新加载的内容)来避免掉帧。</p>
</blockquote>
<p>关键的点如下:</p>
<ul>
<li>在一个UI界面中,不是每一次更新都有必要立即执行。事实上,这么做很浪费资源,而且会导致丢帧和降低用户体现。</li>
<li>不同类型的更新有着不同的优先级- 一个动画的更新需要在数据源的更新前完成。</li>
<li>一个基于push的方案需要应用(你,敲代码的)来决定如何调度任务。但是一个基于pull的方案允许框架(react)更为智能的来为你做这些决定。</li>
</ul>
<p>当前React 并没有明显的利用调度: 一个更新会导致整棵树被立即重绘。利用调度来重写React核心算法是Fiber背后的驱动理念。</p>
<hr>
<p>现在我们已经准备好深入Fiber的实现了。下一章节将比我们讨论到现在所讲的内容更加具有技术性。在继续前,请确认你已经理解上面的材料。</p>
<h2 id="什么是Fiber?"><a href="#什么是Fiber?" class="headerlink" title="什么是Fiber?"></a>什么是Fiber?</h2><p>我们将讨论React Fiber架构的核心。纤维(Fibers)是一种比应用开发者想象中还要低阶的抽象。如果你在尝试去理解时出现了困惑,不要灰心。继续下去,最终你会明白的。(当你最终明白了,请提一些意见来优化这个章节)。</p>
<p>现在让我们开始!</p>
<hr>
<p>利用调度是React Fiber的一个即认目标。具体来说,我们需要有能力做到:</p>
<ul>
<li>暂停任务,并在之后恢复。</li>
<li>为不同类型的任务指派优先级。</li>
<li>复用之前完成了的任务。</li>
<li>在任务不再需要时放弃任务。</li>
</ul>
<p>为了做到当中的任意一点,我们首先需要一个方法将任务拆分成单元。从某种第一种,这就是Fiber。一个纤维代表了任务的一个单元。</p>
<p>为了更进一步理解,让我们回到<a target="_blank" rel="noopener" href="https://github.com/reactjs/react-basic#transformation">React组件是一个包含数据方法</a>的概念,通常表示为</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v = <span class="title function_">f</span>(d)</span><br></pre></td></tr></table></figure>
<p>它遵循以下规则,渲染一个React应用类似于调用一个主体包含其它函数的函数。这个比喻在我们思考fiber时很有用。</p>
<p>计算机通常跟踪程序执行的方式是使用<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Call_stack">调用堆栈</a>。当一个方法被执行时,一个新的栈帧被添加到栈顶。这个栈帧表示这个任务由这个函数执行。</p>
<p>当处理UI时,这个问题是如果一次性执行了太多的任务,会导致动画掉帧和页面卡顿。更糟的是,当中的一些工作最终会被更靠近的更新所替代,完全不是必须的。这就是菊粉UI组件和普通方法的分界线,因为组件比一般方法有更详细的关注点。</p>
<p>较新的浏览器(和React Native)通过实现一些接口来解决了这个问题。<code>requestIdleCallback</code> 调度了一个更低优先级的方法在空闲时调用,而 <code>requestAnimationFrame</code> 调度了一个更高优先级的方法在下一个动画帧执行。问题在于,为了使用这些接口,你需要一个方法去把任务切分为增量的任务。如果你依赖于调用栈,它将会继续执行执行栈被清空。</p>
<p>如果我们能自定义调用栈的行为来优化UI渲染会不会更好?如果我们可以随意中断调用栈并且可以手动调控栈帧会不会更好?</p>
<p>这就是React Fiber的设计动机。Fiber是栈的重新实现,特针对于React组件。你可以将一个fiber想象成一个虚拟栈帧。</p>
<p>重新实现堆栈的优点是,你可以<a target="_blank" rel="noopener" href="https://www.facebook.com/groups/2003630259862046/permalink/2054053404819731/">保持堆栈帧在内存中</a>,并执行它们(和任何时候)你想要的。这对于实现我们的计划目标至关重要。</p>
<p>除了调度,手动处理堆栈帧解锁了诸如并发和错误边界之类的功能的潜力。我们将在以后的章节中讨论这些主题。</p>
<p>在下一节中,我们将更多地了解fiber的结构。</p>
<h3 id="fiber的结构"><a href="#fiber的结构" class="headerlink" title="fiber的结构"></a>fiber的结构</h3><p><em>注意:当我们更具体地了解实现细节时,一些东西可能会已经随着时间被改变了。如果您发现任何错误或过时的信息,请提交公关。</em></p>
<p>具体来说,fiber是一个JavaScript对象,它包括组件本身以及其输入及其输出的信息。</p>
<p>Fiber对应一个栈帧,但是同样也对应一个组件的实例。</p>
<p>下面是一些属于fiber的重要字段。(这个列表并不完整)。</p>
<h4 id="type和key"><a href="#type和key" class="headerlink" title="type和key"></a><code>type</code>和<code>key</code></h4><p>fiber的<code>type</code>和<code>key</code>的作用和React元素一样。(实际上,一个fiber从组件创建时,这两个字段会直接复制过来)</p>
<p>fiber的 <code>type</code>字段描述了它对应的组件。对复合组件这个类型就是函数组件或类组件本身。对于原生组件(div, span,等等),这个字段就是一个字符串。</p>
<p>概念上,type是在执行时会被堆栈帧跟踪到的函数(如在v = f(d)中)。</p>
<p>除了<code>type</code>之外,<code>key</code>是在协调算法中用来决定fiber是否可以重用的字段。</p>
<h4 id="child和sibling"><a href="#child和sibling" class="headerlink" title="child和sibling"></a><code>child</code>和<code>sibling</code></h4><p>这些字段指向别的fiber,描述了fiber的历遍树的结构。</p>
<p>子fiber指的是组件render方法的返回值。所以,在下面的例子中</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Parent</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="language-xml"><span class="tag"><<span class="name">Child</span> /></span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Parent 对应的子Fiber就是Child组件。</p>
<p><code>sibling</code>字段指代着render方法返回多个子元素的情况(fiber的一个新的特性!):</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Parent</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> [<span class="language-xml"><span class="tag"><<span class="name">Child1</span> /></span></span>, <span class="language-xml"><span class="tag"><<span class="name">Child2</span> /></span></span>]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>子fiber们组成了一个首元素是第一个子元素的单向链表。所以在例子中,Parent的子元素是Child1,child1的兄弟元素是Child2。</p>
<p>回顾我们之前的函数类比,你可以把子fiber当作一个<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Tail_call">尾调用函数</a>。</p>
<h4 id="return"><a href="#return" class="headerlink" title="return "></a><code>return </code></h4><p>返回fiber是程序处理完当前fiber时返回的fiber。概念上来说,它和栈帧返回的地址相同。它同样可以被认为是父fiber。</p>
<p>如果一个fiber包含多个子fiber,每一个子fiber的return fiber 都是它的父fiber。所以在我们先前一节的例子中,Child1和Child2的return fiber 就是Parent。</p>
<h4 id="pendingProps和memoizedProps"><a href="#pendingProps和memoizedProps" class="headerlink" title="pendingProps和memoizedProps"></a><code>pendingProps</code>和<code>memoizedProps</code></h4><p>概念上来说,props是函数的参数。一个Fiber的 <code>pendingProps</code>在他执行前就被设定好,而<code>memoizedProps</code>则会在尾部被设置。</p>
<p>当传入的<code>pendingProps</code>和<code>memoizedProps</code>相同时,这传递了一个信号这个fiber的的输出可以被复用,从而避免了不必要的工作。</p>
<h4 id="pendingWorkPriority"><a href="#pendingWorkPriority" class="headerlink" title="pendingWorkPriority"></a><code>pendingWorkPriority</code></h4><p>一个数字代表了fiber的执行优先级。 <a target="_blank" rel="noopener" href="https://github.com/facebook/react/blob/master/src/renderers/shared/fiber/ReactPriorityLevel.js">React优先级</a> 模块列举了不同的优先级和它们代表的意义。</p>
<p>除了例外的<code>NoWork</code>的值是0,值越大代表优先级越低。比如,你可以使用下述的方法来确认一个fiber的优先级是不是不低于给定的等级:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">matchesPriority</span>(<span class="params">fiber, priority</span>) {</span><br><span class="line"> <span class="keyword">return</span> fiber.<span class="property">pendingWorkPriority</span> !== <span class="number">0</span> &&</span><br><span class="line"> fiber.<span class="property">pendingWorkPriority</span> <= priority</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法仅供说明用,它并不是React Fiber源码的一部分。</p>
<p>调度器使用优先级字段来搜索要执行的下一个工作单元。这个算法将在以后的章节中讨论。</p>
<h4 id="alternate"><a href="#alternate" class="headerlink" title="alternate"></a><code>alternate</code></h4><h5 id="flush"><a href="#flush" class="headerlink" title="flush"></a>flush</h5><p> flush 一个fiber意味着将它打印输出到屏幕上。</p>
<h5 id="work-in-progress"><a href="#work-in-progress" class="headerlink" title="work-in-progress"></a>work-in-progress</h5><p> 一个fiber如果还没有完成,那么概念上,栈帧就尚未返回。</p>
<p>在任何时候,一个组件的实例,最多有两个fiber关联: 当前的fiber,flush fiber,和work-in-progress fiber。</p>
<p>当前fiber的替代(<code>alternate</code>)就是进行中的fiber,进行中的fiber的替代就是当前fiber。</p>
<p>一个fiber的替代是通过<code>cloneFiber</code>函数懒创建的。<code>cloneFiber</code>会尝试重用fiber的替代(如果存在)来最小化分配空间,而不是总创建新的对象。</p>
<p>你应当把<code>alternate</code>当做是一个实现细节,但它在代码里面出现了很多次,所以值得在这里讨论。</p>
<h4 id="output"><a href="#output" class="headerlink" title="output"></a><code>output</code></h4><h5 id="host-component"><a href="#host-component" class="headerlink" title="host component"></a><em>host component</em></h5><p> React应用的叶子节点。它们是跟特定的渲染环境相关的(比如,在浏览器应用中,宿主组件是指<code>div</code>, <code>span</code>等)。在JSX中,它们是用小写字母的tag名称表示的。</p>
<p>从概念上讲,fiber的output是一个函数的返回值。</p>
<p>每个fiber最终都会有output,不过output只在宿主组件的叶子节点上创建。这个输出会向上转移到整棵树。</p>
<p>output最终会递交给渲染器让其根据渲染环境来flush。定义输出结果怎么样创建和更新就是渲染器的职责了。</p>
<h2 id="未来的章节"><a href="#未来的章节" class="headerlink" title="未来的章节"></a>未来的章节</h2><p>目前为止就这些了,不过这篇文档远远没有完成,未来的章节将会描述更新周期时使用到的算法。相关的标题包含如下:</p>
<ul>
<li>调度器如何找到下一个需要被执行的单元</li>
<li>如何在整棵fiber树上最终和传递优先级</li>
<li>调度器怎么知道什么时候去暂停和恢复任务</li>
<li>任务如何被冲洗并标记成完成</li>
<li>副作用(如生命周期函数)是如何工作的</li>
<li>什么是协同程序,以及如何使用它来实现上下文和布局等功能。</li>
</ul>
<h2 id="相关视频"><a href="#相关视频" class="headerlink" title="相关视频"></a>相关视频</h2><ul>
<li><a target="_blank" rel="noopener" href="https://youtu.be/aV1271hd9ew">What’s Next for React (ReactNext 2016)</a></li>
</ul>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2017/01/31/Concurrency%20model%20and%20Event%20loop/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2017/01/31/Concurrency%20model%20and%20Event%20loop/" class="post-title-link" itemprop="url">【翻译】并发模型和事件循环</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2017-01-31 12:21:33" itemprop="dateCreated datePublished" datetime="2017-01-31T12:21:33+08:00">2017-01-31</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2023-02-02 04:30:17" itemprop="dateModified" datetime="2023-02-02T04:30:17+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<blockquote>
<p>原址:<a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop">https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop</a></p>
</blockquote>
<p>JavaScript 有一个基于“事件循环”的并发模型。这个模型和其他语言中(C 和 JAVA)中的近似模型有着很大区别。</p>
<h2 id="运行时的概念"><a href="#运行时的概念" class="headerlink" title="运行时的概念"></a>运行时的概念</h2><p>下面的内容描述了一个理论模型。现在 JavaScript 引擎大量的声明和优化了上述语义。</p>
<h3 id="视觉展示"><a href="#视觉展示" class="headerlink" title="视觉展示"></a>视觉展示</h3><p><img src="https://developer.mozilla.org/files/4617/default.svg" alt="Stack, heap, queue"></p>
<h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><p>函数的调用形成有一系列帧组成的栈。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params">b</span>) {</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"> <span class="keyword">return</span> a + b + <span class="number">11</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">bar</span>(<span class="params">x</span>) {</span><br><span class="line"> <span class="keyword">var</span> y = <span class="number">3</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">foo</span>(x * y);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">bar</span>(<span class="number">7</span>));</span><br></pre></td></tr></table></figure>
<p>当调用 bar 时,第一帧被产生用以保存 bar 函数的引用参数和局部变量。当 bar 调用 foo 时,第二帧被生成用以保存 foo 的引用参数和局部变量并入栈。当 foo 返回时,栈顶部的元素被抛出。当 bar 执行返回时,栈被清空。</p>
<h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><p>对象被分配在一个用于表示一个泛广的非结构化内存区域的堆中。</p>
<h3 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h3><p>JavaScript 在运行时会产生一个由一系列等待被处理的消息组成的消息队列。每个消息都与一个方法相关联。当栈有足够容量时,一条消息会被拿出队列并被处理。这处理的过程包含调用关联方法(因此会生成一个初始化帧栈)。当栈被重新执行清空后,消息处理结束。</p>
<h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>事件循环之所以得到这么个名字是因为它通常的实现如下:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (queue.<span class="title function_">waitForMessage</span>()) {</span><br><span class="line"> queue.<span class="title function_">processNextMessage</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>queue.waitForMessage</code> 同步的等待消息到来,如果当前还没有的话。</p>
<h3 id="从运行到完成"><a href="#从运行到完成" class="headerlink" title="从运行到完成"></a>从运行到完成</h3><p>每一条消息都在另一条消息被处理前完成。这提供了一些不错的特性在对你的程序追责时,它记录了函数运行的状态,函数执行时总会在其他代码捷足先登前完全的被执行,(并可以修改函数操作时的数据)。与 C 不同的是,比如(以下为 C 的情况), 如果一个方法在一个线程上跑的时候,它可以在任何时候被中断,并且在另一个线程上跑一些其它的代码。</p>
<p>这种模式的缺点是,如果一条消息花费了很长的时间去完成,那么web应用将无法处理用户的交互,比如点击和滚动。浏览器通过一个“一个脚本执行了太长时间”的弹窗来缓解问题。 一个好的做法是将消息变短,如果可能的话,将消息切割成一系列短的消息。</p>
<h3 id="添加消息"><a href="#添加消息" class="headerlink" title="添加消息"></a>添加消息</h3><p>在web浏览器中,任何时候有事件触发,通过绑定的消息监听器都会触发消息的添加。如果没有监听器的话,事件就会丢失。所以在一个元素上点击会触发一个点击事件并添加消息,相似的还有其他的事件。</p>
<p>调用<code>setTimeout</code>会在参数所给的时间后,将消息添加至队列内。如果此时队列内没有其他的消息了,那么这条消息将会被马上执行。然而,如果有其他消息,这个定时消息则不得不等到其他消息都处理完后才被处理。由于这个原因,函数的第二个参数所指定的时间是最快时间而不是确定的时间。</p>
<h3 id="0延时"><a href="#0延时" class="headerlink" title="0延时"></a>0延时</h3><p>0延时并不意味着回调会在0毫秒后被调用。调用<code>setTimeout</code>函数并设置 0 毫秒的延时并不是在指定时间后执行。这个执行取决于当前队列内有多少正在等待的任务。在下方的例子中,“this is just a message”会在回调函数中的那条消息前辈打印出来,因为这个延时是最小时间而不会确定时间。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'this is the start'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="keyword">function</span> <span class="title function_">cb</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'this is a msg from call back'</span>);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'this is just a message'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="keyword">function</span> <span class="title function_">cb1</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'this is a msg from call back1'</span>);</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'this is the end'</span>);</span><br><span class="line"></span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line"><span class="comment">// "this is the start"</span></span><br><span class="line"><span class="comment">// "this is just a message"</span></span><br><span class="line"><span class="comment">// "this is the end"</span></span><br><span class="line"><span class="comment">// "this is a msg from call back"</span></span><br><span class="line"><span class="comment">// "this is a msg from call back1"</span></span><br></pre></td></tr></table></figure>
<h3 id="运行时的协作通信"><a href="#运行时的协作通信" class="headerlink" title="运行时的协作通信"></a>运行时的协作通信</h3><p>一个 web worker 或一个跨源的帧含有他自己的栈,堆还有消息队列。两个不同线程在运行时可以通过postMessage 方法来进行通信。如果后者监听消息事件,此方法会向前者(队列内)添加消息。</p>
<h2 id="永不阻塞"><a href="#永不阻塞" class="headerlink" title="永不阻塞"></a>永不阻塞</h2><p>事件循环模型的一个非常有趣的属性是,JavaScript(的时间循环模型),不像许多其他语言,永远不会阻塞。处理I / O通常通过事件和回调来执行,因此当应用程序正在等待 IndexedDB 查询返回或 XHR 请求返回时,它仍然可以处理其他事情,如用户输入。</p>
<p>一些遗留的异常情况,如使用 alert 或同步 XHR时 ,但它们被认为是避免交互终端的良好做法。注意,异常的异常确实存在(但通常是出现错误,而不是其他任何东西)。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2016/12/04/A%20deep%20dive%20into%20react%20perf%20debugging%20(part%202)/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2016/12/04/A%20deep%20dive%20into%20react%20perf%20debugging%20(part%202)/" class="post-title-link" itemprop="url">【翻译】深入 React Pref 调试 (PART2)</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2016-12-04 12:21:33" itemprop="dateCreated datePublished" datetime="2016-12-04T12:21:33+08:00">2016-12-04</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2023-02-02 04:30:17" itemprop="dateModified" datetime="2023-02-02T04:30:17+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>这是 React 的性能优化的第二部分。在第一部分中,我们简单的了解了怎么去使用 React 的 Perf 调试工具,常规的 React 的渲染瓶颈,和一些调试的小技巧。如果您还没准备好(继续深入)的话,那最好先去把它(Part 1) 看了</p>
<p>在第二部分中,我们会继续深入react调试的工作流 - 既然已经给了这些个想法,那又会如何在练习中体现呢?(自问自答)我们将会通过一些切合时机的例子,并且使用chrome的开发工具来分析和解决性能问题。(如果您在看完后有任何建议和新的想法,请让我们知道!)</p>
<p>我们将会参考下面这段简单的代码 - 你可以看出它通过 React 渲染了一个简单的 todo list。在后面的 JS fiddle 代码片段中,你可以点击 “Result” 来看一个带交互的实际例子,complete with performance repros(不知道怎么翻译)。我们将会这过程中提交并更新 JS fiddles。</p>
<h2 id="CASE-STUDY-1-TODOLIST"><a href="#CASE-STUDY-1-TODOLIST" class="headerlink" title="CASE STUDY #1: TODOLIST"></a>CASE STUDY #1: TODOLIST</h2><p>让我们开始上面的 <code>TodoList</code> 。通过尝试在代码未优化的例子中,快速的输入,你会发现它有多慢。</p>
<p>让我们开始用<em>Chrome</em> 开发工具中的 <em>Timeline profiler</em> ,通过一些细节的切面的来看浏览器在做了些什么: 处理用户触发的事件,运行JS,渲染和绘制。在输入框内输入一个字符,然后停止 <em>Timeline profiler</em> 。这个过程中你不会感到明显的缓慢,因为我们只输入了一个字符,这是产生用以分析的少量信息的最快的手段。</p>
<p><img src="http://benchling.engineering/content/images/2016/02/0.png" alt="img"></p>
<p>注意图中 <code>Event(textInput)</code> 进度条在 <code>Scripting(Children)</code> 中总计花费了121.10 ms 。这个时段切面中表明了这个缓慢问题是一个脚本的问题,而不是样式或重复计算导致的性能问题。</p>
<p>所以让我们深入脚本。切换到 <em>Profiles tab</em> - <em>Timeline</em> 不仅给了我们浏览器(和 <em>JS Profile</em>)的一个概况,通过这些个代码运行切面则让我们得以继续深入 JS 的内部实现,而且给了我们各种各样可视化工具。根据另一份Profile, 它中指出运行缓慢的问题并不在于我们的应用代码:</p>
<p><img src="http://benchling.engineering/content/images/2016/02/IipbV.png" alt="img"></p>
<p>将 Profile 的 <code>Heavy(Bottom up)</code> 按 <code>Total</code> 字段降序排列,结果指出消耗时间最多的部分是React <code>batchUpdates</code>方法的调用,这很明确的提示了问题出在 React 上。相反,通过 <code>Self</code> 来测量函数中除去子函数的花费的时间 - 根据 <code>Self</code> 排序来查看是否有明显的昂贵(花费时间多)的函数时,这并没有明显的性能瓶颈在应用层面的函数中,所以让我们换 React 的 Pref 来看看。</p>
<p>为了声称一个针对缓慢操作的的评估切面,在console中,我们调用 <code>React.addons.Perf.start()</code>, 然后通过输入一个字符来重现缓慢的操作,然后输入<code>React.addons.Perf.stop()</code>结束监控。(最后) 通过输入 <code>React.addons.Perf.printWasted()</code>我们可以看到应用在不必要的渲染中花费的时间:</p>
<p><img src="http://benchling.engineering/content/images/2016/02/2.png" alt="img"></p>
<p>这第一项表明<code>TodoItem</code>是由<code>Todos</code>渲染的,而(输入)<code>Perf.printWated()</code>可以看出,如果我们避免render树的反复构建,我们就可以省下100ms。 这看起来像是我们优化中最主要的部分。</p>
<p>为了分析为什么TodoItem会浪费这么多时间,我们创建一个自定义的输入函数,以很明确的 <code>WhyDidYouUpdateMinxin</code>来命名它,它会嵌入组件并且打印一些信息,如触发了哪些更新和为什么更新。下面是代码;按你的需求随意使用它</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* eslint-disable no-console */</span></span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">'underscore'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Drop this mixin into a component that wastes time according to Perf.getWastedTime() to find</span></span><br><span class="line"><span class="comment">out what state/props should be preserved. Once it says "Update avoidable!" for {state, props},</span></span><br><span class="line"><span class="comment">you should be able to drop in React.addons.PureRenderMixin</span></span><br><span class="line"><span class="comment">React.createClass {</span></span><br><span class="line"><span class="comment"> mixins: [WhyDidYouUpdateMixin]</span></span><br><span class="line"><span class="comment">}</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isRequiredUpdateObject</span>(<span class="params">o</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="title class_">Array</span>.<span class="title function_">isArray</span>(o) || (o && o.<span class="property">constructor</span> === <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">deepDiff</span>(<span class="params">o1, o2, p</span>) {</span><br><span class="line"> <span class="keyword">const</span> <span class="title function_">notify</span> = (<span class="params">status</span>) => {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">'Update %s'</span>, status);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'%cbefore'</span>, <span class="string">'font-weight: bold'</span>, o1);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'%cafter '</span>, <span class="string">'font-weight: bold'</span>, o2);</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">if</span> (!_.<span class="title function_">isEqual</span>(o1, o2)) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">group</span>(p);</span><br><span class="line"> <span class="keyword">if</span> ([o1, o2].<span class="title function_">every</span>(_.<span class="property">isFunction</span>)) {</span><br><span class="line"> <span class="title function_">notify</span>(<span class="string">'avoidable?'</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (![o1, o2].<span class="title function_">every</span>(isRequiredUpdateObject)) {</span><br><span class="line"> <span class="title function_">notify</span>(<span class="string">'required.'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">const</span> keys = _.<span class="title function_">union</span>(_.<span class="title function_">keys</span>(o1), _.<span class="title function_">keys</span>(o2));</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">of</span> keys) {</span><br><span class="line"> <span class="title function_">deepDiff</span>(o1[key], o2[key], key);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">groupEnd</span>();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (o1 !== o2) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">group</span>(p);</span><br><span class="line"> <span class="title function_">notify</span>(<span class="string">'avoidable!'</span>);</span><br><span class="line"> <span class="keyword">if</span> (_.<span class="title function_">isObject</span>(o1) && _.<span class="title function_">isObject</span>(o2)) {</span><br><span class="line"> <span class="keyword">const</span> keys = _.<span class="title function_">union</span>(_.<span class="title function_">keys</span>(o1), _.<span class="title function_">keys</span>(o2));</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">of</span> keys) {</span><br><span class="line"> <span class="title function_">deepDiff</span>(o1[key], o2[key], key);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">groupEnd</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">WhyDidYouUpdateMixin</span> = {</span><br><span class="line"> <span class="title function_">componentDidUpdate</span>(<span class="params">prevProps, prevState</span>) {</span><br><span class="line"> <span class="title function_">deepDiff</span>({<span class="attr">props</span>: prevProps, <span class="attr">state</span>: prevState},</span><br><span class="line"> {<span class="attr">props</span>: <span class="variable language_">this</span>.<span class="property">props</span>, <span class="attr">state</span>: <span class="variable language_">this</span>.<span class="property">state</span>},</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">constructor</span>.<span class="property">displayName</span>);</span><br><span class="line"> },</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">WhyDidYouUpdateMixin</span>;</span><br></pre></td></tr></table></figure>
<p>一旦我们在TodoItem中注入了这个函数,我们可以看到发生了一些什么:</p>
<p><img src="http://benchling.engineering/content/images/2016/02/3.png" alt="img"></p>
<p>Aha! 我们看到tags这个变量在操作前后近似 - 这个注入函数告诉我们当两个状态对象深度相等而不是严格相等时,这时(渲染)是可以避免的。换个角度说,这个问题的难点在于如何判断两个方法是相等的,因为 <code>Fucntion.bind</code>声明了一个新的方法,尽管绑定的参数相同。这是一些有用的线索,既然如此 - 我们回过头来看看我们是如何传入<code>tags</code>和<code>deleteItem</code>的,看起来没错我们构建一个<code>TodoItem</code>时我们都传递了一些新的值。</p>
<p>假如我们用传递未绑定的进入TodoItem, 同时我们将tags储存成常量,我们将可以避免这些个问题:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">diff --git i/example.<span class="property">js</span> w/example.<span class="property">js</span></span><br><span class="line">index ba2427a..2edc85e <span class="number">100644</span></span><br><span class="line">--- i/example.<span class="property">js</span></span><br><span class="line">+++ w/example.<span class="property">js</span></span><br><span class="line">@@ -<span class="number">11</span>,<span class="number">10</span> +<span class="number">11</span>,<span class="number">13</span> @@ <span class="keyword">const</span> <span class="title class_">TodoItem</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">id</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line"> }).<span class="property">isRequired</span>,</span><br><span class="line"> },</span><br><span class="line">+ <span class="title function_">deleteItem</span>(<span class="params"></span>) {</span><br><span class="line">+ <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">deleteItem</span>(<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">item</span>.<span class="property">id</span>);</span><br><span class="line">+ },</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml">- <span class="tag"><<span class="name">button</span> <span class="attr">style</span>=<span class="string">{{width:</span> <span class="attr">30</span>}} <span class="attr">onClick</span>=<span class="string">{this.props.deleteItem}</span>></span>x<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="language-xml">+ <span class="tag"><<span class="name">button</span> <span class="attr">style</span>=<span class="string">{{width:</span> <span class="attr">30</span>}} <span class="attr">onClick</span>=<span class="string">{this.deleteItem}</span>></span>x<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">span</span>></span>{this.props.item.text}<span class="tag"></<span class="name">span</span>></span></span></span><br><span class="line"><span class="language-xml"> {this.props.tags.map((tag) => {</span></span><br><span class="line"><span class="language-xml"> return <span class="tag"><<span class="name">span</span> <span class="attr">key</span>=<span class="string">{tag}</span> <span class="attr">className</span>=<span class="string">"tag"</span>></span> {tag}<span class="tag"></<span class="name">span</span>></span>;</span></span><br><span class="line"><span class="language-xml">@@ -26,6 +29,9 @@ const TodoItem = React.createClass({</span></span><br><span class="line"><span class="language-xml"> </span></span><br><span class="line"><span class="language-xml"> const Todos = React.createClass({</span></span><br><span class="line"><span class="language-xml"> mixins: [React.addons.LinkedStateMixin],</span></span><br><span class="line"><span class="language-xml">+ statics: {</span></span><br><span class="line"><span class="language-xml">+ tags: ['important', 'starred'],</span></span><br><span class="line"><span class="language-xml">+ },</span></span><br><span class="line"><span class="language-xml"> propTypes: {</span></span><br><span class="line"><span class="language-xml"> initialItems: React.PropTypes.arrayOf(React.PropTypes.shape({</span></span><br><span class="line"><span class="language-xml"> text: React.PropTypes.string.isRequired,</span></span><br><span class="line"><span class="language-xml">@@ -60,8 +66,8 @@ const Todos = React.createClass({</span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">form</span>></span></span></span><br><span class="line"> {<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">items</span>.<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line">- <span class="language-xml"><span class="tag"><<span class="name">TodoItem</span> <span class="attr">key</span>=<span class="string">{item.id}</span> <span class="attr">item</span>=<span class="string">{item}</span> <span class="attr">tags</span>=<span class="string">{[</span>'<span class="attr">important</span>', '<span class="attr">starred</span>']}</span></span></span><br><span class="line"><span class="tag"><span class="language-xml"><span class="attr">-</span> <span class="attr">deleteItem</span>=<span class="string">{this.deleteItem.bind(null,</span> <span class="attr">item.id</span>)} /></span></span></span><br><span class="line">+ <span class="language-xml"><span class="tag"><<span class="name">TodoItem</span> <span class="attr">key</span>=<span class="string">{item.id}</span> <span class="attr">item</span>=<span class="string">{item}</span> <span class="attr">tags</span>=<span class="string">{Todos.tags}</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">+ <span class="attr">deleteItem</span>=<span class="string">{this.deleteItem}</span> /></span></span></span><br><span class="line"> );</span><br><span class="line"> })}</span><br><span class="line"> </div></span><br></pre></td></tr></table></figure>
<p><code>WhyDidYouUpdateMixin</code> 现在表明了 prevProps 和 newProps 浅相等。我们可以使用 PureRenderMixin,来避免当props(和state)浅相等时的组件更新。</p>
<p><img src="http://benchling.engineering/content/images/2016/02/4.png" alt="img"></p>
<p>当我们重新运行Profiler,我们可以看到只花费了35ms(比原来的快了4倍)</p>
<p><img src="http://benchling.engineering/content/images/2016/02/hSPOL.png" alt="img"></p>
<p>这比原来的好了,但是仍然不够理想。在输入框内打字不应该导致这么慢。这表明我们任然没有做到0(list的item数量级别)的工作。我们仅仅定义了常量,我们依然需要对每个item进行浅比较。</p>
<p>此时,你可以能会认为1000个items在todolist中已经是极端的情况了,而且30ms的延迟,对你的应用而言并不是问题。如果你希望能够支持几千个子元素,那么,这任然没有达到理想的60fps</p>
<p>(16ms 每帧 - 慢一点点你都会感受的到)。</p>
<p>将组件拆分成多个组件作为下一步工作是有道理的(同样将之视为第一步也是合理的)。我们观察到Todos这个组件由两个没有交集的子组建构成,一个<code>AddTaskForm</code>组件包含了输入框和按钮,一个<code>TodoItems</code>组件包含了Items的列表。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable constant_">ID</span> = <span class="number">0</span>; <span class="comment">// incrementing counter for todo item ids</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AddTaskForm</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">mixins</span>: [<span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">LinkedStateMixin</span>, <span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">PureRenderMixin</span>],</span><br><span class="line"> <span class="title function_">getInitialState</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="attr">text</span>: <span class="string">''</span>,</span><br><span class="line"> };</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">addTask</span>(<span class="params">e</span>) {</span><br><span class="line"> e.<span class="title function_">preventDefault</span>();</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">addTask</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">text</span>);</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">setState</span>({<span class="attr">text</span>: <span class="string">''</span>});</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">form</span> <span class="attr">onSubmit</span>=<span class="string">{this.addTask}</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">input</span> <span class="attr">valueLink</span>=<span class="string">{this.linkState(</span>'<span class="attr">text</span>')} /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">button</span>></span>Add Task<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">form</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodoItems</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">mixins</span>: [<span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">PureRenderMixin</span>],</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> {this.props.items.map((item) => {</span></span><br><span class="line"><span class="language-xml"> return (</span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">TodoItem</span> <span class="attr">key</span>=<span class="string">{item.id}</span> <span class="attr">item</span>=<span class="string">{item}</span> <span class="attr">tags</span>=<span class="string">{Todos.tags}</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml"> <span class="attr">deleteItem</span>=<span class="string">{this.props.deleteItem}</span> /></span></span></span><br><span class="line"><span class="language-xml"> );</span></span><br><span class="line"><span class="language-xml"> })}</span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodoItem</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">mixins</span>: [<span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">PureRenderMixin</span>],</span><br><span class="line"> <span class="attr">propTypes</span>: {</span><br><span class="line"> <span class="attr">deleteItem</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">func</span>.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">tags</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">arrayOf</span>(<span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>).<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">item</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">shape</span>({</span><br><span class="line"> <span class="attr">text</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">id</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line"> }).<span class="property">isRequired</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">deleteItem</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">deleteItem</span>(<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">item</span>.<span class="property">id</span>);</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">button</span> <span class="attr">style</span>=<span class="string">{{width:</span> <span class="attr">30</span>}} <span class="attr">onClick</span>=<span class="string">{this.deleteItem}</span>></span>x<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">span</span>></span>{this.props.item.text}<span class="tag"></<span class="name">span</span>></span></span></span><br><span class="line"><span class="language-xml"> {this.props.tags.map((tag) => {</span></span><br><span class="line"><span class="language-xml"> return <span class="tag"><<span class="name">span</span> <span class="attr">key</span>=<span class="string">{tag}</span> <span class="attr">className</span>=<span class="string">"tag"</span>></span> {tag}<span class="tag"></<span class="name">span</span>></span>;</span></span><br><span class="line"><span class="language-xml"> })}</span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Todos</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">statics</span>: {</span><br><span class="line"> <span class="attr">tags</span>: [<span class="string">'important'</span>, <span class="string">'starred'</span>],</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">propTypes</span>: {</span><br><span class="line"> <span class="attr">initialItems</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">arrayOf</span>(<span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">shape</span>({</span><br><span class="line"> <span class="attr">text</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">id</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line"> }).<span class="property">isRequired</span>).<span class="property">isRequired</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">getInitialState</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="attr">items</span>: <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">initialItems</span>,</span><br><span class="line"> };</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">addTask</span>(<span class="params">text</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">setState</span>({</span><br><span class="line"> <span class="attr">items</span>: [{<span class="attr">id</span>: <span class="variable constant_">ID</span>++, text}].<span class="title function_">concat</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">items</span>),</span><br><span class="line"> });</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">deleteItem</span>(<span class="params">itemId</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">setState</span>({</span><br><span class="line"> <span class="attr">items</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">items</span>.<span class="title function_">filter</span>(<span class="function">(<span class="params">item</span>) =></span> item.<span class="property">id</span> !== itemId),</span><br><span class="line"> });</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">render</span>: <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">h1</span>></span>My TODOs<span class="tag"></<span class="name">h1</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">AddTaskForm</span> <span class="attr">addTask</span>=<span class="string">{this.addTask}</span> /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">TodoItems</span> <span class="attr">items</span>=<span class="string">{this.state.items}</span> <span class="attr">deleteItem</span>=<span class="string">{this.deleteItem}</span> /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create a Todos component, initialized with 1000 items.</span></span><br><span class="line"><span class="keyword">const</span> items = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="number">1000</span>; i++) {</span><br><span class="line"> items.<span class="title function_">push</span>({<span class="attr">id</span>: <span class="variable constant_">ID</span>++, <span class="attr">text</span>: <span class="string">'Todo Item #'</span> + i});</span><br><span class="line">}</span><br><span class="line"><span class="title class_">React</span>.<span class="title function_">render</span>(<span class="language-xml"><span class="tag"><<span class="name">Todos</span> <span class="attr">initialItems</span>=<span class="string">{items}</span> /></span></span>, <span class="variable language_">document</span>.<span class="property">body</span>);</span><br></pre></td></tr></table></figure>
<p>任何一项重构都能提供实质的性能增益:</p>
<ul>
<li>如果我们通过PureRenderMixin来创建一个TodoItems,因为<code>prevProps.items === this.props.items</code>,我们将能通避免重新渲染每一个item来减少O(n)的时间消耗。</li>
<li>如果我们创建一个AddTaskForm组件时,将文本的state至存在于组件内使,当文本改变时,将不会,Todos组件(列表部分)将不会重新渲染(避免了O(n)的渲染消耗)。</li>
</ul>
<p>(将以上的工作)合起来,我们每次键盘的按键操作只会消耗10ms</p>
<h2 id="CASE-STUDY-2"><a href="#CASE-STUDY-2" class="headerlink" title="CASE STUDY #2:"></a>CASE STUDY #2:</h2><p>场景: 我们想在用户由太多的任务(>3000)时,渲染一个警告,同时我们想给这些todo项添加一个背景样式。</p>
<p>实施:</p>
<ul>
<li>我们由一个近似的todo列表的案例,用(之前的)<code>TodoItems</code>来实现 - 在这个李子中,我们将会把input的文本内容储存在最顶层的组件状态中。</li>
<li>我们创建了一个<code>TaskWarning</code>的组件,来根据任务的数量来决定消息的渲染。为了封装这层逻辑,我们将会返回null,假如它不该被渲染。</li>
<li>我们用样式给<code>div:nth-child(even)</code>所匹配到的元素添加一个灰色的背景。</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable constant_">ID</span> = <span class="number">0</span>; <span class="comment">// incrementing counter for todo item ids</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodoWarning</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">propTypes</span>: {</span><br><span class="line"> <span class="attr">itemCount</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span></span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">itemCount</span> > <span class="number">3000</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span>'YOU HAVE TOO MANY TASKS. SLOW DOWN.'<span class="tag"></<span class="name">div</span>></span></span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodoItems</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">mixins</span>: [<span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">PureRenderMixin</span>],</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">"todoItems"</span>></span></span></span><br><span class="line"><span class="language-xml"> {this.props.items.map((item) => {</span></span><br><span class="line"><span class="language-xml"> return (</span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">TodoItem</span> <span class="attr">key</span>=<span class="string">{item.id}</span> <span class="attr">item</span>=<span class="string">{item}</span> <span class="attr">tags</span>=<span class="string">{Todos.tags}</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml"> <span class="attr">deleteItem</span>=<span class="string">{this.props.deleteItem}</span> /></span></span></span><br><span class="line"><span class="language-xml"> );</span></span><br><span class="line"><span class="language-xml"> })}</span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TodoItem</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">mixins</span>: [<span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">PureRenderMixin</span>],</span><br><span class="line"> <span class="attr">propTypes</span>: {</span><br><span class="line"> <span class="attr">deleteItem</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">func</span>.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">tags</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">arrayOf</span>(<span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>).<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">item</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">shape</span>({</span><br><span class="line"> <span class="attr">text</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">id</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line"> }).<span class="property">isRequired</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">deleteItem</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">deleteItem</span>(<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">item</span>.<span class="property">id</span>);</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">button</span> <span class="attr">style</span>=<span class="string">{{width:</span> <span class="attr">30</span>}} <span class="attr">onClick</span>=<span class="string">{this.deleteItem}</span>></span>x<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">span</span>></span>{this.props.item.text}<span class="tag"></<span class="name">span</span>></span></span></span><br><span class="line"><span class="language-xml"> {this.props.tags.map((tag) => {</span></span><br><span class="line"><span class="language-xml"> return <span class="tag"><<span class="name">span</span> <span class="attr">key</span>=<span class="string">{tag}</span> <span class="attr">className</span>=<span class="string">"tag"</span>></span> {tag}<span class="tag"></<span class="name">span</span>></span>;</span></span><br><span class="line"><span class="language-xml"> })}</span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Todos</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>({</span><br><span class="line"> <span class="attr">mixins</span>: [<span class="title class_">React</span>.<span class="property">addons</span>.<span class="property">LinkedStateMixin</span>],</span><br><span class="line"> <span class="attr">statics</span>: {</span><br><span class="line"> <span class="attr">tags</span>: [<span class="string">'important'</span>, <span class="string">'starred'</span>],</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">propTypes</span>: {</span><br><span class="line"> <span class="attr">initialItems</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">arrayOf</span>(<span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">shape</span>({</span><br><span class="line"> <span class="attr">text</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">id</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line"> }).<span class="property">isRequired</span>).<span class="property">isRequired</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">getInitialState</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="attr">items</span>: <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">initialItems</span>,</span><br><span class="line"> <span class="attr">text</span>: <span class="string">''</span>,</span><br><span class="line"> };</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">addTask</span>(<span class="params">e</span>) {</span><br><span class="line"> e.<span class="title function_">preventDefault</span>();</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">setState</span>({</span><br><span class="line"> <span class="attr">items</span>: [{<span class="attr">id</span>: <span class="variable constant_">ID</span>++, <span class="attr">text</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">text</span>}].<span class="title function_">concat</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">items</span>),</span><br><span class="line"> <span class="attr">text</span>: <span class="string">''</span>,</span><br><span class="line"> });</span><br><span class="line"> },</span><br><span class="line"> <span class="title function_">deleteItem</span>(<span class="params">itemId</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="title function_">setState</span>({</span><br><span class="line"> <span class="attr">items</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">items</span>.<span class="title function_">filter</span>(<span class="function">(<span class="params">item</span>) =></span> item.<span class="property">id</span> !== itemId),</span><br><span class="line"> });</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">render</span>: <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">TodoWarning</span> <span class="attr">itemCount</span>=<span class="string">{this.state.items.length}</span> /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">h1</span>></span>My TODOs<span class="tag"></<span class="name">h1</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">form</span> <span class="attr">onSubmit</span>=<span class="string">{this.addTask}</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">input</span> <span class="attr">valueLink</span>=<span class="string">{this.linkState(</span>'<span class="attr">text</span>')} /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">button</span>></span>Add Task<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">form</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">TodoItems</span> <span class="attr">items</span>=<span class="string">{this.state.items}</span> <span class="attr">deleteItem</span>=<span class="string">{this.deleteItem}</span> /></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create a Todos component, initialized with 1000 items.</span></span><br><span class="line"><span class="keyword">const</span> items = [];</span><br></pre></td></tr></table></figure>
<p>观察诊断:在输入框内快速的输入,页面由很明显的延迟(不超过3000个任务)。如果我们继续添加一个任务(> 3000 个任务),延迟随着按钮消失了。令人惊讶的地方,添加更多的任务似乎解决了这个任务!</p>
<p>调试:Timeline Profile 展现了一些很有意思的东西:</p>
<p><img src="http://benchling.engineering/content/images/2016/02/6.png" alt="img"></p>
<p>因为某些原因,输入单个字符时触发了大量的<code>Recalculate Style</code>,超过了30ms(这就是为什么当我们打字的速度大于30ms/每个字符时,我们会观察到Jank【注:应用刷新的速率没有达到设备的刷新速率而产生的卡顿现象】)。</p>
<p>看看图片底部的<code>First invalidated</code>一节内容。它指出<code>Danger.dangerouslyReplaceNodeWithMarkup</code> 导致了页面的布局失效,从而导致了样式的重新计算。<code>react-with-addons.js:2301</code>处:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">oldChild.<span class="property">parentNode</span>.<span class="title function_">replaceChild</span>(newChild, oldChild);</span><br></pre></td></tr></table></figure>
<p>因为某些原因,React 用一个完全新的DOM节点替换了原先的DOM节点! 回想起来,生成DOM的操作时非常昂贵的。使用<code>Perf.printDOM()</code>,我们可以看到React在DOM操作时的性能:</p>
<p><img src="http://benchling.engineering/content/images/2016/02/7.png" alt="img"></p>
<p>更新的属性反映了当在输入框输入<code>abc</code>时,TaskWarning是不可见的。然而,replace 项(图中的type)又指出了,此时React正在为TaskWarning组件创建DOM,尽管它看起来不应该有明确可见的实体DOM。</p>
<p>正如上面所展示的,React(<= v0.13)使用了一个<code>noscript</code>标签来渲染“no component”,但是却(在做diff时)错误的将两个noscript的标签视为了不相等:<code>noscript</code>最后被另一个noscript替换。此外,回想起来我们给其他的每个元素添加的一个灰色背景的样式。因为CSS的缘故,这3000个节点中任何一个的单独渲染都依赖与它之前的兄弟节点。每次noscript标签被替换,它随后的DOM节点的样式都会被重新计算。</p>
<p>为了修复这个问题,我们可以:</p>
<ul>
<li>让TaskWarning 返回一个空的div</li>
<li>将TaskWarning组件用一个div包裹起来,这样它就不会去影响css选择器对随后节点的选择。【意味不明】</li>
<li>升级React :-)</li>
</ul>
<p>除此以外。这里最关键的一点时,我们能够自己分析出这些,仅仅通过Timeline Profiler!</p>
<h1 id="CONCLUSION"><a href="#CONCLUSION" class="headerlink" title="CONCLUSION"></a>CONCLUSION</h1><p>我希望展示react的性能问题在各种开发工具中的表现是有用的(能够帮到大家)- 通过*Timeline *, <em>profiles</em>,和React的Perf工具的配合使用还有很长的道路要走。</p>
<p>在todolisst中包含上千个项,和随意的着色,似乎有些刻意(简单将就是说我上面举得例子看起来有点不切实际),不过它所变面出的问题却和我们在做electronic lab notebook的项目中实际中遇到的渲染大量文档和表格时的问题很近似。</p>
<p>(后面时招聘广告和客套话,省略…)</p>
<h2 id="读后总结:"><a href="#读后总结:" class="headerlink" title="读后总结:"></a>读后总结:</h2><p> 很老的一篇文章了(最然是今年年初的),原本是奔着文章的题目去的,不过发觉内容里貌似也没怎么深入,Perf的介绍和使用主要还是 <a target="_blank" rel="noopener" href="http://benchling.engineering/performance-engineering-with-react/">Part1</a> 中,不过在文中 timeline 和 profile的配合使用中还是学到了一些东西(思考问题的角度),之后看样子还是要好好的学习如何使用chrome 的devtool。</p>
<p>总结几点吧:</p>
<ul>
<li>要使用PureComponentMixin,使用es6的class来创建组件时则应该继承PureComponent。</li>
<li>合理的拆分组件(组件解耦)。</li>
<li>常量不要在render时声明,应该抽到外面去。</li>
<li>文中所提到的noscript的问题,在react的<a target="_blank" rel="noopener" href="https://github.com/facebook/react/issues/2770">https://github.com/facebook/react/issues/2770</a> 中已经解决了,所以返回null也没什么问题了。具体的<a target="_blank" rel="noopener" href="https://github.com/facebook/react/pull/4825/commits/db589a717510d1a2d235d934a0eec4b5609b4e0b">commit</a></li>
<li>交互的相应理想的状态下要做到30ms内(难点)。文中提到 Jank 的概念。<a target="_blank" rel="noopener" href="http://jankfree.org/">http://jankfree.org/</a></li>
<li>看源码!看源码!看源码!重要的事情说三遍。</li>
<li>文中的图片请挂代理…</li>
</ul>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-Hans">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2016/10/03/Smart-and-Dumb-Components/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="张煜航">
<meta itemprop="description" content="Do you best, and God will do the lest.">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Veda">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2016/10/03/Smart-and-Dumb-Components/" class="post-title-link" itemprop="url">【翻译】展示组件和容器组件</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2016-10-03 12:21:33" itemprop="dateCreated datePublished" datetime="2016-10-03T12:21:33+08:00">2016-10-03</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2023-02-02 04:30:17" itemprop="dateModified" datetime="2023-02-02T04:30:17+08:00">2023-02-02</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>作者:Dan Abramov<br>链接:<a target="_blank" rel="noopener" href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0">https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0</a></p>
<p>当我在写react应用时有一种很简单却非常使用的模式。如果你已经写过一段实践的react,那么可能你已经发现了它。<a target="_blank" rel="noopener" href="https://medium.com/@learnreact/container-components-c0e67432e005#.hr23xqjul">这篇文章</a> 将这种模式解释的很好,不过我希望加入一些额外的要点。</p>
<p>你会发现当你把你的组件拆分成两类时,它们会变得更加易于复用和理解。我称呼他们为容器组件和展示组件,不过我同样也听到如下几种说法,胖组件(Fat)和瘦(Shinny)组件,聪明组件(Smart)和呆(Dumb)组件,多状态(Statefull)组件和纯(Pure)组件,放映和组件(Screens and Components)。它们不尽相同,但核心的思想却非常接近。</p>
<p>我所谓的展示组件:</p>
<ul>
<li>考虑组件长啥样。</li>
<li>可能会同时包含展示组件和容器组件在其中,而且通常会含有写DOM标签和私有样式。</li>
<li>通常用this.props.children来包其他组件。</li>
<li>不依赖于应用内的其他部分,比如Flux的动作和存储。</li>
<li>不会定义数据是如何被加载或改变的。</li>
<li>仅通过props来获取数据和回调函数。</li>
<li>很少包含私有的状态(如果有的话,也只会是UI的状态而不是应用的数据)</li>
<li>一般用<a target="_blank" rel="noopener" href="https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components">方法组件</a> 的写法来写,除非它们需要状态,或者控制生命周期,或者优化性能。</li>
<li>举例: <em>Page, Siderbar, Strory, UserInfo, List</em></li>
</ul>
<p>我所谓的容器组件:</p>
<ul>
<li>考虑组件是如何运作的。</li>
<li>可能会同时包含展示组件和容器组件在其中,出了部分包裹用的div不会拥有任何DOM标签,更绝不会有任何样式。</li>
<li>给展示组件或其他容器组件提供数据和动作。</li>
<li>调用flux动作,并为展示组件提供回调函数</li>
<li>通常会有很多状态,而且通常是应用服务的数据。</li>
<li>通常通过<a target="_blank" rel="noopener" href="https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.tghakfcnl">高阶组件</a> 来生成,比如React Redux的connect()组件, Relay的createContainer(),Flux Utils的Container.create(),而不是手工的去写。</li>
<li>举例:<em>UserPage, FollowersSidebar, StoryContainer, FollowedUserList.</em></li>
</ul>
<p>我将它们放在不同的文件夹下,从而使它们的作用更加明确。</p>
<h3 id="这种做法的好处"><a href="#这种做法的好处" class="headerlink" title="这种做法的好处"></a>这种做法的好处</h3><ul>
<li>将专注的点分离,通过这种方式,你可以更好的理解你的应用和你的UI。</li>
<li>更好的复用。你可以使用相同的展示组件通过不同的状态源,也可以封装成容器组件,在未来复用它。</li>
<li>展示组件本质上是你的应用的“调色板”。你可以将他们放到一个单独的页面内,并且让设计师随意的来调整它们的样式,而不会碰触来应用的逻辑部分。你可以在哪个页面内进行screenshot regression测试。</li>
<li>这会强迫你去解析“布局组件”,比如Sidebar,Page,ContextMeanu 并且使用this.props.children来传递,而不是粘贴复制那块的jsx。</li>
</ul>
<p>记住,组件不一定要生成DOM,它们只需要提供UI上的组合关系和界限。</p>
<h3 id="什么时候去进去容器?"><a href="#什么时候去进去容器?" class="headerlink" title="什么时候去进去容器?"></a>什么时候去进去容器?</h3><p>我建议你在构建你的应用时,先写展示组件的部分。最后,你会发现,你在中间组件中传递了太多的props。当你注意到部分组件并不需要它所接受的prop,而只是传递给它们的子组件,而当子组件需要更多数据,你不得不如重写所有这些中间组件,这时就是引入容器的最佳时机。这样做,你可以将数据和一些动作的props给余下的组件,而不必去在组件树中负担一些无关的组件。</p>
<p>这是一种循序渐进的不断重构的过程,所以没必要在一开始就做到位。当你不断地实现这种模式时,记得只要像你知道何时去增加新的方法一样来增加新的组件就可以了。我的免费的redux教程系列也许会对你有帮助。</p>
<h3 id="其他的二分性"><a href="#其他的二分性" class="headerlink" title="其他的二分性"></a>其他的二分性</h3><p>有很重要的一点你必须知道,容器组件和展示组件的区别并不是技术上的差别,而是两者在目的上的区别。为了比较,我再列举了一些有联系的(但是是不同的!)二分性。</p>
<ul>
<li>多状体和无状态。一部分组件使用了React.setState()方法,而另一部分则没有。容器组件有很多状态,展示组件状态很少,没错,但这并不是一套铁规则。展示组件也可以有很多状态,同理,容器组件的状态也可以很少。</li>
<li>类和函数。从React0.14版本开始,组件可以用类和函数两种方式来生命了。函数组件更容易声明,不过它们缺少了一些只属于类的特性。这些限制或许再未来会消失,但至少现在是存在的。因为函数组件更易于理解,所以我建议你使用它们,除非你需要状态,再生命周期上添加处理,或是需要优化性能,这种时候只能使用类声明的组件了。</li>
<li>纯的和不纯的。人们说一个组件是纯的,给它相同的props和state时,返回的结果一定时相同的。纯组件可以被定义为类和函数,同时既可以时充满状态的也可以使无状态的。纯组件的另一个重要的部分是,春组件并不会因为所谓的props和state产生一些深层的改变,所以可以通过在shouldComponentUpdate函数内浅比较state和props来优化性能。目前只有类组件可以声明shouldComponentUpdate方法,不过在未来这可能会改变。</li>
</ul>
<p>展示组件和容器组件都有上述的特性。在我的经验来看,展示组件倾向于无状态的纯函数,而容器组件情况于多状态的纯类。当然了,这只是我的经验之谈,而不是铁则,我也见过一些完全相反的情况。不要将展示组件和容器组件当作教条。有些时候,划清这条线并不是必要,而且有时很难分清。如果你感到不确定一个组件是展示组件还是容器组件时,不要太纠结,可能时候未到。</p>
<h3 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h3><p>Michael Chan的这篇梗概中讨论了这个。</p>
<h3 id="衍生阅读"><a href="#衍生阅读" class="headerlink" title="衍生阅读"></a>衍生阅读</h3><ul>
<li><p><a target="_blank" rel="noopener" href="https://egghead.io/series/getting-started-with-redux">Getting Started with Redux</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750">Mixins are Dead, Long Live Composition</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://medium.com/@learnreact/container-components-c0e67432e005">Container Components</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://bradfrost.com/blog/post/atomic-web-design/">Atomic Web Design</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://facebook.github.io/react/blog/2015/03/19/building-the-facebook-news-feed-with-relay.html">Building the Facebook News Feed with Relay</a></p>
</li>
</ul>
<h3 id="脚注"><a href="#脚注" class="headerlink" title="脚注"></a>脚注</h3><p>在这篇文章的早些版本中我们称之为聪明组件和呆组件,不过这对展示组件来说有些讲的太过分了,而且这并不能很好的解释两者的意图。我很喜欢这对新的叫法,希望你也是。</p>
<p>在这篇文章的早些版本中我声称展示组件只能包含其他的展示组件。如今我不太认为这时对的。一个组件时展示组件还是容器组件应该由他的内部细节决定。你应该能够用通过容器组件来替换掉一个展示组件同时不修改任何调用的地方。因此,展示组件和容器组件都可以包含其他展示组件和容器组件,这是没问题的。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>