-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1224 lines (989 loc) · 199 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>SPRABBIT</title>
<subtitle>菩提本无树,明镜亦非台;本来无一物,何处惹尘埃?</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://blog.sprabbit.com/"/>
<updated>2016-08-02T04:59:06.638Z</updated>
<id>http://blog.sprabbit.com/</id>
<author>
<name>Kenneth</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>《神秘海域4 盗贼末路》有感</title>
<link href="http://blog.sprabbit.com/2016/08/02/uncharted4/"/>
<id>http://blog.sprabbit.com/2016/08/02/uncharted4/</id>
<published>2016-08-02T04:53:04.000Z</published>
<updated>2016-08-02T04:59:06.638Z</updated>
<content type="html"><![CDATA[<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160802/uncharted4/01.jpg" alt="uncharted4"></p>
<p>作为一个从PS4才成为大法教徒的我,神秘海域系列也是到了PS4版123合集才真正了体验到这一系列的游戏。虽然大家都称之为神作,但是我当时玩到的时候却不觉得惊艳,可能是因为我先玩的《最后生还者》重制版。</p>
<a id="more"></a>
<p>《最后生还者》这一作真的是彻头彻尾的把我震撼了,画面效果堪称完美,分镜头构图有大片风范,人物刻画注入了很多真情实感,剧情节奏安排也十分容易让人沉浸其中,加上适时插入的音乐,很精准地带动玩家的情绪。在讨论《神海4》之前,我觉得还是很有必要说一下《最后生还者》的,因为《神海4》有很多《最后生还者》的影子。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160802/uncharted4/02.jpg" alt="the last of us"></p>
<p>作为末日题材,《最后生还者》与很多其他末日作品不同,设定极为平实,敌人没有夸张的超能力,主角也没有能拯救世界的主角光环。有的只是运用机智战胜敌人,以及大叔和萝莉之间日渐加深的复杂感情。游戏性上,游戏的敌人足够强大,但是又十分笨拙,虽然正面刚没什么胜算,但是运用战略逐一击破是可行的办法,不同的潜入路线甚至可以完全绕开敌人而避免触发战斗,十分具有挑战性和可玩性。而剧情上两位主角之间的感情刻画,为紧张枯燥的战斗注入了活力,这其中蕴含的不只是普通的友情,更是亲情与爱情,感情至深,甚至可以超越世界的安危与人类的存亡。虽然看似夸张,但是比起英雄主义,这更加接近我们这种普通人的现实,所以游戏有很强的代入感。剧情节奏安排方面,每一次启程,作者都给了我们希望,作为每一遭令人恐惧的战斗目标与期待,但是每一个终点,作者都将希望残忍的扑灭,不是是否是抖M心理作怪,我竟然觉得虐心又兴奋,完全被作者玩弄于鼓掌之中。尤其是经历完紧张的战斗后作者给我们展现的美丽风景以及迎面走来的长颈鹿,配上安适的音乐,深深地治愈了我被糟蹋的心情;正当我以为终点就在不远处,终于可以安心一段时间时,作者却给了我当头一棒,目的地竟然只是个空壳,我们不得不以另一个目的地为目标继续前进。让这个故事升华的一笔,就是主角最后的抉择,选择挽救挚爱的艾莉的性命而放弃拯救全人类的机会,这完全出乎我的意料,不按套路出牌啊,但是仔细想,整个旅途中的点点滴滴,难道不足以支撑这一抉择吗?如果这是现实中发生的事情,我大概也会如此选择吧。玩完这个游戏我是震撼的,单从剧情上已经足以让我从游戏世界中清醒过来,露骨得令人发指的真实,让英雄主义与大团圆结局荡然无存,现实意义比虚拟意义更加深刻。</p>
<p>除了上面所说的,《最后生还者》真的还有很多很多可以说的,比如形形色色的人物与其结局,末日与人性之间的思考等等,我都差点忘了这是写《神海4》的玩后感了。回到《神海》系列,因为在PS3时期《最后生还者》就晚于《神海》1-3部出,所以《最后生还者》比《神海》制作更精良也是正常,但是我是倒过来玩的,这个体验就差远了。从定位上看两作是十分不同的虽然都是第三人称冒险游戏,《神海》更注重枪战以及动作解谜,《最后生还者》则更注重潜行和剧情,所以简单来说前者更爽快,后者更细腻。画面就不多说了,正常肯定越晚出的越好了。游戏性上,翻过来玩《神海》确实诸多不习惯,比如没有快速跑,只能用摇杆,应接不暇的枪战,捡不完的弹夹,一个人需要正面跟一个部队进行对抗,这些在《最后生还者》中都是无法想象的。虽然有潜行的设定,但是不知道是我操作渣还是怎样,每个关卡无论如何暗杀两三个敌人后就会被发现,然后开始被集火,所以潜行在1-3代里面远没有来一个爆一个直接,每个关卡都是要灭光敌人才能前进。爬墙走璧这些就没啥新鲜的了,育碧的游戏里面爬的多了,解谜的话还是挺多可圈可点的,不过谜题不算很多,基本不论去到哪里,敌人都会领先一步,各种枪战等着你。不过将枪战与遗迹结合并不是《神海》的专利,早在20年前《古墓丽影》就已经开创了先河,只不过枪战成分毕竟不多,动作解谜成分居多。虽然《神海》号称在剧情人物上面更胜一筹,但是我觉得都很水,玩第一部的时候我觉得剧情就是水过去的,每个角色感觉都很脱线,没办法理解角色的行为与话语,剧情就开始往后发展了。第二部稍微注重叙事,还用了倒叙,但是还是很难捉摸角色的设置用意以及思维方式,比如第二女主克洛伊就让人捉摸不透,并不是这个人很多谋,而是她的言行没有办法支撑她的决策。第三部有点想补完奈森与苏利文的相遇的意味,但是我觉得太晚了,所以如果没有第三部,怎么能理解第一第二部这两人的关系额。总而言之,这三部的剧情设定实在是有点弱,糊里糊涂不知怎么地,爱情亲情友情就出现了,这点跟《最后生还者》相比只能望其项背了。</p>
<p>终于,我们迎来了《神海4》。作为顽皮狗PS4上的首个新作,画面比起前作当然是有过之而无不及,各种细节都经过精雕细琢,每个镜头都可以当桌面啊。游戏性上,本作终于有了真正的潜行系统,跟《最后生还者》十分相似,应该是由其改进而来,在掩体之间移动终于可以绕开敌人避免发生战斗了,地形上更增加了草丛,在其中移动可以避开敌人视线,甚至隐蔽击杀敌人。真正令潜行能够付诸实践的系统,乃是敌人标记以及警戒系统,标记敌人后即使敌人走出视线仍然可以指出其位置,让潜行过程中保持大局观,警戒标记可以看出敌人的警惕程度,换句话说就是有一定的缓冲时间让你能稍微泄露行踪,甚至引诱敌人靠近,而不是像前作一样被看到马上被集火。加上新的移动道具——绳索,结合地形可以做出各种飞越敌人视线的动作,一套连贯的操作,可以直接将你从关卡入口直接带到关卡出口,实在666。这些元素都能看出来,顽皮狗是想认真做潜入了,毕竟很多玩家喜欢智取而非蛮攻,对过多的枪战也是审美疲劳了。剧情上可以说是比前作大有进步,对话和表情表现得当,人物终于有了灵魂:奈森是一个金盆洗手的探险家,为了和爱人安稳的生活,而从事普通打捞工作,但是过去的探险经历却历历在目,内心仍残留着一丝探险家精神。艾莲娜是个顾家的主妇,虽然反对丈夫参与危险的事情,但是又十分体贴,虽然仍然十分脱线,但是至少能看出她是爱着奈森的,关键时刻总会有好运气。苏利文仍是一个交友甚广的探险家,仍从事各种探险交易活动,同时是奈森父亲一样的存在,一直支持和帮助奈森。山姆则是一个对宝藏十分执着的人,即便要欺骗自己的亲兄弟,也要把宝藏弄到手,但是对奈森仍然十分爱护。这种设定下,故事的发展终于有迹可循,不再是无厘头的脱线对话。另外本作的主题《盗贼末路》,也更有深意,除了代表本作宝藏所有者,多名海盗最终为争夺宝藏勾心斗角,自相残杀的终末,也代表着奈森,山姆,拉夫几个宝藏猎人的殊途。除了剧情,气氛渲染上《神海4》也更上了一层楼,不再是一股脑地讲冷笑话,而是严肃中渗透着一丝丝幽默。有些评论说第三章太冗长没有必要,我却觉得这是反映当下奈森枯燥平淡的日常工作的妙招,毕竟连玩家都能体会到这种枯燥无聊的感觉。在孤岛上与艾莲娜重聚后的一段驾驶,充满温馨又不做作的对话配以恬静的音乐与美丽的风光,让我以为我在玩《最后生还者》,刚经历一段恐怖艰苦的战斗后迎来片刻的美好。然而这个世界并没有感染者,游戏把气氛渲染的很诡异,但是却自始至终没有出现超越人类的对手,所以这片刻的宁静,实际上并没有《最后生还者》来的珍贵,只能说这使得本作更加具有人情味,让人能真正的沉浸在奈森的思绪中。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160802/uncharted4/03.jpg" alt="uncharted4"></p>
<p>再来谈谈盗贼末路的主题,显式的意义上来说,这代表了海贼王(?我忘了他的名字了)的终末。数位大海贼建立海贼乌托邦之后,仍然怀着对宝物的疯狂痴迷,在掠夺居民的财宝之后仍然不能满足,互相争斗,最后海贼王及其副船长暗算了其他大海贼,包揽了所有的宝藏准备离开,但是两人都忍耐不住独占宝藏的诱惑,在宝物仓库最后展开殊死搏斗,最后两败俱伤,葬身于宝藏堆中。对于海贼王来说,这是一个不怎么光彩,令人惋惜,但是却又十分符合其海贼身份的结局,终其一生都是为了宝物而活,然而面对死亡,最后却不能带走什么,其一生又有什么意义。现实中的海贼大概都是这种人,像One Piece里面的那么仗义的海贼怕是只能存在于漫画之中。而宝藏猎人在广义上也是盗贼,只不过偷得是死人的东西,他们的终末又如何。拉夫怕是最接近海贼王的人了,所做的一切都是为了宝藏,为了得到宝藏可以不择手段,交友合作全都是为了宝藏,做了的约定也可以出尔反尔,典型的见钱眼开的人。然而他实际上并不缺钱,所以他并不是简单的为了谋财的宝藏猎人,海贼王也不缺钱,但是他觉得所有宝藏都应该是他的所有,他不能容忍其他人跟他分享宝藏,可是拉夫不一样,他不是要独占宝藏,他只是想证明,他自己有能力发现宝藏,所以他其实为的是名誉。靠别人才能找到宝藏或者没有找到全部的宝藏也是他所不能容忍的,所以他对奈森一行人常常赶尽杀绝。最后宝藏仓之战,拉夫还是阴险狡诈,穷途绝路,仍然不放弃独占名誉的念头,某种程度上也是挺让人敬佩的,可惜他终究也只能落得跟海贼王一样的下场,这就是拉夫的末路。</p>
<p>山姆作为宝藏猎人的目标跟拉夫有点相似,所以他们一开始能够进行合作,拉夫也愿意救他出狱。一开始山姆性格比较接近于拉夫,希望证明自己,但是与拉夫不同,他还有个弟弟,一个他爱护着的弟弟。山姆出狱后其实并不想打扰奈森的平淡日常,但是找到海贼王宝藏是他的梦想,他深知以一己之力是难以达成的,迫于无奈才欺骗奈森帮忙,但是种种细节都能看出,他实际上是还是内疚的,比如在艾莲娜面前的沉默。终结之岛上,兄弟俩被拉夫逮到并且山姆的谎言被拆穿后,山姆还是奋不顾身替奈森挡子弹,殊不知奈森心中已经有了无可替代的人,并且即将登场。山姆虽然有野心,但是还是比较讲道义的,你打了我弟弟我可不会愿意帮你忙,一路逃离拉夫的追击。但是他也不是轻易放弃目标之人,一行人与其汇合之后要求他一起逃离终结之岛,明明知道以一己之力难以到达终点,他还是趁机开溜,重新走回夺宝之路。生命与宝藏相比,他还是选择宝藏,前提是亲人好友的性命已经得到保障。虽然最后还是要靠奈森来救,但是他不会觉得耻辱,而是对奈森感激,而且为两兄弟同心协力感到光荣,这是他跟拉夫的不同之处,这时的山姆更像是一代里的奈森,他走的是纯粹的冒险家之路,称不上是末路,但是估计会走上跟奈森类似的人生吧。</p>
<p>奈森所走的路,则跟前作有所区别,他虽然有着冒险精神,也有对于宝藏的执着,但他有了一些以前没有的东西,那就是亲情和爱情。若只有他一个人,他可以奋不顾身,但是他不希望将所爱之人置于险境,虽然被保护的人本身可能不那样想。对于欺骗艾莲娜这件事,奈森也是认识到,即使是为了她好,而将自己身陷危险这件事进行隐瞒,对她也是一种伤害。有时候可能坦诚相待,一起面对困难才能更有效的解决问题,也不会导致隔阂的出现。这些因素也使奈森对宝藏没有执著,知足常乐,只要发现并证实海贼王的宝藏存在,就足够了。可惜山姆却不这么认为,他一意孤行,奈森也不能置之不理。也是只有奈森这样淡泊名利的纯粹冒险家,才能在最后关头临危不惧,冷静应对嫉恨成魔的拉夫,也能顺利帮助山姆逃出险境。结局也颇为之温馨,这里不过多说明结局,总之奈森的冒险人生是圆满结束,过上安稳不失刺激的幸福生活了,这就是奈森的末路。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160802/uncharted4/04.jpg" alt="uncharted4"></p>
<p>以上的种种都使得《神海4》成为了一个更有深度的游戏,抽掉动作成分,也是一个值得探究的剧情电影,这是前作做不到的。不过可惜的是海贼王的经历并没有给我们过多的震撼,与游戏过程渲染出来的紧张气氛有点格格不入,可能气氛渲染上更多的是受到《最后生还者》的影响,时时刻刻我都在担心有只僵尸跳出来,与《神海4》这样的冒险剧情不太搭调,倒是增添了几分暗黑色彩。不过这些都并不能影响《神海4》的优秀,虽然我更喜欢《最后生还者》的大叔与罗莉,并且《神海4》的表现令《最后生还者2》更加值得期待。</p>
]]></content>
<summary type="html">
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160802/uncharted4/01.jpg" alt="uncharted4"></p>
<p>作为一个从PS4才成为大法教徒的我,神秘海域系列也是到了PS4版123合集才真正了体验到这一系列的游戏。虽然大家都称之为神作,但是我当时玩到的时候却不觉得惊艳,可能是因为我先玩的《最后生还者》重制版。</p>
</summary>
<category term="随笔" scheme="http://blog.sprabbit.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="游戏" scheme="http://blog.sprabbit.com/tags/%E6%B8%B8%E6%88%8F/"/>
<category term="PS4" scheme="http://blog.sprabbit.com/tags/PS4/"/>
</entry>
<entry>
<title>参与 Final LoveLive 转播是怎样一种感受</title>
<link href="http://blog.sprabbit.com/2016/04/11/final-lovelive/index/"/>
<id>http://blog.sprabbit.com/2016/04/11/final-lovelive/index/</id>
<published>2016-04-11T03:18:32.000Z</published>
<updated>2016-07-24T02:45:08.213Z</updated>
<content type="html"><![CDATA[<p>其实我去完回来就想写一篇这样的感想,但是一直没有酝酿好情绪,直到今天,我的工作生活发生了一个在我控制范围之外的变化,我才联想到这一件事。至于工作的问题,后面有机会再说。以下开启知乎体。</p>
<p>我没能去现场,去现场实在是难,难于上青天。不过我有幸买到 4.1 广州站转播的票,比只能在场外打 call 的 dalao 们是幸运一点。能参加这一次的转播其实我感受挺深的,因为这是我第一次参加 LoveLive 的演唱会(转播),也没想到这会是最后一次。</p>
<a id="more"></a>
<p>其实一开始我知道可以参加 LoveLive 演唱会转播的时候,我是拒绝的,因为你不能让我去香港我就马上去,而且实际上我已经是半退圈状态,虽然有收集一些 LoveLive 手办周边啥的,但是游戏已经很久没碰过,虽然最后一次连抽到两张 UR,这也没有激起我的热情,反而让我觉得是时候结束了。直到我得知有 Final LoveLive 广州站的演唱会转播以及购票渠道,而冥冥中,我觉得时候后借此机会,对我的 LoveLive 生涯画上一个圆满的句号(虽然叫做 Final LoveLive,但是我以为只是响应剧场版的名字,并不知道这是终结演出)。可能有人会觉得,比起某些真爱dalao来说,我不配拥有这张票,买到票后我也时常这么想,但是我不能否认,这对我来说可能是一个人生中的转折点,对我以后要走的道路可能会有重要的意义。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/microMsg.1459528650659.jpg" alt="荧光棒"></p>
<p>参加演唱会之前,我临时买了应援用的电子荧光棒还有30支一袋的极橙OU化学棒,复习了一些以往的演唱会,学习打call,但是不能说是熟练。演出当天,演出前1小时我打车到达剧院,这时门外已经聚集了一大波dalao,里面也排起了长龙,我还妄想能有场贩销售,实属天真,早在上午9点就已被抢光,虽然大部分都是被黄牛买走,但是我这种下午才到的人也是不值得拥有。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160401_141007.jpg" alt="排队"></p>
<p>排队进场后转播开始前,大部分人都在根据背景音乐来练习挥棒以及打call,直到直播开始。镜头直接东京巨蛋会场,中央舞台被光点的海洋包围,而我们仿佛就是这片海洋的一份子,涌动着激情。穿透屏幕,我们和现场的观众一起,聆听着演出现场的背景音乐,打着call,热情地等待9位小姐姐的登场。随着会场忽然的静寂,音乐响起,9位小姐姐们让人熟悉的歌声纷至沓来,现场以及转播现场的我们达到沸点,不自主的疯狂挥舞着手中的荧光棒,迎接她们的到来。不得不提转播的一个好处,就是有大量的特写镜头,每一个小姐姐都看得清清楚楚,而现场位置不那么好的观众就只能感受舞台上的小姐姐,或者观看远处的大屏幕了。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160401_141242.jpg" alt="准备开场"></p>
<p>就算不会打call,身体也会跟着一些带领打call的dalao们律动起来,根据小姐姐们的演唱变色,听到喜欢的歌曲,感动下泪。但是顾着打call的正确性,很多时都会忘了感动。有几次熟悉的歌曲唱到泪点的时候,几声fuwa硬生生的把我的眼泪塞了回去。我不是反对打call,但是有时打call确实影响到别人的观感,因为打call追求的是整齐和大声,却不包含感情,一句含情脉脉的歌词,搭配的是一句句怒吼,会一定程度影响现场的感官享受。</p>
<p>encore部分,虽然小姐姐们听不到我们说话,但是我们还是喊得声嘶力竭,仿佛声音真的会通过某种媒介传达到巨蛋现场。第一次encore换来了最令人感动的 snow halation,但是因为我是首次参加演唱会,掰荧光棒略显笨拙,变成八爪鱼之后果王独唱部分已经结束,我甚至没有留意到,白白浪费了一次终极的泪点,是我全场最大的遗憾。不过大家都十分整齐迅速,令人欣慰。然而我此时才知道,Final LoveLive 真的是 μ’s 的最后一次演出,虽然不会解散,但是也不会再有演唱会以及新曲,顿时倍感失落。第二 encore 部分,μ’s 成员以剧场版最后的装扮,歌曲以及布景,完美的重现了最后的演出,想到剧场版的主题,不禁黯然。</p>
<p>转播终止,现场观众仍然意犹未尽,自发演唱了几曲,但是我挥舞荧光棒的手臂已然麻木,脑袋已被掏空。好一会儿之后,大家才开始陆续不舍地退出会场,剧院门口ller围得水泄不通,看到场外的ller也满头大汗,看来他们对打call也没有怠慢。人群久久没有散去,而我只是默默地离开了此地。</p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160401_201117.jpg" alt="离场1"></p>
<p><img src="http://7xsush.com1.z0.glb.clouddn.com/sprabbit/images/20160401_201342.jpg" alt="离场2"></p>
<p>原想借次机会ll毕业,没想到却更加的不舍。人啊,总是到了失去的时候才懂得珍惜,我再次切身体会到这条真理。虽然小姐姐们不会再一起出场,但是我相信每位小姐姐的演艺事业都不会就此终止,衷心祝愿各位小姐姐前程似锦,也感谢小姐姐们让我找回追逐梦想的感觉。此时此刻,我将带着 μ’s 的精神重新出发,被工作冲淡的生活中,重拾梦想。</p>
<p>μ’sic forever!</p>
]]></content>
<summary type="html">
<p>其实我去完回来就想写一篇这样的感想,但是一直没有酝酿好情绪,直到今天,我的工作生活发生了一个在我控制范围之外的变化,我才联想到这一件事。至于工作的问题,后面有机会再说。以下开启知乎体。</p>
<p>我没能去现场,去现场实在是难,难于上青天。不过我有幸买到 4.1 广州站转播的票,比只能在场外打 call 的 dalao 们是幸运一点。能参加这一次的转播其实我感受挺深的,因为这是我第一次参加 LoveLive 的演唱会(转播),也没想到这会是最后一次。</p>
</summary>
<category term="随笔" scheme="http://blog.sprabbit.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="LoveLive" scheme="http://blog.sprabbit.com/tags/LoveLive/"/>
<category term="ACG" scheme="http://blog.sprabbit.com/tags/ACG/"/>
</entry>
<entry>
<title>为什么 React 体积那么大</title>
<link href="http://blog.sprabbit.com/2016/03/21/why-is-react-so-large/"/>
<id>http://blog.sprabbit.com/2016/03/21/why-is-react-so-large/</id>
<published>2016-03-21T09:36:32.000Z</published>
<updated>2016-07-24T02:45:08.208Z</updated>
<content type="html"><![CDATA[<p>React 文件体积为何如此庞大?首先,为什么会得出 React 大的结论,对比几个前端框架的 min 文件:</p>
<ul>
<li>Mithril 0.2.3 19K (8K gzipped)</li>
<li>Angular 1.2.16 102K (38K gzipped)</li>
<li>Vue 1.0.8 73K (24K gzipped)</li>
<li>React 0.14.7 133K (38K gzipped)</li>
</ul>
<p>React 作为一个 View-ViewModel 库,相比于 Mithril,Vue 这些目的大致相同的库,文件显得尤为庞大,甚至比 Angular 这种全能 MVVM 框架还大。但是这就能说 React 大吗,我认为是的,Mithril 与 React 都是基于 Virtual DOM 的实现,我觉得这很有可比性。虽然 Mithril 的真实性能大致为 React 的一半,但是代码量却是 React 的不到 1/10(实际上 Mithril 只有大概 2000 行代码),Mithril 在使用了一些小技巧之后甚至性能飙升至 React 的数十倍。对 Mithril 感兴趣可以看下 <a href="/2016/03/14/Mithril-Rendering/" title="我对 Mithril 渲染性能的分析">我对 Mithril 渲染性能的分析</a>。</p>
<a id="more"></a>
<p>通过分析 React 的源码及其打包过程,可以得出 React 的成分构成:</p>
<ul>
<li>React: 687KB 100%<ul>
<li>renderers 这部分代表了 Virtual DOM 的渲染部分<ul>
<li>ReactDOM 316KB 55% 主要作用是将 Virtual DOM 渲染成真实 DOM,并进行关联以及之后的 DOM 操作,DOM 事件处理框架</li>
<li>ReactDOMServer 9KB 1% 这部分代码很大程度上重用 ReactDOM 所以代码量不多</li>
<li>ReactReconciler 106KB 15% 这部分代表了 React 的差异比较以及做出 DOM 操作的调和算法</li>
<li>ReactEvent 63KB 9%</li>
</ul>
</li>
<li>ReactIsomorphic 103KB 15% 这部分是 Virtual DOM 的结构代码,包括 ReactComponent,ReactClass,ReactElement 等的实现</li>
<li>辅助测试代码 48KB 7% 可能这里有一些没有打包的模块,但是确实有一些如 ReactPerf,ReactDefaultPerf 等模块是打包进去了</li>
<li>其他依赖库 38KB 6%</li>
</ul>
</li>
</ul>
<p>这里分析的百分比是打包进 react.js(压缩前)的各部分源码文件大小(及其依赖文件)占所有打包文件大小(不包括addons以及fbjs)的百分比,跟压缩合并后的百分比有一定差别,但是可以一定程度上代表代码成分。从成分看来,实际上 VirtualDOM 的代码并不多,大部分代码都放在跟真实 DOM 相关的操作里面,尽管从认知上来看,这些操作并不需要写如此多的代码。我认为导致代码量大的原因大致有以下几点。</p>
<h3 id="注重安全性"><a href="#注重安全性" class="headerlink" title="注重安全性"></a>注重安全性</h3><p>严格的 Virtual DOM 检查以及 ReactDOM 操作,包括 props 类型,经典 DOM 元素的标签名,属性名,CSS属性名,标签嵌套合法性,支持的原生 DOM 事件类型,等等都有明确的定义。React 毫不吝啬地在所有涉及安全问题的地方使用白名单来提高安全性,这也是代码量大的原因之一:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> HTMLDOMPropertyConfig = {</div><div class="line"> isCustomAttribute: <span class="built_in">RegExp</span>.prototype.test.bind(</div><div class="line"> <span class="regexp">/^(data|aria)-[a-z_][a-z\d_.\-]*$/</span></div><div class="line"> ),</div><div class="line"> Properties: {</div><div class="line"> <span class="comment">/**</span></div><div class="line"> * Standard Properties</div><div class="line"> */</div><div class="line"> accept: <span class="literal">null</span>,</div><div class="line"> acceptCharset: <span class="literal">null</span>,</div><div class="line"> accessKey: <span class="literal">null</span>,</div><div class="line"> action: <span class="literal">null</span>,</div><div class="line"> allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,</div><div class="line"> allowTransparency: MUST_USE_ATTRIBUTE,</div><div class="line"> alt: <span class="literal">null</span>,</div><div class="line"> <span class="keyword">async</span>: HAS_BOOLEAN_VALUE,</div><div class="line"> autoComplete: <span class="literal">null</span>,</div><div class="line"> <span class="comment">// autoFocus is polyfilled/normalized by AutoFocusUtils</span></div><div class="line"> <span class="comment">// autoFocus: HAS_BOOLEAN_VALUE,</span></div><div class="line"> autoPlay: HAS_BOOLEAN_VALUE,</div><div class="line"> capture: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,</div><div class="line"> cellPadding: <span class="literal">null</span>,</div><div class="line"> cellSpacing: <span class="literal">null</span>,</div><div class="line"> charSet: MUST_USE_ATTRIBUTE,</div><div class="line"> challenge: MUST_USE_ATTRIBUTE,</div><div class="line"> checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,</div><div class="line"> classID: MUST_USE_ATTRIBUTE,</div><div class="line"> <span class="comment">// To set className on SVG elements, it's necessary to use .setAttribute;</span></div><div class="line"> <span class="comment">// this works on HTML elements too in all browsers except IE8. Conveniently,</span></div><div class="line"> <span class="comment">// IE8 doesn't support SVG and so we can simply use the attribute in</span></div><div class="line"> <span class="comment">// browsers that support SVG and the property in browsers that don't,</span></div><div class="line"> <span class="comment">// regardless of whether the element is HTML or SVG.</span></div><div class="line"> className: hasSVG ? MUST_USE_ATTRIBUTE : MUST_USE_PROPERTY,</div><div class="line"> cols: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,</div><div class="line"> colSpan: <span class="literal">null</span>,</div><div class="line"> content: <span class="literal">null</span>,</div><div class="line"> contentEditable: <span class="literal">null</span>,</div><div class="line"> contextMenu: MUST_USE_ATTRIBUTE,</div><div class="line"> controls: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,</div><div class="line"> coords: <span class="literal">null</span>,</div><div class="line"> crossOrigin: <span class="literal">null</span>,</div><div class="line"> data: <span class="literal">null</span>, <span class="comment">// For `<object />` acts as `src`.</span></div><div class="line"> dateTime: MUST_USE_ATTRIBUTE,</div><div class="line"> <span class="keyword">default</span>: HAS_BOOLEAN_VALUE,</div><div class="line"> defer: HAS_BOOLEAN_VALUE,</div><div class="line"> <span class="comment">// ... 省略150行</span></div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="实现同构"><a href="#实现同构" class="headerlink" title="实现同构"></a>实现同构</h3><p>ReactDOM 在前端渲染需要做同构处理,即将服务端返回的第一遍渲染出来的 html DOM 绑定到前端 Virtual DOM 树,这增加了不少 ReactDOM 和 ReactDOMServer 的共用代码,因为必须兼顾浏览器端和服务端渲染的一致性。</p>
<p>除此之外,ReactDOMServer 即使在前端渲染不会用到,但是为了前后端使用同一份代码,所以依然打包进了 react.js,这也增加了一些体积。</p>
<h3 id="开发者友好"><a href="#开发者友好" class="headerlink" title="开发者友好"></a>开发者友好</h3><p>大量的提示文本,针对每个模块方法的误用或被抛弃的方法,都有具体详细的提示,这些提示混淆压缩后长度不变:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">warning(</div><div class="line"> owner._warnedAboutRefsInRender,</div><div class="line"> <span class="string">'%s is accessing getDOMNode or findDOMNode inside its render(). '</span> +</div><div class="line"> <span class="string">'render() should be a pure function of props and state. It should '</span> +</div><div class="line"> <span class="string">'never access something that requires stale data from the previous '</span> +</div><div class="line"> <span class="string">'render, such as refs. Move this logic to componentDidMount and '</span> +</div><div class="line"> <span class="string">'componentDidUpdate instead.'</span>,</div><div class="line"> owner.getName() || <span class="string">'A component'</span></div><div class="line">);</div></pre></td></tr></table></figure></p>
<p>另外,喜欢使用完整的语句作为方法及属性名,如 <code>registrationNameDependencies</code>,也是导致容量大的原因,因为方法名和属性名即使经过压缩混淆,长度也不会改变。以下是压缩后的 React 入口模块代码(格式化):</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="meta">"use strict"</span>;</div><div class="line"><span class="keyword">var</span> r = e(<span class="number">35</span>),</div><div class="line">o = e(<span class="number">45</span>),</div><div class="line">a = e(<span class="number">61</span>),</div><div class="line">i = e(<span class="number">23</span>),</div><div class="line">u = e(<span class="number">104</span>),</div><div class="line">s = {};</div><div class="line">i(s, a),</div><div class="line">i(s, {</div><div class="line"> findDOMNode : u(<span class="string">"findDOMNode"</span>, <span class="string">"ReactDOM"</span>, <span class="string">"react-dom"</span>, r, r.findDOMNode),</div><div class="line"> render : u(<span class="string">"render"</span>, <span class="string">"ReactDOM"</span>, <span class="string">"react-dom"</span>, r, r.render),</div><div class="line"> unmountComponentAtNode : u(<span class="string">"unmountComponentAtNode"</span>, <span class="string">"ReactDOM"</span>, <span class="string">"react-dom"</span>, r, r.unmountComponentAtNode),</div><div class="line"> renderToString : u(<span class="string">"renderToString"</span>, <span class="string">"ReactDOMServer"</span>, <span class="string">"react-dom/server"</span>, o, o.renderToString),</div><div class="line"> renderToStaticMarkup : u(<span class="string">"renderToStaticMarkup"</span>, <span class="string">"ReactDOMServer"</span>, <span class="string">"react-dom/server"</span>, o, o.renderToStaticMarkup)</div><div class="line">}),</div><div class="line">s.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = r,</div><div class="line">s.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = o,</div><div class="line">t.exports = s</div></pre></td></tr></table></figure>
<h3 id="过度模块化"><a href="#过度模块化" class="headerlink" title="过度模块化"></a>过度模块化</h3><p>react.js 里面包含了151个模块的定义,平均每个模块化增加的额外代码量:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 模块编译后生成代码</span></div><div class="line"><span class="number">2</span> : [<span class="function"><span class="keyword">function</span> (<span class="params">e, t, n</span>) </span>{</div><div class="line"> <span class="comment">// 原模块定义代码</span></div><div class="line"> }, {</div><div class="line"> <span class="number">106</span> : <span class="number">106</span>,</div><div class="line"> <span class="number">136</span> : <span class="number">136</span>,</div><div class="line"> <span class="number">63</span> : <span class="number">63</span></div><div class="line"> }</div><div class="line">]</div></pre></td></tr></table></figure>
<p>每个模块增加的代码约为45Byte,加上统一处理函数,共约为7KB的大小。虽然看起来不多,但实际上这占了压缩后的 react.min.js(133KB) 的 5% 的大小。至于真的需要写那么多个模块吗,我觉得是不用的,至少一个模块里面只有一个函数这种(如 onlyChild 模块)是可以集中写的。除此之外,虽然有如此庞大的模块集合,React 的模块间耦合还是很高,模块间相互调用十分繁多。</p>
<h3 id="其他依赖"><a href="#其他依赖" class="headerlink" title="其他依赖"></a>其他依赖</h3><p>觉得 React 大的原因除了 react.js 本身的庞大外,还有需要和 React 搭配使用的库也有很多,包括 React 自己的 addons,封装好的 http 请求,Flux,Redux 等框架,处理复杂状态的 Immutable.js 等等等。另外如果要适配 IE8 还要引入一堆 polyfill 也增加了不少容量。</p>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>React 确实很大,但是也并非大而无当,起码出发点是好的。但是有没有优化的空间呢,我认为是有的,起码如果区分开发版和发行版能有效的去掉对用户来说没有用的开发者提示。</p>
<h2 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h2><p>我们知道我们在程序中需要使用 react-dom.js 来使用 ReactDOM,然而实际上这个文件并不真的包含 ReactDOM 的实现,按照上面的分析 ReactDOM 是 react.js 的一部分:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOM;</div><div class="line">React.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOMServer;</div></pre></td></tr></table></figure>
<p>那么 react-dom.js 到底干了啥,我们来看看:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span>(<span class="params">React</span>) </span>{</div><div class="line"> <span class="keyword">return</span> React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>你TM逗我。其实这也可以理解,React 内部模块的耦合决定了 ReactDOM 不可能单独抽取出来用,react-dom.js 这个模块文件只是给我们暴露一个入口。</p>
]]></content>
<summary type="html">
<p>React 文件体积为何如此庞大?首先,为什么会得出 React 大的结论,对比几个前端框架的 min 文件:</p>
<ul>
<li>Mithril 0.2.3 19K (8K gzipped)</li>
<li>Angular 1.2.16 102K (38K gzipped)</li>
<li>Vue 1.0.8 73K (24K gzipped)</li>
<li>React 0.14.7 133K (38K gzipped)</li>
</ul>
<p>React 作为一个 View-ViewModel 库,相比于 Mithril,Vue 这些目的大致相同的库,文件显得尤为庞大,甚至比 Angular 这种全能 MVVM 框架还大。但是这就能说 React 大吗,我认为是的,Mithril 与 React 都是基于 Virtual DOM 的实现,我觉得这很有可比性。虽然 Mithril 的真实性能大致为 React 的一半,但是代码量却是 React 的不到 1/10(实际上 Mithril 只有大概 2000 行代码),Mithril 在使用了一些小技巧之后甚至性能飙升至 React 的数十倍。对 Mithril 感兴趣可以看下 <a href="/2016/03/14/Mithril-Rendering/" title="我对 Mithril 渲染性能的分析">我对 Mithril 渲染性能的分析</a>。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="React" scheme="http://blog.sprabbit.com/tags/React/"/>
<category term="Mithril" scheme="http://blog.sprabbit.com/tags/Mithril/"/>
</entry>
<entry>
<title>ARV 渲染实现番外篇之 Mithril</title>
<link href="http://blog.sprabbit.com/2016/03/14/Mithril-Rendering/"/>
<id>http://blog.sprabbit.com/2016/03/14/Mithril-Rendering/</id>
<published>2016-03-14T09:21:32.000Z</published>
<updated>2016-07-24T02:45:08.201Z</updated>
<content type="html"><![CDATA[<p>这个系列本来没有打算去研究 <a href="https://angular.io" target="_blank" rel="external">Angular</a>,<a href="https://facebook.github.io/react" target="_blank" rel="external">React</a>,<a href="http://vuejs.org/" target="_blank" rel="external">Vue</a> 之外的框架,但是 <a href="/2016/03/08/Angular-React-Vue-Rendering-4/" title="上次的性能测试">上次的性能测试</a> 让 <a href="http://mithril.js.org/" target="_blank" rel="external">Mithril</a> 这个框架格外引人注目,毕竟他的成绩超过了其他所有框架。因为我没有在项目中用过 Mithril,所以只能从其文档及代码中寻找他惊人能力背后的实现。因为这个是番外篇了,所以可能不会太严谨,科普性质吧。</p>
<h2 id="Mithril"><a href="#Mithril" class="headerlink" title="Mithril"></a>Mithril</h2><p>Mithril 翻译过来就是秘银的意思,这是魔幻小说或游戏里面才有的虚构金属物质,基本上是最好的金属,可以打造出最上等的武器装备。可能作者希望这个库就是那么厉害的东西,而且用 Mithril 做出来的产品也是上等的吧。他的第一个版本于 2014 年 3 月发布,相比 React (2013 年 5 月),Vue (2013 年 12 月),都要晚一点,而现在也只发布到 0.2.3 版本。</p>
<a id="more"></a>
<h3 id="Start-Up"><a href="#Start-Up" class="headerlink" title="Start Up"></a>Start Up</h3><p>Mithril 的启动代码大致如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> app = {</div><div class="line"> controller: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{},</div><div class="line"> view: <span class="function"><span class="keyword">function</span>(<span class="params">ctrl</span>) </span>{</div><div class="line"> <span class="keyword">return</span> (</div><div class="line"> m(<span class="string">"body"</span>, [</div><div class="line"> m(<span class="string">"input"</span>),</div><div class="line"> m(<span class="string">"button"</span>, <span class="string">"Add"</span>)</div><div class="line"> ])</div><div class="line"> );</div><div class="line"> };</div><div class="line">};</div><div class="line">m.mount(<span class="built_in">document</span>.getElementById(<span class="string">"example"</span>), app);</div></pre></td></tr></table></figure>
<p>思维敏锐的你一定看出来了,这结构跟 ReactComponent 不是差不多。实际上,连 Mithril 的书写格式都可以使用类似 JSX 这种预编译模版来写,他们称之为 <a href="https://github.com/insin/msx" target="_blank" rel="external">MSX</a> 是利用 React 的 JSX 改写而来的:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> app = {</div><div class="line"> controller: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{},</div><div class="line"> view: <span class="function"><span class="keyword">function</span>(<span class="params">ctrl</span>) </span>{</div><div class="line"> <span class="keyword">return</span> (</div><div class="line"> <span class="xml"><span class="tag"><<span class="name">body</span>></span></span></div><div class="line"> <span class="tag"><<span class="name">input</span>></span></div><div class="line"> <span class="tag"><<span class="name">button</span>></span>Add<span class="tag"></<span class="name">button</span>></span></div><div class="line"> <span class="tag"></<span class="name">body</span>></span></div><div class="line"> );</div><div class="line"> };</div><div class="line">};</div><div class="line">m.mount(document.getElementById("example"), app);</div></pre></td></tr></table></figure>
<h3 id="Virtual-DOM"><a href="#Virtual-DOM" class="headerlink" title="Virtual DOM"></a>Virtual DOM</h3><p>怎么会出现 Virtual DOM,没错,Mithril 确实是用 Virtual DOM 实现的,你完全可以按照 React 的 Virtual DOM 来理解 Mithril 的 Virtual DOM。对 Virtural 建议先去看我 <a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="关于 React 渲染的分析">关于 React 渲染的分析</a>。但是他们确实在细节上又有些不同。</p>
<h3 id="Cell"><a href="#Cell" class="headerlink" title="Cell"></a>Cell</h3><p>cell 即是虚拟 DOM 元素,有一个 children 属性指定内部子元素,对应于 React 的 ReactElement。可以使用 <code>m</code> 命令创建:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">m(tag, attrs, children, ...);</div></pre></td></tr></table></figure>
<p>Cell 可以是一个普通 DOM 元素或者一个 Component 实例。</p>
<h3 id="Tag"><a href="#Tag" class="headerlink" title="Tag"></a>Tag</h3><p>代表一个 Cell 的类型,可以是一个查询字符串,类似 <code>div.classname#id[param=one][param2=two]</code>,或者一个 ComponentClass 对象。</p>
<h3 id="ComponentClass"><a href="#ComponentClass" class="headerlink" title="ComponentClass"></a>ComponentClass</h3><p>ComponentClass 是一个自定义组件类对象,用于构造 Component 实例。包含 <code>view</code> 和 <code>controller</code> 属性,<code>view</code> 返回一个 Cell,而 <code>controller</code> 就像是一个初始化函数,将在第一次渲染前执行 ,在里面需要初始化 ViewModel:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> myComponent = {</div><div class="line"> controller: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.data = m.prop()</div><div class="line"> },</div><div class="line"> view: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">return</span> m(<span class="string">"body"</span>)</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>Mithril 的 ComponentClass 对应于 React 的 ReactClass,<code>view</code> 对应于 <code>render</code>, <code>controller</code> 类似 <code>componentWillMount</code> 方法。而 controller 需要做的事情包括初始化 ViewModel,这又跟 ReactClass 的 <code>getInitialState</code> 类似。</p>
<h3 id="CellCache"><a href="#CellCache" class="headerlink" title="CellCache"></a>CellCache</h3><p>是指一个根 Cell 的快照,他包含了其孩子 Cell 在内的一棵完整的虚拟 DOM 树,每一个根 Cell 在第一遍渲染的时候都会获得一个全局唯一的 CellCacheKey,并在每次 Redraw 的时候根据这个 Key 取出对应的虚拟 DOM 树与当前的虚拟 DOM 树进行对比,完成对比后再将当前虚拟 DOM 树创建快照并替换。CellCache 跟原本的 Cell 对象是不同的引用,CellCache 会比原始 Cell 对象多出来一个 <code>nodes</code> 属性,包含跟这个 Cell 关联的所有真实 DOM 元素。</p>
<h3 id="Redraw"><a href="#Redraw" class="headerlink" title="Redraw"></a>Redraw</h3><p>Redraw 指的是 Mithril 的一遍渲染过程,他会执行一次对虚拟 DOM 数的自顶向下差异比较(diff),然后进行仅必要的 DOM 操作以重绘视图。</p>
<p>Mithril 使用的 diff 算法跟 React 还是有一点区别的:</p>
<p>元素级别的对比:</p>
<ul>
<li>若一个 Cell 没有被缓存(Cached),则创建元素并保存 CellCache</li>
<li>若一个 Cell 有对应的 CellCache,并满足以下至少一项,则创建新 DOM 元素,否则在当前元素上做修改:<ul>
<li>Tag 不一致</li>
<li>属性名(attrs)有增减或改变</li>
<li>id/key属性的值有改变</li>
</ul>
</li>
<li>若一个 Cell 的父节点发生变化,则将自己绑定到新的父元素上</li>
</ul>
<p>列表级别的对比:</p>
<ul>
<li>为新列表中没有 Key 属性的元素创建全局唯一 Key</li>
<li>若新列表中有,旧列表中没有的 Key,则标记为创建</li>
<li>若新列表中有,旧列表中也有的 Key,则标记为移动</li>
<li>若新列表中有,旧列表中没有的 Key,则标记为删除</li>
<li>执行标记的 DOM 操作</li>
</ul>
<p>比较算法自顶向下递归地遍历对比每一个 Cell 与 CellCache 及其 children,并作出对应的 DOM 修改操作。</p>
<h3 id="Redraw-时机"><a href="#Redraw-时机" class="headerlink" title="Redraw 时机"></a>Redraw 时机</h3><p>重绘会在组件 controller 执行后或事件触发后进行,也就是说自动的视图更新需要在 Mithril 的体系下进行,比如 <code>m.request</code>,<code>controller</code>,<code>onClick</code> 等,这与 Angular 类似。如果在体系外,比如使用 <code>setTimeout</code>,则需要在改变 ViewModel 的代码前后加上 <code>m.startComputation</code> 和 <code>m.endComputation</code> 这对计算标记:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</div><div class="line"> m.startComputation()</div><div class="line"> vm.update()</div><div class="line"> m.endComputation()</div><div class="line">})</div></pre></td></tr></table></figure>
<p>而实际上所有可以响应的 ViewModel 变动都是在 <code>m.startComputation</code> 和 <code>m.endComputation</code> 之间进行的, Mithril 体系内也是如此处理。一对计算标记内可以嵌套另外的计算标记对,重绘只会在嵌套最外层的 <code>m.endComputation</code> 调用后执行,这是通过计数器实现的,在调用 <code>m.startComputation</code> 时计数加一,调用 <code>m.endComputation</code> 时减一,当减为 0 时则会调用更低级的 <code>m.redraw</code> 方法。所以可以通过使用计算标记,可以保证将一系列连续的操作执行完毕后再进行重绘,避免在修改过程中因为异步操作引发的不必要的重绘操作。</p>
<p>然而,调用 <code>m.redraw</code> 也并不一定真正进行重绘,Mithril 在重绘上采取了跟(游戏)渲染引擎类似的锁帧操作,即在一个预设好的时间段内的数据改变,延迟到时间段结束的时候再进行重绘。对于游戏一般需要 30-60fps 以上的刷新率才能算是连贯,而 Mithril 则采用了 60fps,即一个重绘周期为 1/60s,约为 16ms。</p>
<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>如果 Mithril 只有 diff,那么他跟 React 的性能基本上是差不多的。Mithril 在性能上致胜的关键在于重绘时机的选取:</p>
<ul>
<li><code>m.startComputation</code> 和 <code>m.endComputation</code> 保证了在一次连续(异步)操作内不会促发重绘</li>
<li>锁帧操作保证了在肉眼可感知到的变化周期内(16ms)不会重复渲染</li>
</ul>
<p>事实上我做了另外一个 <a href="http://jsperf.com/angular-vs-knockout-vs-ember/843" target="_blank" rel="external">性能对比测试</a>,与<a href="http://jsperf.com/angular-vs-knockout-vs-ember/842" target="_blank" rel="external">上一次测试不同</a>,我在 Mithril 的测试样本进行一遍变更后调用 <code>m.redraw(true)</code> 直接强制一遍重绘来取消锁帧的影响(这很公平,因为其他框架也可以应用这样的小技巧)。结果跟我想象中差不多:</p>
<img src="/2016/03/14/Mithril-Rendering/benchmark.png" alt="benchmark.png" title="">
<p>Mithril 的性能下降到 React 的一半左右。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>上面的测试结果可以看出来,单从 Virtual DOM 及 diff 算法上来说,Mithril 的效率并没有 React 高,但是 Mithril 仅仅使用了 2000 多行代码就实现了 Virtual DOM,甚至还包括 Router 在内的模块,致力于实现一个平台级的全能 MVC 框架。压缩后 20k 的大小相比 200k 以及需要额外库支持的 React,确实能省掉不少首次加载页面的时间。</p>
<p>但是在我看来他也仅仅实现了 View-ViewModel 部分以及封装了网络请求,并没有真正实现 Model 部分。他的创新之处也就剩下基于帧的重绘时机控制,然而,这个办法或多或少都属于小手段,并不是在逻辑上真正提高渲染效率,只是利用了人类的极限视觉来节省运算。而且这一招并不是没人想到过,只是很多框架都不需要如此极致的效率提高,因为渲染速度一般并不会影响到体验(除非要显示上万条列表数据)。只有在性能出现瓶颈的时候,才需要去做这样那样的小手段,而很多其他框架实际上都是可以另外实现锁帧操作的。</p>
<p>并没有很多人真的在用 Mithril 因为他跟 React 是如此类似(我甚至怀疑作者是不是看完 React 然后自己另外实现了一个),真的要在其中选大多数人也会选 React,毕竟社区健全完善,解决方案众多。但是 Mithril 给了我们很多灵感,比如 React 的代码量是不是可以更加精简,是不是可以通过锁帧进一步提高 React 的效率。</p>
]]></content>
<summary type="html">
<p>这个系列本来没有打算去研究 <a href="https://angular.io">Angular</a>,<a href="https://facebook.github.io/react">React</a>,<a href="http://vuejs.org/">Vue</a> 之外的框架,但是 <a href="/2016/03/08/Angular-React-Vue-Rendering-4/" title="上次的性能测试">上次的性能测试</a> 让 <a href="http://mithril.js.org/">Mithril</a> 这个框架格外引人注目,毕竟他的成绩超过了其他所有框架。因为我没有在项目中用过 Mithril,所以只能从其文档及代码中寻找他惊人能力背后的实现。因为这个是番外篇了,所以可能不会太严谨,科普性质吧。</p>
<h2 id="Mithril"><a href="#Mithril" class="headerlink" title="Mithril"></a>Mithril</h2><p>Mithril 翻译过来就是秘银的意思,这是魔幻小说或游戏里面才有的虚构金属物质,基本上是最好的金属,可以打造出最上等的武器装备。可能作者希望这个库就是那么厉害的东西,而且用 Mithril 做出来的产品也是上等的吧。他的第一个版本于 2014 年 3 月发布,相比 React (2013 年 5 月),Vue (2013 年 12 月),都要晚一点,而现在也只发布到 0.2.3 版本。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
<category term="Angular" scheme="http://blog.sprabbit.com/tags/Angular/"/>
<category term="React" scheme="http://blog.sprabbit.com/tags/React/"/>
<category term="Vue" scheme="http://blog.sprabbit.com/tags/Vue/"/>
<category term="Mithril" scheme="http://blog.sprabbit.com/tags/Mithril/"/>
</entry>
<entry>
<title>ARV 渲染实现比较总结</title>
<link href="http://blog.sprabbit.com/2016/03/08/Angular-React-Vue-Rendering-4/"/>
<id>http://blog.sprabbit.com/2016/03/08/Angular-React-Vue-Rendering-4/</id>
<published>2016-03-08T03:42:32.000Z</published>
<updated>2016-07-24T02:45:08.192Z</updated>
<content type="html"><![CDATA[<p>前面我已经分析了 <a href="https://angular.io" target="_blank" rel="external">Angular</a>,<a href="https://facebook.github.io/react" target="_blank" rel="external">React</a>,<a href="http://vuejs.org/" target="_blank" rel="external">Vue</a> 的渲染实现及其性能:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a></li>
<li><a href="/2016/03/07/Angular-React-Vue-Rendering-3/" title="ARV 渲染实现比较之 Vue">ARV 渲染实现比较之 Vue</a>
</li>
</ul>
<p>直观一点地的看他们之间的效率对比可以看一下这一个 <a href="http://jsperf.com/angular-vs-knockout-vs-ember/842" target="_blank" rel="external">性能评估测试</a>。</p>
<a id="more"></a>
<p>这个测试的原理是针对每一个前端框架,创建一个数组模型到视图的绑定,并重复以下操作:清空列表数据,然后循环对数组插入100个元素。重复操作一段时间,最后将比较他们之间每秒可以达到的操作次数。他比较了包括 Angular,React,Vue 在内的数个流行框架,其中 <a href="http://mithril.js.org/" target="_blank" rel="external">Mithril</a> 的数据十分高,是 Vue 的2到3倍,而且体积十分小压缩后只有十几k,但是很遗憾他并没有列入我们的讨论范围,因为他还在开发阶段,使用人数也并不多。虽然这个测试可能比较片面,现实开发中用到的数据并不会那么简单粗暴,但是他也一定程度上能代表这些框架的性能了。</p>
<p>下面这幅图是这些框架的渲染速度横向对比:</p>
<img src="/2016/03/08/Angular-React-Vue-Rendering-4/benchmark.png" alt="benchmark.png" title="">
<p>在这幅图中,你几乎看不到 Angular 的数据,因为他每秒只能进行 100 次左右的操作。React 的操作数大概在 1000 - 2000 之间,而 Vue 则在 10000 次以上。这也印证了前面几篇文章的分析。</p>
<h2 id="Angular"><a href="#Angular" class="headerlink" title="Angular"></a>Angular</h2><p>Angular 很慢,因为他用了脏值检查这种被动的遍历方法穷举所有监听的表达式。在这个例子中使用了 <code>track by $index</code> 命令,因为数组项是纯字符串。每对数组做一次修改,所有数组项都被重新评估。</p>
<p>做 100 次操作,实际上要比较 100 x 100 = 10000 个数据,这些比较都是逻辑比较,速度应该还行,主要是这些比较导致了 10000 个模版关联,更新 DOM 树的操作,也就这种级别的运算速度了。但是他的慢到不能用吗,实际上也并不,因为正常需求来说不需要这么高频率的更新。</p>
<h2 id="React"><a href="#React" class="headerlink" title="React"></a>React</h2><p>React 每秒能进行 1000 次操作,大约是 Angular 的 10 倍,这得益于 React 的虚拟 DOM 树。这个例子中,使用了列表的序号作为 key。</p>
<p>做 100 次操作,需要做 100 * 100 = 10000 个虚拟 DOM 节点的差异对比,但是因为数组项对应的 DOM 的 key 没有变化,因此并不会对 DOM 树结构进行操作,而仅仅会更新这个元素内部的文字,这个操作的复杂度是线性的。但是这里的虚拟 DOM 节点的差异对比并不是简单的值的对比,耗时比 10000 次比较数据要多,性能的提高点在于省掉大量修改真实 DOM 树结构的时间</p>
<h2 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h2><p>Vue 每秒能进行 10000 次操作,又大约是 React 的 10 倍。每一次对数组的赋值,都直接触发了数据项的对比然后直接更新绑定 DOM 元素的内部文本。没有模版关联,也不用对比虚拟 DOM 节点差异。</p>
<p>做 100 次操作,只需执行 100 x 100 = 10000 求值并对比,然后在线性时间内更新文本,没有其他额外的耗时操作。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>漫长的分析后可以自信地说出这个结果了,渲染性能上 Vue > React > Angular。</p>
<h2 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h2><p>虽然本文仅从渲染性能上对比 Angular,React 和 Vue,但是用于不用这些框架绝对不能仅看性能的。Angular 社区成熟,轮子丰富,开发快捷,适合开发面向客户的产品的需要追求沉稳需求;React 组件化思想浓厚,高度可重用,响应数据变化快捷,结合 Redux 等框架后开发具有复杂交互操作的产品十分便利,但是体积庞大,代码冗长书写速度较慢,适合开发对内的运营项目;Vue 轻便简洁,渲染速度极高,只做一件事并做到极致,但是缺少轮子,而且不支持 IE8,适合开发 Html5 项目,尤其是手机端 SPA。当然,最终的选择,还是看团队需求。</p>
<p>文章链接:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a></li>
<li><a href="/2016/03/07/Angular-React-Vue-Rendering-3/" title="ARV 渲染实现比较之 Vue">ARV 渲染实现比较之 Vue</a></li>
<li><a href="/2016/03/08/Angular-React-Vue-Rendering-4/" title="ARV 渲染实现比较总结">ARV 渲染实现比较总结</a></li>
<li><a href="/2016/03/14/Mithril-Rendering/" title="ARV 渲染实现番外篇之 Mithril">ARV 渲染实现番外篇之 Mithril</a>
</li>
</ul>
]]></content>
<summary type="html">
<p>前面我已经分析了 <a href="https://angular.io">Angular</a>,<a href="https://facebook.github.io/react">React</a>,<a href="http://vuejs.org/">Vue</a> 的渲染实现及其性能:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a></li>
<li><a href="/2016/03/07/Angular-React-Vue-Rendering-3/" title="ARV 渲染实现比较之 Vue">ARV 渲染实现比较之 Vue</a>
</li>
</ul>
<p>直观一点地的看他们之间的效率对比可以看一下这一个 <a href="http://jsperf.com/angular-vs-knockout-vs-ember/842">性能评估测试</a>。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
<category term="Angular" scheme="http://blog.sprabbit.com/tags/Angular/"/>
<category term="React" scheme="http://blog.sprabbit.com/tags/React/"/>
<category term="Vue" scheme="http://blog.sprabbit.com/tags/Vue/"/>
</entry>
<entry>
<title>ARV 渲染实现比较之 Vue</title>
<link href="http://blog.sprabbit.com/2016/03/07/Angular-React-Vue-Rendering-3/"/>
<id>http://blog.sprabbit.com/2016/03/07/Angular-React-Vue-Rendering-3/</id>
<published>2016-03-07T02:47:32.000Z</published>
<updated>2016-07-24T02:45:08.188Z</updated>
<content type="html"><![CDATA[<p>之前已经分析了 <a href="https://angular.io" target="_blank" rel="external">Angular</a> 和 <a href="https://facebook.github.io/react" target="_blank" rel="external">React</a> 的渲染:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a>
</li>
</ul>
<p>今天来分析 <a href="http://vuejs.org/" target="_blank" rel="external">Vue</a>。</p>
<h2 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h2><p>Vue 是一个新兴的前端视图框架,他做的事情跟 React 很类似,实现了前端组件化以及 View-ViewModel 绑定,但是同时又支持跟 Angular 很类似的模版语法,以及比 Angular 更方便的 filter 和 directive 定义方法,大大提高了开发效率。React 和 Angular 都熟悉的开发者转到 Vue 使用起来十分方便得心应手。</p>
<p>如果只是 React 跟 Angular 两者优点的结合,那 Vue 也没那么出彩,然而他的渲染效率甚至是 React 的 10 倍之多,这也是很多开发者转向 Vue 的理由。</p>
<a id="more"></a>
<h3 id="Start-Up"><a href="#Start-Up" class="headerlink" title="Start Up"></a>Start Up</h3><p>Vue 的启动代码大致如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> exampleVM = <span class="keyword">new</span> Vue({</div><div class="line"> el: <span class="string">'#example-1'</span>,</div><div class="line"> data: exampleData</div><div class="line">})</div></pre></td></tr></table></figure>
<p>而定义一个 VueComponent 的代码如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 定义</span></div><div class="line"><span class="keyword">var</span> MyComponent = Vue.extend({</div><div class="line"> template: <span class="string">'<div>A custom component!</div>'</span>,</div><div class="line"> data: {}</div><div class="line">})</div><div class="line"></div><div class="line"><span class="comment">// 注册</span></div><div class="line">Vue.component(<span class="string">'my-component'</span>, MyComponent)</div></pre></td></tr></table></figure>
<p>注册后就可以在 HTML 或其他模版中使用这个 VueComponent:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">my-component</span>></span><span class="tag"></<span class="name">my-component</span>></span></div></pre></td></tr></table></figure>
<h3 id="Vue-实例"><a href="#Vue-实例" class="headerlink" title="Vue 实例"></a>Vue 实例</h3><p>无论直接使用 <code>new Vue()</code> 还是 <code>new MyComponent()</code> 还是在模版编译过程中使用 VueComponent <code><my-component></my-component></code> 都会产生一个对应到 DOM 元素的 Vue 实例,这跟 React 的 ReactComponent 实例十分类似。</p>
<p>每个 Vue 实例都有一个 <code>data</code> 属性,它相当于 Vue 的 ViewModel 部分,只有赋予到 <code>data</code> 的数据,才能在视图模版中被调用,这一点跟 React 的 state 十分相似。</p>
<h3 id="模版编译"><a href="#模版编译" class="headerlink" title="模版编译"></a>模版编译</h3><p>在 Vue 实例化的过程中,会对模版(DOM 元素或者字符串模版,字符串模版会先转换成 DOM 元素)进行编译。这个过程跟 Angular 的 $compile 过程类似,他会递归地对所有普通元素的 directive 进行初始化,然后返回一个绑定函数,用于绑定 Vue 实例与初始化好的 DOM 元素。通过这个函数绑定 DOM 和 Vue 实例,就可以建立起 ViewModel 到 View 的关联。如果模版中使用了 VueComponent 子元素,那么这个 VueComponent 将会被实例化。</p>
<h3 id="Watcher-amp-视图更新"><a href="#Watcher-amp-视图更新" class="headerlink" title="Watcher & 视图更新"></a>Watcher & 视图更新</h3><p>在 Vue 实例化的过程中会对 Vue 实例的 <code>data</code> 属性赋初值(data的值应该是一个对象),Vue 会遍历 <code>data</code> 对象的每一个属性,并将其转换成ES5 getter/setter。这是 Vue 能监听到 <code>data</code> 变化的前提。</p>
<p>然后与 Angular 的 <code>$watch</code> 类似,Vue directive 初始化过程也需要创建对应的 Watcher,不过这些 Watcher 不需要用户手动去定义,Vue 会自动根据 params 和 directive 表达式来创建。如果这些监听的值发生变化,Watcher 就会调用 directive 的 <code>update</code> 方法,从而更新视图。所以基本上所有的视图更新操作都是在 <code>update</code> 方法中进行,一个 directive 的定义方式如下,相比 Angular 要简单许多:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">Vue.directive(<span class="string">'my-directive'</span>, {</div><div class="line"> bind: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="comment">// do preparation work</span></div><div class="line"> },</div><div class="line"> update: <span class="function"><span class="keyword">function</span> (<span class="params">newValue, oldValue</span>) </span>{</div><div class="line"> <span class="comment">// do something based on the updated value</span></div><div class="line"> },</div><div class="line"> unbind: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="comment">// do clean up work</span></div><div class="line"> }</div><div class="line">})</div></pre></td></tr></table></figure>
<p>到目前为止 Watcher 的行为都跟 Angular 类似,但是 Watcher 怎么监听到表达式的变化呢,难道像 Angular 一样进行 dirty-check?上面提到 Vue 实例化时会为 <code>data</code> 的属性创建 getter/setter,这是利用了 ES5 的 defineProperty 特性。当然这些 getter/setter 并不是简单的读写值,当 Watcher 进行表达式的第一次求值运算时,每接触一个 getter/setter,这个 getter/setter,就会将这个属性记录到 Watcher 的依赖列表。之后若这个属性的值被改变,那就意味着这个属性的 setter 将被调用。setter 设值后将调用依赖到这个属性的 Watcher 重新求值,这就可以监听到表达式的变化并触发视图更新。</p>
<p>这就是 Vue 对实例化后再添加的 <code>data</code> 属性无法响应的原因,虽然可以通过 Vue 实例的 <code>$set</code> 方法添加新的属性,但是这将导致所有跟这个 <code>data</code> 有依赖关系的 Watcher 重新求值,所以还是预先定义好 <code>data</code> 的属性比较靠谱。</p>
<h3 id="异步更新"><a href="#异步更新" class="headerlink" title="异步更新"></a>异步更新</h3><p>实际上 <code>update</code> 方法的调用是异步的,因为 Watcher 可能依赖到多个数据更新,如果每个数据更新都调用 <code>update</code> 方法会导致同一个 <code>update</code> 方法调用多次,而只有最后一次的 <code>update</code> 的结果才是需要的。 为了解决这个问题 Vue 会维护一个异步更新队列,并进行去重后再进行更新。因此对 <code>data</code> 并不会马上更新到视图,而只会在下一个心跳(setTimeout)更新,这跟 Angular 中对 <code>$scope</code> 修改又是类似的。</p>
<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>Vue 的性能可以说是无可挑剔的,利用 ES5 的 defineProperty 特性,准确而直接地响应了数据的变更,当且仅当必须更新视图时,才会去更新渲染,直截了当。一般情况下无需担心 Vue 的性能问题,只要渲染集合数据时,如果对整个集合引用进行替换,他跟 Angular 会有同样的问题,无法得知变动前后集合元素的对应关系,但这同样的可以通过添加 <code>track-by</code> 指定一个元素的稳定 id 来解决。而且 Vue 压缩后的大小是 Angular,React 之中最小的,同样提高了加载速度。但是跟 React 差不多,他只是一个 View-ViewModel 库,如果需要加入 Model 部分,可能还要引入 Redux,Vuex 之类的库。当然,使用 plain object 也是可以的,我在一个项目中为了追求简便就是这么干的。</p>
<p>然而使用 ES5 的特性提高了性能也成为了 Vue 的短板,他无法支持 IE8 及不支持 ES5 defineProperty 特性的浏览器,这个特性也没法通过 polyfill 来补救,而兼容 IE8 恰好是 Angular 跟 React 都做到的。由于比较新 Vue 的插件也是比较少的,但是简单的语法让我们重新造轮子也非常得心应手,相比之下如果你让我在 Angular 下面造轮子,我还得去重新理解一遍他的概念,认真比对文档,生怕语法有什么弄错了。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>相比于 Angular 和 React,我这篇写的实在太短了,因为他的概念简单而熟悉,性能也没有多大问题,如果不需要支持 IE8 以下,无疑是个好的选择。如果需要开发手机端 WEB 应用应该可以毫无顾虑地用 Vue 了。</p>
<p>至此 ARV 渲染实现比较即将完结了,总结留到下一期。</p>
<p>文章链接:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a></li>
<li><a href="/2016/03/07/Angular-React-Vue-Rendering-3/" title="ARV 渲染实现比较之 Vue">ARV 渲染实现比较之 Vue</a></li>
<li><a href="/2016/03/08/Angular-React-Vue-Rendering-4/" title="ARV 渲染实现比较总结">ARV 渲染实现比较总结</a></li>
<li><a href="/2016/03/14/Mithril-Rendering/" title="ARV 渲染实现番外篇之 Mithril">ARV 渲染实现番外篇之 Mithril</a>
</li>
</ul>
]]></content>
<summary type="html">
<p>之前已经分析了 <a href="https://angular.io">Angular</a> 和 <a href="https://facebook.github.io/react">React</a> 的渲染:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a>
</li>
</ul>
<p>今天来分析 <a href="http://vuejs.org/">Vue</a>。</p>
<h2 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h2><p>Vue 是一个新兴的前端视图框架,他做的事情跟 React 很类似,实现了前端组件化以及 View-ViewModel 绑定,但是同时又支持跟 Angular 很类似的模版语法,以及比 Angular 更方便的 filter 和 directive 定义方法,大大提高了开发效率。React 和 Angular 都熟悉的开发者转到 Vue 使用起来十分方便得心应手。</p>
<p>如果只是 React 跟 Angular 两者优点的结合,那 Vue 也没那么出彩,然而他的渲染效率甚至是 React 的 10 倍之多,这也是很多开发者转向 Vue 的理由。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
<category term="Angular" scheme="http://blog.sprabbit.com/tags/Angular/"/>
<category term="React" scheme="http://blog.sprabbit.com/tags/React/"/>
<category term="Vue" scheme="http://blog.sprabbit.com/tags/Vue/"/>
</entry>
<entry>
<title>ARV 渲染实现比较之 React</title>
<link href="http://blog.sprabbit.com/2016/03/05/Angular-React-Vue-Rendering-2/"/>
<id>http://blog.sprabbit.com/2016/03/05/Angular-React-Vue-Rendering-2/</id>
<published>2016-03-05T11:06:55.000Z</published>
<updated>2016-07-24T02:45:08.179Z</updated>
<content type="html"><![CDATA[<p>我在 <a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="上一篇">上一篇</a> 总结了 <a href="https://angular.io" target="_blank" rel="external">Angular</a> 的渲染实现,今天我们来看看 <a href="https://facebook.github.io/react" target="_blank" rel="external">React</a>。</p>
<h2 id="React"><a href="#React" class="headerlink" title="React"></a>React</h2><p>React 的渲染套路简单来说就是虚拟DOM树(Virtual DOM)。React 的思想有点儿像 Web Component,但是他又是一个独立的实现,可以纯粹使用 js 逻辑实现了前端组件化。看似 HTML + JS 的新语言 JSX,实际上会编译成完全的 js 代码,所有的 HTML 都转换成了 JS 的 DOM 操作,只不过这里操作的 DOM 并不是真正的 DOM,而是 React 实现的一套映射到真正 DOM 的虚拟 DOM。并且 React 的重点在于 ‘React’,即视图响应数据变化。</p>
<a id="more"></a>
<p>这里讨论的 React 目前是 0.14 版本的,鉴于对这个版本 0.12 版本做了很大的更改,所以并不确定更新到 1.0 后本文还是否适用,请注意。</p>
<h3 id="StartUp"><a href="#StartUp" class="headerlink" title="StartUp"></a>StartUp</h3><p>React 的 jsx 启动代码大概如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">ReactDOM.render(</div><div class="line"> <span class="xml"><span class="tag"><<span class="name">h1</span>></span>Hello, world!<span class="tag"></<span class="name">h1</span>></span></span>,</div><div class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'example'</span>)</div><div class="line">);</div></pre></td></tr></table></figure>
<h3 id="JSX"><a href="#JSX" class="headerlink" title="JSX"></a>JSX</h3><p>虽然使用 React 一般使用React专用的 JSX 语法,但是运行时实际上会编译为 JS 语法,比如 JSX 的:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> root = <span class="xml"><span class="tag"><<span class="name">ul</span> <span class="attr">className</span>=<span class="string">"my-list"</span>></span></span></div><div class="line"> <span class="tag"><<span class="name">li</span>></span>Text Content<span class="tag"></<span class="name">li</span>></span></div><div class="line"> <span class="tag"></<span class="name">ul</span>></span>;</div></pre></td></tr></table></figure>
<p>会编译成类似以下的 JS:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> root = React.createElement(<span class="string">'ul'</span>, { className: <span class="string">'my-list'</span> },</div><div class="line"> React.createElement(<span class="string">'li'</span>, <span class="literal">null</span>, <span class="string">'Text Content'</span>)</div><div class="line"> );</div></pre></td></tr></table></figure>
<p>为了不那么绕,所以我接下来将直接采用 JS 语法来分析。</p>
<h3 id="ReactElement"><a href="#ReactElement" class="headerlink" title="ReactElement"></a>ReactElement</h3><p>ReactElement 是组成 React 虚拟 DOM 的基本元素,它对应于真实 DOM 中的 HTMLElement,比如 div,li,或我们自定义的 Component。一个 ReactElement 可以是以下类型:</p>
<ul>
<li>ReactDOMElement(即HTML原生支持的元素)</li>
<li>ReactComponentElement(我们定义的 Component 元素)</li>
</ul>
<p>创建 ReactElement 方法如下:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> element = React.createElement(type, props, children, ...);</div></pre></td></tr></table></figure>
<p>其中第一个参数指定元素的标签名或 ReactClass,第二个参数可以指定元素的属性(props),比如 className 之类的,三个参数以及更多参数指定元素的内部元素,类型是 ReactNode。</p>
<h3 id="ReactNode"><a href="#ReactNode" class="headerlink" title="ReactNode"></a>ReactNode</h3><p>ReactNode 即是 React 虚拟 DOM 树的节点,它对应于真实 DOM 的节点。一个 ReactNode 可以是下面的类型:</p>
<ul>
<li>ReactText(文字,数字)</li>
<li>ReactElement</li>
<li>ReactFragment(一个元素为ReactNode的数组)</li>
</ul>
<p>利用上面这些结构类型,就可以创建一棵由 ReactElement 构成的树。</p>
<h3 id="ReactClass-amp-ReactComponent"><a href="#ReactClass-amp-ReactComponent" class="headerlink" title="ReactClass & ReactComponent"></a>ReactClass & ReactComponent</h3><p>有了上面的普通 ReactElement 和 ReactNode 就已经可以创建一棵树了,通过创建一些工厂函数也能重用一些元素结构。但是到目前为止也只实现了 View 而缺少 ViewModel,没有真正做到组件化。</p>
<p>ReactClass 就是用来产生可重用的 ReactComponent 的类型,其实就是一个构造函数。下面是定义一个类名为 MyComponent 的组件类的方法:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> MyComponent = React.createClass({render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{...}});</div></pre></td></tr></table></figure>
<p>其中的 render 属性是一个必须实现的属性,他必须返回一个 ReactElement,React 就是通过这个 render 方法来将一个 ReactComponent 渲染成 ReactElement 的。有了 ReactClass 我们就可以为产生的 ReactComponent 赋予状态属性,然后在 render 方法根据状态的不同,返回结构不同的 ReactElement。</p>
<p>注意以上定义好的 MyComponent 只是一个 ReactClass,要用它生产出 ReactComponent 元素,还需要像生成 ReactElement 那样去掉用 createElement 方法:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> element = React.createElement(MyComponent, props, child);</div></pre></td></tr></table></figure>
<p>如果觉得 ReactClass 和 ReactComponent 理解起来还是比较抽象,那么可以将 ReactClass 想象成标签名,即 <code>div</code>,<code>video</code>,这种,但是他不是原生的标签,所以使用前需要先用 <code>createClass</code> 定义好。而 ReactComponent 就是跟 ReactElement 一个级别的东西。</p>
<h3 id="ReactDOM-amp-第一遍渲染"><a href="#ReactDOM-amp-第一遍渲染" class="headerlink" title="ReactDOM & 第一遍渲染"></a>ReactDOM & 第一遍渲染</h3><p>有了上面的 ReactNode,ReactElement,ReactComponent,我们就可以完全的定制一棵有状态控制的虚拟DOM树,之所以说是虚拟的,是因为它只是逻辑上的数据结构,跟我们肉眼看到的页面并没有直接的联系。</p>
<p>我们通过 ReactDOM 这个子库来做跟真实DOM相关的操作,比如将一棵虚拟 DOM 树渲染到一个 HTML 的 DOM 元素中:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> component = ReactDOM.render(element, container, callback);</div></pre></td></tr></table></figure>
<p>其中第一参数为一棵虚拟 DOM 树的根节点元素,第二个参数为一个 HTML DOM 元素,返回结果是绑定成功后生成的component 实例的引用(ref)。这里又多了一个概念 ReactElement 的实例,据我们所知 ReactComponent 跟 ReactElement 是一个级别的东西,是虚拟 DOM 树的基本结构,为什么还有实例?简单来说,只有一个虚拟的 ReactElement 被绑定到一个真实 DOM 元素上,它内部才能正常运作,所以只有绑定后才能产生一个真正可以调用的实例。</p>
<p>调用了 <code>ReactDOM.render</code> 之后,这个虚拟 DOM 树的结构将按初始状态原封不动的渲染成真实 DOM 结构,并完成内部 ReactElement 到 DOMElement 的绑定。</p>
<h3 id="数据更新"><a href="#数据更新" class="headerlink" title="数据更新"></a>数据更新</h3><p>React 的 ViewModel 实际上就是 ReactComponent 的 state 和 props,View 将根据这些状态和属性来改变。但是 React 不会去监听这些状态和属性的变化,每次改变 state 和 props 都需要手动调用 ReactComponent 的<br><code>setState</code> 和 <code>setProps</code> 方法,这些方法会通知 Component 更新渲染,也可以调用 <code>forceUpdate</code> 强制更新。更新渲染实际上是通过 render 方法实现的,因此 render 方法内部一般不可以再修改状态,否则有可能导致渲染死循环。</p>
<h3 id="Reconciliation"><a href="#Reconciliation" class="headerlink" title="Reconciliation"></a>Reconciliation</h3><p>如果每次改动都需要重新渲染,那 React 造这些轮子也没啥价值了。接下来的 Reconciliation 才是 React 渲染实现的重点。</p>
<p>Reconciliation 这个词怎么翻译,我也不确定,估计可以称之为“调解”,调解因从一棵虚拟 DOM 树(局部)变成另一棵 DOM 树引起的“冲突”。</p>
<p>如果要准确比较两棵虚拟 DOM 树的差异并分解成最少的节点操作,可能需要 O(n3) 的复杂度,这样最节省操作真实 DOM 的成本,但是显然这相对于直接重新渲染而言是更耗费性能的。React 使用了一种较为粗暴的 O(n) 方法来找到一个较优解,在 DOM 操作和对比差异之间取得了良好的平衡。</p>
<p>元素级别的对比:</p>
<ul>
<li>如果对比某个节点的类型发生变化,则重新渲染该节点</li>
<li>类型相同的一般DOM元素(非 ReactComponent),可以在 O(n) 时间内找到其属性的变化并重现</li>
<li>类型相同的 ReactComponent 不会被替换,将使用新组件的 props 去调用旧组件的 <code>component[Will/Did]ReceiveProps()</code> 方法并重新调用他的 render 方法</li>
</ul>
<p>列表级别的对比:</p>
<ul>
<li>按照顺序对两个列表的每一个列表项进行元素级别的对比操作,若最后多了就删掉,少了就插入新的元素</li>
<li>如果一个列表项元素设置了 key 属性,则会对 key 值相同的项进行对比,并重现对应的调整位置,删除,插入操作</li>
</ul>
<p>利用以上规则来对比虚拟 DOM 树的变化并将操作重现到真实 DOM 上,节省了大部分重新创建删除 DOM 元素的成本,也让对比操作的复杂度保持在一个可控范围内。</p>
<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>由 Reconciliation 的过程可以看出,提高性能的方法有几种:</p>
<ul>
<li>如果两个 ReactComponent 结构相近,并且会相互切换,尝试合并成一个</li>
<li>所有列表项都加上 key 属性,并且令每个 key 在列表内稳定且唯一</li>
<li>实现 ReactComponent 的 shouldComponentUpdate 方法,避免无谓的重新渲染</li>
</ul>
<p>其中如果你的 ReactComponent 的渲染结果是幂等的(即针对相同的 props 和 state 其渲染结果也一致),那么可以直接使用 React 提供的 PureRenderMixin,她相当于帮你实现了 shouldComponentUpdate 方法,如果 props 和 state 没有变化(对象级别为引用对比),将直接返回 false 防止重复渲染。</p>
<p>但是如果你的 prop 或 state 属性是对象,而你必须对比对象内部属性的变化,可以使用 Facebook 的另外一个库 <a href="https://facebook.github.io/immutable-js/" target="_blank" rel="external">Immutable</a>,他可以用来创建不可变的对象,即要改变对象的属性必须拷贝并创建一个新的对象,这就令通过对比引用来对比对象属性变化成为可能。</p>
<p>但是我个人觉得这样每修改一次就创建新的对象也是一笔开销,同时还要使用 Immutable 定义的属性方法去修改对象,如果可以自己实现 shouldComponentUpdate 去做必要的比较还是可行的。不过如果真的想用不可变数据的话建议看看 <a href="http://swannodette.github.io/mori/" target="_blank" rel="external">森(Mori)</a>,比 Immutable 要快一点。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>React 渲染速度很快,这要得益于他将所有模版操作都预先编译成了虚拟 DOM 创建操作,这节省了运行时编译的开销;得益于“调和”算法,虚拟 DOM 结构改变映射到真实 DOM 也节省了大量不必要的操作。</p>
<p>然而 React 的优势又不止性能,与 ES6 高度兼容,搭配 Webpack 轻易实现模块化,前后端可以实现同构(isomorphic)等等都是 React 的加分点。React 可以说是做 View-ViewModel 这一块只做一件事,并且做到了极致。但是这也意味着只使用 React 可能是不够的,还需要用到 Flux,Redux 之类的数据模型和事件处理框架。然而单单 React 一个库压缩后就达到了 200k 左右的大小,加上其他诸如 React-Router,jQuery,Immutable 等等大小就更加令人瞠目结舌,虽然今天网速已经很快,但是第一次加载仍然会让人觉得等待很漫长,毕竟需要先加载完 React 才能进行渲染,除非通过同构服务端渲染来进行第一遍渲染。</p>
<p>Vue 下期再写了。</p>
<p>文章链接:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a></li>
<li><a href="/2016/03/07/Angular-React-Vue-Rendering-3/" title="ARV 渲染实现比较之 Vue">ARV 渲染实现比较之 Vue</a></li>
<li><a href="/2016/03/08/Angular-React-Vue-Rendering-4/" title="ARV 渲染实现比较总结">ARV 渲染实现比较总结</a></li>
<li><a href="/2016/03/14/Mithril-Rendering/" title="ARV 渲染实现番外篇之 Mithril">ARV 渲染实现番外篇之 Mithril</a>
</li>
</ul>
]]></content>
<summary type="html">
<p>我在 <a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="上一篇">上一篇</a> 总结了 <a href="https://angular.io">Angular</a> 的渲染实现,今天我们来看看 <a href="https://facebook.github.io/react">React</a>。</p>
<h2 id="React"><a href="#React" class="headerlink" title="React"></a>React</h2><p>React 的渲染套路简单来说就是虚拟DOM树(Virtual DOM)。React 的思想有点儿像 Web Component,但是他又是一个独立的实现,可以纯粹使用 js 逻辑实现了前端组件化。看似 HTML + JS 的新语言 JSX,实际上会编译成完全的 js 代码,所有的 HTML 都转换成了 JS 的 DOM 操作,只不过这里操作的 DOM 并不是真正的 DOM,而是 React 实现的一套映射到真正 DOM 的虚拟 DOM。并且 React 的重点在于 ‘React’,即视图响应数据变化。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
<category term="Angular" scheme="http://blog.sprabbit.com/tags/Angular/"/>
<category term="React" scheme="http://blog.sprabbit.com/tags/React/"/>
<category term="Vue" scheme="http://blog.sprabbit.com/tags/Vue/"/>
</entry>
<entry>
<title>ARV 渲染实现比较之 Angular</title>
<link href="http://blog.sprabbit.com/2016/03/02/Angular-React-Vue-Rendering-1/"/>
<id>http://blog.sprabbit.com/2016/03/02/Angular-React-Vue-Rendering-1/</id>
<published>2016-03-02T03:09:55.000Z</published>
<updated>2016-07-24T02:45:08.172Z</updated>
<content type="html"><![CDATA[<p>重新开始写博客的第一篇,还是来点干货,在闭关一年多之间,我在项目中使用过 <a href="https://angular.io" target="_blank" rel="external">Angular</a>, <a href="https://facebook.github.io/react" target="_blank" rel="external">React</a>, 和 <a href="http://vuejs.org/" target="_blank" rel="external">Vue</a>,今天我想说一下他们在渲染方面的一些实现的异同。</p>
<p>首先,要澄清一件事情,这几个东西虽然都可以用来渲染,但是却是不同级别的东西。Angular是一个功能十分强大的 MVVM(Model-View-ViewModel)框架,拥有包括模块化、服务在内的大而全的解决方案;而 React 只是一个组件化虚拟 DOM 库,也就是 View-ViewModel 部分;Vue 也是一个组件化的 View-ViewModel 库,但是他使用了类似 Angular 的模版语法。</p>
<p>Angular 虽然功能很全,但是这就意味着灵活性的降低。包括他自己定义的一套 html 扩展语法到js代码的映射关系,定义服务和过滤器的复杂写法,想用一个非 Angular 系的插件都要回想一下如何遵守他的规定来写扩展,大大降低了我们写代码的积极性。虽然 Angular2 号称解决了很多 Angular1 版本的缺点,但是既然他还在Beta阶段,我也不好去做什么评价。至少现在还没有多少人会去真正的使用它,所以今天这里的 Angular 仅代表他的第一代的产品。</p>
<a id="more"></a>
<p>扯远了,忍不住吐槽了一下 Angular ,今天只讨论他们之间渲染的异同。至于 <a href="https://www.polymer-project.org/1.0/" target="_blank" rel="external">Polymer</a> 这种真正基于 Web Component 的Polyfill 我只能说太超前了,一个组件至少一个请求我还是接受不能,而且虽然是一个 Polyfill,但是却不兼容目前大部分的运行环境,也并不知道未来是否真的能成为一套完善的标准,所以暂时不纳入讨论范围。有机会倒是可以研究一下其效率,这是后话了。</p>
<h2 id="Angular"><a href="#Angular" class="headerlink" title="Angular"></a>Angular</h2><p>这里假设读者对 Angular 的术语有一定的了解,比如 scope,filter,directive,controller。Angular 响应 ViewModel 变化的方法简单来说就是脏值检查(dirty-check),以下介绍这个脏值检查的基本概念。</p>
<h3 id="Start-Up"><a href="#Start-Up" class="headerlink" title="Start Up"></a>Start Up</h3><p>整个 Angular 应用的启动代码类似下面这样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">$compile($<span class="built_in">document</span>)($rootScope);</div><div class="line">$rootScope.$digest();</div></pre></td></tr></table></figure>
<h3 id="Scope"><a href="#Scope" class="headerlink" title="Scope"></a>Scope</h3><p>Scope 相当于 MVVM 中的 ViewModel,所有跟渲染视图有关的变量,事件处理方法,都会绑定到 Scope 对象上,这样就可以在视图模版对这些值和方法进行调用。 Scope 可以有子 Scope,子 Scope 跟父 Scope 具有类似于 prototype 的继承关系。而整个系统的顶级 Scope 就是 $rootScope。</p>
<h3 id="Template-Linking"><a href="#Template-Linking" class="headerlink" title="Template Linking"></a>Template Linking</h3><p><code>$compile</code> 是一个 Angular 模版编译器,他将在DOM树中的所有 directive 绑定到对应的DOM元素,并进行初始化。编译器执行后会返回一个绑定函数,这个绑定函数可以用来将编译后的模版绑定到一个 Scope 对象,这个过程称为模版关联(template linking):</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> linker = $compile(element);</div><div class="line">linker($scope);</div></pre></td></tr></table></figure>
<p>在这个过程中,一些 directive 会创建子 $scope,比如 <code>ng-controller</code>,<code>ng-repeat</code> 等等。这样 ViewModel 终于和 View 绑定起来了。</p>
<p>但是绑定后并不会马上渲染,绑定后执行 Scope 对象的 <code>$digest</code> 方法,这个模版才真正运作起来。</p>
<h3 id="Watch-amp-视图更新"><a href="#Watch-amp-视图更新" class="headerlink" title="Watch & 视图更新"></a>Watch & 视图更新</h3><p>在分析 <code>$digest</code> 之前,先了解一下 Scope 的变化是怎么映射到视图的。<code>$scope.$watch</code> 方法是用来监听 Scope 中的值变化的基本方法。他的语法是:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$scope.$watch(watchExpression, listener, [objectEquality]);</div></pre></td></tr></table></figure>
<p>简单来说,绑定监听器后,如果 <code>watchExpression</code> 的运算结果变化了,<code>listener</code> 就会被调用。第三个参数 <code>objectEquality</code> 则代表是否用深度拷贝的方法来比较对象的变化,默认情况下,对于表达式运算结果为对象的监听器只采用引用对比。另外 <code>watchExpression</code> 必须是幂等的,即对于相同的输入,运算结果必须一致。</p>
<p>如果使用 <code>ng-repeat</code> 这种基于集合数据的监听,就会使用 <code>$watchCollection</code> 方法监听集合操作:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$scope.$watchCollection(obj, listener);</div></pre></td></tr></table></figure>
<p><code>$watchCollection</code> 实际上会穷举集合中的每一个 key 的值,只要有增删改的操作,<code>listener</code> 就会被调用。</p>
<p>一般的 Angular 用户可能接触这些方法不是很多,但是如果写过插件,写过 directive 的用户就可能接触的比较多,他们就会知道,实际上所有基于 Scope 中变量改变而引发的行为,都是通过 <code>$watch</code> 绑定的,包括使用最多的 directive <code>ng-bind</code>,只是这些 directive 都已经封装好可以直接用了。</p>
<p>所以,所有的视图更新,都是在 <code>listener</code> 中实现的,比如 <code>ng-bind</code>,在检测到表达式结果改变时,直接调用所在 element 的 html 方法将表达式的结果写到 element 的 innerHtml 里面去:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">scope.$watch(ngBindHtmlWatch, <span class="function"><span class="keyword">function</span> <span class="title">ngBindHtmlWatchAction</span>(<span class="params"></span>) </span>{</div><div class="line"> element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || <span class="string">''</span>);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>至于 Watch 是怎么监听到 <code>watchExpression</code> 的变化的,请继续往下看。</p>
<h3 id="Digest-amp-脏值检查"><a href="#Digest-amp-脏值检查" class="headerlink" title="Digest & 脏值检查"></a>Digest & 脏值检查</h3><p><code>$scope.$digest</code> 方法是用来检查 Scope 数据变化的方法,调用一次 $digest 会执行一次上面提到的脏值检查,这将执行所有在这个 Scope 及其子 Scope 用 <code>$watch</code> 绑定的 <code>watchExpression</code>,并将运算结果与上一次检查时的结果进行对比,若发生变化则调用对应的 <code>listener</code> 处理函数,并将本次 <code>$digest</code> 标记为 <code>dirty</code>。如果一次 <code>$digest</code> 执行完毕后结果为 <code>dirty</code>,那么将马上重新执行另外一次 <code>$digest</code>, 直到不会再产生 <code>dirty</code>。这就被称为 Digest 循环。</p>
<p>之所以在一次 <code>dirty</code> 的 <code>$digest</code> 之后重新执行一次 <code>$digest</code>,是因为 <code>listener</code> 中的逻辑可能涉及到修改 Scope,以至于之前检查过的 <code>watchExpression</code> 产生不同的计算结果。</p>
<p>至于 Digest 执行的时机,一般而言并不需要用户去手动执行,如果你的系统完全运行在 Angular 体系下, Angular 会在适当的时候去执行,比如使用 <code>$timeout</code>,<code>$http</code>,<code>ng-controller</code>,<code>ng-click</code>,<code>ng-model</code> 等,这也是 Angular 要重新造轮子的原因。</p>
<p>但是如果你要自己写 directive 或者想用 Angular 体系外的东西时,就得手动触发 Digest 循环:<code>$scope.$apply()</code>。</p>
<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>总的来说,Angular 渲染的逻辑就是,在任何可能引发 Scope (ViewModel) 改变的地方,调用 Digest 循环进行脏值检查并在发现脏值后修改 DOM 树 (View) 进行渲染,直到 Scope (ViewModel) 稳定下来。</p>
<p>可以看出来,一次可能的变动,将遍历整个应用中绑定的监听器,效率十分低。不过优化的方法也很明显,就是减少绑定监听器的数量,比如在一些绑定后不会变化的地方在一次脏值出现后马上注销监听器(bind-once),或者在可能发生脏值之前再注册监听器,之后马上注销监听器等等。另外使用 <code>ng-repeat</code> 时最好使用稳定的属性(比如id)来作为 <code>track by</code> 的跟踪属性,防止不必要的模版关联。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>Angular 虽然有点慢,但是无可否认,开发者会为此埋单,毕竟良好的兼容性,众多的插件,方便的双向绑定都得到了人们的中意。</p>
<p>由于文章有点长,所以 React 和 Vue <a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="下期再写">下期再写</a>。</p>
<p>文章链接:</p>
<ul>
<li><a href="/2016/03/02/Angular-React-Vue-Rendering-1/" title="ARV 渲染实现比较之 Angular">ARV 渲染实现比较之 Angular</a></li>
<li><a href="/2016/03/05/Angular-React-Vue-Rendering-2/" title="ARV 渲染实现比较之 React">ARV 渲染实现比较之 React</a></li>
<li><a href="/2016/03/07/Angular-React-Vue-Rendering-3/" title="ARV 渲染实现比较之 Vue">ARV 渲染实现比较之 Vue</a></li>
<li><a href="/2016/03/08/Angular-React-Vue-Rendering-4/" title="ARV 渲染实现比较总结">ARV 渲染实现比较总结</a></li>
<li><a href="/2016/03/14/Mithril-Rendering/" title="ARV 渲染实现番外篇之 Mithril">ARV 渲染实现番外篇之 Mithril</a>
</li>
</ul>
]]></content>
<summary type="html">
<p>重新开始写博客的第一篇,还是来点干货,在闭关一年多之间,我在项目中使用过 <a href="https://angular.io">Angular</a>, <a href="https://facebook.github.io/react">React</a>, 和 <a href="http://vuejs.org/">Vue</a>,今天我想说一下他们在渲染方面的一些实现的异同。</p>
<p>首先,要澄清一件事情,这几个东西虽然都可以用来渲染,但是却是不同级别的东西。Angular是一个功能十分强大的 MVVM(Model-View-ViewModel)框架,拥有包括模块化、服务在内的大而全的解决方案;而 React 只是一个组件化虚拟 DOM 库,也就是 View-ViewModel 部分;Vue 也是一个组件化的 View-ViewModel 库,但是他使用了类似 Angular 的模版语法。</p>
<p>Angular 虽然功能很全,但是这就意味着灵活性的降低。包括他自己定义的一套 html 扩展语法到js代码的映射关系,定义服务和过滤器的复杂写法,想用一个非 Angular 系的插件都要回想一下如何遵守他的规定来写扩展,大大降低了我们写代码的积极性。虽然 Angular2 号称解决了很多 Angular1 版本的缺点,但是既然他还在Beta阶段,我也不好去做什么评价。至少现在还没有多少人会去真正的使用它,所以今天这里的 Angular 仅代表他的第一代的产品。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
<category term="Angular" scheme="http://blog.sprabbit.com/tags/Angular/"/>
<category term="React" scheme="http://blog.sprabbit.com/tags/React/"/>
<category term="Vue" scheme="http://blog.sprabbit.com/tags/Vue/"/>
</entry>
<entry>
<title>重启:从 Octopress 转移到 Hexo</title>
<link href="http://blog.sprabbit.com/2016/03/01/From-Octopress-to-Hexo/"/>
<id>http://blog.sprabbit.com/2016/03/01/From-Octopress-to-Hexo/</id>
<published>2016-03-01T07:30:48.000Z</published>
<updated>2016-07-24T02:45:08.165Z</updated>
<content type="html"><![CDATA[<p>距离上次发文已经一年多了,纵观以前发的文章,越来越发现从前的自己是多么的傻与天真,但是也那么的可爱,对技术的追求是那么的纯粹。经过重重历练,现在我算是一个有点资格的WEB前端开发工程师了,但是也逐渐丧失了对技术的热爱与追求,毕竟被工作磨平了棱角,每天重复重复再重复地敲代码,却不是为了自己的梦想。最近我觉得这样下去,我最终必定沦为机器般的无情生物,终生碌碌无为,于是有点想重拾过去的美好,恢复学习的热情,挽回在偏离梦想的道路上留下的步步脚印。</p>
<a id="more"></a>
<p>于是今天,我将博客从 Octopress 迁移到 <a href="http://hexo.io" target="_blank" rel="external">Hexo</a> 了,并不喜欢 Ruby 这种语言,还是直接用 js 来的亲切。迁移过程也十分得心应手,按照<a href="https://hexo.io/docs/migration.html#Octopress" target="_blank" rel="external">hexo文档</a>上说的来做,基本上不需要做过多的改动,实际上我当时迁移的时候并没有看到这份文档,自己摸索着就能找到修改的方法。</p>
<p>为了证明我的诚意,我花了一周的时间为自己的博客写了一套主题:<a href="https://github.com/denjones/hexo-theme-chan" target="_blank" rel="external">Chan</a>。之所以叫”Chan”,是因为我希望这套主题能体现出”禅”的精简宁静的感觉,但是又博大精深,能蕴含天地间的智慧精华。其实一开始想用”Zen”后来发现已经被别人注册了项目,而且”Zen”这个译法实际上来源于日文发音,而日本的Zen实际上又是中国的禅法东渡后产生的新的含义,回归原本,我觉得用”Chan”才能代表这种原始简朴的智慧。按照这个角度出发,这套模版就显得十分精简,没有花哨多样的侧边栏,也没有碍事的横幅头部,只有左边一列连接,右边一排文章,排版尽量做到优雅大方。</p>
<p>简约但是却不简单,用 Hexo 的人大部分都会用到一个叫 <a href="https://github.com/fancyapps/fancyBox" target="_blank" rel="external">FancyBox</a> 的图像展示插件,但是我觉得他太丑了,而我特别喜欢另外一个叫做 <a href="https://github.com/fancyapps/fancyBox" target="_blank" rel="external">PhotoSwipe</a> 的插件,但是这个插件又需要预先定义好图片宽高,我于是写了些兼容让他读取到文件再去修改宽高。另外,作为半个画师和摄影师我十分注重版权的维护,受到LOFTER支持CC协议的启发,我觉得这种静态博客应该也受到CC协议的保护,于是我也为他写了<a href="https://creativecommons.org/" target="_blank" rel="external">CC 4.0 国际版</a>的支持。虽然不知道国际版对国内情况是否能起到实质性的作用,我觉得这也是一次积极的尝试。</p>
<p>希望这次重启并不单是我一次心血来潮的尝试,希望我真的能坚持这条路,坚持做技术乃至各方面的积累,坚持我的梦想,最终真正步入大师级的殿堂,立此为证。</p>
]]></content>
<summary type="html">
<p>距离上次发文已经一年多了,纵观以前发的文章,越来越发现从前的自己是多么的傻与天真,但是也那么的可爱,对技术的追求是那么的纯粹。经过重重历练,现在我算是一个有点资格的WEB前端开发工程师了,但是也逐渐丧失了对技术的热爱与追求,毕竟被工作磨平了棱角,每天重复重复再重复地敲代码,却不是为了自己的梦想。最近我觉得这样下去,我最终必定沦为机器般的无情生物,终生碌碌无为,于是有点想重拾过去的美好,恢复学习的热情,挽回在偏离梦想的道路上留下的步步脚印。</p>
</summary>
<category term="随笔" scheme="http://blog.sprabbit.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="Octopress" scheme="http://blog.sprabbit.com/tags/Octopress/"/>
<category term="Hexo" scheme="http://blog.sprabbit.com/tags/Hexo/"/>
</entry>
<entry>
<title>IOS Safari 浏览器的点击事件问题</title>
<link href="http://blog.sprabbit.com/2014/09/03/onclick-ios-safari/"/>
<id>http://blog.sprabbit.com/2014/09/03/onclick-ios-safari/</id>
<published>2014-09-03T08:11:00.000Z</published>
<updated>2016-07-24T02:45:08.161Z</updated>
<content type="html"><![CDATA[<p>今天碰到一个问题,在IOS的Safari浏览器中,一个网页的div元素在绑定了click事件后点击时有变暗的闪烁。在其他浏览器均无出现此现象,即使是桌面的Safari也没有。<br>我于是清空了click事件中的所有代码,仍然会出现此状况,但是直接去掉时间绑定则不会变暗,于是确定是因为绑定click事件引起的变暗。<br>后面我又在事件处理中写了个alert(),发现在提示框弹出的时候,那个div已经变暗了,也就是说在处理事件之前,浏览器已经给div附加了变暗效果。那基本上可以确定是IOS Safari的默认样式效果了。<br>于是StackOverflow了一下问题迎刃而解:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="selector-tag">html</span> {</div><div class="line"> <span class="attribute">-webkit-tap-highlight-color</span>: transparent;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>参考: <a href="http://stackoverflow.com/questions/2355154/iphone-darkens-div-on-click" target="_blank" rel="external">http://stackoverflow.com/questions/2355154/iphone-darkens-div-on-click</a></p>
]]></content>
<summary type="html">
<p>今天碰到一个问题,在IOS的Safari浏览器中,一个网页的div元素在绑定了click事件后点击时有变暗的闪烁。在其他浏览器均无出现此现象,即使是桌面的Safari也没有。<br>我于是清空了click事件中的所有代码,仍然会出现此状况,但是直接去掉时间绑定则不会变暗,于
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
<category term="iOS" scheme="http://blog.sprabbit.com/tags/iOS/"/>
</entry>
<entry>
<title>Javascript备忘</title>
<link href="http://blog.sprabbit.com/2013/08/22/javascript-memo-1/"/>
<id>http://blog.sprabbit.com/2013/08/22/javascript-memo-1/</id>
<published>2013-08-22T12:21:00.000Z</published>
<updated>2016-07-24T02:45:08.157Z</updated>
<content type="html"><![CDATA[<p><link rel="stylesheet" href="/styles/2013-08-22-javascript-memo-1/imagecrop.css" type="text/css"></p>
<link rel="stylesheet" href="/styles/2013-08-22-javascript-memo-1/photodrag.css" type="text/css">
<p>这个系列只是一个备忘录,主要是讲述平时编程遇到的一些奇怪的问题。<br>本来标题叫做Javascript奇技淫巧系列,但是现在我又被掉往服务端开发了,所以这个系列可能会不再更新,于是改回Javascript备忘。<br>其实这种奇怪的问题一般都出在IE上,所以重点就放在IE上了。</p>
<p>这次的主题是图片拖拽的问题,主要情景有几种,第一种是可以在网页上面拖拽的图片,另一种是在可拖动的元素下面有一张图。<br>当然还有一种是浏览器默认的图片拖拽,使用浏览器默认的图片拖拽可以直接将图片拖出浏览器,在新网页中打开,或者拖到其他软件中处理。<br>但是,往往我们都不希望出现默认的情况,尤其是想制造出前面两种效果的时候。</p>
<a id="more"></a>
<h2 id="可以在网页上面拖拽的图片"><a href="#可以在网页上面拖拽的图片" class="headerlink" title="可以在网页上面拖拽的图片"></a>可以在网页上面拖拽的图片</h2><p>下面这里有一个例子:</p>
<div class="photo"><br> <img class="photo_drag" src="/images/jsmemo/enako.jpg" alt="enako"><br></div>
<p>点击图片可以拖动,如果你在IE之外支持canvas的浏览器中打开,还会有旋转的效果。<br>但是,如果只添加了拖动相关的代码的话,一开始会是下面这样:</p>
<div class="photo"><br> <img src="/images/jsmemo/enako.jpg" alt="enako"><br></div>
<p>没错,就跟没添加没什么区别(实际上我懒得再写一份没做处理的javascript了,所以上面这个确实没添加-_-|||,嘛效果是一样的)。</p>
<p>好了,区别在于前者在处理在图片上的<code>mousedown</code>事件时,做了如下操作:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">e.preventDefault();</div><div class="line">e.stopPropagation();</div></pre></td></tr></table></figure>
<p><code>e</code>是传进jQuery事件处理函数的jQuery事件参数,前者阻止了浏览器使用默认方法对事件的处理,后者阻止事件冒泡。<br>于是事件传递到此结束,浏览器也就不会产生拖拽图片的操作。<br>其他浏览器事件都支持preventDefault()方法,IE是比较奇淫的,通过返回值来判断是否执行默认操作。<br>所以jQuery事件的preventDefault()方法类似下面:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> e = <span class="keyword">this</span>.originalEvent;</div><div class="line"></div><div class="line"><span class="keyword">this</span>.isDefaultPrevented = returnTrue;</div><div class="line"><span class="keyword">if</span> ( !e ) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// If preventDefault exists, run it on the original event</span></div><div class="line"><span class="keyword">if</span> ( e.preventDefault ) {</div><div class="line"> e.preventDefault();</div><div class="line"></div><div class="line"><span class="comment">// Support: IE</span></div><div class="line"><span class="comment">// Otherwise set the returnValue property of the original event to false</span></div><div class="line">} <span class="keyword">else</span> {</div><div class="line"> e.returnValue = <span class="literal">false</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>而停止冒泡基本就是方法名不一样了。</p>
<h2 id="可以在图片上面拖拽的元素"><a href="#可以在图片上面拖拽的元素" class="headerlink" title="可以在图片上面拖拽的元素"></a>可以在图片上面拖拽的元素</h2><p>上面的解决方案可能很多人都知道。<br>不过在图片上拖拽元素这个可能就比较少实现。<br>一个比较常见的应用就是截图插件:</p>
<div id="photo">
<img id="scene" src="/images/jsmemo/scene.jpg" alt="scene">
</div>
<p>如果你想做出来选中内容跟未选中内容表现得不一样的效果,如上面的模糊与清晰的差别,那么很幸运,你将不会遇到奇怪的问题。<br>一旦你选择做出下面这种没有差别的效果,那就跪了,在IE你几乎无法移动那个小框(点小框的边框就可以)。</p>
<div id="photo2">
<img id="scene2" src="/images/jsmemo/scene.jpg" alt="scene">
</div>
<p>一开始我百思不得其解,因为点选框内,却产生了默认拖拽图片的操作,于是按上面的方法来修改onmouse事件,却无效。<br>最令人想不明白的是,明明小框显示在图片之上,为什么图片反而拦截到了点击事件?</p>
<p>其实答案却非常简单,只要仔细想想为什么点小框的边框就可以移动就明白了,点小框可以移动,证明小框获取消息的等级确实是比背景图要高。<br>而点击中间部分却无法移动,因为我们没有点到小框上的任何东西!是真的没有任何东西,直接穿透了小框,点到了图片上。<br>然后你就会发现,在IE中,原来元素里面没有任何东西,包括背景的话,点击的消息传递时是会直接跳过这个元素的。<br>IE在这里耍了点小聪明,以为这样可以方便消息的穿透,谁知道却适得其反。<br>不过好在解决方法是简单的,希望保持透明的话,加一层透明的背景就行了,这就不是没内容,而只是内容看不见而已。</p>
<div id="photo3">
<img id="scene3" src="/images/jsmemo/scene.jpg" alt="scene">
</div>
<p>而第一个效果分离的截图插件之所以不会遇到问题是因为这种效果一般是在小框内再包含一幅图像来实现的,就不再是透明的小框了。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>其实这里还有一个技巧,就是实现模糊效果的方法。<br>目前只有Chrome支持高斯模糊滤镜,为了在其他浏览器实现模糊效果,只能进行模拟。<br>我们知道高斯模糊可以通过横向模糊加纵向模糊叠加实现,于是我们就可以用多张半透明图像进行错位叠加实现。</p>
<p>总而言之,在做前端开发中会遇到很多神奇的问题,以后我将一一记录在这里。另外吐槽一下,IE你为什么那么傲娇啊,看到IE就想哭啊有木有啊。</p>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/scripts/2013-08-22-javascript-memo-1/jquery.photodrag.js"></script>
<script src="/scripts/2013-08-22-javascript-memo-1/photodrag.js"></script>
<script src="http://apps.bdimg.com/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
<script src="/scripts/2013-08-22-javascript-memo-1/jquery.imagecrop.js"></script>
<script src="/scripts/2013-08-22-javascript-memo-1/imagecrop.js"></script>
]]></content>
<summary type="html">
<p><link rel="stylesheet" href="/styles/2013-08-22-javascript-memo-1/imagecrop.css" type="text/css"></p>
<link rel="stylesheet" href="/styles/2013-08-22-javascript-memo-1/photodrag.css" type="text/css">
<p>这个系列只是一个备忘录,主要是讲述平时编程遇到的一些奇怪的问题。<br>本来标题叫做Javascript奇技淫巧系列,但是现在我又被掉往服务端开发了,所以这个系列可能会不再更新,于是改回Javascript备忘。<br>其实这种奇怪的问题一般都出在IE上,所以重点就放在IE上了。</p>
<p>这次的主题是图片拖拽的问题,主要情景有几种,第一种是可以在网页上面拖拽的图片,另一种是在可拖动的元素下面有一张图。<br>当然还有一种是浏览器默认的图片拖拽,使用浏览器默认的图片拖拽可以直接将图片拖出浏览器,在新网页中打开,或者拖到其他软件中处理。<br>但是,往往我们都不希望出现默认的情况,尤其是想制造出前面两种效果的时候。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
</entry>
<entry>
<title>Dust.js语法简介(三)</title>
<link href="http://blog.sprabbit.com/2013/08/19/introduction-dustjs-3/"/>
<id>http://blog.sprabbit.com/2013/08/19/introduction-dustjs-3/</id>
<published>2013-08-19T07:53:00.000Z</published>
<updated>2016-07-24T02:45:08.148Z</updated>
<content type="html"><![CDATA[<p><a href="/2013/08/17/introduction-dustjs-2/">上一篇</a>介绍了Dust的最基本的语法,已经足够应付一般的模板翻译。这一章将介绍一些涉及逻辑的Dust语法以及介绍如何在前端应用模板。</p>
<a id="more"></a>
<p>##逻辑区段</p>
<p>###?标签</p>
<p>用<code>?</code>来代替区段标签中的<code>#</code>时,仅当<code>name</code>的值为真时,才执行区段主体部分。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{?name} body {/name}</div></pre></td></tr></table></figure>
<p>###^标签</p>
<p>用<code>^</code>来代替<code>#</code>时,仅当<code>name</code>的值为假时,才执行区段主体部分。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{^name} body {/name}</div></pre></td></tr></table></figure>
<p>###{:else}标签</p>
<p>当一个区段标签(包括<code>#</code>、<code>?</code>、<code>^</code>、以及逻辑标签等)的值为假时,若区段主体中包含{:else}标签,则执行<code>{:else}</code>标签以及区段结束标签之间的内容,否则忽略这些内容。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">ul</span>></span></div><div class="line">{#friends}</div><div class="line"> <span class="tag"><<span class="name">li</span>></span>{name}, {age}{~n}<span class="tag"></<span class="name">li</span>></span></div><div class="line">{:else}</div><div class="line"> <span class="tag"><<span class="name">p</span>></span>You have no friends!<span class="tag"></<span class="name">p</span>></span></div><div class="line">{/friends}</div><div class="line"><span class="tag"></<span class="name">ul</span>></span></div></pre></td></tr></table></figure>
<p>若friend为空,则仅仅输出:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">p</span>></span>You have no friends!<span class="tag"></<span class="name">p</span>></span></div><div class="line"><span class="tag"></<span class="name">ul</span>></span></div></pre></td></tr></table></figure>
<p>###值的真假</p>
<p>在区段中判断标签的真假的方法与Javascript本身稍有不同,Dust将以下值判断为假:</p>
<ul>
<li>空字符串<code>’’</code>、<code>””</code></li>
<li>布尔<code>false</code></li>
<li><code>null</code></li>
<li><code>undefined</code></li>
<li>空列表<code>[]</code></li>
</ul>
<p>其余值均为真值,包括数字“0”,空对象<code>{}</code>。</p>
<p>##拆分(Partials)</p>
<p>拆分是一种将重复使用的模板抽取出来,并在使用到这段模板的模板中直接导入该模板,避免重复劳动的方法。在服务端,一个名为“xxx”的Dust模板通常通常保存在一个名为xxx.dust的模板文件中。我们可以利用模板名来在模板中插入一段来自其他模版文件的模板:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{>name /}</div></pre></td></tr></table></figure>
<p>以上是一个自封闭的区段标签,代表将name.dust中的模版插入到当前位置。若文件包含路径,则用双引号包裹:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{>”dust/name” /}</div></pre></td></tr></table></figure>
<p>标签中也可以填写参数:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{>”dust/name” foo=”Hello” bar=” World”/}</div></pre></td></tr></table></figure>
<p>甚至可以使用动态路径:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{>”dust/{pathName}” /}</div></pre></td></tr></table></figure>
<p>##区块(Blocks)</p>
<p>通过拆分可以重用一个模版,但是用这种方法来派生模版有一个缺点,就是你需要记得需要在什么位置插入哪个模版,并且对每一个派生出来的模版都要重新布局一次。区块可以解决这个问题,在父模板中使用区块可以方便地在子模板中替换区块中的内容。区块也是一种特殊的区段,定义方法如下:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{+name /}</div></pre></td></tr></table></figure>
<p>或者在区段中填写默认内容,当区块没有被替换时,将显示默认内容:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{+name}default Content{/name}</div></pre></td></tr></table></figure>
<p>使用区块替换需要在子模板中使用拆分区段(<code>></code>)导入父模板,并使用替换区段(<code><</code>)进行替换:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">{>father/}</div><div class="line">{<span class="tag"><<span class="name">name}Content{</span>/<span class="attr">name</span>}</span></div></pre></td></tr></table></figure>
<p>比如一个父模板可以写成这样:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"page"</span>></span></div><div class="line"> <span class="tag"><<span class="name">h1</span>></span>{+pageHeader}PayPal{/pageHeader}<span class="tag"></<span class="name">h</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bodyContent"</span>></span></div><div class="line"> {+bodyContent/}</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"footer"</span>></span></div><div class="line"> {+pageFooter}Contact Us {/pageFooter}</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"><span class="tag"></<span class="name">div</span>></span></div></pre></td></tr></table></figure>
<p>然后保存为shared/base_template.dust文件,然后定义子模板:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">{! 首先导入父模板 !}</div><div class="line">{>"shared/base_template"/}</div><div class="line"></div><div class="line">{! 然后定义对应的部分 !}</div><div class="line">{<bodyContent}</div><div class="line"><p>These are your current settings:</p></div><div class="line"><ul></div><div class="line"> <li>xxxx</li></div><div class="line"> <li>yyy</li></div><div class="line"></ul></div><div class="line">{/bodyContent}</div><div class="line">{<pageFooter}</div><div class="line"> <hr></div><div class="line"> <a href="/contactUs">About Us</a> |</div><div class="line"> <a href="/contactUs">Contact Us</a></div><div class="line">{/pageFooter}</div></pre></td></tr></table></figure>
<p>##应用Dust模板</p>
<p>至此,Dust自带的基本功能语法已经介绍完毕,目前大家可能只在测试器中使用过模板,以下将介绍如何直接在前端中应用模板。</p>
<p>###编译</p>
<p>之前也介绍过,Dust是编译型模板,意思则是若需应用模板,首先要将模板可执行化,即将模板变成可执行的代码。如果你使用过Dust测试器,那么你会发现在你输入模板后,会在2号框中显示一个函数定义,那就是编译生成的代码。使用编译型模板有一个好处,就是当模板编译好之后,若需要重复使用模板,不需要每次都对模板重新进行分析,加快模板解析的速度,而且,模板可以预先编译好保存在服务器,甚至让前端连第一次编译的时间都节省了。</p>
<p>因此Dust库有两种发行版本:</p>
<ul>
<li>dust-core-2.0.2.js</li>
<li>dust-full-2.0.2.js</li>
</ul>
<p>前者为核心(Core)版本,其只包含模板解析的相关代码,大小只有十几k,而完全版(Full)则包含Dust的所有代码,包括编译器,大小有一百多k。对于不需要在前端进行编译的项目,仅仅需要使用核心版本即可,这也是速度比较快的做法。但是对于需要在前端动态编译的项目,则只能使用包含编译器的完全版。</p>
<p>编译模板的方法很简单,使用完全版的dust.compile()方法:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> compiled = dust.compile(<span class="string">"Hello {name}!"</span>, <span class="string">"intro"</span>);</div></pre></td></tr></table></figure>
<p>其中第一个参数为模板字符串,第二个参数为模板名,函数将返回包含编译好的可执行代码的字符串。这个操作不会注册这个模板,仅进行编译,此时仍不可通过模板名来调用这段代码。</p>
<p>###注册</p>
<p>如果直接执行一遍compiled中的代码,则模板会按之前指定的名字注册到dust,从而可以通过模板名来调用该模板。但若compiled代码未被执行过,则需要在渲染前手动将其注册到dust中,注册的方法很简单:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">dust.loadSource(compiled);</div></pre></td></tr></table></figure>
<p>###渲染</p>
<p>通过编译注册可以让多套模板处于就绪状态,对于这些模板,我们可以直接用它将JSON对象渲染成HTML文本,通过调用dust.render()方法。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">dust.render(<span class="string">"intro"</span>, {name: <span class="string">"Fred"</span>}, <span class="function"><span class="keyword">function</span>(<span class="params">err, out</span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(out);</div><div class="line">});</div></pre></td></tr></table></figure>
<p>这个方法接受3个参数,第一个为模板名,第二个为JSON对象,第三个是一个接受两个参数的回调函数。执行这个方法后Dust会使用注册好的对应模板对JSON对象进行处理,得出一个渲染结果字符串,然后调用回调函数,其中第一个参数包含了在处理过程中出现的错误信息,第二个参数就是渲染结果字符串。一般会在回调函数中将渲染结果插入到当前的DOM结构中,以便在浏览器中显示渲染结果。</p>
<p>###区块和拆分</p>
<p>一般使用文件来保存模板并且使用区块和拆分是让Dust作为服务端模板时应用的技术,因为在客户端Javascript中无法很方便地对分布式文件进行操作。但是我们可以通过在本地部署模板数据,编译成可执行代码并用一个js文件来保存的方式来使用区块和拆分。</p>
<p>若在Linux平台则直接在终端安装npm和dust并使用dustc命令编译成代码,得到js文件。</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">$ npm install dustjs-linkedin</div><div class="line">$ dustc input.dust output.js</div></pre></td></tr></table></figure>
<p>或者在js引擎中使用dust.compile(),将模板复制到第一个参数,指定第二参数为其不带后缀的文件名,并将结果输出到js文件。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> output1 = dust.compile(partialStr, <span class="string">"partial"</span>);</div><div class="line"><span class="keyword">var</span> output2 = dust.compile(baseStr, <span class="string">"base"</span>);</div><div class="line"><span class="keyword">var</span> output3 = dust.compile(childStr, <span class="string">"child"</span>);</div></pre></td></tr></table></figure>
<p>最后在HTML中导入所有生成的js文件即可使用。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"partial.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></div><div class="line"><span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"base.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></div><div class="line"><span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"child.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></div></pre></td></tr></table></figure>
<p>注意此时不再需要使用dust.loadSource()来注册,因为script标签将js文件执行了一次,已经将模板注册好了。此时已可使用dust.render()进行渲染。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>至此,我们已经可以在前端中使用模板了,但是还有一些高级功能这里并未涉及,包括<code>@</code>辅助标签以及自定义扩展标签,如果有动力写Dust.js语法简介(四)的话,我将会在那介绍。除此之外,这里只提供了在Linux编译模板的一些官方方法,若需要在Windows下编译模板,则比较麻烦,有机会再写一篇如何在Windows下编译模板的教程吧。</p>
<p>##文章链接</p>
<ul>
<li><a href="/2013/08/16/introduction-dustjs-1">Dust.js语法简介(一)</a></li>
<li><a href="/2013/08/17/introduction-dustjs-2">Dust.js语法简介(二)</a></li>
<li><a href="/2013/08/19/introduction-dustjs-3">Dust.js语法简介(三)</a></li>
</ul>
]]></content>
<summary type="html">
<p><a href="/2013/08/17/introduction-dustjs-2/">上一篇</a>介绍了Dust的最基本的语法,已经足够应付一般的模板翻译。这一章将介绍一些涉及逻辑的Dust语法以及介绍如何在前端应用模板。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
</entry>
<entry>
<title>Dust.js语法简介(二)</title>
<link href="http://blog.sprabbit.com/2013/08/17/introduction-dustjs-2/"/>
<id>http://blog.sprabbit.com/2013/08/17/introduction-dustjs-2/</id>
<published>2013-08-17T08:26:00.000Z</published>
<updated>2016-07-24T02:45:08.142Z</updated>
<content type="html"><![CDATA[<p>从这篇文章开始我将介绍Dust的语法,其实内容基本上和Dust的<a href="https://github.com/linkedin/dustjs/wiki/Dust-Tutorial" target="_blank" rel="external">Tutorial</a>差不多,可能还要简化一点。</p>
<a id="more"></a>
<p>##Dust在线测试器</p>
<p>首先要介绍一下Dust项目中的一个<a href="http://linkedin.github.io/dustjs/test/test.html" target="_blank" rel="external">在线测试器</a>,在了解Dust语法的同时,在这个测试器上尝试应用学到的语法,既可以验证语法是否正确,也可以加强对语法的记忆。进入测试器后可以见到四个框,从左上、左下、右上、右下分别编号为1、2、3、4。测试时在1号框中填入一个Dust模板,然后2号框将显示该模板编译后的结果,再在3号框填入一个JSON对象,4号框中将显示最终的渲染结果。</p>
<p>##标签(Tag)</p>
<p>Dust模板以一种嵌入到HTML中的标签的形式存在。Dust标签使用一对花括号包裹,类似于HTML标签使用一对尖括号包裹:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{name}</div></pre></td></tr></table></figure>
<p>##注释</p>
<p>以下标签将不会产生任何内容,即可用作注释(感叹后之间):</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{! Comment syntax !}</div></pre></td></tr></table></figure>
<p>##键(Key)</p>
<p>一般Dust标签的表示只有两种形式,一种是键,另一种是区段。键是一个最简单的Dust标签,其中包含的花括号中的值称之为键,对应于JSON对象的属性名,对应的属性值一般为简单类型,比如字符串,渲染后将直接以属性值代替整个标签。如果搜索不到任何匹配值,则不会返回任何数据。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{name}</div></pre></td></tr></table></figure>
<p>在键名后面可以跟随过滤器,使用竖线分隔,一般用于选择处理“<”,“>”等特殊符号的转义:</p>
<ul>
<li>{name|s} 禁用自动转码</li>
<li>{name|h} 强制使用HTML转码</li>
<li>{name|j} 强制使用Javascript转码</li>
<li>{name|u} 使用encodeURI编码</li>
<li>{name|uc} 使用encodeURIComponent编码</li>
<li>{name|js} 将JSON对象转换为字符串</li>
<li>{name|jp} 将JSON 字符串转换为JSON对象</li>
</ul>
<p>过滤器也可以进行组合:</p>
<pre><code>{name|jp|h}
</code></pre><p>一些特殊字符也可以键的形式直接取值输出:</p>
<ul>
<li>{~n} 换行</li>
<li>{~r} CR换行</li>
<li>{~lb} 左花括号</li>
<li>{~rb} 右花括号</li>
<li>{~s} 空格</li>
</ul>
<p>##区段(Section)</p>
<p>以下两个标签及其包裹的部分称之为区段,用于循环显示数据。其中“#”为开始标签,“/”为结束标签,其后的键值同样对应于JSON对象的属性名,对应的属性值一般为数组或单个对象,单个对象将被当做一个只有一个元素的数组来对待。模板会按下标对数组中的每个元素调用一次区段包裹着的模板。上一篇中的例子就是利用了区段来循环输出列表元素。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#names}....{/names}</div></pre></td></tr></table></figure>
<p>在区段中可以使用两个特殊的键:</p>
<ul>
<li>{$idx} 表示当前迭代的序号(从0开始)</li>
<li>{$len} 表示数组长度</li>
</ul>
<p>##上下文(Context)</p>
<p>Dust对键或区段值的查询与javascript中对作用域链中变量值的查询类似,换而言之使用区段时会临时改变当前的上下文。</p>
<p>例如一个嵌套的JSON对象:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="string">"name"</span>: <span class="string">"root"</span>,</div><div class="line"> <span class="string">"anotherName"</span>: <span class="string">"root2"</span>,</div><div class="line"> <span class="string">"A"</span>:{</div><div class="line"> <span class="string">"name"</span>:<span class="string">"Albert"</span>,</div><div class="line"> <span class="string">"B"</span>:{</div><div class="line"> <span class="string">"name"</span>:<span class="string">"Bob"</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>使用区段索值:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#A}{name}{/A}</div></pre></td></tr></table></figure>
<p>则会得到这个对象的<code>A.name</code>的值:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Albert</div></pre></td></tr></table></figure>
<p>因为使用区段时将上下文转移到A属性对应的对象中。</p>
<p>而使用以下区段索值:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#A}{anotherName}{/A}</div></pre></td></tr></table></figure>
<p>因为在对象A的属性中不存在“anotherName”属性,于是Dust会向上查询A所处的上下文,发现存在“anotherName”属性,于是得到:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">root2</div></pre></td></tr></table></figure>
<p>若往上查找到JSON对象根部间的所有的上下文均无对应属性时将返回空白,索值不会向下查找。</p>
<p>##路径(Path)</p>
<p>若使用不带路径的区段索值,那么相当于从JSON对象的根部开始定位区段上下文。而使用路径可以指定开始搜索的位置。路径使用标志“.”来标记标签,跟javascript语法类似。依然是这个JSON对象:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="string">"name"</span>: <span class="string">"root"</span>,</div><div class="line"> <span class="string">"anotherName"</span>: <span class="string">"root2"</span>,</div><div class="line"> <span class="string">"A"</span>:{</div><div class="line"> <span class="string">"name"</span>:<span class="string">"Albert"</span>,</div><div class="line"> <span class="string">"B"</span>:{</div><div class="line"> <span class="string">"name"</span>:<span class="string">"Bob"</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>若我们需要取A属性下的B属性的name则可以表达成这样:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{A.B.name}</div></pre></td></tr></table></figure>
<p>或者使用路径标记区块:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#A.B}{name}{/A.B}</div></pre></td></tr></table></figure>
<p>或者使用单个“.”表示当前上下文对象(当前为字符串):</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#A.B.name}{.}{/A.B.name}</div></pre></td></tr></table></figure>
<p>规定路径后,首先在指定的上下文进行查找name的值,找不到时不会向上追溯,而是从根部开始查找。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#A.B}{A.name}{/A.B}</div></pre></td></tr></table></figure>
<p>上面这个模板将会在A.B中搜索A,因为B并无A属性,所以从JSON对象根部开始找到A属性,从而找到A.name,返回“Albert”,若从根部也无法找到,则返回空白。</p>
<p>##修改上下文</p>
<p>我们也可以在一定程度上修改上下文的关系。通过使用冒号“:”可以用冒号后面的键值代替前面的键值的父级上下文:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{#A:A2} ... {/A}</div></pre></td></tr></table></figure>
<p>以上这个区段会屏蔽掉A的父级上下文,临时将A2作为A的父级上下文,即在A中找不到目标时不会往上回溯,而去搜索A2下的属性。</p>
<p>##区段参数</p>
<p>在区段中可以设置参数:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{#A.B foo="Hi" bar=" Good to see you"}</div><div class="line"> {foo} {name} {bar}</div><div class="line">{/A.B}</div></pre></td></tr></table></figure>
<p>模板会将参数值替代键值标签,结果为:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Hi Bob Good to see you</div></pre></td></tr></table></figure>
<p>参数也可以是键名,但是赋值时的上下文在区段之外:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{#A.B foo=A.name bar=anotherName}</div><div class="line"> {foo} {name} {bar}</div><div class="line">{/A.B}</div></pre></td></tr></table></figure>
<p>##</p>
<p>至此,我们已经可以简单地将模板付诸应用了。下一节将介绍一些逻辑相关的语法。</p>
<p>##文章链接</p>
<ul>
<li><a href="/2013/08/16/introduction-dustjs-1">Dust.js语法简介(一)</a></li>
<li><a href="/2013/08/17/introduction-dustjs-2">Dust.js语法简介(二)</a></li>
<li><a href="/2013/08/19/introduction-dustjs-3">Dust.js语法简介(三)</a></li>
</ul>
]]></content>
<summary type="html">
<p>从这篇文章开始我将介绍Dust的语法,其实内容基本上和Dust的<a href="https://github.com/linkedin/dustjs/wiki/Dust-Tutorial">Tutorial</a>差不多,可能还要简化一点。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
</entry>
<entry>
<title>Dust.js语法简介(一)</title>
<link href="http://blog.sprabbit.com/2013/08/16/introduction-dustjs-1/"/>
<id>http://blog.sprabbit.com/2013/08/16/introduction-dustjs-1/</id>
<published>2013-08-16T01:44:00.000Z</published>
<updated>2016-07-24T02:45:08.134Z</updated>
<content type="html"><![CDATA[<p><a href="/2013/08/15/introduction-client-template/">经过一轮挣扎</a>,我作出了与LinkedIn一样的选择,使用Dust.js作为模板,但是因为Dust.js缺少中文文档,导致在国内的普及率比较低。于是我决定在这里对Dust的语法进行一些必要的介绍。</p>
<a id="more"></a>
<p>##为什么要用前端模板?</p>
<p>在静态页面中,包括已经从后台生成的HTML中,一般很少需要应用到模板,但是随着AJAX技术的发展,不刷新页面而动态更新内容的需求越来越高。为了降低通讯成本,这种通讯技术传输的一般是一个JSON对象,而不是一整串HTML字符串,所以在前端接受JSON数据之后,还要经过处理才能按要求显示在浏览器上。若只是用纯javascript进行拼接处理,将是一个比较繁琐的过程,而且写出来的代码不直观,可读性比较低。比如如果一个JSON对象<code>people</code>是下面这样的:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="string">"title"</span>: <span class="string">"Famous People"</span>,</div><div class="line"> <span class="string">"names"</span>: [{ <span class="string">"name"</span>: <span class="string">"Larry"</span> }, { <span class="string">"name"</span>: <span class="string">"Curly"</span> }, { <span class="string">"name"</span>: <span class="string">"Moe"</span> }]</div><div class="line">}</div></pre></td></tr></table></figure>
<p>我们要把他渲染成一个HTML列表如下:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">Famous People</div><div class="line"><span class="tag"><<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">li</span>></span>Larry<span class="tag"></<span class="name">li</span>></span></div><div class="line"> <span class="tag"><<span class="name">li</span>></span>Curly<span class="tag"></<span class="name">li</span>></span></div><div class="line"> <span class="tag"><<span class="name">li</span>></span>Moe<span class="tag"></<span class="name">li</span>></span></div><div class="line"><span class="tag"></<span class="name">ul</span>></span></div></pre></td></tr></table></figure>
<p>使用纯粹的javascript将是这样的:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> result = people.title + <span class="string">'\n'</span>;</div><div class="line">result += <span class="string">'<ul>'</span> + <span class="string">'\n'</span>;</div><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < people.names.length; i++) {</div><div class="line"> result += <span class="string">'<li>'</span> + people.names[i].name + <span class="string">'</li>'</span> + <span class="string">'\n'</span>;</div><div class="line">}</div><div class="line">result += <span class="string">'</ul>'</span></div></pre></td></tr></table></figure>
<p>当然了这只是一个比较简单的例子,而且代码具有专用性且不是最简形式,但是为了兼顾可读性和简介性这样写是比较好的。而使用dust模板将只需要一个模板:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">{title}</div><div class="line"><span class="tag"><<span class="name">ul</span>></span></div><div class="line">{#names}</div><div class="line"> <span class="tag"><<span class="name">li</span>></span>{name}<span class="tag"></<span class="name">li</span>></span>{~n}</div><div class="line">{/names}</div><div class="line"><span class="tag"></<span class="name">ul</span>></span></div></pre></td></tr></table></figure>
<p>然后将<code>people</code>传给编译好的模板则可生成所需要的结果,非常直观。</p>
<p>##什么是Dust.js?</p>
<p>Dustjs、dust.js或者直接叫Dust,是一种模板,一开始是由<a href="https://github.com/akdubya" target="_blank" rel="external">Aleksander</a> 编写并于2010发布第一个版本<a href="https://github.com/akdubya/dustjs" target="_blank" rel="external">于Github</a>。因为后台编译使用 Node.js 所以延续了在插件名后加.js的传统。Aleksander 很喜欢胡子模板 Mustache 的语法。但是Mustache缺少了Aleksander想要的特性,比如模板块和高性能。</p>
<p>从<a href="http://akdubya.github.io/dustjs/" target="_blank" rel="external">dust.js的导引页面</a>看来,这个模板制作者还是很有诚意的。可惜这个项目已于两年前停止更新,版本停留在0.3.0。好消息是大型职业社交网<a href="http://www.linkedin.com" target="_blank" rel="external">LinkedIn</a>也了解到了这个模板的优点和潜力,并接手了Dust.js的后续开发,最终出来的就是现在的<a href="http://linkedin.github.io/dustjs/" target="_blank" rel="external">Dust.js(LinkedIn)</a>,为了简便起见后面继续称之为Dust。经过不断地更新,Dust目前已经到了2.0版本了。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>由于时间关系,我还是决定将真正的语法介绍留到下一篇文章了,敬请期待。</p>
<p>##文章链接</p>
<ul>
<li><a href="/2013/08/16/introduction-dustjs-1">Dust.js语法简介(一)</a></li>
<li><a href="/2013/08/17/introduction-dustjs-2">Dust.js语法简介(二)</a></li>
<li><a href="/2013/08/19/introduction-dustjs-3">Dust.js语法简介(三)</a></li>
</ul>
]]></content>
<summary type="html">
<p><a href="/2013/08/15/introduction-client-template/">经过一轮挣扎</a>,我作出了与LinkedIn一样的选择,使用Dust.js作为模板,但是因为Dust.js缺少中文文档,导致在国内的普及率比较低。于是我决定在这里对Dust的语法进行一些必要的介绍。</p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
</entry>
<entry>
<title>前端模板的选择</title>
<link href="http://blog.sprabbit.com/2013/08/15/introduction-client-template/"/>
<id>http://blog.sprabbit.com/2013/08/15/introduction-client-template/</id>
<published>2013-08-15T12:01:00.000Z</published>
<updated>2016-07-24T02:45:08.129Z</updated>
<content type="html"><![CDATA[<p>最近接到一个任务,让我选一个前端模板。但是我其实连js模版实战经验都没有,为什么要让我选呢。不过接到任务就得照办了。首先得先知道什么是前端模板呢,稍微查了些百科,简单来说就是利用内嵌于HTML中的一些标记,来直观迅速生成HTML字符串的工具了,类似于后台用PHP标记<?=xxx>来生成HTML这样,而不需要繁琐的字符串拼接操作。需要在前端用javascript来使用,所以叫做前端模板。那么类似后台有多种语言,前端模板也有多种多样,以至于有人在github上做了一个应用方便大家选择前端模板:<a href="http://garann.github.io/template-chooser/" target="_blank" rel="external">TemplateChooser</a></p>
<a id="more"></a>
<p>虽然其中没有包括所有的模板,但是人们最常用的都在里面了,点开左边的标签设置选项,右面剩下的模板名就是符合要求的了。</p>
<p>第一项 Is this for use on the client or the server,就是模板需要运行在前端还是后台,对于前端模板当然选前台,但是不少模板是可以运行在前后端的。</p>
<p>第二项 How much logic should it have,表明你需要模板中可以包含多少逻辑处理。其中 the entirety of JS 选项代表模板中需要可以运行JS代码;just the basics 则代表模板中仅包含基本的逻辑,比如判断这类的;none at all 则表示模板中不包含任何逻辑,换而言之几乎只能做变量替换的操作。</p>
<p>第三项 Does it need to be one of the very fastest,表示你是否对速度有特别严苛的要求,yes or no。选完yes后会发现只剩三种doT.js、Microtemplating、Underscore templates,这是由一个应用来判定的:<a href="http://jsperf.com/javascript-templating-shootoff-extended" target="_blank" rel="external">jsPerf</a>。这三种在测试中是排行前三的。但是我们发现里面评测的模板并不完全,所以其实这项并没有太大的参考价值。</p>
<p>第四项 Do you need to pre-compile templates,表示你是否需要可以预编译的模板。首先要解释一下编译的意思,对于一般编程语言,编译就是让代码转换并优化成机器可以识别的机器码,从而机器可以直接运行。这里的编译意思类似,一个模板只是一个直观的数据字符串,我们需要让他变得可执行,那么我们就要让他转变成一个可以执行的函数对象,虽然这个对象一般也是以字符串的形式存在的。是否可以预编译的差别在于,如果你的模板是固定不变的,那么如果预先编译好并将编译结果保存起来,要使用的时候就不需要再动态编译,可以减少你分析模板的时间。</p>
<p>第五项 Do you need compile-time partials,表示你是否需要在编译时的模板拆分。这里的拆分其实并不是准确的表达,但是我找不到一个更好的词来形容了。实际上这个概念很简单,就是模块化的思想,可以将多个模板中重复使用的部分拆分出来单独存成一个模板文件,而让其他需要使用这个部分的模板导入这个模板文件。这种操作一般只在服务端模板中进行,因为在前端难以动态读取后台的分布式拆分文件。但是对于可以预编译的模板,则可以预先将分布式的文件编译好,然后在前台一一导入后使用。</p>
<p>第六项 Do you want a DOM structure, or just a string,那是问你的模板是一个字符串还是一个DOM结构。关于这点我也不太清楚,因为在我的印象中模板就是一个字符串,而里面包含了所渲染的HTML字符串的直观表示。选了DOM之后,同样只剩下三种模板dom.js、pure.js、Transparency。我简单地查看了一下pure.js模板(他同时也是一个不包含逻辑的模板),发现他并不是将一个模板保存为字符串,而是直接在HTML文件中为标签添加一些特殊的类,然后渲染的时候再用选择器出这个DOM对象进行渲染,换而言之他的模板是直接内嵌在HTML文件中的!那么确实如果希望应用逻辑是不大可能的,因为必须保证模板符合XHTML规范。</p>
<p>第七项 Aside from template tags, should it be the same language before and after rendering,这项比较好理解,除了模板标签之外,模板的语法要和模板渲染后的语法一样,那么在这里我们要渲染出HTML语言,结果是显然易见的,就是模板是一种在HTML语言中内嵌标签的语言。或许你会觉得很奇怪,难道还有什么除了插入标签外的神奇的语法吗?选No之后又再一次出现了三种模板dom.js、Jade templates、Microtemplating。我再一次简单的看了下dom.js,因为他同时也是一个使用DOM结构的模板。结果发现DOM是一种完全使用Javascript的模板!他的语法还是非常直观的:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line">header(</div><div class="line"> h1(<span class="string">'Heading'</span>),</div><div class="line"> h2(<span class="string">'Subheading'</span>));</div><div class="line"></div><div class="line">nav(</div><div class="line"> ul({ <span class="string">'class'</span>: <span class="string">'breadcrumbs'</span> },</div><div class="line"> li(a({ href: <span class="string">'/'</span> }, <span class="string">'Home'</span>)),</div><div class="line"> li(a({ href: <span class="string">'/section/'</span>}, <span class="string">'Section'</span>)),</div><div class="line"> li(a(<span class="string">'Subject'</span>))));</div><div class="line"></div><div class="line">article(</div><div class="line"> p(<span class="string">'Lorem ipsum...'</span>));</div><div class="line"></div><div class="line">footer(<span class="string">'Footer stuff'</span>);</div></pre></td></tr></table></figure>
<p>明白了上面的含义的话,其实还是很好选择的。关于选项中的第二项可能有点争议,到底模板需要多少逻辑呢。如果从模板的出发点来看的话,其实答案是显然易见的。有些人可能觉得功能越强大的模板越好,但是事实恰好相反,人们发明了模板就是为了直观地设计输出数据的表现形式,让显示与逻辑数据分离。此时若为了让模板支持更强大的功能而硬是往里面添加复杂的逻辑,甚至让其支持javascript,是本末倒置的,这些明明可以直接在javascript中操作的东西又何必硬塞到模板里面让其可读性降低呢。然而不包含任何逻辑毕竟不现实,因此大多数模板都提供了最基本的逻辑能力,比如遇到空对象的特殊处理方法。所以这里一般选择just the basics。</p>
<p>最后剩下来的模板也只剩6个,但是到底要如何取舍,还是不能简单的决定。经过一些对比评测[1],大家普遍认为以下三种是综合评分最高的:Mustache.js、Handlebars.js、dust.js。</p>
<p><a href="https://github.com/janl/mustache.js/" target="_blank" rel="external">Mustache</a> 应该是最流行的模版了,显示与逻辑分离,模板语法清晰,但是逻辑功能太过缺乏,代码庞大而且效率比较低,已经停止维护超过两年。他的语法如下:<br><figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{ {#stooges} }</div><div class="line"><span class="tag"><<span class="name">b</span>></span>{ {name} }<span class="tag"></<span class="name">b</span>></span></div><div class="line">{ {/stooges} }</div></pre></td></tr></table></figure></p>
<p><a href="https://github.com/wycats/handlebars.js/" target="_blank" rel="external">Handlebars</a><br>是Mustache的改进,显示与逻辑分离,语法兼容Mustache,可以编译成代码,改进Mustache对路径的支持,但是若需要在服务端运行需要使用服务端Javascript引擎如Node.js。<br><figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{ {#each comments} }</div><div class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"body"</span>></span>{ {body} }<span class="tag"></<span class="name">div</span>></span></div><div class="line">{ {/each} }</div></pre></td></tr></table></figure></p>
<p><a href="https://github.com/linkedin/dustjs" target="_blank" rel="external">Dust</a><br>也是显示与逻辑分离,语法比Mustache更清晰简便(少写一对花括号{}!),可以编译成代码,比Mustache提供更好的路径支持,支持更多的扩展功能,异步渲染,执行效率更高。但是若需要在服务端运行需要使用服务端Javascript引擎如Node.js。<br><figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{#names}</div><div class="line"><span class="tag"><<span class="name">li</span>></span>{name}<span class="tag"></<span class="name">li</span>></span>{~n}</div><div class="line">{/names}</div></pre></td></tr></table></figure></p>
<p>以上三种都是最优秀的模板了,语法可以说是比较类似,但是无论从结果还是过程看Dust都有着无比强大的优势。目前Dust.js是由LinkedIn团队维护的,最近已更新到2.0版本,相比以前有更大的优化,所以我觉得选用dust.js作为模版是比较合适的。只可惜Dust.js毕竟没有胡子Mustache家族势力广泛,尤其在我大天朝,少有见到关于dust的技术文章。下次我打算尽我微薄之力,为大家介绍一下Dust的使用方法,普及一下dust这个LinkedIn推荐的模板。</p>
<p>参考:</p>
<p>[1] <a href="http://engineering.linkedin.com/frontend/client-side-templating-throwdown-mustache-handlebars-dustjs-and-more" target="_blank" rel="external">The client-side templating throwdown: mustache, handlebars, dust.js, and more</a></p>
]]></content>
<summary type="html">
<p>最近接到一个任务,让我选一个前端模板。但是我其实连js模版实战经验都没有,为什么要让我选呢。不过接到任务就得照办了。首先得先知道什么是前端模板呢,稍微查了些百科,简单来说就是利用内嵌于HTML中的一些标记,来直观迅速生成HTML字符串的工具了,类似于后台用PHP标记&lt;?=xxx&gt;来生成HTML这样,而不需要繁琐的字符串拼接操作。需要在前端用javascript来使用,所以叫做前端模板。那么类似后台有多种语言,前端模板也有多种多样,以至于有人在github上做了一个应用方便大家选择前端模板:<a href="http://garann.github.io/template-chooser/">TemplateChooser</a></p>
</summary>
<category term="技术" scheme="http://blog.sprabbit.com/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Javascript" scheme="http://blog.sprabbit.com/tags/Javascript/"/>
</entry>
<entry>
<title>从游戏服务端到Web前端</title>
<link href="http://blog.sprabbit.com/2013/08/13/game-server-to-front-end/"/>
<id>http://blog.sprabbit.com/2013/08/13/game-server-to-front-end/</id>
<published>2013-08-13T08:03:00.000Z</published>
<updated>2016-07-24T02:45:08.122Z</updated>
<content type="html"><![CDATA[<p>虽然这个标题看起来很厉害,但是其实只是我的一篇随笔,没有很大的技术参考价值,况且我无论哪个方面也没有很高的造诣。这里只是抒发一下我开始工作后的感受,在这个公司提前下班的七夕里。</p>