/
atom.xml
1148 lines (1083 loc) · 88.5 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[I'm Chris]]></title>
<link href="http://chrisyip.im/atom.xml" rel="self"/>
<link href="http://chrisyip.im"/>
<updated>2013-04-01T16:47:51.271Z</updated>
<id>http://chrisyip.im/</id>
<author>
<name><![CDATA[Chris Yip]]></name>
<email><![CDATA[i@chrisyip.im]]></email>
</author>
<generator uri="http://zespia.tw/hexo">Hexo</generator>
<entry>
<title type="html"><![CDATA[Nimble Quest:贪食蛇版 RPG 游戏]]></title>
<link href="http://chrisyip.im/post/nimble-quest/"/>
<id>http://chrisyip.im/post/nimble-quest/</id>
<published>2013-04-01T06:11:01.000Z</published>
<updated>2013-04-01T07:09:28.000Z</updated>
<content type="html"><![CDATA[<p>最近发现一款好玩的小游戏——Nimble Quest。</p>
<p><img src="/bimg/nimble-quest.jpg" alt="Nimble Quest"></p>
<p>玩的方式和 Nokia 手机经典游戏贪食蛇一样,通过滑动手指或方向键(OS X)来控制方向,规则也和贪食蛇一样,撞到障碍物人物会死亡,只不过由于融合了 RPG 要素,每个角色都有自己的血量,撞到障碍物或血量降到 0 就会死亡,而排头的队长死亡则 Game Over。</p>
<p>Nimble Quest 采用 IAP 收费,但是玩起来可以一分钱也不花,没有广告。游戏内类货币的物件分为两个,一个是宝石,一个为 token。宝石通过游戏内收集,用于升级角色和辅助道具;token 则可 IAP 购买,游戏过程中有极低机率掉落,主要用于 Game Over 时继续游戏或者购买队伍人员上限。</p>
<p>角色的解锁只要顺利推进进度即可,当然也可以 IAP 购买,而 Game Over 时如果不用 token 继续游戏就需要从头开始打,如果觉得进度不错,建议直接用 token 继续吧。</p>
<p>角色的升级有两个途径,杀怪和使用宝石。</p>
<p>作为一款悠闲小游,很适合无聊时玩玩;同时提供 iOS 和 Mac 版本:</p>
<p>iOS:<a href="https://itunes.apple.com/cn/app/id583638819?mt=8"><a href="https://itunes.apple.com/cn/app/id583638819?mt=8">https://itunes.apple.com/cn/app/id583638819?mt=8</a></a></p>
<p>Mac:<a href="https://itunes.apple.com/cn/app/id598685044?mt=12"><a href="https://itunes.apple.com/cn/app/id598685044?mt=12">https://itunes.apple.com/cn/app/id598685044?mt=12</a></a></p>
]]></content>
<category scheme="http://chrisyip.im/tags/ios/" term="iOS"/>
<category scheme="http://chrisyip.im/tags/os-x/" term="OS X"/>
<category scheme="http://chrisyip.im/tags/game/" term="Game"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/04/" term="04"/>
</entry>
<entry>
<title type="html"><![CDATA[HTML5 Storage APIs]]></title>
<link href="http://chrisyip.im/post/html5-storage-apis/"/>
<id>http://chrisyip.im/post/html5-storage-apis/</id>
<published>2013-03-28T19:31:58.000Z</published>
<updated>2013-04-01T06:52:18.000Z</updated>
<content type="html"><![CDATA[<p>因为太久没用这些东西,之前被人问起时只能勉强答出两个,并且连事件也不记得,只好把常用的列出来强化记忆了。</p>
<p><code>localStorage</code> 和 <code>sessionStorage</code> 的 APIs 在行为表现上是一致的,以下以 <code>localStorage</code> 为例,而 <code>globalStorage</code> 因为不是标准,所以略过。</p>
<h3>函数和属性:</h3>
<h4><code>setItem(key, value)</code></h4>
<p>将 <code>value</code> 以 <code>key</code> 为名存入 <code>localStorage</code>。</p>
<p><code>key</code> 和 <code>value</code> 理论上可接受任何类型,但实际会通过 <code>String(value)</code> 转换为字符串,所以需慎用,比如 <code>Object</code> 会转换成 <code>'[object Object]'</code>,<code>null</code> 会转换成 <code>'null'</code>。</p>
<pre><code>localStorage.setItem(<span class="string">'hello'</span>, <span class="string">'Chris'</span>)
localStorage.key(<span class="number">0</span>) + <span class="string">' '</span> + localStorage.getItem(<span class="string">'hello'</span>) <span class="comment">// 'hello Chris'</span>
localStorage.clear()
<span class="keyword">var</span> obj = { str: <span class="string">'an object'</span> }
localStorage.setItem(obj, { posts: [{}, {}] })
localStorage.key(<span class="number">0</span>) <span class="comment">// '[object Object]'</span>
localStorage.getItem(obj) <span class="comment">// '[object Object]'</span>
<span class="keyword">var</span> otherObj = { str: <span class="string">'another object'</span> }
localStorage.setItem(otherObj, <span class="literal">true</span>)
localStorage.getItem(obj) <span class="comment">// 'true'</span>
localStorage.clear()
localStorage.setItem(<span class="literal">null</span>, <span class="literal">null</span>)
localStorage.getItem(<span class="literal">null</span>) <span class="comment">// 'null'</span>
</code></pre>
<h4><code>getItem(key)</code></h4>
<p>要注意 <code>String()</code> 的问题。</p>
<p>若 <code>key</code> 不存在,<code>getItem()</code> 会返回 <code>null</code>,如果在 <code>setItem()</code> 时并没有进行严格验证的话,建议同时判断是否为 <code>'null'</code> 来确定是否存在。</p>
<h4>访问 key/value 的其他方法</h4>
<p><code>localStorage</code> 可以像数组一样操作,而 <code>key</code> 也会作为普通对象的属性一样被访问。</p>
<pre><code>localStorage[<span class="string">'hello'</span>] = <span class="string">'world'</span>
localStorage.hello <span class="comment">// 'world'</span>
localStorage.hello = <span class="string">'Chris'</span>
localStorage[<span class="string">'hello'</span>] <span class="comment">// 'Chris'</span>
</code></pre>
<h4><code>removeItem()</code> 和 <code>clear()</code></h4>
<p>一个删除单个,一个删除全部,不返回任何值,<code>key</code> 不存在时也不会报错。</p>
<h4><code>key(keyID)</code></h4>
<p>返回指定 ID 的 <code>key</code> 名字。</p>
<p><code>keyID</code> 只接受整数,因为会通过 <code>Math.floor(keyID)</code> 来转换,所以支持非整数,如 <code>'1.1'</code>、<code>'1a'</code> 和 <code>true</code> 等等,只是结果为 <code>NaN</code> 时取 <code>0</code>;若不存在,返回 <code>null</code>。</p>
<p>已知问题:</p>
<ul>
<li>Chrome v25 和 Firefox v19 的行为不一样:Chrome 会将 keys 按照字符串大小进行正排序,而 Firefox 则几乎是乱序,不可预测 <code>key</code> 的位置。</li>
</ul>
<pre><code>localStorage.setItem(<span class="string">'google'</span>, <span class="string">'what the hell?'</span>)
localStorage.setItem(<span class="string">'miscrosoft'</span>, <span class="string">'not bad'</span>)
localStorage.setItem(<span class="string">'apple'</span>, <span class="string">'great'</span>)
localStorage.key(<span class="number">0</span>) <span class="comment">// Chrome: apple</span>
localStorage.key(<span class="number">1</span>) <span class="comment">// Chrome: google</span>
</code></pre>
<h4><code>length</code></h4>
<p>返回 <code>key</code> 的总数。</p>
<h3>事件</h3>
<p>目前只支持一个事件:<code>storage</code>。</p>
<pre><code><span class="keyword">if</span> (window.addEventListener) {
window.addEventListener(<span class="string">"storage"</span>, handle_storage, <span class="literal">false</span>);
} <span class="keyword">else</span> {
window.attachEvent(<span class="string">"onstorage"</span>, handle_storage);
};
</code></pre>
<p>这段代码我偷懒从 <a href="http://diveintohtml5.info/storage.html">Dive Into HTML5</a> 搬过来的,专有的事件属性如下:</p>
<ul>
<li>key:不解释</li>
<li>oldValue:旧值</li>
<li>newValue:新值</li>
<li>url:页面的 URL</li>
</ul>
<p><code>url</code> 有点特殊,原来是叫 <code>uri</code> 的,所以要兼容某些浏览器的话,最好两个都判断,较新的标准浏览器应该没有这个问题。</p>
<p>此事件不可终止,并且只有在 <code>setItem()</code>、<code>removeItem()</code> 和 <code>clear()</code> 确实影响了值才会触发:</p>
<pre><code>localStorage.setItem(<span class="string">'hello'</span>, <span class="string">'world'</span>) <span class="comment">// will fire</span>
localStorage.clear() <span class="comment">// will fire</span>
localStorage.clear() <span class="comment">// will not fire</span>
</code></pre>
<p>另外,对触发页面的要求也很特别:<strong>会触发事件的页面并不是执行修改操作的页面,而是已打开的、共享同一个储存区的其他页面</strong>。</p>
<p>最后,事件绑定只能绑定在 <code>window</code> 上。</p>
<p><em>以上仅在 Chrome v25 和 Firefox v19 上测试过。</em></p>
]]></content>
<category scheme="http://chrisyip.im/tags/html5/" term="HTML5"/>
<category scheme="http://chrisyip.im/tags/javascript/" term="JavaScript"/>
<category scheme="http://chrisyip.im/tags/storage/" term="Storage"/>
<category scheme="http://chrisyip.im/tags/localstorage/" term="localStorage"/>
<category scheme="http://chrisyip.im/tags/sessionstorage/" term="sessionStorage"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/03/" term="03"/>
</entry>
<entry>
<title type="html"><![CDATA[→ 为什么 Google 要砍掉 Google Reader?]]></title>
<link href="https://www.quora.com/Google-Reader-Shut-Down-March-2013/Why-is-Google-killing-Google-Reader/answer/Brian-Shih"/>
<id>http://chrisyip.im/post/why-is-google-killing-google-reader/</id>
<published>2013-03-17T14:03:29.000Z</published>
<updated>2013-03-17T14:32:49.000Z</updated>
<content type="html"><![CDATA[<p>在 Google <a href="http://www.theverge.com/2013/3/13/4101144/google-shuts-down-reader-rss-aggregation-service">宣布</a>将会在 2013 年 7 月 1 日关闭 Google Reader 之后,Quora 就有了一个问题:「<a href="https://www.quora.com/Google-Reader-Shut-Down-March-2013/Why-is-Google-killing-Google-Reader">为什么 Google 要砍掉 Google Reader?</a>」。</p>
<p>曾经担任过 Reader PM 的 Brian Shih <a href="https://www.quora.com/Google-Reader-Shut-Down-March-2013/Why-is-Google-killing-Google-Reader/answer/Brian-Shih">回答说</a>:Google 会关闭 Reader,只是为了社交领域,比如 Google+,而在此之前,Google 已经<strong>三度尝试</strong>把 Reader 团队拉到社交产品的开发上:</p>
<ul>
<li>2008:OpenSocial</li>
<li>2009:Buzz</li>
<li>2010:Google+</li>
</ul>
<p>因为 Facebook 一直在影响着互联网广告市场,以广告为主要收入的 Google 自然不会放弃这块蛋糕,会进入社交领域属于必然之举。</p>
<p>而声称「不作恶」的 Google,并没有声称「不赚钱」,没有直接盈利又无法和社交媒体抗衡(毕竟不是一回事)的 Reader 自然没有运营下去的价值。因此,Google 的广告策略也备受吐槽,比如 Google Glass 的恶搞视频(<em><a href="http://youtu.be/_mRF0rBXIeg">YouTube</a>,<a href="http://v.youku.com/v_show/id_XNDIxMDQzODMy.html">优酷</a></em>)。</p>
<p>Reader 被砍掉确实让人难过,因为它不仅储存着很多不可访问的网站的文章,还有着成熟的第三方,不过天下无不散之筵席,所以并不需要太纠结于此,何况 Reeder、Feedly 甚至 Digg 都表示会做一些事情。</p>
<p>从某个角度来说,因 Reader 而停滞多年的 RSS 市场,可能会因此有难以想象的成长。</p>
<p>塞翁失马,焉知非福,不是吗?</p>
<p><a href="https://www.quora.com/Google-Reader-Shut-Down-March-2013/Why-is-Google-killing-Google-Reader/answer/Brian-Shih">∞ Permalink</a>]]></content>
<category scheme="http://chrisyip.im/tags/google/" term="Google"/>
<category scheme="http://chrisyip.im/tags/google-reader/" term="Google Reader"/>
<category scheme="http://chrisyip.im/tags/rss/" term="RSS"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/03/" term="03"/>
</entry>
<entry>
<title type="html"><![CDATA[→ Animate.css: CSS 动画懒人包]]></title>
<link href="http://daneden.me/animate/build/"/>
<id>http://chrisyip.im/post/animate-css-builder/</id>
<published>2013-03-12T10:34:01.000Z</published>
<updated>2013-03-12T10:53:31.000Z</updated>
<content type="html"><![CDATA[<p><a href="http://daneden.me/animate/build/">Animate.css</a> 打包了常见的 CSS 动画代码,只要勾上然后按按钮就直接下载打包好的 CSS 文件,然后在 HTML 代码里加上对应的 class 就可以,相当便利。</p>
<p><a href="http://daneden.me/animate/build/">∞ Permalink</a>]]></content>
<category scheme="http://chrisyip.im/tags/css/" term="CSS"/>
<category scheme="http://chrisyip.im/tags/animation/" term="Animation"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/03/" term="03"/>
</entry>
<entry>
<title type="html"><![CDATA[→ Why I loved building Basecamp for iPhone in RubyMotion]]></title>
<link href="http://37signals.com/svn/posts/3432-why-i-loved-building-basecamp-for-iphone-in-rubymotion"/>
<id>http://chrisyip.im/post/why-i-loved-building-basecamp-for-iphone-in-rubymotion/</id>
<published>2013-02-19T08:43:35.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>37signals 在最近发布 <a href="https://itunes.apple.com/us/app/basecamp-official-app/id599139477?mt=8">Basecamp for iPhone</a>,由于 37signals 是 Ruby 的坚定支持者,所以该 app 是由 <a href="http://www.rubymotion.com/">RubyMotion</a> 打造的,甚至开发工具也跳过了 Xcode。</p>
<p>在上架不久后,<a href="http://twitter.com/qrush">Nick</a> 发了<a href="http://37signals.com/svn/posts/3432-why-i-loved-building-basecamp-for-iphone-in-rubymotion">一篇文章</a>来说明他们是为什么选择和怎样使用 RubyMotion 打造这款应用。</p>
<h3>学习成本和 IDE 的缺陷</h3>
<p>Nick 对使用 Objective-C 开发应用的看法时:「如果使用 Objective-C,就意味着我需要抛弃我现有的工具和工作流,然后学习新的 API、框架和更多的东西(如 IDE)」,并且提到他早年使用 Visual Basic 控件和 Visual Studio 时的<strong>不好</strong>经验,因此非常抗拒使用 Xcode 和 Interface Builder。</p>
<p>我相当同意 Nick 的观点,就像 <a href="http://coffeescript.org/">CoffeeScript</a> 和 <a href="http://code.google.com/p/brython/">Brython</a> 和 JavaScript 的关系,对于 Ruby 或 Python 程序员来说,直接用前两者会少很多学习成本。</p>
<p>而 Xcode,说实话,稳定性还没 Visual Studio 好,在代码补全、文档、Interface Builder、应用发布等方面虽然会比一般编辑器要好,所以还能忍受缺陷,但如果有代替方案呢?</p>
<h3><a href="https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/Introduction.html">Auto Layout</a></h3>
<p>Auto Layout 是 iOS 6 新加的 API,为<del>马脸</del> iPhone 5 准备的:</p>
<pre><code><span class="comment">#</span> <span class="comment">horizontal</span>
<span class="comment">"|</span>-<span class="comment">10</span>-<span class="title">[</span><span class="comment">switchButton</span>]<span class="literal">-</span><span class="comment">10</span>-<span class="comment">|"</span>
<span class="comment">"|</span>-<span class="comment">10</span>-<span class="title">[</span><span class="comment">helpButton</span>]<span class="literal">-</span><span class="comment">10</span>-<span class="comment">|"</span>
<span class="comment">#</span> <span class="comment">vertical</span>
<span class="comment">"|</span>-<span class="comment">15</span>-<span class="title">[</span><span class="comment">switchButton</span>]<span class="literal">-</span><span class="comment">10</span>-<span class="title">[</span><span class="comment">helpButton(==switchButton)</span>]<span class="literal">-</span><span class="comment">15</span>-<span class="comment">|"
</code></pre>
<h3>第三方框架和类库</h3>
<p>Nick 吐槽了 Objective-C 第三方框架和类库令人不满的现状:</p>
<ul>
<li>(相对于 Ruby)没有令人满意的依赖管理工具,<a href="http://cocoapods.org/">CocoaPods</a> 做得很好,可是还不够</li>
<li>大部分不在 CocoaPods 的框架或类库的 README 只是简单地使用「拖曳 <code>.xcodeproj</code> 文件到 Xcode」或「拖曳 <code>.h</code> 和 <code>.m</code> 文件到你的项目」等等让人头痛的说明(谁叫你不用 Xcode :P)</li>
</ul>
<p>CocoaPods 我也有在用,虽然它让使用第三方框架和类库变得简单了一些,但并不是很完美,如果能像 RubyGems 或 Node Packaged Modules 那样的话,就真的真的 save my ass 了。</p>
<h3>调试</h3>
<p>37signals 是使用 <code>rake</code> 和 <a href="http://www.rubymotion.com/developer-center/articles/testflight/">TestFlight</a> 来进行调试,而 RubyMotion 官方也提供了<a href="http://www.rubymotion.com/developer-center/articles/debugging/">如何调试的指南</a>。</p>
<p>因为最近多了一些 iOS 的调试器,都很逆天的感觉,比如 <a href="http://shopify.github.com/superdb/">SuperDB</a>,所以在调试方面,可能脱离 Xcode 也不会造成很大的问题。</p>
<p><a href="http://37signals.com/svn/posts/3432-why-i-loved-building-basecamp-for-iphone-in-rubymotion">∞ Permalink</a>]]></content>
<category scheme="http://chrisyip.im/tags/ruby/" term="Ruby"/>
<category scheme="http://chrisyip.im/tags/objective-c/" term="Objective-C"/>
<category scheme="http://chrisyip.im/tags/dev/" term="Dev"/>
<category scheme="http://chrisyip.im/tags/ios/" term="iOS"/>
<category scheme="http://chrisyip.im/tags/rubymotion/" term="RubyMotion"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/02/" term="02"/>
<category scheme="http://chrisyip.im/categories/2013/02/ruby/" term="Ruby"/>
</entry>
<entry>
<title type="html"><![CDATA[迁移到 Hexo]]></title>
<link href="http://chrisyip.im/post/hexo-rocks/"/>
<id>http://chrisyip.im/post/hexo-rocks/</id>
<published>2013-02-04T11:08:08.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>忙了两天,终于从 <a href="http://octopress.org">Octopress</a> 迁移到 <a href="http://zespia.tw/hexo/">Hexo</a> 了,订阅的朋友,如果收到了重复的更新请见谅。</p>
<p>迁移的过程中,最困难的是想要尽可能兼容原本在 <a href="">Octopress</a> 能用的东西,比如 linklog 的自定义样式。除此之外,真的深刻地感受到 Hexo 的优点,V8 引擎的快就不必说了,使用的 Stylus 和 EJS 都让人觉得舒服。Jekyll 抱着 Liquid 不放真是一大败笔。</p>
<p>接下来就是要为 Hexo 写一些插件,比如压缩 HTML,来达到最大的自定义。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/hexo/" term="Hexo"/>
<category scheme="http://chrisyip.im/tags/jekyll/" term="Jekyll"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/02/" term="02"/>
</entry>
<entry>
<title type="html"><![CDATA[→ The Good Man Project]]></title>
<link href="http://thegoodman.cc/"/>
<id>http://chrisyip.im/post/the-good-man-project/</id>
<published>2013-02-01T08:20:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p><a href="http://thegoodman.cc/">The Good Man</a> is a project shows the capacity of CSS3 animations as <strong>an animation tool</strong>.</p>
<p>This project is built only by HTML, CSS3, web fonts and shapes, without images.</p>
<p>Although it's pretty amazing, but there are still some things that need to be concerned:</p>
<ul>
<li>Firefox 18: animations work well, but the positions of elements are incorrect;</li>
<li>Chrome 24: most of the time, the performance is fine, no lags, very smooth, but not all the time;</li>
<li>The audio may be interrupted, and if it happends, you'll lose all sounds. Need a solution to detect download states and redownload if interrupted.</li>
</ul>
<p><a href="http://thegoodman.cc/">∞ Permalink</a>]]></content>
<category scheme="http://chrisyip.im/tags/css/" term="CSS"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/02/" term="02"/>
</entry>
<entry>
<title type="html"><![CDATA[优化 CSS 和 JavaScript 请求数的另类方法]]></title>
<link href="http://chrisyip.im/post/load-css-and-javascript-in-special-ways/"/>
<id>http://chrisyip.im/post/load-css-and-javascript-in-special-ways/</id>
<published>2013-01-27T13:43:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p><a href="http://hancic.info/">Jan Hančič</a> 公布了一个在一次请求里同时加载 CSS 和 JavaScript 的方案,<a href="http://janhancic.github.com/platypus.js/">platypus.js</a>。</p>
<p>实现原理是利用 CSS 的 <code>content</code> 属性,把 JavaScript 转换成 base64 字符串放进去,再动态插入 DOM。</p>
<p>示例 CSS:</p>
<pre><code><span class="id">#platypus-0</span> <span class="rules">{
<span class="rule"><span class="attribute">display</span>:<span class="value"> none;</span></span>
<span class="rule"><span class="attribute">content</span>:<span class="value"> <span class="string">'YWxlcnQoJ0hpLCBteSBmcmllbmQhIEZyb20gQ2hyaXMnKQo='</span>;</span></span>
<span class="rule">}</span></span>
</code></pre>
<p>示例 JS:</p>
<pre><code>(<span class="function"><span class="keyword">function</span> <span class="params">( document )</span> {</span>
<span class="keyword">var</span> linkTags = document.querySelectorAll ( <span class="string">'link[rel="stylesheet"]'</span> );
<span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>; i < linkTags.length; i++ ) {
<span class="keyword">var</span> dummyDiv = document.createElement ( <span class="string">'div'</span> );
dummyDiv.id = <span class="string">'platypus-'</span> + i;
document.body.appendChild ( dummyDiv );
<span class="keyword">var</span> styles = document.defaultView.getComputedStyle ( dummyDiv, <span class="literal">null</span> );
<span class="keyword">var</span> js = styles.getPropertyValue ( <span class="string">'content'</span> ); <span class="comment">// w3c</span>
<span class="comment">//backgroundImage = someElement.currentStyle["background-image"]; //IE</span>
<span class="keyword">if</span> ( js === <span class="string">''</span> ) {
<span class="keyword">continue</span>;
}
dummyDiv.parentNode.removeChild ( dummyDiv );
<span class="keyword">if</span> ( js[<span class="number">0</span>] === <span class="string">"'"</span> || js[<span class="number">0</span>] === <span class="string">'"'</span> ) {
js = js.substr ( <span class="number">1</span>, js.length - <span class="number">2</span> );
}
<span class="keyword">var</span> scriptTag = document.createElement ( <span class="string">'script'</span> );
<span class="comment">// window.atob - decode base64 string, unavailable on IE9-, MDN says IE10 supports it</span>
scriptTag.innerHTML = atob ( js );
document.body.appendChild ( scriptTag );
};
} ( document ) );
</code></pre>
<p>按照 Jan 的说法,platypus.js 兼容非兼容视图的 IE9,大致上主流的桌面、移动浏览器都可以兼容。</p>
<p>这个方案很有意思,虽然 CSS 文件的大小增加了,但在越来越快的带宽和越来越好的 CDN 的影响下,DNS Lookup、Connecting 和 Wating 的消耗说不定比多出的部分少不了多少,特别是小文件。</p>
<p>我也想过利用 <code>localStorage</code> 来实现类似 <code>manifest</code> 的效果,简单的示例代码如下:</p>
<pre><code><span class="keyword">var</span> load = (<span class="keyword">function</span>() {
<span class="keyword">var</span> insertToDOM, getSource
insertToDOM = <span class="keyword">function</span>( content ) {
<span class="keyword">var</span> script = document.createElement( <span class="string">'script'</span> )
script.textContent = content
document.body.appendChild( script )
}
getSource = <span class="keyword">function</span>( file ) {
<span class="keyword">var</span> source = localStorage.getItem( file )
<span class="keyword">if</span> (<span class="keyword">typeof</span> source !== <span class="string">'string'</span>) {
<span class="comment">// if no record, get source from server</span>
$.ajax( {
url: file,
success: <span class="keyword">function</span>( data ) {
localStorage.setItem( file, data )
insertToDOM( data )
}
} )
} <span class="keyword">else</span> {
insertToDOM( source )
}
}
<span class="keyword">return</span> <span class="keyword">function</span>(files) {
files.forEach( <span class="keyword">function</span>( file ) {
getSource( file )
})
};
})();
load( [<span class="string">'//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js'</span>,
<span class="string">'script.js'</span>] )
</code></pre>
<p>特点:</p>
<ul>
<li>可以支持很多文件,理论上能用 Data URI 加载的都可以;</li>
<li><code>localStorage</code> 的访问速度远远快于 DNS Lookup 等消耗;</li>
<li><code>localStorage</code> 默认能支持最大 4MB 的储存,对于大部分站点完全足够;</li>
<li>把脚本封装成 <code>(fn)()</code> 的形式并在底部插入特定的变量可判断版本、是否加载完成等,便于控制;</li>
<li>可控性和易用性高于 <code>manifest</code>,不需要配置服务器,需要清空时操作 <code>localStorage</code> 即可,而 <code>localStorage</code> 的存储持久性和 <code>manifest</code> 基本一样。</li>
</ul>
<p>只不过,这种做法和 AMD、CommonJS 等规范不太合拍——至少我还没想到好做法——小团队项目开发似乎挺不错,暂时只是草稿性质的想法。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/css/" term="CSS"/>
<category scheme="http://chrisyip.im/tags/javascript/" term="JavaScript"/>
<category scheme="http://chrisyip.im/tags/performance/" term="Performance"/>
<category scheme="http://chrisyip.im/tags/web-development/" term="Web Development"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/01/" term="01"/>
</entry>
<entry>
<title type="html"><![CDATA[Mutation 事件和 MutationObserver]]></title>
<link href="http://chrisyip.im/post/mutation-events-and-mutationobserver/"/>
<id>http://chrisyip.im/post/mutation-events-and-mutationobserver/</id>
<published>2013-01-26T12:22:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<h3>Mutation 事件</h3>
<p>DOM Level 3 提供了一系列的 mutation 事件,如 <code>DOMAttrModified</code>、<code>DOMNodeInserted</code>,供开发者观察 DOM 的变动。这些事件虽然不太常用,但却非常方便好用。比如在使用 ajax 往 DOM 添加新元素时,需要检查总高度超过了容器,常见的方法是在 ajax 完成时的回调函数里执行,但这样并不利于组织代码,甚至回调函数会很臃肿;如果使用 <code>DOMNodeInserted</code> 就好多了:</p>
<pre><code>$container.on( <span class="string">'DOMNodeInserted'</span>, <span class="keyword">function</span>() {
<span class="keyword">if</span> ( children_height > $container.height() ) {
<span class="comment">// do something</span>
}
})
$.ajax({
success: <span class="keyword">function</span>( data ) {
$container.append( $( data.html ) )
}
})
</code></pre>
<p>这样职责就清晰很多—— ajax 的回调函数仅处理返回的数据。</p>
<p>但 mutation 事件的性能太糟了,Chrome 也未必吃得消,而 Mozilla 在 <a href="https://developer.mozilla.org/en-US/docs/Extensions/Performance_best_practices_in_extensions">Performance best practices in extensions</a> 里也建议尽可能别用 mutation 事件。</p>
<p>因此 mutation 事件的处境很尴尬,能用的只有它,但又不能用,最终也被 DOM Level 4 引入的 <code>MutationObserver</code> 取代了。</p>
<h3>MutationObserver</h3>
<p>先看以下代码:</p>
<pre><code><ul></ul>
<script>
var ul = document.querySelector('ul')
, observer
try {
observer = new ( window.MutationObserver ||
window.WebKitMutationObserver )( function( mutationRecord,
observer ) {
mutationRecord.forEach( function( mutation ) {
console.log( mutation )
})
})
observer.observe( ul, { attributes: true, childList: true } )
} catch ( ex ) {}
ul.className = 'list'
ul.appendChild( document.createElement( 'li' ) )
// 不会触发
ul.children[0].className = 'item'
// 在不需要时,停止继续观察
observer.disconnect()
</script>
</code></pre>
<p>目前支持的浏览器似乎只有 webkit 核心和 Firefox (14+),而在 Chrome 24 和 Safari 6.0.2 里仍属于私有实现,所以必须先判断:<code>window.MutationObserver || window.WebKitMutationObserver</code>。</p>
<p><code>MutationObserver</code> 创建实例时需要一个函数作为参数作为回调函数,该函数有两个参数,一个是包含所有被观察的 <code>MutationRecord</code> 实例的数组,一个是 <code>MutationObserver</code> 实例。</p>
<p><code>observer.observe( node, anObject )</code> 接受两个参数,需观察的对象和事件类型。事件类型用对象的形式来赋值,如 <code>{ attributes: true, childList: true }</code>,这决定了什么事件会被触发,比如上述例子执行结果为:</p>
<pre><code>{
addedNodes: <span class="literal">null</span>,
attributeName: <span class="string">"class"</span>,
attributeNamespace: <span class="literal">null</span>,
nextSibling: <span class="literal">null</span>,
oldValue: <span class="literal">null</span>,
previousSibling: <span class="literal">null</span>,
removedNodes: <span class="literal">null</span>,
target: ul.list,
type: <span class="string">"attributes"</span>
}
{
addedNodes: NodeList[<span class="number">1</span>],
attributeName: <span class="literal">null</span>,
attributeNamespace: <span class="literal">null</span>,
nextSibling: <span class="literal">null</span>,
oldValue: <span class="literal">null</span>,
previousSibling: #text,
removedNodes: NodeList[<span class="number">0</span>],
target: ul,
type: <span class="string">"childList"</span>
}
</code></pre>
<p><code>MutationObserver</code> 和一般的 <code>event</code> 不一样,默认仅观察指定对象,除非指定 <code>subtree: true</code>,所以更改 <code>li</code> 的 <code>className</code> 没有触发属性变动事件:</p>
<pre><code><span class="title">ul</span>.children[<span class="number">0</span>].<span class="class">className = 'list'
</code></pre>
<p>此外还要注意的是,被观察的对象是 <code>node</code> 类型,所以类似这样的操作也会触发事件:<code>ul.appendChild( document.createTextNode( 'Hello World' ) )</code>。</p>
<p>更详细的内容请参考 <a href="https://developer.mozilla.org/en-US/docs/DOM/MutationObserver">MDN 的文档</a>。</p>
<h3>CSS 动画事件的妙用</h3>
<p><code>MutationObserver</code> 虽然实用度和性能都很优秀,但毕竟是 DOM Level 4 的东西——太新——在老一点的浏览器有没有别的代替方案呢?</p>
<p>如果只是需要 <code>DOMNodeInserted</code>,那可以利用 CSS 动画事件实现类似的效果:</p>
<pre><code><style>
@-webkit-keyframes NodeInserted {
from {}
}
@keyframes NodeInserted {
from {
clip: rect(0px, auto, auto, auto);
}
to {
clip: rect(0px, auto, auto, auto);
}
}
li {
-webkit-animation-duration: .001s;
animation-duration: .001s;
-webkit-animation-name: NodeInserted;
animation-name: NodeInserted;
}
</style>
<ul></ul>
<script>
var nodeInserted = function( event ) {
if ( 'NodeInserted' === event.animationName ) {
console.log( 'New node inserted!' )
}
}
document.addEventListener( 'webkitAnimationStart', nodeInserted, false )
document.addEventListener( 'animationstart', nodeInserted, false )
document.querySelector( 'ul' ).appendChild( document.createElement( 'li' ) )
</script>
</code></pre>
<p>这个技巧利用了 CSS 动画开始时触发的 <code>animationstart</code> 事件,因为如果一个元素只有在 DOM 里才会开始动画。</p>
<p>一些需要注意的地方:</p>
<ul>
<li>Webkit 系列(Chrome 24 & Safari 6.0.2)要加上 prefix,事件绑定也是;</li>
<li>尽量别用 <code>jQuery.on()</code> 绑定事件,因为 <code>event</code> 参数是被 jQuery 创建的,目前并不支持 <code>event.animationName</code> 等几个属性(1.9.0);</li>
<li>Chrome 24 有点奇葩,只有一个空的 <code>from {}</code> 也会触发动画事件(其他 webkit 系没测试);</li>
<li>Firefox 需要比较完整并有实际效果的 <code>keyframes</code>,比如示例代码里的 <code>clip</code>,如果想省事,可以单独用 <code>from { opacity: 1 }</code>,但不是所有属性都可以触发;</li>
<li><code>keyframes</code> 里的 CSS 属性要多注意,别不小心覆盖了需要的,比较安全的是 <code>opacity</code> 和 <code>clip</code>;</li>
<li>建议通过判断 <code>event.animationName</code> 来避免非预期情况;</li>
<li>建议用父元素做事件代理,如 <code>document.querySelector( 'ul' ).addEventListener( 'animationstart', nodeInserted, false )</code>,并酌情停止冒泡。</li>
</ul>
<p>这个技巧可以用在任何支持 CSS 动画的浏览器上,泛用性比较高,可以参考我写的 <a href="http://userscripts.org/scripts/show/157225">Twitter Filter 用户脚本</a>,做成 bookmarklet 一样可以用在别的浏览器。</p>
<p>补充:IE9 只支持 5 个 mutation 事件,不支持 CSS 动画,<code>MutationObserver</code> 目前还没支持的迹象,所以忘了 IE 系列吧。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/css/" term="CSS"/>
<category scheme="http://chrisyip.im/tags/javascript/" term="JavaScript"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/01/" term="01"/>
</entry>
<entry>
<title type="html"><![CDATA[Objective-C 的消息转发机制]]></title>
<link href="http://chrisyip.im/post/objective-c-message-forwarding/"/>
<id>http://chrisyip.im/post/objective-c-message-forwarding/</id>
<published>2013-01-10T09:23:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>通常,向一个对象发送它不能处理的消息会得到类似这样的错误提示:<code>No visible @interface for 'SomeObject' declares the selector 'someSelector:'</code> 或 <code>instance method '-someMethod' not found</code> 等等,而动态语言,如 Ruby,则有另一种处理方式:</p>
<pre><code><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span></span>
<span class="function"><span class="keyword">def</span> <span class="title">initialize</span></span>
<span class="variable">@items</span> = []
<span class="keyword">end</span>
<span class="function"><span class="keyword">def</span> <span class="title">method_missing</span><span class="params">(m, *args, &block)</span></span>
<span class="keyword">unless</span> <span class="variable">@items</span>.respond_to? m
<span class="keyword">super</span>(m, *args)
<span class="keyword">end</span>
<span class="variable">@items</span>.__send_<span class="number">_</span>(m, *args)
<span class="keyword">end</span>
<span class="keyword">end</span>
t = <span class="constant">MyClass</span>.new
t.push <span class="string">"hello"</span>
puts t.first <span class="comment"># output: "hello"</span>
t.trim <span class="comment"># error: undefined method `trim' (NoMethodError)</span>
</code></pre>
<p>这种特性赋予了开发者很大的自由性和便利性。</p>
<p>虽然 Objective-C 并不是动态语言,但却具备动态类型,有类似的特性 -- <a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html">消息转发</a>。</p>
<h3>快速转发</h3>
<p>这种方式把消息原封不动地转发给目标对象,速度是最快的也是最简单的 -- 仅需在类里实现 <code>- (id)forwardingTargetForSelector:(SEL)aSelector;</code>。</p>
<pre><code>@<span class="class"><span class="keyword">interface</span> <span class="title">NewArray</span> : <span class="title">NSObject</span>
@<span class="title">property</span> (<span class="title">strong</span>, <span class="title">nonatomic</span>) <span class="title">NSArray</span> *<span class="title">array</span>;
- (<span class="title">id</span>)<span class="title">forwardingTargetForSelector</span>:(<span class="title">SEL</span>)<span class="title">aSelector</span>;
- (<span class="title">id</span>)<span class="title">firstObject</span>;
- (<span class="title">NSUInteger</span>)<span class="title">count</span>;
@<span class="title">end</span>
@<span class="title">implementation</span> <span class="title">NewArray</span>
- (<span class="title">id</span>)<span class="title">forwardingTargetForSelector</span>:(<span class="title">SEL</span>)<span class="title">aSelector</span> {</span>
<span class="keyword">return</span> [_array respondsToSelector:aSelector] ?
_array : nil;
}
- (id)firstObject {
<span class="keyword">return</span> _array.count ? [_array objectAtIndex:<span class="number">0</span>] : nil;
}
- (NSUInteger)count { <span class="keyword">return</span> <span class="number">0</span>; }
@end
NewArray *arr = [[NewArray alloc] init];
arr.array = @[@<span class="string">"a"</span>, @<span class="string">"b"</span>];
NSLog(@<span class="string">"%@ and %@, count %li"</span>, [(NSArray *)arr lastObject],
[arr firstObject], [arr count]);
<span class="comment">// output: "b and a, count 0"</span>
</code></pre>
<h3>普通转发</h3>
<p>这种方式让你可以控制目标对象、方法和参数,但是性能没有第一种方法好。这个方法需要实现 <code>- (void)forwardInvocation:(NSInvocation *)anInvocation;</code> 和 <code>- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector;</code>。</p>
<pre><code>@<span class="class"><span class="keyword">implementation</span> <span class="id">NSArray</span> (<span class="id">MessageForwarding</span>)</span>
- (<span class="keyword">void</span>)forwardInvocation:(NSInvocation *)anInvocation {
<span class="keyword">for</span> (<span class="keyword">id</span> obj in <span class="keyword">self</span>) {
<span class="comment">// Only apply for string that lenght == 1</span>
<span class="keyword">if</span> (<span class="number">1</span> == [(<span class="built_in">NSString</span> *)obj length]) {
[anInvocation invokeWithTarget:obj];
}
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [<span class="keyword">super</span> methodSignatureForSelector:sel];
<span class="keyword">if</span> (!signature) {
<span class="keyword">for</span> (<span class="keyword">id</span> obj in <span class="keyword">self</span>) {
<span class="keyword">if</span> ((signature = [obj methodSignatureForSelector:aSelector])) {
<span class="keyword">break</span>;
}
}
}
<span class="keyword">return</span> signature;
}
@<span class="keyword">end</span>
<span class="built_in">NSArray</span> *arr = @[[<span class="built_in">NSMutableString</span> stringWithString:@<span class="string">"a"</span>],
[<span class="built_in">NSMutableString</span> stringWithString:@<span class="string">"bc"</span>]];
[(<span class="built_in">NSMutableString</span> *)arr appendString:@<span class="string">"!"</span>];
<span class="built_in">NSLog</span>(@<span class="string">"%@"</span>, arr);
<span class="comment">// output: "a! bc"</span>
</code></pre>
<p><code>-methodSignatureForSelector:</code> 的作用是告诉运行时参数有多少个、是什么类型,这是用来兼容 C 的。</p>
<h3>转发的注意事项</h3>
<p>可能你已经在上述代码里发现了一些问题,这是它们的共同问题:</p>
<ol>
<li>发送非自己处理的消息时需要转换类型,如 <code>[(NSArray *)arr lastObject]</code>;</li>
<li>如果第一个接收消息的对象实现了同样的方法,则不会触发转发,如第一例中的 <code>[arr count]</code>。</li>
</ol>
<p>第一个问题,目的是让运行时知道要找什么类型,以第一例为例,如果改成 <code>[(NSString *)arr lastObject]</code> 则会触发 Xcode 报错,CodeRunner 则只会警告。一般来说,用目标类型即可,比如 <code>NSArray</code> 就用 <code>NSArray</code>,不过我比较喜欢动态语言的方式,所以一般用 <code>id</code>。</p>
<p>第二个问题,需要用别的方式来解决,<code>NSProxy</code>。</p>
<h3>NSProxy</h3>
<p>使用 <code>NSProxy</code> 需要实现一个子类。</p>
<pre><code><span class="preprocessor">#import <Foundation/Foundation.h></span>
@<span class="class"><span class="keyword">interface</span> <span class="id">ArrayProxy</span> : <span class="id">NSProxy</span></span>
@<span class="keyword">property</span> (<span class="keyword">strong</span>, <span class="keyword">nonatomic</span>) <span class="built_in">NSArray</span> *items;
+ (<span class="keyword">id</span>)proxyWithTarget:(<span class="built_in">NSArray</span> *)anArray;
- (<span class="keyword">void</span>)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
@<span class="keyword">end</span>
@<span class="class"><span class="keyword">implementation</span> <span class="id">ArrayProxy</span></span>
+ (<span class="keyword">id</span>)proxyWithTarget:(<span class="built_in">NSArray</span> *)anArray {
ArrayProxy *proxy = [ArrayProxy alloc];
proxy<span class="variable">.items</span> = anArray;
<span class="keyword">return</span> proxy;
}
- (<span class="keyword">void</span>)forwardInvocation:(NSInvocation *)anInvocation {
<span class="keyword">for</span> (<span class="keyword">id</span> obj in _items) {
[anInvocation invokeWithTarget:obj];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
<span class="comment">// DO NOT do this:</span>
<span class="comment">// NSMethodSignature *signature = [super methodSignatureForSelector:sel];</span>
<span class="comment">// Will raise `NSInvalidArgumentException`</span>
NSMethodSignature *signature;
<span class="keyword">for</span> (<span class="keyword">id</span> obj in _items) {
<span class="keyword">if</span> ((signature = [obj methodSignatureForSelector:aSelector])) {
<span class="keyword">break</span>;
}
}
<span class="keyword">return</span> signature;
}
@<span class="keyword">end</span>
@<span class="class"><span class="keyword">implementation</span> <span class="id">NSArray</span> (<span class="id">MessageForwarding</span>)</span>
- (<span class="keyword">id</span>)<span class="keyword">do</span> { <span class="keyword">return</span> [ArrayProxy proxyWithTarget:<span class="keyword">self</span>]; }
@<span class="keyword">end</span>
@<span class="class"><span class="keyword">implementation</span> <span class="id">NSString</span> (<span class="id">MessageForwarding</span>)</span>
- (NSUInteger)count {
<span class="built_in">NSLog</span>(@<span class="string">"%li"</span>, <span class="keyword">self</span><span class="variable">.length</span>);
<span class="keyword">return</span> <span class="keyword">self</span><span class="variable">.length</span>;
}
@<span class="keyword">end</span>
<span class="built_in">NSArray</span> *arr = @[@<span class="string">"a"</span>, @<span class="string">"bc"</span>];
[[arr <span class="keyword">do</span>] count];
<span class="comment">// output: 1, 2</span>
</code></pre>
<p>因为是经过 <code>NSProxy</code> 来处理,所以就不受对象是否能响应对应消息的限制。另,不要在 <code>-methodSignatureForSelector:</code> 里往 <code>super</code> 发送消息,会抛出 <code>NSInvalidArgumentException</code>。</p>
<p>需要注意的是,<code>-do</code> 只返回最后一个结果:</p>
<pre><code>NSLog(@<span class="string">"%li"</span>, <span class="string">[[arr do] length]); // 2
</code></pre>
<h3>结尾</h3>
<p>上面的示例代码仅供参考如何实现消息转发,除此以外的部分有更好的实现方式,比如 <code>firstObject</code> 就应该用 <code>category</code> 来实现,而有些需求,则可以使用 <code>delegate</code>,比如 UI 元素。</p>
<p>我对上面三种消息转发的主要用法是扩展无法通过 <code>category</code> 实现的功能,比如为 <code>NSMutableArray</code> 添加可操作自身的方法,Objective-C 不允许操作自身,所以像 <code>while(obj = arr.unshift())</code> 之类的技巧就无法使用。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/objective-c/" term="Objective-C"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/01/" term="01"/>
</entry>
<entry>
<title type="html"><![CDATA[使用 Pow 和 Privoxy 绕开 OS X 的沙盒限制和 SOCKS5 兼容问题]]></title>
<link href="http://chrisyip.im/post/use-pow-and-privoxy-bypass-mac-sandbox-and-socks5-issue/"/>
<id>http://chrisyip.im/post/use-pow-and-privoxy-bypass-mac-sandbox-and-socks5-issue/</id>
<published>2013-01-07T16:42:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>Apple 在 OS X 上推行沙盒之后,发现原来能用 PAC 代理配置不能用于所有程序了,原因是因为<strong>启用沙盒的程序无法访问 PAC 文件</strong>。除此之外,也发现了 SOCKS5 的兼容问题(如 Safari、Tweetbot),也就意味着在日益严峻的天朝网络环境里某些提高生产力的工具,如 <a href="https://github.com/clowwindy/shadowsocks">shadowsocks</a> ,就半残废了,只能走全局的 SOCKS 代理,而想要访问大陆站点的时候,比如优酷,这明显不科学,只能想办法解决。</p>
<h3>沙盒的权限问题</h3>
<p>虽然沙盒政策不让程序访问未授权的文件,不过对于「远程」文件倒没限制,所以我们可以把 PAC 文件部署在本地的服务器上。</p>
<p>安装 Pow 或 Anvil<em>(<a href="/blog/simplify-local-sites-management-with-pow-and-anvil/">参考</a>)</em>,使用 Apache/nginx 也可以,总之可以通过类似 <code>http://localhost/proxy.pac</code> 的方式访问本地的 PAC 文件即可。</p>
<p>以 Anvil 为例,安装并启动后,将 PAC 文件所在目录拖曳到状态栏的图标即可,得到如 <code>http://pac.dev/</code> 的地址。将加上文件名后的完整地址,如 <code>http://pac.dev/proxy.pac</code>,复制到 Network Proxies 设置里 Automatic Proxy Configuration 的文本框里:</p>
<p><img src="/bimg/configure-pac-with-anvil.png" alt="Automatic Proxy Conguration with Anvil"></p>
<h3>SOCKS5 的问题</h3>
<p>虽然绕过了沙盒的限制,但是 Safari 和 Tweetbot 是不支持 SOCKS5 的,而 Chrome/Firefox 却只支持 SOCKS5,这里就有两种方法。</p>
<p>第一种,PAC 里这样写:</p>
<pre><code> <span class="attribute">'SOCKS5</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8080</span>;SOCKS <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8080</span>'
</code></pre>
<p>第二种,把 SOCSK5 转换成都支持的 HTTP 代理,这就需要使用 <a href="http://www.privoxy.org/">Privoxy</a>。Privoxy 非常强大,但是比 PAC 复杂,如非需要,建议还是用第一种方法处理。</p>
<p>注意:本文是通过 <a href="http://mxcl.github.com/homebrew/">homebrew</a> 来安装 Privoxy,配置方式和通过官方<a href="http://www.privoxy.org/user-manual/installation.html#INSTALLATION-MAC">安装指南</a>安装的有所区别。</p>
<p>打开命令行依次执行以下命令:</p>
<pre><code><span class="title">brew</span> install privoxy
<span class="title">cd</span> /usr/local/etc/privoxy/
<span class="title">echo</span><span class="string"> 'listen-address 0.0.0.0:8118\nforward-socks5 / localhost:8080 .'</span> >> config
</code></pre>
<p><code>0.0.0.0</code> 可以让别的设备用,如果不需要,可以用 <code>127.0.0.1</code>;<code>8118</code> 是默认端口;<code>localhost:8080</code> 是我的 SOCKS5 地址,根据需要自行修改。</p>
<p>需要注意的是,Privoxy 读取配置文件只会在当前目录读取,所以运行时建议用这个命令:<code>privoxy /usr/local/etc/privoxy/config</code>,没看到任何信息就代表成功了。</p>
<p>把 PAC 文件里的 SOCKS5 地址改成 HTTP 地址:</p>
<pre><code><span class="title">function</span> FindProxyForURL(url, host) {
<span class="title">proxy</span> = <span class="string">'PROXY 127.0.0.1:8118'</span>
if (shExpMatch(url, <span class="string">"*.twitter.com/*"</span>)) {
<span class="title">return</span> proxy;
}
<span class="title">return</span> <span class="string">'DIRECT'</span>
}
</code></pre>
<p>好了,配合之前解决的沙盒问题,PAC 又可以正常工作了。</p>
<p>如果需要自启动,在命令行执行如下命令:</p>
<pre><code>cd ~
touch org.privoxy.plist | echo '<span class="pi"><?xml version="1.0" encoding="UTF-8"?></span><span class="doctype"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span><span class="tag"><<span class="title">plist</span> <span class="attribute">version</span>=<span class="value">"1.0"</span>></span><span class="tag"><<span class="title">dict</span>></span><span class="tag"><<span class="title">key</span>></span>Label<span class="tag"></<span class="title">key</span>></span><span class="tag"><<span class="title">string</span>></span>org.privoxy.launchd.privoxy<span class="tag"></<span class="title">string</span>></span><span class="tag"><<span class="title">key</span>></span>ProgramArguments<span class="tag"></<span class="title">key</span>></span><span class="tag"><<span class="title">array</span>></span><span class="tag"><<span class="title">string</span>></span>/usr/local/sbin/privoxy<span class="tag"></<span class="title">string</span>></span><span class="tag"><<span class="title">string</span>></span>/usr/local/etc/privoxy/config<span class="tag"></<span class="title">string</span>></span><span class="tag"></<span class="title">array</span>></span><span class="tag"><<span class="title">key</span>></span>RunAtLoad<span class="tag"></<span class="title">key</span>></span><span class="tag"><<span class="title">true</span>/></span><span class="tag"></<span class="title">dict</span>></span><span class="tag"></<span class="title">plist</span>></span>' >> org.privoxy.plist
sudo chown root:wheel org.privoxy.plist
sudo cp org.privoxy.plist /Library/LaunchDaemons/org.privoxy.plist
launchctl load /Library/LaunchDaemons/org.privoxy.plist
launchctl start org.privoxy.launchd.privoxy
</code></pre>
<h3>Privoxy 的进阶内容</h3>
<p>其实 Privoxy 并不只是把 SOCKS5 转换成 HTTP 代理,她本身还有很强大的功能,比如 <code>action</code> 和 <code>filter</code>,在这仅仅简单介绍一下,具体用途看官方<a href="http://www.privoxy.org/user-manual/configuration.html#CONFOVERVIEW">文档</a>。</p>
<p><code>Action</code> 可以起到 PAC 文件的作用,也可以当成 AdBlock、user script 和 Stylebot 来使用。</p>
<p>默认的文件分别是 <code>/usr/local/etc/privoxy/user.action</code> 和 <code>/usr/local/etc/privoxy/user.filter</code>。</p>
<p>大概的语法如下:</p>
<pre><code>direct = +forward-override{forward .}
ssh = +forward-override{forward-socks5 <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8080</span> .}
<span class="keyword">default</span> = direct
{<span class="keyword">default</span>}
/
{direct}
<span class="variable">.youku</span><span class="variable">.com</span>
<span class="variable">.twitter</span><span class="variable">.com</span>
{ssh}
<span class="variable">.twitter</span><span class="variable">.com</span>
<span class="preprocessor"># 后面的规则优先级比前面的高</span>
{+block}
<span class="variable">.weibo</span><span class="variable">.com</span> <span class="preprocessor"># 屏蔽微博</span>
{-block}
<span class="variable">.weibo</span><span class="variable">.com</span>/chrisyipw <span class="preprocessor"># 但是不屏蔽自己</span>
{+block-as-image}
<span class="variable">.weibo</span><span class="variable">.com</span> <span class="preprocessor"># 屏蔽微博下所有合法图片,即使扩展名不是图片扩展名也会被屏蔽</span>
</code></pre>
<p>可以通过不同的 <code>alias</code> 配置不同的代理程序,如果是用 SSH 而不是 shadowsocks,可以考虑链接多个服务器进行分流,毕竟 SSH 并发性能不高。</p>
<p>还有更高级的,比如结合 <code>filter</code> 修改页面内容,比如重定向 URL 什么的:</p>
<pre><code><span class="cell">{+filter{filter_name}</span>}
.<span class="transposed_variable">weibo.</span>com
<span class="cell">{+fast-redirects{check-decoded-url}</span>}
<span class="transposed_variable">news.</span><span class="transposed_variable">google.</span>com/news/<span class="transposed_variable">url.</span>*&url=<span class="transposed_variable">http.</span>*&
</code></pre>
<p>具体看 <code>user.action</code> 里的注释吧,而 <code>filter</code> 是需要结合 <code>user.filter</code> 来用的,如果想要使用多个 <code>action</code> 文件,在 <code>config</code> 加入 <code>actionsfile name.action</code> 就行。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/anvil/" term="Anvil"/>
<category scheme="http://chrisyip.im/tags/os-x/" term="OS X"/>
<category scheme="http://chrisyip.im/tags/pow/" term="Pow"/>
<category scheme="http://chrisyip.im/tags/privoxy/" term="Privoxy"/>
<category scheme="http://chrisyip.im/tags/proxy/" term="Proxy"/>
<category scheme="http://chrisyip.im/categories/2013/" term="2013"/>
<category scheme="http://chrisyip.im/categories/2013/01/" term="01"/>
</entry>
<entry>
<title type="html"><![CDATA[使用 Pow 和 Anvil 管理本地网站]]></title>
<link href="http://chrisyip.im/post/simplify-local-sites-management-with-pow-and-anvil/"/>
<id>http://chrisyip.im/post/simplify-local-sites-management-with-pow-and-anvil/</id>
<published>2012-12-23T10:34:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>做网页开发的(特别是 Rails 开发者)应该听过甚至在使用 <a href="http://pow.cx/">Pow</a> 了吧?Pow 让开发者从 <code>localhost:port</code> 地狱中解放出来,她让每个项目都可以通过以项目名(实际是文件夹名)和 <code>.dev</code> 组成的域名来代替 <code>localhost:port</code> 的方式,如 <code>http://project_name.dev/</code>,不管在易用性还是视觉上都是非常赞的。</p>
<p>使用方法很简单,通过 <code>curl get.pow.cx | sh</code> 安装完后,把项目用 symbol link 连到 <code>~/.pow</code> 下就可以:</p>
<pre><code><span class="title">cd</span> ~/.pow
<span class="title">ln</span> -s /path/to/myapp
</code></pre>
<p>但是每次都要跑命令行还是有点麻烦,而 <a href="http://anvilformac.com/">Anvil</a> 就是为了解决这个问题而诞生的。</p>
<p>Anvil 是一个只在菜单栏占一个位置的程序,如果要添加项目,只需要把文件夹拖放到菜单栏上的图标即可,Anvil 就为你做剩下的工作;删除项目时只需点击图标,在菜单里点击对应的删除按钮即可:</p>
<p><img src="/bimg/anvil.png" alt="Anvil"></p>
<p>Anvil 实在简单得没什么可以说的,不过有一点需要注意,因为<strong>Anvil 本质上相当于是 Pow 的图形界面</strong>,所以是需要安装 Pow 的,运行时 Anvil 也会提示需要安装 Pow;需要注意的是,Anvil(0.61)<strong>不能</strong>识别通过 <a href="http://mxcl.github.com/homebrew/">Homebrew</a> 安装的 Pow,需要通过 <code>curl get.pow.cx | sh</code> 或点击 Anvil 的安装按钮来安装 Pow。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/anvil/" term="Anvil"/>
<category scheme="http://chrisyip.im/tags/app/" term="App"/>
<category scheme="http://chrisyip.im/tags/os-x/" term="OS X"/>
<category scheme="http://chrisyip.im/tags/pow/" term="Pow"/>
<category scheme="http://chrisyip.im/tags/web/" term="Web"/>
<category scheme="http://chrisyip.im/categories/2012/" term="2012"/>
<category scheme="http://chrisyip.im/categories/2012/12/" term="12"/>
</entry>
<entry>
<title type="html"><![CDATA[发布 Data URI Converter 1.0]]></title>
<link href="http://chrisyip.im/post/announcing-data-uri-converter/"/>
<id>http://chrisyip.im/post/announcing-data-uri-converter/</id>
<published>2012-11-25T09:37:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p><a href="/data-uri-converter">Data URI Converter</a> 是我写的第一个 正式对外发布的 Mac App,写这货的动机是因为有段时间常用的拖曳式生成 DataURI 网站不是挂了就是被墙,剩下的都是不理想的,比如是传统 <code><input type=file></code>,所以萌生了写一个本地软件的想法。</p>
<p>只是完成了原先计划的基础功能,如<strong>拖曳</strong>、<strong>自动识别 mime 类型</strong>、<strong>历史记录</strong>等,还有一些暂时没时间,先搁置,历史记录也做得相对简单了,预览图:</p>
<p><img src="/data-uri-converter/screenshot2.png" alt="Data URI Converter Preview"></p>
<p>欢迎任何意见或建议 :)</p>
]]></content>
<category scheme="http://chrisyip.im/tags/app/" term="App"/>
<category scheme="http://chrisyip.im/tags/base64/" term="Base64"/>
<category scheme="http://chrisyip.im/tags/data-uri/" term="Data URI"/>
<category scheme="http://chrisyip.im/tags/os-x/" term="OS X"/>
<category scheme="http://chrisyip.im/categories/2012/" term="2012"/>
<category scheme="http://chrisyip.im/categories/2012/11/" term="11"/>
</entry>
<entry>
<title type="html"><![CDATA[Alfred 扩展:Open in Sublime Text 2]]></title>
<link href="http://chrisyip.im/post/alfred-extension-for-sublime-text-2/"/>
<id>http://chrisyip.im/post/alfred-extension-for-sublime-text-2/</id>
<published>2012-11-17T11:02:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>做了两枚用 Sublime Text 2 打开目录或文件的 Alfred 扩展:</p>
<ul>
<li>打开 Finder 当前目录:<a href="http://d.pr/f/KLMR"><a href="http://d.pr/f/KLMR">http://d.pr/f/KLMR</a></a>,修改自 <a href="https://twitter.com/pwc">@pwc</a> 的 Terminal+Here;</li>
<li>打开 Alfred 当前选定的文件 / 目录:<a href="http://d.pr/f/Ypzd"><a href="http://d.pr/f/Ypzd">http://d.pr/f/Ypzd</a></a>。</li>
</ul>
<p>图标来自于 <a href="https://github.com/dmatarazzo">dmatarazzo</a> 的 <a href="https://github.com/dmatarazzo/Sublime-Text-2-Icon">Sublime-Text-2-Icon</a>。</p>
]]></content>
<category scheme="http://chrisyip.im/tags/alfred/" term="Alfred"/>
<category scheme="http://chrisyip.im/tags/extension/" term="Extension"/>
<category scheme="http://chrisyip.im/tags/sublime-text/" term="Sublime Text"/>
<category scheme="http://chrisyip.im/categories/2012/" term="2012"/>
<category scheme="http://chrisyip.im/categories/2012/11/" term="11"/>
</entry>
<entry>
<title type="html"><![CDATA[一些很有用但不常见的 JavaScript APIs]]></title>
<link href="http://chrisyip.im/post/some-useful-javascript-apis/"/>
<id>http://chrisyip.im/post/some-useful-javascript-apis/</id>
<published>2012-11-15T12:12:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p><a href="http://www.nczonline.net/">Nicholas Zakas</a> 发布了一个<a href="http://www.slideshare.net/nzakas/javascript-apis-youve-never-heard-of-and-some-you-have">幻灯片</a>,讲的是一些知名度低的 JavaScript API,我把它们都整理出来,并适当加了一些个人见解。</p>
<p>这篇文章里介绍的 API 大部分都是被普遍支持的(大部分 IE6~7 也能用),但是像是被我当成速查手册的 <a href="javascriptkit.com">javascriptkit.com</a> 也没完全包含进去,这就有点怪异了,我是认为像这类入门网站,是该好好完善兼容性高的基础 API,这样新人入行时才不至于漏掉一些实用知识。</p>
<p>下面的例子如无特殊说明,都是基于这个 HTML 结构:</p>
<pre><code><nav id="nav">
<!-- comment -->
<a>Home</a>
<a>Blog</a>
</nav>
<script>
var nav = document.getElementById('nav')
</script>
</code></pre>
<h3>元素遍历</h3>
<p><code>Element.children[]</code>:这个 API 比起 <code>Element.childNodes[]</code> 的优点就是只包含 <code>1 === nodeType</code> 亦即 <code>ELEMENT_NODE</code> 的子节点,对于元素遍历来说方便很多,和 <code>jQuery.children()</code> 是一样效果的。注:IE8- 会包含 <code>8 === nodeType</code> 即 <code>COMMENT_NODE</code>,需要手动判断。</p>
<pre><code>nav.childNodes[<span class="number">0</span>] <span class="comment">// '#text'</span>
nav.children[<span class="number">0</span>] <span class="comment">// <a></span>
</code></pre>
<p><code>Element.firstElementChild</code>、<code>Element.lastElementChild</code>、<code>Element.nextElementSibling</code> 和 <code>Element.previousElementSibling</code>:和 <code>children[]</code> 一样,都会跳过非 <code>ELEMENT_NODE</code> 的子节点。注:IE8- 不支持。</p>
<pre><code>nav.firstChild <span class="comment">// '#text'</span>
nav.lastChild <span class="comment">// '#text'</span>
nav.firstElementChild <span class="comment">// <a>Home</a></span>
nav.lastElementChild <span class="comment">// <a>Blog</a></span>
nav.firstElementChild.nextSibling <span class="comment">// '#text'</span>
nav.lastElementChild.previousSibling <span class="comment">// '#text'</span>
nav.firstElementChild.nextElementSibling <span class="comment">// <a>Blog></span>
nav.lastElementChild.previousElementSibling <span class="comment">// <a>Home</a></span>
</code></pre>
<p><code>Element.contains()</code>:判断一个元素是否包含另一个元素。相比之下,<code>jQuery.contains()</code> 实现得很别扭,只能对比原生 DOM 元素,自己的 jQuery 对象无法对比。</p>
<pre><code>document.body.contains(nav) <span class="comment">// true</span>
jQuery.contains(document.body, nav) <span class="comment">// true</span>
jQuery.contains($(<span class="string">'body'</span>), nav) <span class="comment">// false</span>
</code></pre>
<h3>元素修改</h3>
<p><code>Element.insertAdjacentHTML(location, html_string)</code>:把 <code>html_string</code> 插入到指定的地方,<code>html_string</code> 必须是合法的 HTML 片段,<code>location</code> 有四个值,分别是 <code>beforebegin</code>、<code>beforeend</code>、<code>afterbegin</code> 和 <code>afterend</code>,假设往 <code>nav</code> 插入 HTML,对应的位置如下:</p>
<pre><code><!-- beforebegin --><nav><!-- afterbegin -->
<a>Home</a>
<a>Blog</a>
<!-- beforeend --></nav><!-- afterend -->
</code></pre>
<p>需要注意的是 <code>beforebegin</code> 和 <code>afterend</code> 是只有元素<strong>拥有父元素且在 DOM 树中</strong>时才有效,用在特定元素上也有可能出现非预期结果:</p>
<pre><code><span class="comment">// 用在 body 上会产生两个 <head> 和 <body>(仅在 Chrome 上测试过)</span>
document.body.insertAdjacentHTML(<span class="string">'beforebegin'</span>, <span class="string">'<p>hello'</span>)
document.body.insertAdjacentHTML(<span class="string">'afterend'</span>, <span class="string">'<p>hello'</span>)
<span class="comment">// <p> 会消失</span>
<span class="keyword">var</span> d = document.createElement(<span class="string">'div'</span>)
d.insertAdjacentHTML(<span class="string">'afterend'</span>, <span class="string">'<p>hello'</span>)
nav.appendChild(d)
<span class="comment">// <p> 会出现在 <nav> 里,和 <div> 同级</span>
<span class="keyword">var</span> d2 = document.createElement(<span class="string">'div'</span>)
nav.appendChild(d2)
d2.insertAdjacentHTML(<span class="string">'afterend'</span>, <span class="string">'<p>hello'</span>)
</code></pre>
<p>在<a href="http://jsperf.com/innerhtml-vs-insertadjacenthtml-vs-dom/2">性能</a>上 <code>insertAdjacentHTML</code> 和 <code>innerHTML</code> 相近,选择用谁要看需求,如果插入的位置依赖于某个元素,<code>insertAdjacentHTML</code> 通常会便利一些。</p>
<p><code>Element.outerHTML</code>:<code>innerHTML</code> 的变种,也就不必过多说明了。应用起来和上两者一样,某些情况下会很便利。</p>
<p><code>document.implementation.createHTMLDocument()</code>: 创建一个新的 <code>DOCUMENT_NODE</code>,也就是 <code>document</code>。虽然似乎没什么用,不过可以做的东西有很多,比如:</p>
<pre><code><span class="keyword">var</span> new_doc = document.implementation.createHTMLDocument()
new_doc.body.innerHTML = html_string
<span class="comment">// 删除特定标签</span>
[].forEach.call(new_doc.body.querySelectorAll(<span class="string">'script,link,object'</span>), <span class="keyword">function</span>(el){
el.parentNode.removeChild(el)
})
<span class="comment">// 将处理过、干净安全的 HTML 代码添加到真正的 DOM</span>
document.body.innerHTML = new_doc.body.innerHTML
</code></pre>
<p>需要注意的是,创建出来文档里的元素用 <code>getComputedStyle()</code> 不一定能得到样式值的,所以要用于计算的话,请谨慎(仅在 Chrome 测试过):</p>
<pre><code><span class="keyword">var</span> new_doc = document.implementation.createHTMLDocument()
, p = new_doc.createElement(<span class="string">'p'</span>)
p.innerHTML = <span class="string">'hello world'</span>
p.style.cssText = <span class="string">'color: red;'</span>
new_doc.body.appendChild(p)
console.log(p.style.color) <span class="comment">// 'red'</span>
console.log(getComputedStyle(new_doc.querySelector(<span class="string">'p'</span>))) <span class="comment">// all empty</span>
</code></pre>
<h3>文本选择</h3>
<p><code>Element.select()</code>:选择文本框内的文本。</p>
<p><code>Element.setSelectionRange(position, length)</code>:选择指定范围内的文本,<code>position</code> 从 <code>0</code> 开始算,小于 <code>0</code> 的也算 <code>0</code>;<code>length</code> 如果小于或等于 <code>position</code> 则不会选择任何字符。</p>
<p><code>Element.selectionStart</code>、<code>Element.selectionEnd</code>:标识文本选择的起始位置。</p>
<p><code>document.activeElement</code>:选择文档内获得焦点的元素。</p>
<h3>XMLHttpRequest</h3>
<p>XMLHttpRequest 很多人都会选择使用已经封装好的库吧,毕竟有兼容的问题,不过我喜欢从简,一般会挑个轻量的,之前也写了一个 jQuery 风格的 ajax <a href="https://gist.github.com/4054658">函数</a>。如果你也想自己写,可以试试以下几个能加强 XHR 的玩意。</p>
<p><code>new FormData()</code>:实际就是模拟一个 <code><form></code> 提交时封装的数据包,不过是用类似 <code>key: value</code> 方式来处理,同时也能直接接受 <code><input></code> 的值,相当便利。</p>
<p><code>xhr.upload.onprogress</code>:通常请求时都是用动态 GIF 来表示进行中,不过我曾经遇过一个客户,想要显示进度,又不希望用 Flash,当时因为难度问题,说服了他放弃,不过现在倒是有了这东西可以用。注:IE9- 不支持。</p>
<p><code>xhr.timeout</code>、<code>xhr.ontimeout</code>:控制超时。注:IE9- 不支持。</p>
<p><code>xhr.responseType</code>:目前支持四种,<code>text</code>、<code>document</code>、<code>blob</code> 和 <code>arraybuffer</code>,配合 <code>xhr.response</code> 使用。原来是只有 <code>responseText</code> 和 <code>responseXML</code> 的,不过由于是字符串,下载后的处理比较麻烦,通过这些扩展类型,会方便很多,比如 <code>document</code>、<code>blob</code> 就能直接处理了:</p>
<pre><code><span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest()
, data = <span class="keyword">new</span> FormData() <span class="comment">// or FormData(document.form[index])</span>
data.append(<span class="string">'key'</span>, <span class="string">'value'</span>)
data.append(<span class="string">'key2'</span>, fileInput.file[<span class="number">0</span>]) <span class="comment">// 获取 <input> 元素的值</span>
xhr.open(<span class="string">'get'</span>, url, <span class="literal">true</span>)
xhr.timeout = <span class="number">5000</span>
xhr.ontimeout = <span class="keyword">function</span>(event){
console.log(arguments)
}
xhr.responseType = <span class="string">'document'</span>
xhr.upload.onprogress = <span class="keyword">function</span>(event){
console.log(arguments)
}
xhr.onload = <span class="keyword">function</span>(event){
<span class="keyword">var</span> doc = event.currentTarget.response
<span class="comment">// do something like</span>
doc.querySelector(<span class="string">'body'</span>)
}
xhr.send()
</code></pre>
<h3>CSS 相关的</h3>
<p><code>Element.matchesSelector()</code>:<code>jQuery.is()</code> 的原生实现。注:IE8- 不支持,其他的都要前缀,如 <code>webkitMatchesSelector</code>。</p>
<p><code>Element.getBoundingClientRect()</code>:获取指定元素的矩形区域信息,简单地说,就是这个元素在文档里的座标、长高分别是多少,比起 <code>getComputedStyle()</code> 更方便,因为值是纯数字。注:IE7- 会给每个座标加 2,就像是被一个 <code>padding: 2px</code> 的容器包裹。</p>
<p><code>Element.document.elementFromPoint(x, y)</code>:获取指定座标的元素,如有多个,取 <code>z-index</code> 最大的。用在游戏或互动界面上应该不错,比如说球是不是进入了球门的座标里,或者结合 <code>window.innerWidth</code> 和 <code>window.innerHeight</code> 来判断一个元素是否进入了可视范围,不过我更喜欢用 <code>getBoundingClientRect()</code>。</p>
<p>下面是「加载更多」的不完整实现:</p>
<pre><code><span class="keyword">var</span> footer = document.querySelector(<span class="string">'footer'</span>)
window.addEventListener(<span class="string">'scroll'</span>, <span class="keyword">function</span>(){
<span class="keyword">var</span> rect = footer.getBoundingClientRect()
<span class="keyword">if</span> (rect.top <= window.innerHeight) {
<span class="comment">// do something...</span>
}
}, <span class="literal">false</span>)
</code></pre>
<p><code>window.matchMedia()</code>:判断 <code>window</code> 符不符合 CSS Media Query 的条件,比如 <code>window.matchMedia("(max-width: 320px)")</code>。主要用途是为移动设备启用不同的 JS 效果</p>
<h3>完</h3>
<p>JavaScript 原生的 API 是越来越强大、好用,很多时候都可以不需要库的加持,不过如果仍然要苦逼地支持 IE7-(其实我想说 IE8-……)的话,库还是最好的选择,毕竟解决了很多兼容性的问题。</p>
<p>BTW,最近看《松本行弘的程序世界》,Matz 对动态类型语言的一个观点觉得很实用:「无论如何都想检查(参数类型)的时候,也不要检查对象是否属于某个类,而是要检查对象是否有某个方法」,也就是这样:</p>
<pre><code><span class="keyword">var</span> respond_to = <span class="function"><span class="keyword">function</span> <span class="params">(o, f)</span> {</span>
<span class="keyword">return</span> !!(o != <span class="literal">null</span> && o[f]);
}
<span class="comment">// 假设需转换标题为大写字母,但是传入参数不一定是 string</span>
<span class="function"><span class="keyword">function</span> <span class="title">title</span> <span class="params">(o)</span> {</span>
<span class="keyword">if</span> (respond_to(o, <span class="string">'toUpperCase'</span>)) {
<span class="keyword">return</span> o.toUpperCase()
}
<span class="comment">// 传统做法</span>
<span class="keyword">if</span> (<span class="keyword">typeof</span> o === <span class="string">'string'</span>) {
<span class="keyword">return</span> o.toUpperCase()
}
}
</code></pre>
<p>这种做法原本是针对支持继承和多态的语言,比如父类和子类就有可能拥有同名的方法,如果单纯判断是不是某个类,就不能应对所有情况,而直接判断是否有这个方法的话,就可以适应各种情况,在 JavaScript 里也可以实现类似的效果:</p>
<pre><code><span class="keyword">var</span> text_post = { title: <span class="string">"Hello World"</span>, toUpperCase: <span class="keyword">function</span>(){
<span class="keyword">return</span> <span class="keyword">this</span>.title.toUpperCase()
}
}
, music_post = { name: <span class="string">"Hey Jade"</span>, toUpperCase: <span class="keyword">function</span>(){
<span class="keyword">return</span> <span class="keyword">this</span>.name.toUpperCase()
}
}
console.log(title(text_post)) <span class="comment">// 'HELLO WORLD'</span>
console.log(title(music_post)) <span class="comment">// 'HEY JADE'</span>
</code></pre>
<hr>
<p><em>参考资料:</em></p>
<ul>
<li><em><a href="http://www.slideshare.net/nzakas/javascript-apis-youve-never-heard-of-and-some-you-have">JavaScript APIs you’ve never heard of (and some you have)</a></em></li>
</ul>
]]></content>
<category scheme="http://chrisyip.im/tags/css/" term="CSS"/>
<category scheme="http://chrisyip.im/tags/javascript/" term="JavaScript"/>
<category scheme="http://chrisyip.im/tags/html/" term="HTML"/>
<category scheme="http://chrisyip.im/categories/2012/" term="2012"/>
<category scheme="http://chrisyip.im/categories/2012/11/" term="11"/>
</entry>
<entry>
<title type="html"><![CDATA[Alfred 扩展:移除 OS X 右键菜单的重复项]]></title>
<link href="http://chrisyip.im/post/remove-duplicate-items-in-contextual-menu-with-alfred/"/>
<id>http://chrisyip.im/post/remove-duplicate-items-in-contextual-menu-with-alfred/</id>
<published>2012-11-07T10:14:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p>升级到 Mountain Lion 之后,最恼人的是这个 bug:</p>
<p><img src="http://d.pr/i/P0FH+" alt="Duplicate items in contextual menu"></p>
<p>只要安装、升级程序都有可能造成重复项,实在烦人,但是并不是没有办法,可以用以下命名去重建右键菜单:</p>
<pre><code>/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -domain local -domain system -domain user
</code></pre>
<p>复制到 Terminal 回车然后重启 Finder 就可以了。</p>
<p>如果你是 Alfred 的 powerpack 用户,可以用我做的扩展,注意里面有重启 Finder 的命令,<code>killall Finder</code>,<strong>Finder 的工作还没做好之前,不要执行这个扩展,如有任何问题,本人不负责</strong>,你也可以在安装后删除那行命令:</p>
<p><a href="http://d.pr/f/VsSF"><a href="http://d.pr/f/VsSF">http://d.pr/f/VsSF</a></a></p>
<p><em>Image via <a href="http://forums.macrumors.com/showthread.php?t=1409978">MacRumors</a></em></p>
<p><em>这是一篇老文,从旧 blog 里搬过来的,因为有点用。</em></p>
]]></content>
<category scheme="http://chrisyip.im/tags/alfred/" term="Alfred"/>
<category scheme="http://chrisyip.im/tags/extension/" term="Extension"/>
<category scheme="http://chrisyip.im/tags/os-x/" term="OS X"/>
<category scheme="http://chrisyip.im/categories/2012/" term="2012"/>
<category scheme="http://chrisyip.im/categories/2012/11/" term="11"/>
</entry>
<entry>
<title type="html"><![CDATA[Zurui Design]]></title>
<link href="http://chrisyip.im/post/sly-design-for-web-programmer/"/>
<id>http://chrisyip.im/post/sly-design-for-web-programmer/</id>
<published>2012-11-05T07:27:00.000Z</published>
<updated>2013-03-12T10:49:48.000Z</updated>
<content type="html"><![CDATA[<p><a href="https://twitter.com/ken_c_lo">TAE</a> 在日本「<a href="http://connpass.com/event/1185/">第一回 プログラマ向けデザイン勉強会</a>」(面向程序员的设计学习班)上做了「Zurui Design」的<a href="https://speakerdeck.com/ken_c_lo/zurui-design-technique-english-version">演讲</a>,TAE 称之为「Zurui Design」。「Zurui」在日文的意思是「狡猾的」,大概是因为大部分情况下都可以用,很少出问题的缘故吧。</p>
<p>演示内的例子使用了高阶的 CSS 属性,大部分在 IE8 或以下会有支持问题,如 <code>rgba()</code>,这里就不多说明怎么处理,请善用 Google。</p>
<p>以下整理一些实用的:</p>