-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
469 lines (295 loc) · 288 KB
/
atom.xml
File metadata and controls
469 lines (295 loc) · 288 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Nundy的博客</title>
<subtitle>要么庸俗,要么孤独</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://nundy.cn/"/>
<updated>2018-10-26T06:06:19.000Z</updated>
<id>http://nundy.cn/</id>
<author>
<name>Nundy</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>做一个bulingbuling~的按钮</title>
<link href="http://nundy.cn/2018/10/26/%E5%81%9A%E4%B8%80%E4%B8%AAbulingbuling-%E7%9A%84%E6%8C%89%E9%92%AE/"/>
<id>http://nundy.cn/2018/10/26/做一个bulingbuling-的按钮/</id>
<published>2018-10-26T14:03:20.000Z</published>
<updated>2018-10-26T06:06:19.000Z</updated>
<content type="html"><![CDATA[<p><!DOCTYPE html></p><html lang="en"><br> <head><meta name="generator" content="Hexo 3.8.0"><br> <meta charset="UTF-8"><br> <meta name="viewport" content="width=device-width, initial-scale=1.0"><br> <meta http-equiv="X-UA-Compatible" content="ie=edge"><br> <title>Document</title><br> <style><br> .box{<br> position: relative;<br> }<br> .light{<br> cursor:pointer;<br> position: absolute;<br> left: -180px;<br> top: 0;<br> width: 100px;<br> height: 400px;<br> background-image: -moz-linear-gradient(0deg,rgba(255,255,255,0),rgba(255,255,255,0.5),rgba(255,255,255,0));<br> background-image: -webkit-linear-gradient(0deg,rgba(255,255,255,0),rgba(255,255,255,0.5),rgba(255,255,255,0));<br> transform: skewx(-25deg);<br> -o-transform: skewx(-25deg);<br> -moz-transform: skewx(-25deg);<br> -webkit-transform: skewx(-25deg);<br> }<br> .box:hover .light{<br> left:50px;<br> -moz-transition:1s;<br> -o-transition:1s;<br> -webkit-transition:1s;<br> transition:1s;<br> }<br> </style><br> </head><br> <body><br> <div class="box"><br> <img src="http://www.nowamagic.net/librarys/images/201402/2014_02_15_01.jpg"><br> <i class="light"></i><br> </div><br> </body><br></html>]]></content>
<summary type="html">
<p>&lt;!DOCTYPE html&gt;</p>
<html lang="en"><br> <head><meta name="generator" content="Hexo 3.8.0"><br> <meta charset="UTF-8"><br
</summary>
</entry>
<entry>
<title>MySql 的问题笔记</title>
<link href="http://nundy.cn/2018/10/26/MySql-%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/"/>
<id>http://nundy.cn/2018/10/26/MySql-遇到的问题/</id>
<published>2018-10-26T10:19:49.000Z</published>
<updated>2018-10-26T02:48:39.000Z</updated>
<content type="html"><![CDATA[<p>记录 Mysql 操作中遇到的一些问题 !</p><a id="more"></a><h4 id="The-server-requested-authentication-method-unknown-to-the-client"><a href="#The-server-requested-authentication-method-unknown-to-the-client" class="headerlink" title="The server requested authentication method unknown to the client"></a>The server requested authentication method unknown to the client</h4><p>描述:Mysql 安装了最新版本8.0.11后创建用户并授权后,授权的用户连接数据库提示如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">The server requested authentication method unknown to the client</span><br></pre></td></tr></table></figure><p>原因:新老版本Mysql账号密码解锁机制不一致导致</p><p>解决:</p><p>1、删除创建的用户和授权,修改 mysql 配置文件,将其变为原来的验证方式,然后重新创建用户并授权即可,配置文件修改如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">default_authentication_plugin=mysql_native_password</span><br></pre></td></tr></table></figure><p>2、使用指令修改</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mysql -uroot -p </span><br><span class="line">use mysql;</span><br><span class="line">ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';</span><br></pre></td></tr></table></figure><h4 id="zsh-command-not-found-mysql"><a href="#zsh-command-not-found-mysql" class="headerlink" title="zsh: command not found: mysql"></a>zsh: command not found: mysql</h4><p>描述:Mac 安装 Mysql 后提示命令找不到</p><p>原因:当前 shell 找不到 Mysql 指令</p><p>解决:</p><p>1、因为我的 shell 用的是 zsh,所以修改以下文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi ~/.zshrc</span><br></pre></td></tr></table></figure><p>添加以下语句,导出 mysql</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export PATH=${PATH}:/usr/local/mysql/bin</span><br></pre></td></tr></table></figure><p>之后使用 source 执行一下文件,使其生效,否则重启后生效</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">source ~/.zshrc</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>记录 Mysql 操作中遇到的一些问题 !</p>
</summary>
<category term="Mysql" scheme="http://nundy.cn/tags/Mysql/"/>
<category term="Mac" scheme="http://nundy.cn/tags/Mac/"/>
</entry>
<entry>
<title>前端仔是如何调试手机网页的</title>
<link href="http://nundy.cn/2018/10/22/%E4%BD%BF%E7%94%A8Charles%E5%AF%B9%E6%89%8B%E6%9C%BA%E8%BF%9B%E8%A1%8C%E4%BB%A3%E7%90%86%E8%B0%83%E8%AF%95/"/>
<id>http://nundy.cn/2018/10/22/使用Charles对手机进行代理调试/</id>
<published>2018-10-22T11:39:18.000Z</published>
<updated>2018-10-26T03:03:16.000Z</updated>
<content type="html"><![CDATA[<p>对于一些移动端页面,或者 Hybrid 中的一些页面来说,开发的时候可以借助 Chrome 模拟调试</p><p>但是移动端实际使用中遇到 Bug,想检查代码时,就很蓝瘦了。</p><p>本文就简单记录如何通过神器 Charles, 将手机流量代理到电脑上,并借助电脑来进行调试。</p><a id="more"></a><p>对于 Charles 来说,想要使用它,首先得进行安装证书,电脑和手机都得安装!</p><h6 id="MAC-或-Windows-安装"><a href="#MAC-或-Windows-安装" class="headerlink" title="MAC 或 Windows 安装"></a>MAC 或 Windows 安装</h6><p>1、点击 Help — SSL Proxying — Install Charles Root Certificate</p><p>2、在打开的钥匙串中找到 Charles Proxy 双击,在’信任选项中’,全部调整为信任</p><h6 id="手机安装"><a href="#手机安装" class="headerlink" title="手机安装"></a>手机安装</h6><p>(crt 小米6、cer、pem save)</p><p>1、手机打开浏览器,输入 ‘chls.pro/ssl’ 下载并安装证书</p><p>2、如果无法安装,则点击 ‘Save Charles Root Certificate’,保存下来一个.pem的证书,传输到手机,更改后缀名为 .crt,然后双击安装</p><h6 id="代理手机"><a href="#代理手机" class="headerlink" title="代理手机"></a>代理手机</h6><p>1、手机与电脑连接到同一局域网</p><p>2、打开手机 wifi,在高级选项中,点击手动设置代理</p><p>3、远程地址设置为电脑在局域网中的 IP,端口号为 8888</p><h5 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h5><p>1、手机访问网页,若 Charles 可以监听到流量,则证明代理成功</p><h5 id="将手机流量转到测试环境"><a href="#将手机流量转到测试环境" class="headerlink" title="将手机流量转到测试环境"></a>将手机流量转到测试环境</h5><p>1、点击 Tools — Map Remote</p><p>2、Add 添加代理,输入需要过滤的 URL,以及需要代理的机器即可</p><p>先简单记录一下,还未详细整理</p><p>细节未完善,对于 IOS 、小米等手机来说,还需要根据各自的特点,在手机安全设置中修改一些内容。</p>]]></content>
<summary type="html">
<p>对于一些移动端页面,或者 Hybrid 中的一些页面来说,开发的时候可以借助 Chrome 模拟调试</p>
<p>但是移动端实际使用中遇到 Bug,想检查代码时,就很蓝瘦了。</p>
<p>本文就简单记录如何通过神器 Charles, 将手机流量代理到电脑上,并借助电脑来进行调试。</p>
</summary>
<category term="Charles" scheme="http://nundy.cn/tags/Charles/"/>
<category term="NA 端" scheme="http://nundy.cn/tags/NA-%E7%AB%AF/"/>
</entry>
<entry>
<title>iTerm2 + Oh my zsh 最佳实践</title>
<link href="http://nundy.cn/2018/10/22/iTerm2%20+%20Oh%20my%20zsh/"/>
<id>http://nundy.cn/2018/10/22/iTerm2 + Oh my zsh/</id>
<published>2018-10-22T11:39:18.000Z</published>
<updated>2018-10-26T03:44:52.000Z</updated>
<content type="html"><![CDATA[<p>使用 iTerm2 作为终端,zsh + oh my zsh 定制个性主题,Powerline 改造终端状态栏。</p><p>终极 DIY ,打造舒适终端 ~</p><a id="more"></a><h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><p>1、iTerm2是Terminal的替代品,是一款比较小众的软件,比Terminal优秀太多了。 下载官网为<code>http://www.iterm2.cn/</code>,下载后直接安装即可。iTerm2可以设置主题,支持画面分隔、各种快捷键。</p><p>2、shell 种类:shell的类型有很多种,linux下默认的是<code>bash</code>,虽然bash的功能已经很强大,但对于以懒惰为美德的程序员来说,bash的提示功能不够强大,界面也不够炫,并非理想工具。</p><p>3、zsh 介绍:而zsh的功能极其强大,只是配置过于复杂,起初只有极客才在用。后来,有个穷极无聊的程序员可能是实在看不下去广大猿友一直只能使用单调的bash, 于是他创建了一个名为<code>oh-my-zsh</code>的开源项目…自此,只需要简单的安装配置,小白程序员们都可以用上高档大气上档次,狂拽炫酷吊炸天的<code>zsh</code>。</p><p>4、oh-my-zsh: Oh My Zsh是一款社区驱动的命令行工具,正如它的主页上说的,Oh My Zsh 是一种生活方式,它基于zsh命令行,提供了主题配置,插件机制,已经内置的便捷操作。</p><p>5、Powerline简介: 它是vim、zsh、bash、tmux、IPython、Awesome、bar、fish、lemonbar、pdb、rc、shell、tcsh、wm、i3 和Qtil 中的一个状态栏插件。 它给程序提供了状态栏,并使程序更好看,用Python 写成。它的使用依赖于一个字体库</p><p>命令提示:</p><p>1、查看自己系统的默认 shell, 使用以下命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo $SHELL</span><br></pre></td></tr></table></figure><p>2、查看已安装的所有 shells</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /etc/shells</span><br></pre></td></tr></table></figure><h4 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h4><p>1、iTerm2: 可以直接去官网下载:<a href="https://www.iterm2.com/" target="_blank" rel="noopener">https://www.iterm2.com/</a></p><p>2、zsh: 设置 zsh 为默认 shell</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chsh -s /bin/zsh</span><br></pre></td></tr></table></figure><p>3、安装 oh my zsh</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh</span><br></pre></td></tr></table></figure><p>4、安装 Powerline</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install powerline-status</span><br></pre></td></tr></table></figure><p>如果没有 pip指令,请使用以下指令安装</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo easy_install pip</span><br></pre></td></tr></table></figure><p>5、安装 powerline 字体库</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/powerline/fonts.git</span><br><span class="line">cd fonts</span><br><span class="line">./install.sh</span><br></pre></td></tr></table></figure><h4 id="配置工作"><a href="#配置工作" class="headerlink" title="配置工作"></a>配置工作</h4><p>1、设置配色:item2/preferences/profiles/colors/ 的 color presets 选项,选择solarized dark 主题</p><p>2、设置字体:item2/preferences/profiles/text/ 的 font 选项,选择 Meslo LG M Regular for powerline</p><p>3、设置主题:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">vi ~/.zshrc</span><br><span class="line"></span><br><span class="line">- ZSH_THEME="robbyrussell"</span><br><span class="line">+ ZSH_THEME="agnoster"</span><br><span class="line"></span><br><span class="line">source ~/.zshrc</span><br></pre></td></tr></table></figure><p>4、设置指令高亮</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cd ~/.oh-my-zsh/custom/plugins/</span><br><span class="line">git clone git://github.com/zsh-users/zsh-syntax-highlighting.git</span><br><span class="line">vi ~/.zshrc</span><br><span class="line"></span><br><span class="line">+ plugins=( zsh-syntax-highlighting )</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">5、命令补全</span><br></pre></td></tr></table></figure><p>cd ~/.oh-my-zsh/custom/plugins/<br>git clone <a href="https://github.com/zsh-users/zsh-autosuggestions" target="_blank" rel="noopener">https://github.com/zsh-users/zsh-autosuggestions</a><br>vi ~/.zshrc</p><ul><li>plugins=( zsh-autosuggestions )<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">5、缩短前缀,如 xxx@xxx</span><br></pre></td></tr></table></figure></li></ul><p>vim ~/.oh-my-zsh/themes/agnoster.zsh-theme<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">将里面的build_prompt的prompt_context自由修改</span><br><span class="line"></span><br><span class="line">#### Bug Fix</span><br><span class="line"></span><br><span class="line">###### 1、VSCode终端乱码</span><br><span class="line"></span><br><span class="line">描述:由于配置了 zsh 的字体,导致VSCode终端有部分乱码,</span><br><span class="line"></span><br><span class="line">打开设置,搜索 font.family ,同item2一样设置为 'Meslo LG M for Powerline' 就解决了</span><br><span class="line"></span><br><span class="line">###### 2、[oh-my-zsh] 提示检测到不安全的完成相关目录</span><br><span class="line"></span><br><span class="line">描述:权限问题</span><br></pre></td></tr></table></figure></p><p>[oh-my-zsh] Insecure completion-dependent directories detected:<br>[oh-my-zsh] xxx 一堆文件名<br>[oh-my-zsh] For safety, we will not load completions from these directories until<br>[oh-my-zsh] you fix their permissions and ownership and restart zsh.<br>[oh-my-zsh] See the above list for directories with group or other writability.<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">解决:</span><br><span class="line"></span><br><span class="line">1、将每一行中提示的文件 xxx 都按照以下命令修改一遍权限,每一行都得单独执行一遍</span><br></pre></td></tr></table></figure></p><p>chmod 755 xxx<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">2、设置变量 ZSH_DISABLE_COMPFIX = true</span><br><span class="line"></span><br><span class="line">在.zshrc文件的第一行添加 ZSH_DISABLE_COMPFIX = true,运行source命令,重新加载.zshrc文件</span><br></pre></td></tr></table></figure></p><p>vi ~/.zshrc</p><ul><li>ZSH_DISABLE_COMPFIX = true<br><code>`</code></li></ul>]]></content>
<summary type="html">
<p>使用 iTerm2 作为终端,zsh + oh my zsh 定制个性主题,Powerline 改造终端状态栏。</p>
<p>终极 DIY ,打造舒适终端 ~</p>
</summary>
<category term="Mac" scheme="http://nundy.cn/tags/Mac/"/>
<category term="iTerm2" scheme="http://nundy.cn/tags/iTerm2/"/>
<category term="Oh my zsh" scheme="http://nundy.cn/tags/Oh-my-zsh/"/>
</entry>
<entry>
<title>喝着阔落就把PHP环境搭好了~</title>
<link href="http://nundy.cn/2018/08/13/%E5%96%9D%E7%9D%80%E9%98%94%E8%90%BD%E5%B0%B1%E6%8A%8APHP%E7%8E%AF%E5%A2%83%E6%90%AD%E5%A5%BD%E4%BA%86/"/>
<id>http://nundy.cn/2018/08/13/喝着阔落就把PHP环境搭好了/</id>
<published>2018-08-13T10:50:14.000Z</published>
<updated>2018-10-19T09:04:08.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/php%E6%9C%8D%E5%8A%A1%E5%99%A8.png" alt="孔雀东南飞"></p><p>女朋友的 Linux 环境总出问题</p><p>炒鸡生气,今天来把这个服务器给安排一下!</p><a id="more"></a><h1 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作 ~"></a>准备工作 ~</h1><p>开始之前,把服务器系统重装一下,系统重装为 Ubuntu Server 16.04.1 LTS 64位</p><p>我的是腾讯云服务器,可以再腾讯云的控制台进行重装系统</p><p>重装之后,默认账户是<code>ubuntu</code>,密码在重装的时候会提示你进行设置</p><p>首先,通过<code>XShell</code>使用默认账户登录系统,然后我们进行设置<code>root</code>账户的密码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo passwd root</span><br></pre></td></tr></table></figure><p>执行上面命令,然后输入两次你需要设置的密码就OK了</p><p>现在,root账户的密码设置好了</p><p>但是,我们还不能使用<code>XShell</code>等软件直接通过root账户登录,因为,为了安全考虑,root账户直接登录一般被禁止</p><p>此处,为了方便,我们修改一下配置文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>打开该配置文件,做如下修改:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- PermitRootLogin prohibit-password</span><br><span class="line">+ PermitRootLogin yes</span><br></pre></td></tr></table></figure><p>重启SSH服务,使配置生效</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">service ssh restart</span><br></pre></td></tr></table></figure><p>现在我们就可以通过 XShell 使用 root 账户直接登录了!</p><h1 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装 ~"></a>开始安装 ~</h1><p>接下来我们来搭建 LAMP(LNMP) 的环境,因为需要安装若干东西,先来更新一下源列表</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">su root</span><br><span class="line">apt-get update</span><br></pre></td></tr></table></figure><p>源列表保存在 <code>/etc/apt/sources.list</code></p><p>更新好列表以后,我们开始安装:</p><ol><li>安装服务器</li></ol><p>安装 Apache2 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install apache2</span><br></pre></td></tr></table></figure><p>Apache 默认的WEB根目录在<code>/var/www/html</code>,如果安装成功,它会在该目录生成一个 <code>index.html</code></p><p>该页面展示了 Apache 服务器的基本信息,可以通过访问IP地址或者域名的方式来测试</p><p>如果看到该页面,则证明安装成功。</p><p>或者 安装 Nginx</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install nginx</span><br></pre></td></tr></table></figure><p>nginx 默认 WEB 根目录和 Apache 一样,此时,访问服务器可以看到 nginx 的提示信息!证明成功</p><p>2.安装 MySql</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install mysql-server mysql-client</span><br></pre></td></tr></table></figure><p>安装过程中会提示进行设置 mysql 的 root 密码</p><p>安装完成后,可以通过如下命令进行测试登录:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql -u root -p</span><br></pre></td></tr></table></figure><p>3.安装 PHP7.0</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install php7.0</span><br></pre></td></tr></table></figure><p>安装完成后,可以使用<code>php -v</code>进行查看版本信息,验证是否安装成功</p><p>到此为止,三大法宝(Apache2/Mysql/php7.0)都已经单独安装成功</p><p>接下来我们为它们建立联系</p><h1 id="建立联系"><a href="#建立联系" class="headerlink" title="建立联系 ~"></a>建立联系 ~</h1><p>4.让服务器支持 php</p><p>Apache: 安装 libapache2-mod-php7.0</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install libapache2-mod-php7.0</span><br></pre></td></tr></table></figure><p>它的作用是让 Apache2 可以解析 PHP</p><p>Nginx: 修改配置文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/nginx/sites-available/default</span><br></pre></td></tr></table></figure><p>去除 location ~.php$ 的选项</p><p>5.安装 php7.0-mysql</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install php7.0-mysql</span><br></pre></td></tr></table></figure><p>它的作用是为 PHP 提供访问 Mysql 的接口</p><p>6.重启服务,使安装失效</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">service apache2 restart 或者 service nginx restart</span><br><span class="line">service mysql restart</span><br></pre></td></tr></table></figure><p>好了,现在基本的工作已经做完了,我们可以在根目录创建一个<code>phpinfo.php</code>文件,写上如下内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><?php echo phpinfo(); ?></span><br></pre></td></tr></table></figure><p>然后在浏览器中访问该文件,就可以查看关于 php 的所有信息了~</p><h1 id="再搞个-phpmyadmin"><a href="#再搞个-phpmyadmin" class="headerlink" title="再搞个 phpmyadmin ~"></a>再搞个 phpmyadmin ~</h1><p>最后,我们再安装一个 phpmyadmin ,方便我们操作数据库</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install phpmyadmin</span><br></pre></td></tr></table></figure><p>安装过程中会出现选项,我们选择 apache2 后点击确定。然后需要输入一下数据库 root 密码</p><p>安装完成之后,由于 phpmyadmin 默认路径在 <code>/usr/share/phpmyadmin</code> ,我们需要为其在WEB根目录创建一个符号(软)链接</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ln -s /usr/share/phpmyadmin /var/www/html</span><br></pre></td></tr></table></figure><p>对于 Apache 来说,我们还需要启用 Apache2 的 mod_rewrite 模块</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a2enmod rewrite</span><br></pre></td></tr></table></figure><p>重启 php7.0-fpm 和 apache2</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">service php7.0-fpm restart</span><br><span class="line">service apache2 restart</span><br></pre></td></tr></table></figure><p>这样,我们就可以通过<code>IP/phpmyadmin</code> 或者 <code>域名/phpmyadmin</code> 的方式访问 phpmyadmin 了</p><p>之后管理数据库就 So Easy ~</p><h1 id="添加可执行类型"><a href="#添加可执行类型" class="headerlink" title="添加可执行类型 ~"></a>添加可执行类型 ~</h1><p>最后的最后~我们再来配置一下 Apache2 或者 Nginx 的可执行类型</p><p>这个又有什么卵用呢?它可以添加 ‘可以执行php的文件类型’,我们来看一下</p><p>Apache:</p><p>打开 Apache2 配置文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/apache2/apache2.conf</span><br></pre></td></tr></table></figure><p>添加如下代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AddType application/x-httpd-php .php .htm .html</span><br><span class="line">AddDefaultCharset UTF-8</span><br></pre></td></tr></table></figure><p>重启 Apache2</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">service apache2 restart</span><br></pre></td></tr></table></figure><p>Nginx:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/nginx/nginx.conf</span><br></pre></td></tr></table></figure><p>再 index.html index.htm 等的后面加上 index.php</p><p>现在,我们只要在 html 代码中写 php,也可以进行执行了,Cool</p><p>OK,完毕 ~ 基础配置就这么多</p><h1 id="汇报战果"><a href="#汇报战果" class="headerlink" title="汇报战果 ~"></a>汇报战果 ~</h1><p>小可爱,过来看一下,可还满意?</p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/php%E6%9C%8D%E5%8A%A1%E5%99%A8.png" alt="孔雀东南飞"></p>
<p>女朋友的 Linux 环境总出问题</p>
<p>炒鸡生气,今天来把这个服务器给安排一下!</p>
</summary>
</entry>
<entry>
<title>红绍愿</title>
<link href="http://nundy.cn/2018/06/20/%E7%BA%A2%E7%BB%8D%E6%84%BF/"/>
<id>http://nundy.cn/2018/06/20/红绍愿/</id>
<published>2018-06-19T16:01:35.000Z</published>
<updated>2018-08-07T09:09:44.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/hongshaoyuan.png" alt="红绍愿"></p><p>手中雕刻生花, 刀锋千转蜿蜒成画, 盛名功德塔, 是桥畔某处人家<br>春风绕过发梢红纱, 刺绣赠他, 眉目刚烈拟作妆嫁</p><a id="more"></a><p>轰烈流沙枕上白发, 杯中酒比划<br>年少风雅鲜衣怒马, 也不过一刹那</p><p>难免疏漏儿时檐下, 莫测变化<br>隔却山海, 转身, 从容煎茶</p><p>一生长, 重寄一段过往, 将希冀都流放, 可曾添些荒唐, 才记得你的模样<br>一身霜, 谁提笔只两行, 换一隅你安康, 便销得这沧桑, 你还在我的心上</p><p>镜子中的她:手中雕刻生花, 现实中的她:刀锋千转蜿蜒成画<br>镜子中的她:盛名功德塔, 现实中的她:是桥畔某处人家</p><p>摘自-Litten《我与’刺客信条’》</p><blockquote><p>我还是喜欢以前。<br>喜欢奔马高扬前蹄,嘶鸣掠过的罗马<br>喜欢明丽的音乐,随阿尔诺河一起流淌的翡冷翠<br>喜欢巍巍然屹立于大地的君士坦丁堡<br>还有孤僻而善良的阿泰尔,缜密而大气的艾吉奥…</p></blockquote><blockquote><p>所以,事情变得无趣的原因很简单:<br>我变了,sublime没变;我没变,游戏变了。<br>总之,你们的步调不一样了,<br>这在物理里,叫“相对位移”<br>在古文里,叫“曾经沧海难为水”<br>在王家卫里,叫“可我也只能到喜欢为止了”</p></blockquote>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/hongshaoyuan.png" alt="红绍愿"></p>
<p>手中雕刻生花, 刀锋千转蜿蜒成画, 盛名功德塔, 是桥畔某处人家<br>春风绕过发梢红纱, 刺绣赠他, 眉目刚烈拟作妆嫁</p>
</summary>
<category term="句子迷" scheme="http://nundy.cn/tags/%E5%8F%A5%E5%AD%90%E8%BF%B7/"/>
</entry>
<entry>
<title>那年青春我们正好</title>
<link href="http://nundy.cn/2018/01/01/my-18/"/>
<id>http://nundy.cn/2018/01/01/my-18/</id>
<published>2017-12-31T22:22:22.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/To-2018.png" alt="To-18"></p><a id="more"></a><p>新的一年,好好奋斗,照顾好她</p><p>有时间多陪父母聊聊天,不要让父母担心</p><p>凭自己的努力给父母添点需要的东西</p><p>不要意气用事,多为以后着想</p><p>挑一个温柔的时间,陪她去一个美丽的地方旅行</p><p>锻炼身体,增肌增重,最终目标 150斤,重拾我的六块腹肌</p><p>emmm…还有情商别太低了</p><p>把自己照顾好一点,不要再那么糙了。。。</p><p>为什么突然想到了看美剧,好吧,希望我们都过四级</p><p>说好了一起留在北京,赚够钱我们就溜,过自己喜欢的生活</p><p>把『谢谢』改为『谢谢你』,学会倾听,好好说话!</p><p>认真吃早餐就是好好生活,认真迎接新的一天到来的仪式感</p><p>周末别赖在屋里,一起骑行,爬山,游泳,露营,网球,攀岩,看展览演出,什么都行。</p><p>一起为每一个生命的第一次不断打卡,去做以前没有做过的事,去以前没有去过的地方旅行,去认识各种有趣的人,去发现一个未知的自己!</p><p>emmm….第一次写,That’all</p><p>抓紧2017最后的小尾巴跟一波风。以下,是我的18岁!</p><p><img src="http://ozgbjelmj.bkt.clouddn.com/18-1.jpg" alt="18-1"></p><p><img src="http://ozgbjelmj.bkt.clouddn.com/18-2.jpg" alt="18-2"></p><p><img src="http://ozgbjelmj.bkt.clouddn.com/18-3.jpg" alt="18-3"></p><p><img src="http://ozgbjelmj.bkt.clouddn.com/18-4.jpg" alt="18-4"></p><p><img src="http://ozgbjelmj.bkt.clouddn.com/18-5.jpg" alt="18-5"></p><p><img src="http://ozgbjelmj.bkt.clouddn.com/18-6.jpg" alt="18-6"></p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/To-2018.png" alt="To-18"></p>
</summary>
<category term="至此流年各天涯" scheme="http://nundy.cn/tags/%E8%87%B3%E6%AD%A4%E6%B5%81%E5%B9%B4%E5%90%84%E5%A4%A9%E6%B6%AF/"/>
</entry>
<entry>
<title>单例模式</title>
<link href="http://nundy.cn/2017/11/26/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
<id>http://nundy.cn/2017/11/26/单例模式/</id>
<published>2017-11-26T15:03:37.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p>单例模式(Singleton):又被称为单体模式,是只允许实例化一次的对象类。</p><p>有时我们也用一个对象来规划一个命名空间,井井有条的管理对象上的属性和方法。</p><a id="more"></a><h2 id="命名空间"><a href="#命名空间" class="headerlink" title="命名空间"></a>命名空间</h2><p>命名空间也被称为名称空间,它解决了这么一类问题:</p><p>为了让代码更易懂,人们常常用单词或者拼音定义变量和方法,但由于人们可用的单词或者汉字拼音是有限的</p><p>所以不同的人定义的变量名称可能冲突。</p><p>此时,就需要用命名空间来约束每个人定义的变量来解决这个问题。</p><h2 id="模块分明"><a href="#模块分明" class="headerlink" title="模块分明"></a>模块分明</h2><p>我们还可以通过单例模式来管理自己代码库中的各个模块,比如:</p><p>当添加设置元素class方法或插入一个元素方法时,它们就会放到 dom 模块中</p><p>当添加事件中阻止事件的冒泡方法时或阻止事件的默认行为时,它们就会放到 event 模块中</p><p>当添加去除字符串首尾空白字符方法或将字符串进行HTML编码时,它们就会放到 string 模块中</p><p>这样模块分明,会更加有利于我们管理自己的代码库</p><h2 id="静态变量"><a href="#静态变量" class="headerlink" title="静态变量"></a>静态变量</h2><p>JavaScript中没有静态变量,而使用静态变量又非常重要</p><p>但是,JavaScript非常的灵活,人们根据静态变量的特性想到了一种解决方法:</p><p>将变量放在一个函数内部,然后不提供赋值变量的方法,只提供获取变量的方法</p><p>这样就做到了限制变量的修改并且可以供外部访问的需求了</p><p>还有一点就是变量放在函数内部还需供外部访问,所以需要让创建的函数执行一次。</p><p>示例代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Method = (<span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">/* 定义的静态变量 */</span></span><br><span class="line"> <span class="keyword">var</span> conf = {</span><br><span class="line"> MAX_NUM = <span class="number">10</span>,</span><br><span class="line"> MIN_NUM = <span class="number">1</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 返回一个取值器 */</span></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> get : <span class="function"><span class="keyword">function</span>(<span class="params">name</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> conf[name] ? conf[name] : <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}());</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 获取静态变量 */</span></span><br><span class="line"><span class="keyword">var</span> num = Method.get(<span class="string">'MAX_NUM'</span>); <span class="comment">//10</span></span><br><span class="line"><span class="keyword">var</span> num = Method.get(<span class="string">'MIN_NUM'</span>); <span class="comment">//1</span></span><br></pre></td></tr></table></figure><p>在大部分的编程语言中都习惯大写变量名,所以我们模拟静态变量时也尊重这种使用习惯</p><h2 id="惰性单例"><a href="#惰性单例" class="headerlink" title="惰性单例"></a>惰性单例</h2><p>有时候单例对象需要延迟创建,这种方式我们称之为:惰性创建</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> LazySingle = (<span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">/* 单例实例引用 */</span></span><br><span class="line"> <span class="keyword">var</span> _instance = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 单例 */</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">Single</span> (<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> publicMethod : <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{},</span><br><span class="line"> publicProperty : <span class="string">'1.0'</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 单例对象接口 */</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span>( !_instance ){</span><br><span class="line"> _instance = Single();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> _instance;</span><br><span class="line"> }</span><br><span class="line">}())</span><br></pre></td></tr></table></figure><p>总而言之,如果你想让系统中只存在一个对象,那么单例模式是你的最佳解决方案!</p>]]></content>
<summary type="html">
<p>单例模式(Singleton):又被称为单体模式,是只允许实例化一次的对象类。</p>
<p>有时我们也用一个对象来规划一个命名空间,井井有条的管理对象上的属性和方法。</p>
</summary>
<category term="设计模式" scheme="http://nundy.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>我希望自己尽早知道的7个 JavaScript 怪癖</title>
<link href="http://nundy.cn/2017/11/26/JavaScript%E6%80%AA%E7%99%96/"/>
<id>http://nundy.cn/2017/11/26/JavaScript怪癖/</id>
<published>2017-11-25T16:48:28.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/javascript%E6%80%AA%E7%99%96.png" alt="JavaScript怪癖"></p><p>如果对你来说 JavaScript 还是一门全新的语言,或者你是在最近的开发中才刚刚对它有所了解,那么你可能会有些许挫败感。</p><p>任何编程语言都有它自己的怪癖(quirks)—— 然而,当你从那些强类型的服务器端语言转向 JavaScript 的时候 ,你会感到非常困惑。</p><p>我就是这样!当我在几年前做全职 JavaScript 开发的时候,我多么希望关于这门语言的许多事情我都能尽早地知道。</p><p>我希望通过本文中分享的一些怪癖能让你免于遭受我所经历过的那些头疼的日子!</p><a id="more"></a><p>本文并非一个详尽的列表,只是一些取样,目的是抛砖引玉,并且让你明白当你一旦逾越了这些障碍,你会发现 JavaScript 是多么强大。</p><p>我们会把焦点放在下面这些怪癖上:</p><ul><li><p>1.相等</p></li><li><p>2.点号 vs 方括号</p></li><li><p>3.函数上下文</p></li><li><p>4.函数声明 和 函数表达式</p></li><li><p>5.具名 和 匿名函数</p></li><li><p>6.自调用函数表达式</p></li><li><p>7.typeof vs object.ptototype.toString</p></li></ul><h3 id="相等"><a href="#相等" class="headerlink" title="相等"></a>相等</h3><p>因为 C# 的缘故我习惯于用==运算符来做比较。</p><p>具有相同值的值类型(以及字符串)是相等的,反之不然。指向相同引用的引用类型是相等的,反之也不然。</p><p>(当然这是建立在你没有重载==运算符或者GetHashCode方法的前提下)当我知道JavaScript有==和===两种相等运算符时,令我惊诧不已。</p><p>我所见过的大多数情况都是使用 == ,所以我如法炮制。然而,当我运行下面的代码时JavaScript并没有给我想当然的结果:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(x == <span class="string">"1"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"YAY! They're equal!"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>呃……这是什么黑魔法?整型数1怎么会和字符串”1”相等?</p><p>在JavaScript里有相等(equality ==)和恒等(strict equality ===)。</p><p>相等运算符会先会先把运算符两边的运算元强制转换为同种类型,然后再进行恒等比较。所以上面例子中的字符串”1”会先被转换成整数1,然后再和我们的变量x进行比较。</p><p>恒等不会进行强制类型转换。如果运算元是不同类型的(就像整型数1和字符串”1”)那么他们就是不相等的:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对于恒等,首先类型必须一样</span></span><br><span class="line"><span class="keyword">if</span>(x === <span class="string">"1"</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Sadly, I'll never write this to the console"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(x === <span class="number">1</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"YES! Strict Equality FTW."</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你可能已经开始为各种不可预知的强制类型转换担忧了,它们可能会在你的应用中让真假混乱,导致一些bug,而这些bug你很难从代码中看出来。</p><p>这并不奇怪,因此,那些有经验的JavaScript开发者建议我们总是使用恒等运算符。</p><h3 id="点号-vs-方括号"><a href="#点号-vs-方括号" class="headerlink" title="点号 vs 方括号"></a>点号 vs 方括号</h3><p>你可能会对JavaScript中用访问数组元素的方式来访问一个对象的属性这种形式感到诧异,当然,这取决于你之前使用的其他语言:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// getting the "firstName" value from the person object:</span></span><br><span class="line"><span class="keyword">var</span> name = person.firstName;</span><br><span class="line"></span><br><span class="line"><span class="comment">// getting the 3rd element in an array:</span></span><br><span class="line"><span class="keyword">var</span> theOneWeWant = myArray[<span class="number">2</span>]; <span class="comment">// remember, 0-based index</span></span><br></pre></td></tr></table></figure><p>然而 ,你知道我们也能用方括号来引用对象的成员吗?例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name = person[<span class="string">"firstName"</span>];</span><br></pre></td></tr></table></figure><p>那这有什么用呢?可能大部分时间你还是使用点号,然而有些为数不多的情况下,方括号给我们提供了一些点号方式无法完成的捷径。</p><p>比如,我可能会经常把一些大的switch语句重构成一个调度表(dispatch table),像下面这样:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> doSomething = <span class="function"><span class="keyword">function</span>(<span class="params">doWhat</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span>(doWhat) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"doThisThing"</span>:</span><br><span class="line"> <span class="comment">// more code...</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"doThatThing"</span>:</span><br><span class="line"> <span class="comment">// more code...</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"doThisOtherThing"</span>:</span><br><span class="line"> <span class="comment">// more code....</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">// additional cases here, etc.</span></span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="comment">// default behavior</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>它们能被转换成下面这样:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> thingsWeCanDo = {</span><br><span class="line"> doThisThing : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">/* behavior */</span> },</span><br><span class="line"> doThatThing : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">/* behavior */</span> },</span><br><span class="line"> doThisOtherThing : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">/* behavior */</span> },</span><br><span class="line"> <span class="keyword">default</span> : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="comment">/* behavior */</span> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> doSomething = <span class="function"><span class="keyword">function</span>(<span class="params">doWhat</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> thingToDo = thingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : <span class="string">"default"</span></span><br><span class="line"> thingsWeCanDo[thingToDo]();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然,使用switch本身并没有什么错(并且,在大多数情况下,如果你对迭代和性能很在意的话,switch可能比调度表要好)。</p><p>然而,调度表提供了一种更好的组织和扩展方式,并且方括号允许你在运行时动态地引用属性。</p><h3 id="函数上下文"><a href="#函数上下文" class="headerlink" title="函数上下文"></a>函数上下文</h3><p>已经有很多不错的博客里解释过JavaScript中的this所代表的上下文</p><p>然而,我还是明确地决定把它加到我“希望自己尽早知道的事”的清单里。</p><p>在代码的任意地方明确this所代表的东西是什么并不困难——你只需要记住几条规则。</p><p>然而,我之前读过的那些关于这点的解读只能增添我的困惑,因此,我尝试用一种简单的方式来表述:</p><ul><li>第一,开始时假设它是全局的</li></ul><p>默认情况下,this引用的是全局对象(global object),直到有原因让执行上下文发生了改变。</p><p>在浏览器里它指向的就是window对象(或者在node.js里就是global)。</p><ul><li>第二,方法内部的this</li></ul><p>如果你有个对象中的某个成员是个function,那么当你从这个对象上调用这个方法的时候this就指向了这个父对象。例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> marty = {</span><br><span class="line"> firstName: <span class="string">"Marty"</span>,</span><br><span class="line"> lastName: <span class="string">"McFly"</span>,</span><br><span class="line"> timeTravel: <span class="function"><span class="keyword">function</span>(<span class="params">year</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.firstName + <span class="string">" "</span> + <span class="keyword">this</span>.lastName + <span class="string">" is time traveling to "</span> + year);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">marty.timeTravel(<span class="number">1955</span>);</span><br><span class="line"><span class="comment">// Marty McFly is time traveling to 1955</span></span><br></pre></td></tr></table></figure><p>你可能已经知道你可以通过创建一个新的对象,来引用marty对象上的timeTravel方法。</p><p>这确实是JavaScript一个非常强大的特性——能让我们把函数应用到不止一个目标实例上:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> doc = {</span><br><span class="line"> firstName: <span class="string">"Emmett"</span>,</span><br><span class="line"> lastName: <span class="string">"Brown"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">doc.timeTravel = marty.timeTravel;</span><br></pre></td></tr></table></figure><p>那么,我们调用doc.timeTravel(1885)会发生什么事呢?</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">doc.timeTravel(<span class="number">1885</span>);</span><br><span class="line"><span class="comment">// Emmett Brown is time traveling to 1885</span></span><br></pre></td></tr></table></figure><p>呃……再一次被黑魔法深深地刺伤了。</p><p>其实事实也并非如此,还记得我们前面提到过的当你调用一个方法,那么这个方法中的this将指向调用它的那个父对象。</p><p>握紧你德罗宁(DeLoreans)跑车的方向盘吧,因为车子变重了。</p><p>(译注:作者示例代码的参考背景是一部叫《回到未来》的电影,Marty McFly 是电影里的主角,Emmett Brown 是把DeLoreans跑车改装成时光旅行机的博士,所以marty对象和doc对象分别指代这两人。而此时this指向了doc对象,博士比Marty重,所以……我一定会看一下这部电影。 )</p><p>当我们保存了一个marty.TimeTravel方法的引用并且通过这个引用调用这个方法时到底发生了什么事呢?我们来看一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> getBackInTime = marty.timeTravel;</span><br><span class="line">getBackInTime(<span class="number">2014</span>);</span><br><span class="line"><span class="comment">// undefined undefined is time traveling to 2014</span></span><br></pre></td></tr></table></figure><p>为什么是“undefined undefined”?!为什么不是“Marty McFly”?</p><p>让我们问一个关键的问题:当我们调用getBackInTime函数时,它的父/拥有者对象是谁呢?</p><p>因为getBackInTime函数是存在于window上的,我们是把它当作函数(function)调用,而不是某个对象的方法(method)。</p><p>当我们像上面这样直接调用一个没有拥有者对象的函数的时候,this将会指向全局对象。David Shariff对此有个很妙的描述:</p><blockquote><p>无论何时,当一个函数被调用,我们必须看方括号或者是圆括号左边紧邻的位置,如果我们看到一个引用(reference),那么传到function里面的this值就是指向这个方法所属于的那个对象,如若不然,那它就是指向全局对象的。</p></blockquote><p>因为getBackInTime的this是指向window的,而window对象里并没有firstName和lastName属性,这就是解释了为什么我们看到的会是“undefined undefined”。</p><p>因此,我们就知道了直接调用一个没有拥有者对象的函数时结果就是其内部的this将会是全局对象。</p><p>但是,我也说过我们的getBackInTime函数是存在于window上的。我是怎么知道的呢?</p><p>除非我把getBackInTime包裹到另一个不同的作用域中,否则我声明的任何变量都会附加到window上。</p><p>下面就是从Chrome的控制台中得到的证明:</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_1.png" alt="外部加载图片"></p><p>现在是讨论关于this诸多重点之一 “绑定事件处理函数” 的最佳时机。</p><ul><li>第三,异步调用的方法内部的this</li></ul><p>我们假设在某个button被点击的时候我们想调用marty.timeTravel方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> flux = <span class="built_in">document</span>.getElementById(<span class="string">"flux-capacitor"</span>);</span><br><span class="line">flux.addEventListener(<span class="string">"click"</span>, marty.timeTravel);</span><br></pre></td></tr></table></figure><p>当我们点击button的时候,上面的代码会输出“undefined undefined is time traveling to [object MouseEvent]”。</p><p>什么?!好吧,首先,最显而易见的问题是我们没有给timeTravel方法提供year参数。</p><p>反而是把这个方法直接作为一个事件处理函数,并且,MouseEvent被作为第一个参数传进了事件处理函数中。</p><p>这个很容易修复,然而真正的问题是我们又一次看到了“undefined undefined”。</p><p>别失望,你已经知道为什么会发生这种情况了(即使你没有意识到这一点)。</p><p>让我们修改一下timeTravel函数,输出this来帮助我们获得一些线索:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">marty.timeTravel = <span class="function"><span class="keyword">function</span>(<span class="params">year</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.firstName + <span class="string">" "</span> + <span class="keyword">this</span>.lastName + <span class="string">" is time traveling to "</span> + year);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>现在我们再点击button的时候,应该就能在浏览器控制台中看到类似下面这样的输出:</p><p>在方法被调用时第二个console.log输出了this,它实际上是我们绑定的button元素。</p><p>感到奇怪么?就像之前我们把marty.timeTravel赋值给一个getBakInTime的变量引用一样,此时的marty.timeTravel被保存为我们事件处理函数的引用,并且被调用了,但是并不是从“拥有者”marty对象那里调用的。</p><p>在这种情况下,它是被button元素实例中的事件触发接口调用的。</p><p>那么,有没有可能让this是我们想要的东西呢?当然可以!这种情况下,解决方案非常简单。</p><p>我们可以用一个匿名函数代替marty.timeTravel来做事件处理函数,然后在这个匿名函数里调用marty.timeTravel。同时这样也让我们有机会修复之前丢失year参数的问题。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">flux.addEventListener(<span class="string">"click"</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> marty.timeTravel(someYearValue);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>点击button会看到像下面这样的输出:</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_2.png" alt="外部加载图片"></p><p>成功了!但是为什么成功呢?思考一下我们是怎么调用timeTravel方法的。</p><p>第一次的时候我们是把这个方法的本身的引用作为事件处理函数,因此它并不是从父对象marty上调用的。</p><p>第二次的时候,我们的匿名函数中的this是指向button元素的,然而当我们调用marty.timeTravel时,我们是从父对象marty上调用的,所以此时这个方法里的this是marty。</p><ul><li>第四,构造函数里的this</li></ul><p>当你用构造函数创建一个对象的实例时,那么构造函数里的this就是你新建的这个实例。例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> TimeTraveler = <span class="function"><span class="keyword">function</span>(<span class="params">fName, lName</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.firstName = fName;</span><br><span class="line"> <span class="keyword">this</span>.lastName = lName;</span><br><span class="line"> <span class="comment">// Constructor functions return the</span></span><br><span class="line"> <span class="comment">// newly created object for us unless</span></span><br><span class="line"> <span class="comment">// we specifically return something else</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> marty = <span class="keyword">new</span> TimeTraveler(<span class="string">"Marty"</span>, <span class="string">"McFly"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(marty.firstName + <span class="string">" "</span> + marty.lastName);</span><br><span class="line"><span class="comment">// Marty McFly</span></span><br></pre></td></tr></table></figure><p>使用Call,Apply和Bind</p><p>从上面给出的例子你可能已经猜到了,通过一些语言级别的特性是允许我们在调用一个函数的时候指定它在运行时的this的。</p><p>让你给猜对了。call和apply方法存在于Function的prototype中,它们允许我们在调用一个方法的时候传入一个this的值。</p><p>call方法的签名中先是指定this参数,其后跟着的是方法调用时要用到的参数,这些参数是各自分开的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">someFn.call(<span class="keyword">this</span>, arg1, arg2, arg3);</span><br></pre></td></tr></table></figure><p>apply的第一个参数同样也是this的值,而其后跟着的是调用这个函数时的参数的数组。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">someFn.apply(<span class="keyword">this</span>, [arg1, arg2, arg3]);</span><br></pre></td></tr></table></figure><p>我们的doc和margy对象自己能进行时光旅行(译注:即对象中有timeTravel方法)</p><p>然而爱因斯坦(译注:Einstein,电影中博士的宠物,是一只狗)需要别人的帮助才能进行时光旅行,所以现在让我们给之前的doc对象(就是之前把marty.timeTravel赋值给doc.timeTravel的那个版本)添加一个方法,这样doc对象就能帮助einstein对象进行时光旅行了:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">doc.timeTravelFor = <span class="function"><span class="keyword">function</span>(<span class="params">instance, year</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.timeTravel.call(instance, year);</span><br><span class="line"> <span class="comment">// alternate syntax if you used apply would be</span></span><br><span class="line"> <span class="comment">// this.timeTravel.apply(instance, [year]);</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>现在我们可以送爱因斯坦上路了:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> einstein = {</span><br><span class="line"> firstName: <span class="string">"Einstein"</span>,</span><br><span class="line"> lastName: <span class="string">"(the dog)"</span></span><br><span class="line">};</span><br><span class="line">doc.timeTravelFor(einstein, <span class="number">1985</span>);</span><br><span class="line"><span class="comment">// Einstein (the dog) is time traveling to 1985</span></span><br></pre></td></tr></table></figure><p>我知道这个例子让你有些出乎意料,然而这已经足以让你领略到把函数指派给其他对象调用的强大。</p><p>这里还有一种我们尚未探索的可能性。</p><p>我们给marty对象加一个goHome的方法,这个方法是个让marty回到未来的捷径,因为它其实是调用了this.timeTravel(1985):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">marty.goHome = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.timeTravel(<span class="number">1985</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们已经知道,如果把 marty.goHome 作为事件处理函数绑定到button的click事件上,那么this就是这个button。</p><p>并且,button对象上也并没有timeTravel这个方法。</p><p>我们可以用之前那种匿名函数的办法来绑定事件处理函数,再在匿名函数里调用marty对象上的方法。不过,我们还有另外一个办法,那就是bind函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flux.addEventListener(<span class="string">"click"</span>, marty.goHome.bind(marty));</span><br></pre></td></tr></table></figure><p>bind函数其实是返回一个新函数,而这个新函数中的this值正是用bind的参数来指定的。</p><p>如果你需要支持那些旧的浏览器(比如IE9以下的)你就需要用个bind方法的补丁(或者,如果你使用的是jQuery,那么你可以用$.proxy;另外underscore和lodash库中也提供了_.bind)。</p><p>有一件事需要注意,如果你在一个原型方法上使用bind,那它会创建一个实例级别的方法,这样就屏蔽了原型上的同名方法,你应该意识到这并不是个错误。</p><h3 id="函数声明-vs-函数表达式"><a href="#函数声明-vs-函数表达式" class="headerlink" title="函数声明 vs 函数表达式"></a>函数声明 vs 函数表达式</h3><p>在JavaScript主要有两种定义函数的方法(而ES6会在这里作介绍):函数声明和函数表达式。</p><p>函数声明不需要var关键字。事实上,正如 Angus Croll 所说:“把他当作变量声明的兄弟是很有帮助的”。例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">timeTravel</span>(<span class="params">year</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.firstName + <span class="string">" "</span> + <span class="keyword">this</span>.lastName + <span class="string">" is time traveling to "</span> + year);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上例中名叫timeTravel的函数不仅仅只在其被声明的作用域内可见,而且对这个函数自身内部也是可见的(这一点对递归函数的调用尤为有用)。</p><p>函数声明其实就是命名函数,换句话说,上面的函数的name属性就是timeTravel。</p><p>函数表达式是定义一个函数并把它赋值给一个变量。一般情况下,它们看起来会是这样:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> someFn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"I like to express myself..."</span>);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>函数表达式也是可以被命名的,只不过不像函数声明那样,被命名的函数表达式的名字只能在该函数内部的作用域中访问(译注:上例中的代码,关键字function后面直接跟着圆括号,此时你可以用someFn.name来访问函数名,但是输出将会是空字符串</p><p>而下例中的someFn.name会是”iHazName”,但是你却不能在iHazName这个函数体之外的地方用这个名字来调用此函数):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> someFn = <span class="function"><span class="keyword">function</span> <span class="title">iHazName</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"I like to express myself..."</span>);</span><br><span class="line"> <span class="keyword">if</span>(needsMoreExpressing) {</span><br><span class="line"> iHazName(); <span class="comment">// the function's name can be used here</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// you can call someFn() here, but not iHazName()</span></span><br><span class="line">someFn();</span><br></pre></td></tr></table></figure><p>函数表达式和函数声明的讨论远不止这些,除此之外至少还有提升(hoisting)。</p><p>提升是指函数和变量的声明被解释器移动到包含它们的作用域的顶部。</p><h3 id="具名和匿名函数"><a href="#具名和匿名函数" class="headerlink" title="具名和匿名函数"></a>具名和匿名函数</h3><p>基于我们刚刚讨论的,你肯定猜到所谓的匿名函数就是没有名字的函数。</p><p>大多数JavaScript开发者都能很快认出下例中第二个参数是一个匿名函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">someElement.addEventListener(<span class="string">"click"</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="comment">// I'm anonymous!</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>而事实上我们的marty.timeTravel方法也是匿名的:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> marty = {</span><br><span class="line"> firstName: <span class="string">"Marty"</span>,</span><br><span class="line"> lastName: <span class="string">"McFly"</span>,</span><br><span class="line"> timeTravel: <span class="function"><span class="keyword">function</span>(<span class="params">year</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.firstName + <span class="string">" "</span> + <span class="keyword">this</span>.lastName + <span class="string">" is time traveling to "</span> + year);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为函数声明必须有个名字,只有函数表达式才可能是匿名的。</p><h3 id="自调用函数表达式"><a href="#自调用函数表达式" class="headerlink" title="自调用函数表达式"></a>自调用函数表达式</h3><p>自从我们开始讨论函数表达以来,有件事我就想立马搞清楚,那就是自调用函数表达式( the Immediately Invoked Function Expression (IIFE))。</p><p>但简而言之,它就是一个没有赋值给任何变量的函数表达式,它并不等待稍后被调用,而是在定义的时候就立即执行。</p><p>下面这些浏览器控制台的截图能帮助我们理解:</p><p>首先让我们输入一个函数表达式,但是不把它赋值给任何变量,看看会发生什么:</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_4.png" alt="外部加载图片"></p><p>无效的JavaScript语法——它其实是一个缺少名字的函数声明。想让它变成一个表达式,我们只需用一对圆括号把它包裹起来:</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_5.png" alt="外部加载图片"></p><p>当把它变成一个表达式后控制台立即返回给我们这个匿名函数(我们并没有把这个函数赋值给其他变量,但是,因为它是个表达式,我们只是获取到了表达式的值)</p><p>然而,这只是实现了“自调用函数表达式”中的“函数表达式”部分。</p><p>对于“自调用”这部分,我们是通过给这个返回的表达式后面加上另外一对圆括号来实现的(就像我们调用任何其他函数一样)。</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_6.png" alt="外部加载图片"></p><p>“但是等等!Jim,我记得我以前在哪看到过把后面的那对圆括号放进表达式括号里面的情况。”</p><p>你说得对,这种语法完全正确(因为Douglas Crockford 更喜欢这种语法,才让它变得众所周知):</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_7.png" alt="外部加载图片"></p><p>这两种语法都是可用的,然而我强烈建议你读一下对这两种用法<a href="https://github.com/airbnb/javascript/issues/21#issuecomment-10203921" target="_blank" rel="noopener">有史以来最好的解释</a>。</p><p>OK,我们现在已经知道什么是IIFE了,那为什么说它很有用呢?</p><p>它可以帮助我们控制作用域,这是JavaScript中很重要的一部分!</p><p>marty对象一开始是被创建在一个全局作用域里。这意味着window对象(假定我们运行在浏览器里)里有个marty属性。</p><p>如果我们JavaScript代码都照这个写法,那么很快全局作用域下就会被大量的变量声明给填满,污染了window对象。</p><p>即使是在最理想的情况下,这都是不好的做法,因为把很多细节暴露给了全局作用域,那么,当你在声明一个对象时对它命名,而这个名字恰巧又和window对象上已经存在的一个属性同名,那么会发生什么事呢?这个属性会被覆盖掉!</p><p>比如,你打算建个“阿梅莉亚·埃尔哈特(Amelia Earhart)”的粉丝网站,你在全局作用域下声明了一个叫navigator的变量,那么我们来看一下这前后发生了些什么</p><p>(译注:阿梅莉亚·埃尔哈特是一位传奇的美国女性飞行员,不幸在1937年,当她尝试全球首次环球飞行时,在飞越太平洋期间失踪。当时和她一起在飞机上的导航员(navigator)就是下面代码中的这位佛莱得·努南(Fred Noonan)):</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_8.png" alt="外部加载图片"></p><p>呃……显然,污染全局作用域是种不好的做法。</p><p>JavaScript使用的是函数作用域(而不是块作用域,如果你是从C#或者Java转过来的,这点一定要小心!)</p><p>所以,阻止我们的代码污染全局作用域的办法就是创建一个新作用域,我们可以用IIFE来达到这个目的,因为它里面的内容只会在它自己的函数作用域里。</p><p>下面的例子里,我要先在控制台查看一下window.navigator的值,再用一个IIFE来包裹起具体的行为和数据,并把他赋值给amelia。</p><p>这个IIFE返回一个对象作为我们的“应用程序作用域”。在这个IIFE里我声明了一个navigator变量,它不会覆盖window.navigator的值。</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_9.png" alt="外部加载图片"></p><p>作为一点额外的福利,我们上面创建的IIFE其实是JavaScript模块模式(module pattern)的一个开端。</p><h3 id="typeof运算符和Object-prototype-toString"><a href="#typeof运算符和Object-prototype-toString" class="headerlink" title="typeof运算符和Object.prototype.toString"></a>typeof运算符和Object.prototype.toString</h3><p>终有一天你会遇到与此类似的情形,那就是你需要检测一个函数传进来的值是什么类型。</p><p>typeof运算符似乎是不二之选,然而,它并不是那么可靠。</p><p>例如,当我们对一个对象,一个数组,一个字符串,或者一个正则表达式使用typeof时,会发生什么呢?</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_10.png" alt="外部加载图片"></p><p>好吧,至少它能把字符串从对象,数组和正则表达式中区分出来。幸亏我们还有其它办法能从这些检测的值里得到更多准确的信息。我们可以使用Object.prototype.toString函数并且应用上我们之前掌握的call方法的知识:</p><p><img src="http://www.codingserf.com/wp-content/uploads/2014/05/jsquirks_afjq_11.png" alt="外部加载图片"></p><p>为什么我们要使用Object.prototype上的toString方法呢?</p><p>因为它可能被第三方的库或者我们自己的代码中的实例方法给重载掉。而通过Object.prototype我们可以强制使用原始的toString。</p><p>如果你知道typeof会给你返回什么,并且你也不需要知道除此之外的其他信息(例如,你只需要知道某个值是不是字符串),那么用typeof就再好不过了。</p><p>然而,如果你想区分数组和对象或者正则表达式和对象等等的,那么就用Object.prototype.toString吧!</p><h3 id="特别声明"><a href="#特别声明" class="headerlink" title="特别声明"></a>特别声明</h3><p>此文由David根据Jim Cowart的英文文章《Seven JavaScript Quirks I Wish I’d Known About》进行翻译,目的纯为个人学习所用。</p><p><a href="http://developer.telerik.com/featured/seven-javascript-quirks-i-wish-id-known-about/" target="_blank" rel="noopener">英文原文</a></p><p><a href="http://www.codingserf.com/index.php/2014/05/jsquirks/" target="_blank" rel="noopener">中文译文</a></p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/javascript%E6%80%AA%E7%99%96.png" alt="JavaScript怪癖"></p>
<p>如果对你来说 JavaScript 还是一门全新的语言,或者你是在最近的开发中才刚刚对它有所了解,那么你可能会有些许挫败感。</p>
<p>任何编程语言都有它自己的怪癖(quirks)—— 然而,当你从那些强类型的服务器端语言转向 JavaScript 的时候 ,你会感到非常困惑。</p>
<p>我就是这样!当我在几年前做全职 JavaScript 开发的时候,我多么希望关于这门语言的许多事情我都能尽早地知道。</p>
<p>我希望通过本文中分享的一些怪癖能让你免于遭受我所经历过的那些头疼的日子!</p>
</summary>
<category term="JavaScript" scheme="http://nundy.cn/tags/JavaScript/"/>
</entry>
<entry>
<title>命令模式</title>
<link href="http://nundy.cn/2017/11/22/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/"/>
<id>http://nundy.cn/2017/11/22/命令模式/</id>
<published>2017-11-22T11:45:47.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p>命令模式是将执行的命令封装,解决命令的发起者与命令的执行者之间的耦合。</p><p>每一条命令实质上是一个操作,命令的使用者不必要了解命令的执行者(执行对象)的命令接口是如何实现的、命令是如何接受的、命令是如何执行的。</p><p>所有的命令都被存储在命令对象中。</p><a id="more"></a><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><p>解决命令使用者之间的耦合,新的命令很容易加入到命令系统中,供使用者使用。</p><p>命令的使用具有一致性,多数的命令在一定程度上是简化操作方法的使用的。</p><h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><p>命令模式是对一些操作的封装,这就造成每执行一次操作都要调用一次命令对象,增加了系统的复杂度。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><h3 id="实现一个视图命令"><a href="#实现一个视图命令" class="headerlink" title="实现一个视图命令"></a>实现一个视图命令</h3><h4 id="index-html-:"><a href="#index-html-:" class="headerlink" title="index.html :"></a>index.html :</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>21.命令模式 —— 视图命令<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"titleWarp"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"photoWarp"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./index.js"</span>></span><span class="undefined"></span></span><br><span class="line"><span class="xml"><span class="tag"></<span class="name">body</span>></span></span></span><br><span class="line"><span class="xml"><span class="tag"></<span class="name">html</span>></span></span></span><br></pre></td></tr></table></figure><h4 id="index-js-:"><a href="#index-js-:" class="headerlink" title="index.js :"></a>index.js :</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*创建视图命令*/</span></span><br><span class="line"><span class="keyword">var</span> viewCommand = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">/*创建视图模板*/</span></span><br><span class="line"> <span class="keyword">var</span> tpl = {</span><br><span class="line"> <span class="comment">/*图片结构*/</span></span><br><span class="line"> product : [</span><br><span class="line"> <span class="string">'<div>'</span>,</span><br><span class="line"> <span class="string">'<img src="{#src#}" alt="">'</span>,</span><br><span class="line"> <span class="string">'<p>{#text#}</p>'</span>,</span><br><span class="line"> <span class="string">'</div>'</span>,</span><br><span class="line"> ].join(<span class="string">''</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*标题结构*/</span></span><br><span class="line"> title : [</span><br><span class="line"> <span class="string">'<div class="title">'</span>,</span><br><span class="line"> <span class="string">'<div class="main">'</span>,</span><br><span class="line"> <span class="string">'<h2>{#title#}</h2>'</span>,</span><br><span class="line"> <span class="string">'<p>{#tips#}</p>'</span>,</span><br><span class="line"> <span class="string">'</div>'</span>,</span><br><span class="line"> <span class="string">'</div>'</span></span><br><span class="line"> ].join(<span class="string">''</span>)</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*缓存字符串*/</span></span><br><span class="line"> <span class="keyword">var</span> html = <span class="string">''</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*格式化字符串*/</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">formatString</span>(<span class="params">str,obj</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> str.replace(<span class="regexp">/\{#(\w+)#\}/g</span>,<span class="function"><span class="keyword">function</span>(<span class="params">match,key</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> obj[key];</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*方法集合*/</span></span><br><span class="line"> <span class="keyword">var</span> Action = {</span><br><span class="line"> <span class="comment">/*创建方法*/</span></span><br><span class="line"> create : <span class="function"><span class="keyword">function</span> (<span class="params">data,view</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(data.length){</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>,len=data.length;i<len;i++){</span><br><span class="line"> html += formatString(tpl[view],data[i]);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> html += formatString(tpl[view],data);</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*展示方法*/</span></span><br><span class="line"> display : <span class="function"><span class="keyword">function</span> (<span class="params">container,data,view</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(data){</span><br><span class="line"> <span class="keyword">this</span>.create(data,view);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">document</span>.getElementById(container).innerHTML = html;</span><br><span class="line"> html = <span class="string">''</span>;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*命令接口*/</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">excute</span> (<span class="params"> msg </span>)</span>{</span><br><span class="line"> <span class="comment">//解析命令,确保msg.param为数组,以满足apply的参数要求</span></span><br><span class="line"> msg.param = <span class="built_in">Object</span>.prototype.toString.call(msg.param) === <span class="string">'[object Array]'</span> ? msg.param : [msg.param];</span><br><span class="line"> <span class="comment">//Action 内部调用的方法引用了this,故为保证作用域this执行传入Action</span></span><br><span class="line"> Action[msg.command].apply(Action,msg.param);</span><br><span class="line"> }</span><br><span class="line"> }());</span><br><span class="line"></span><br><span class="line"><span class="comment">/*调用命令创建标题*/</span></span><br><span class="line">viewCommand({</span><br><span class="line"> command : <span class="string">'display'</span>,</span><br><span class="line"> param : [</span><br><span class="line"> <span class="string">'titleWarp'</span>,</span><br><span class="line"> {</span><br><span class="line"> title : <span class="string">'吼吼,我是标题!'</span>,</span><br><span class="line"> tips: <span class="string">'我是提示'</span></span><br><span class="line"> },</span><br><span class="line"> <span class="string">'title'</span></span><br><span class="line"> ]</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">/*调用命令创建图片*/</span></span><br><span class="line">viewCommand({</span><br><span class="line"> command : <span class="string">'display'</span>,</span><br><span class="line"> param : [</span><br><span class="line"> <span class="string">'photoWarp'</span>,</span><br><span class="line"> [</span><br><span class="line"> {</span><br><span class="line"> src : <span class="string">'http://ozgbjelmj.bkt.clouddn.com/sky-ground.jpg'</span>,</span><br><span class="line"> text : <span class="string">'仰望星空与脚踏实地'</span></span><br><span class="line"> },{</span><br><span class="line"> src : <span class="string">'http://ountlr0uu.bkt.clouddn.com/%E6%88%91%E7%9A%84%E5%AD%A4%E7%8B%AC%E6%98%AF%E4%B8%80%E5%BA%A7%E8%8A%B1%E5%9B%AD%EF%BC%8C%E5%85%B6%E4%B8%AD%E5%8F%AA%E6%9C%89%E4%B8%80%E6%A3%B5%E6%A0%91%E3%80%82BY%E9%98%BF%E5%A4%9A%E5%B0%BC%E6%96%AF.jpg'</span>,</span><br><span class="line"> text : <span class="string">'我的孤独是一座花园,其中只有一棵树。BY阿多尼斯'</span></span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> <span class="string">'product'</span></span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="实现一个绘图命令"><a href="#实现一个绘图命令" class="headerlink" title="实现一个绘图命令"></a>实现一个绘图命令</h3><h4 id="index-html"><a href="#index-html" class="headerlink" title="index.html"></a>index.html</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>21.命令模式 —— 绘图命令<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">canvas</span> <span class="attr">id</span>=<span class="string">"canvas"</span>></span><span class="tag"></<span class="name">canvas</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./index.js"</span>></span><span class="undefined"></span></span><br><span class="line"><span class="xml"><span class="tag"></<span class="name">body</span>></span></span></span><br><span class="line"><span class="xml"><span class="tag"></<span class="name">html</span>></span></span></span><br></pre></td></tr></table></figure><h4 id="index-js"><a href="#index-js" class="headerlink" title="index.js"></a>index.js</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*创建绘图命令*/</span></span><br><span class="line"><span class="keyword">var</span> canvasCommand = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">/*获取canvas*/</span></span><br><span class="line"> <span class="keyword">var</span> canvas = <span class="built_in">document</span>.getElementById(<span class="string">'canvas'</span>);</span><br><span class="line"> <span class="keyword">var</span> ctx = canvas.getContext(<span class="string">'2d'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*方法集合*/</span></span><br><span class="line"> <span class="keyword">var</span> Action = {</span><br><span class="line"> <span class="comment">/*填充色彩*/</span></span><br><span class="line"> fillStyle : <span class="function"><span class="keyword">function</span>(<span class="params">c</span>) </span>{</span><br><span class="line"> ctx.fillStyle = c;</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*填充矩形*/</span></span><br><span class="line"> fillRect : <span class="function"><span class="keyword">function</span>(<span class="params">x,y,width,height</span>) </span>{</span><br><span class="line"> ctx.fillRect(x,y,width,height);</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*描边颜色*/</span></span><br><span class="line"> strokeStyle : <span class="function"><span class="keyword">function</span>(<span class="params">c</span>) </span>{</span><br><span class="line"> ctx.strokeStyle = c;</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*描边矩形*/</span></span><br><span class="line"> strokeRect : <span class="function"><span class="keyword">function</span>(<span class="params">x,y,width,height</span>) </span>{</span><br><span class="line"> ctx.strokeRect(x,y,width,height);</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*填充文字*/</span></span><br><span class="line"> fillText : <span class="function"><span class="keyword">function</span>(<span class="params">text,x,y</span>) </span>{</span><br><span class="line"> ctx.fillText(text,x,y);</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*开启路径*/</span></span><br><span class="line"> beginPath : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> ctx.beginPath();</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*移动画笔触电*/</span></span><br><span class="line"> moveTo : <span class="function"><span class="keyword">function</span>(<span class="params">x,y</span>) </span>{</span><br><span class="line"> ctx.moveTo(x,y)</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*画笔连线*/</span></span><br><span class="line"> lineTo : <span class="function"><span class="keyword">function</span>(<span class="params">x,y</span>) </span>{</span><br><span class="line"> ctx.lineTo(x.y);</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*绘制弧线*/</span></span><br><span class="line"> arc : <span class="function"><span class="keyword">function</span>(<span class="params">x,y,r,begin,end,dir</span>) </span>{</span><br><span class="line"> ctx.arc(x,y,r,begin,end,dir);</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*填充*/</span></span><br><span class="line"> fill : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> ctx.fill();</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*描边*/</span></span><br><span class="line"> stroke : <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> ctx.stroke();</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*命令接口*/</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">excute</span> (<span class="params"> msg </span>)</span>{</span><br><span class="line"> <span class="comment">/*判断指令是否为空*/</span></span><br><span class="line"> <span class="keyword">if</span>( !msg ){</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*判断为数组还是对象(数组有length,对象length undefined)*/</span></span><br><span class="line"> <span class="keyword">if</span>(msg.length){</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>, len = msg.length; i<len; i++){</span><br><span class="line"> <span class="built_in">arguments</span>.callee(msg[i]);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//解析命令,确保msg.param为数组,以满足apply的参数要求</span></span><br><span class="line"> msg.param = <span class="built_in">Object</span>.prototype.toString.call(msg.param) === <span class="string">'[object Array]'</span> ? msg.param : [msg.param];</span><br><span class="line"> <span class="comment">//Action 内部调用的方法引用了this,故为保证作用域this执行传入Action</span></span><br><span class="line"> Action[msg.command].apply(Action,msg.param);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}());</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*使用绘图命令*/</span></span><br><span class="line"> canvasCommand([</span><br><span class="line"> {<span class="attr">command</span> : <span class="string">'fillStyle'</span>,<span class="attr">param</span> : <span class="string">'red'</span>},</span><br><span class="line"> {<span class="attr">command</span> : <span class="string">'strokeRect'</span>,<span class="attr">param</span> : [<span class="number">0</span>,<span class="number">0</span>,<span class="number">20</span>,<span class="number">20</span>]},</span><br><span class="line"> {<span class="attr">command</span> : <span class="string">'arc'</span>,<span class="attr">param</span> : [<span class="number">20</span>,<span class="number">20</span>,<span class="number">5</span>,<span class="number">20</span>,<span class="number">20</span>,<span class="number">5</span>]}</span><br><span class="line"> ]);</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>命令模式是将执行的命令封装,解决命令的发起者与命令的执行者之间的耦合。</p>
<p>每一条命令实质上是一个操作,命令的使用者不必要了解命令的执行者(执行对象)的命令接口是如何实现的、命令是如何接受的、命令是如何执行的。</p>
<p>所有的命令都被存储在命令对象中。</p>
</summary>
<category term="设计模式" scheme="http://nundy.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>如何让你的网站遵循W3C标准</title>
<link href="http://nundy.cn/2017/11/19/W3C%E6%A0%87%E5%87%86/"/>
<id>http://nundy.cn/2017/11/19/W3C标准/</id>
<published>2017-11-18T21:17:57.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/W3C%E6%A0%87%E5%87%86.png" alt="W3C标准"></p><p>作为网站技术开发人员而言,往往是站在自己的开发角度来实施网站布署(读取数据及开发的方便性等等),而不是站在网站访问者与搜索引擎角度。</p><p>因此大部分的网站在浏览方面不够直观或是方便,特别是现在w3c的规范,更是在大部分的网站开发人员脑里一片空白。</p><a id="more"></a><p>何况百度 、google、msn、yahoo等专业搜索引擎更有自己的搜索规则及判断网页等级技术</p><p>所以网站要优化,优化的目的只有一个:符合标准,符合蜘蛛爬行的标准,更重要的是符合网站访问者浏览的方便及易用性。</p><h2 id="什么是W3C?"><a href="#什么是W3C?" class="headerlink" title="什么是W3C?"></a>什么是W3C?</h2><p>要知道W3C标准,有必要先弄清楚什么是W3C?</p><p>W3C其实就是World Wide Web Consortium,全球万维网联盟的简称。</p><p>W3C的主要职责就是确定未来万维网的发展方向,并且制定相关的推荐 (recommendation, 由于W3C是一个民间组织,没有约束性,因此只提供建议)。</p><p>HTML4.01规范建议(HTML4.01 Specification Recommendation)就是由W3C所制定的。它还负责制定XML,MathML等其他网络语言规范。</p><h2 id="什么是W3C标准?"><a href="#什么是W3C标准?" class="headerlink" title="什么是W3C标准?"></a>什么是W3C标准?</h2><p>什么是W3C标准?你的企业网站是否符合这个标准呢?</p><p>目前W3C标准已经成为高端客户设计网站的首选,我们知道国内上网者中,用IE浏览器的比较多,但从国内或全世界的上网客户来看,有些客户并不是用IE来上网浏览内容的,他们会用一些其它的浏览工具如: Netscape, Mozilla, FireFox,Opera等等,如果您的网站不是采用的w3c标准,使用其它浏览器的用户,就无法看到 您的网站。即:此标准是国际上的通用标准,符合此标准的网站,能用任何浏览器来浏览您的网站。</p><p>如果您的网站不符合这个标准,那么一些客户就无法看到您的企业,您的产品,虽然这部分客户比较少,但也毕竟是一部分客户,丢掉任何潜在客户,对企业来说,都是损失。</p><p>从HTML诞生至今,在协议不断发展的过程中,各大浏览器产商为了“鼓励”人们制作网页,从而“纵容”了人们各种各样的不良习惯。 同时,它们为了占据“标准制订”的制高点,也不遗余力地发展出各种特性加入到HTML和相关的技术里。于是,有了现在乱糟糟的局面:</p><p>各种各样语法错误的HTML都能够得到各种浏览器的很好的“支持”。而这样的局面如果任其发展下去,我们的WWW万维网今后就会一团糟。</p><p>W3C,万维网联盟,一个负责制订并维护着我们所熟悉的万维网的诸多标准和协议的组织,在1999年12月24日在先前发布的HTML4.0版本的基础上修正发布了HTML4.01并将之作为建议标准。2000年1月26 日在HTML4.01的基础上发布了其XML版本XHTML1.0,并将之作为建议标准(之后发布的XHTML1.1作为候选建议标准)。</p><h2 id="如何符合w3c规范?"><a href="#如何符合w3c规范?" class="headerlink" title="如何符合w3c规范?"></a>如何符合w3c规范?</h2><p>1、 确保所有的标签都使用小写字母</p><p>2、 确保所有的属性值都放在引号里</p><p>3、 确保所有成对标签出现的顺序、不成对的标签都用/>结束, ”/”和”>”之间不要有空格</p><h2 id="改善网站"><a href="#改善网站" class="headerlink" title="改善网站"></a>改善网站</h2><h3 id="为页面添加正确的DOCTYPE"><a href="#为页面添加正确的DOCTYPE" class="headerlink" title="为页面添加正确的DOCTYPE"></a>为页面添加正确的DOCTYPE</h3><p>很多设计师和开发者都不知道什么是DOCTYPE,DOCTYPE有什么用。DOCTYPE是document type的简写。主要用来说明你用的XHTML或者HTML是什么版本。浏览器根据你DOCTYPE定义的DTD(文档类型定义)来解释页面代码。所以,如果你不注意设置了错误的DOCTYPE,结果会让你大吃一惊。XHTML1.0提供了三种DOCTYPE可选择:</p><ul><li>过渡型(Transitional )</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></span></span><br></pre></td></tr></table></figure><ul><li>严格型(Strict )</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"></span></span><br></pre></td></tr></table></figure><ul><li>框架型(Frameset )</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"></span></span><br></pre></td></tr></table></figure><p>对于我们初级改善来说,只要选用过渡型的声明就可以了。它依然可以兼容你的表格布局、表现标识等,不至于让你觉得变化太大,难以掌握。</p><h3 id="设定一个名字空间(Namespace)"><a href="#设定一个名字空间(Namespace)" class="headerlink" title="设定一个名字空间(Namespace)"></a>设定一个名字空间(Namespace)</h3><p>直接在DOCTYPE声明后面添加如下代码:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">XMLns</span>=<span class="string">"http://www.w3.org/1999/xhtml"</span> ></span></span><br></pre></td></tr></table></figure><p>一个namespace是收集元素类型和属性名字的一个详细的DTD,namespace声明允许你通过一个在线地址指向来识别你的namespace。只要照样输入代码就可以。</p><h3 id="声明你的编码语言"><a href="#声明你的编码语言" class="headerlink" title="声明你的编码语言"></a>声明你的编码语言</h3><p>为了被浏览器正确解释和通过标识校验,所有的XHTML文档都必须声明它们所使用的编码语言。代码如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"Content-Type"</span> <span class="attr">content</span>=<span class="string">"text/html; charset=GB2312"</span> /></span></span><br></pre></td></tr></table></figure><p>这里声明的编码语言是简体中文GB2312,你如果需要制作繁体内容,可以定义为BIG5。</p><h3 id="用小写字母书写所有的标签"><a href="#用小写字母书写所有的标签" class="headerlink" title="用小写字母书写所有的标签"></a>用小写字母书写所有的标签</h3><p>XML对大小写是敏感的,所以,XHTML也是大小写有区别的。所有的XHTML元素和属性的名字都必须使用小写。否则你的文档将被W3C校验认为是无效的。例如下面的代码是不正确的:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">TITLE</span>></span>公司简介<span class="tag"></<span class="name">TITLE</span>></span></span><br></pre></td></tr></table></figure><p>正确的写法是:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">title</span>></span>公司简介<span class="tag"></<span class="name">title</span>></span></span><br></pre></td></tr></table></figure><h3 id="为图片添加-alt-属性"><a href="#为图片添加-alt-属性" class="headerlink" title="为图片添加 alt 属性"></a>为图片添加 alt 属性</h3><p>为所有图片添加alt属性。alt属性指定了当图片不能显示的时候就显示供替换文本,这样做对正常用户可有可无,但对纯文本浏览器和使用屏幕阅读机的用户来说是至关重要的。只有添加了alt属性,代码才会被W3C正确性校验通过。注意的是我们要添加有意义的alt属性,象下面这样的写法毫无意义:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"logo_unc_120x30.gif"</span> <span class="attr">alt</span>=<span class="string">"logo_unc_120x30.gif"</span>></span></span><br></pre></td></tr></table></figure><p>正确的写法:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"logo_unc_120x30.gif"</span> <span class="attr">alt</span>=<span class="string">"UNC公司标志,点击返回首页"</span>></span></span><br></pre></td></tr></table></figure><h3 id="给所有属性值加引号"><a href="#给所有属性值加引号" class="headerlink" title="给所有属性值加引号"></a>给所有属性值加引号</h3><p>在HTML中,你可以不需要给属性值加引号,但是在XHTML中,它们必须被加引号。</p><p>例:height=”100”,而不能是height=100。</p><h3 id="关闭所有的标签"><a href="#关闭所有的标签" class="headerlink" title="关闭所有的标签"></a>关闭所有的标签</h3><p>在XHTML中,每一个打开的标签都必须关闭。就象这样:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">p</span>></span>每一个打开的标签都必须关闭。<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">b</span>></span>HTML可以接受不关闭的标,XHTML就不可以。<span class="tag"></<span class="name">b</span>></span></span><br></pre></td></tr></table></figure><p>这个规则可以避免HTML的混乱和麻烦。</p><p>举例来说:如果你不关闭图像标签,在一些浏览器中就可能出现CSS显示问题。</p><p>用这种方法能确保页面和你设计的一样显示。</p><p>需要说明的是:空标签也要关闭,在标签尾部使用一个正斜杠”/“来关闭它们自己。例如:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">br</span> /></span></span><br><span class="line"><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"webstandards.gif"</span> /></span></span><br></pre></td></tr></table></figure><p>经过上述七个规则处理后,页面就基本符合XHTML1.0的要求。但我们还需要校验一下是否真的符合标准了。</p><p>我们可以利用W3C提供的<a href="http://validator.w3.org/" target="_blank" rel="noopener">免费校验服务</a>,发现错误后逐个修改。</p><p>当最后通过了XHTML验证,恭喜你已经向网站标准迈出了一大步。不是想象中的那么难吧!</p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/W3C%E6%A0%87%E5%87%86.png" alt="W3C标准"></p>
<p>作为网站技术开发人员而言,往往是站在自己的开发角度来实施网站布署(读取数据及开发的方便性等等),而不是站在网站访问者与搜索引擎角度。</p>
<p>因此大部分的网站在浏览方面不够直观或是方便,特别是现在w3c的规范,更是在大部分的网站开发人员脑里一片空白。</p>
</summary>
<category term="Network" scheme="http://nundy.cn/tags/Network/"/>
</entry>
<entry>
<title>《老情书》-- 张嘉佳</title>
<link href="http://nundy.cn/2017/11/16/%E8%80%81%E6%83%85%E4%B9%A6/"/>
<id>http://nundy.cn/2017/11/16/老情书/</id>
<published>2017-11-15T20:35:09.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p>老太太说:我就特别看不起你们这帮年轻人,二三十岁就叨叨说平平淡淡才是真。你们配吗?</p><p>我上山下乡,知青当过,饥荒挨过,这你们没办法体会。</p><p>但我今儿平安喜乐,没事打几圈牌,早睡早起,你以为凭空得来的心静自然凉?</p><p>老和尚说终归要见山是山,但你们经历见山不是山了吗?</p><p>不趁着年轻拔腿就走,去刀山火海,不入世就自以为出世,以为自己是活佛涅槃来的?</p><p>我的平平淡淡是苦出来的,你们的平平淡淡是懒惰,是害怕,是贪图安逸,是一条不敢见世面的土狗。</p>]]></content>
<summary type="html">
<p>老太太说:我就特别看不起你们这帮年轻人,二三十岁就叨叨说平平淡淡才是真。你们配吗?</p>
<p>我上山下乡,知青当过,饥荒挨过,这你们没办法体会。</p>
<p>但我今儿平安喜乐,没事打几圈牌,早睡早起,你以为凭空得来的心静自然凉?</p>
<p>老和尚说终归要见山是山,
</summary>
<category term="句子迷" scheme="http://nundy.cn/tags/%E5%8F%A5%E5%AD%90%E8%BF%B7/"/>
</entry>
<entry>
<title>学长de面试题走一波</title>
<link href="http://nundy.cn/2017/11/13/interview-test/"/>
<id>http://nundy.cn/2017/11/13/interview-test/</id>
<published>2017-11-13T12:29:44.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/interview-test.jpg" alt="学长面试题走一波"></p><p>永远也不要忘记能够笑的坚强 (๑╹◡╹)ノ”””</p><p>不想看书,不想Coding,刷一波题吧!</p><a id="more"></a><h2 id="The-first-day"><a href="#The-first-day" class="headerlink" title="The first day !"></a>The first day !</h2><h3 id="从输入-URL到页面加载发生了什么?"><a href="#从输入-URL到页面加载发生了什么?" class="headerlink" title="从输入 URL到页面加载发生了什么?"></a>从输入 URL到页面加载发生了什么?</h3><h4 id="浏览器查找域名对应的-IP"><a href="#浏览器查找域名对应的-IP" class="headerlink" title="浏览器查找域名对应的 IP"></a>浏览器查找域名对应的 IP</h4><p>(1) 浏览器搜索自己的 DNS 缓存;</p><p>(2) 搜索操作系统中的 DNS 缓存;</p><p>(3) 操作系统向它的本地 DNS 服务器(LDNS)发送一个 DNS 查询报文(包含需要被转换的主机名),LDNS 查询自己的 DNS 缓存,若查找失败则发起一个迭代 DNS解析请求进行分级查询:</p><ul><li><p>由于根域名服务器的 NS 记录和 IP 记录一般不会变化,所以内置在 DNS 服务器中,本地 DNS 服务器将报文转发到所有的根域名服务器,询问顶级域名服务器的 NS 记录,最先回复的根域名服务器被缓存,以后只向这台服务器发送请求, 根域名服务器向本地 DNS 服务器返回对应的顶级域名服务器的 NS 记录和 IP 记录列表</p></li><li><p>本地 DNS 服务器向这些所有 TLD 服务器(顶级域名服务器)发送查询报文,TLD 服务器以次级域名服务器的 NS 记录及其 IP 地址作为响应</p></li><li><p>本地 DNS 服务器向这些所有 SLD 服务器(次级域名服务器)发送查询报文,SLD 服务器以主机名的 IP 地址作为响应。</p></li></ul><p>(4) LDNS 将得到的 IP 地址返回给操作系统,同时自己也将 IP 缓存起来</p><p>(5) 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 缓存起来</p><p>(6) 至此,浏览器已经得到了域名对应的 IP</p><p>备注:</p><p>(1) 在一个请求链中,当某 DNS 服务器接收到一个 DNS 回答时,它能将该回答中的信息缓存在本地存储器中</p><p>(2) 每一级域名都有自己的 NS 记录,NS 记录指向该级域名的域名服务器。这些服务器知道下一级域名的各种记录。所谓”分级查询”,就是从根域名开始,依次查询每一级域名的<br>NS 记录,直到查到最终的 IP 地址</p><p>(3) 主机名.次级域名.顶级域名.根域名 对应 host.sld.tld.root,根域名.root 对于所有域名都是一样的,所以平时是省略的,主机名也称三级域名</p><p>(4) 根域名下有子域,才有顶级域名;顶级域名下有子域,才有对应的次级域名;次级域名下有主机,才有主机名</p><h4 id="建立-TCP-连接(三次握手)"><a href="#建立-TCP-连接(三次握手)" class="headerlink" title="建立 TCP 连接(三次握手)"></a>建立 TCP 连接(三次握手)</h4><p>(1)客户端向 IP 地址对应的服务器发送一个建立连接的请求</p><p>(2)服务器接到请求后发送同意连接的信号</p><p>(3)客户端接到同意连接的信号后,再次向服务器发送确认信号,至此,客户端和服务器建立了连接</p><p>备注:在客户和服务器上分别有一个套接字与该连接相关联,客户端和服务器进程就可以通过套接字接口(socket,进程通过套接字接口向网络发送报文和从网络接收报文)访问 TCP</p><h4 id="发送-HTTP-请求"><a href="#发送-HTTP-请求" class="headerlink" title="发送 HTTP 请求"></a>发送 HTTP 请求</h4><p>(1)浏览器根据 URL 内容生成 HTTP 请求,请求中包含文件的位置、请求的方式、请求中客户端向服务端传递的数据、请求的一些附加信息等,浏览器向该服务器发送一个 HTTP<br>请求报文</p><p>(2)服务器接到请求后,从其中的存储器(内存或磁盘)检索出对象并封装到 HTTP 响应报文,向客户发送 HTTP 响应报文,若请求成功,响应报文中会包含客户端请求的 HTML<br>文件</p><h4 id="浏览器解析并渲染页面"><a href="#浏览器解析并渲染页面" class="headerlink" title="浏览器解析并渲染页面"></a>浏览器解析并渲染页面</h4><p>浏览器边解析边渲染。首先浏览器解析 HTML 文件构建 DOM 树,解析 CSS 文件构建CSSOM 树,接着在 DOM 树和 CSSOM 树的基础上构建渲染树,一旦渲染树构建完成,浏<br>览器就开始布局和显示(绘制‘paint’)页面元素。</p><p>若解析过程中,需要请求外部资源如图像、JS 文件等,除了 JS 文件,浏览器将异步发送HTTP 请求以获得相应资源,不会影响 HTML 文档进行加载,若是 JS 文件,HTML 会挂起渲染过程,等到重新请求获得的 JS 文件加载完毕并解析执行完毕后,才会继续 HTML 的渲染,JS 的解析由浏览器的 JS 解析引擎完成(涉及 JS 的单线程运行特性和事件循环执行机制:一个主线程+一个任务队列)</p><h4 id="断开连接(四次挥手)"><a href="#断开连接(四次挥手)" class="headerlink" title="断开连接(四次挥手)"></a>断开连接(四次挥手)</h4><p>(1)主机向服务器发送断开连接的请求(断开)</p><p>(2)服务器发送确认收到请求的信号(确认)</p><p>(3)服务器向主机发送断开通知(断开)</p><p>(4)主机接到断开通知后断开连接并反馈一个确认信号,服务器收到确认信号后断开连接(确认)</p><h4 id="关于加载顺序"><a href="#关于加载顺序" class="headerlink" title="关于加载顺序"></a>关于加载顺序</h4><p>(1) 当浏览器获得一个 html 文件, 会”自上而下”加载, 并在加载过程中进行解析渲染. 下载和渲染是同时进行的</p><p>(2) 在渲染到页面的某一部分时, 其上面到所有部分都已经下载完成(并不是说所有关联元素都已经下载完)</p><p>(3) 如果加载过程中遇到外部 css 文件, 浏览器会发出一个请求, 来获取 css 文件.</p><p>(4) 样式表在下载完成后, 将和以前下载的所有样式表一起进行解析, 解析完成后, 将对此前所有元素(含以前已经渲染的)重新进行渲染</p><p>(5)遇到图片资源, 浏览器会发出请求获取图片资源. 这是异步请求, 并不会影响 html 文档进行加载</p><p>(6)当文档加载过程中遇到 js 文件, html 文档会挂起渲染(加载解析渲染同步)的线程, 不仅要等待文档中js文件加载完毕, 还要等待解析执行完毕, 才可以恢复html文档的渲<br>染线程. 即 js 的加载不能并行下载和解析</p><p>原因: js 有可能会修改 DOM, 比如 document.write. 这意味着, 在 js 执行完成前, 后续所有资源的下载可能是没有必要的, 这是 js 阻塞后续资源下载的根本<br>原因. 所以一般将外部引用的 js 文件放在body的结束标签前</p><p>(7) 虽然 css 文件的加载不影响 js 文件的加载,但是却影响 js 文件的执行, 即使 js 文件内只有一行代码, 也会造成阻塞</p><p>原因: 可能会有: var width = $(‘#id’).width(). 这意味着, 在 js 代码执行前, 浏览器必须保证 css 文件已下载和解析完成。这也是 css 阻塞后续 js 的根本原因。</p><p>办法:当 js 文件不需要依赖 css 文件时,可以将 js 文件放在头部 css 的前面。当然除了,link标签这种形式,内联style这种样式定义,在考虑阻塞时也要考虑</p><p>(8)js,css 中如果有重定义, 后定义函数将覆盖前定义函数</p><h4 id="主要解析过程"><a href="#主要解析过程" class="headerlink" title="主要解析过程"></a>主要解析过程</h4><p>(1) 浏览器解析 html 代码, 创建一棵 DOM 树</p><p>(2) 浏览器解析 CSS 代码, 创建 CSSOM 树(CSS Object Model)</p><p>CSSOM 树是附在 DOM 结构上的样式的一种表示方式,与 DOM 树的呈现方式一样,只是每个节点都带上样式,包括明确定义的和隐式继承的</p><p>CSS 是一种渲染阻塞资源,需要解析完毕后才能进入生成渲染树的环节,CSS 并不像 HTML 能够执行部分并显示,因为 CSS 具有继承属性,后面定义的样式会覆盖或修改前面的样式。所以 CSS 也会阻塞脚本。</p><p>(3) JavaScript 解析并执行</p><p>(4) 构建 DOM 树和 CSSOM 树后,下一步就是构建一棵渲染树(rendering tree)</p><p>DOM 树完全与 html 标签一一对应, 但是渲染树会忽略掉不需要渲染的元素, 比如head, display: none 的元素等</p><p>(5) 生成布局,基于 HTML 页面的 meta viewport 标签生成布局</p><p>(6) 绘制</p><h3 id="对浏览器内核的理解"><a href="#对浏览器内核的理解" class="headerlink" title="对浏览器内核的理解"></a>对浏览器内核的理解</h3><p>主要分成两部分:渲染引擎和 js 引擎。最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。</p><h4 id="渲染引擎(The-rendering-engine):"><a href="#渲染引擎(The-rendering-engine):" class="headerlink" title="渲染引擎(The rendering engine):"></a>渲染引擎(The rendering engine):</h4><p>职责就是渲染,即在浏览器窗口中显示所请求的内容。默认情况下,渲染引擎可以显示 html、xml 文档及图片,也可以借助插件显示一些其他类型的数据,比如使用 PDF 阅读器插件可以显示 PDF 格式数据。</p><h4 id="渲染主流程"><a href="#渲染主流程" class="headerlink" title="渲染主流程"></a>渲染主流程</h4><p>渲染引擎获得所请求的文档后 => 解析 html 以构建 DOM 树 => 加载并加息 CSS,构建 CSSOM 树 => 构建 render 树 => 布局 render 树 => 绘制 render 树</p><p>详细过程:渲染引擎开始解析 html,并将标签转化为内容树中的 DOM 节点,接着解析 CSS文件或者 style 中的样式,构建 CSSOM 树,CSSOM 树以及 DOM 中的可见性指令将被用来构建另一棵树,即 render 树,Render 树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。Render 树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。再下一步就是绘制,即遍历 render 树,并使用 UI后端层绘制每个节点。</p><h4 id="JS-引擎"><a href="#JS-引擎" class="headerlink" title="JS 引擎"></a>JS 引擎</h4><p>解析和执行 javascript 来实现网页的动态效果。</p><h3 id="GET-和-POST-的区别"><a href="#GET-和-POST-的区别" class="headerlink" title="GET 和 POST 的区别"></a>GET 和 POST 的区别</h3><p>(1) GET 是向服务器发索取数据的一种请求,而 POST 是向服务器提交数据的一种请求,在FORM 表单中 Method 默认为 GET,实质上两者只是发送机制不同,并不是一个取一个发</p><p>(2) GET 请求的数据会暴露在 URL,POST 把请求的数据放置在 HTTP 请求的主体中,且数据格式不限</p><p>(3) 对数据长度的限制:特定的浏览器和服务器对 URL 的长度有限制,各个服务器对 POST 提交的数据大小有限制,Apache、IIS 都有各自的配置,一般比 GET 能够传输的数据多</p><p>(4) 对数据类型的限制:GET 只允许 ASCII 字符,POST 没有限制,也允许二进制数据</p><p>(5) POST 的安全性比 GET 高</p><p>(6) GET 形式的 URL 对搜索引擎更加友好,有助于提高搜索引擎排名,其他网站和用户可以链接到 get 形式的 url,无论用户的访问,还是搜索引擎的收录而相应提高了页面排名,能够直接或间接提高网站浏览量;POST 使用的 URL 有时会阻止爬虫和搜索引擎的访问</p><p>(7) GET 的 URL 可以收藏为书签且参数会保留在浏览器历史中,POST 的不能收藏且参数不会保存在浏览器历史中</p><p>(8) 后退/刷新:GET 无害,对于 POST 数据会被重新提交(浏览器应该告知用户数据会被重新提交)</p><h4 id="何时使用-POST"><a href="#何时使用-POST" class="headerlink" title="何时使用 POST"></a>何时使用 POST</h4><p>(1) 请求的结果有持续性的副作用,例如,数据库内添加新的数据</p><p>(2) 当使用 GET 进行表单提交会让 URL 过长时</p><p>(3) 要传送的数据不是采用 7 位的 ASCII 编码</p><h4 id="何时使用-GET"><a href="#何时使用-GET" class="headerlink" title="何时使用 GET"></a>何时使用 GET</h4><p>(1) 请求是为了查找资源,HTML 表单数据仅用来帮助搜索</p><p>(2) 请求的结果无持续性的副作用</p><p>(3) 收集的数据及表单内的输入字段名称的总长不超过 1024 个字符(为了兼容 IE,IE支持的最大长度是 2083 个字符)</p><h4 id="GET-相对-POST-的优势"><a href="#GET-相对-POST-的优势" class="headerlink" title="GET 相对 POST 的优势"></a>GET 相对 POST 的优势</h4><ul><li><p>请求中的 URL 可以被手动输入、被存在书签或者历史记录中、可以被搜索引擎收录</p></li><li><p>可以重复的交互,比如查询、读取,用 GET</p></li><li><p>不可重复的交互,比如创建/修改记录,用 POST,因为 POST 不会被缓存,所以浏览器不会多次提交</p></li></ul><h4 id="其他一些-HTTP-请求方法"><a href="#其他一些-HTTP-请求方法" class="headerlink" title="其他一些 HTTP 请求方法"></a>其他一些 HTTP 请求方法</h4><ul><li><p>HEAD 与 GET 相同,但只返回 HTTP 报头,不返回文档主体。</p></li><li><p>PUT 上传指定的 URI 表示。</p></li><li><p>DELETE 删除指定资源。</p></li><li><p>OPTIONS 返回服务器支持的 HTTP 方法。</p></li><li><p>CONNECT 把请求连接转换到透明的 TCP/IP 通道。</p></li></ul><h3 id="如何加快页面加载速度"><a href="#如何加快页面加载速度" class="headerlink" title="如何加快页面加载速度"></a>如何加快页面加载速度</h3><p>减少 HTTP 访问次数(适当使用 DataURL、CSS Sprite)、CDN、minify、服务器增加缓存或浏览器本地缓存、CSS 放前面 JS 放后面、图片压缩等。</p><h3 id="如果一个元素-absolute,没设-left、top,位置是哪里"><a href="#如果一个元素-absolute,没设-left、top,位置是哪里" class="headerlink" title="如果一个元素 absolute,没设 left、top,位置是哪里"></a>如果一个元素 absolute,没设 left、top,位置是哪里</h3><p>相当于 static 的默认位置。左上角之所以不准确,是因为如果父元素在本元素之前如果还有子元素的话,那就不是左上角了。</p><h3 id="HTTP-状态码"><a href="#HTTP-状态码" class="headerlink" title="HTTP 状态码"></a>HTTP 状态码</h3><p>状态码是由 3 位数组成,表示服务器对 HTTP 请求的响应状态,第一个数字定义了响应的类别,且有五种可能取值:</p><ul><li><p>1XX Informational 信息性状态码,表示接受的请求正在处理</p></li><li><p>2XX Success 成功状态码,表示请求正常处理完毕</p></li><li><p>3XX Redirection 重定向状态码,表示需要客户端需要进行附加操作</p></li><li><p>4XX Client Error 客户端错误状态码,表示服务器无法处理请求</p></li><li><p>5XX Server Error 服务器错误状态码,表示服务器处理请求出错</p></li></ul><h4 id="常用状态码及对应的意思"><a href="#常用状态码及对应的意思" class="headerlink" title="常用状态码及对应的意思"></a>常用状态码及对应的意思</h4><p>200 204 206(Range, Content-Range)</p><p>301(Location,更新书签) 302(不更新书签) 303 304(If-Match, If-Modified-Since,If-None-Match, If-Unmmodified-Since, If-Range) 307</p><p>400(语法错误) 401(WWW-Authenticate, Authorization) 403 404</p><p>500 503(超负荷或正在停机维护,Retry-After)</p><p>302 指定使用原有请求方法,303 指定使用 GET 方法,307 不指定;</p><p>401 Unauthorized未被授权:该状态码表示发送的请求需要有通过HTTP认证(Basic认证,Digest 认证)的认证信息。返回含有 401 的响应,必须在头部包含 WWW-Authenticate以指明服务器需要哪种方式的认证。当客户端再次请求该资源的时候,需要在请求头中的Authorization 包含认证信息。</p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/interview-test.jpg" alt="学长面试题走一波"></p>
<p>永远也不要忘记能够笑的坚强 (๑╹◡╹)ノ”””</p>
<p>不想看书,不想Coding,刷一波题吧!</p>
</summary>
<category term="神的记事本" scheme="http://nundy.cn/tags/%E7%A5%9E%E7%9A%84%E8%AE%B0%E4%BA%8B%E6%9C%AC/"/>
</entry>
<entry>
<title>仰望星空、脚踏实地</title>
<link href="http://nundy.cn/2017/11/12/sky-ground/"/>
<id>http://nundy.cn/2017/11/12/sky-ground/</id>
<published>2017-11-11T16:11:56.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/sky-ground.jpg" alt="仰望星空与脚踏实地"></p><p>文章原名为《2016年前端技术观察》。无意中翻出这篇文章,给自己带来了很多感触。所以分享出来!</p><p>无论前端学习,还是大学生活,耳边少了亲人的叮咛,老师的训诫,我们或许都已变得太过浮躁,泼泼冷水,清醒一下也好!</p><p>评论中有人称赞,有人批评。但仁者见仁,智者见智,争辩是没有意义的。如果文章中某一点触动了你,也不枉浪费一番时间!</p><a id="more"></a><p>以下为《2016年前端技术观察》:</p><h2 id="作者"><a href="#作者" class="headerlink" title="作者"></a>作者</h2><p>曹刘阳,网名阿当,资深Web技术专家,必杀技 “前端开发、软件架构和敏捷开发”。先后任职于雅虎、淘宝、新浪企业。在“如何编写高质量代码”领域研究颇深,《编写高质量代码——Web前端开发修炼之道》作者。<a href="http://geek.csdn.net/news/detail/128912" target="_blank" rel="noopener">原文链接</a></p><h2 id="写在前面的话"><a href="#写在前面的话" class="headerlink" title="写在前面的话"></a>写在前面的话</h2><p>2015年底,我在微博上谈起对Sass、React和Angular的不满,出乎意料地引起了一场论战。从知乎至微博,与我相识或不相识的许多人卷了进来。这场争论中很多人的言论让我意识到,前端这两年居然乱到了这个地步——我不怕技术大爆炸,但怕劣币驱逐良币,怕人们丢了思考,陷入不必要的泥潭。</p><p>大战中我也被很多人贴了标签——守旧的老人,懒惰不思进取又倚老卖老。有同学笑称我是气宗,看不惯剑宗。</p><p>然而对于技术,无论是CSS重构、RIA、JavaScript框架、打通前后端的全栈、H5、移动前端开发、SPA、敏捷、Hybrid、Canvas2d和WebGL,我都非常积极甚至超前。对这两三年流行起来的技术,并非因为守旧而排斥,而是确实做过思考,结合自己过往的经验,持很重的怀疑态度。</p><h2 id="希望读者警惕两件事:"><a href="#希望读者警惕两件事:" class="headerlink" title="希望读者警惕两件事:"></a>希望读者警惕两件事:</h2><p>跨界而来的技术专家和他们的解决方案,注重个人能力忽略团队合作的Geek。他们的方案出发点可能是好的,但其实有可能会有更具性价比也更容易落地更少隐患的技术方案。一个是视野问题,另一个是实践经验问题。跨界而来的专家往往能带来不同领域的视野,这是好事,但他们同时也缺乏本领域的实践经验。Geek是个有点酷的词,意味着高手,但同时也意味着门槛,而一旦上升到团队合作,门槛和合作时的内耗,以及团队成员流失后交接的隐患就会成倍上升。</p><p>只提优点不提缺点的技术布道。布道某种意义上和销售没区别,会选择性地给听众传达信息。因此在面对布道时,不要只听官方说的好处,要自己思考它的缺点——技术方案不会只有优点而没缺点,做任何的技术选型都是在对优点和缺点做权衡。而缺点,别指望布道者会主动告诉你。如果你有足够工作经验,请结合实际经验勇敢地提问,如果没有太多工作经验,就真的很麻烦了,你需要加倍谨慎和怀疑。</p><p>经验、反思、置疑精神,希望同学们可以在当下这前端界的乱世下,通过这三个武器寻找到自己的路。</p><h2 id="关于CSS预处理器:"><a href="#关于CSS预处理器:" class="headerlink" title="关于CSS预处理器:"></a>关于CSS预处理器:</h2><p>我对CSS预处理器一直无感。不是说它们毫无价值,而是说,性价比不高。关于预处理器的优点,官方都说过了,对吧?我就不赘述了,说说缺点吧。</p><p>预处理器的引入,为本来很简单的CSS书写带来了编译的步骤,也带来了以下几个问题:</p><p>所有开发团队成员需要安装预处理工具,需要学习预处理器语法,增加了上手门槛。</p><p>不可以直接对编译后的CSS文件进行修改,要统一修改源文件。这在调试时,增加了工作量。</p><p>源文件增加了很多原生CSS不具备的功能,如定义变量和嵌套,这在提供方便的同时,也引入了一些不必要的麻烦,比如嵌套带来的副作用——CSS选择符权重增加,HTML结构与样式挂钩耦合,以及预处理器层面需要增加规范(变量和mix模块放在什么地方定义、文件如何拆解以配合@import导入、什么时候定义新的class名、什么时候使用嵌套),以方便团队合作。</p><p>如果需要跨团队合作,如将代码和另一支不使用预处理器的团队的代码做整合,会怎么样?</p><p>如果团队有人离职,他维护的代码在交接上是否会需要多进行一些沟通?有新人入职,是否在正式工作前,需要学习预处理器语法,以及公司关于预处理器的使用规范,导致进入工作状态的成本变高了?如果团队在新人入职后的新兵训练营工作做得并不好,又会怎么样?事实上,很多公司压根没有什么新兵训练营计划吧?</p><p>CSS预处理器带来的这些麻烦,一旦上升到团队合作,成本和风险就大了,因而并不是笔低成本买卖。再看看它号称的收益,是否真的没有别的解决方案了,如果有话,即使是单兵作战,是不是也不是什么高收益的选择了呢?</p><p>变量、mixin、扩展/继承的替代方案 —— 让HTML标签挂多个class</p><p>Sass变量方案:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">$hightLight: red;</span><br><span class="line">.title{color:$hightLight;font-size:20px;}</span><br><span class="line">.title2{ color:$hightLight;fontsize:16px;}</span><br><span class="line"><h1 class="title">标题1</h1></span><br><span class="line"><h2 class="title2">标题2</h1></span><br></pre></td></tr></table></figure><p>多class方案:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">.hightLight{color: red;}</span><br><span class="line">.title{ font-size:20px;}</span><br><span class="line">.title2{font-size:16px;}</span><br><span class="line"><h1 class=”title highLight”>标题1</h1></span><br><span class="line"><h2 class=”title2 highLight”>标题2</h1></span><br></pre></td></tr></table></figure><p>Sass继承方案:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.message{border: 1px solid #ccc; padding:10px; color: #333;}</span><br><span class="line">.success{@extend .message; border-color:green;}</span><br><span class="line">.error{@extend .message; border-color:red;}</span><br><span class="line">.warning{@extend .message; border-color:yellow;}</span><br><span class="line"><div class="success">成功</div></span><br><span class="line"><div class="error">错误</div></span><br><span class="line"><div class="warning">警告</div></span><br></pre></td></tr></table></figure><p>多class方案:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.message{border: 1px solid #ccc; padding:10px; color: #333;}</span><br><span class="line">.success{border-color: green;}</span><br><span class="line">.error{border-color: red;}</span><br><span class="line">.warning{border-color: yellow;}</span><br><span class="line"><div class="message success">成功</div></span><br><span class="line"><div class="message error">错误</div></span><br><span class="line"><div class="message warning">警告</div></span><br></pre></td></tr></table></figure><p>嵌套的副作用以及低成本的替换方案 —— 长命名</p><p>嵌套会带来HTML结构与CSS选择符耦合、选择符权重增加的副作用,而短命名会带来易冲突的问题,如果代码一旦进行修改,耦合就会带来不必要的麻烦,而CSS作为层叠样式表,选择符的权重直接决定了当不同的选择符样式出现冲突时,使用哪个选择符定义的样式。预处理器看似提供了作用域,其实只是个障眼法,编译出来的结果,只是普通的子孙选择器而已。如果依赖预处理器的这种错觉,采用短命名的话,当多人合作或采用低质量的第三方代码时,很可能会产生冲突。你能保证自己不直接使用短命名,一定会嵌套着使用,但无法保证别人不直接在全局使用短命名。如果代码持续维护,如果多人合作,如果DHTML很多,这三个副作用都会是不小的麻烦。</p><p>作为一门DSL,CSS没有编程特性,没有作用域,是很正常的,同样是没有编程特性的另一个工具Redis,就使用长命名很好地解决了作用域问题,比如Redis在实践过程中,被鼓励这样的命名:usersInfo:1000:name、usersInfo:1001:age。其实CSS也是种键值对的形式,选择符可以嵌套,但也可以完全借长命名来解决作用域问题。嵌套带来的作用域是在预处理器中的障眼法而已,如果和第三方合作,需要一些必要的约定,很脆弱。而长命名带来的作用域,是非常强的真作用域,只要命名空间前辍不冲突,怎么命名都是安全的。</p><p>Sass嵌套方案:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">.box{</span><br><span class="line">.header{}</span><br><span class="line">.body{}</span><br><span class="line">.footer{}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><div class=”box”></span><br><span class="line"><div class=”header”>header</div></span><br><span class="line"><div class=”body”>body</div></span><br><span class="line"><div class=”footer”>footer</div></span><br><span class="line"></div></span><br></pre></td></tr></table></figure><p>长命名方案:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">.box{}</span><br><span class="line">.box_header{}</span><br><span class="line">.box_body{}</span><br><span class="line">.box_footer{}</span><br><span class="line"></span><br><span class="line"><div class=”box”></span><br><span class="line"><div class=”box_header”>header</div></span><br><span class="line"><div class=”box_body”>body</div></span><br><span class="line"><div class=”box_footer”>footer</div></span><br><span class="line"></div></span><br></pre></td></tr></table></figure><p>所以对于CSS预处理器,我个人并不支持,更推荐使用原生CSS技巧来解决问题。性价比高,副作用小。</p><h2 id="跨界、CoffeeScript、TypeScript和ES6"><a href="#跨界、CoffeeScript、TypeScript和ES6" class="headerlink" title="跨界、CoffeeScript、TypeScript和ES6"></a>跨界、CoffeeScript、TypeScript和ES6</h2><p>从2011年Ruby社区为前端圈带来了CoffeeScript开始,前端圈开始对语法糖特别关心了。CoffeeScript、TypeScript、ES6接踵而来。印象里,对语言最感兴趣的,是Server端工程师,老爱谈这个语言的语法特性,那个语言的语法特性,这个语言的性能,那个语言的性能。很少见C++客户端、AS、游戏客户端的同学谈论语言吧?为什么呢?</p><p>因为客户端和图形界面打交道,而图形界面在细节上的工作量非常大,这里有没有对齐,那里能否自适应兼容硫片化终端,这里的动效是否流畅,图形界面提供的API其实很繁复,仅是熟悉这些API,了解不同API在实战时可维护性上的区别就会花上大量时间。我常举一个例子,说明H5在布局方案上有多头痛——垂直居中。听到这四个字,有经验的前端工程师,脑子里能马上想到诸多种情况以及多种实现方案,不同的方案有不同的优缺点。而做服务端开发的同学其实完全不会遇到这些问题。再举一个例子吧,同样是使用Java语言的,你看看搞Android开发的同学是不是像搞Java服务端的一样,那么喜欢研究不同语言语法糖和性能?</p><p>我入行不久就开始前后端都写,前端写HTML、CSS、JavaScript、Flash,后端写PHP。之后一度专攻前端,然后又从2010年开始继续前后端都写,后端我没有继续选PHP,也完全看不上Node,探了下Ruby on Rails的坑后,果断选了Python。虽然后端我不是那么精通,但编程经验还是不少。可能有不少后端的同学可能会不服气,但我的确觉得后端的工作量比前端少很多,也简单很多。我做过的几个包揽前后端的项目里,花在前端上的时间至少3倍于后端时间。在MVC框架的帮助下,后端的编码体验其实很轻松,设计好数据库和缓存,工作量基本上就完成了一半。然后Controller里的代码不会很跳跃,专心在逻辑上就可以了。但在写前端代码时,除了要思考逻辑,还要思考表现层的工作。前端表现层的工作可不像服务端所谓的view层那样,搞个模板引擎就完了,前端的CSS能否用好,一是关系到设计图的还原度,二是关系到终端碎片化的自适应,三是关系到需求变更时维护的难易度。</p><p>很多搞服务端的同学转到前端来时,都会栽在CSS里。所以Bootstrap火了,你问问谁对Bootstrap最依赖,绝对是搞服务端的程序员。CSS2时代,服务端的同学对CSS尚且难以攻克,到了Mobile和CSS3时代,这鸿沟怕是更难以轻易逾越了。同样是DSL,CSS可比SQL复杂多得多。</p><p>有追求一点的服务端同学为什么那么喜欢研究不同语言的语法糖,以及性能问题?一是因为性能问题对于服务端是真需求,特别对于高访问量高并发的产品,二是因为工作量没那么大,知识点也没那么多,三是因为换语言这事其实学习成本没那么高。</p><p>怎么理解“换语言这事学习成本没那么高”这话?做任何一个领域的开发,要掌握的知识都是多方位的,语言只是其中之一而已,比如服务端开发至少要学一门语言,一个框架,一种数据库,一种Web服务器,另外,大多数情况下还得学习一种缓存,最好再学一下Linux运维。大多数语言只是语法糖有或大或小的区别,核心都差不多,变量、常量、类、对象、数组、字典、字符串、布尔、if else、switch、函数、try catch、for循环。即使不使用该语言的高级语法糖,也一样可以用常规的通用的编程技巧完成工作。</p><p>我用过JavaScript、AS、PHP、Java、Ruby和Python语言,也用过HTML、Flash、Flex、wxPython、Canvas2d和WebGL界面开发,还用过SSH、ROR、Django的服务端开发,所以当服务端同学在聊不同语言时,我一点也不羡慕,别自娱自乐了,真那么好学,有本事把CSS3搞定。</p><p>说回到CoffeeScript、TypeScript和ES6上。CoffeeScript和TypeScript分别是Ruby和C#社区的产物,它们都觉得JavaScript语法不够好,想改善它。只是它们俩分别代表了豪放派和严谨派,是两个极端,让它们俩先打一架好了。</p><p>我既不支持CoffeeScript也不支持TypeScript,原因很简单,我不想多加一层编译环节,JavaScript语法固然不完美,可是不完美的语言多了,Lua更简陋,一样活得好好的。GWT曾经想让Java语言通过编译直接解决JavaScript语言的问题,然后呢?然后GWT死了。连谈恋爱的人都知道,不要试图改变对方,你能改变的只有你自己。与其想着保留自己熟悉的语法习惯试图改变JavaScript语法,不如改变自己认真开始学JavaScript。有同学可能会说,TypeScript是JavaScript的超集,你可以完全按照自己的习惯写JavaScript。同学,可是在团队合作的规范上,这不是给自己埋坑吗?</p><p>CoffeeScript只在刚出来时,我身边有几位同学短时间地表示了一下兴趣,然后也没见着他们真的跟进去了。而TypeScript出来时,我身边没有一个前端工程师有丝毫兴趣,最感兴趣的是写C++和AS3的同学。我完全不看好这两门JavaScript预处理语言,在我看来,它们都是非前端社区的产物,迎合的也是非前端的工程师群体的编程习惯,它们很难打入真前端工程师的圈子。</p><p>再说ES6,它固然提供了更友好的语法糖,可是,这些语法糖真的像很多同学说的那样,是救世主,能“极大地提高前端开发的工作效率”吗?我持怀疑态度。首先,我觉得JavaScript的语法规则在进化是好事,只是在实际工作时不必拔苗助长,我反对Babel。Babel的做法太激进了,ES6的语法规则在团队合作上,肯定会带来门槛和内耗,Babel编译又进一步加大了调试的成本,得不偿失。工作效率的极大改善,从来不在语法糖这里,就像Ruby宣称的,语法糖是为了让程序员工作起来开心的。这开心的成本若太高,就不值当了。还是那话,个人开发和团队开发是有区别的。个人开发可以激进,而团队开发最好保守。</p><p>语言层面保守的解决方案是什么呢?是通过JavaScript框架来封装语法糖。最著名的例子就是jQuery,jQuery几乎把原生的DOM方法都给重写了吧?为一些实用的语法特性封装个Lang库,把想要的语法封装起来嘛,class、extend、map,没有什么功能是封装不出来的吧?其实在Babel出来之前,Lang库一直是主流的解决方案,并非新鲜事物。</p><p>急什么呢?CSS2不是过渡到CSS3了吗?HTML4不是过渡到HTML5了吗?该来的会来的,不该来的,急也不急不来,看看ES4怎么死的?再看看Python 2和Python 3。</p><h2 id="关于Node"><a href="#关于Node" class="headerlink" title="关于Node"></a>关于Node</h2><p>上面我提到的任何一个领域,要用到的知识都是多方位的。比如服务端要掌握的知识除了语言核心语法,还有语言的功能模块、Web框架、数据库、缓存、服务器配置、Linux运维、性能优化,甚至可能还得学消息中间件和网络安全。这是一整套知识体系,而且编程体验和前端截然不同。</p><p>所以当Node横空出世时,我看了下它楚楚可怜的模块库、文档和版本号,还有作者开发它的初心,心想这个玩具也就写写脚本吧,肯定忽悠不了人的,谁会拿生产环境陪它躺坑?它唯一能提供的只有可怜的“核心语法”这一项吧?但这一项的学习成本对于整个服务端开发知识体系来说,算个啥?Node距离成熟,还有很长的一段路要走,所以当npm笑话一般的unpublish事件爆发时,我一点也不意外,这是填坑必须付出的代价。</p><p>事实上,打从一开始,我就没觉得Node跟前端有什么关系。是啊,服务端有了个基于es语法的解决方案了,可是,关前端什么事?那些说全栈的同学,麻烦等一等,下面我会专门提到全栈的事。先还是说说Node的处境吧。</p><p>Node后来的发展,我猜中了一半,猜错了另一半。Node当前有两个用途,一是服务端开发,二是脚本工具。我猜中了传统服务端圈不待见它,但也很惊讶它作为脚本工具居然如火如荼。</p><p>因为传统服务端开发的同学,对es核心语法本就不算熟,所以Node在安利他们时,只能拿异步性能说事,但事实上服务端也有异步的框架,比如Gevent和Twisted,所以并没有太大的吸引力。也不是说完全忽悠不到任何一些服务端工程师,只是人家很可能在保留Java、PHP、Python为主武器的基础上,简单试试Node,毕竟es语言的学习成本也不高,什么MD5模块啊、Express之类的框架啊并没有什么新东西,新瓶装旧酒而已,比起CSS可熟悉太多了。只是这波同学忠诚度不会高的,人家愿意多学一门Node的,也不会介意再多学一门Go、Erlang、Ruby什么的。而且真需要团队合作了,需要赶时间了,需要接受性能挑战了,人家还是会换回主武器的。就算真起来个新贵,从口碑来看,我也更看好Go。</p><p>Node在安利Web前端圈的同学时,就主要拿es说事,什么一门语言就可以前后通吃了呀,全栈呀。拜托,后端编程的知识体系和前端编程的知识体系是一样的吗?十万八千里好吗?如果是些工作经验足够的前端工程师,你让他扩展下后端知识,这也是有益无害的,问题是,Node忽悠了一大片又懒又有野心的新手孩子。我之所以讨厌Node圈的布道者,就是这个原因。试想想,用Node做服务端开发,对于一个传统前端工程师来说,唯一可以偷懒的地方是什么?不只有“es核心语法”这一点吗?一个连核心语法都懒得学的人,你指望他花大量精力去攻克后端的那些庞杂知识体系?现在搞Node的前端工程师最喜欢的数据库是什么?是MongoDB。我一点不意外。为什么?因为数据库好设计,不就是个JSON吗?嵌套多好设计,数组字典都有,还搞什么外键,什么三大范式啊多累。可是你知道传统服务端工程师都不敢在正式项目中使用MongoDB做持久化存储,只倾向于拿它存存日志吗?好吧,就算这帮孩子真的花了大量精力成了合格的后端,而不仅仅是个票友,可是这时他还是前端工程师吗?也许他成了合格的后端,但他的前端知识学扎实了吗,日常工作都已经熟练了吗?怕就怕哪个都没学会,哪个都是个半吊子,既不对自己负责,也不对公司负责,然后还想当然地自以为全栈,一心想做架构师,想马上加薪,以对得起自己的“过人能力”。</p><p>Node进军Server端,当前来看,是失败的,它丝毫动摇不了Java和PHP的主力地位。也许一些公司里有Node团队,但看客请注意三件事:</p><p>Node的市场份额如何;<br>公司Node团队的Leader是什么出身,前端还是服务端;<br>Node团队负责的业务是不是稳定性、安全性要求高的核心业务。<br>某位JavaScript框架作者跟我争论时,理直气壮地拿出几篇国外的博客给我看,说Node在国外已被广泛应用了。我看了下,一篇是这样的:2013年,一个设计师出身转型工程师具体经验不明的人,在内部Java服务端工程师们持怀疑态度的情况下,将一小块功能尝试性地由Java迁到了Node,并证明性能不错。很高兴地向外界发了博文宣布公司里生产环境用上了Node,并表示未来会用Node重构更多地方。另一篇是这样的:1个前Google工程师为招人,写了篇介绍公司技术选型介绍的文章,提到了服务端选型用到了Node和Go,Node因为事件循环阻塞出现了性能问题,作者通过多起实例的办法填了这坑,然后用Go的部分一切顺利。数据库选型没听过。作者自认技术选型非主流,而且招聘的工程师要求也非主流,最好全端全栈。这就是Node做服务端开发拿出来说事的案例? 这里有一篇关于Facebook员工的访谈,谈到了Facebook是否使用Node进行服务端开发的问题,答案是“Whenever they want to run JavaScript on server (e.g. rendering React on server) they just use V8 directly rather than using Node”(文章见:10 things you probably didn’t know about JavaScript (React and Node.js) and GraphQL development at Facebook)请读者朋友们想想为什么。</p><p>Node写写脚本工具还是可以的,毕竟不用团队合作,也不用上生产环境去接受考验。只是写脚本,很多语言都可以胜任,Perl、Python、Ruby、Java都是不错的选择,根据自己喜好来吧。比如Sass是Ruby写的,Ant是Java写的,曾经很流行的YUI compressor也是Java写的。我在雅虎工作时,脚本工具用的是Ant,在新浪工作时,是团队用PHP自己写的脚本,在盛大工作时,我自己用Python写脚本。曾经为了方便打包合并压缩方便,我自己用wxPython写了个带GUI的工具,现在还挂在CSDN上,叫GUIcompressor。原理很简单,用wxPython写GUI,通过Python的os模块调用YUI的compressor,简单来说就是包个壳。Node的脚本也有很多都是从别的社区包个壳就拿过来了。</p><p>脚本工具这东西确实能帮上些忙,只是在我看来,并不是太重要,也并不像某些同学说的那样能“极大地提高工作效率”。所以在过去相当长一段时间里,其实前端的所谓工程化脚本,并没有流行开来,好一点的公司才自己折腾下,一般的公司了不起就做做CSS和JavaScript文件压缩就完了。多年以前,前端的部署问题很简单,往服务器上一扔就完了。但后来随着前端的网络性能优化技巧逐渐完善,前端要做的事情变得越来越麻烦:图片合并、图片压缩、代码合并、代码压缩、动态加载、CDN。如果再加上jsLint之类的代码检查,就更加麻烦了。之前流行的加载是线上动态加载,requirejs那种方式,而现在流行在打包阶段按需加载,这就使得打包变得更加复杂。我声明下对Node脚本的态度,我觉得Node能为前端带来专业的开源的工具,这是打包脚本最好的结果,比自己折腾些质量一般的脚本好太多了!对于一个成熟的团队来说,有开源的成熟的打包工具是应该的,这是Node为前端圈做的一件好事。</p><p>只是前端圈太不理性了,一会儿Grunt,一会儿Gulp,一会儿Webpack,然后听说又有人想折腾npm打包。管它哪个,选一个就完了呀,猴子掰玉米般捡一个扔一个真心没必要。类似的闹剧,在前端社区之外,还真没听说过,这也是前端社区一个极不成熟的表现。</p><h2 id="关于跨界、全栈、公司定岗"><a href="#关于跨界、全栈、公司定岗" class="headerlink" title="关于跨界、全栈、公司定岗"></a>关于跨界、全栈、公司定岗</h2><p>对于跨界的人和技术方案,我一直持特别谨慎的态度,原因无他,因为我自己就跨界跨得厉害,我知道坑很多。我最早是做视觉设计的,做点线面、欧美风、韩风、icon设计、矢量图位图,然后玩DW里拖表格布局,做Flash动画、Flash脚本、Flash全站,再到CSS重构和DHTML,做后端PHP,用Flex、用Ruby on Rails、Python、Socket编程、wxPython桌面编程,用Canvas2d、WebGL、游戏开发,用敏捷,Scrum、XP、精准看板还有TDD。而现在我还参与是产品设计和产品营销。看的多了,踩的坑多了,也就开始有了自己的判断和坚持了。</p><p>在我看来,前端圈子当前的混乱,主要原因来自跨界。从2011年CoffeeScript、Less、Node同时进入前端圈推广开始,到后来的Angular、React Native(React我觉得还是有价值的,但React Native就相当不接地气了),全都走偏了路。CoffeeScript是Ruby社区从自己的语法习惯出发的,Less是Ruby社区觉得CSS写起来很麻烦,好心地帮不能编程的CSS加入了一些更高级的特性,Node是位玩语言的票友意外看到了V8引擎,包了个壳,本人一点前端的基因也没有,而Angular是Google的服务端团队好心帮前端带来了更接近服务端MVC的框架,React Native是Facebook一支没有iOS开发的团队为了让前端开发出原生App程序而折腾的。这帮人全是跨界的,带着自己的专长和对前端或深或浅的理解,提供了自认为不错的解决方案,解决了自己的问题。至于有没有更好的解决方案,至于解决方案是否会有坑,至于这些技术落地需要什么样的人员配备和职位定岗,他们都没有去讲,也没有义务去讲,为了技术推广甚至会恶意地回避去讲。而这些问题,是需要使用者们自己去思考自己去承担的。</p><p>没有技术是只有好处而全无坏处的,从来都是两面同时存在,而不同的项目,不同的团队,做技术选择时需要权衡地去看,只有优点大于缺点,风险可控的情况时,一些有风险的选择才是合适的。让我非常担心的一点是,跨界而来的这些方案,基本上只听得到赞美声,很难听到置疑声。而赞美声全是官方宣扬的,其实是不用动脑子的,也不需要自己的经验去验证的,人云亦云即可。置疑声需要有独立思考能力、项目经验支撑,另外,以我的经验来看,最近前端圈的攻击性特别强。因此,置疑者还需要有非常强大的内心。</p><p>所以,我最近常问的问题是——你知道自己正在用的技术,缺点是什么吗?看客不妨也反思下这个问题。 当我在问这个问题时,经常会得到这么几个答案:</p><p>你懒,你不好学。不同的技术都有它适用的场景,学学总是好的。<br>你固步自封,老守着前端这一个小领域,我要做全栈。、<br>你反对新技术是因为你思想还停留在IE 6时代吧,你切一辈子页面,写一辈子jQuery吧。<br>前文已经提到笔者曾经研究和实践过的技术,关于是否好学,翻翻Blog便能得到一路过来的痕迹。所以请不要怀疑我的置疑动机,而是认真想想我提出的问题。</p><p>我觉得全栈这个口号其实很毁人——别误解我的意思,我不是反对个人往全栈方向发展,而是想说,全栈并不容易,不是说前端开发的同学学了Node就全栈了,也不是说后端同学有了Angular和Bootstrap就全栈了,更不是说前端或者后端玩玩React Native,就能让iOS和Android下岗了。我发现一个可怕的事情是,全栈的口号让一些同学忽略了其他领域的知识深度,然后靠自我安慰甚至是自欺欺人地贬低其他领域的知识,以安慰自己已然全栈。</p><p>世界并不以某个人为中心旋转,前端在通过Node抢夺服务端的地盘的同时,服务端也在通过Angular和Bootstrap抢夺前端的地盘,各方在通过React Native想抢夺App的地盘时,你听说Swift也在进击服务端了吗?做iOS开发的同学也在号称全栈了,而Android更是Java语言早就占领了服务端开发了。在这场互相抢夺地盘的战争中,每一方都号称自己全栈,可是啃下对方的地盘真的那么容易吗?玩票是可以的,但真要实战起来,会遇到一个巨槛——公司的职位定岗。</p><p>常规公司里,会配备好前端开发、iOS、Android、服务端开发这四种技术团队,实际做项目时,几支团队是分工合作,只在必要的地方通过接口配合。在PC时代,不考虑C/S结构软件,只存在前端和服务端。前端和服务端配合时,主要是通过前端提供模板,后端负责数据持久化和逻辑处理来合作的,双方唯一可能起争执的地方就是模板引擎由谁来套,这个模板引擎指的是服务端模板引擎,大部分公司里,这个模板是由服务端来套的。虽然也有AJAX请求,服务端吐JSON数据的地方,但总体来说并不是那么多这种接口,渲染也只是局部渲染,一般到不了使用JavaScript模板引擎的地步。应该说,这个时期前后分离的需求并不高,而SEO的需求又很重,所以前后端两支团队也算泾渭分明井水不犯河水。</p><p>到了移动时代,原来PC时代B/S结构一家独大的局面被打破了,iOS和Android异军突起,C/S结构将B/S结构压得死死的,Native App强势压制WebApp,一时间Web已死的论调甚嚣尘上。作为服务端开发,与Native合作天然就只能采用JSON接口——你让它套个iOS和Android模板试试?然后与前端合作怎么办呢?服务端当然可以选择继续套模板,但问题是,这样服务端的工作量就上去了,在服务端看来,iOS、Android和前端都是客户端GUI技术,这三个统一都叫前端都可以,无所谓B/S还是C/S,这三端的界面最好长得一模一样,功能迭代统一走,服务端统一只吐一套JSON数据出来。于是在移动时代,前后端分离终于有了契机。</p><p>有些同学稀里糊涂地以为前后分离是前端技术革命带来的,真不是。愿意的话,HTML4时代就完全可以做到前后分离了。技术造型除了技术本身,这里还有一个台面下的问题,就是不同团队的定职定岗问题。前端的同学学了Node就可以在公司做服务端开发了?你先问问服务端团队答不答应,出了问题你负责吗?就算你愿意负责,工作交给你前端来做了,服务端团队是不是可以裁员回家了?学了React Native就可以在公司做App开发了?你先问问iOS和Android团队答不答应。这些问题是忽略不掉的。之所以现在可以玩前后分离了,是因为服务端团队因为工作量原因,人家答应了呀。可是你玩Node服务端和ReactNative,能在公司里落地吗?你可以对外宣称,更少的人做更多的工作,可是这些方案在质量上,真的比得过人家专职团队吗?先把自己的坑填完再来挑战人家专职团队吧,人家可不会把你当做朋友的,你是来抢饭碗的。</p><p>那这些所谓全栈,能在什么环境里落地呢?很简单:</p><p>个人项目里<br>成熟公司里的非核心项目里<br>创业公司里,恨不得1个人当10个人用,重视成本重视速度,不重视也不懂质量问题的那种<br>全栈之风从哪儿吹来的?硅谷。硅谷有一堆的小创业公司,也有一些工程师能力极强的工程师文化公司。而你呢?你在恨不得一个人当多个人用的创业公司里,还是在Google、Facebook?全栈在公司定岗上天然会水土不服的。即便在美国,我相信除了硅谷,也一样很难有全栈的容身之地。当然,如果你仅想提高个人能力,多学多看绝对是好事,我是欣赏的,而且我自己也是这样做的。</p><p>最后再泼盆冷水——就算你在小创业公司里做着全栈,该来的问题还是会来。Twitter还是家小创业公司时,用的是时髦的Ruby on Rails,号称5分钟搭个博客系统出来的服务端框架。可等它做大,系统整个就重写了。</p><h2 id="关于前端的核心竞争力"><a href="#关于前端的核心竞争力" class="headerlink" title="关于前端的核心竞争力"></a>关于前端的核心竞争力</h2><p>如果说服务端同学进击全栈是试试水,Native进击全栈是试试水,那前端里很多同学进击全栈就是在拿生命在玩全栈了。</p><p>服务端玩玩Node,不喜欢就算了,玩玩Angular和Bootstrap也就在后台开开荤,前台各位视觉设计,UAT还原检查,各种动效,用Angular和Bootstrap能把自己玩死,而后台基本上一直是服务端的自留地,很多做前端开发的同学甚至没开发过后台界面吧?Django甚至都自动给你生成了。后端的核心竞争力在哪儿?在添删改查,在数据库设计,在性能优化,在shell脚本,在分布式,在网络安全。玩玩票不影响自己的大本营。</p><p>同样可以一门语言前后台通吃的Android开发,你看看他们对全栈是不是像前端圈那样热情。从DNA来看,对Java语言可比JavaScript和Node更亲吧?再往远了说,看看C++客户端的同学对服务端有没有那么大热情?</p><p>还是那句话,好学是好的,前提是自己的大本营要守住,一专多长。你得先专一门,再想着横向扩展其他领域知识。前端开发的核心竞争力是什么?2016年年中,我在微博上说,前端的核心竞争力在于一些HTML标签、CSS,JavaScript的熟练度上。这些东西是前端自己领域的知识,比如Form2.0、Websocket、离线缓存、Webworker、Border-image、Canvas。一些同学回复说“核心竞争力居然只是些API,这有什么难度?”此言差矣。或许这样认为的,以跨界而来的“全栈”工程师居多。有些知识确实只是API,比如JSON.stringify和window.getComputedStyle之类,看了就会用,用起来也没有什么实践方面的坑。但并不全是,比如Form2.0可是有一系列新东西,新标签如output,新类型如number,新属性如pattern,新的CSS伪类如:valid,需要融合在一起考虑,形成一个Form2.0的解决方案。再比如Canvas2d,Canvas提供了像素级API,可以直接存取颜色,可以把像素导出成base64的字符串,提供了DOM没有能力,但同时也完全没有了DOM的便利,比如Canvas上画的某个按钮该如何进行事件监听呢?比如不能使用CSS了,该如何实现:hover伪类,又如何让布局实现自适应性呢?什么样的情况下该使用Canvas,什么情况下该使用DOM,如果有某个功能必须依赖Canvas实现,比如在网页上做个美图秀秀,将产品的哪些元素放到Canvas上,哪些元素放到DOM上,两者又如何合作呢?换成纯Canvas解决方案会不会更适合呢?前端的知识不同于服务端,大部分的工作量都在图形界面上,而图形界面是件很细的活,工作量和技术含量全在细节。我经常对非前端的同学举一个例子——你知道垂直居中有几种方法,不同方法的优缺点吗?有些跨界而来的同学,以及部分前端圈的新同学都不以为然,嘲笑说这叫“回字的四种写法”。其实,前端在实战时,垂直居中有多种方法,基本上没有方法是无副作用的,要看情况,不同的情况要选用不同的方式才能实现最好的自适应性。感兴趣的同学可以去搜搜看前端的垂直居中方法整理。看完之后,就能明白前端CSS的精彩和玄妙。</p><p>我批评很多同学基础不扎实就开始乱折腾,不是说多学习不好,而是说大本营都未扎牢,如何实打实地高效完成日常工作?ES6、CoffeeScript、React、Webpack等,都解决不了你在实战时遇到的具体挑战。这全是些外围功夫,并非核心。先把核心学好,外围功夫什么时候学都可以,又不难,你说对吧?</p><p>那么什么是核心呢?HTML、CSS和JavaScript。我指的是原生的这些东西,不用上来就跟我说React的JavaScriptx语法重定义了HTML,Sass改良了CSS,TypeScript给JavaScript带来了静态语言的语法,这些都是外围,今天是React,明天可以换成Angular,今天是Sass明天可以换成Less,今天是TypeScript明天可以是CoffeeScript,这些不重要。就像jQuery鼎盛时期,很多同学不学原生JavaScript,上来直接就上手jQuery一样,走不远。要理解jQuery为什么这么封装,其实在底层发生了什么,用原生会遇到什么问题,直接用原生能解决吗?把原生的技巧学熟了,这些外围的东西上手很快,而且什么情况下用什么,心里会非常有底。</p><p>过去,前端领域并不像如今这样浮躁,很多人都知道基础的重要性,也知道基础是什么。但当跨界的“全栈”进入前端圈以后,很多浅显的道理都被有意无意地搅昏了。速成、革命、淘汰、全栈成了主旋律和政治正确。可是,就像投资界里爱说的一句话一样:“风起来了,在风口上猪都会飞。可是等风停了,还在飞的是老鹰,而猪会摔死。”风会停吗?当然。该潜心修炼还得修炼,基本功不扎实以前,别糊里糊涂跟风浪费自己时间,缺什么要恶补。</p><p>今天说得很热闹的HTML5其实是从HTML4加强而来,两者不是替换关系,而是“强化”,就像ES6之于ES3一样。很多新入行的同学希望可以速成,然后从哪儿热门往哪儿入手,这其实不对,最好的学习方法是从HTML4学起,尽管在实践时有很多HTML4时代的技巧,在HTML5时代有了更好的替换方案,但也有很多HTML4时代可以一直用过来的技巧。让我担心的是,HTML4时代的好书,到了HTML5时代已经不再出版了,而HTML5相关的书籍基本上只讲了HTML5相较于HTML4的增量部分。而HTML4时代的书和相关技巧就这么失传了。除了书,博客和所谓社区也是一样,现在已经不再讨论以前的一些精华技巧了,有些技巧确实是淘汰掉了没有什么价值,比如IE6的hack技术,但也有些技术是很棒的CSS技巧,比如CSS滑动门依然适用。我推荐一下几本书和学习步骤,给有心弥补基本功的同学:</p><ul><li><p>《CSS网站布局实录》——国产CSS2入门书,有些技巧已经淘汰,但仍不失为最好的CSS入门教程。</p></li><li><p>《无懈可击的Web设计》——讲CSS应用技巧的书,国内外粉丝别多,说是开创了CSS技巧流派也不为过。</p></li><li><p>《DOM JavaScript编程艺术——JavaScript最好的入门书,没有之一,这本书是帮助你了解如何将DOM、CSS和JavaScript连接起来的一本书。严格来说,后端Node根本不算JavaScript,JavaScript是基于ES语法的一门脱水语言,如何实现的胶水?这本书将带你入门。</p></li><li><p>《JavaScript高级程序设计》 ——JavaScript必读的一本精典,读完之后对JavaScript的理解和实践会上升非常大的一个台阶。</p></li><li><p>《编写高质量代码——Web前端开发修炼之道》 —— 举贤不避亲,这本书是我写的。推荐的原因是,这本书重点讲团队合作的注意事项。虽然一些具体的技巧,在今天已然过时,比如IE6的hack,但在团队合作方面的思考,直到今天我也没看到其他书在讲,这些思想没有其他书可替代。</p></li><li><p>《HTML5和CSS3权威指南》——目前为止,我读过的HTML5方面最好的一本原创书。配合实例进行API讲解,非常详细具体。连HTML5都提供了哪些底层的东西都不知道,又该如何去用好它呢?在我看来,是学习HTML5的必读书。</p></li><li><p>《响应式Web设计:HTML5和CSS3实战》——作者是《无懈可击的Web设计》忠实粉丝,所以很自然地,这也是本CSS技巧流派的书,侧重点在CSS3的实践技巧上,让人大开眼界。</p></li><li><p>《JavaScript设计模式》——JavaScript在实战时的高级技巧。</p></li></ul><p>前端很棒的书有很多,这只是几本我觉得最不该错过的书而已。从HTML4一路到HTML5和移动时代,一路上有了很多新技巧,也淘汰了一些旧技巧。当下的学习氛围虽然前所未有的强烈,但急功近利和盲目无头绪现象也很严重。在我看来,很多人不愿意做苦活累活扎扎实实打基本功,一句“那些都淘汰了”就拒绝了所有的优秀遗产,希望花少量时间看看流行时髦的新工具新框架,然后就迅速挤身行业顶端,这想法既偷懒又幼稚。什么是外围功夫,什么是真核心技巧,什么是珍珠什么是盒子要分得清,自欺欺人并不是什么明智的想法。你可以几天几个星期就掌握的东西,别人也可以,就算人家比你笨,多花一倍的时间也能跟上你吧?要真的拉开和其他人的距离,只有下苦功这一途。</p><p>这些话,恐怕没有几位前端老人愿意说。当我问他们,拼命强调新风向,而不再提基本功,造成知识断层,造成这些同学心高气傲但完成不了工作怎么办时,一些老人的回答是“他们自己不重视基本功,怪我喽”。如果你基本功很扎实了,想学什么外围功夫都可以,虽然多学总不是坏事,只是在决定投入使用时,还需要看团队情况再慎重决定,团队合作要考虑的事情有很多,要有责任感,别只顾着自己当Geek。</p><h2 id="关于Angular,后台,SPA"><a href="#关于Angular,后台,SPA" class="headerlink" title="关于Angular,后台,SPA"></a>关于Angular,后台,SPA</h2><p>关于Angular,我是不认同的。Angular是Google服务端团队折腾的作品,整套代码组织的思路和服务端的MVC框架如出一辙:URL路由 + Controller + 数据抽象 + 模板引擎。虽然服务端出身的同学会倍感亲切,但这真的不是前端代码最好的组织方式。</p><p>我对此的判断是,Angular团队因为自身服务端出身的基因和思维模式,创造了一个对服务端团队最友好的框架,目前用户群体也是以服务端团队为主,适用场景以后台这种定制动效和定制UI要求不高的场景为主。事实上,Angular2决定使用TypeScript开发我也毫不意外,因为TypeScript本来就是Java服务端团队更喜欢的静态严格语法,Angular团队决定讨好他们自己讨好和他们相同背景的程序员群体,放弃普通前端工程师习惯的脚本弱类型习惯,也完全是情理之中的事。</p><p>唯一让我奇怪的是,我们的传统前端工程师为什么要去追捧这么一个框架呢?人家根本就没在意你好吗?就像从传统服务端转到Node服务端的同学会奇怪,为什么一群前端工程师对Node服务端这么感兴趣一样?这关前端什么事?我们去凑什么热闹?最近流行一个段子,说一个放羊的和一个砍柴的聊了一天,然后天黑了,人家放羊的羊吃饱了回家去了,可你砍柴的呢,你的柴呢?你的前端基本功呢?你的前台应用场景呢?</p><p>我觉得“全栈”这个词很讨厌,原因在于它搅混了很多事。从定岗上来说,后台的前端部分,到底是由谁来开发?是由前端工程师来开发呢,还是由服务端工程师自己来开发?在过去相当长的一段时间里,后台是怎么开发的?是由Frameset来组织代码的,并非SPA,后台的界面往往非常难看,基本上只是后端工程师自己顺手就给做了,没什么UI定制需求,也没什么动效,了不起有些正则表达式验证而已,连AJAX的要求都不高。前端工程师基本上是不做后台相关的工作的。那么如果后台并不是给自己使用的,而是给第三方使用的呢?是外包公司呢?后台界面如果需要有更好的卖相怎么办?Extjs就派上用场了,还算不错的界面设计和丰富的组件,而且完全支持SPA开发,只不过Extjs组织SPA是以组件为单位来组织的,并非服务端熟悉的MVC那一套。所以这种情况下,后台是需要依赖专业的前端工程师介入的。Angular最适用的场景应该就是这样的情况了,取代Extjs,让后端工程师自己按照自己的熟悉的方式进行“全栈”开发。</p><p>问题是,后台因为种种原因,从来也不是前端工程师的主战场啊。后端能自己搞定自己玩去嘛,前端工程师在实际工作中,更多的工作在什么地方,在前台啊,和视觉设计师的PSD直接打交道吧?和交互设计师或产品经理设计的交互动效直接打交道吧?要切页面,要玩透CSS吧?跟人后端工程师屁股后面追Angualr,结果人家Angular升级到2用TypeScript了,不兼容了,傻眼了吧?信不信人服务端过来的“全栈”比你一前端玩Angular玩的更溜?而且就算你辛辛苦苦把Angular那么复杂约束性那么强的一套框架学得特别顺了,可是别忘了你的工作主战场在什么地方?你是天天在写后台,当我没说,可是如果你天天在写前台,那是真心找虐……醒醒,你这砍柴的别陪人放羊的一起谈全栈了。</p><p>前台最适合以什么方式组织代码,下面我会再讲,现在先跟后端工程师谈谈心 ——以Angular来为后台组织SPA真的是最好的实践吗?和传统的Frameset相比,真的性价比更高吗?首先,我赞成从纯技术角度来看,Angular是适合后台场景的方案之一,只是即使是后台开发,也有三种技术方案可以使用:</p><p>Frameset——一般的后台,能用就行了,样式,无刷新都不重要,反正给自己人用的,怎么快怎么来。开发速度快,需要前端配合,分工也容易,维护也容易,招人门槛、人流失进行交接都风险小得多。在技术选型之前,先想想你是否真的需要为后台提供SPA。<br>Angular——给第三方使用的后台,对样式,无刷新体验要求较高的,而团队中又没有专职前端工程师,需要服务端工程师自己上的,结合Angular和Bootstrap是个不错的选择。只是Angular这一套的学习成本不低,而这一套其实又很难和普通前端前台需求普适,性价比不高。如果想用一套技术完美通吃前后台的前后端代码组织,你应该考虑方案3。<br>组件化组织,自己抽象——其实思路和Extjs一样,以组件化方式来组织后台的SPA。只是Extjs的学习成本也不低,而且样式也不好定制,所以你需要的并不是去学Extjs,而是学习如何自己封装组件。封装些Scene、SceneManager、Widget抽象类、组织个SPA出来很简单的,模块化组件化可维护性都不是问题。有同学会问了,那模板呢?通信呢?首先模板不是必须的,我个人不推荐在前端使用模板引擎,这在本质上和组件化是相悖的,就算真的要用模板引擎,有很多垂直的JavaScript模板引擎可以选择,通信也不用Angular那么麻烦,通信最主要是想解决一个什么问题?组件和组件之间解耦,但又需要保留组件内部的Context,很简单,通过全局的自定义事件,为事件进行传参,就可以轻松实现组件的通信了。多简单一件事,折腾出约束性这么强的一个框架出来,何苦呢?<br>关于自定义事件,以及组件化,我做过一个视频教程,不依赖React之类的框架,很原生态,方便对组件化的具体技巧有更深入的理解。有兴趣的同学可以看看<a href="http://imooc.com/learn/99" target="_blank" rel="noopener">阿当大话西游之Web组件</a>。</p><h2 id="关于React"><a href="#关于React" class="headerlink" title="关于React"></a>关于React</h2><p>2015年底我批了Angular,同时也批了React。但我反对它们的理由其实并不一样。</p><p>首先,React是组件化的思路,这个我是认同的。首先跳出Web前端这个圈子,咱们看看别的GUI技术,无论是桌面软件开发、Flex、移动还是游戏,都是以组件的方式来组织代码的,可以说GUI编程按组件来组织代码是普适的最佳实践。Web前端有两方面的原因,导致其代码组织方式出现了Angular这种非主流:</p><p>Web服务端是唯一可以和HTML无缝结合的,这是为什么JavaScript、PHP、ASP又被叫做动态网页的原因。所以前端和服务端有扯不断理还乱的历史原因。看看服务端染指iOS或者桌面开发容易不?<br>前端的原生控件实在太少也太弱(看看.NET和Flex的控件),数都数得清楚,text、radio、checkbox、password、button、img、audio、video等,长期以来,前端不得不自己封装组件,Extjs、YUI、Dojo、Prototype无不如此,还有丰富的各种jQuery组件。成也封装败也封装,封装给前端带来了强劲表现力的同时,也降低了前端同学自己动手写组件的能力。大多数人都是拿来主义,但拿来主义并不是最可怕的,可怕的是拿来主义的同学没有意识到组件之于GUI的意义是那么不可动摇。<br>举个简明的例子说明Angular的MVC和组件化之间的不同吧:</p><p>Angular是什么思路:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line">m : [a_m, b_m, c_m],</span><br><span class="line">v : [a_v, b_v, c_v],</span><br><span class="line">c : [a_c, b_c, c_c]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而组件化是什么思路:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line">a: [a_m, a_v, a_c],</span><br><span class="line">b: [b_m, b_v, b_c],</span><br><span class="line">c: [c_m, c_v, c_c]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Angular为代表的类服务端MVC框架,上来直接分成M、V、C三个层,然后将互相关联的三块分到了三个层里去,这么干最大的坏处是破坏了抽象。而组件化的核心就在于抽象,将相关的属性方法内聚到一个组件里去,这是符合面向对象的思维模式的。我自己既做前端开发也做服务端开发,服务端开发在MVC框架的帮助下,基本上在应用层不需要做任何的抽象,Controller不过是函数而已,M是和数据库、缓存打交道的命令而已,即使有ORM抽象,那也是框架替我抽象好的,不用我自己做任何抽象,而模板引擎就更加机械化操作了。但我做前端开发时状态完全不一样,我需要通过组件化来将界面变成一个个自定义的组件,我需要不停地抽象:这是不是可以抽象成一个组件?它的属性有什么?它的方法有什么?它的DOM节点最外层容器是什么?我如果要封装方法该封装什么方法为公用,什么方法为私用呢?我需要不停地进行思考,因为前台的界面是各种各样的,不可能像服务端一样由框架高度抽象出一些通用逻辑进行封装的。事实上,前端除了少数的通用组件可以直接使用第三方的,比如模拟弹窗、富文本编辑器什么的,大多数的需求只能自己封装,这是件非常考验人面向对象思维能力的事。</p><p>所以,我认为前端的前台需求,不应该由重框架来提供解决方案,“轻框架 + 组件化 + OO”设计才是最优解。也就是说,前端不应该多提供约束性的框架,而应该提供基础工具帮助和通用组件就可以了,在应用层,要考验前端工程师自己的能力,没有任何框架可以帮你去抽象你的业务需求。所以我觉得前端比服务端苦逼很多。</p><p>那么我说的“轻框架 + 组件化 + OO”设计是不是之前没有业内的实践呢?当然不是。事实上,前端长久以来,一直是轻框架 + 第三方组件的模式。只不在过应用层,各家公司的前端工程师大多数能力水平很有限,而业务需求也并不复杂,所以在前端圈里OO设计和自定义组件并没有流行开来,究其原因主要是门槛有些高,而大多数程序员们又自我要求很低。</p><p>听不明白轻框架 + 第三方组件 + 应用层面条代码?想想jQuery + jQuery组件 + 大多数前端工程师用jQuery写的代码你就明白了。</p><p>既然jQuery已然可以提供必要的帮助了,那么还要React干嘛?jQuery有一个致命的问题,就是jQuery本身是基于plug模式来写扩展的,对第三方组件并没有统一的更组件化的方式来标准化它们,这就直接造成了不同的人写jQuery组件会有不同的风格。这对团队合作并不是什么好事情,如果说是第三方组件,如果封装的质量很好,倒也罢了,毕竟只使用它的API就够了,内部实现再怎么乱也不用去计较。问题出在自家公司的自定义组件,如果公司里有几个人团队合作,一个人一个风格怎么行?我们需要一套标准来约束大家按相同的风格来组织代码。这就是React最大的意义所在,填补了jQuery的这个不足。但这是否意味着离开React就无法组件化了?当然不是,比如我的视频教程里就有另一套风格的组件化范式。</p><p>这套风格并非是我原创,而是从YUI3学来的,思路是可以穿越语言和框架的,我不过是将这套思路从YUI3带到了jQuery。事实上我是YUI的忠实粉丝,真的是座巨大的金矿,随着雅虎日渐衰落而停止了维护,真是让人遗憾不已。requireJs、React的组件生命周期,YUI早已在2008年就实现了,它的思想超出这个时代很多年。但这又如何?技术理念的先进有什么用呢?还不如会推广和上手门槛低来得实惠,我觉得YUI和jQuery相比,一个是航空母舰,一个是小渔船,但又如何?jQuery获了开源大奖,API上了犀牛书,成了事实上的工业标准,而YUI呢,谁还记得这个低调的大拿呢?</p><p>我不再追流行,也不看理念是否先进,而是重视工业标准,原因正因为我并没觉得有多少新东西,而且理念先进不等于接地气,不接地气不讨好普通的懒惰的程序员就很可能活不下来。可能因为受过太多伤,所以变得不再容易被挑唆,变得格外挑剔吧。有兴趣的同学可以看看我曾经写过的一篇文章,文章写于Flex在前端圈异军突起,无比时髦的2008年。现在看来,这些激情还是太幼稚了。《[RIA之争,我的看法]》一些喜欢说我不求上进倚老卖老的同学,想送给他们一句王朔的话:谁没有年轻过,可是你老过吗?</p><p>说回到React的问题上。我之所以不认同,并不是认为React在技术上一定是走错了路,而是因为React把简单的事情越搞越复杂,现在一提React就是React全家桶,它的野心很大,还想解决Native开发的问题,但经验告诉我,这很危险,这意味着学习门槛、团队合作成本、招人成本、人员流失风险。如果React并不能带来技术普及,只是昙花一现呢?谁来为公司里的那些遗留代码负责?谁又来为初出茅庐基本功都未扎实的同学浪费的时间负责呢?我脑子里时常想的是YUI、Dojo、Extjs这些过去前辈们的心血,就这么基本毫无痕迹地被遗忘了,而jQuery这小草根居然活下来了。谁能保证React这个套路下去,不是个短暂的狂欢呢?除了一些在社区里闹腾的同学,这些技术真的被广泛采纳和推广下去了吗?以我的经验,深表怀疑。</p><p>再强调一下,对于个人要学习,没有任何人会阻拦你,而且学习是对的。问题是,别轻易在团队里强推,想清楚在公司里推广起来坑多还是利多。我总是习惯性站在一个技术负责人的立场上来看问题,难免保守,我只是希望激进的同学理解,公司并非个人的实验田,你不开心你会考虑换工作,但你留下的摊子,还得有人收拾。所以我想,冲突的根本应该就在于此吧。</p><p>React总的来说,不像Angular走错了路,但因为全家桶的原因门槛越来越高,这不是个好现象,未来有待观察。个人不是太看好,但确实也没有更有力的挑战者。要么像我一样坚守jQuery,自己做组件抽象类,配合jQuery的第三方组件工作,等待更有冠军相的角色出现,代替jQuery成为新工业标准,要么就小心翼翼用上React好了,我也想不到有什么别的路可走,当下这阶段确实挺邋里尴尬的。另外,别小看jQuery,我认为jQuery仍然非常坚挺,而且仍然有非常大的可能性成为最后赢家——补上组件抽象类和单向数据绑定就可以了。而这些通过jQueryui都不难做到。</p><p>组件化是对的,但组件化框架其实是很轻量级的,用不用框架,以及用什么框架问题都不大,不是说有了什么组件化框架,就像奢龙刀一样开了外挂了,不是的。组件化不过是编写高质量代码的基础而已,真正的挑战在抽象上。能不能养成面向对象的思维模式才是关键,而面向对象要具体去抽象组件时,是没有定式的,要看应用层具<br>体的需求去做抽象,这也是为什么我说前端不该去做约束性特别强的框架的原因所在。轻框架,重应用层抽象才是GUI编程的正途。</p><p>关于抽象和面向对象,你真的掌握了吗?封装、继承、多态、设计模式、解耦、API设计这些真的都熟练了吗?我接触过的前端,绝大多数同学都毫无概念,均表示听说过,尝试过,但实践不多。我扔几个案例出来,感兴趣的同学可以拿来做一下面向对象编程的练习:</p><p><a href="http://www.adanghome.com/js_demo/1/" target="_blank" rel="noopener">Demo1</a><br><a href="http://www.adanghome.com/js_demo/10/" target="_blank" rel="noopener">Demo2</a><br><a href="http://www.adanghome.com/js_demo/35/" target="_blank" rel="noopener">Demo3</a></p><p>可以看看我的源码,如果是你自己开发,又会如何组织代码。</p><h2 id="关于SPA和Web-Site"><a href="#关于SPA和Web-Site" class="headerlink" title="关于SPA和Web Site"></a>关于SPA和Web Site</h2><p>说到Angular、React之类,很多同学都会提到SPA和Web App。然后说SPA的春天来了,说代码质量要提高了,jQuery可以去死了。但事实上,真的如此吗?</p><p>后台是否搞SPA我其实并不关心,搞不搞都可以,搞的话我也不打算用Angular来做,服务端的全栈们如果要用Angular搞后台我也没意见,只是你自己开发的自己维护,别你做的技术选型硬拉上我。但对于前台部分,我期待SPA其实很多年了。从我用Flash做RIA应用时,我就知道前端做SPA比起Web Site要复杂多少了。我2008年开始写HTML的SPA,早期做的一个很复杂的例子,现在还有保存,请见:<a href="http://www.adanghome.com/tbs/manage.html" target="_blank" rel="noopener">链接</a>。真的很复杂,当时通宵了好几天。我从2009年开始,做的前端程序基本全是SPA,我很早就在一些技术大会上讲,前端应用时代会来临(当时还没有SPA这个叫法),模糊C/S和B/S的产品形态边界,同学们要抓紧时间提高自己,不要到时被淘汰了。但我等了很多年,也没迎来SPA的大爆发。Web Site一直是主力产品形态,根本就等不来SPA时代。今天很多同学说SPA说得很热闹,事实上很可能只是这些同学的一厢情愿。</p><p>事实上,SPA时代等是等不来的。一个可悲的事实是,工程师在公司里的定位都是执行层,而非策划层,策划是上游,对应的岗位是公司老板,最次也是产品经理,不是工程师。而产品经理们会什么呀?他们身上没有技术基因,不会主动想到Web前端可以做新的产品形态的。如果产品层面没需求,你怎么等来SPA时代?只有一个可能性能等来,就是国外有一个非常成功的项目,是用SPA做的。那么国内很可能从老板到产品经理会跟风去抄袭一个。也就是说,国外的老外们不做出点什么产品创新,我们国内的工程师就很难有机会有生产环境上做些SPA的富应用了。有些同学拿了几个简单的页面给我看, 到了AJAX无刷新,然后display:none和block实现了场景切换,跟我说,看,我们SPA了。其实真不是。在我看来,SPA真正的威力不是把多个网页变成单个网页,而是在产品形态上就彻底跳出网页般的排版布局。比如说做游戏、做网络IDE、做网页版PhotoShop之类。而这,需要的不仅是技术能力,而进一步需要产品设计能力,能不能技术驱动产品创新,不要再折腾什么技术工程化,发点力在技术产品化上。如果Google的工程师不用AJAX做Gmail,网络邮箱是不是会一直是Frameset方式?如果Google不做Google Map,是不是我们就一直没有网页地图可用了?我们自己能不能主动想想Websocket、Canvas2d、WebGL、CSS3之类的东西能不能做些和HTML4时代不同的产品出来?我举个例子,带图画板的网络聊天室怎么样?基于WebGL的Mmorpg怎么样?在同一个页面上打通DOM和WebGL会怎么样?我再留一个问题给各位思考:Canvas可以实现截图,toDataURL方法嘛,那么DOM能不能通过技术手段实现截图功能?给个提示:遍历DOM节点,然后做z轴上的排序,然后一个一个将他们画到一个看不见的Canvas上去……</p><p>几年前我就一直置力于技术驱动产品创新,号召圈里的同学们多投入点精力在技术产品化上,不然HTML5那么多牛逼的功能都白瞎了,说是进入了HTML5时代,可是产品形态依然没有啥变化,身为前端工程师我觉得很耻辱。可是,我一个人的能力实在有限,我努力了几年,我也号召了几年,然后终于放弃了。现在我已经死心转型做产品了。相信我写了这样的文章,看官看完之后,依然会该干嘛干嘛。</p><p>关于SPA,再多说两句,就是SPA的代码组织方式不用围绕URL路由来组织,如果是个纯单机应用没有服务端,是不是代码就没法组织了?当然不是。路由是好东西,但我推荐用自定义事件来进行路由,不要用URL。这里有个我写的斗地主单机小游戏,有兴趣的同学可以看下:<a href="http://www.adanghome.com/js_demo/29/" target="_blank" rel="noopener">链接</a> 。</p><p>最后,SPA什么时候可以迎来大爆发?我反正挺悲观的,我等了6、7年没等来。现在说得热闹,可是没见着几个SPA的强产品需求。没有产品层面的强需求,其实意味着折腾来折腾去,不过是前端圈自己在玩,孤芳自赏,以及面试时为难应聘者,跟前端面试时面算法似的。</p><p>React Native和PhoneGap<br>再说说React Native。我不看好这种方式,阉割版的CSS导致前端技能的受限,对Native底层的黑盒导致调试和扩展的困难,另外“learn once write anywhere”的性价比也并不高。而且,公司定岗的原因,iOS和Android团队也绝对不会对你友好,就像前端学了点Node服务端去挑战Java、PHP后端位置一下。于技术,于团队,真的是坑多于利的技术。</p><p>相比之下,PhoneGap那种思路,GUI交给Webview里的H5,底层交给iOS和Android,Native和H5通过jsBridge通信,是团队合作成本最低的方式,而且只要jsBridge写得好,一套HTML5代码也是最容易实现“write once run anywhere”的。三端的界面可以共用一套代码。我更倾向于后者。</p><p>当然,一个不容忽视的问题是,webview当前的性能还有严重问题,如果交互效果复杂一些,在低端机上的表现就会卡顿,所以采用这种技术方案时,受限于性能,产品的设计要尽量简单。</p><p>当下并没有完美的解决方案,但各方面原因考虑下来,hybird仍然是更好的选择。React Native唯一能拿出来说事的只有webview性能不好这一点,只是如果webview的性能问题在未来得到解决了呢?记得摩尔定律吗?所以说React Native不过是个不怎么样的临时解决方案而已,一旦webview的性能问题得到质的变化,React Native就没什么存在价值了。当然,话又说回来了,webview性能问题什么时候能够被解决呢?也没那么乐观,反正我从2011年等到现在也没等到。iOS其实已经很不错了,Android的碎片化是个深坑。</p><p>对了,我徒弟就用HTML5做了个SPA,用hybrid方式包成了iOS和Android的App,叫做“健康日记”(一个致力于帮助你养成健康规律生活习惯的App)。在Android下确实被碎片化折腾死了,但开发团队确实成本很低。感兴趣的同学可以下载试试。</p><h2 id="关于微信小程序"><a href="#关于微信小程序" class="headerlink" title="关于微信小程序"></a>关于微信小程序</h2><p>今年一个很热闹的词就是微信小程序,从上半年张小龙宣布应用号在筹备,到下半年小程序SDK掀开神秘面纱,小程序着实牵动了很多人的心。</p><p>很多人稀里糊涂说微信小程序是H5技术,其实看完SDK我发现这跟HTML5没有半毛钱关系。微信小程序和之前百度、UC、QQ浏览器、Chrome Web Store、Firefox OS的Webapp完全不同。作为一个开放平台,不知道微信为什么放弃百度轻应用的思路,设计这么个SDK。坦白讲,这么设计SDK讨好不到任何人,Native开发的同学并不熟悉,而前端开发的同学也会觉得怪怪的很受限制。最重要的是,HTML5代码没法简单适配一下移植过来,还得重新开发一套。也就是说,这是一个伪HTML5的技术,开发的同学又多了一个平台要侍候。</p><p>我猜微信平台的同学可能会说,这是出于性能上的考虑。但我只能说,店大欺客,仗着用户量高,对开发者真不友好。做“健康日记”时,本来打算借应用号的东风的,希望在应用号发布上简单适配一下就上微信的,结果等了半年等来这么个SDK,一看程序完全没法移植,要完全重新开发一套出来,只好放弃了。</p><p>就仗着用户量,iPhone不也活活逼死了Flash吗,你牛。</p><h2 id="关于前端的缺人和高薪水"><a href="#关于前端的缺人和高薪水" class="headerlink" title="关于前端的缺人和高薪水"></a>关于前端的缺人和高薪水</h2><p>我工作十年以来,前端圈前所未有如此盛世,技术圈也热闹,薪水也涨得飞快。但不得不说,技术圈的热闹和薪水涨得飞快并没有关系,仅仅是个巧合。</p><p>技术圈的热闹,原因在于跨界和伪全栈。薪水涨得飞快,原因在于HTML5的应用场景变多了,和技术没有关系。</p><p>说说HTML5现在的应用场景吧:</p><ul><li><p>PC端浏览器</p></li><li><p>移动端浏览器</p></li><li><p>超级App的Hybrid</p></li><li><p>微信公众号这种App开放平台</p></li><li><p>微信朋友圈微博的营销页面</p></li><li><p>百度轻应用(很小众)</p></li><li><p>后台(部分公司)</p></li><li><p>微信小程序(姑且也算)</p></li></ul><p>特别需要注意的是“超级App的hybrid”、“微信公众号”和“微信朋友圈微博营销”这三点,在移动互联网之初的2011、2012年,C/S结构的App是主流,Web已死是主基调,这两年前端的日子不算好过。然后随着淘宝、京东这种超级App越来越多,C/S结构更新麻烦,特别是iOS还需要在App Store审核的问题越来越放大,超级App不得不选择Hybrid的方式来进行发布和团队合作。而随着App也提供开放平台,Hybrid是唯一的选择(直到微信小程序打破它),前端的需求量突然大增,供不应求,于是前端长久以来一直被严重低估的局面终于得以扭转,薪水一路水涨船高。这种局面其实在2011、2012年的Android和iOS圈里也发生过,然后培训机构大量提供Android和iOS培训,再然后供求关系得以平衡,Native工程师的薪水恢复到了正常情况。有需求就会有供给,值得注意的是现在培训机制也在开展HTML5培训了,等到HTML5工程师被培训机制批量提供出来,前端的好日子也就到头了。</p><p>到那时,风就停了,猪就会摔下来。所以不要稀里糊涂地被技术圈的大跃进冲昏了头脑,你学会了两个时髦工具,面试官也在面试时问到了你时髦工具,然后你也顺利到了很高的薪水,就真以为是工具帮了你,你的能力真的就高了。不是的,是时代的原因。那个坐你旁边工作了好几年的一声不吭的老Java工程师薪水没你这个小毛头高,并不是人家水平没你好,并不是人家工作技术含量低,并不是人家比你笨,仅仅是因为做Java开发的太多了,市场饱和度高,仅此而已。所以偷着乐就可以了,然后赶紧补基本功。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>我曾经写过一篇文章,叫“置疑精神”。所以也请读者带着置疑精神来看我的这篇文章,认为有道理的,就听,认为不对的,保留你的疑问,不要迷信任何权威,这是技术人该有的美德。虽然我写了这么多,但也有可能,全是错的。</p><p>全文完</p><p>要仰望星空,更要脚踏实地。凡事多思考,共勉!</p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/sky-ground.jpg" alt="仰望星空与脚踏实地"></p>
<p>文章原名为《2016年前端技术观察》。无意中翻出这篇文章,给自己带来了很多感触。所以分享出来!</p>
<p>无论前端学习,还是大学生活,耳边少了亲人的叮咛,老师的训诫,我们或许都已变得太过浮躁,泼泼冷水,清醒一下也好!</p>
<p>评论中有人称赞,有人批评。但仁者见仁,智者见智,争辩是没有意义的。如果文章中某一点触动了你,也不枉浪费一番时间!</p>
</summary>
<category term="思考" scheme="http://nundy.cn/tags/%E6%80%9D%E8%80%83/"/>
</entry>
<entry>
<title>肖申克的救赎</title>
<link href="http://nundy.cn/2017/11/02/shawshank-redemption/"/>
<id>http://nundy.cn/2017/11/02/shawshank-redemption/</id>
<published>2017-11-01T16:26:53.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/有些鸟儿注定不会被关在笼子里.jpg" alt="有些鸟儿注定不会被关在笼子里"></p><p>You know some birds are not meant to be caged, their feathers are just too bright.</p><p>你知道,有些鸟儿是注定不会被关在牢笼里的,它们的每一片羽毛都闪耀着自由的光辉.</p><a id="more"></a><hr><p><img src="http://ozgbjelmj.bkt.clouddn.com/锤子与圣经.jpg" alt="安迪说的那棵树"></p><p>我回首前尘往事,犯下重罪的小笨蛋</p><p>我想跟他沟通让他明白</p><p>但我办不到,那个少年早就不见了,只剩下我垂老之躯。</p><hr><p><img src="http://ozgbjelmj.bkt.clouddn.com/主题封面.jpg" alt="主题封面"></p><p>或许有一天你鼓起勇气,把心中的一切和盘托出,结果只落得让别人看笑话.</p><p>因为他们压根儿不懂你在说什么,也不知道你为什么觉得事情那么重要.</p><p>说着说着,几乎要哭了出来。</p><p>我想普天下最糟的事,莫过于怀着满腔心事与秘密,却非无人可诉,而是没有人听得懂!</p><p><a href="/photos/">肖申克的救赎图册</a></p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/有些鸟儿注定不会被关在笼子里.jpg" alt="有些鸟儿注定不会被关在笼子里"></p>
<p>You know some birds are not meant to be caged, their feathers are just too bright.</p>
<p>你知道,有些鸟儿是注定不会被关在牢笼里的,它们的每一片羽毛都闪耀着自由的光辉.</p>
</summary>
<category term="Movie" scheme="http://nundy.cn/tags/Movie/"/>
</entry>
<entry>
<title>package.json详解</title>
<link href="http://nundy.cn/2017/11/01/package-json/"/>
<id>http://nundy.cn/2017/11/01/package-json/</id>
<published>2017-11-01T15:09:00.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/package-json.jpg" alt="package-json"></p><p>本文档的内容是package.json配置里边各个属性的含义解释。</p><a id="more"></a><p>package.json中最重要的属性是name和version两个属性,这两个属性是必须要有的,否则模块就无法被安装。</p><p>这两个属性一起形成了一个npm模块的唯一标识符。</p><p>模块中内容变更的同时,模块版本也应该一起变化。name属性就是你的模块名称。</p><h3 id="name属性"><a href="#name属性" class="headerlink" title="name属性"></a>name属性</h3><ul><li><p>name必须小于等于214个字节,包括前缀名称在内(如 xxx/xxxmodule)。</p></li><li><p>不能含有大写字母</p></li><li><p>不要使用和node核心模块一样的名称</p></li><li><p>name中不要含有”js”和”node”。</p></li><li><p>name属性会成为模块url、命令行中的一个参数或者一个文件夹名称,任何非url安全的字符在name中都不能使用,也不能以”_”或”.”开头</p></li><li><p>name属性也许会被写在require()的参数中,所以最好取个简短而语义化的值。</p></li><li><p>创建一个模块前可以先到<a href="https://www.npmjs.com/" target="_blank" rel="noopener">网址</a>查查name是否已经被占用.</p></li><li><p>name属性可以有一些前缀</p></li></ul><h3 id="version属性"><a href="#version属性" class="headerlink" title="version属性"></a>version属性</h3><ul><li>version必须可以被npm依赖的一个node-semver模块解析。具体规则见下面的dependencies模块</li></ul><h3 id="description属性"><a href="#description属性" class="headerlink" title="description属性"></a>description属性</h3><ul><li>一个描述,方便别人了解你的模块作用,搜索的时候也有用。</li></ul><h3 id="keywords属性"><a href="#keywords属性" class="headerlink" title="keywords属性"></a>keywords属性</h3><ul><li>一个字符串数组,方便别人搜索到本模块</li></ul><h3 id="homepage属性"><a href="#homepage属性" class="headerlink" title="homepage属性"></a>homepage属性</h3><ul><li>项目主页url。注意: 这个项目主页url和url属性不同,如果你填写了url属性,npm注册工具会认为你把项目发布到其他地方了,获取模块的时候不会从npm官方仓库获取,而是会重定向到url属性配置的地址。(原文档中用了 spit(吐)这个单词,作者表示他不是在开玩笑:)</li></ul><h3 id="bugs属性"><a href="#bugs属性" class="headerlink" title="bugs属性"></a>bugs属性</h3><ul><li><p>填写一个bug提交地址或者一个邮箱,被你的模块坑到的人可以通过这里吐槽</p></li><li><p>url和email可以任意填或不填,如果只填一个,可以直接写成一个字符串而不是对象。如果填写了url,npm bugs命令会使用这个url。</p></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "url" : "https://github.com/owner/project/issues",</span><br><span class="line"> "email" : "project@hostname.com"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="license属性"><a href="#license属性" class="headerlink" title="license属性"></a>license属性</h3><ul><li>为你的模块制定一个协议,让用户知道他们有何权限来使用你的模块,以及使用该模块有哪些限制。最简单的,例如你用BSD-3-Clause 或 MIT之类的协议,如下:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "license" : "BSD-3-Clause"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>查看<a href="https://spdx.org/licenses/" target="_blank" rel="noopener">协议列表</a>。</li></ul><h3 id="和用户相关的属性-author、contributors"><a href="#和用户相关的属性-author、contributors" class="headerlink" title="和用户相关的属性: author、contributors"></a>和用户相关的属性: author、contributors</h3><ul><li>“author”是一个码农, “contributors”是一个码农数组。 “person”是一个有一些描述属性的对象,如下 like this:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name" : "Barney Rubble", </span><br><span class="line"> "email" : "b@rubble.com", </span><br><span class="line"> "url" : "http://barnyrubble.tumblr.com/"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="files属性"><a href="#files属性" class="headerlink" title="files属性"></a>files属性</h3><ul><li>“files”属性的值是一个数组,内容是模块下文件名或者文件夹名.如果是文件夹名,则文件夹下所有的文件也会被包含进来(除非文件被另一些配置排除了)你也可以在模块根目录下创建一个”.npmignore”文件,写在这个文件里边的文件即便被写在files属性里边也会被排除在外,这个文件的写法”.gitignore”类似。</li></ul><h3 id="main属性"><a href="#main属性" class="headerlink" title="main属性"></a>main属性</h3><ul><li>main属性指定了程序的主入口文件。意思是,如果你的模块被命名为foo,用户安装了这个模块并通过require(“foo”)来使用这个模块,那么require返回的内容就是main属性指定的文件中 module.exports指向的对象。它应该指向模块根目录下的一个文件。对大对数模块而言,这个属性更多的是让模块有一个主入口文件,然而很多模块并不写这个属性。</li></ul><h3 id="bin属性"><a href="#bin属性" class="headerlink" title="bin属性"></a>bin属性</h3><ul><li>很多模块有一个或多个需要配置到PATH路径下的可执行模块,npm让这个工作变得十分简单(实际上npm本身也是通过bin属性安装为一个可执行命令的)如果要用npm的这个功能,在package.json里边配置一个bin属性。bin属性是一个已命令名称为key,本地文件名称为value的map如下:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "bin" : {</span><br><span class="line"> "myapp" : "./cli.js"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>模块安装的时候,若是全局安装,则npm会为bin中配置的文件在bin目录下创建一个软连接(对于windows系统,默认会在C:\Users\username\AppData\Roaming\npm目录下),若是局部安装,则会在项目内的./node_modules/.bin/目录下创建一个软链接。因此,按上面的例子,当你安装myapp的时候,npm就会为cli.js在/usr/local/bin/myapp路径创建一个软链接。如果你的模块只有一个可执行文件,并且它的命令名称和模块名称一样,你可以只写一个字符串来代替上面那种配置,例如:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "my-program",</span><br><span class="line"> "version": "1.2.5",</span><br><span class="line"> "bin": "./path/to/program"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>作用和如下写法相同:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "my-program",</span><br><span class="line"> "version": "1.2.5",</span><br><span class="line"> "bin" : {</span><br><span class="line"> "my-program" : "./path/to/program"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="man属性"><a href="#man属性" class="headerlink" title="man属性"></a>man属性</h3><ul><li>制定一个或通过数组制定一些文件来让linux下的man命令查找文档地址。如果只有一个文件被指定的话,安装后直接使用man+模块名称,而不管man指定的文件的实际名称。例如:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name" : "foo",</span><br><span class="line"> "version" : "1.2.3",</span><br><span class="line"> "description" : "A packaged foo fooer for fooing foos",</span><br><span class="line"> "main" : "foo.js",</span><br><span class="line"> "man" : "./man/doc.1"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>通过man foo命令会得到 ./man/doc.1 文件的内容。如果man文件名称不是以模块名称开头的,安装的时候会给加上模块名称前缀。因此,下面这段配置:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name" : "foo",</span><br><span class="line"> "version" : "1.2.3",</span><br><span class="line"> "description" : "A packaged foo fooer for fooing foos",</span><br><span class="line"> "main" : "foo.js",</span><br><span class="line"> "man" : [ "./man/foo.1", "./man/bar.1" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>会创建一些文件来作为man foo和man foo-bar命令的结果。man文件必须以数字结尾,或者如果被压缩了,以.gz结尾。数字表示文件将被安装到man的哪个部分。</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name" : "foo",</span><br><span class="line"> "version" : "1.2.3",</span><br><span class="line"> "description" : "A packaged foo fooer for fooing foos",</span><br><span class="line"> "main" : "foo.js",</span><br><span class="line"> "man" : [ "./man/foo.1", "./man/foo.2" ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>会创建 man foo 和 man 2 foo 两条命令。</li></ul><h3 id="directories属性"><a href="#directories属性" class="headerlink" title="directories属性"></a>directories属性</h3><ul><li>CommonJs通过directories来制定一些方法来描述模块的结构,看看npm的package.json文件<a href="https://registry.npmjs.org/npm/latest" target="_blank" rel="noopener">https://registry.npmjs.org/npm/latest</a> ,可以发现里边有这个字段的内容。目前这个配置没有任何作用,将来可能会整出一些花样来。</li></ul><h3 id="directories-lib属性"><a href="#directories-lib属性" class="headerlink" title="directories.lib属性"></a>directories.lib属性</h3><ul><li>告诉用户模块中lib目录在哪,这个配置目前没有任何作用,但是对使用模块的人来说是一个很有用的信息。</li></ul><h3 id="directories-bin属性"><a href="#directories-bin属性" class="headerlink" title="directories.bin属性"></a>directories.bin属性</h3><ul><li>如果你在这里指定了bin目录,这个配置下面的文件会被加入到bin路径下,如果你已经在package.json中配置了bin目录,那么这里的配置将不起任何作用。</li></ul><h3 id="directories-man属性"><a href="#directories-man属性" class="headerlink" title="directories.man属性"></a>directories.man属性</h3><ul><li>指定一个目录,目录里边都是man文件,这是一种配置man文件的语法糖。</li></ul><h3 id="directories-doc属性"><a href="#directories-doc属性" class="headerlink" title="directories.doc属性"></a>directories.doc属性</h3><ul><li>在这个目录里边放一些markdown文件,可能最终有一天它们会被友好的展现出来(应该是在npm的网站上)</li></ul><h3 id="directories-example属性"><a href="#directories-example属性" class="headerlink" title="directories.example属性"></a>directories.example属性</h3><ul><li>放一些示例脚本,或许某一天会有用 - -!</li></ul><h3 id="repository属性"><a href="#repository属性" class="headerlink" title="repository属性"></a>repository属性</h3><ul><li>指定一个代码存放地址,对想要为你的项目贡献代码的人有帮助。像这样:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">"repository" :</span><br><span class="line">{</span><br><span class="line"> "type" : "git",</span><br><span class="line"> "url" : "https://github.com/npm/npm.git"</span><br><span class="line">}</span><br><span class="line">"repository" :</span><br><span class="line">{</span><br><span class="line"> "type" : "svn",</span><br><span class="line"> "url" : "https://v8.googlecode.com/svn/trunk/"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>若你的模块放在GitHub, GitHub gist, Bitbucket, or GitLab的仓库里,npm install的时候可以使用缩写标记来完成:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">"repository": "npm/npm"</span><br><span class="line">"repository": "gist:11081aaa281"</span><br><span class="line">"repository": "bitbucket:example/repo"</span><br><span class="line">"repository": "gitlab:another/repo"</span><br></pre></td></tr></table></figure><h3 id="scripts属性"><a href="#scripts属性" class="headerlink" title="scripts属性"></a>scripts属性</h3><ul><li>scripts属性是一个对象,里边指定了项目的生命周期个各个环节需要执行的命令。key是生命周期中的事件,value是要执行的命令。具体的内容有 install start stop 等,详见<a href="https://docs.npmjs.com/misc/scripts" target="_blank" rel="noopener">这里</a></li></ul><h3 id="config属性"><a href="#config属性" class="headerlink" title="config属性"></a>config属性</h3><ul><li>用来设置一些项目不怎么变化的项目配置,例如port等。用户用的时候可以使用如下用法:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http.createServer(...).listen(process.env.npm_package_config_port)</span><br></pre></td></tr></table></figure><ul><li>可以通过npm config set foo:port 80来修改config。详见<a href="https://docs.npmjs.com/misc/config" target="_blank" rel="noopener">这里</a></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name" : "foo",</span><br><span class="line"> "config" : { "port" : "8080" }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="dependencies属性"><a href="#dependencies属性" class="headerlink" title="dependencies属性"></a>dependencies属性</h3><ul><li><p>dependencies属性是一个对象,配置模块依赖的模块列表,key是模块名称,value是版本范围,版本范围是一个字符,可以被一个或多个空格分割。dependencies也可以被指定为一个git地址或者一个压缩包地址。不要把测试工具或transpilers写到dependencies中。 下面是一些写法,详见<a href="https://docs.npmjs.com/misc/semver" target="_blank" rel="noopener">这里</a></p></li><li><p>version精确匹配版本</p></li><li>>version 必须大于某个版本</li><li>>=version 大于等于</li><li><version 小于</li><li><=versionversion 小于</li><li>~version “约等于”,具体规则详见semver文档</li><li>^version “兼容版本”具体规则详见semver文档</li><li>1.2.x 仅一点二点几的版本</li><li>http://…见下面url作为denpendencies的说明</li><li>“” 空字符,和*相同,任意版本</li><li>version1 - version2 相当于 >=version1 <=version2.</li><li>range1 || range2 范围1和范围2满足任意一个都行</li><li>git… 见下面git url作为denpendencies的说明</li><li>user/repo See 见下面GitHub仓库的说明</li><li>tag 发布的一个特殊的标签,见<a href="https://docs.npmjs.com/getting-started/using-tags" target="_blank" rel="noopener">npm-tag的文档</a></li><li>path/path/path 见下面本地模块的说明下面的写法都是可以的:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "dependencies" :</span><br><span class="line"> {</span><br><span class="line"> "foo" : "1.0.0 - 2.9999.9999",</span><br><span class="line"> "bar" : ">=1.0.2 <2.1.2",</span><br><span class="line"> "baz" : ">1.0.2 <=2.3.4",</span><br><span class="line"> "boo" : "2.0.1",</span><br><span class="line"> "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",</span><br><span class="line"> "asd" : "http://asdf.com/asdf.tar.gz",</span><br><span class="line"> "til" : "~1.2",</span><br><span class="line"> "elf" : "~1.2.3",</span><br><span class="line"> "two" : "2.x",</span><br><span class="line"> "thr" : "3.3.x",</span><br><span class="line"> "lat" : "latest",</span><br><span class="line"> "dyl" : "file:../dyl"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="URLs-as-Dependencies属性"><a href="#URLs-as-Dependencies属性" class="headerlink" title="URLs as Dependencies属性"></a>URLs as Dependencies属性</h3><ul><li>在版本范围的地方可以写一个url指向一个压缩包,模块安装的时候会把这个压缩包下载下来安装到模块本地。</li></ul><h3 id="Git-URLs-as-Dependencies属性"><a href="#Git-URLs-as-Dependencies属性" class="headerlink" title="Git URLs as Dependencies属性"></a>Git URLs as Dependencies属性</h3><ul><li>Git url可以像下面一样:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git://github.com/user/project.git#commit-ish</span><br><span class="line">git+ssh://user@hostname:project.git#commit-ish</span><br><span class="line">git+ssh://user@hostname/project.git#commit-ish</span><br><span class="line">git+http://user@hostname/project/blah.git#commit-ish</span><br><span class="line">git+https://user@hostname/project/blah.git#commit-ish</span><br></pre></td></tr></table></figure><ul><li>commit-ish 可以是任意标签,哈希值,或者可以检出的分支,默认是master分支。</li></ul><h3 id="GitHub-URLs属性"><a href="#GitHub-URLs属性" class="headerlink" title="GitHub URLs属性"></a>GitHub URLs属性</h3><ul><li>支持github的 username/modulename 的写法,#后边可以加后缀写明分支hash或标签:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "foo",</span><br><span class="line"> "version": "0.0.0",</span><br><span class="line"> "dependencies": {</span><br><span class="line"> "express": "visionmedia/express",</span><br><span class="line"> "mocha": "visionmedia/mocha#4727d357ea"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Local-Paths属性"><a href="#Local-Paths属性" class="headerlink" title="Local Paths属性"></a>Local Paths属性</h3><ul><li>npm2.0.0版本以上可以提供一个本地路径来安装一个本地的模块,通过npm install xxx –save 来安装,格式如下:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">../foo/bar</span><br><span class="line">~/foo/bar</span><br><span class="line">./foo/bar</span><br><span class="line">/foo/bar</span><br></pre></td></tr></table></figure><ul><li>package.json 生成的相对路径如下:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "baz",</span><br><span class="line"> "dependencies": {</span><br><span class="line"> "bar": "file:../foo/bar"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>这种属性在离线开发或者测试需要用npm install的情况,又不想自己搞一个npm server的时候有用,但是发布模块到公共仓库时不应该使用这种属性。</li></ul><h3 id="devDependencies属性"><a href="#devDependencies属性" class="headerlink" title="devDependencies属性"></a>devDependencies属性</h3><ul><li>如果有人想要下载并使用你的模块,也许他们并不希望或需要下载一些你在开发过程中使用的额外的测试或者文档框架。在这种情况下,最好的方法是把这些依赖添加到devDependencies属性的对象中。这些模块会在npm link或者npm install的时候被安装,也可以像其他npm配置一样被管理,详见npm的config文档。对于一些跨平台的构建任务,例如把CoffeeScript编译成JavaScript,就可以通过在package.json的script属性里边配置prepublish脚本来完成这个任务,然后需要依赖的coffee-script模块就写在devDependencies属性种。例如:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "ethopia-waza",</span><br><span class="line"> "description": "a delightfully fruity coffee varietal",</span><br><span class="line"> "version": "1.2.3",</span><br><span class="line"> "devDependencies": {</span><br><span class="line"> "coffee-script": "~1.6.3"</span><br><span class="line"> },</span><br><span class="line"> "scripts": {</span><br><span class="line"> "prepublish": "coffee -o lib/ -c src/waza.coffee"</span><br><span class="line"> },</span><br><span class="line"> "main": "lib/waza.js"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>prepublish脚本会在发布之前运行,因此用户在使用之前就不用再自己去完成编译的过程了。在开发模式下,运行npm install也会执行这个脚本(见npm script文档),因此可以很方便的调试。</li></ul><h3 id="peerDependencies属性"><a href="#peerDependencies属性" class="headerlink" title="peerDependencies属性"></a>peerDependencies属性</h3><ul><li>有时候做一些插件开发,比如grunt等工具的插件,它们往往是在grunt的某个版本的基础上开发的,而在他们的代码中并不会出现require(“grunt”)这样的依赖,dependencies配置里边也不会写上grunt的依赖,为了说明此模块只能作为插件跑在宿主的某个版本范围下,可以配置peerDependencies:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "tea-latte",</span><br><span class="line"> "version": "1.3.5",</span><br><span class="line"> "peerDependencies": {</span><br><span class="line"> "tea": "2.x"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>上面这个配置确保再npm install的时候tea-latte会和2.x版本的tea一起安装,而且它们两个的依赖关系是同级的:├── <a href="mailto:tea-latte@1.3.5" target="_blank" rel="noopener">tea-latte@1.3.5</a>└── <a href="mailto:tea@2.2.0" target="_blank" rel="noopener">tea@2.2.0</a>这个配置的目的是让npm知道,如果要使用此插件模块,请确保安装了兼容版本的宿主模块。</li></ul><h3 id="bundledDependencies属性"><a href="#bundledDependencies属性" class="headerlink" title="bundledDependencies属性"></a>bundledDependencies属性</h3><ul><li>上面的单词少个d,写成bundleDependencies也可以。指定发布的时候会被一起打包的模块。</li></ul><h3 id="optionalDependencies属性"><a href="#optionalDependencies属性" class="headerlink" title="optionalDependencies属性"></a>optionalDependencies属性</h3><ul><li>如果一个依赖模块可以被使用, 同时你也希望在该模块找不到或无法获取时npm继续运行,你可以把这个模块依赖放到optionalDependencies配置中。这个配置的写法和dependencies的写法一样,不同的是这里边写的模块安装失败不会导致npm install失败。当然,这种模块就需要你自己在代码中处理模块确实的情况了,例如:</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">try {</span><br><span class="line"> var foo = require('foo')</span><br><span class="line"> var fooVersion = require('foo/package.json').version</span><br><span class="line">} catch (er) {</span><br><span class="line"> foo = null</span><br><span class="line">}</span><br><span class="line">if ( notGoodFooVersion(fooVersion) ) {</span><br><span class="line"> foo = null</span><br><span class="line">}</span><br><span class="line">// .. then later in your program ..</span><br><span class="line">if (foo) {</span><br><span class="line"> foo.doFooThings()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>optionalDependencies 中的配置会覆盖dependencies中的配置,最好只在一个地方写。</li></ul><h3 id="engines属性"><a href="#engines属性" class="headerlink" title="engines属性"></a>engines属性</h3><ul><li>你可以指定项目运行的node版本范围,如下:{ “engines” : { “node” : “>=0.10.3 <0.12” } }和dependencies一样,如果你不指定版本范围或者指定为*,任何版本的node都可以。也可以指定一些npm版本可以正确的安装你的模块,例如:{ “engines” : { “npm” : “~1.0.20” } }要注意的是,除非你设置了engine-strict属性,engines属性是仅供参考的。</li></ul><h3 id="engineStrict属性"><a href="#engineStrict属性" class="headerlink" title="engineStrict属性"></a>engineStrict属性</h3><ul><li>注意:这个属性已经弃用,将在npm 3.0.0 版本干掉。</li></ul><h3 id="os属性"><a href="#os属性" class="headerlink" title="os属性"></a>os属性</h3><ul><li>可以指定你的模块只能在哪个操作系统上跑:“os” : [ “darwin”, “linux” ]也可以指定黑名单而不是白名单:“os” : [ “!win32” ]服务的操作系统是由process.platform来判断的,这个属性允许黑白名单同时存在,虽然没啥必要这样搞…</li></ul><h3 id="cpu属性"><a href="#cpu属性" class="headerlink" title="cpu属性"></a>cpu属性</h3><ul><li>限制模块只能在某某cpu架构下运行“cpu” : [ “x64”, “ia32” ]同样可以设置黑名单:“cpu” : [ “!arm”, “!mips” ]cpu架构通过 process.arch 判断</li></ul><h3 id="preferGlobal属性"><a href="#preferGlobal属性" class="headerlink" title="preferGlobal属性"></a>preferGlobal属性</h3><ul><li>如果您的软件包主要用于安装到全局的命令行应用程序,那么该值设置为true ,如果它被安装在本地,则提供一个警告。实际上该配置并没有阻止用户把模块安装到本地,只是防止该模块被错误的使用引起一些问题。</li></ul><h3 id="private属性"><a href="#private属性" class="headerlink" title="private属性"></a>private属性</h3><ul><li>如果这个属性被设置为true,npm将拒绝发布它,这是为了防止一个私有模块被无意间发布出去。如果你只想让模块被发布到一个特定的npm仓库,如一个内部的仓库,可与在下面的publishConfig中配置仓库参数。</li></ul><h3 id="publishConfig属性"><a href="#publishConfig属性" class="headerlink" title="publishConfig属性"></a>publishConfig属性</h3><ul><li>这个配置是会在模块发布时用到的一些值的集合。如果你不想模块被默认被标记为最新的,或者默认发布到公共仓库,可以在这里配置tag或仓库地址。</li></ul><h3 id="DEFAULT-VALUES属性"><a href="#DEFAULT-VALUES属性" class="headerlink" title="DEFAULT VALUES属性"></a>DEFAULT VALUES属性</h3><ul><li>npm设置了一些默认参数,如:“scripts”: {“start”: “node server.js”}如果模块根目录下有一个server.js文件,那么npm start会默认运行这个文件。“scripts”:{“preinstall”: “node-gyp rebuild”}如果模块根目录下有binding.gyp, npm将默认用node-gyp来编译preinstall的脚本“contributors”: […]若模块根目录下有AUTHORS 文件,则npm会按Name (url)格式解析每一行的数据添加到contributors中,可以用#添加行注释</li></ul>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/package-json.jpg" alt="package-json"></p>
<p>本文档的内容是package.json配置里边各个属性的含义解释。</p>
</summary>
<category term="Node" scheme="http://nundy.cn/tags/Node/"/>
</entry>
<entry>
<title>URI 的故事</title>
<link href="http://nundy.cn/2017/10/25/url-coding/"/>
<id>http://nundy.cn/2017/10/25/url-coding/</id>
<published>2017-10-25T15:22:22.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/uri-coding.png" alt="uri-coding"></p><p>一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。</p><p>这是因为网络标准RFC 1738做了硬性规定</p><a id="more"></a><blockquote><p>“…Only alphanumerics [0-9a-zA-Z], the special characters “$-<em>.+!*’()”,[not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL.”<br>“只有字母和数字[0-9a-zA-Z]、一些特殊符号”$-</em>.+!*’()”,[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”</p></blockquote><p>这意味着,如果URL中有汉字,就必须编码后使用。然而RFC 1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定,这导致”URL编码”成为了一个混乱的领域。</p><h2 id="编码的四种情况"><a href="#编码的四种情况" class="headerlink" title="编码的四种情况"></a>编码的四种情况</h2><ul><li><p>网址路径中包含汉字 : 使用UTF-8编码(猜测:浏览器厂商可能默认都为UTF-8编码,故其实是遵循浏览器默认编码)</p></li><li><p>查询字符串包含汉字 : 不统一,包括操作系统默认编码和UTF-8(猜测:浏览器默认编码)编码</p></li><li><p>表单参数的中文编码 : 首先遵循表单的accept-charset属性,其次遵循页面整体的charset属性(猜测:之后应该依次为浏览器、操作系统默认的编码,IE低版本可能是跳过浏览器编码,直接采用操作系统编码)</p></li><li><p>Ajax请求的中文编码 : IE浏览器遵循的是操作系统的默认编码,其它为UTF-8</p></li></ul><p>总结 :无论哪种情况,优先级从高到低依次为:accept-charset、charset、浏览器默认、操作系统默认。(编码情况与浏览器厂商、版本、页面的具体设置等多方面因素影响,不可一概而论,具体情况应当具体考虑,如总结有误,欢迎批评指出)</p><h2 id="编码的三种方式"><a href="#编码的三种方式" class="headerlink" title="编码的三种方式"></a>编码的三种方式</h2><ul><li><p>escape()(不提倡)</p></li><li><p>encodeURI()</p></li><li><p>encodeURIComponent()</p></li></ul><h3 id="一、escape-、unescape"><a href="#一、escape-、unescape" class="headerlink" title="一、escape()、unescape()"></a>一、escape()、unescape()</h3><p>规则:除了ASCII字母、数字、标点符号”@ * _ + - . /“以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式</p><p>注意:</p><p>1、不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值</p><p>2、(没理解这点)无论网页的原始编码是什么,一旦被Javascript编码,就都变为unicode字符。也就是说,Javascipt函数的输入和输出,默认都是Unicode字符</p><p>3、escape()不对”+”编码,但是网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。</p><h3 id="二、encodeURI-、decodeURI"><a href="#二、encodeURI-、decodeURI" class="headerlink" title="二、encodeURI()、decodeURI()"></a>二、encodeURI()、decodeURI()</h3><p>规则:除常见的符号以外,对其他一些在网址中有特殊含义的符号” ; / ? : @ & = + $ , # “,不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%</p><p>注意:</p><p>1、encodeURI()是Javascript中真正用来对URL编码的函数</p><p>2、它不对单引号’编码</p><h3 id="三、encodeURIComponent-、decodeURIComponent"><a href="#三、encodeURIComponent-、decodeURIComponent" class="headerlink" title="三、encodeURIComponent()、decodeURIComponent()"></a>三、encodeURIComponent()、decodeURIComponent()</h3><p>规则:它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。”; / ? : @ & = + $ , # “ 这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。具体编码方法都是一样的。</p><hr><p>在具体编写代码时,犯过一个错误:将encodeURI(),写成了encodeURL();</p><p>所以此处牵扯出了URI-URL-URN的区别这个问题</p><h2 id="URI-URL-URN区别"><a href="#URI-URL-URN区别" class="headerlink" title="URI-URL-URN区别"></a>URI-URL-URN区别</h2><p><img src="http://ozgbjelmj.bkt.clouddn.com/URI-URL-URN.webp" alt="URI-URL-URN"></p><blockquote><p>统一资源标识符(URI)提供了一个简单、可扩展的资源标识方式。URI规范中的语义和语法来源于万维网全球信息主动引入的概念,万维网从1990年起使用这种标识符数据,并被描述为“万维网中的统一资源描述符”。</p></blockquote><h3 id="示例1"><a href="#示例1" class="headerlink" title="示例1"></a>示例1</h3><p>如果是一个人,我们会想到他的姓名和住址。</p><p>URL类似于住址,它告诉你一种寻找目标的方式(在这个例子中,是通过街道地址找到一个人)。要知道,上述定义同时也是一个URI。</p><p>相对地,我们可以把一个人的名字看作是URN;因此可以用URN来唯一标识一个实体。由于可能存在同名(姓氏也相同)的情况,所以更准确地说,人名这个例子并不是十分恰当。更为恰当的是书籍的ISBN码和产品在系统内的序列号,尽管没有告诉你用什么方式或者到什么地方去找到目标,但是你有足够的信息来检索到它。所有的URN都遵循如下语法(引号内的短语是必须的)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">< URN > ::= "urn:" < NID > ":" < NSS ></span><br></pre></td></tr></table></figure><p>其中NID是命名空间标识符,NSS是标识命名空间的特定字符串。</p><p>关于URL:</p><blockquote><p>URL是URI的一种,不仅标识了 Web 资源,还指定了操作或者获取方式,同时指出了主要访问机制和网络位置。</p></blockquote><p>关于URN:</p><blockquote><p>URN是URI的一种,用特定命名空间的名字标识资源。使用URN可以在不知道其网络位置及访问方式的情况下讨论资源。</p></blockquote><h3 id="示例2"><a href="#示例2" class="headerlink" title="示例2"></a>示例2</h3><p>如果有:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://bitpoetry.io/posts/hello.html#intro</span><br></pre></td></tr></table></figure><p>我们开始分析</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://</span><br></pre></td></tr></table></figure><p>是定义如何访问资源的方式。另外</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bitpoetry.io/posts/hello.html</span><br></pre></td></tr></table></figure><p>是资源存放的位置,那么,在这个例子中,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#intro</span><br></pre></td></tr></table></figure><p>是资源。</p><p>URL是URI的一个子集,告诉我们访问网络位置的方式。在我们的例子中,URL应该如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://bitpoetry.io/posts/hello.html</span><br></pre></td></tr></table></figure><p>URN是URI的子集,包括名字(给定的命名空间内),但是不包括访问方式,如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bitpoetry.io/posts/hello.html#intro</span><br></pre></td></tr></table></figure><p>就是这样。现在你应该能够辨别出URL和URN之间的不同。</p><p>如果你忘记了这篇文章的内容,至少要记住一件事:URI可以被分为URL、URN或两者的组合。如果你一直使用URI这个术语,就不会有错。</p><p>参考文献:</p><p>1、<a href="http://www.haorooms.com/post/js_escape_encodeURIComponent" target="_blank" rel="noopener">haorooms</a></p><p>2、<a href="http://blog.csdn.net/sgbfblog/article/details/37996081" target="_blank" rel="noopener">石锅拌饭</a></p><p>3、<a href="http://www.ruanyifeng.com/blog/2010/02/url_encoding.html" target="_blank" rel="noopener">阮一峰</a></p><p>4、<a href="https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651552631&idx=1&sn=9a05fd22a1d93551c960717270e9bb01&chksm=8025acb6b75225a05c4bc454e3a163faf2a5dbceb4c65ceddfeb288f08de137cddefbdb2fafd&mpshare=1&scene=1&srcid=1026IkHjCnbghTE0J0nluNZg#rd" target="_blank" rel="noopener">伯乐在线</a></p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/uri-coding.png" alt="uri-coding"></p>
<p>一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。</p>
<p>这是因为网络标准RFC 1738做了硬性规定</p>
</summary>
<category term="Network" scheme="http://nundy.cn/tags/Network/"/>
</entry>
<entry>
<title>《孔雀东南飞》</title>
<link href="http://nundy.cn/2017/08/16/peacock-fly-southeast/"/>
<id>http://nundy.cn/2017/08/16/peacock-fly-southeast/</id>
<published>2017-08-15T16:17:46.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/孔雀东南飞.jpg" alt="孔雀东南飞"></p><hr><p>我还在为了什么而停留?</p><p>记忆中曾有过些许小小的暗伤,确实困挠了我好久。</p><p>有时候什么都不说,沉默让人觉得稳重而深沉。</p><p>有时候什么也不做,这时光依然如此蹉跎。</p><a id="more"></a><p>该说的总是要说,该做的也始终要去做。</p><p>再等待,徒让岁月空接寂寞。</p><hr><p>我真的听得见时光的寂寞,深深的萦绕在每一个失眠的孤夜。</p><p>而最是每当午夜梦回,额上的汗珠,都让我难过得想马上缩做一团,想着怎么样才能让月光照不到我。</p><p>也许不哭,就会不太难过。一旦流下眼泪,那种压抑的情绪就再也不会受自己的控制。</p><p>生命的确像大海般浩瀚空广,我们都在寻找未知的对岸,我们谁都不知道,是谁在等着我们。</p><p>也许游过了这浩瀚汪洋,等我们的不会是我们盼望已久的。</p><p>想要表达的千言万语,这些框框架架怎么能够包罗得下,如今有了能够包容下这些的框架,所有语言,情感偏偏都一去无踪。</p><hr><p>我看孔雀东南飞,飞的并不都是孔雀,我一厢情愿的以为东南边飞着的都是孔雀,至少我还可以幻想一回,还可以留个美好的念想。</p><p>孔雀东南飞,除了它们,可还有谁到了天崖就一去不回。</p><p>诸般索碎万千喜悲,未曾体会,但又讲真正伤心难过的时候,哪一个能共我买酒同醉?</p><p>梦里伊人如酒,是否醉倒了时光无情的潮水,流连不返间,鬓角竟渐有白发催。</p><p>孔雀依然傲飞世间天南,那一方的潮水,是不是也和此间一样,有人流连忘返,是否同样的有人鬓角渐白依然横笛于苍然月下,对残月同起昔年舞,同寒霜共饮酒一壶?</p><hr><p>我们都老得太快,却聪明得太迟。</p><p>我心中的孔雀,愿你飞得平稳,愿你飞得平安,愿你飞得漂亮,不论你飞到什么地方,身边依然会有我如影随形的思念。</p><p>这思念跟酒一样,我早就醉倒了。</p><p>呵呵,我喜欢这样一种感觉,可是却不喜欢过那样一种人生。让别人同情却又分外凄然绝美的一种残缺岁月,</p><p>每一种结局,但凡我喜欢的,都是残缺不完美的,太完美了,逊于生活,断断续续,残缺的故事更容易让人心疼。</p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/孔雀东南飞.jpg" alt="孔雀东南飞"></p>
<hr>
<p>我还在为了什么而停留?</p>
<p>记忆中曾有过些许小小的暗伤,确实困挠了我好久。</p>
<p>有时候什么都不说,沉默让人觉得稳重而深沉。</p>
<p>有时候什么也不做,这时光依然如此蹉跎。</p>
</summary>
<category term="至此流年各天涯" scheme="http://nundy.cn/tags/%E8%87%B3%E6%AD%A4%E6%B5%81%E5%B9%B4%E5%90%84%E5%A4%A9%E6%B6%AF/"/>
</entry>
<entry>
<title>HTTPS 协议原理解析</title>
<link href="http://nundy.cn/2017/08/05/https/"/>
<id>http://nundy.cn/2017/08/05/https/</id>
<published>2017-08-05T14:22:30.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/https.jpg" alt="HTTPS协议"></p><p>HTTP使用普通的非加密TCP作为其传输机制</p><p>因此,处在网络适当位置的攻击者能够截取这个机制。</p><p>而HTTPS机制课保护网络传送的所有数据的隐秘性和完整性。</p><a id="more"></a><h2 id="HTTPS与HTTP关系"><a href="#HTTPS与HTTP关系" class="headerlink" title="HTTPS与HTTP关系"></a>HTTPS与HTTP关系</h2><h3 id="共同点"><a href="#共同点" class="headerlink" title="共同点:"></a>共同点:</h3><ul><li><p>它两本质上都属于应用层协议</p></li><li><p>无论是否使用SSL,HTTP的请求与响应都以完全相同的方式工作</p></li></ul><h3 id="不同点"><a href="#不同点" class="headerlink" title="不同点:"></a>不同点:</h3><ul><li><p>HTTP使用普通非加密TCP作为其传输机制,使用80端口</p></li><li><p>HTTPS通过一种安全的传输机制——安全套接层SSL传送数据,使用443端口</p></li></ul><h3 id="相比较"><a href="#相比较" class="headerlink" title="相比较:"></a>相比较:</h3><ul><li><p>HTTPS主要可以保护网络传送的所有数据的完整性和隐密性与身份认证功能</p></li><li><p>窃听风险——隐秘性;篡改风险——完整性;冒充风险——身份认证</p></li></ul><h2 id="SSL与TLS"><a href="#SSL与TLS" class="headerlink" title="SSL与TLS"></a>SSL与TLS</h2><h3 id="SSL与TLS历史"><a href="#SSL与TLS历史" class="headerlink" title="SSL与TLS历史"></a>SSL与TLS历史</h3><ul><li><p>产生背景:网景公司设计了SSL协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS</p></li><li><p>安全套接层协议:SSL1.0并未公布,SSL2.0公布后发现了许多漏洞,SSL3.0公布后得到了广泛采用</p></li><li><p>传输层安全协议:TLS1.0是在SSL3.0的基础上改进的,目前有TLS1.0、TLS1.1、TLS1.2三个版本,通常也会将其对应的标识为SSL 3.1,SSL 3.2,SSL 3.3</p></li><li><p>实际上我们现在的HTTPS都是用的TLS协议,但是由于SSL出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL依然是HTTPS的代名词</p></li></ul><h3 id="SSL与TLS主要结构"><a href="#SSL与TLS主要结构" class="headerlink" title="SSL与TLS主要结构"></a>SSL与TLS主要结构</h3><ul><li><p>SSL/TLS都分别可以分为两部分:记录协议、握手协议</p></li><li><p>记录协议建立在TCP之上,提供数据封装、加密等基本功能</p></li><li><p>握手协议建立在记录协议之上,提供身份认证、协商秘钥等功能</p></li><li><p>SSL/TLS处于应用层之下、传输层之上的中间部分,具体没有明确规定</p></li></ul><h3 id="SSL与TLS主要差异"><a href="#SSL与TLS主要差异" class="headerlink" title="SSL与TLS主要差异"></a>SSL与TLS主要差异</h3><ul><li><p>伪随机函数:TLS使用了称为PRF的伪随机函数来将密钥扩展成数据块,是更安全的方式</p></li><li><p>报警代码:TLS支持几乎所有的SSLv3.0报警代码,而且TLS还补充定义了很多报警代码</p></li><li><p>加密计算:TLS与SSLv3.0在计算主密值(master secret)时采用的方式不同</p></li></ul><h2 id="HTTPS加密原理"><a href="#HTTPS加密原理" class="headerlink" title="HTTPS加密原理"></a>HTTPS加密原理</h2><h3 id="对称秘钥-例如AES"><a href="#对称秘钥-例如AES" class="headerlink" title="对称秘钥(例如AES)"></a>对称秘钥(例如AES)</h3><p>优点:</p><p>a.秘钥生成简单,加密强度高</p><p>b.计算速度快</p><p>c.基本无长度限制</p><p>缺点:对称秘钥需要双方都知道,互相传输告知过程中容易被窃取;管理海量秘钥是很麻烦、很不安全的一件事;</p><h3 id="非对称秘钥-例如RSA"><a href="#非对称秘钥-例如RSA" class="headerlink" title="非对称秘钥(例如RSA)"></a>非对称秘钥(例如RSA)</h3><p>优点:RSA体制非常安全,公钥所有人都可以知道,每个人保存好自己的私钥就好</p><p>缺点:</p><p>a.不是每个客户都有公钥和私钥的,如果只有服务端,那么客户给服务端请求是安全了,但是,服务端返回的信息却还是不安全,不能要求每一个客户都去准备一份公钥私钥</p><p>b.RSA计算量太大,严重降低服务器性能</p><p>c.RSA每次加密,对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是 2048 位,意味着待加密内容不能超过 256 个字节</p><h3 id="结合非对称秘钥和对称秘钥-例如RSA-AES"><a href="#结合非对称秘钥和对称秘钥-例如RSA-AES" class="headerlink" title="结合非对称秘钥和对称秘钥(例如RSA+AES)"></a>结合非对称秘钥和对称秘钥(例如RSA+AES)</h3><p>由客户端生成一个AES需要的随机数X,通过RSA进行传输给服务端,然后此次会话双方就使用AES算法用随机数X来加密数据。现实中往往会更加复杂,为了保证秘钥的安全性,这个随机数一定不能有规律,被别人猜到,而通信双方都不相信对方生成的这个随机数是否真的随机。</p><p>所以,往往是客户端首次发起请求时一并发送一个随机数A,服务器响应时一并返回一个随机数B,第三次客户端通过公钥加密一个随机数C,然后双方根据这三个随机数,按照前两回合商量好的算法生成一份AES需要的秘钥。注意,前两个随机数均为加密,后一个是经过加密的!</p><h3 id="协商过程"><a href="#协商过程" class="headerlink" title="协商过程"></a>协商过程</h3><p>clientHello</p><ul><li><p>支持的SSL协议版本</p></li><li><p>支持的加密算法(RSA)</p></li><li><p>支持的压缩算法</p></li><li><p>随机数A</p></li></ul><p>serverHello</p><ul><li><p>确定使用的SSL协议版本</p></li><li><p>确定使用的加密算法(RSA)</p></li><li><p>确定使用的压缩算法</p></li><li><p>随机数B</p></li><li><p>服务器证书(颁发机构)</p></li><li><p>如果服务器也需要验证客户端,发出 CerficateRequest 消息</p></li></ul><p>客户端验证</p><ul><li>验证服务器证书是否可信,若不可信,则发出警告是否继续,否则继续通信</li></ul><p>客户端回应</p><ul><li><p>编码改变通知,表示之后的信息都将用双方商定的加密方法和密钥发送</p></li><li><p>随机数C,该随机数使用服务器证书内包含的公钥进行加密</p></li><li><p>如果服务器也需要验证客户端,附带客户端证书</p></li></ul><p>服务端验证</p><ul><li>验证客户端证书是否可信,若不可信,则断开连接,否则继续通信</li></ul><p>服务端响应</p><ul><li><p>编码改变通知,表示之后的信息都将用双方商定的加密方法和密钥发送</p></li><li><p>前面发送的所有内容的hash值,用来供客户端校验,也表示握手阶段结束</p></li></ul><p>接下来的工作实际上使用的就是普通的HTTP协议,只不过内容经过协商好的秘钥加密了</p><h3 id="涉及到的数字证书"><a href="#涉及到的数字证书" class="headerlink" title="涉及到的数字证书"></a>涉及到的数字证书</h3><p>a.数字证书颁发机构</p><p>b.数字证书有效期</p><p>c.数字证书拥有者是谁</p><p>d.数字证书拥有者的公钥</p><p>d.数字证书内的数字签名</p><p>e.数字证书内的数字签名使用的算法</p><p>注意点</p><p>a.指纹:数字证书的摘要HASH;指纹算法:生成数字证书的摘要HASH所需要的算法</p><p>b.数字签名的签发过程跟公钥加密的过程刚好相反,即:用’私钥’加密,’公钥’解密</p><p>c.根CA证书都是自签名,即用自己的公钥和私钥完成了签名的制作和验证</p><p>即:指纹和指纹算法通过数字签名算法加密生成数字签名</p><p>故:因为是使用了特定的数字签名的算法加密,所以客户端可以使用颁发机构的’公钥’进行解密,进而得到指纹和指纹算法。因为得到了指纹算法,所以客户端可以再次计算生成一个指纹,与之前的指纹进行对比,查看证书是否完整,有无被篡改。</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><ul><li><p>百度为例,关于算法部分讲的特别详细 <a href="http://blog.jobbole.com/86660/" target="_blank" rel="noopener">点我</a></p></li><li><p>对话形式,简单生动易理解 <a href="http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html" target="_blank" rel="noopener">点我</a></p></li><li><p>原理剖析比较深 <a href="https://segmentfault.com/a/1190000002554673" target="_blank" rel="noopener">点我</a></p></li><li><p>阮一峰大师镇楼:<a href="http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html" target="_blank" rel="noopener">点我</a></p></li></ul><p>以上,转载请注明出处!</p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/https.jpg" alt="HTTPS协议"></p>
<p>HTTP使用普通的非加密TCP作为其传输机制</p>
<p>因此,处在网络适当位置的攻击者能够截取这个机制。</p>
<p>而HTTPS机制课保护网络传送的所有数据的隐秘性和完整性。</p>
</summary>
<category term="Network" scheme="http://nundy.cn/tags/Network/"/>
</entry>
<entry>
<title>HTTP 缓存详解</title>
<link href="http://nundy.cn/2017/08/02/http-cache/"/>
<id>http://nundy.cn/2017/08/02/http-cache/</id>
<published>2017-08-01T20:33:31.000Z</published>
<updated>2018-07-15T03:43:19.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://ozgbjelmj.bkt.clouddn.com/cache-title.jpg" alt="cache-title"></p><p>在前端开发中,性能一直都是被大家所重视的一点,然而判断一个网站的性能最直观的就是看网页打开的速度。</p><p>其中提高网页反应速度的一个方式就是使用缓存。</p><p>一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。</p><a id="more"></a><h2 id="缓存分类"><a href="#缓存分类" class="headerlink" title="缓存分类"></a>缓存分类</h2><p>web缓存分为很多种:数据库缓存、代理服务器缓存、CDN缓存,以及浏览器缓存。</p><p>其中浏览器缓存也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage等</p><p>本文主要讨论浏览器缓存中的 HTTP 缓存 (HTTP缓存属于协议层,而 H5 新增的 localstorage 和数据库缓存属于应用层缓存)</p><h2 id="HTTP-缓存概述"><a href="#HTTP-缓存概述" class="headerlink" title="HTTP 缓存概述"></a>HTTP 缓存概述</h2><p>HTTP 缓存由header参数控制。</p><p>主要可以分为 强制缓存 和 协商缓存</p><p>强制缓存: Expires 和 Cache-Control</p><p>协商缓存: Last-modefied 和 ETag</p><h2 id="HTTP-缓存详解"><a href="#HTTP-缓存详解" class="headerlink" title="HTTP 缓存详解"></a>HTTP 缓存详解</h2><h3 id="一、Expires(HTTP-1-0)"><a href="#一、Expires(HTTP-1-0)" class="headerlink" title="一、Expires(HTTP 1.0)"></a>一、Expires(HTTP 1.0)</h3><p>缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点(绝对时间)</p><p>也就是说,Expires=max-age + 请求时间(当时服务器的时间)</p><p>缺点:由于是绝对时间,用户可能会将客户端本地的时间进行修改,而导致浏览器判断缓存失效,重新请求该资源,同时,还导致客户端与服务端的时间不一致,致使缓存失效。</p><h3 id="二、Cache-Control(HTTP-1-1)"><a href="#二、Cache-Control(HTTP-1-1)" class="headerlink" title="二、Cache-Control(HTTP 1.1)"></a>二、Cache-Control(HTTP 1.1)</h3><p>1、max-age(单位为s)指定设置缓存最大的有效时间,定义的是时间长短。</p><p>当浏览器向服务器发送请求后,在max-age这段时间里浏览器就不会再向服务器发送请求了(相对时间)</p><p>2、s-maxage(单位为s)同max-age,只用于共享缓存(比如CDN缓存)。</p><p>比如,当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。也就是说max-age用于普通缓存,而s-maxage用于代理缓存。如果存在s-maxage,则会覆盖掉max-age和Expires。</p><p>3、public 指定响应会被缓存(发送请求的客户端、代理服务器等等),并且在多用户间共享。</p><p>4、private 响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。</p><p>5、no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存</p><p>因此有的时候只设置no-cache防止缓存还是不够保险,还可以加上private指令,将过期时间设为过去的时间。</p><p>6、no-store 绝对禁止缓存,一看就知道如果用了这个命令当然就是不会进行缓存啦~每次请求资源都要从服务器重新获取。</p><p>7、must-revalidate指定如果页面是过期的,则去服务器进行获取。这个指令并不常用,就不做过多的讨论了。</p><h3 id="三、Last-modified-If-Modified-Since"><a href="#三、Last-modified-If-Modified-Since" class="headerlink" title="三、Last-modified + If-Modified-Since"></a>三、Last-modified + If-Modified-Since</h3><p>当浏览器再次进行请求时,会向服务器传送If-Modified-Since报头,询问Last-Modified时间点之后资源是否被修改过。</p><p>如果没有修改,则返回码为304,使用缓存</p><p>如果修改过,则再次去服务器请求资源,返回码和首次请求相同为200,资源为服务器最新资源。</p><h3 id="四、ETag-If-None-Match"><a href="#四、ETag-If-None-Match" class="headerlink" title="四、ETag + If-None-Match"></a>四、ETag + If-None-Match</h3><p>根据实体内容生成一段hash字符串,标识资源的状态,由服务端产生</p><p>第一次响应时,服务器响应头设置 ETag属性,值为该hash字符串</p><p>第二次请求时,浏览器请求头设置 If-None-Match属性,值为该hash字符串</p><p>服务器端检查 ETag 值是否变化来返回 ‘304 使用缓存’ 或者 ‘200 和 新文件’</p><p>优点:使用ETag可以解决Last-modified存在的一些问题:</p><p>a、某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新</p><p>b、如果资源修改非常频繁,在秒以下的时间内进行修改,而Last-modified只能精确到秒</p><p>c、一些资源的最后修改时间改变了,但是内容没改变,使用ETag就认为资源还是没有修改的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>说了这么多的属性,那么整体来看,它的具体流程是这样的,如下图:</p><p><img src="http://ozgbjelmj.bkt.clouddn.com/cache.webp" alt="缓存流程"></p><p>还有关于 Cache-Control 的属性使用,如下图:</p><p><img src="http://ozgbjelmj.bkt.clouddn.com/cache-control.webp" alt="缓存流程"></p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://github.com/laizimo/zimo-article/issues/24?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io" target="_blank" rel="noopener">浏览器缓存篇</a></p><p><a href="https://zhuanlan.zhihu.com/p/29750583?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io" target="_blank" rel="noopener">HTTP 缓存机制一二三</a></p><p><a href="https://github.com/zuopf769/notebook/blob/master/fe/%E5%89%8D%E7%AB%AF%E5%BF%85%E9%A1%BB%E8%A6%81%E6%87%82%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/README.md?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io" target="_blank" rel="noopener">前端需要懂得缓存机制</a></p><p><a href="https://www.zhihu.com/question/20790576" target="_blank" rel="noopener">大公司里怎样开发和部署前端代码</a></p>]]></content>
<summary type="html">
<p><img src="http://ozgbjelmj.bkt.clouddn.com/cache-title.jpg" alt="cache-title"></p>
<p>在前端开发中,性能一直都是被大家所重视的一点,然而判断一个网站的性能最直观的就是看网页打开的速度。</p>
<p>其中提高网页反应速度的一个方式就是使用缓存。</p>
<p>一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。</p>
</summary>
<category term="Network" scheme="http://nundy.cn/tags/Network/"/>
</entry>
</feed>