-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdata.json
860 lines (860 loc) · 157 KB
/
data.json
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
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 53,
"max_score": 1.0,
"hits": [
{
"_index": "blog",
"_type": "articles",
"_id": "120",
"_score": 1.0,
"_source": {
"title": "[Angular]数据和表现的分离,那种感觉太美妙",
"keywords": [
"angular"
],
"content": "<p>之前用<code>Angular</code>的循环输出<code>ng-repeat</code>的时候,都是顺序输出集合内容的。\n今天有个需求要将数据<code>倒序输出</code>,一开始的想法是将集合内的数据反序一下,\n输出来就是倒序了。</p>\n\n<h1>但是</h1>\n\n<p>一般“但是”后面都是大新闻。\n我仔细想过,这样改变数据层,以适应表现层的变化,违背了这个框架本事的精髓,带来了非常大的耦合。</p>\n\n<p>因为这样去操作数据,这个数据已经不是本身的<code>抽象定义</code>,而是变成了针对<code>视图</code>定制的模型。</p>\n\n<p>于是我打算,在不改变数据层本心的基础上,实现倒序输出。</p>\n\n<p>还好,Angular提供了这样的方法,在循环输出的时候加上 orderBy选项即可,如下:\n<code> ng-repeat="message in messages.live | orderBy:'-time'" </code></p>\n\n<p>总结下,像比如MVC这些体系下,一般表现层变化,万不得已(比如增加东西)不要改变数据模型。</p>\n\n",
"createdAt": 1411704540,
"modified": 1415586330
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "108",
"_score": 1.0,
"_source": {
"title": "socket.io两次重复emit的问题",
"keywords": [
"node",
"socket.io",
"socketio",
"websocket"
],
"content": "<p>背景:</p>\n\n<pre><code>express+angular+socket.io实现的即时通信,在客户端emit一个事件信号的时候,\n会重复发两次!任何信号都是!</code></pre>\n\n<p>纠结这个问题很久,查了好多资料,再一次印证google强大的事实,在浏览一篇外文时找到了答案。\n<code> http://stackoverflow.com/questions/18815843/socket-io-event-being-triggered-twice </code>\n楼主自问自答也是蛮拼的,反正是解决了我的问题。</p>\n\n<blockquote><p>For future reference this was occurring because I was setting my angular controller in both my HTML and under my $routeProvider. This was then triggering everything twice. </p></blockquote>\n\n<p><strong>原因就是:Angular惹的祸,在html页面里定义的控制器,和在路由表里定义的控制器,都会生效,这样就造成了定义了两个同样的控制器,那么触发两次emit就不难理解了。以下是两个定义路由的地方:</strong>\n<img alt=\"QQ截图20140924103714.png\" src=\"http://log.fyscu.com/usr/uploads/2014/09/1373443136.png\"/>\n<img alt=\"QQ截图20140924103743.png\" src=\"http://log.fyscu.com/usr/uploads/2014/09/1977159788.png\"/>\n<strong> 解决方案:我取消了路由表里定义的控制器,因为还是比较习惯在html里写控制器,scope范围比较好掌握。 </strong></p>",
"createdAt": 1411526340,
"modified": 1415952079
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "206",
"_score": 1.0,
"_source": {
"title": "yum安装memcache到CentOS",
"keywords": [
"linux",
"memcache",
"memcached",
"centos"
],
"content": "<h3>第一步</h3>\n\n<pre><code>yum install libevent</code></pre>\n\n<h3>第二步</h3>\n\n<pre><code>wget http://dag.wieers.com/rpm/packages/rpmforge-release/rpmforge-release-0.3.6-1.el5.rf.i386.rpm\nrpm -ivh rpmforge-release-0.3.6-1.el5.rf.i386.rpm</code></pre>\n\n<h3>第三步</h3>\n\n<pre><code>yum -y install –enablerepo=rpmforge memcached php-pecl-memcache\npecl install memcache</code></pre>\n\n<h3>启动</h3>\n\n<pre><code>servive memcached start</code></pre>",
"createdAt": 1416535920,
"modified": 1429899687
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "145",
"_score": 1.0,
"_source": {
"title": "用setTimeout来做循环,实现串行效果",
"keywords": [
"js",
"javascript",
"timer"
],
"content": "<h2>背景</h2>\n\n<p>javascript 里,我们一般用setInterval方法来实现一个定时循环业务,函数原型如下:</p>\n\n<pre><code>setInterval(function(){\n //这里放你要执行的业务,后面的参数是时间,单位毫秒,1秒=1000毫秒,比如\n console.log('哈');\n},5000);</code></pre>\n\n<p>上面的代码,单独执行的时候,效果诚然是如我们所想的:</p>\n\n<pre><code>哈 哈 哈 哈 (循环 )</code></pre>\n\n<h1>但是,问题来了。</h1>\n\n<p><code>javascript 是单进程的,即时是在浏览器还是在服务端node,这时候问题来了。</code>\n而 <code>异步操作次序的不确定性</code> ,一个程序里不可能仅仅这几行代码,有可能出现一个情况,\n就是前一个callback(其他行为,比如写入一个大文件)执行的时间很长,回调队列处于阻塞状态,\n而你的<code>哈</code>正在以每5秒一个的频率进入队列。然后你可能看到的是这样的:</p>\n\n<pre><code>哈 哈 (某长时间操作) 哈哈哈哈哈哈 哈 哈 哈 ......</code></pre>\n\n<p>中间某一段,因为回调堆积,引起的瞬间打印多个<code>哈</code>,这并不是我们想看到的。</p>\n\n<p><code>===================================================================</code></p>\n\n<h1>怎么办呢?</h1>\n\n<p>我们可以用 <code>setTimeout</code> 来实现,它的参数列表和上面的一样,区别是这个逻辑只执行一次。\n惯例先上代码:</p>\n\n<pre><code>//先把逻辑定义成一个函数\nvar do_sth = function(){\n console.log('哈');\n //然后用神奇的setTimeout,还是5秒\n setTimeout(function(){\n //注意了,调用自己\n do_sth();\n },5000);\n};</code></pre>\n\n<p>以上代码就是,输出了第一个<code>哈</code>以后,再申请下一个延时操作。\n这样,就能够让每个<code>哈</code>一个跟一个的输出,而不会出现回调堆积的情况。</p>\n\n<h2>新技能GET!!</h2>\n\n<p>后话:\n用这样的方法,可能会出现两次间隔超出5秒的情况,其实上面两种都会有这种情况。\n用setTimeout的意义在于解决堆积,避免一次性同时触发(尤其是一些IO操作)。</p>",
"createdAt": 1413635460,
"modified": 1415951975
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "259",
"_score": 1.0,
"_source": {
"title": "[JS]对象复制的奇技淫巧一则",
"keywords": [
"javascript"
],
"content": "<p>Javascript的对象操作,直接用 “=” 结果是对象的一个引用,任意一个引用的改变都会让原对象产生变化。</p>\n\n<p>而很多时候我们需要对一个对象进行“复制”,最直观的方法是遍历原对象,这也是最实际的方法。</p>\n\n<p>而在这我们给出一个“奇技淫巧”,仅适用于对象成员都是基本数据类型。\n(适用于诸如 <code>对象模板</code> 等应用场景)</p>\n\n<pre><code>var a = {\n 'a':1\n};\n\nvar b = JSON.parse(JSON.stringify(a));\nb.a=2;\n\nconsole.log(a); //Object { a=1}\nconsole.log(b); //Object { a=2}</code></pre>\n\n<p>可以发现,经过序列化/反序列化操作,原对象被“简单”的复制了一遍(主要是因为序列化以后是string简单类型)。\n这个方法代码简单,具体<code>时间效率</code>还需要实际测试一下。</p>",
"createdAt": 1433748882,
"modified": 1433748882
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "260",
"_score": 1.0,
"_source": {
"title": "V8引擎几个常见无法优化的点",
"keywords": [
"javascript",
"node",
"v8"
],
"content": "<h2>1. try-catch(-final)</h2>\n\n<p> 但凡在函数中有try-catch语句的,会导致整个函数无法优化。\n 所以,应该把try-catch语句封装到一个独立的函数里。</p>\n\n<pre><code>//不能优化\nfunction(){\n /**\n **一些业务\n **/\n try{\n //do sth\n }catch(ex){\n //handle\n }\n}\n\n//能被优化\nvar foo = function(){\n try{\n //do sth\n }catch(ex){\n //handle\n }\n}\nfunction(){\n /**\n **一些业务\n **/\n\n foo();\n\n}</code></pre>\n\n<h2>2. with语句</h2>\n\n<p>with语句可以少写很多对象名,但是不值得,因为它也会导致整个函数无法优化</p>\n\n<pre><code>function(){\n with(object1){\n name = 'tom';\n }\n /**\n * 应该写成\n * object1.name = 'tom';\n */\n}</code></pre>\n\n<h2>3. For-In</h2>\n\n<p>for-in 并不是不能优化,只是有比它效率更高的便利对象方式</p>\n\n<pre><code>var keys = Object.keys(object1);\nfor (var i = 0; i < keys.length; i++) {\n //do sth with object1[i]\n //这种方式会优化得很好\n}</code></pre>\n\n<h2>4. Yield</h2>\n\n<p>这个我不太了解就不说了,作为这么重要的东西,V8后续版本应该会给它做优化的吧</p>",
"createdAt": 1436866500,
"modified": 1436866559
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "256",
"_score": 1.0,
"_source": {
"title": " 实测 对数字字符串转成数字的效率比较",
"keywords": [],
"content": "<p>代码:</p>\n\n<pre><code>var a = '1' ;\nfor (var i = 0; i < 100000000; i++) {\n\t parseInt(a); //方案一\n // a * 1; //方案二\n}\nconsole.log(process.uptime());</code></pre>\n\n<p>结果,单位秒:\n方案一: 1.559 , 1.492 , 1.523\n方案二:3.841 , 3.899 , 3.877</p>",
"createdAt": 1431750008,
"modified": 1431750008
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "264",
"_score": 1.0,
"_source": {
"title": "编写一个原生的Promise封装函数",
"keywords": [
"javascript",
"promise"
],
"content": "<p> ## Show Code First</p>\n\n<pre><code>var Promised = function (fn) {\nreturn function () {\n var args = [];\n for(var k in arguments){\n args.push(arguments[k]);\n }\n //console.log(args);\n return new Promise(function (resolve, reject) {\n var done = function (e,r) {\n if(!e){\n resolve(r);\n }else{\n reject(e)\n }\n };\n args.push(done);\n fn.apply(this,args);\n });\n}</code></pre>\n\n<h2>How To Use</h2>\n\n<pre><code>var getAjax = Promised(req.get);\ngetAjax('http://121.41.85.236:33002/beating').then(function (ret) {\n console.log(ret.body);\n}).catch(function (err) {\n console.log(err);\n});</code></pre>",
"createdAt": 1442999976,
"modified": 1442999976
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "270",
"_score": 1.0,
"_source": {
"title": "读《减少switch case语句的使用》有感",
"keywords": [],
"content": "<h3>原文地址</h3>\n\n<pre><code>http://log.fyscu.com/index.php/archives/269/</code></pre>\n\n<h4>现在我是作为一个长者,给你们一些人生经验</h4>\n\n<hr/>\n\n<h4>原文</h4>\n\n<pre><code>因为switch...case结构要求,在每一个case的最后一行必须是break语句</code></pre>\n\n<h4>评:</h4>\n\n<pre><code>并不是“必须break”,反而在实际编码中,经常少写一些break,以实现分类、聚合的功能\n比如:\nswitch (day){\n case '星期一':\n case '星期二':\n case '星期三':\n case '星期四':\n case '星期五':\n return '工作日';\n break;\n case '星期六':\n case '星期日':\n return '假日';\n break;\n}</code></pre>\n\n<h4>结论</h4>\n\n<pre><code>啪!</code></pre>\n\n<hr/>\n\n<h4>原文</h4>\n\n<pre><code>建议改写成对象结构</code></pre>\n\n<h4>评</h4>\n\n<pre><code>太恶心了!\n用原本case的字符串来做对象成员,如果是 'baidu','腾讯','360' 呢?\nswitch本来的作用就是匹配变量不同的字符串,分流到不同的逻辑,可以说是这个个案里最理想的解决办法,\n而用原文中的“面向对象”的方法,主要有如下问题:\n1.总不能用中文key吧\n2.不能聚合,除非先定义function,多个key指向同一个function,那样实际上更乱\n3.当输入枚举增加,你就要修改object,违反了"开-闭 原则"(http://log.fyscu.com/index.php/archives/60/),不是一种好现象\n4.垃圾回收的时候,如果返回值没有被释放,那么doAction即使已经执行完了,也会因此而不被回收,这种习惯有OOM的风险\n5.如果有需要处理上下文中的变量,返回的function的参数表就会和业务耦合得很厉害(这一点自己领会)\n6.凑数</code></pre>\n\n<h4>结论</h4>\n\n<pre><code>啪!啪!</code></pre>\n\n<hr/>\n\n<h4>小蓝说</h4>\n\n<pre><code>其实switch...case 作为几乎所有语言基本语法的一部分,肯定是千锤百炼的,无需怀疑的,\n一些忘记写break之类的问题,一方面多从自己身上找原因,理清思路才是真,\n此外也可以换个好点的IDE,比如webstorm,设置一些代码模板,自动break。\n\n而对于原文中的 object 方法,属于JS比较有特点的对象声明方式,我们尽量还是用比较通用化的办法,\n积累的经验以后在其他语言也能用。</code></pre>",
"createdAt": 1446552360,
"modified": 1446552485
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "60",
"_score": 1.0,
"_source": {
"title": "软件设计七大原则(一):开-闭 原则",
"keywords": [
"设计模式"
],
"content": "<pre><code>一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。</code></pre>\n\n<h2>解读:</h2>\n\n<p>所谓“开-闭”,就是对扩展开放,对修改关闭;\n换言之,你可以让我不伦不类,但不能改变我的初衷。\n举个实际点的例子,“人”这个实体,有年龄这个属性,有走路这个行为,\n你可以选择扩展出一个“杨国宝”,增加一个颜值属性,增加一个装哔行为,\n但是你不能把年龄改成颜值,这样杨国宝就没有年龄属性了,\n这样他就不是一个“人”了。\n这样就违反了“开-闭 原则”</p>\n\n<hr/>\n\n<p>未完待续,感谢部长友情演出。</p>",
"createdAt": 1410260220,
"modified": 1415952393
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "295",
"_score": 1.0,
"_source": {
"title": "yield方式的异步代码什么原理?(刨根向)",
"keywords": [
"generator",
"yield",
"promise"
],
"content": "<blockquote><p>江湖上流传着一个说法,说是通过 yield 和 generator ,就是用同步的方式写出异步的代码。</p><p>真的是这样吗?</p><p>也许你可以从网上搜到很多相关教程,照着样例代码,你也能写出这样的代码。但是你是否真的已经明白,这背后到底发生了什么?</p></blockquote>\n\n<h3>第一集,他是谁?</h3>\n\n<p>这就是一个 <code>generator</code>,</p>\n\n<pre><code>var gen = function* (){\n\treturn 1;\n}</code></pre>\n\n<p>看上去像个函数,浑身有一种莫名其妙的的罪恶感,对了,他让我想起那恶心的指针。</p>\n\n<p>我们看看运行起来会怎么样?</p>\n\n<pre><code>gen();\n// {}</code></pre>\n\n<p>看来只是长得像,跟函数没啥关系呢。</p>\n\n<p>我想他还有其他不为人知的地方,比如,<code>next()</code></p>\n\n<pre><code>var a = gen();\nconsole.log( a.next() );\n\n// { value: 1, done: true } duang!!</code></pre>\n\n<p>我明白了,要调用<code>next</code>才能让他执行起来。</p>\n\n<h3>第二集,yield ?</h3>\n\n<p>如果 <code>generator</code> 遇上 <code>yield</code> 会怎样?</p>\n\n<pre><code>var gen = function* (){\n\tyield 2;\n\treturn 1;\n}\nvar a = gen();\nconsole.log( a.next() );\n// { value: 2, done: false }</code></pre>\n\n<p>他并没有返回1,而是2 ,一个被 <code>yield</code>了的2 。</p>\n\n<p>那么这个<code>done</code>的值,应该就是告诉我还没执行完的意思吧。</p>\n\n<p>那我能不能多<code>next</code>几次?</p>\n\n<pre><code>var a = gen();\nconsole.log( a.next() );\n// { value: 2, done: false }\nconsole.log( a.next() );\n// { value: 1, done: true }\nconsole.log( a.next() );\n// { value: undefined, done: true }</code></pre>\n\n<p>果然,只要<code>done</code>没返回 <code>true</code> 就可以一直 <code>next</code>下去。(注:<code>false</code>也可以继续<code>next</code>,不过已经没有什么意义了 )</p>\n\n<p><strong>一个典型的迭代器呼之欲出 !</strong></p>\n\n<p>没错,<code>generstor</code>就是一个迭代器,暂时来说和异步编程没有任何关系</p>\n\n<pre><code>var item = null;\nwhile( item = a.next() ){\n\tif(item.done === true){\n\t\tbreak;\n\t}\n\t// do anything with {item}\n\tconsole.log(item.value);\n}\n\n// 2 , 1</code></pre>\n\n<h3>第三集,Promise 和 CO</h3>\n\n<p><code>generator</code>的诞生就是为了成为一个伟大的迭代器,阴差阳错之下,被用来写异步代码。\n这得从他遇上 <code>promise</code> 说起。</p>\n\n<p><strong><code>generator</code>,或者说 <code>yield</code>有一个很特别的能力——移花接木。</strong></p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/yield1.png\"/></p>\n\n<p>一个 <code>yield</code> 表达式包含很丰富的操作,一行代码包含了两个阶段逻辑</p>\n\n<ul><li>执行最右边的操作 <code>2+3</code> ,把结果通过<code>value</code>带出去。(<code>next</code> 的返回值)</li><li>再次调用<code>next</code>时,可以接受参数,将外面的值传进来,赋值给 <code>a</code></li></ul>\n\n<p><strong>君子无罪,怀璧其罪</strong></p>\n\n<p>这种特异功能被眼尖的程序员发现了,一个伟大的设想诞生了。</p>\n\n<ul><li>先将异步操作用<code>promise</code>实现,通过<code>yield</code>带出去</li><li>然后执行<code>then</code>函数,获取异步处理结果</li><li>再次执行<code>next</code>,将异步结果传回 <code>generator</code>内部,赋值给<code>yield</code>左边的表达式</li></ul>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/yield2.png\"/></p>\n\n<p><strong>如此一来,这一行代码看起来就像同步代码一样!</strong></p>\n\n<p>我们看看成品代码:</p>\n\n<pre><code>var gen = function* () {\n let content = yield httpGet('http://lanhao.name');\n return content;\n};\n\nvar myCo = (fn) => {\n let state = null;\n let g = fn();\n return (function next(data){\n\t state = g.next(data);\n\t if(state.done){\n\t return state.value;\n\t }else{\n\t return state.value.then( val => next(val) );\n\t }\n })();\n};\n\n\nvar a = myCo(gen);\n\na.then(val=>console.log(val));\n\n//{"code":200,"data":[],"message":""}</code></pre>\n\n<p>而且我们发现,不管<code>generator</code>逻辑如何,<code>Co</code>的写法都是一样的,不会重复编码。</p>\n\n<p>只要通过<code>Co</code>来执行<code>generator</code>,我们就能像同步代码一样写异步操作。</p>\n\n<p><strong>以上就是 yield方式的异步代码 原理解释</strong></p>\n\n<p>(未完)</p>",
"createdAt": 1467257644,
"modified": 1467257644
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "302",
"_score": 1.0,
"_source": {
"title": "致杨先生,关于《javascript到底是如何运行的》",
"keywords": [
"javascript",
"js",
"node"
],
"content": "<h3>致杨先生:</h3>\n\n<h4>前言</h4>\n\n<blockquote><p>引用 <a href=\"http://xiaomingplus.com/full-stack/how-to-run-for-javascript/\">javascript到底是如何运行的?--杨先生</a></p></blockquote>\n\n<p>当我们讨论<code>javascript</code>运行机制这种问题,有别于语法,语法我们有标准可以参考,对于运行机制,我们应该明确是什么引擎。</p>\n\n<p>在这我以V8为例,猜想杨先生大概也是V8,从node源码的角度,补充一二。\n行文没有顺序,只是针对一些观点的补充。</p>\n\n<h4>正文</h4>\n\n<hr/>\n\n<blockquote><p>在js中,有多种类型的观察者,包括文件I/O观察者,网络请求观察者等(其实这里的观察者一般来讲就是宿主环境提供的API)</p></blockquote>\n\n<p>观察者其实不是什么API,而是一种结构。</p>\n\n<p>举个例子,如果是网络IO观察者,它的结构是这样的:</p>\n\n<pre><code>{\n\tfd,\n\tcallback\n}</code></pre>\n\n<p>这个是<code>uv__io_s </code>类型的对象,而我们常说的<code>事件循环</code>里的一个个单元,就是这样的对象。</p>\n\n<p>而文件IO观察者,想必也是差不多,待我进一步研读源码再来求证。</p>\n\n<hr/>\n\n<blockquote><p>一个JavaScript运行时包含一个待处理的任务队列,该队列是先进先出的,队列里的每一个任务都与一个函数相关联(一般是调用API时指定的回调函数)。当[执行栈]为空时,[执行栈]会从[任务队列]中取出队列里最前面的一个任务进行处理。</p></blockquote>\n\n<p>任务队列一直一个值得我们深刻研究的话题,在杨先生的描述里,js有一个“执行栈”,一个任务队列。执行栈 先执行一次,然后从队列里取出一个,如此往复,直到结束。</p>\n\n<p>某个程度上可以这么描述,不过对于杨先生的水平,我认为是要有更高的要求的,这里面,有很多不太严谨的地方。</p>\n\n<p>我将从几个问题来展开:</p>\n\n<p><strong>1.什么是任务队列?</strong></p>\n\n<p>任务队列是 <code>default_loop_struct</code>,来自结构体定义 <code>uv_loop_s</code>。正如前面所说,里面存放的是观察者。</p>\n\n<p><strong>2.什么是 <code>Tick</code></strong></p>\n\n<p>我们知道有一个 <code>process.nextTick</code> 方法,但是 <code>Tick</code> 到底是什么,要从任务队列的运行机制去了解。</p>\n\n<p><code>default_loop_struct</code> 里面的观察者是以链表的结构存储的。而 <code>Tick</code> 里是以 数组 的方式存储的。</p>\n\n<p>杨先生之前的描述只是 执行栈 与 <code>Tick</code> 之间的交互,实际上,每次取出一个任务,都是从<code>Tick</code>里取的。而<code>Tick</code>其实是 <code>default_loop_struct</code>的全量镜像。</p>\n\n<blockquote><p>注:是不是全量,我还需要继续考证,在此仅供参考。</p></blockquote>\n\n<p>所以真正的关系链是:</p>\n\n<pre><code>main() <--- Tick <--- default_loop_struct</code></pre>\n\n<p>在这里顺便说一下,<code>process.nextTick</code> 会让一个callback加到<code>Tick</code>里,而其他的 timer 之类(setTimeout,setImmediate),只会加到 <code>default_loop_struct </code> 。此中区别,还需细细品味。</p>\n\n<p><strong>3.任务队列的时机</strong></p>\n\n<p>在杨先生文中的那个图,我不能说它是错的,但是并没有表达出一个关键信息,就是执行轨迹。</p>\n\n<p>真实的情况是 <code>main()</code>执行完所有JS代码后,进入<code>uv_run()</code>,再去看看 <code>default_loop_struct </code>有没有观察者,没有则程序结束,如果有,则进入<code>epoll_wait()</code>。</p>\n\n<p>以上不理解没关系,可以日后细细研究,在此我只想表达一点,JS不会一边执行代码一边跟任务队列做互动。不信你可以试试,连续一堆 <code>setTimeout </code>,然后直接<code>process.exit()</code>,那么还没来得及进入<code>uv_run()</code>,程序就结束了,这些任务自然一个都执行不了。</p>\n\n<h4>其他</h4>\n\n<p>以上还有一些用语不太严谨,不过为了行文连贯,做出一定的牺牲,需要考究准确性的地方,我们还需要私下讨论为佳。</p>",
"createdAt": 1475820357,
"modified": 1475820357
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "220",
"_score": 1.0,
"_source": {
"title": "[资源]不用翻墙了,httpsqs相关下载",
"keywords": [
"httpsqs"
],
"content": "<p><a href=\"http://pan.baidu.com/s/1c0F6wxi\">libevent-2.0.12-stable.tar.gz</a></p>\n\n<p><a href=\"http://pan.baidu.com/s/1gd3oUKb\">tokyocabinet-1.4.47.tar.gz</a></p>\n\n<p><a href=\"http://pan.baidu.com/s/1o65c8d0\">httpsqs-1.7.tar.gz</a></p>\n\n<hr/>\n\n<p>安装介绍请看 <a href=\"http://zyan.cc/httpsqs/7/1/\">张宴博客</a>\n安装过程出现的error自行百度,不难,\nconfigure 第二个东西的时候,会提示缺少 zlib,执行 <code>yum install bzip2-devel</code> 即可 </p>",
"createdAt": 1419482880,
"modified": 1419576218
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "236",
"_score": 1.0,
"_source": {
"title": "PHP实现 HTML5数据推送SSE原理",
"keywords": [
"php",
"sse"
],
"content": "<p>SSE是一种允许服务器端向客户端推送新数据(简称数据推送)的HTML5技术。</p>\n\n<p><img alt=\"请输入图片描述\" src=\"http://bbs.html5cn.org/data/attachment/forum/201503/23/154252lgtq0fb9ifsggbbt.png\"/></p>\n\n<p>当数据源有新数据时,服务器端能立刻发送给一个或多个客户端,而不用等客户端来请求,这些新数据可能是突发新闻、最新股票、上线朋友的聊天信息、新的天气预报、策略游戏中的下一步等。</p>\n\n<p>SSE适用于更新频繁、低延迟并且数据都是从服务端到客户端。它和WebSocket的区别:</p>\n\n<p>1)便利,不需要添加任何新组件,用任何习惯的后端语言和框架就能继续使用,不用为新建虚拟机弄一个新的IP或新的端口号而劳神。</p>\n\n<p>2)服务器端的简洁。因为SSE能在现有的HTTP/HTTPS协议上运作,所以它能够直接运行于现有的代理服务器和认证技术。</p>\n\n<p>WebSocket相较SSE最大的优势在于它是双向交流的,这意味着服务器发送数据就像从服务器接受数据一样简单,而SSE一般通过一个独立的Ajax请求从客户端向服务端传送数据,因此相对于WebSocket使用Ajax会增加开销。因此,如果需要以每秒一次或者更快的频率向服务端传输数据,就应该用WebSocket。</p>\n\n<h2>浏览器端:</h2>\n\n<pre><code><!doctype html>\n<html>\n <head>\n <meta charset="UTF-8">\n <title>basic SSE test</title>\n </head>\n <body>\n <pre id = "x">initializting...</pre>\n <!--之所以使用pre标签而不是p或者div是为了确保数据能以它被接受时的格式呈现,而不会修改或格式化-->\n\n </body>\n <script>\n var es = new EventSource("basic_sse.php");\n es.addEventListener("message",function(e){\n //e.data\n document.getElementById("x").innerHTML += "\\n"+e.data;\n },false);//使用false表示在冒泡阶段处理事件,而不是捕获阶段。\n </script>\n</html></code></pre>\n\n<h2>服务器端:</h2>\n\n<pre><code><?php\n header('Content-Type: text/event-stream');\n header('Cache-Control: no-cache');\n $time = date('Y-m-d H:i:s');\n\n echo 'retry: 1000'.PHP_EOL;\n echo 'data: The server time is: '.$time.PHP_EOL.PHP_EOL;\n?></code></pre>\n\n<p>效果截图\n<img alt=\"屏幕快照 2015-03-24 下午2.18.38.png\" src=\"http://log.fyscu.com/usr/uploads/2015/03/2566330320.png\"/></p>\n\n<h1>注意事项:</h1>\n\n<blockquote><p>1.“Content-Type: text/event-stream”是专门为SSE设计的MIME类型`</p><p>2.retry可以定义推送间隔,如果不发送这个指令,默认间隔5000毫秒`</p><p>3.每行指令后面要有换行 \\n ,用php的请用兼容方案 PHP_EOL`</p><p>4.最后一条指令要两个换行`</p><p>5.未完,其他详细容后发布`</p></blockquote>",
"createdAt": 1427178060,
"modified": 1442481084
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "276",
"_score": 1.0,
"_source": {
"title": "今天我们来说说[函数依赖的参变量对调用方全部可见]",
"keywords": [
"函数"
],
"content": "<h3>函数依赖的参变量对调用方全部可见</h3>\n\n<p>这句话什么意思?</p>\n\n<p>就是,当你需要写一个函数时,你应该遵守一个原则,让调用函数的主体,知晓影响函数执行结果的一切参变量。</p>\n\n<p>比如 ,我输入 “面粉”,“糖”,“鸡蛋”,你应该返回甜味的馒头,你不能偷偷往里加盐,而又不让我知道也不让我阻止。</p>\n\n<p>而实际上,很多时候我们写的代码,都没有很好遵守这个原则,</p>\n\n<pre><code>function renderHtml($veiw){\n $style = '2016newyear';\n return $this->template($view,$style)\n}</code></pre>\n\n<p>看吧,强行使用了2016newyear风格,调用方完全无可奈何。</p>\n\n<pre><code>function renderHtml($veiw,$style='default'){\n return $this->template($view,$style)\n}</code></pre>\n\n<p>这样就好多了。</p>\n\n<p>不要小看这些原则,这是高质量代码的一小步。</p>",
"createdAt": 1453714529,
"modified": 1453714529
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "283",
"_score": 1.0,
"_source": {
"title": "对象的成员函数,不要偷偷地修改一些数据",
"keywords": [
"oop"
],
"content": "<p>请看一段代码:</p>\n\n<pre><code> $this->setTitle('title');\n\n\n //in class Article\n public function setTitle($title){\n $this->title = $title;\n\n $this->keywords = $this->getKeywords();\n $this->descriptions = $this->getDescriptions();\n }</code></pre>\n\n<p>这里的setTitle 就偷偷地修改了这个成员的 keyword和descriptions,\n就算这样的代码运行起来没有错,但这个做法都是不推荐的。然而很多人都有犯这样的错误,偷懒、无原则地封装逻辑。</p>\n\n<p>记住一个函数,不要做超出自己范围的行为,不要做自己名字没有交代的事情。</p>\n\n<p>代码首先是给人看的,其次才是计算机。</p>",
"createdAt": 1460084762,
"modified": 1460084762
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "281",
"_score": 1.0,
"_source": {
"title": "[转]GET和POST有什么区别?及为什么网上的多数答案都是错的",
"keywords": [
"get",
"post",
"http"
],
"content": "<blockquote><p>原文地址:http://www.cnblogs.com/nankezhishi/archive/2012/06/09/getandpost.html</p></blockquote>\n\n<h3>我的经历</h3>\n\n<p>前几天有人问我这个问题。我说GET是用于获取数据的,POST,一般用于将数据发给服务器之用。</p>\n\n<p>这个答案好像并不是他想要的。</p>\n\n<p>于是他继续追问有没有别的区别?</p>\n\n<p>我说这就是个名字而已,如果服务器支持,他完全可以把GET改个名字叫GET2。</p>\n\n<p>他反问道,那就是单纯的名字上的区别喽?</p>\n\n<p>我想了想,我觉得如果说再具体的区别,只能去看RFC文档了,还要取决于服务器(指Apache,IIS)的具体实现。但我不得不承认,我的确没有仔细看过HTTP的RFC文档。于是我说,我对HTTP协议不太熟悉。这个问题也就结束了。</p>\n\n<h3>最普遍的答案</h3>\n\n<p>回来之后寻思了很久,他到底是想问我什么?我一直就觉得GET和POST没有什么除了语义之外的区别,自打我开始学习Web编程开始就是这么理解的。</p>\n\n<p>可能很多人都已经猜到了,他要的答案是:</p>\n\n<ol><li><p>GET使用URL或Cookie传参。而POST将数据放在BODY中。</p></li><li><p>GET的URL会有长度上的限制,则POST的数据则可以非常大。</p></li><li><p>POST比GET安全,因为数据在地址栏上不可见。</p></li></ol>\n\n<h4>但是很不幸,这些区别<code>全是错误</code>的,更不幸的是,这个答案还是<a href=\"https://www.google.com/search?q=get%E5%92%8Cpost%E7%9A%84%E5%8C%BA%E5%88%AB&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:zh-CN:official&client=firefox-a&channel=fflb\">Google搜索的头版头条</a>,然而我根本没想着这些是答案,因为在我看来他们都是错的。我来一一解释一下。</h4>\n\n<h3>GET和POST与数据如何传递没有关系</h3>\n\n<p> GET和POST是由<a href=\"http://www.w3.org/Protocols/rfc2616/rfc2616.html\">HTTP协议定义</a>的。\n在HTTP协议中,Method和Data(URL, Body, Header)是<code>正交</code>的两个概念,也就是说,使用哪个Method与应用层的数据如何传输是没有相互关系的。</p>\n\n<p> HTTP没有要求,如果Method是POST数据就要放在BODY中。也没有要求,如果Method是GET,数据(参数)就一定要放在URL中而不能放在BODY中。</p>\n\n<p> 那么,网上流传甚广的这个说法是从何而来的呢?我<a href=\"http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.1\">在HTML标准中,找到了相似的描述</a>。这和网上流传的说法一致。但是这只是<code>HTML标准对HTTP协议的用法的约定</code>。怎么能当成GET和POST的区别呢?</p>\n\n<p> 而且,现代的Web Server都是支持GET中包含BODY这样的请求。虽然这种请求不可能从浏览器发出,但是现在的Web Server又<code>不是只给浏览器用</code>,已经完全地超出了HTML服务器的范畴了。</p>\n\n<p> 知道这个有什么用?我不想解释了,有时候就得自己痛一次才记得住。</p>\n\n<h3>HTTP协议对GET和POST都没有对长度的限制</h3>\n\n<p> HTTP协议明确地指出了,HTTP头和Body都没有长度的要求。而对于URL长度上的限制,有两方面的原因造成:</p>\n\n<ol><li><p>浏览器。据说早期的浏览器会对URL长度做限制。据说IE对URL长度会限制在2048个字符内(流传很广,而且无数同事都表示认同)。但我自己试了一下,我构造了90K的URL通过IE9访问live.com,是正常的。网上的东西,哪怕是Wikipedia上的,也不能信。</p></li><li><p>服务器。URL长了,对服务器处理也是一种负担。原本一个会话就没有多少数据,现在如果有人恶意地构造几个几M大小的URL,并不停地访问你的服务器。服务器的最大并发数显然会下降。另一种攻击方式是,把告诉服务器Content-Length是一个很大的数,然后只给服务器发一点儿数据,嘿嘿,服务器你就傻等着去吧。哪怕你有超时设置,这种故意的次次访问超时也能让服务器吃不了兜着走。有鉴于此,多数服务器出于安全啦、稳定啦方面的考虑,会给URL长度加限制。但是这个限制是针对所有HTTP请求的,与GET、POST没有关系。</p></li></ol>\n\n<h4>安全不安全和GET、POST没有关系</h4>\n\n<p> 我觉得这真是中国特色。我讲个小段子,大家应该可以体会出这个说法多么的可笑。</p>\n\n<p> 觉得POST数据比GET数据安全的人会说</p>\n\n<p> “防君子不防小人;中国小白多,能防小白用户就行了。”</p>\n\n<p> “哼,”我不以为然,“那你怎么不说,URL参数都Encode过了,或是Base64一下,小白也看不懂啊。”</p>\n\n<p> 那人反驳道,“Encode太简单了,聪明点儿的小白很容易就可以Decode并修改掉。”</p>\n\n<p> 我笑道,“五十步笑百步耳,再聪明点儿的小白还会截包并重发呢,Opera就有这功能。”</p>\n\n<p> 那人阴险地祭出神器——最终解释权,说,“这个不算小白。”</p>\n\n<p> 我日啊。</p>\n\n<h3>最后一点儿感想</h3>\n\n<p> 我之前一直做Windows桌面应用,对Web开发无甚了解,直到一年多前转做服务器端开发,才开始接触到HTTP。(注意,我说的是HTTP,不是HTML。服务器开放接口是基于REST理念设计的,使用的协议是HTTP,但是传输的内容不是HTML。这不是Web Server,而是一个Web Service)</p>\n\n<p> 所以我对于GET和POST的理解,是纯粹地来源于HTTP协议。他们只有一点根本区别,简单点儿说,一个用于获取数据,一个用于修改数据。具体的请参考RFC文档。</p>\n\n<p> <code>如果一个人一开始就做Web开发,很可能把HTML对HTTP协议的使用方式,当成HTTP协议的唯一的合理使用方式。</code></p>\n\n<p><code>从而犯了以偏概全的错误。</code></p>\n\n<p> 可能有人会觉得我钻牛角尖。我只是不喜欢模棱两可,不喜欢边界不清、概念不明,不喜欢“拿来主义”,也不喜欢被其它喜欢钻牛角尖的人奚落得无地自容。</p>\n\n<p> <h1> “知之为知之,不知为不知,是知也。”</h1></p>",
"createdAt": 1459277040,
"modified": 1459277233
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "282",
"_score": 1.0,
"_source": {
"title": "Git-flow 带你飞起",
"keywords": [
"git",
"git-flow-flow"
],
"content": "<h2>简单介绍</h2>\n\n<h3>git-flow,是一个工具,是一种规范</h3>\n\n<blockquote><pre><code>Git Flow是构建在Git之上的一个组织软件开发活动的模型,是在Git之上构建的一项软件开发最佳实践。Git Flow是一套使用Git进行源代码管理时的一套行为规范和简化部分Git操作的工具。</code></pre><p>( http://www.ituring.com.cn/article/56870 )</p></blockquote>\n\n<p>所以说,git-flow 是一个规则,一种约定,一种规范,并不是什么洪水猛兽,\n是提高你使用git技术的一个进阶实践。</p>\n\n<h3>git-flow 基于git本身的分支管理机制</h3>\n\n<p>所以,你必须先了解什么是git的分支(branch).请看图</p>\n\n<p><img alt=\"\" src=\"https://raw.githubusercontent.com/quickhack/translations/master/git-workflows-and-tutorials/images/git-workflow-release-cycle-2feature.png\"/></p>\n\n<p>因为时间关系,我们假设你已经知道什么是branch,\ngit-flow就是通过在一个项目里划分不同的分支,来实现功能开发、bug修复、版本发布,以及开发过程中的冲突处理等。</p>\n\n<p>(如果觉得这个描述有点绕,那么暂时不用理解它。)</p>\n\n<h3>git-flow把分支划分了几个类别</h3>\n\n<p>Master</p>\n\n<pre><code>就是平时我们看到的master,项目的主要分支,对外的第一门面。\n所有外人浏览你的项目,使用你的项目,第一时间都是看到master。\n你可以把它理解成 稳定无bug发布版 。(任何时候都ready to deploy)\n所以,git-flow 要求我们不能在master下做开发。</code></pre>\n\n<p>Develop</p>\n\n<pre><code>处于功能开发最前线的版本,查看develop分支就能知道下一个发布版有哪些功能了。\ndevelop一开始是从master里分出来的,并且定期会合并到master里,\n每一次合并到master,表示我们完成了一个阶段的开发,产生一个稳定版。\n同样的,develop下也不建议直接开发代码,develop代表的是已经开发好的功能\n的回归版本(为什么说回归?)</code></pre>\n\n<p>Feature</p>\n\n<pre><code>带着develop处的疑问,我们在feature里为你解答。(有点长,别不看)\nfeature的作用是为每一个新功能从develop里创建出来的一个分支。\n例如小明和小白分别做两个不相干的功能,就应该分别创建两个分支,\n各自开发完以后,先后合并到develop里,这就叫做回归。\n在这个过程里,小明小白不需要任何的沟通,分别并行地开发,\ngit-flow能很好的处理好分支间并行开发的关系。\n\n而develop,则会在适当的时候,由合适的人,合并到master,作为下一个稳定版本。</code></pre>\n\n<p>Hotfix</p>\n\n<pre><code>以上3种以外,还有一个很重要的类型,hotfix。\n它是用来修复紧急bug的,而bug通常是来自线上的,\n所以hotfix分支是从master里创建出来的,并且,在bug修改好以后,\n要同时合并到master和develop,这一点需要特别注意。</code></pre>\n\n<p>Release</p>\n\n<pre><code>release更多倾向与版本发布,项目上线前的一些全面测试以及上线准备。\n同样也肩负着版本归档,回滚支持等。</code></pre>\n\n<h2>开始使用</h2>\n\n<p>上面说到了,git-flow本质只是一个约定,所以你完全可以在现行的git命令行里,\n手动地完成全部git-flow操作,(手动创建、合并分支等),\n重点是遵守git-flow规范,遵守命名约定和分支管理流程。</p>\n\n<p>不过,git-flow早就有插件了。参看这个文章:</p>\n\n<blockquote><p>http://blog.163.com/tod_zhang/blog/static/1025522142012913113957679/</p><p>安装了这个插件,你的git就多了一系列方便的命令,比如:</p><p>git flow init</p><p>git flow feature start</p><p>git flow feature finish </p><p>等等。</p></blockquote>\n\n<p>不过,我觉得这个插件还是不够方便,我墙裂推荐你们都用 SourceTree 。</p>\n\n<p><img alt=\"\" src=\"https://www.sourcetreeapp.com/dam/jcr:4c4d9b59-1049-4abc-b773-cc052c17f73c/sourcetree_rgb_slate.png?cdnVersion=fi\"/></p>\n\n<p>如果我要推荐一个git-flow客户端,我会推荐SourceTree。</p>\n\n<p>如果我要推荐一个git客户端,那我还是推荐SourceTree。</p>\n\n<p>没错,SourceTree是专门为git-flow开发的git客户端,它涵盖了所有git本身的功能,</p>\n\n<p>所以即使你不flow,你也可以使用SourceTree来管理你的git项目。</p>\n\n<p>官方网站:</p>\n\n<blockquote><p>https://www.sourcetreeapp.com/</p></blockquote>\n\n<p>至于如何下载、安装、/<em> 破解 </em>/ ,这些内容就交给各位自行baidu了。</p>\n\n<h2>详细说明</h2>\n\n<p>那么下面我们来实际操作一次,看看SourceTree如何帮助我们使用git-flow</p>\n\n<p><strong>1.小明创建了一个新项目,就做 Demo,并且用SourceTree来打开它。如果这一步都\n不会,你还是别做开发了。</strong></p>\n\n<p>(一个新的项目,就不发图了)</p>\n\n<p><strong>2.小明为了使用git-flow,需要为git项目做一次初始化。</strong></p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/1.png\"/></p>\n\n<p>这些可以改动的地方,为了方便其他协作人员,还是用默认好了。</p>\n\n<p><strong>3.初始化后,会自动切换到develop分支,接下来,小明要发一个新功能</strong></p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/2.png\"/></p>\n\n<p>注意那个 <strong>Git Flow</strong> 按钮,所有flow的功能都是从那里开始操作的。\n在弹出的窗口里输入功能的名字即可,小明决定开发一个 test1 功能。</p>\n\n<p>这时,左侧的菜单已经看到分支切换到test1了(有加粗效果)</p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/3.png\"/></p>\n\n<p><strong>4.然后小明开始了暗无天日的编码过程,千辛万苦后写了一行readme</strong></p>\n\n<p>接下来我们演示一下如何提交修改</p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/4.png\"/></p>\n\n<p>这个界面十分清晰地告诉你本地没有commit的代码,</p>\n\n<p>当然了,这仅仅是commit到本地,因为这始终是git,我们还需要push到远端。</p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/5.png\"/></p>\n\n<p>可以在左侧看到,我们暂存了多少个commit。\n按照图上流程,选择你要push 的分支,你也可以一次选择多个,\n在这里我们先push 功能分支test1 。</p>\n\n<p><strong>5.提交过几次代码之后,小明认为功能已经开发完毕,可以回归到develop了</strong></p>\n\n<p>注意,这个时候,只有test1的代码是改变了的,develop还是停留在小明创建feature时的状态。</p>\n\n<p>为了安全起见,每次合并之前,最好pull一下develop,再次不表。(SourceTree拉取)</p>\n\n<p>那么我们现在把开发好的test1合并到develop:</p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/6.png\"/></p>\n\n<p>整个界面很简单,就一个操作,实际上,它背后做了很多。 (@ -_-)</p>\n\n<p>如无意外,test1里的改动会合并到develop里,并且会删除<strong>本地</strong>的test1,</p>\n\n<p>然后把分支切换到develop,这时候我们应该能看到整个test1期间的变动数目:\n<img alt=\"\" src=\"http://lanhao.name/img/upload/7.png\"/></p>\n\n<p>接下来你应该立即把develop的改动push到远端!</p>\n\n<p>OK,这就是一个功能开发的完整流程,就算有多个功能在并行开发,</p>\n\n<p>通过git-flow的协调,都能互不干扰地开发,最终全部作用到develop上。</p>",
"createdAt": 1459615140,
"modified": 1499497390
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "198",
"_score": 1.0,
"_source": {
"title": "【记录】javascript获取页面的referer",
"keywords": [
"javascript"
],
"content": "<p>服务端自不多说,通过<code>headers</code>信息即可,而在浏览器端,也是有办法的。</p>\n\n<pre><code>var getReferrer=function() {\n\n var referrer = '';\n\n try {\n\n referrer = window.top.document.referrer;\n\n } catch(e) {\n\n if(window.parent) {\n\n try {\n\n referrer = window.parent.document.referrer;\n\n } catch(e2) {\n\n referrer = '';\n\n }\n\n }\n\n }\n\n if(referrer === '') {\n\n referrer = document.referrer;\n\n }\n\n return referrer;\n\n};</code></pre>\n\n<p>单纯记录一下,记不起的时候看看</p>",
"createdAt": 1415783460,
"modified": 1415951062
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "249",
"_score": 1.0,
"_source": {
"title": "[ Javascript探究 ] delete",
"keywords": [
"javascript"
],
"content": "<p>1,对象属性删除</p>\n\n<pre><code>function fun(){\n this.name = 'mm';\n}\n\nvar obj = new fun();\n\nconsole.log(obj.name);//mm\n\ndelete obj.name;\n\nconsole.log(obj.name); //undefined</code></pre>\n\n<p>2,变量删除</p>\n\n<pre><code>var name = 'lily';\ndelete name;\nconsole.log(name); //lily</code></pre>\n\n<p><code>直接用delelte删除不了变量</code></p>\n\n<p>3,删除不了原型链中的变量</p>\n\n<pre><code>fun.prototype.age = 18;\ndelete obj.age;\nconsole.log(obj.age) //18</code></pre>",
"createdAt": 1431399454,
"modified": 1431399454
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "280",
"_score": 1.0,
"_source": {
"title": "replace into 真的好使吗?",
"keywords": [
"mysql",
"replace"
],
"content": "<h2>replace into 真的好使吗?</h2>\n\n<h3>关于replace into</h3>\n\n<pre><code>我们常用insert into ,你肯定也经历过,插入错误,提示duplicate key,\n这就是你的表结构里包含了一个唯一键,这个时候,你可能会先删除再插入,或者\n改用update,或者放弃。\n\n其实可以试试replace。\n\nreplace的语法和insert基本一样,大部分情况下把insert 换成replace即可。\n其原理是,如果插入不成功,自动删除上一条记录,然后再进行插入。</code></pre>\n\n<h3>那么问题来了</h3>\n\n<pre><code>replace在它适用的场景十分方便,但是会引起一个问题。\n由于是先删除后插入,如果有自增ID,也会被重新分配。\n这对于一些业务系统,是不能忍受的,比如 user_id。\n总不能说,更改了密码,uid都变了,其他关联表也乱了。</code></pre>\n\n<h3>怎么办?</h3>\n\n<pre><code>我们有一种不改变自增ID的方法。一颗赛艇\n\nON DUPLICATE KEY UPDATE\n\ninsert into table1(A, B, C) values(1,2,3) \n\ton duplicate key update A=1,B=2,C=3;\n\n以上语句,就是捕获duplicate错误,然后执行update,\n如此如此,就不会改变自增ID了。</code></pre>",
"createdAt": 1458284662,
"modified": 1458284662
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "286",
"_score": 1.0,
"_source": {
"title": "从一段代码剖释PHP foreach做了什么",
"keywords": [
"php",
"foreach",
"unset"
],
"content": "<h2>从一段代码剖释PHP foreach做了什么</h2>\n\n<p>当我们使用 foreach 的时候,php到底在做什么?我们看一段代码</p>\n\n<pre><code>$arr = [ 1 , 2 , 3 ];\n\nforeach($arr as $key => &$value){\n\t$value *= 2;\n}\n// [2,4,6]\n\nforeach($arr as $key => $value){\n\techo $value;\n}\n// 2,4,4 why ?</code></pre>\n\n<p>为什么不是输出 2 , 4 , 6 ? 要解决这个谜题,我们需要搞清楚foreach到底做了什么。\n以下均为伪代码:</p>\n\n<pre><code>foreach($arr as $key => &$value){\n\t/* \n\t1.首先 php 会内部执行 \n\t$key = key($arr);\n\t$value = & current($arr); \n\t注,因为上面代码使用 &$value 所以此处也是引用\n\t*/\n\t$value *= 2;\n}\n//第一部分没有问题,正如你理解一样\n//但是重点来了,这个时候 $value 仍然是 $arr 最后一个元素的引用\n\nforeach($arr as $key = $value){\n\t/* \n\t1.首先 php 会内部执行 \n\t$key = key($arr);\n\t$value = current($arr); \n\t注,结合上面的情况,第一循环相当于\n\t( $arr[2] = $arr[0] )\n\t所以第一循环以后,数组变成 2 , 4 , 2 ;想想为什么?\n\t同理,第二循环相当于 $arr[2] = $arr[1],\n\t数组变成 2 , 4 , 4\n\t第二循环相当于 $arr[2] = $arr[2],\n\t数组还是 2 , 4 , 4\n\t*/\n}</code></pre>\n\n<p>疑难得解,那么怎么解决?</p>\n\n<p>方法一,不用引用</p>\n\n<pre><code>foreach($arr as $key => $value){\n\t$arr[$key] = $value * 2;\n}</code></pre>\n\n<p>方法二,unset引用变量</p>\n\n<pre><code>foreach($arr as $key => &$value){\n\t$value *= 2;\n}\nunset($value);</code></pre>",
"createdAt": 1461308820,
"modified": 1461315325
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "93",
"_score": 1.0,
"_source": {
"title": "大家最好安安静静地做一个程序员,不要胡搞毛搞",
"keywords": [],
"content": "<h4>前言:DO NOT SAY JH/JUANJUAN</h4>\n\n<p>研发2.0经过上一学期的一波大跃进之后,\n尽管取得了相当好的效果,\n但是到了新学期,展现出一种遇到瓶颈的迷失期。</p>\n\n<p>所以呢,有必要提个醒,借用水哥一句话:</p>\n\n<blockquote><p>安安静静做人,踏踏实实做事。</p></blockquote>\n\n<h4>1.FIND OUT YOUR POSITION</h4>\n\n<p>清楚自己在哪个阶段。既然大家都很迷失,我用一张美图来说明一下:\n<img alt=\"001\" src=\"http://log.fyscu.com/usr/uploads/2014/09/862556436.png\"/></p>\n\n<p>我随机抽出5个案例来说明:\n1. 像小白和JH这个阶段,感觉坡很陡很难,是因为没有走过门槛阶段,觉得处处受限发挥不佳。这个时期需要坚持下去,不耻求问水哥,争取早日走到下一阶段。这个阶段的程序员最需要我们的关心,大家不要觉得烦就不理他们,多一点爱,多一点包容。\n2.像大佳、吴会这个阶段的,跨过了一个坡,进入平台期,感觉上是什么都会做,有时又好像什么都做不了。这个阶段需要认准某个方向或产品,比如妹子UI、socket等,深入学习,从中加固基础知识,最终实现融会贯通的理想境界。\n3.像DSG大神这样的,已经进入了第二个瓶颈期,这个阶段的特点就是,想到的都能做到,但是作为程序员,要做好,才能成为工程师。这个阶段需要静下心来了解技术原理,知其然知其所以然,穷技术之极致,最后忽然天成,无招胜有招,一花一叶皆为代码。</p>\n\n<p> ## (正文已结束,后面都是题外话)</p>\n\n<h4>2.WHY NODE</h4>\n\n<p>很多人问我,node有什么好,为什么要用node。主要是两方面:\n- 其一,node的事件驱动和异步IO,与其说这是node 的优势,还不如说是我看到的亮点,之所以用node,正正就是我弄php上这样的缺失,可以说是node弥补了我用php的空洞,这叫对症下药,而不在于网络上吹嘘得有多夸张。\n- 其二,就是缘分,能做到事件驱动和异步IO的平台很多,node只是其中一种。而在我需要弥补空洞的时候它的出现,就顺理成章成为我的选择。有时就是这样,能陪你走下去的,不一定是最好的那个,而是在你最需要的时候出现的对的那个。</p>\n\n<h4>3.加油同仁,闻道有先后,逼格有专攻</h4>\n\n<p>在这个团队,给你最多的不是技术支持,而是精神支持,更多的是需要你自己学习,比如我根本无法给你指导C语言,你要跟百(goo)度(gle)学习。\n三人行,必有一个逼格高,最好的学习是相互学习,大家都是朋友嘛。</p>\n\n<h4>4.还是要SAY SAY JH</h4>\n\n<p>好像大家都知道了,JH做GPA用了3个月,做bill用了两天。与其说是因为做GPA取得的进步,我更愿意相信是因为juanjuan给的动力,你愿意相信哪一个呢?\n所以,只要有足够的动(dong)力(ji),就能爆发很大的能量,实现自己的梦(ye)想(wang)。</p>",
"createdAt": 1410754140,
"modified": 1410754272
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "180",
"_score": 1.0,
"_source": {
"title": "jquery 新建的元素事件绑定问题 ",
"keywords": [
"jquery"
],
"content": "<p> js的事件监听跟css不一样,css只要设定好了样式,不论是原来就有的还是新添加的,都有一样的表现。而事件监听不是,你必须给每一个元素单独绑定事件。</p>\n\n<p>常见的例子是处理表格的时候。每行行末有个删除按钮,点了这个能够删除这一行。</p>\n\n<pre><code><table>\n <tbody>\n <tr>\n <td>这行原来就有</td>\n <td><buttonclass="del">删除</button></td>\n </tr>\n <tr>\n <td>这行原来就有</td>\n <td><buttonclass="del">删除</button></td>\n </tr>\n </tbody>\n</table></code></pre>\n\n<p>通常,我会这么绑定</p>\n\n<pre><code>jQuery(function($){\n //已有删除按钮初始化绑定删除事件\n $(".del").click(function() {\n $(this).parents("tr").remove();\n });\n});</code></pre>\n\n<p>对于在domready之前就存在的删除按钮,一切都很完美。但如果在domready之后用js动态添加几行,那新增的几行中的这些按钮都将失去任何作用。</p>\n\n<p>如何解决这个问题?以下提供4种解决方案:</p>\n\n<p>0号解决方案——onclick法\n如果不顾结构与行为分离的准则的话,通常,我会这么做。\n注意,此时的deltr这个function必须是全局函数,得放jQuery(function($) {})外面,放里边就成局部函数了,html里的onclick就调用不到了!</p>\n\n<pre><code><td><buttononclick="deltr(this)">删除</button></td>\n\n jQuery(function($){\n //添加行\n $("#add2").click(function(){\n $("#table2>tbody").append('<tr><td>新增行</td><td><button nclick="deltr(this)">删除</button></td></tr>')\n });\n });\n//删除行的函数,必须要放domready函数外面\nfunction deltr(delbtn){\n $(delbtn).parents("tr").remove();\n};</code></pre>\n\n<p>1号解决方案——重复绑定法\n即,在domready的时候就给已有的元素绑定事件处理函数,\n而后当新增加的元素的时候再次绑定。</p>\n\n<pre><code><td><buttonclass="del">删除</button></td>\njQuery(function($){\n//定义删除按钮事件绑定\n//写里边,防止污染全局命名空间\nfunction deltr(){\n $(this).parents("tr").remove();\n};\n//已有删除按钮初始化绑定删除事件\n$("#table3 .del").click(deltr);\n//添加行\n$("#add3").click(function(){\n $('<tr><td>新增行</td><td><button class="del">删除</button></td></tr>')\n .find(".del").click(deltr).end()\n .appendTo($("#table3>tbody"));\n});\n});</code></pre>\n\n<p>2号解决方案——事件冒泡法\n利用事件冒泡的原理,我们给这个按钮的祖先元素绑定事件处理函数。\n然后通过event.target这个对象来判断,这个事件是不是我们要找的对象触发的。\n通常可以利用一些DOM属性,比如event.target.className、event.target.tagName等之类的来判断。</p>\n\n<pre><code><td><buttonclass="del">删除</button></td>\n\njQuery(function($){\n //第四个表格的删除按钮事件绑定\n $("#table4").click(function(e) {\n if (e.target.className=="del"){\n $(e.target).parents("tr").remove();\n };\n });\n //第四个表格的添加按钮事件绑定\n $("#add4").click(function(){\n $("#table4>tbody").append('<tr><td>新增行</td><td><button class="del">删除</button></td></tr>')\n });\n});</code></pre>\n\n<p>3号解决方案——复制事件法\n上面几种方案可以说即便你没有用到jQuery库,你也能相对比较容易的实现。但这种方案相对依赖jQuery的程度更高。而且必须要求jQuery 1.2版以上。低版本jQuery需要插件。\n上面两个方案都是对删除函数动了很多脑筋,换了多种触发、绑定的方式。这个方案不同,可以与平时纯静态的元素一样在domready的时候绑定。但在我们添加新行的时候我们改动一下,不再想上面那样拼接字符串来添加新行了。这回我们尝试使用复制DOM元素的方式。并且复制的时候连同绑定的事件一起复制,复制完之后再用find之类的修改内部的元素。\n同时,就像这个例子,如果你会把所有元素都删除光,那template这个模板是必须的,如果不会删光,那就未必需要用template了。为了防止被误删,此处我把template设了隐藏。\n我使用了jQuery中特有的clone(true)</p>\n\n<pre><code>1..template{display:none;}\n\n1.<trclass="template">\n2. <td>这里是模板</td>\n3. <td><button class="del">删除</button></td>\n4. </tr>\n5. <tr>\n6. <td>这行原来就有</td>\n7. <td><button class="del">删除</button></td>\n8. </tr>\n9. <tr>\n10. <td>这行原来就有</td>\n11. <td><button class="del">删除</button></td>\n12. </tr>\n\n1.jQuery(function($){\n2. //第五个表格的删除按钮事件绑定\n3. $("#table5 .del").click(function() {\n4. $(this).parents("tr").remove();\n5. });\n6. //第五个表格的添加按钮事件绑定\n7. $("#add5").click(function(){\n8. $("#table5>tbody>tr:eq(0)")\n9. //连同事件一起复制\n10. .clone(true)\n11. //去除模板标记\n12. .removeClass("template")\n13. //修改内部元素\n14. .find("td:eq(0)")\n15. .text("新增行")\n16. .end()\n17. //插入表格\n18. .appendTo($("#table5>tbody"))\n19. });\n20.});</code></pre>\n\n<p>复制代码</p>\n\n<p>总评:\n上面4种方案,各有优劣。\n0号方案,结构与行为完全没有分离,而且污染全局命名空间。最不推荐。所以我都不把它当作一个方案来看。但对于js初学者,可以用来项目救急。\n1号方案,中规中矩,没啥好也没啥不好\n2号方案,这种方法充分的发挥了js事件冒泡的优势。而且效率最高。但同时由于这种方案无视了jQuery强大的选择器,所以如果涉及的元素属性要求过多就会比较麻烦了。你会徘徊在众多if的条件的是非关系之中。后来我想起来,可以用jQuery中的$(event.target).is(selector)来作为条件。这样可以极大提升开发效率,但略微降低执行效率。\n3号方案,这是我认为最能体现结构与行为分离的思想的一种方案。但缺点也很明显,对于jQuery依赖性过于高了,要不就自己写一个复制连同事件一起复制的函数,但这也显然对于初学者来说异常困难。但从未来的趋势的角度来看,还是很推荐使用这种方案的。</p>\n\n<p>具体选用哪一个方案,没有定数。具体看你的项目以及你js还有结构与行为分离的思想的掌握程度。最适合的才是最好的。</p>",
"createdAt": 1414117740,
"modified": 1415951877
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "305",
"_score": 1.0,
"_source": {
"title": "记一次结构化数据的经历",
"keywords": [
"结构化",
"schema"
],
"content": "<h4>0.背景</h4>\n\n<p>故事的背景,是源于要为第三方提供一套接口,而这套接口又是依赖于其他内部数据API。</p>\n\n<p>这看起来好像有点平常,不过事情通常没有想象中那么顺利。</p>\n\n<p>这问题在于,内部的API接口,正在处于一个动荡的年代,结构说变就变。有时候可能只是属性名变了,有时候甚至整块数据都不见了。</p>\n\n<p>而对于一套给第三方用的接口,这是致命的。我们需要保持数据模型的绝对稳定性。</p>\n\n<p><img alt=\"\" src=\"http://lanhao.name/img/upload/fake_api.png\"/></p>\n\n<h4>1.有哪些需要做的?</h4>\n\n<p>从内部API拿到数据,剔除部分敏感信息,调整一下属性名,简化一下层次结构,这就是我们这套接口需要做的东西。</p>\n\n<p>以上的事情,都可以直接在代码里做的,反正简单<code>unset</code>一下,<code>array_*</code>一下,又不会太累,只要对照结构写就行。</p>\n\n<p><strong>但是开发者小蓝不会这么做!</strong></p>\n\n<p>这么做有以下不爽的地方:</p>\n\n<ul><li>底层API每次变动,都需要跟进编码,编码意味着可能出BUG</li><li>对于要产出的结构,没有一个明确的描述,只能靠代码</li><li>为了解决第二个问题,需要专门维护这个文档,告诉你产出是怎样的,时间长了,很容易“文(档)不对(代)码”</li></ul>\n\n<h4>2.应该怎么做</h4>\n\n<p>针对以上几个问题的思考,我希望能实现以下的指标:</p>\n\n<ul><li>最大限度地不受底层API的影响,即使底层有迭代,这边接口的输出结构稳定</li><li>依赖一些<code>Schema</code>文件来描述底层数据与输出数据之间的转化关系,以及直接能表达出输出数据的结构</li><li>有条件的情况下,还能根据<code>Schema</code>直接生产数据文档,保证文档与执行结果的统一性</li></ul>\n\n<p><strong>综上所述,我需要制定一个描述<code>Schema</code>的规范,以及编写一个数据转换的方法</strong></p>\n\n<h4>3.实际操作</h4>\n\n<p>我们先来放出一个源数据的例子</p>\n\n<pre><code>$source = [\n 'arr' => [\n [\n 'a' => 1,\n 'secret'=>'hey'\n ],\n [\n 'a' => 4,\n 'secret'=>'jude'\n ]\n ],\n 'hello' => '!'\n];</code></pre>\n\n<p>首先是<code>Schema</code>部分,为了简单易用,目前直接使用编程语言内部的数据类型来描述。</p>\n\n<pre><code>$schema = [\n 'arr=array' => [\n [\n 'a=c' => ''\n ]\n ]\n];</code></pre>\n\n<p> 这个<code>Schema</code>的含义就是:</p>\n\n<ul><li>首先<code>arr</code>这样的描述不清楚,要重命名为<code>array</code></li><li>其次,<code>array</code>是个数组,里面的元素的模型只展示一个<code>a</code>属性,并且重命名为<code>c</code></li><li><p><code>hello</code>这个属性,直接干掉</p><p>然后输出的结果就是:</p><pre><code>{\n "array" : [\n {\n 'a' : 1\n },\n {\n 'a' : 4\n }\n ]\n}</code></pre></li></ul>\n\n<p>当然,<code>Schema</code>规则肯定不止这两条,这里只挑比较直接的说明一下。</p>\n\n<p>同样的,规则可以慢慢添加,重要的是,整个体系的依托,要建立起来。</p>\n\n<p><strong>接下来就差一个转换函数了</strong></p>\n\n<p>代码就不贴了,无非就是根据<code>Schema</code>来读取<code>Source</code>的数据,</p>\n\n<p>遇到数组,就让<code>Source</code>循环起来,</p>\n\n<p>遇到对象,就让<code>Source</code>和<code>Schema</code>递归进去,</p>\n\n<p>对,递归是重点。</p>\n\n<p>写这个函数的过程就是,慢慢调bug,抓头发,拍大腿的过程,反正就是越来越健壮就是了。</p>\n\n<h4>4.立竿见影的好处</h4>\n\n<p>转换函数初步可用后,尝试着编写一个数据接口,包括编写<code>Schema</code>的时间,10分钟不到就出来了,还可靠。(按照对数据手写的话,估计半小时以上)</p>\n\n<p>而且写<code>Schema</code>的时候,可用将大脑切换到<strong>数据模式</strong>,一门心思地想数据,这样出错的可能也低许多。</p>\n\n<p>到运行稳定后甚至可以让其他人员来维护<code>Schema</code>,我们专心做其他技术细节。</p>",
"createdAt": 1485174420,
"modified": 1485176123
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "299",
"_score": 1.0,
"_source": {
"title": "【PHP】比较 exit 和 die",
"keywords": [
"php",
"die",
"exit"
],
"content": "<blockquote><p>无意中在网上看到 <code>exit</code> 和 <code>die</code> 的区别,歪果仁一言不合就讨论开了,其中不乏一些有趣的结论。</p></blockquote>\n\n<h4>官方:<code>exit</code> 和 <code>die</code> 是一样的</h4>\n\n<p>PHP Manual for exit:</p>\n\n<pre><code>Note: This language construct is equivalent to die().</code></pre>\n\n<p>PHP Manual for die:</p>\n\n<pre><code>This language construct is equivalent to exit().</code></pre>\n\n<p>PHP官方明确指明这两个是一样的,并且有以下佐证:</p>\n\n<p><a href=\"http://php.net/manual/en/aliases.php\">List of Function Aliases (函数别名列表)</a>\n,在这个列表里,<code>exit</code> 是主函数,<code>die</code>是别名,PHP官方不推荐用别名,因为这有可能会在将来的版本里被干掉。(可能性不大)</p>\n\n<p>另一个证据更充分,有人分析了 <code>parser token</code>, 两者解释出来都是 <code>T_EXIT</code></p>\n\n<p>意思就是,php解释这两个的时候,对应的是同一个东西。</p>\n\n<h4>坊间,两者还是有一些区别的</h4>\n\n<ul><li><p>起源不一样</p><p>php的诞生是参考了很多语言的, 可以参考 <a href=\"https://exploringdata.github.io/vis/programming-languages-influence-network/#PHP\">这里</a> 。</p><p>php主要受 <code>Perl</code>, <code>C</code>, <code>Python</code>, <code>C++</code>, <code>C#</code>, <code>Tcl</code>, <code>Java</code>, <code>Smalltalk</code> 影响。</p><p>而 <code>exit</code>是来源于 <code>C</code>语言中的 exit(),<code>die</code>是来源于<code>Perl</code>中的 die 。</p></li><li><p>角度刁钻,解释起来效率不一样</p><p>有一位歪果仁,没学过php,但是从脚本语言解释的角度分析了两者的区别。</p><p>大体意思就是,php被解释的时候, 一些函数、变量之类会被翻译成<code>TOKEN</code>,</p><p>如果在你的项目里 <code>d</code>开头的函数比较多,那<code>die</code>解释起来就慢,反之亦然。</p><p>我觉得这个也是有一定的道理的,至少在解释阶段确实是存在差异。</p></li><li><p>从语义的角度看</p><p>也有的朋友从语义的角度分析, </p><p><code>die</code>是程序挂了,一般用于出错的场合:</p><pre><code>if( $data === false ) {\n\tdie( "Failure." ); // something wrong\n}</code></pre><p><code>exit</code>表示程序退出,一般是正常退出</p><pre><code>// start\n// all thing done\n// and exit as normal\nexit(0);</code></pre><p>这个观点是相当有建设性的,我一贯坚持代码首先是给人看的,</p><p>这样的区分使用是对代码可读性的很好实践。</p></li></ul>",
"createdAt": 1470378670,
"modified": 1470378670
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "277",
"_score": 1.0,
"_source": {
"title": "Javascript 对象之 Object.defineProperty",
"keywords": [
"javascript",
"object"
],
"content": "<p>平时我们定义一个对象,给它添加属性,通常会这样做</p>\n\n<pre><code> var o = {};\n o.foo = function(){\n console.log('foo');\n}\no.foo(); // print 'foo'</code></pre>\n\n<p>这是可行的,但是如果我们这样</p>\n\n<pre><code>o.foo = null;</code></pre>\n\n<p>foo就被改变了,然后引起 TypeError: o.foo is not a function 的错误。</p>\n\n<h2>于是,Object.defineProperty 就进入我们视野</h2>\n\n<pre><code>var a = {};\nObject.defineProperty(a,'foo',{\n value:function(){\n console.log('foo..');\n }\n});</code></pre>\n\n<p>这样,foo就不会被改变,强行赋值(o.foo = null;)在严格模式下会出 TypeError: "foo" is read-only 错误</p>\n\n<h2>解读:</h2>\n\n<pre><code>Object.defineProperty 接受三个参数,\n第一个为对象,要添加属性的对象;\n第二个为属性的名字,字符串;\n第三个是一个对象,是对新添加的属性的一些设置,</code></pre>\n\n<p>重点讲第三个参数,它有以下设置项:</p>\n\n<pre><code>value:属性的值,可以是基本属性、对象、函数等等\n\nwritable:是否可以写,默认false,所以上面的foo是readonly\n\nconfigurable:是否可以修改设置,默认false,即定义过以后是否能再次修改设置项\n\nenumerable:是否能被枚举,关系到for in 或者 Object.keys的时候会不会被列出来\n\nget、set:设置了get、set就不能设置 writable 或 value,不然会报错,这是对属性读写时的钩子,下面有栗子\n\nvar b = 1;\nvar a = {};\n Object.defineProperty(a,'foo',{\n set:function(v){\n b = v;\n console.log('set');// you can do any thing here\n },\n get:function(){\n console.log('get'); // you can do any thing here\n return b;\n }\n });\na.foo; // print 'get' ,return 1\na.foo = 2; // print 'set' ,return 2\na.foo; // print 'get' ,return 2</code></pre>\n\n<h2>是不是很棒?</h2>",
"createdAt": 1453862040,
"modified": 1453862732
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "131",
"_score": 1.0,
"_source": {
"title": "FYSCU 2.7.1发布,让PHP更简单!",
"keywords": [
"php"
],
"content": "<p>零 . 虽然大版本号没变,但是里面包含重大更新,已经不兼容2.5版本,勉强升级,会带来麻烦。\n一 . 如果真要升级,可以使用新版创建一个项目,再将你的action和tpl覆盖进去,再修改一下配置文件即可。\n二 . 以下是本次更新内容:</p>\n\n<pre><code>1.controller和base,合成lib,强调这是框架核心模块\n2.组件各自维护一套配置文件,从global里分离出去,更加强调组件的即插即用\n3.CONFIG的调用模式改为静态方法CONFIG::get(),方便组件的配置注入\n4.修正了fytpl的一处配置引用常量\n5.去掉没什么用的url_base\n6.action_base里render增加返回输出内容\n7.action_base增加get_route方法返回路由信息\n8.cache文件缓存组件0.1.1</code></pre>\n\n<p>三 . 比较重要的是core的重新组合,以及彻底的组件化(每个组件独立维护配置文件,即插即用)。\n 另一个比较重要的是配置文件的调用方式,2.5版本也是因为这个变动而不能兼容。\n 增加了一个file_cache组件,详情参考项目主页。</p>\n\n<p>四 . 项目主页: http://fyscu.lanhao.name\n 目前PHP代码920行。</p>",
"createdAt": 1412875080,
"modified": 1415952054
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "306",
"_score": 1.0,
"_source": {
"title": "深究JS引用類型傳參",
"keywords": [
"js",
"javascript"
],
"content": "<h2>深究JS引用類型傳參</h2>\n\n<p>先看一段代碼:</p>\n\n<pre><code>var obj = {\n name: 'Tom'\n};\n\nfunction foo (o) {\n o.name = 'Jack';\n}\n\nfoo(obj);\n\nconsole.log(obj); //{ name: 'Jack' }</code></pre>\n\n<p>是不是說明,在 <code>foo</code> 調用的過程中, <code>obj</code>是按引用傳參的呢?</p>\n\n<p>我們不妨再看一段代碼:</p>\n\n<pre><code>var obj = {\n name: 'Tom'\n};\n\nfunction foo2 (o) {\n o = {\n name: 'Jack'\n };\n}\n\nfoo2(obj);\n\nconsole.log(obj); //{ name: 'Tom' }</code></pre>\n\n<p>仔細對比上面的代碼,到底參數 <code>o</code> 是不是以 <code>obj</code> 的引用的形式傳進去的呢?</p>\n\n<h2>解釋:</h2>\n\n<p>1.將一個對象賦值給一個變量的含義:</p>\n\n<p><code>var obj = { name: 'Tom' } ; </code></p>\n\n<p>這裡首先是通過 <code>{}</code>操作,建立這個對象的內存空間,然後將地址賦值給 <code>obj</code> ,這個很關鍵,反復讀三遍。</p>\n\n<p>2.當這個變量作為參數傳給一個函數</p>\n\n<p><code>foo(obj);</code></p>\n\n<p>正如上面所說,這個時候傳進去的,是一個對象的地址。</p>\n\n<p>所以,如果我們改變 <code>o.name</code>,實際上是改變 <code>obj.name</code>,</p>\n\n<p>於是最後打印出來的是 <code>{ name: 'Jack' }</code>。</p>\n\n<p><strong>相比之下</strong></p>\n\n<p>在 <code>foo2</code> 里,我們直接 <code>o = { name: 'Jack' }</code> 操作,參考第一條解釋,我們是給“Jack”建立了新的內存空間,然後把地址賦值給 <code>o</code>, 那這個時候<code>o</code>跟<code>obj</code>的關聯的斷開了,</p>\n\n<p>於是,<code>obj</code>還是原來的樣子 <code>{ name: 'Tom' }</code></p>",
"createdAt": 1490338140,
"modified": 1490338463
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "301",
"_score": 1.0,
"_source": {
"title": "PHP PSR2 与 PHPCS 相关",
"keywords": [
"php",
"psr2",
"phpcs"
],
"content": "<h2>PSR2 is ?</h2>\n\n<p><a href=\"https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-2-coding-style-guide.md\">github上一个中文介绍的项目</a></p>\n\n<h2>phpcs (php_codesniffer)</h2>\n\n<ul><li><p>Install : composer global require "squizlabs/php_codesniffer=*"</p></li><li><p>PSR-1 的程式出現在 1.3.5 ,PSR-2 出現在 1.4.0 </p></li><li><p>phpcs --config-set default_standard PSR2</p></li></ul>\n\n<h2>IDE</h2>\n\n<ul><li><p>sublime text <a href=\"https://my.oschina.net/u/130139/blog/290638\">参考配置</a></p></li><li><p>phpstorm </p><pre><code>preferences -> Editor -> Code Style -> PHP \n\n-> set from...(右上角) -> Predefined Style -> PSR1/PSR2</code></pre></li></ul>\n\n<h2>git hook</h2>\n\n<ul><li>安装 phpcs </li></ul>\n\n<pre><code>\tcomposer global require "squizlabs/php_codesniffer=*"</code></pre>\n\n<ul><li><p>Link</p><pre><code>ln -s ~/.composer/vendor/bin/phpcs /usr/local/bin/phpcs</code></pre></li><li><p>edit hook</p></li></ul>\n\n<p>1.</p>\n\n<pre><code>\tvi {project_root}/.git/hooks/pre-commit</code></pre>\n\n<p>2.</p>\n\n<pre><code> #!/bin/sh\n PHPCS_BIN=/usr/local/bin/phpcs\n PHPCS_CODING_STANDARD=PSR2\n PHPCS_FILE_PATTERN="\\.(php)$"\n\n\tFILES=$(git diff --name-only --cached)\n\n\tif [ "$FILES" == "" ]; then\n\t exit 0\n\tfi\n\n\tfor FILE in $FILES\n\tdo\n\t echo "$FILE" | egrep -q "$PHPCS_FILE_PATTERN"\n\t RETVAL=$?\n\t if [ "$RETVAL" -eq "0" ]\n\t then\n\n \t\tPHPCS_OUTPUT=$($PHPCS_BIN --standard=$PHPCS_CODING_STANDARD $FILE)\n \t\tPHPCS_RETVAL=$?\n\n \t\tif [ $PHPCS_RETVAL -ne 0 ];\n \t\tthen\n \t\techo $PHPCS_OUTPUT\n \t\texit 1\n \t\tfi\n\t\tfi\n\tdone\n\texit 0</code></pre>\n\n<p>3.</p>\n\n<pre><code> chmod +x {project_root}/.git/hooks/pre-commit</code></pre>\n\n<ul><li>all</li></ul>",
"createdAt": 1475168880,
"modified": 1475168930
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "293",
"_score": 1.0,
"_source": {
"title": "两种方式手写实现Promise(基本版)",
"keywords": [
"promise",
"javascript",
"JS"
],
"content": "<h1>Promise / deferred 分开的版本</h1>\n\n<pre><code>'use strict';\nlet myPromise = (deferred) => {\n this._resolved = null;\n this._rejected = null;\n this.then = (resolve,reject) => {\n switch (deferred.state){\n case 'pending':\n this._resolved = (typeof resolve === 'function') ? \n resolve \n : null;\n this._rejected = (typeof reject === 'function') ? \n reject \n : null;\n break;\n case 'rejected':\n (typeof reject === 'function') \n && reject.call(null,deferred.reason);\n break;\n case 'resolved':\n (typeof resolve === 'function') \n && resolve.call(null,deferred.result);\n break;\n }\n };\n};\n\nlet myDeferred = () => {\n let _self = this;\n this.state = 'pending';\n this.reason = null;\n this.result = null;\n this.promise = new myPromise(_self);\n\n this.resolved = (val) => {\n this.state = 'resolved';\n this.result = val;\n if(this.promise._resolved){\n this.promise._resolved.call(null,val);\n }\n };\n\n this.rejected = (err) => {\n this.state = 'rejected';\n this.reason = err;\n if(this.promise._rejected){\n this.promise._rejected.call(null,err);\n }\n };\n};</code></pre>\n\n<h3>USAGE</h3>\n\n<pre><code>const fs = require('fs');\nlet getFileSync = function (path) {\n\n let df = new myDeferred();\n\n fs.readFile(path, (e, r) => {\n if(e){\n \t df.rejected(e);\n }else{\n df.resolved(r);\n }\n });\n return df.promise;\n};\n\nlet r =getFileSync('./README.md');\n//pending\nr.then(\n //resolved\n (val) => console.log(val),\n\n //rejected\n (err) => console.log(err)\n);</code></pre>\n\n<h1>一体化的版本</h1>\n\n<pre><code>let myPromise = (fn) => {\n let _self = this;\n this.state = 'pending';\n this.result = null;\n this.reason = null;\n this.resolvedHandler = null;\n this.rejectedHandler = null;\n\n this.resolved = (val) => {\n _self.state = 'resolved';\n _self.result = val;\n if(typeof _self.resolvedHandler === 'function'){\n _self.resolvedHandler(val);\n }\n };\n\n this.rejected = (err) => {\n _self.state = 'rejected';\n _self.reason = err;\n\n if(typeof _self.rejectedHandler === 'function'){\n _self.rejectedHandler(err);\n }\n };\n\n this._promise = {};\n\n this._promise._p = _self;\n\n this._promise.then = (resolvedHandler, rejectedHandler) => {\n switch (_self.state){\n case 'pending':\n \t_self.resolvedHandler = (typeof resolvedHandler === 'function') ? \n resolvedHandler \n : null;\n \t_self.rejectedHandler = (typeof rejectedHandler === 'function') ? \n rejectedHandler \n : null;\n \tbreak;\n\n case 'resolved':\n \t(typeof resolvedHandler === 'function') \n && resolvedHandler(_self.result);\n \tbreak;\n case 'rejected':\n \t(typeof rejectedHandler === 'function') \n && rejectedHandler(_self.reason);\n }\n };\n\n fn(this.resolved,this.rejected);\n\n return this._promise;\n};</code></pre>\n\n<h3>USAGE</h3>\n\n<pre><code>const fs = require('fs');\n\nlet getFileSync = (path) => {\n return new myPromise( (resolved, rejected) => {\n fs.readFile(path, (e, r) => {\n if(!e){\n resolved(r);\n }else{\n rejected(e);\n }\n });\n });\n};\n\nlet r = getFileSync('./README.md');\n//pending\n\nr.then(\n //resolved\n (val) => console.log(val), \n //rejected\n (err) => console.log(err)\n);</code></pre>",
"createdAt": 1467022560,
"modified": 1467116983
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "288",
"_score": 1.0,
"_source": {
"title": "[转]阻止UPDATE语句没有添加WHERE条件的发生",
"keywords": [
"mysql",
"update"
],
"content": "<p>支持原创请访问:<a href=\"http://wing324.github.io/2016/05/01/%E5%A6%82%E4%BD%95%E5%9C%A8MySQL%E4%B8%AD%E9%98%BB%E6%AD%A2UPDATE%E8%AF%AD%E5%8F%A5%E6%B2%A1%E6%9C%89%E6%B7%BB%E5%8A%A0WHERE%E6%9D%A1%E4%BB%B6%E7%9A%84%E5%8F%91%E7%94%9F/\">原文地址</a></p>\n\n<p>如果在生产环境中使用UPDATE语句更新表数据,此时如果忘记携带本应该添加的WHERE条件,那么。。</p>\n\n<p>Oh,no…后果可能不堪设想。。。</p>\n\n<p>之前就遇到一个同事在生产环境UPDATE忘记携带WHERE条件。。\n于是只能从binlog日志中找到相关数据,然后去恢复。。宝宝当时表示心好累。。。\n那么有没有什么办法可以阻止这样的事情发生,又不使用任何的审核工具呢。。。\n办法当然是有的,请听我说~</p>\n\n<p>sql_safe_updates</p>\n\n<p>sql_safe_updates这个MySQL自带的参数就可以完美的解决我们的问题,并且该参数是可以在线变更的哦~\n当该参数开启的情况下,你必须要在UPDATE语句后携带WHERE条件,否则就会报出ERROR。。</p>\n\n<p>举个栗子</p>\n\n<pre><code># sql_safe_updates=0,即未开启\nroot@127.0.0.1 : test 07:58:34> set sql_safe_updates=0;\nQuery OK, 0 rows affected (0.00 sec)\n\nroot@127.0.0.1 : test 07:58:43> show variables like 'sql_safe_updates';\n+------------------+-------+\n| Variable_name | Value |\n+------------------+-------+\n| sql_safe_updates | OFF |\n+------------------+-------+\n1 row in set (0.00 sec)\n\nroot@127.0.0.1 : test 07:58:55> select * from t;\n+-------+\n| pd |\n+-------+\n| hello |\n| mysql |\n+-------+\n2 rows in set (0.00 sec)\n\nroot@127.0.0.1 : test 07:58:59> begin;\nQuery OK, 0 rows affected (0.00 sec)\n\nroot@127.0.0.1 : test 07:59:04> update t set pd='MySQL';\nQuery OK, 2 rows affected (0.00 sec)\nRows matched: 2 Changed: 2 Warnings: 0\n\nroot@127.0.0.1 : test 07:59:12> select * from t;\n+-------+\n| pd |\n+-------+\n| MySQL |\n| MySQL |\n+-------+\n2 rows in set (0.00 sec)\n\n# sql_safe_updates=1,即开启\nroot@127.0.0.1 : test 08:00:00> set sql_safe_updates=1;\nQuery OK, 0 rows affected (0.00 sec)\n\nroot@127.0.0.1 : test 08:00:11> show variables like 'sql_safe_updates';\n+------------------+-------+\n| Variable_name | Value |\n+------------------+-------+\n| sql_safe_updates | ON |\n+------------------+-------+\n1 row in set (0.00 sec)\n\nroot@127.0.0.1 : test 08:00:16> select * from t;\n+-------+\n| pd |\n+-------+\n| hello |\n| mysql |\n+-------+\n2 rows in set (0.00 sec)\n\noot@127.0.0.1 : test 08:00:25> begin;\nQuery OK, 0 rows affected (0.00 sec)\n\nroot@127.0.0.1 : test 08:00:27> update t set pd='MySQL';\nERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column</code></pre>\n\n<p>如上属的栗子所示,当参数sql_safe_updates开启的时候,UPDATE语句不携带WHERE条件将会爆出一个错误。。所以小心使用UPDATE语句是真的很重要哇。。。</p>",
"createdAt": 1462272480,
"modified": 1491557696
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "304",
"_score": 1.0,
"_source": {
"title": "总结一些最近写Command Line Tool 的一些心得",
"keywords": [
"node",
"命令行"
],
"content": "<blockquote><p>总结一些最近写Command Line Tool 的一些心得</p></blockquote>\n\n<h2>yargs</h2>\n\n<pre><code>npm install yargs --save</code></pre>\n\n<p><code>yargs</code>是优雅地接受命令行输入参数的模块,功能非常强大且易用,免去了很多重复的工作,一段代码足以证明:</p>\n\n<pre><code>const argv = require('yargs').argv; \n\n// argv 会自动接受所有输入参数 ,比如 demo_command -a hello\n\nconsole.log(argv.a); \n//hello</code></pre>\n\n<p>当然可以对 <code>a</code> 做更多的配置 </p>\n\n<pre><code>const yargs = require('yargs');\n\nlet argv = yargs.\n\t\toptions('a', {\n\t\t alias: 'api',\n\t\t default: 'all',\n\t\t }).argv;\n//这样可以给 argv.a设置一个默认值,同时有一个别名,如:demo --api hello</code></pre>\n\n<p>更多的配置方式看<a href=\"https://github.com/yargs/yargs\">github</a></p>\n\n<hr/>\n\n<h2>colors</h2>\n\n<pre><code>npm install colors --save</code></pre>\n\n<p><code>colors</code>是用来控制命令行输出文本颜色的, 还有更多功能如 <code>背景色</code>、<code>字体特效</code> 等我用得比较少,需要学全的请看<a href=\"https://github.com/Marak/colors.js\">github</a>。</p>\n\n<pre><code>//用起来也是特别简单,\nconst colors = require('colors');\n\n//输出绿色的hello , 黄色的 world\nconsole.log(colors.green('hello ') + colors.yellow('world'));</code></pre>\n\n<p>更多预设颜色:black、red、green、yellow、blue、magenta、cyan、white、gray、grey</p>\n\n<p>主要是用于命令行输出提示信息,如红色的<code>error message</code></p>\n\n<hr/>\n\n<h2>readline-sync</h2>\n\n<pre><code>npm install readline-sync --save</code></pre>\n\n<p><code>readline-sync</code>是同步获取用户输入的模块,主要用于带交互的基本输入。</p>\n\n<p>常见的有询问用户是否同意 ,同意输入<code>yes</code>, 或者需要用户填写什么信息之类。</p>\n\n<pre><code>const readline = require('readline-sync');\n\nlet name = readline.question('What is your name? ', {defaultInput: 'Tom'});\n\nconsole.log('Oh! I got it! You are '+name);</code></pre>\n\n<p>输出:</p>\n\n<pre><code>$ node test.js \nWhat is your name? Gay \nOh! I got it! You are Gay</code></pre>\n\n<p><code>readline-sync</code>的功能远远不止这样,还有很多交互方式,详情看<a href=\"https://github.com/anseki/readline-sync\">github</a></p>\n\n<hr/>\n\n<h2>关于目录的一些问题</h2>\n\n<h4>HOME</h4>\n\n<p><code>HOME</code>目录在Command Line 中也比较常用,可以让你生成一些本地文件,或者配置文件。\n<code>Linux</code> 和 <code>OSX</code>中,可以通过环境变量来获取。</p>\n\n<pre><code>process.env['HOME'];\n// 当前用户的目录</code></pre>\n\n<p>而<code>windows</code>的话,我就没研究了。</p>\n\n<h4>命令所在的源码目录</h4>\n\n<pre><code>__dirname\n\n当前执行的脚本的目录,不管你在哪个目录下调用,这个返回的都是源码所在目录</code></pre>\n\n<p>这个指的是你当前命令的执行文件源码的目录(或理解为 <code>安装目录</code>)。</p>\n\n<p>它的作用是,你可能需要从你开发的命令的源码里加载一些文件,或者模板。</p>\n\n<p>比如 一些框架的脚手架 ,需要生产代码 ,自然就是从脚手架的目录里copy一份模板。</p>\n\n<h4>当前目录</h4>\n\n<pre><code>process.cwd()</code></pre>\n\n<p>这个是指你输入命令的当前目录。</p>\n\n<p><strong>要活用以上两个目录,在写 Command Line Tool 的时候会经常用到。</strong></p>\n\n<h3>注意 EOL 的坑</h3>\n\n<pre><code>require('os').EOL</code></pre>\n\n<p>这是一个很方便的功能,它会抹平操作系统的差异性,返回一个换行符。\n在你打印信息的时候十分常用。</p>\n\n<p><strong>不过注意有一个坑</strong></p>\n\n<p>如果你需要生产一些文本文件,那你需要特别注意里面用的换行符。</p>\n\n<p>比如我在<code>linux</code>下生成的配置文件,弄到 <code>windows</code>下就解释不出来了。</p>",
"createdAt": 1484897520,
"modified": 1512093781
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "181",
"_score": 1.0,
"_source": {
"title": "Express全局跨域代码段",
"keywords": [
"node",
"express",
"跨域"
],
"content": "<p><pre><code>\napp.all('*', function(req, res, next) {\n res.header("Access-Control-Allow-Origin", "*");\n res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");\n res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");\n res.header("X-Powered-By",' 3.2.1');\n if(req.method=="OPTIONS") res.send(200);\n else next();\n});\n</code></pre>\n 通过上面代码设置全部路由都允许跨域,每个参数的含义,自行搜索。\n马克专用</p>",
"createdAt": 1414262558,
"modified": 1414262558
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "261",
"_score": 1.0,
"_source": {
"title": "Redis 的 GEO还没来到,也可以自己实现的 ",
"keywords": [
"redis",
"LBS",
"地理信息"
],
"content": "<p>听说GEO快要出来了,还没了解到具体原理,\n不过在这之前,我倒是用redis实现了基于地理位置的处理。</p>\n\n<h1>有点绕,有更好办法的欢迎指出。</h1>\n\n<p><strong>zset</strong> 、 <strong>sinter</strong></p>\n\n<h3>基础一</h3>\n\n<p>zset是set的一个特殊表现,它可以给每个成员附加一个score排序,从小到大。\nzset的一个灵活应用在于范围查询,比如你存储的member是uid,score是年龄,\n那你可以用 <code>ZRANGEBYSCORE key 18 22</code> 来查询18到22岁的uid集合</p>\n\n<h3>基础二</h3>\n\n<p>sinter 文档只看到对set的操作,它是取两个集合的交集,(有看到支持zset的请通知我一下)。\n比如,集合test1 = [ 1 , 2 , 3 ],集合test2 = [ 2 , 3 , 4], (test1、 test2是两个set,这里用数组表示)\n那么,sinter test1 test2 就会返回 [2,3]</p>\n\n<h3>实现</h3>\n\n<p>1.参考基础一,以 uid(用户) 或 place(地点) 作为member,<strong>经度</strong>作为 score 存储一个zset set1,\n2.同样,参考基础一,以 uid(用户) 或 place(地点) 作为member,<strong>纬度</strong>作为 score 存储一个zset set2,\n3.根据基础一的范围查询,分别查询两个zset在某个经度(纬度)范围内的结果,得到两个临时结果 <strong>无序set</strong>\n3.1 (把两个临时结果存放在 set 里,用完后自行删除)\n4.这两个临时set做交集,就能得到既在某经度范围内,也在某纬度范围内的坐标了</p>",
"createdAt": 1437448020,
"modified": 1462783579
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "28",
"_score": 1.0,
"_source": {
"title": "[翻译] M3U 和M3U8 详解_from wiki",
"keywords": [
"m3u8"
],
"content": "<p>文件格式\nm3u文件是用来描述一个或多个媒体文件地址的纯文本文件,通常以 M3U 或 m3u 作为扩展名。</p>\n\n<p>m3u文件里描述的最小单位(元素/行/记录),可以是一下三者之一:</p>\n\n<pre><code>1.一个文件的绝对路径\n2.相对于m3u文件的相对路径\n3.一个网络url</code></pre>\n\n<p>以#好开头的,是m3u的注释,而一些m3u扩展指令也是由#号开头</p>\n\n<p>m3u的一个常见用途是作为一个指向网络中一个流媒体的播放列表,比如在线视频、广播等等。</p>\n\n<p>你可以直接用文本编辑器编写一个m3u文件,但需要保存为 window-1252格式(ASCII的一个扩展集)。</p>\n\n<p>M3U指令的扩展</p>\n\n<pre><code>#EXTM3U 文件头,必须出现在第一行 如:略\n#EXTINF\t 引导信息,包含播放时间(时长)和标题 如:#EXTINF:191,Artist Name - Track Title</code></pre>\n\n<p>M3U8\n所谓的M3U8就是用unicode编写的M3U文件,这最初是用在IOS设备上播放http实时流的基础格式。</p>\n\n<p>例子1:\n这是一个在window系统下的 扩展m3u 文件。其中包含sample.mp3和Example.ogg两个媒体文件。\n123和321是文件时长,单位是秒,但如果这里用-1的话,表示的是一个媒体流,而不是真正的时长。\n接下来的是要显示的标题,用来描述下一行的这个媒体文件。</p>\n\n<hr/>\n\n<pre><code>#EXTM3U\n\n#EXTINF:123, Sample artist - Sample title\nC:\\Documents and Settings\\I\\My Music\\Sample.mp3\n\n#EXTINF:321,Example Artist - Example title\nC:\\Documents and Settings\\I\\My Music\\Greatest Hits\\Example.ogg</code></pre>\n\n<hr/>\n\n<p>例子2:\n下面这个例子是用m3u文件来指向一个给定的目录(比如U盘或者光驱)。\n这个m3u文件只能包含一行语句:就是指向的这个路径。\n这样,播放器就能自动播放这个目录下的全部文件。</p>\n\n<hr/>\n\n<pre><code>C:\\Music</code></pre>\n\n<hr/>\n\n<p>例子3\n这个例子当中我们使用了相对路径。m3u文件和媒体文件放在同一个目录下,\n而当你要把文件转移的时候,必须保持m3u文件和媒体文件的目录结构不改变。\n这种方式比较弹性,不必要求媒体文件永远放在一个固定的地方。\n还是刚才的文件,我们修改一下:</p>\n\n<hr/>\n\n<pre><code>#EXTM3U\n\n#EXTINF:123, Sample artist - Sample title\nSample.mp3\n\n#EXTINF:321,Example Artist - Example title\nGreatest Hits\\Example.ogg</code></pre>\n\n<hr/>\n\n<p>例子4:\n下面是一个全面的例子</p>\n\n<hr/>\n\n<pre><code>Alternative\\Band - Song.mp3\nClassical\\Other Band - New Song.mp3\nStuff.mp3\nD:\\More Music\\Foo.mp3\n..\\Other Music\\Bar.mp3\nhttp://emp.cx:8000/Listen.pls\nhttp://www.example.com/~user/Mine.mp3</code></pre>\n\n<hr/>\n\n<p>备注:\n· “Alternative”和“Classical”是m3u文件所在位置的子目录(一看就明白吧)\n· “Song”和“New Song”两个文件都在m3u文件所在位置的子目录里(外国人都那么细致)\n` “Stuff”这个文件就在m3u文件当前目录\n· "Foo"这个文件就在一个绝对路径,跟当前目录无关。\n· “Bar”这个文件,在当前目录的上一层的子目录下(拗口。。)\n· “Listen”这个是个Shoutcast在线地址(貌似是国外一个出名的在线播放产品?)\n· “Mine”是个纯粹的在线音频地址</p>\n\n<p>例子5:\nm3u同样支持嵌套引用</p>\n\n<hr/>\n\n<pre><code>AnotherPlayList.m3u</code></pre>\n\n<hr/>\n\n<p>例子6:\n(一些MP3标签的写法,暂不翻译)</p>\n\n<p>软件:\n支持m3u的播放器有很多,此处不表。</p>",
"createdAt": 1409795640,
"modified": 1429899910
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "7",
"_score": 1.0,
"_source": {
"title": "Express怎么获取用户IP",
"keywords": [
"node",
"express"
],
"content": "<p>如果是直接启动node程序的</p>\n\n<pre><code>req.connection.remoteAddress</code></pre>\n\n<p>如果是使用了nginx反向代理,在nginx相关配置地方加上</p>\n\n<pre><code>proxy_set_header X-Real-IP $remote_addr;\nproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</code></pre>\n\n<p>然后代码里使用</p>\n\n<pre><code>var headers = req.headers;\nconsole.log(headers['x-real-ip'] || headers['x-forwarded-for']);</code></pre>",
"createdAt": 1404330120,
"modified": 1410327385
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "67",
"_score": 1.0,
"_source": {
"title": "Javascript 闭包与作用域",
"keywords": [
"闭包"
],
"content": "<h4>1.Javascript的作用域是函数作用域而非块级作用域</h4>\n\n<pre><code>//C语言\n#include <stdio.h>\nvoid main()\n{\n int i=2;\n i--;\n if(i)\n {\n int j=3;\n }\n printf("%d/n",j);\t\t\n //use an undefined variable:j\n}</code></pre>\n\n<p>这是因为c中的作用域是块级的,j是在if后的{ }中定义的,所以无法访问,然而在js中会是什么情况?\n (function(){\n var i=1;\n if(i==1){\n var j=3;\n }\n console.log(j);\t //3\n })()</p>\n\n<p>在这里,j是可以访问的,也就是说在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的</p>\n\n<p>这里提及一句Javascript的作用域链(<code> scope chain </code>),每个函数 定义 时都会将他的作用域链定设为他定义的环境</p>\n\n<pre><code>function a(){\n function b(){\n //code\n }\n}</code></pre>\n\n<p>这段代码中,b的环境为a,a的环境为全局(<code>window</code>),在b中查找变量时会先搜索自身函数内部,如果不存在就去a的内部查找,还不存在就去全局中查找,若还是找不到就是undefined,这就构成一条链</p>\n\n<h4>2.Javascript中变量的作用域分为全局变量和局部变量</h4>\n\n<p>在函数内部可以访问全局变量和函数内的局部变量,而在函数外部访问不到函数内的变量,看代码</p>\n\n<pre><code>var p=11;\nfunction f1(){\n console.log(p);\n}\nf1(); //11\n\nfunction f1(){\n var p=11;\n}\nf1();\nconsole.log(p); //ReferenceError: p is not defined</code></pre>\n\n<p>通过这俩段代码可以理解全局变量和局部变量,但是定义局部变量时<code>一定要注意加上 var</code> ,如果不加上其实定义的是一个全局变量,看代码</p>\n\n<pre><code>function f1(){\n p=11;\n}\nf1();\nconsole.log(p); //11</code></pre>\n\n<h4>3.那如何访问函数内部的变量并对它进行操作呢?这里就需要用到闭包</h4>\n\n<p>先看看闭包的官方解释:<code> 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分</code></p>\n\n<p>看到这句户我不禁想问,这是个啥?</p>\n\n<p>后来参考了一些博客和《Javascript秘密花园》才开始理解,闭包大概就是函数内部的一个函数被外部调用,这样就可以调用内部变量了,比如下面这段</p>\n\n<pre><code>function f1(){\n var p=11;\n return {\n increment: function() {\n p++;\n },\n\n show: function() {\n alert(p)\n }\n }\n}\nvar f=f1();\nf.show();\t //11\nf.increment();\nf.show();\t //12</code></pre>\n\n<p>这里可以看到,f包含increment和show两个函数,而这两个函数是f1的内部函数所以可以访问p这个变量,在我理解,这里的increment和show就是f1()的两个闭包,用他们就可以从外部调用这个变量</p>\n\n<h4>4.闭包可以做些什么?</h4>\n\n<p>首先我觉得可以模拟<code>private</code>,就像上面那段代码,这个变量只能在这个函数内部访问,也只有使用了闭包才能访问</p>\n\n<p>第二,和Javascript的垃圾回收有关,这里我还不是很清楚,等到搞明白了再来补上</p>\n\n<p>5.这里有一个要注意的就是循环中使用闭包的问题,这里借用《<code>Javascript秘密花园</code>》里的一个例子</p>\n\n<pre><code>function f1(){\n for(var i = 0; i < 10; i++) {\n setTimeout(function() {\n console.log(i); \n }, 1000);\n }\n}\nf1();</code></pre>\n\n<p>这段代码输出的是10个10而不是期望的0到9,因为闭包内是对i的引用,然后函数执行时i已经变成了10,这里可以使用自执行的匿名函数</p>\n\n<pre><code>function f1(){\n for(var i = 0; i < 10; i++) {\n (function(e) {\n setTimeout(function() {\n console.log(e); \n }, 1000);\n })(i); \n }\n}\nf1();</code></pre>\n\n<p>这里的匿名函数将i作为参数,这里的e会有i的一个拷贝,而引用时是对e的引用,这就避免了上述的问题</p>",
"createdAt": 1410409980,
"modified": 1415952324
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "292",
"_score": 1.0,
"_source": {
"title": "[总结] ES6中的变量和作用域",
"keywords": [
"ES6",
"JS",
"javascript"
],
"content": "<p><a href=\"http://www.w3cplus.com/javascript/variables-and-scoping-in-es6.html?utm_source=tuicool&utm_medium=referral\">原文地址</a></p>\n\n<h3>通过let和const确定块作用域</h3>\n\n<p>使用 <code>let</code> 和 <code>const</code> 创建块作用域,他们声明的变量只存在块内。\n比如下面的示例, <code>let</code> 声明的变量 <code>tmp</code> 只存在于 <code>if</code> 语句块,也只有在 <code>if</code> 语句块内有效。</p>\n\n<pre><code>function func () {\n if (true) {\n let tmp = 123;\n }\n console.log(tmp); // ReferenceError: tmp is not defined\n}</code></pre>\n\n<p>相比之下,使用 <code>var</code> 声明的变量,在整个函数域内都有效:</p>\n\n<pre><code>function func () {\n if (true) {\n var tmp = 123;\n }\n console.log(tmp); // 123\n}</code></pre>\n\n<p>块作用域也可以存在于整个函数内:</p>\n\n<pre><code>function func () {\n let foo = 5;\n if (...) {\n let foo = 10;\n console.log(foo); // 10\n }\n console.log(foo); // 5\n}</code></pre>\n\n<h3>const创建不可变的变量(常量)</h3>\n\n<p>let 创建的变量是可变的:</p>\n\n<pre><code>let foo = "abc";\nfoo = "def";\nconsole.log(foo); // def</code></pre>\n\n<p>而使用 <code>const</code> 创建的变量是不可变量,其是一个常量:</p>\n\n<pre><code>const foo = "abc";\nfoo = "def"; // TypeError</code></pre>\n\n<p>注意: <code>const</code> 并不影响一个常数是否可变,如果一个常数是一个对象,那它总是一个引用对象,但仍然可以改变对象的本身(如果它是可变的)。</p>\n\n<pre><code>const obj = {};\nobj.prop = 123;\nconsole.log(obj.prop); // 123\nobj = {}; // TypeError</code></pre>\n\n<p>如果你想让 <code>obj</code> 是一个真正的常数,可以使用 <code>freeze</code> 方法 来冻结其值:</p>\n\n<pre><code>const obj = Object.freeze({});\nobj.prop = 123; // TypeError</code></pre>\n\n<h3>循环体内的 const</h3>\n\n<p>一旦通过 <code>const</code> 创建的变量它就是一个常量,它是不能被改变的。但这也并不意味着你不能重新给其设置一个新值。例如,可以通过一个循环来操作:</p>\n\n<pre><code>function logArgs (...args) {\n for (let [index, elem] of args.entries()) {\n const message = index + '. ' + elem;\n console.log(message);\n }\n}\nlogArgs("Hello", "everyon");</code></pre>\n\n<p>输出的结果</p>\n\n<pre><code>0. Helloe\n1. everyone</code></pre>\n\n<h3>什么应该使用 let ,什么时候应该使用 const</h3>\n\n<p>如果你想改变一个变量保存其原始值,你就不能使用 <code>const</code> 来声明:</p>\n\n<pre><code>const foo = 1;\nfoo++; // TypeError</code></pre>\n\n<p>然而,你可以使用 <code>const</code> 声明变量,来引用可变量的值:</p>\n\n<pre><code>const bar = [];\nbar.push("abc"); // array是一个可变的</code></pre>\n\n<p>我还在仔细考虑使用什么方式才是最好的方式,但是目前情况使用的都是像前面的示例,因为 <code>bar</code> 变量是可变的。我使用 <code>const</code> 表明变量和值是不可变的:</p>\n\n<pre><code>const EMPTY_ARRAY = Object.freeze([]);</code></pre>\n\n<h3>暂时性死区</h3>\n\n<p>使用 <code>let</code> 或 <code>const</code> 声明的变量有一个所谓的暂时性死区(<code>TDZ</code>):当进入作用域范围,它就不能接受( get 或 set )访问,直到其被声明。</p>\n\n<p>我们来来看一个有关于 <code>var</code> 变量的生命周期,它没有暂时性死区:</p>\n\n<ul><li>当 <code>var</code> 声明了一个变量,其就有一个存储空间(创建一个所谓的绑定)。变量就初始化了,其默认值为 <code>undefined</code></li><li>当执行的范围到达声明处,变量设置为指定的值(如果有赋值的话),如果变量没有赋值,其值仍然是 <code>undefined</code></li></ul>\n\n<p>通过 <code>let</code> 声明变量存在暂时性死区,这意味着他们的生命周期如下:</p>\n\n<ul><li>当使用 let 创建一个变量,其就有一个块作用域,也具有一个存储空间(也就是创建一个所谓的绑定)。其值仍未初始化变量\n获取或设置一个未初始化的变量,得到一个错误ReferenceError</li><li>当执行范围内到达声明的变量处,如果有赋值的话,变量的初始值为指定的初始化值。如果没有,变量的值仍为 undefined</li></ul>\n\n<p>使用 <code>const</code> 声明的变量工作类似于 <code>let</code> ,但它必须要有一个初始化值,而且不能被改变。</p>\n\n<p>在一个<code>TDZ</code>内,通过 <code>if</code> 语句秋设置或获取一个变量将会报错:</p>\n\n<pre><code>if (true) { // TDZ开始\n // 未初始化tmp变量\n tmp = "abc"; // ReferenceError\n console.log(tmp); // ReferenceError\n let tmp; // TDZ结束,tmp已初始化,其初始值为undefined\n console.log(tmp); // undefined\n tmp = 123;\n console.log(tmp); // 123\n}</code></pre>\n\n<p>下面的例子演示了死区是时间(基于时间),而不是空间(基于位置):</p>\n\n<pre><code>if (true) { // 进入新作用域,TDZ开始\n const func = function () {\n console.log(myVar); // OK\n }\n //在TDZ内访问myVar,会引起ReferenceError错误\n let myVar = 3; // TDZ结束\n func (); // 调用外面的TDZ\n}</code></pre>\n\n<h3>typeof和TDZ</h3>\n\n<p>一个变量在难以接近TDZ时,这也意味着你不能使用 <code>typeof</code> :</p>\n\n<pre><code>if (true) {\n console.log(typeof tmp); // ReferenceError\n let tmp;\n}</code></pre>\n\n<p>在实践中我不认为这是一个问题,因为你不能有条的通过 <code>let</code> 声明变量范围。相反,你可以使用 <code>var</code> 声明变量,而且可以通过 window 创建一个全局变量:</p>\n\n<pre><code>if (typeof myVarVariable === 'undefined') {\n // `myVarVariable` does not exist => create it\n window.myVarVariable = 'abc';\n}</code></pre>\n\n<h3>循环头中的 <code>let</code></h3>\n\n<p>在循环中,你可以通过 <code>let</code> 声明变量,为每次迭代重新绑定变量。比如在 <code>for</code> 、 <code>for-in</code> 和 <code>for-of</code> 循环中。</p>\n\n<p>看起来像下面:</p>\n\n<pre><code>let arr = [];\nfor (let i = 0; i < 3; i++) {\n arr.push(() = > i);\n}\nconsole.log(arr.map(x => x())); // [0,1,2]</code></pre>\n\n<p>相比之下,使用 <code>var</code> 声明的变量将在整个循环中都可以工作:</p>\n\n<pre><code>let arr = [];\nfor (var i = 0; i < 3; i++) {\n arr.push(() => i);\n}\nconsole.log(arr.map(x => x())); // [3,3,3]</code></pre>\n\n<p>每次迭代得到一个新的绑定似乎有些奇怪,但当你使用循环创建功能(比如回调事件处理),它显得就非常有用。</p>\n\n<h2>参数</h2>\n\n<h3>参数和局部变量</h3>\n\n<p>如果使用 <code>let</code> 声明变量,它有一个相同的名称,称作参数。静态加载会出错:</p>\n\n<pre><code>function func (arg) {\n let arg; // Uncaught SyntaxError: Identifier 'arg' has already been declared\n}</code></pre>\n\n<p>同样的,将其放在一个作用块里:</p>\n\n<pre><code>function func (arg) {\n {\n let arg; // undefined\n }\n} </code></pre>\n\n<p>相比之下,使用 <code>var</code> 声明一个和参数相同变量,其作用范围在同一个范围内:</p>\n\n<pre><code>function func (arg) {\n var arg;\n}</code></pre>\n\n<p>或者</p>\n\n<pre><code>function func (arg) {\n {\n var arg;\n }\n}</code></pre>\n\n<h3>参数默认值和<code>TDZ</code></h3>\n\n<p>如果 参数有默认值 ,他们会当作一系列的 <code>let</code> 语句,而且存在<code>TDZ</code>。</p>\n\n<pre><code>// OK: `y` accesses `x` after it has been declared\nfunction foo(x=1, y=x) {\n return [x, y];\n}\nfoo(); // [1,1]\n\n// Exception: `x` tries to access `y` within TDZ\nfunction bar(x=y, y=2) {\n return [x, y];\n}\nbar(); // ReferenceError</code></pre>\n\n<h3>默认参数不知道其自身的范围</h3>\n\n<p>参数默认值的范围是独立于其自身范围。这意味着内部定义的方法或函数参数的默认值不知道其内部的局部变量:</p>\n\n<pre><code>let foo = 'outer';\nfunction bar(func = x => foo) {\n let foo = 'inner';\n console.log(func()); // outer\n}\nbar();</code></pre>\n\n<h3>全局对象</h3>\n\n<p>JavaScript全局对象(浏览器中的window,Node.js中的global)存在的问题比他的特性多,尤其是性能。这也是为什么ES6中不引用的原因。</p>\n\n<p>全局对象的属性都是全局变量,在全局作用域下都有效,可以通过 <code>var</code> 或 <code>function</code> 方式声明。</p>\n\n<p>但现在全局变量也不是全局对象。在全局作用域下,可以通过 <code>let</code> 、 <code>const</code> 或者 <code>class</code> 声明。</p>\n\n<h3>函数声明和class声明</h3>\n\n<p>function 声明:</p>\n\n<ul><li>像 let 一样,声明的是一个块作用域</li><li>像 var 一样,在全局对象创建属性(全局作用域)</li><li>存在生命提升:独立于一个函数声明中提到它的范围,它总是存在于开始时创建的范围内</li></ul>\n\n<p>下面的代码演示了函数声明的提升:</p>\n\n<pre><code>{ // Enter a new scope\n\n console.log(foo()); // OK, due to hoisting\n function foo() {\n return 'hello';\n }\n}</code></pre>\n\n<h4>类声明:</h4>\n\n<ul><li>是一个块作用域</li><li>不能在全局对象上创建属性</li><li>不存在生命提升</li></ul>\n\n<p><code>class</code> 不存在生命提升可能令人惊讶,那是因为其存在于引擎下,而不是一个函数。这种行为的理由是,他们扩展表达式。这些表达式在适当的时间内被执行。</p>\n\n<pre><code>{ // Enter a new scope\n\n const identity = x => x;\n\n // Here we are in the temporal dead zone of `MyClass`\n let inst = new MyClass(); // ReferenceError\n\n // Note the expression in the `extends` clause\n class MyClass extends identity(Object) {\n }\n}</code></pre>",
"createdAt": 1466590020,
"modified": 1466592711
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "296",
"_score": 1.0,
"_source": {
"title": "php就不能认真一点对待数组?!",
"keywords": [
"php",
"array",
"json"
],
"content": "<blockquote><p>php 数组,对象混搭的现象由来已久,不过小心一点也就可以了,但是有些地方,真的是坑死你不偿命</p></blockquote>\n\n<h3>黑 is cheap ,show you my code</h3>\n\n<pre><code>$arr = [\n 0 => 'a',\n 1 => 'b',\n 2 => 'c',\n];\necho json_encode($arr);\n\n// ["a","b","c"]</code></pre>\n\n<p>OK</p>\n\n<p>没有任何问题,但是……</p>\n\n<pre><code>$arr = [\n //0 => 'a',\n 1 => 'b',\n 2 => 'c',\n];\necho json_encode($arr);\n\n// {"1":"b","2":"c"}</code></pre>\n\n<h2>WHAT THE FUCK!!</h2>",
"createdAt": 1467868413,
"modified": 1467868413
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "294",
"_score": 1.0,
"_source": {
"title": "当我们说 Promise ,我们在说什么?(刨根向)",
"keywords": [
"promise",
"javascript"
],
"content": "<p>当我要组织文章内容的时候,我感到十分的吃力。</p>\n\n<p>这是源于一个困惑:我们现在是否还需要探讨什么是<code>Promise </code>?</p>\n\n<p>我们很容易就能 “使用” <code>Promise </code>,已经有很多优秀的模块实现了不同标准的<code>Promise </code>。\n而随着ES6原生 <code>Promise </code> 的落实,我们更容易写出 <code>Promise </code> 风格的异步代码。</p>\n\n<pre><code>// ES6 下的原生Promise\nvar httpGet = (url) => {\n return new Promise( (resolved,rejected)=>{\n request.get(url,(err,res)=>{\n if(!err){\n resolved(res);\n }else{\n rejected(err);\n }\n });\n });\n}\n\nhttpGet('http://abc.com').then(...);</code></pre>\n\n<p>但是,治学这东西,知其然,还要知其所以然。</p>\n\n<p>尤其是现在很多声音还存在对 <code>Promise </code> 的曲解的情况下。</p>\n\n<h3>那么,Promise到底是什么东西?</h3>\n\n<p>有一种说法是用了<code>Promise </code>就不需要callback的写法了,实际上是不是这样,我们看一段代码</p>\n\n<pre><code>func(arg ,(err,ret)=>{\n //pass\n});\n\nfunc(arg).then( (err,ret)=>{\n //pass\n});</code></pre>\n\n<p>实际上只是callback写的位置不一样而已,并没有什么实际的改变。</p>\n\n<p><strong>是不是这样呢?我们层层深入地理解一下 Promise 到底是什么。</strong></p>\n\n<p>对于上面这个问题, <code>Promise </code> 是不是只是把callback换个地方写呢?</p>\n\n<p>我从《深入浅出Nodejs》 4.3.2 中的一段话收到了启发</p>\n\n<blockquote><p>上面的异步调用中,必须严谨地设置目标。那么是否有一种先执行异步调用,延迟传递处理的方式呢?</p></blockquote>\n\n<p>说的就是传统的callback写法,必须在异步调用的时候,明确地声明回调处理的逻辑。</p>\n\n<pre><code>httpGet('/api',{\n success:onSuccess,\n error:onError\n});</code></pre>\n\n<p>就是说,你必须明确指明了异步调用结束时的各种回调,然后才会执行异步操作</p>\n\n<pre><code>(声明异步操作)---> (注册回调) ---> (执行异步操作)</code></pre>\n\n<p>反过来看看 <code>Promise </code> 是不是就不一样呢?先看一段代码</p>\n\n<pre><code>var p = httpGet('/api');\n\np.then(onSuccess , onError);</code></pre>\n\n<p>你猜到底在哪一步发起了http请求?</p>\n\n<p>正如你猜测的一样,在 <code>p</code> 初始化的时候,这个异步操作就执行了。\n所以对于<code>Promise </code>来说,流程是这样的</p>\n\n<pre><code>(声明异步操作)---> (执行异步操作) ---> (注册回调)</code></pre>\n\n<p> >原来真的存在一种方法,先执行异步调用,延迟传递处理的方式。这就是<code>Promise </code>与传统callback一个很显著的区别。</p>\n\n<h3>状态机</h3>\n\n<p>如果 <code>Promise </code> 只是对callback在逻辑顺序及书写方式上面的一点改动的话,</p>\n\n<p>那你就小看它了。</p>\n\n<p>有没有想过,为什么 <code>Promise </code> 能先执行异步操作,再指明回调逻辑呢?下面这段代码又会如何?</p>\n\n<pre><code>var p = httpGet('/api');\n\n// do something\n\np.then(onSuccess , onError);\n\n// do something\n\np.then(onSuccess , onError);</code></pre>\n\n<p>请问,第二个<code>then</code>是否正常执行 ?</p>\n\n<h4>Promise 下的状态</h4>\n\n<p> <code>Promise </code> 定义了其内部的几个状态</p>\n\n<ul><li>pending</li><li>resolved (fullfilled)</li><li>rejected </li></ul>\n\n<p><code>Promise </code> 初始化后 内部状态为 <code>pending</code> ,然后开始执行异步操作,\n当异步操作完成,内部状态转换为 <code>resolved</code> 或 <code>rejected</code> (失败时)。</p>\n\n<p>一旦状态改变,就会被固化,不会再改变。</p>\n\n<p>所以就很好解释,一个状态已经确定的 <code>Promise </code> ,无论你调动多少次<code>then</code>,它都会返回正确且唯一的结果,因为 <code>Promise </code>的结果,是完全依赖它自己的内部状态。</p>\n\n<h4>这个时候我们有必要说一下<code>then</code> 方法到底做了什么?</h4>\n\n<p>不去深入探究的话,你大概会认为<code>then</code>只是注册了一系列回调函数。</p>\n\n<p>其实 <code>then</code> 除了注册回调函数,还会检查 <code>Promise </code> 状态,只要不是<code>pending</code>状态,就回调用相应状态的回调。</p>\n\n<p>同样的,当状态改变的时候,也会检查对应状态是否有已经绑定的回调函数,再按照<code>Promise </code>的方式去执行回调。</p>\n\n<p>正因为这种机制,才能真正实现了<code>Promise </code>的异步操作与回调声明分离,并且通过维护状态变化,更好地控制异步操作结果中的不同情况。</p>\n\n<blockquote><p>注:为了降低理解成本,本文的实现里一个状态默认只处理一个回调函数</p></blockquote>\n\n<h3>一字长蛇阵</h3>\n\n<p>以上便是一个独立的 <code>Promise</code> 的运行机理,在这之上灵活运用才是<code>Promise</code>的终极玩法。</p>\n\n<p>那么我们经常听到的用 <code>Promise</code> 解决回调地狱,是怎么回事呢?看看最终代码:</p>\n\n<pre><code>loadPic('/path/of/picture.jpg')\n .then(...) //图片压缩\n .then(...) //生成缩略图\n .then(...); //存储到指定地方</code></pre>\n\n<p>想想用<code>callback</code>的时候你是怎么写的。</p>\n\n<p>它的原理就是,每个<code>then</code>的 resolved 部分,返回一个新的 <code>Promise</code>,\n这么一来就能继续 <code>then</code> 下去,只要每一个环节都遵守<code>Promise</code>规范,就能将一个回调地狱梳理成<strong>串行</strong>的<strong>链式调用</strong>。</p>\n\n<h4>最后附上两种手写 <code>Promise</code> 的参考</h4>\n\n<blockquote><p>http://www.lanhao.name/article/293</p></blockquote>\n\n<p>(未完)</p>",
"createdAt": 1467022980,
"modified": 1513778792
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "250",
"_score": 1.0,
"_source": {
"title": "[模块分享] nodemon",
"keywords": [
"node",
"nodemon"
],
"content": "<h1>http://nodemon.io/</h1>\n\n<pre><code>nodemon 可以在node程序开发阶段,自动监测代码变化,自动重启程序,\n让你可以像写php一样,保存一下,刷新页面就有效果。</code></pre>\n\n<h1>安装</h1>\n\n<p><code>npm install nodemon -g</code></p>\n\n<pre><code>安装在全局,所有项目都能用。</code></pre>\n\n<h1>使用</h1>\n\n<pre><code>nodemon app.js 代替原来的 node app.js\n值得注意的是,nodemon是监控当前启动nodemon的目录,\n所以,如果你在 / 下启动 nodemon /path/of/app.js\n它将监控整个 / 的变化。细思极恐</code></pre>\n\n<h1>建议</h1>\n\n<pre><code>建议启动nodemon时,加上 -i 参数,忽略掉views目录和public目录,\nnodemon app.js -i views -i public \n-i 参数表示忽略指定目录或文件的变化。\n这样做的好处是,单纯改改html、css、js这种文件的时候,不用自动重启,\n导致session丢失的问题。</code></pre>",
"createdAt": 1431498404,
"modified": 1431498404
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "234",
"_score": 1.0,
"_source": {
"title": "node 对中文字符MD5加密的问题",
"keywords": [
"node",
"md5"
],
"content": "<pre><code>//错误做法,不支持中文\nfunction(data){\n var md5sum = crypto.createHash('md5');\n md5sum.update(data);\n data = md5sum.digest('hex');\n return data;\n};\n\n\n\n//正确做法,支持中文\nfunction(data){\n var Buffer = require("buffer").Buffer;\n var buf = new Buffer(data);\n var str = buf.toString("binary");\n var md5sum = crypto.createHash('md5');\n md5sum.update(str);\n data = md5sum.digest('hex');\n return data;\n};</code></pre>",
"createdAt": 1425473580,
"modified": 1429897776
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "275",
"_score": 1.0,
"_source": {
"title": " 详解spl_autoload_register()函数",
"keywords": [
"php",
"autoload",
"oop"
],
"content": "<pre><code>在了解这个函数之前先来看另一个函数:__autoload。 </code></pre>\n\n<p>一、__autoload </p>\n\n<p>这是一个自动加载函数,在PHP5中,当我们实例化一个未定义的类时,就会触发此函数。看下面例子: </p>\n\n<pre><code>printit.class.php \n\n<?php \n\nclass PRINTIT { \n\n function doPrint() {\n echo 'hello world';\n }\n}\n?> \n\nindex.php \n\n<?\nfunction __autoload( $class ) {\n $file = $class . '.class.php'; \n if ( is_file($file) ) { \n require_once($file); \n }\n} \n\n$obj = new PRINTIT();\n$obj->doPrint();\n?></code></pre>\n\n<p>运行index.php后正常输出hello world。在index.php中,由于没有包含printit.class.php,在实例化printit时,自动调用__autoload函数,参数$class的值即为类名printit,此时printit.class.php就被引进来了。 </p>\n\n<p>在面向对象中这种方法经常使用,可以避免书写过多的引用文件,同时也使整个系统更加灵活。 </p>\n\n<p>二、spl<em>autoload</em>register() </p>\n\n<p>再看spl<em>autoload</em>register(),这个函数与__autoload有与曲同工之妙,看个简单的例子: </p>\n\n<pre><code><?\nfunction loadprint( $class ) {\n $file = $class . '.class.php'; \n if (is_file($file)) { \n require_once($file); \n } \n} \n\nspl_autoload_register( 'loadprint' ); \n\n$obj = new PRINTIT();\n$obj->doPrint();\n?></code></pre>\n\n<p>将<strong>autoload换成loadprint函数。但是loadprint不会像</strong>autoload自动触发,这时spl<em>autoload</em>register()就起作用了,它告诉PHP碰到没有定义的类就执行loadprint()。 </p>\n\n<p>spl<em>autoload</em>register() 调用静态方法 </p>\n\n<pre><code><? \n\nclass test {\n public static function loadprint( $class ) {\n $file = $class . '.class.php'; \n if (is_file($file)) { \n require_once($file); \n } \n }\n} \n\nspl_autoload_register( array('test','loadprint') );\n//另一种写法:spl_autoload_register( "test::loadprint" ); \n\n$obj = new PRINTIT();\n$obj->doPrint();\n?></code></pre>",
"createdAt": 1452835440,
"modified": 1452835501
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "237",
"_score": 1.0,
"_source": {
"title": "[备忘]两条命令升级node版本",
"keywords": [
"node"
],
"content": "<p><code> npm install -g n </code>\n安装 n (神器)</p>\n\n<p><code> n stable </code>\n自动升级到最新版</p>\n\n<p>过程中可能需要权限,最好用sudo运行</p>",
"createdAt": 1427944500,
"modified": 1427944591
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "290",
"_score": 1.0,
"_source": {
"title": "快速体验redis地理信息API",
"keywords": [
"redis",
"lbs",
"geo"
],
"content": "<p>近日 redis 发布了3.2版本,期待已久的GEO Api终于能够使用,我们第一时间体验一下</p>\n\n<h3>GEOADD</h3>\n\n<blockquote><p>GEOADD key longitude latitude member [longitude latitude member ...]</p></blockquote>\n\n<p>例子:</p>\n\n<pre><code>GEOADD user_location 103.1123 30.676 "Tom"\n参数讲解:\nGEOADD 添加地理信息\nuser_location 定义一个地理信息点的集合,叫做 用户位置\n103.1123 经度,X坐标\n30.676 纬度,Y坐标\n"Tom" 名字,用户名之类\n(后3个参数作为一组信息,可以无线重复多个,一次录入多个点)\n比如:\nGEOADD user_location 103.1123 30.676 "Tom" 111.27 44.566 "Jack"</code></pre>\n\n<blockquote><p>注意的是,很多时候我们看到的经纬度单位 <strong>度|分|秒</strong>,GEOADD里要转换成小数。\n同时,有效的经度从-180度到180度,有效的纬度从-85.05112878度到85.05112878度。\n所以,地球两极是处理不了的。</p><p>具体原因是,GEO将球形转换成平面的XY矩形,极点无法转化。\n(参考 EPSG:900913 / EPSG:3785 / OSGEO:41001 )</p></blockquote>\n\n<h3>GEODIST</h3>\n\n<blockquote><p>GEODIST key member1 member2 [unit]</p></blockquote>\n\n<p>例子:</p>\n\n<pre><code>GEODIST user_location Jack Tom km\n参数讲解:\nGEODIST 计算两点距离\nuser_location 地理信息集合,目前只能计算同一个集合下的点\nJack Tom 两个点的名字\nkm 单位,m 表示米,km 表示千米,mi 表示英里,ft 表示英尺。\n返回:\n"1701.7446" 表示两点距离1701.74 km</code></pre>\n\n<blockquote><p>GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。</p></blockquote>\n\n<h3>附近的人 GEORADIUS , GEORADIUSBYMEMBER</h3>\n\n<p> >GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]</p>\n\n<pre><code>例子:\nGEORADIUS user_location 100 35 1000 km \n表示从指定集合里,查找 (100,35)这个点附近1000km的点,\n返回: Tom ,说明Jack不在1000km内。\n\n参数说明:\nuser_location 指定的地理信息集合\n100 35 \t经纬度\n1000\t距离\nkm\t\t距离单位,m|km|ft|mi \n以下可选开关:\nWITHCOORD\t返回坐标\nWITHDIST\t返回距离\nWITHHASH\t返回GeoHash\nCOUNT count\t表示只返回多少个结果,如:COUNT 10 \nASC\t\t按距离正序\nDESC\t按距离倒序\n\n所以,假如是要获取附近1公里的人,就是\nGEORADIUS user_location {X} {Y} 1 km WITHDIST ASC</code></pre>\n\n<blockquote><p>值得注意的是,使用 WITHDIST 来返回距离时,使用的单位和radius的单位是一致的,所以,尽量使用小单位,给自己更多的操作空间。</p></blockquote>",
"createdAt": 1462863851,
"modified": 1462863851
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "61",
"_score": 1.0,
"_source": {
"title": "软件设计七大原则(二):里氏代换原则",
"keywords": [
"设计模式"
],
"content": "<pre><code>如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换称o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型.</code></pre>\n\n<h2>解读:</h2>\n\n<pre><code>公孙龙曰:白马非马。\n就是违法这个原则的经典例子。正确的应该是 白马是马,马非白马。\n\n用在软件设计中的含义就是,子类要全部实现基类的属性和方法,是全部。\n换言之,基类能做到的东西,子类必须全部做到,\n任何使用基类的地方,替换成其任意一个子类,都能正常运作。\n\n就好比,“蒋航”这个实体,具备noob的技能;\n那么“管理员蒋航”也必须实现noob技能,\n在“蒋航”施展noob技能的地方,换了“管理员蒋航”,通用生效,\n重要的事,我们根本擦觉不了这种替换。</code></pre>\n\n<hr/>\n\n<p>未完待续,感谢蒋航友情演出。</p>",
"createdAt": 1410260640,
"modified": 1429899786
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "53",
"_score": 1.0,
"_source": {
"title": "马克一下,QQ头像接口",
"keywords": [
"qq"
],
"content": "<p><img alt=\"请输入图片描述\" src=\"http://q1.qlogo.cn/g?b=qq&s=100&nk=540590988\"/></p>\n\n<p><code> http://q1.qlogo.cn/g?b=qq&s=100&nk={号码}</code></p>",
"createdAt": 1410017640,
"modified": 1410097303
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "90",
"_score": 1.0,
"_source": {
"title": "【JS】为什么输出444",
"keywords": [
"js",
"javascript",
"异步"
],
"content": "<h2>首先感谢大佳提出这个很典(dou)型(bi)的问题,对已理解异步是很有帮助的,首先看题。</h2>\n\n<pre><code>for(var a=1;a<=3;a++){\n setTimeout(function(){\n console.log(a);\n },0);\n}</code></pre>\n\n<p>大佳一定迫不及待了,我们先看一下<code>setTimeout</code> 。</p>\n\n<pre><code>setTimeout(code,millisec)</code></pre>\n\n<p><code>code</code> 要执行的代码,可以用高阶函数传入。\n<code>millisec</code> 毫秒数,就是多少时间以后 执行code</p>\n\n<h3>这个操作会发起一个异步,意在<code>millisec</code> 毫秒后执行<code>code</code>。</h3>\n\n<p>OK,你听我说完,\n好像没什么问题,1,2,3, 输出很明确是不是?</p>\n\n<h1>大佳你错了!</h1>\n\n<p>这个连DSG都懂了,答案是 333,哦不,444 。</p>\n\n<hr/>\n\n<p>割割割割割割割割割割割割割割割割割割割割割割割割</p>\n\n<hr/>\n\n<h1>接下来就是见证JJ的时刻了。</h1>\n\n<p>先说一下JS执行任务的模式,JS是单线程的,每次只能有一个任务执行,\n就好比一个队列,如图:\n<code> ----【任务4】【任务3】【任务2】【任务1】---> </code>\n任何新的任务,都会进到队伍末尾,一个个等待执行,\n当然就包括<code>时机成熟的异步回调</code>。</p>\n\n<p>而在你发起循环的时候,每一个循环已经进到队列了,于是呼,实际情况是这样的:\n<code> ----【a++然后发起异步】【a++然后发起异步】【a++然后发起异步】---> </code>\n然后呢,异步会等到它的时间(<code>millisec</code>),进入队列,于是乎:\n<code> ----【log】【log】【log】【a++然后发起异步】【a++然后发起异步】【a++然后发起异步】---> </code></p>\n\n<h1>答案已经很明显!</h1>\n\n<p>至于你问,这个延时是0啊,我只能告诉你,那不是真正的<code>0</code>,下一个循环进入队列的间隔,也是很短,至于为什么 下一个循环能更快进入队列,我们下一节再讲。</p>",
"createdAt": 1410544560,
"modified": 1415952121
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "297",
"_score": 1.0,
"_source": {
"title": "一些笔记 , Elasticsearch",
"keywords": [
"elasticsearch",
"es"
],
"content": "<ul><li>开始</li></ul>\n\n<p>安装</p>\n\n<pre><code>访问ES官方网站\nhttps://www.elastic.co/downloads/elasticsearch\n获取最新版,当前本班2.1.1 。\nES对运行环境唯一要求为JAVA 1.6以上,\n同样推荐官方最新版。(www.java.com)</code></pre>\n\n<p>配置</p>\n\n<pre><code>ES的配置文件 ./config/elasticsearch.yml\nnetwork.host: 0.0.0.0 #地址绑定\n\nindex.number_of_shards: 10 #默认主分片个数\nindex.number_of_replicas: 0 #默认从分片个数</code></pre>\n\n<p>启动</p>\n\n<pre><code>ES解压即用,\n./bin/elasticsearch -d \n\n-d 参数表示在后台运行,注意ES不允许使用root用户运行,\n所以你需要为ES专门分配用户,并且确保该用户对ES的数据\n目录、日志目录具有写入权限。\n默认数据目录在ES目录下的data</code></pre>\n\n<ul><li>运行时</li></ul>\n\n<p>head插件</p>\n\n<pre><code>安装方式参看\n./bin/plugin install mobz/elasticsearch-head\n\n访问地址:\nhttp://hostname:9200/_plugin/head\n\nhead插件可以查看集群概况,删除、关闭Indices,\n浏览数据,发送http请求操作数据等。\n上线时注意修改ES配置不能让外部网络访问。</code></pre>\n\n<p>kibana</p>\n\n<pre><code>下载地址(最新4.x变化比较大,推荐)\nhttps://www.elastic.co/downloads/kibana\n\nkibana也是解压即可用,直接运行即可\nnohup ./bin/kibana >log.txt &\n(由于没有 -d 参数,要用nohup保持运行)</code></pre>\n\n<p>kibana-marvel</p>\n\n<pre><code>marvel是kibana的一个插件,是一个动态监控ES运行状态的工具,\n安装方式请看 \nhttps://www.elastic.co/downloads/marvel\n访问地址: \nhttp://hostname:5601/app/marvel</code></pre>\n\n<ul><li>集群</li></ul>\n\n<p>发现</p>\n\n<pre><code>ES配置文件默认集群名字是elasticsearch,在同一个局域网中,\n多个实例(不限主机)使用相同的集群名字,会自动发现并组成集群。\n2.0版本后,使用 multicast 自动发现的用户,需要单独安装:\nbin/plugin install discovery-multicast\n然后配置文件加上 \ndiscovery.zen.ping.multicast.enabled: true\n\nES也支持指定IP列表的集群,但灵活性和自动化不如上一种方式,\n所以在具备局域网组建条件的情况下推荐使用自动发现。</code></pre>\n\n<p>主从</p>\n\n<pre><code>每个运行的ES实例,称为一个节点。\n当集群中运行了两个节点时(可以是单台主机上的两个节点),我们就\n实现了主从功能,后启动的节点会复制一份数据,意味着磁盘空间成倍增加。</code></pre>\n\n<p><img alt=\"\" src=\"https://raw.githubusercontent.com/looly/elasticsearch-definitive-guide-cn/master/images/elas_0203.png\"/></p>\n\n<p>第三个节点</p>\n\n<pre><code>当有第三个节点介入时,ES集群会重新组织自己,重新分配数据分片。\n数据分片(shard)是Indices存储的逻辑空间,ES以分片为单位转移数据,如图:</code></pre>\n\n<p><img alt=\"\" src=\"https://raw.githubusercontent.com/looly/elasticsearch-definitive-guide-cn/master/images/elas_0204.png\"/></p>\n\n<pre><code>这样,每个节点上维护的分片减少,整个集群的吞吐量和容量都得到提升。</code></pre>\n\n<p><strong>*从节点会增加搜索速度,但是降低写入效率;\n而主节点能提高写入速度,轻微降低搜索速度,所以要权衡好数量</strong>*</p>\n\n<ul><li><p>RESTFUL 接口</p><p>ES</p></li><li>维护</li></ul>\n\n<p>分片与节点</p>\n\n<pre><code>对于目前的应用场景,单机情况下,建议开启10个shard,0个从节点\n\n索引方面,建议每天一个,kibana支持跨索引做局和图表,\n如:log-20160105,log-20160106</code></pre>\n\n<p>内存与磁盘</p>\n\n<pre><code>通过rest接口,可以删除指定的index,按时间创建index以后尤其方便删除:\ncurl -x delete http://hostname:9200/log-20160105\n\n建议创建计划任务,每天固定时间删除一个月前的index</code></pre>",
"createdAt": 1469612040,
"modified": 1469612971
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "298",
"_score": 1.0,
"_source": {
"title": "PHP pdo 事务,代码里的一个容易踩的坑",
"keywords": [
"php",
"pdo",
"事务"
],
"content": "<p>PHP pdo 在支持事务的存储引擎(innoDB)下,可以使用以下代码实现事务</p>\n\n<pre><code>$pdo->beginTransaction();\n\ntry{\n $pdo->exec('insert into tt set s=11,t=99 ');\n $pdo->commit();\n // 注意!!\n}catch (Exception $e){\n var_dump($e);\n $pdo->rollback();\n}</code></pre>\n\n<p>上面的逻辑,在数据库插入失败时,会执行<code>rollback</code>,本次事务结束。</p>\n\n<p>表面上看这段代码没有问题,</p>\n\n<p>但是,如果你在上面【注意】的地方,还有其他代码的话。比如:</p>\n\n<pre><code>$pdo->beginTransaction();\n\ntry{\n $pdo->exec('insert into tt set s=11,t=99 ');\n $pdo->commit();\n // 注意!!\n throw new Exception('boom');\n}catch (Exception $e){\n var_dump($e);\n $pdo->rollback();\n}</code></pre>\n\n<p>这样一来,数据库插入没有问题,顺利<code>commit</code> ,但是catch到了一个其他的异常,</p>\n\n<p>导致<code>rollback</code>也被执行了一次,这时候 <code>pdo</code> 会报一个错误</p>\n\n<pre><code>Uncaught PDOException: There is no active transaction in ...</code></pre>\n\n<p>这是因为 <code>beginTransaction</code> 会让这个事务状态为 <code>active</code>,</p>\n\n<p>而 <code>commit</code> 或 <code>rollback</code> 会让事务变成 <code>inactive</code> 。</p>\n\n<p>显而易见,当然不能执行了 <code>commit</code> 后又执行 <code>rollback</code> 了。</p>\n\n<h3>所以, commit 应该写在 try 语句块的最后一行。</h3>",
"createdAt": 1470300483,
"modified": 1470300483
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "291",
"_score": 1.0,
"_source": {
"title": "八一八HTTP的一些事情",
"keywords": [
"http"
],
"content": "<h1>八一八HTTP的一些事情</h1>\n\n<blockquote><p>如果想了解全面的,请自己看<a href=\"http://www.rfc-editor.org/search/rfc_search_detail.php?title=http&pubstatus%5B%5D=Any&pub_date_type=any\">RFC</a></p></blockquote>\n\n<h2>从0.9到1.0</h2>\n\n<p>0.9是第一个版本,有以下几个显著特点。</p>\n\n<pre><code>1.实现了 get 请求,没有协议头,整个请求只有一行:GET /index.html \n\n2.它只定义了请求(Request),返回(Response)没有任何定义。\n 所以,服务端只能返回纯文本。\n\n3.典型的无状态请求,每一个事务都重新建立并关闭 一次TCP连接。\n\n4.如果请求的资源不存在,或是服务器不可用,不会有任何的返回。</code></pre>\n\n<p>0.9的时代大家都很单纯,由于各种客观原因,能够从网络上拿到纯文本信息,\n已经能为互联网提供很大的想象空间。</p>\n\n<p>但是很明显的,以我们现在的眼光来看http0.9,这是一个非常直接非常单薄的发明。</p>\n\n<p>而真正到了接近我们现在理解的http,就是1.0的时代了。</p>\n\n<p>1.0是一个相对完善的版本,所以跟0.9比较起来,内容多了很多,其中比较值得关注的是</p>\n\n<pre><code>1.多了post的方法,这是web2.0的基础\n\n2.有了http headers的概念,这是最伟大的部分,后面细说\n\n3.有了状态码</code></pre>\n\n<h2>http 1.0 让世界有了色彩</h2>\n\n<p>由于1.0相对对0.9增加的内容太多,后来的很多client(如浏览器),都强制从1.0开始支持。</p>\n\n<p>post方法带来变化,相信大家都比较能理解,在这里不细说。</p>\n\n<p>这里我想重点说一下为什么 headers 是最伟大的部分。</p>\n\n<p>1.多媒体的支持</p>\n\n<pre><code>在0.9时代,从网络上传回来的数据,我们只能按照纯文本来处理,\n因为我并不知道这些内容是什么含义。\n\n而从1.0开始,为了能告诉客户端数据类型,http返回头里会包含 content-type 属性。\n\ncontent-type的格式是 \n“主类型/子类型” ( [type]/[subtype])\n如 : text/html\n\n常见的content-type请看 http://tool.oschina.net/commons\n\n有了这个信息,服务器可以明确告诉客户端,这是音频、视频、图片、ppt等,\n客户端也就可以根据这个属性,调用本地的解释器来执行返回的内容。\n\n于是就有了多媒体。</code></pre>\n\n<p>2.缓存</p>\n\n<pre><code>缓存部分通过几个机制来完成,层层递进,分别是\n\nExpires : \n例子:Expires: Thu, 01 Dec 2016 16:00:00 GMT\n返回头包含这个信息,告诉client在这个时间之前,你都可以使用本地的副本。\n这个机制可以直接 免除 网络请求,效果明显,\n但是问题是当服务器时间跟客户端时间不一致时,会出现一些问题。\n\nLast-Modified、If-Modified-Since (精确到秒)\n当Expires过期,客户端就重新请求资源,并且请求头在带上If-Modified-Since\n(而上一次请求得到资源时,返回头会包含 Last-Modified 表明资源的修改时间),\n服务器收到请求,会检查本地资源在If-Modified-Since之后是否有变化,\n如果有,返回200,更新Last-Modified系信息,返回资源实体。\n如果没有变化,返回304 ,由于没有返回实际数据,这个请求很快,\n然后客户端就还是使用本地的副本。\n\nETag (http 1.1)\nETag是1.1补充到,在这样顺便说明一下。\nEtag是服务器对资源的一个特征值校验,一旦资源发生变化,Etag也会变化。\n它是弥补Last-Modified的一个缺陷,就是当资源在一秒内发生变化,\nLast-Modified是无法察觉的。</code></pre>\n\n<p>3.状态码</p>\n\n<pre><code>每一个状态码都有它的含义,在一些实现得好的http环境下,能够表达很多的含义。\n虽然我们没法记住全部状态码,但是可以通过码段区分:\n1XX 是传达一些信息的\n2XX 是成功一类的状态,不过也分很多种,received,understood, and accepted\n3XX 重定向一类,只有头部返回信息\n4XX Client Error ,这个错误是由客户端产生的,比如uri写错的404\n5XX Server Error ,说明是服务端的问题,必要时报警通知管理员</code></pre>\n\n<p>4.其他</p>\n\n<pre><code>http1.0还有很多丰富的进步,有兴趣的可以去看 rfc1945</code></pre>\n\n<h2>http 1.1</h2>\n\n<p>1.1是对1.0的一个补充,所以现在的client基本上都同时支持1.0/1.1,\n实际上我们玩得6的基本都在1.0,而1.1也有以下一些振奋人心的东西:</p>\n\n<ol><li>host</li></ol>\n\n<pre><code>\t1.1要求请求头加入host信息,表明目前请求的域名,这样web服务器(nginx等)就能\n\t根据头部信息,分发内容到VirtualHost,实现了一台主机多个应用。\n\t(1.0以前,一个IP只能部署一个应用)。</code></pre>\n\n<ol><li>keepalive</li></ol>\n\n<pre><code>\tkeepalive是客户端与服务端在一个超时范围内,可以重复使用tcp链接,\n\t(过去是一次请求一次返回就断开)\n\t其实在1.0就已经支持keepalive,不过要手动设定,\n\t而到了1.1,默认所有请求都加入keepalive选项。</code></pre>\n\n<ol><li>range</li></ol>\n\n<pre><code>\trange技术多用于下载,它允许分段传输文件。\n\t在请求头中加入:Range: bytes 0-800 表示下载前800bytes的内容,\n\t服务器会返回前800bytes并且状态是206 。\n\t有了这个技术,可以实现断点续传、多线程下载等。</code></pre>\n\n<ol><li><p>session/cookie</p><pre><code>这个我们最熟悉的东西,居然不是http里定义的。</code></pre><p></p></li></ol>",
"createdAt": 1463211600,
"modified": 1511941874
}
},
{
"_index": "blog",
"_type": "articles",
"_id": "307",
"_score": 1.0,
"_source": {
"title": "gitlab-ci 坑后感与指北",
"keywords": [
"ci",
"cd",
"gitlab"
],
"content": "<blockquote><p>本文的目的:</p><p> 最主要是备忘, 其次是分享</p><p>疗效:</p><p>并不能让你一下子掌握CI/CD, 这只是一个比较完整的解决方案,其他基础知识,自行补充.</p></blockquote>\n\n<hr/>\n\n<h4>基调</h4>\n\n<blockquote><p>首先,这不是屠龙刀,不要奢望一篇文章可以走遍天下.这里只是提供一个具体的落地方案, 一个具体的技术选型.</p></blockquote>\n\n<hr/>\n\n<h4>阶段1: 代码仓库</h4>\n\n<p>关于 <strong>代码仓库</strong>, 本文选取的方案是 <code>gitlab</code></p>\n\n<p><code>gitlab</code>的搭建:</p>\n\n<p>以目前的情况来说, 推荐使用<code>docker</code>来搭建你的系统, 不然你会陷入各种膜明其妙的问题.</p>\n\n<blockquote><p>docker的知识, 请自行补充一下,篇幅有限不能展开细说.</p></blockquote>\n\n<p>在这里我推荐一个:</p>\n\n<p><code>https://hub.docker.com/r/sameersbn/gitlab/</code></p>\n\n<p>打开以后直接搜索<code>Quick Start</code>, 按照<code>docker-compose</code>的方式启动你的<code>gitlab</code>. </p>\n\n<blockquote><p>不要对英文心存恐惧 ---- 孔子</p></blockquote>\n\n<p>下载好 <code>docker-compose.yml</code>之后不要急着启动, 需要修改几个参数:</p>\n\n<blockquote><p>需要学习一点点yml的知识, 大约5分钟, 自行google</p></blockquote>\n\n<ul><li><code>GITLAB_SECRETS_DB_KEY_BASE</code>, </li><li><code>GITLAB_SECRETS_SECRET_KEY_BASE</code>, </li><li><code>GITLAB_SECRETS_OTP_KEY_BASE</code></li></ul>\n\n<p>上面三个是<code>gitlab</code>用于加密时用的key, 随便给个长度64的字符串, 这块不做 深究.</p>\n\n<ul><li><code>GITLAB_ROOT_EMAIL</code></li><li><code>GITLAB_ROOT_PASSWORD</code></li></ul>\n\n<p>上面两个就是初始化时管理员账号的<code>账号密码</code>, 按自己的需要填写</p>\n\n<ul><li><code>GITLAB_HOST</code></li></ul>\n\n<p>这是 <strong>gitlab</strong> 内部使用的地址, 这关系到你gitlab页面上的项目地址,没设置的话, 到时候显示的是<code>127.0.0.1</code>, 这个鬼才能<code>clone</code>下来.</p>\n\n<blockquote><p>这个 host 一旦设置, 初始化完就改不了了, 所以一定要在第一次启动之前 就设置好.</p></blockquote>\n\n<h5>启动</h5>\n\n<p><code>docker-compose up</code></p>\n\n<p>一系列的初始化信息以后, 你就能访问你的gitlab了.</p>\n\n<p>默认是 <code>http://{你的IP}:10080</code></p>\n\n<p><code>\n其他关于gitlab的使用技巧, 就不深入了.\n能关注这篇文章的都不是萌新了,这些内容自己补充吧.\n</code></p>\n\n<hr/>\n\n<h4>阶段2: 提交触发</h4>\n\n<p>接上文.</p>\n\n<p><code>gitlab-ci</code>在最新版的<code>gitlab</code>已经是内置的了, 只要项目里有<code>.gitlab-ci.yml</code>,同时有对应的<code>gitlab-runner</code>, 就能实现<code>CI</code>, 相比之下不需要太多的配置.</p>\n\n<blockquote><p>名词解释:</p><p>.gitlab-ci.yml:</p><p> 这是gitlab-ci使用的任务描述文件, 里面主要是定义CI的过程需要执行哪些行为, 简单说就是, 要进行哪几个步骤, 每个步骤是哪些命令.</p><p>gitlab-runner:</p><p> 另一个程序, 也可以用docker启动, 就是负责执行 CI 任务的机器人, runner这块后面会展开讲.</p></blockquote>\n\n<hr/>\n\n<p><strong>启动并注册<code>gitlab-runner</code></strong></p>\n\n<p>我们还是使用<code>docker</code>来启动,这是一个大方向</p>\n\n<pre><code>docker run -d --name gitlab-runner --restart always \\\n\n-v /srv/gitlab-runner/config:/etc/gitlab-runner \\\n\n-v /var/run/docker.sock:/var/run/docker.sock \\\n\ngitlab/gitlab-runner:latest</code></pre>\n\n<blockquote><p>想深入了解的话, 请看 </p><p>https://docs.gitlab.com/runner/install/docker.html</p></blockquote>\n\n<p><strong>敲黑板!!</strong></p>\n\n<p>在这里, 我们将宿主机的<code>docker.sock</code>映射进去,让<code>runner</code>可以跟宿主用同一个<code>daemon</code>, (意味着你进去runner内部执行<code>docker images</code>是可以看到外面的镜像列表的), 这样做是埋下一个<strong>伏笔</strong>, 以便后面阶段使用<code>dind</code>(docker in docker)时, 获得更好的体验.</p>\n\n<hr/>\n\n<p><strong>继续</strong></p>\n\n<p>好了, 这个时候你启动了一个<code>runner</code>, 你要告诉它应该到哪里去"服役",</p>\n\n<p>这一步叫做: <strong>注册</strong></p>\n\n<blockquote><p>注册runner的方式请看 </p><p>https://docs.gitlab.com/runner/register/index.html#docker</p></blockquote>\n\n<p>不过, 还是请你使用以下命令来注册:</p>\n\n<pre><code>docker exec -it gitlab-runner gitlab-runner register \\\n\n--docker-volumes /var/run/docker.sock:/var/run/docker.sock \\\n\n--docker-privileged</code></pre>\n\n<p>这里使用了两个参数, 都是为了 <strong>docker in docker</strong> 能得到更好的体验而服务的.</p>\n\n<p>输入以上命令后, 根据提示填写信息, 其中:</p>\n\n<ul><li>host,token 这些, 请打开你刚装好的gitlab, 进入 <code>Admin area</code>-<code>Runners </code>,然后照着填写就是了</li><li>特别注意期间会让你选一个<code>executor </code>类型, 个人推荐最好的方式是<code>docker </code>, 至于<code>shell</code>这种方式, 玩玩可以,实际使用时副作用太多.</li><li>更多参数的细节, 自行研究.</li></ul>\n\n<p>完成以上步骤之后, 你在<code>gitlab</code> - <code>Admin area</code>-<code>Runners </code>页面就能看到注册好的<code>runner</code>了, 当然你现在还是感觉不到它的作用.</p>\n\n<pre><code>这个环节内容比较多, 操作比较多, 走到这里建议休息一下喝杯茶.</code></pre>\n\n<hr/>\n\n<h4>阶段3: Runner Job</h4>\n\n<p>这个阶段, 是指代码提交以后, <code>gitlab-runner</code>会自动读取项目的<code>.gitlab-ci.yml</code>, 运行里面定义的每个<code>Job</code>.</p>\n\n<p>这里给出一个极简的<code>.gitlab-ci.yml</code>例子,</p>\n\n<p>它做的就是, 在提交代码以后, 自动的<strong>测试</strong>, 自动的<strong>构建</strong>, 自动的<strong>发布</strong> :</p>\n\n<pre><code>stages:\n - test\n - build\n - deploy\n\njob_01:\n stage: test\n image: dev_tool/node_builder:1.0.0\n script: \n - npm install --registry=https://registry.npm.taobao.org\n - node server.js &\n - node test_api.js\n\njob_02:\n stage: build\n image: gitlab/dind\n script:\n - docker build -t ci-demo:latest .\n\njob_03:\n stage: deploy\n image: dev_tool/rancher-cli:latest\n script:\n - rancher-tool init\n - rancher up -d --pull --force-upgrade --confirm-upgrade</code></pre>\n\n<p>一目了然, 上面的第一个定义: <code>stages</code> 数组,</p>\n\n<p>意思是这个项目的<code>CI/CD</code>过程要执行三个步骤(<code>stage</code>),</p>\n\n<p>分别是<code>test测试</code>-<code>build编译</code>-<code>deploy发布</code></p>\n\n<p>然后下面的三个<code>job_*</code>,名字是随意的, 重点是里面的<code>stage</code>属性,</p>\n\n<p>告诉<code>gitlab-ci</code>这个任务是在哪个<code>stage</code>执行的,</p>\n\n<p>一个<code>stage</code>你可以写很多个<code>job</code></p>\n\n<p><strong>敲黑板!!!</strong> </p>\n\n<p>需要注意的是, 我们之前选择了<code>docker executor</code>, <code>job</code>里面就要声明<code>image</code>属性,指定这个<code>Job</code>的<code>scripts</code>要在哪个<code>image</code>里面运行.</p>\n\n<p><strong>重点说明!! 再次大力敲黑板!!</strong></p>\n\n<p>这里第二步使用了<code>gitlab/dind</code> , 仔细看<code>script</code>, 这是在一个容器里面去构建一个镜像, 为了<strong>整体体验</strong>与<strong>构建效率</strong>着想, 我们之前注册<code>runner</code>的时候,将宿主机的<code>docker.sock</code>映射进去是十分必要的!!\n(重新翻上去看吧)</p>\n\n<p><strong>看到这里, 聪明的朋友已经发现了,</strong></p>\n\n<p>我们需要自己<strong>打造</strong>一批用于运行<code>Job</code>的基础镜像, 这些镜像里要预先安装好我们需要的依赖环境.</p>\n\n<p>举个栗子:</p>\n\n<p>你要在<code>build</code>这一步做<code>webpack</code>打包的话, 你要准备好一个内部安装好<code>webpack</code>的镜像(相关的<code>node</code>,<code>npm</code>之类就更不用说了)</p>\n\n<p><strong>听起来好麻烦?</strong></p>\n\n<p>也不是, 这是个 <strong>功在当代,利在千秋</strong> 的行为, 前期打造好基础镜像, 后面的项目就可以很容易写<code>CI Job</code>了.</p>\n\n<blockquote><p>更多 gitlab-ci.yml 的高级写法,还是建议看官方文档\nhttps://docs.gitlab.com/ee/ci/yaml/README.html</p></blockquote>\n\n<hr/>\n\n<h4>阶段4: 坐享其成 && 总结</h4>\n\n<p>如果按照上面的步骤把这个系统搭建起来以后, 你应该已经能够感受到<code>gitlab-ci</code>带来的好处了.</p>\n\n<p>现在你只管提交代码, 就能快速看到新功能集成到相应的环境了.</p>\n\n<p>此后, 你只要写好每一步的<code>Job</code> 就可以了.</p>\n\n<p>尤其是<strong>测试</strong>这个环节.</p>\n\n<p>尤其是<strong>测试</strong>这个环节. </p>\n\n<p>尤其是<strong>测试</strong>这个环节.</p>\n\n<hr/>\n\n<h3>后记</h3>\n\n<ul><li><code>gitlab</code> 真的很吃资源, 虚拟机玩够呛, 团队用的话, 建议装一台PC来搭建.</li><li><code>基础镜像</code>别偷懒, 多打磨,让你的<code>scripts</code>可以更简洁</li><li>更进一步的话, 自己开发一系列的命令行工具, 让你的<code>scripts</code>更强大. </li><li>有事找我, 包教会.</li></ul>",
"createdAt": 1514636100,
"modified": 1514642654
}
}
]
}
}