-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
592 lines (533 loc) · 125 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Ontides's Blog</title>
<subtitle>Share my growth and thoughts</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://ontides.github.io/"/>
<updated>2017-02-19T05:34:12.000Z</updated>
<id>https://ontides.github.io/</id>
<author>
<name>Ontides</name>
<email>[email protected]</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Web全栈之路——《Web全栈工程师的自我修养》读书笔记</title>
<link href="https://ontides.github.io/2017/01/25/Web%E5%85%A8%E6%A0%88%E4%B9%8B%E8%B7%AF%E2%80%94%E2%80%94%E3%80%8AWeb%E5%85%A8%E6%A0%88%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%9A%84%E8%87%AA%E6%88%91%E4%BF%AE%E5%85%BB%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<id>https://ontides.github.io/2017/01/25/Web全栈之路——《Web全栈工程师的自我修养》读书笔记/</id>
<published>2017-01-25T05:25:32.000Z</published>
<updated>2017-02-19T05:34:12.000Z</updated>
<content type="html"><![CDATA[<p>初次听到全栈工程师这个称谓的时候,我就被这个「霸气」的名字所吸引,全栈在我心里一直是技术厉害的大触的专属标签。虽然经常听到这个词,但是对这个词本身的理解却不甚清晰,甚至一度把全栈工程师简单地以为是全“端”工程师,以为同时会前端和后端就是全栈了,后来才知道,这种认识是多么粗浅。</p>
<p>无意间看到了《Web全栈工程师的自我修养》这本书,简单看了看目录就决定要买下来了,怀着对全栈的向往,我还是十分期待看这本书的。</p>
<h2 id="全书小述"><a href="#全书小述" class="headerlink" title="全书小述"></a>全书小述</h2><p>首先贴出全书的脉络图,我根据书本身的章节分成了16部分,尽可能概括地展现书中对我印象较为深刻的内容,并讲一些我觉得比较重要的地方详细地描述,而一些枝节便删了去。</p>
<p><img src="https://ww4.sinaimg.cn/large/006tKfTcgy1fcvpvw52aqj31k017a12v.jpg" alt="Web全栈工程师的自我修养"></p>
<p>感觉起来这本书更像是作者的随笔集,内容广而不深,读起来也就比较轻松。整本书的内容通俗易懂,其中还专门设置了一章“从学生到工程师”,可以看出全书更多的还是面向在校生和初学者。</p>
<p>正如书名上所用的词“自我修养”,这本书详细地描述了作为Web全栈工程师所应具备的“修养”,不仅仅包括编码上的能力,也包括了职场处事,思维方式,和生活方式上的“修养”,对拓展视野和思维方式有所裨益。</p>
<h2 id="知识小记"><a href="#知识小记" class="headerlink" title="知识小记"></a>知识小记</h2><p>看完全书,我感觉内容多而杂,好在内容平易近人,还是有不少有所收获的点,这里对一些点做下总结。</p>
<h3 id="何为全栈"><a href="#何为全栈" class="headerlink" title="何为全栈"></a>何为全栈</h3><p>「全栈」表示未完成一个项目,所需要的一系列技术的集合</p>
<p>「全栈工程师」是一专多长,能够熟练运用多种技术栈独立完成一个产品的人。而那些拥有众多技术栈,而每种技术栈只是浅尝辄止,略知一二的人则被作者称为「野生程序员」</p>
<h3 id="如何成为全栈工程师"><a href="#如何成为全栈工程师" class="headerlink" title="如何成为全栈工程师"></a>如何成为全栈工程师</h3><ol>
<li><p>先精后广,一专多长</p>
<p>学习全栈技能的时候应当在特定的方向上有比较深入的钻研,然后再将学习目标逐渐推广开来。</p>
</li>
<li><p>围绕商业目标</p>
<p>老板雇佣一个程序员,不是因为他能写程序,而是因为他能帮助自己赚钱。应该解决问题,而不是醉心技术,如果痴迷于技术本身,反而看不到问题的所在。</p>
</li>
<li><p>关注用户体验</p>
<p>每一个糟糕的体验背后都蕴含着商机,应当从用户体验的角度考虑问题,要做自己会用的产品</p>
</li>
</ol>
<h3 id="Web全栈工程师所应具备的技术"><a href="#Web全栈工程师所应具备的技术" class="headerlink" title="Web全栈工程师所应具备的技术"></a>Web全栈工程师所应具备的技术</h3><ol>
<li><p>HTTP</p>
<ul>
<li><p>前端视角</p>
<p>目的:让网站又好又快地展现在用户的面前</p>
<p>关注点:</p>
<ul>
<li>尽量减少同一域下的HTTP请求数</li>
<li>尽量减少每一个资源的体积</li>
</ul>
</li>
<li><p>后端视角</p>
<p>目的:让服务器尽快响应请求以及减少请求对服务器的开销</p>
<p>关注点:</p>
<ul>
<li>提高服务器的请求处理能力</li>
<li>预防DDoS攻击</li>
</ul>
</li>
<li><p>前后端交集——BigPipe</p>
</li>
</ul>
</li>
<li><p>缓存</p>
<ul>
<li>服务器缓存<ul>
<li>基本的数据库查询缓存</li>
<li>扩展数据库缓存:memcached</li>
<li>文件缓存</li>
<li>静态化</li>
</ul>
</li>
<li>浏览器缓存<ul>
<li>Expires</li>
<li>Last-Modified</li>
<li>Cache-Control</li>
</ul>
</li>
</ul>
</li>
<li><p>持续集成</p>
<ul>
<li><p>版本控制</p>
<p>方式有Git和SVN</p>
<p>最佳实践:</p>
<ul>
<li>鼓励频繁地提交</li>
<li>确定分支流程</li>
<li>定义于主干原则并坚守它</li>
<li>不要把逻辑的修改和代码格式化放在一起</li>
<li>不相干的代码分开提交</li>
<li>保持工作代码库的“干净”</li>
</ul>
</li>
<li><p>包管理</p>
<p>工具有npm,bower,Composer,gem,CocoaPods等</p>
</li>
<li><p>构建工具</p>
<p>在良好架构基础上使用构建工具来整合代码</p>
<p>工具有Make,Grunt和Gulp等</p>
<p>良好的架构:</p>
<ul>
<li>有合适的分离粒度</li>
<li>最小知识原则</li>
<li>DRY</li>
<li>最小化预先设计</li>
<li>通过良好的分级,让文件易于找到</li>
<li>有一致且可执行的命名规则</li>
</ul>
</li>
</ul>
</li>
<li><p>软件设计方法</p>
<ul>
<li><p>设计模式</p>
<p>关注点:</p>
<ul>
<li>高效编写代码</li>
<li>高可复用性</li>
<li>抽象带来的可读性</li>
</ul>
<p>分类:</p>
<ul>
<li>创建型模式</li>
<li>结构型模式</li>
<li>行为型模式</li>
</ul>
</li>
<li><p>架构模式</p>
<p>MVC模式</p>
</li>
<li><p>设计原则</p>
<ul>
<li>DRY</li>
<li>惯例优于设置</li>
<li>KISS原则</li>
<li>最少知道原则</li>
</ul>
</li>
</ul>
</li>
<li><p>大前端知识体系</p>
<p>初级:</p>
<ul>
<li>了解浏览器兼容性</li>
<li>理解HTML/CSS/JavaScript语法和原理</li>
<li>熟悉编辑器和插件</li>
<li>了解调试工具</li>
<li>熟悉版本管理软件</li>
<li>熟练使用前端库/框架</li>
<li>了解相关标准和规范</li>
</ul>
<p>中级:</p>
<ul>
<li>对代码质量,代码规范有了解</li>
<li>熟悉JavaScript单元测试</li>
<li>应用和理解性能优化</li>
<li>应用和理解SEO</li>
<li>代码部署能力</li>
<li>移动Web相关知识</li>
</ul>
<p>高级:</p>
<ul>
<li>代码架构</li>
<li>安全</li>
<li>对自动化测试的理解</li>
</ul>
</li>
</ol>
<h3 id="全栈工程师生活方式和思维方式"><a href="#全栈工程师生活方式和思维方式" class="headerlink" title="全栈工程师生活方式和思维方式"></a>全栈工程师生活方式和思维方式</h3><ol>
<li><p>积累作品集</p>
<p>用处:向其他人展现自己的能力</p>
<p>方式:Github和静态页面</p>
</li>
<li><p>高效</p>
<ul>
<li><p>阅读英文资料</p>
</li>
<li><p>时间管理四象限</p>
<p><img src="https://ww3.sinaimg.cn/large/006tNbRwjw1fbadecoyv2j309i06gglz.jpg" alt="002vgR8Mty6DXeznd0S62&690"></p>
</li>
<li><p>消除重复工作</p>
</li>
<li><p>给自己留出不被打扰的时间</p>
</li>
<li><p>番茄工作法</p>
</li>
<li><p>跨界思考</p>
</li>
<li><p>纸上头脑风暴</p>
</li>
<li><p>使用版本控制工具盒构建系统</p>
</li>
</ul>
</li>
<li><p>全栈思维</p>
<ul>
<li>想着把自己的产品和自己的名字联系起来</li>
<li>学一点管理<ul>
<li>好的管理者能让平凡的员工做不平凡的事</li>
<li>根据员工特质来授权</li>
</ul>
</li>
<li>培养沟通能力</li>
</ul>
</li>
</ol>
]]></content>
<summary type="html">
初次听到全栈工程师这个称谓的时候,我就被这个「霸气」的名字所吸引,全栈在我心里一直是技术厉害的大触的专属标签。虽然经常听到这个词,但是对这个词本身的理解却不甚清晰,甚至一度把全栈工程师简单地以为是全“端”工程师,以为同时会前端和后端就是全栈了,后来才知道,这种认识是多么粗浅。
</summary>
<category term="Web" scheme="https://ontides.github.io/tags/Web/"/>
<category term="读书笔记" scheme="https://ontides.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>[译]《Understanding ECMAScript 6》Chapter 1:Block Bindings</title>
<link href="https://ontides.github.io/2016/11/17/%E8%AF%91-%E3%80%8AUnderstanding-ECMAScript-6%E3%80%8BChapter-1%EF%BC%9ABlock-Bindings/"/>
<id>https://ontides.github.io/2016/11/17/译-《Understanding-ECMAScript-6》Chapter-1:Block-Bindings/</id>
<published>2016-11-17T04:32:43.000Z</published>
<updated>2017-02-16T05:43:12.000Z</updated>
<content type="html"><![CDATA[<h1 id="《理解-ECMAScript-6》第一章:块绑定"><a href="#《理解-ECMAScript-6》第一章:块绑定" class="headerlink" title="《理解 ECMAScript 6》第一章:块绑定"></a>《理解 ECMAScript 6》第一章:块绑定</h1><hr>
<p>以前,变量声明方式是JavaScript编程中一个令人困惑的部分。在大部分类C语言里,变量(或者说作用域)在声明的地方生成。但是在JavaScript中,情况就不是这样了。变量实际上创建的地方取决于你声明它的方式,而ECMAScript 6提供了一些可选的方式来让你更容易地控制作用域。本章将会告诉你为什么经典的<code>var</code>声明会令人感到困惑,并且会介绍ECMAScript 6中的块级作用域,然后会给出如何使用它们的最佳实践。</p>
<h2 id="1-1-变量声明和提升"><a href="#1-1-变量声明和提升" class="headerlink" title="1.1 变量声明和提升"></a>1.1 变量声明和提升</h2><p>使用<code>var</code>的变量声明,会被当做处于当前所在函数的顶部(如果在函数外声明的话则是全局作用域)中的声明那样处理———无论它实际的声明位置在哪,这被称作<em>提升</em>。为了展示提升做了什么,看看下面的函数定义:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">getValue</span>(<span class="params">condition</span>) </span>{</div><div class="line"> <span class="keyword">if</span> (condition) {</div><div class="line"> <span class="keyword">var</span> value = <span class="string">"blue"</span>;</div><div class="line"> <span class="comment">// 其他代码</span></div><div class="line"> <span class="keyword">return</span> value;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 这里value的值是undefined</span></div><div class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 这里value的值是undefined</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果你对JavaScript不熟悉,你可能会觉得变量<code>value</code>只有当<code>condition</code>的值为真的时候才会被创建。实际上,变量<code>value</code>无论如何都会被创建。在背地里,JavaScript引擎会将<code>getValue</code>函数变换成类似这种形式:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">getValue</span>(<span class="params">condition</span>) </span>{</div><div class="line"> <span class="keyword">var</span> value;</div><div class="line"> <span class="keyword">if</span> (condition) {</div><div class="line"> value = <span class="string">"blue"</span>;</div><div class="line"> <span class="comment">// 其他代码</span></div><div class="line"> <span class="keyword">return</span> value;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>value</code>的声明被提升到了顶部,而值的初始化依然留在了原来的地方。这意味着变量<code>value</code>实际上在<code>else</code>语句中依然可以被访问到,而如果在那里被访问,变量只会拥有一个<code>undefined</code>的值,因为它没有被初始化。</p>
<p>通常需要一些时间来让新的JavaScript开发者习惯于声明提升。误解了JavaScript中这个独特的行为常常会造成一些bug,因此,ECMAScript 6提供了一个块级作用域的可选方式来更好地控制一个变量的生命周期。</p>
<h2 id="1-2-块级声明"><a href="#1-2-块级声明" class="headerlink" title="1.2 块级声明"></a>1.2 块级声明</h2><p>块级声明是那些变量在给定块作用域外不能访问的声明方式。块级作用域,也叫词法作用域,被创建于:</p>
<ol>
<li>一个函数内部</li>
<li>一个块内部 (通过 <code>{</code> 和 <code>}</code> 字符来标识)</li>
</ol>
<p>块级作用域是大量类C语言的工作方式,ECMAScript 6块级声明的引入是为了把同样的灵活性(和一致性)带入到JavaScript中。</p>
<h3 id="1-2-1-let声明"><a href="#1-2-1-let声明" class="headerlink" title="1.2.1 let声明"></a>1.2.1 let声明</h3><p><code>let</code>声明语法和<code>var</code>的语法相同。你可以简单地把<code>var</code>替换成<code>let</code>来声明一个变量,这样做的话就会把这个变量的作用域限定在当前代码块里(这里有一些其他的细微差别,之后将会讨论)。<br>因为<code>let</code>声明不会被提升到当前块的顶部,你可能总是想要把<code>let</code>声明放在块最开始的地方,这样的话她们就可以在整个块中能够访问了。这里有个例子:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">getValue</span>(<span class="params">condition</span>) </span>{</div><div class="line"> <span class="keyword">if</span> (condition) {</div><div class="line"> <span class="keyword">let</span> value = <span class="string">"blue"</span>;</div><div class="line"> <span class="comment">// 其他代码</span></div><div class="line"> <span class="keyword">return</span> value;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 这里不存在变量value</span></div><div class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 这里不存在变量value</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>这个版本的<code>getValue</code>函数的行为更接近于你在其他类C语言中所期望的样子。由于变量<code>value</code>用<code>let</code>来替代<code>var</code>进行声明,声明并没有被提升到函数定义的头部,并且一旦执行流到了<code>if</code>块的外面,变量<code>value</code>便无法访问到。如果<code>condition</code>的值为<code>false</code>的话,变量<code>value</code>就永远不会被声明或者初始化。</p>
<h3 id="1-2-2-没有重复声明"><a href="#1-2-2-没有重复声明" class="headerlink" title="1.2.2 没有重复声明"></a>1.2.2 没有重复声明</h3><p>如果一个标识符已经在一个作用域中定义,再在那个作用域中使用<code>let</code>声明同一个标识符就会导致抛出错误。比如说:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> count = <span class="number">30</span>;</div><div class="line"><span class="comment">// 错误的语法</span></div><div class="line"><span class="keyword">let</span> count = <span class="number">40</span>;</div></pre></td></tr></table></figure>
<p>在这个例子中,<code>count</code>被声明了两次:一次使用<code>var</code>,一次使用<code>let</code>。因为<code>let</code>永远不会重复定义一个已经存在在相同作用域的标识符,所以<code>let</code>声明会抛出一个错误。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> count = <span class="number">30</span>;</div><div class="line"><span class="comment">// 没有抛出错误</span></div><div class="line"><span class="keyword">if</span> (condition) {</div><div class="line"> <span class="keyword">let</span> count = <span class="number">40</span>;</div><div class="line"> <span class="comment">// 更多的代码</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里<code>let</code>声明不会抛出错误,因为它在<code>if</code>语句里创建了一个叫做<code>count</code>的新变量,而不是在被包含块中创建的。在<code>if</code>块里面,这个新的变量隐藏了全局的<code>count</code>变量,阻止了在块内对它(全局变量)的访问。</p>
<h3 id="1-2-3-常量声明"><a href="#1-2-3-常量声明" class="headerlink" title="1.2.3 常量声明"></a>1.2.3 常量声明</h3><p>在ECMAScript 6里你也可以使用<code>const</code>声明语法来定义变量。使用<code>const</code>来声明的变量会被认为是一个<em>常量</em>,意味着一旦被设置了,他们的值无法再改变。因此,每一个<code>const</code>变量必须要在声明 的时候初始化,正如下面例子那样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 合法的常量</span></div><div class="line"><span class="keyword">const</span> maxItems = <span class="number">30</span>;</div><div class="line"><span class="comment">// 语法错误:未初始化</span></div><div class="line"><span class="keyword">const</span> name;</div></pre></td></tr></table></figure>
<p>变量<code>maxItems</code>被初始化了,因此它的<code>const</code>声明能够正常运行。不过变量<code>name</code>则会在运行中抛出语法错误,因为<code>name</code>未被初始化。</p>
<h4 id="const声明和let声明"><a href="#const声明和let声明" class="headerlink" title="const声明和let声明"></a>const声明和let声明</h4><p>常数声明(即用<code>const</code>声明的变量)就像<code>let</code>声明一样,是一种块级的声明。这意味着一旦执行流到了块的外面,这些常数便无法再被访问,并且声明也不会被提升,就像下面这个例子中所展现的一样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (condition) {</div><div class="line"> <span class="keyword">const</span> maxItems = <span class="number">5</span>;</div><div class="line"> <span class="comment">// 更多的代码</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// maxItems 无法在这里被访问到</span></div></pre></td></tr></table></figure>
<p>在这段代码里,常量<code>maxItems</code>在<code>if</code>语句中声明,一旦语句执行完毕,<code>maxItems</code>在块外便无法访问。</p>
<p>另外一个和<code>let</code>类似的点是,如果使用<code>const</code>定义一个已经在当前作用域定义的变量就会致使系统抛出错误——无论那个变量是使用<code>var</code>(在全局或者函数作用域中)定义还是使用<code>let</code>(在块作用域中)定义。比如说,考虑下面的代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> message = <span class="string">"Hello!"</span>;</div><div class="line"><span class="keyword">let</span> age = <span class="number">25</span>;</div><div class="line"><span class="comment">// 下面的两条语句都会抛出错误</span></div><div class="line"><span class="keyword">const</span> message = <span class="string">"Goodbye!"</span>;</div><div class="line"><span class="keyword">const</span> age = <span class="number">30</span>;</div></pre></td></tr></table></figure>
<p>这两条<code>const</code>声明单独存在的话都是合法的,不过在前面已经使用<code>var</code>和<code>let</code>声明的情况下,它们都不会如你所期望那样执行。</p>
<p>尽管在<code>let</code>和<code>const</code>之间有这么多的相似点,他们之间还是有一个很大的不同。无论在严格模式下还是非严格模式下,尝试去分配<code>const</code>到一个已定义的常量会抛出错误:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> maxItems = <span class="number">5</span>;</div><div class="line">maxItems = <span class="number">6</span>; <span class="comment">// 抛出错误</span></div></pre></td></tr></table></figure>
<p>类似其他语言中的常数,变量<code>maxItems</code>不能再被分配一个新的值。不过,有一点不像其他语言中的常量,如果这个常量是一个对象的话,它是可以被改变的。</p>
<h4 id="使用const声明对象"><a href="#使用const声明对象" class="headerlink" title="使用const声明对象"></a>使用const声明对象</h4><p><code>const</code>声明阻止了绑定的改变,而不是值本身的改变。这意味着使用<code>const</code>声明的对象不会阻止这些对象的改变。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> person = {</div><div class="line"> <span class="attr">name</span>: <span class="string">"Nicholas"</span></div><div class="line">};</div><div class="line"><span class="comment">// 正常运行</span></div><div class="line">person.name = <span class="string">"Greg"</span>;</div><div class="line"><span class="comment">// 抛出错误</span></div><div class="line">person = {</div><div class="line"> <span class="attr">name</span>: <span class="string">"Greg"</span></div><div class="line">};</div></pre></td></tr></table></figure>
<p>这里,<code>person</code>由一个带有一个属性的对象初始值所创建。改变<code>person.name</code>的值并不会导致错误,因为这只改变了<code>person</code>包含的东西,而没有改变<code>person</code>本身的绑定。当代码尝试去给<code>person</code>分配一个新值的时候(因此也就尝试去改变绑定本身),系统就会抛出一个错误。<code>const</code>配合对象的工作方式中这个细微的点很容易遭到误解。只需记住,<code>const</code>只阻止了绑定本身的改变,而没有阻止已绑定的值的改变。</p>
<h3 id="1-2-4-暂时性死区"><a href="#1-2-4-暂时性死区" class="headerlink" title="1.2.4 暂时性死区"></a>1.2.4 暂时性死区</h3><p>一个使用<code>let</code>或<code>const</code>声明的变量只有在声明之后才能被访问到。如果尝试在声明之前访问的话就会导致一个引用错误(reference error)——即使使用一般来说较为安全的操作符如下面例子中的<code>typeof</code>操作符:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (condition) {</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">typeof</span> value); <span class="comment">// ReferenceError!</span></div><div class="line"> <span class="keyword">let</span> value = <span class="string">"blue"</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这个例子中,变量<code>value</code>使用<code>let</code>来定义和初始化,但是这条语句永远不会被执行,因为前一行抛出了一个错误。它的问题就是<code>value</code>存在于这个被JavaScript社区称作<em>暂时性死区</em>(temporal dead zone, TDZ)的地方。TDZ从未在ECMAScript标准中明确命名,不过它常常用来解释用<code>let</code>和<code>const</code>声明的变量在它们的声明之前无法被访问的现象。这块内容包含了由于TDZ的存在而造成的关于声明位置的一些细节,这里的例子中都是以<code>let</code>作为示范,不过这些也同样适用于<code>const</code>。</p>
<p>当JavaScript引擎执行到一个块中,检测到一个变量声明时,它可能把这个声明提升到函数的顶部,或者全局作用域中(对<code>var</code>),也可能把声明放到TDZ中(对<code>let</code>和<code>const</code>)。任何想要尝试访问一个处于TDZ中的变量的行为都会导致运行错误。只有当执行流到了变量声明的地方的时候,这个变量才会从TDZ中移除,这样才能安全地使用。</p>
<p>当你尝试去使用一个用<code>let</code>或者<code>const</code>声明的变量的时候,在声明前是不能够访问的。正如前一个例子中所展示的那样,这条规则甚至对与通常上来说安全的<code>typeof</code>操作符也是适用的。不过,你可以在变量声明的块的外面使用<code>typeof</code>——虽然这么做不会得到你想要的结果。看看下面的代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> value); <span class="comment">// "undefined"</span></div><div class="line"><span class="keyword">if</span> (condition) {</div><div class="line"> <span class="keyword">let</span> value = <span class="string">"blue"</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>当<code>typeof</code>操作符执行的时候变量<code>value</code>不在TDZ中,因为它存在于<code>value</code>声明的块的外面。这意味着这里没有<code>value</code>的任何绑定,<code>typeof</code>也就简单地返回<code>"undefined"</code></p>
<p>TDZ只是块绑定中一个特殊的方面。另外一个特殊的方面就是它们在循环中的使用方式。</p>
<h2 id="1-3-循环中的块绑定"><a href="#1-3-循环中的块绑定" class="headerlink" title="1.3 循环中的块绑定"></a>1.3 循环中的块绑定</h2><p>或许开发者们最想要变量块级作用域的地方就是在<code>for</code>循环中了,这意味着一次性使用的计数变量只能在循环中使用。举个例子,在JavaScript中这样的代码十分常见:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</div><div class="line"> process(items[i]);</div><div class="line">}</div><div class="line"><span class="comment">// i在这里依然可以被访问到</span></div><div class="line"><span class="built_in">console</span>.log(i); <span class="comment">// 10</span></div></pre></td></tr></table></figure>
<p>在其他语言中,块级作用域默认存在,这个例子会如所期望那样执行,即变量i只有在循环中才能被访问到。然而在JavaScript中,变量i在循环结束后依然可以被访问到,因为<code>var</code>声明被提升了。如果使用<code>let</code>来替代它,如下面代码中那样,就能得到预想那样的行为。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</div><div class="line"> process(items[i]);</div><div class="line">}</div><div class="line"><span class="comment">// i在这里不能被访问 - 抛出了一个错误</span></div><div class="line"><span class="built_in">console</span>.log(i);</div></pre></td></tr></table></figure>
<p>在这个例子中,变量i只是存在于<code>for</code>循环内部。一旦循环结束,这个变量再也不能在其他位置访问到了。</p>
<h3 id="1-3-1-循环中的函数"><a href="#1-3-1-循环中的函数" class="headerlink" title="1.3.1 循环中的函数"></a>1.3.1 循环中的函数</h3><p><code>var</code>的特性使得在循环中创建函数变得十分困难,因为循环中的变量在循环的范围的外面依然可以被访问到。看看下面的代码:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> funcs = [];</div><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</div><div class="line"> funcs.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="built_in">console</span>.log(i); });</div><div class="line">}</div><div class="line">funcs.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">func</span>) </span>{</div><div class="line"> func(); <span class="comment">// 输出了数字“10”十次</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p>你或许会认为这段代码会打印出数字0到9,但是实际上它在一行中输出了数字10十次。这是因为<code>i</code>在循环的每次迭代中被共用,这意味着在循环中创建的所有函数都拥有对同一个变量的引用。变量<code>i</code>在循环结束的时候的值为<code>10</code>,因此当<code>console.log(i)</code>被调用的时候,每次都会打印同一个值。</p>
<p>为了解决这个问题,开发者们在循环中使用了立即处理函数表达式(IIFEs)来在每次迭代的时候强制创建变量的一个新的副本,正如下面例子那样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> funcs = [];</div><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</div><div class="line"> funcs.push((<span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>{</div><div class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(value);</div><div class="line"> }</div><div class="line"> }(i)));</div><div class="line">}</div><div class="line">funcs.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">func</span>) </span>{</div><div class="line"> func(); <span class="comment">// 输出 0, 然后 1, 然后 2, 直到 9</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p>这个版本在循环中使用了IIFE。变量<code>i</code>传到IIFE里,在IIFE中创建了它值的副本并且作为<code>value</code>保存下来。这是在当次迭代中的函数所使用的值,因此在循环从0增加到9的时候,调用每个函数都会返回所期望的值。幸运的是,ECMAScript 6中使用<code>let</code>和<code>const</code>的块级绑定可以为你简化这种循环。</p>
<h3 id="1-3-2-循环中的let声明"><a href="#1-3-2-循环中的let声明" class="headerlink" title="1.3.2 循环中的let声明"></a>1.3.2 循环中的let声明</h3><p><code>let</code>声明通过有效地模仿前面例子中IIFE的行为来简化了循环。在每次的迭代中,循环会创建一个新的变量并且用和上一个迭代中同样的变量值和变量名来初始化它。这意味着你可以抛弃IIFE并得到你所期望的结果,就像这样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> funcs = [];</div><div class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</div><div class="line"> funcs.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(i);</div><div class="line"> });</div><div class="line">}</div><div class="line"></div><div class="line">funcs.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">func</span>) </span>{</div><div class="line"> func(); <span class="comment">// 输出 0, 然后 1, 然后 2, 直到 9</span></div><div class="line">})</div></pre></td></tr></table></figure>
<p>这个循环工作起来像极了使用了<code>var</code>和IIFE的循环,不过,看得出来,这更加简洁。在每次循环中<code>let</code>声明创建了一个新的<code>i</code>变量,这使得在循环中创建的每一个函数都获得了它自己对于<code>i</code>的副本。每一个<code>i</code>的副本都拥有在每个循环迭代的开始它被创建的地方所分配的值。这在<code>for-in</code>和<code>for-of</code>循环中都是适用的,就像这里所展示的那样:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> funcs = [],</div><div class="line"> object = {</div><div class="line"> <span class="attr">a</span>: <span class="literal">true</span>,</div><div class="line"> <span class="attr">b</span>: <span class="literal">true</span>,</div><div class="line"> <span class="attr">c</span>: <span class="literal">true</span></div><div class="line"> };</div><div class="line"><span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> object) {</div><div class="line"> funcs.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(key);</div><div class="line"> });</div><div class="line">}</div><div class="line">funcs.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">func</span>) </span>{</div><div class="line"> func(); <span class="comment">// 输出了 "a", 接着 "b", 接着 "c"</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p>在这个例子中,<code>for-in</code>循环表现出了和<code>for</code>循环中相同的行为。每次循环的时候,会创建一个新的<code>key</code>绑定,并且每个函数都拥有它自己的<code>key</code>变量的副本。如果使用<code>var</code>来声明<code>key</code>,所有的函数都会输出<code>"c"</code>。</p>
<p><code>let</code>声明在循环中的行为是规定中一个特殊的行为而不是和<code>let</code>不被提升的特性有关,理解这点很重要。实际上,早期对<code>let</code>的实现并没有包含这个行为,直到后来才被添加。</p>
<h3 id="1-3-3-循环中的const声明"><a href="#1-3-3-循环中的const声明" class="headerlink" title="1.3.3 循环中的const声明"></a>1.3.3 循环中的const声明</h3><p>ECMAScript 6 的说明中并没有明确不允许在循环中使用<code>const</code>声明。不过,基于这个类型的行为将会和你在循环中使用的有所不同。对于一个普通的<code>for</code>循环,你可以使用<code>const</code>来初始化,不过如果你尝试去改变它的值的话,循环会抛出一个警告。比如说:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> funcs = [];</div><div class="line"><span class="comment">// 在一次迭代后抛出错误</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">const</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</div><div class="line"> funcs.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(i);</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在这段代码中,变量<code>i</code>作为一个常量被声明。循环的第一次迭代中,<code>i</code>的值为0,执行成功。当<code>i++</code>执行的时候,就抛出了一个错误,因为尝试去修改一个常量的值。因此,你只能在循环的初始化中使用<code>const</code>声明一个不会修改的变量。</p>
<p>另一方面,当在<code>for-in</code>和<code>for-of</code>循环中使用的时候,一个<code>const</code>变量的表现和<code>let</code>变量一致。因此下面的例子不会造成错误:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> funcs = [],</div><div class="line"> object = {</div><div class="line"> <span class="attr">a</span>: <span class="literal">true</span>,</div><div class="line"> <span class="attr">b</span>: <span class="literal">true</span>,</div><div class="line"> <span class="attr">c</span>: <span class="literal">true</span></div><div class="line"> };</div><div class="line"><span class="comment">// 不会造成错误!</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> object) {</div><div class="line"> funcs.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(key);</div><div class="line"> });</div><div class="line">}</div><div class="line">funcs.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">func</span>) </span>{</div><div class="line"> func(); <span class="comment">// 输出 "a", 接着 "b", 接着 "c"</span></div><div class="line">});</div></pre></td></tr></table></figure>
<p>这段代码几乎和“循环中的let声明”节中第二个例子的表现一致。唯一的不同就是<code>key</code>的值在循环中不能改变。<code>for-in</code>与<code>for-of</code>和<code>const</code>之所以能够良好地工作是因为循环初始化器在每次迭代的时候创建一个新的变量,而不是尝试修改已存在变量的值(和前面例子中使用<code>for</code>而不是<code>for-in</code>的情况一样)。</p>
<h2 id="1-4-全局块绑定"><a href="#1-4-全局块绑定" class="headerlink" title="1.4 全局块绑定"></a>1.4 全局块绑定</h2><p><code>let</code>和<code>const</code>另外一个不同于<code>var</code>的地方是他们在全局作用域的表现。当<code>var</code>在全局作用域中使用时,它会创建一个全局变量,也是全局对象上的属性(浏览器中为<code>window</code>对象)。这意味着你使用<code>var</code>可能偶然地覆写一个已经存在的全局变量,比如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 在浏览器中</span></div><div class="line"><span class="keyword">var</span> <span class="built_in">RegExp</span> = <span class="string">"Hello!"</span>;</div><div class="line"><span class="built_in">console</span>.log(<span class="built_in">window</span>.RegExp); <span class="comment">// "Hello!"</span></div><div class="line"><span class="keyword">var</span> ncz = <span class="string">"Hi!"</span>;</div><div class="line"><span class="built_in">console</span>.log(<span class="built_in">window</span>.ncz); <span class="comment">// "Hi!"</span></div></pre></td></tr></table></figure>
<p>即使全局对象<code>RegExp</code>在<code>window</code>中已经定义,它由于会被<code>var</code>声明给覆写而不安全。这个例子声明了一个新的全局变量<code>RegExp</code>,它覆写了初始的全局变量。类似的,<code>ncz</code>也被定义为一个全局变量,并且立即定义了一个<code>window</code>上的属性。这是JavaScript一贯工作的方式。</p>
<p>如果你替代性地在全局作用域中使用<code>let</code>或者<code>const</code>,在全局作用域中会创建一个新的变量绑定,但是不会给全局对象上添加属性。这也意味着你不能通过<code>let</code>或者<code>const</code>来覆写一个全局变量——你只能隐藏它。这里有个例子:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 在浏览器中</span></div><div class="line"><span class="keyword">let</span> <span class="built_in">RegExp</span> = <span class="string">"Hello!"</span>;</div><div class="line"><span class="built_in">console</span>.log(<span class="built_in">RegExp</span>); <span class="comment">// "Hello!"</span></div><div class="line"><span class="built_in">console</span>.log(<span class="built_in">window</span>.RegExp === <span class="built_in">RegExp</span>); <span class="comment">// false</span></div><div class="line"><span class="keyword">const</span> ncz = <span class="string">"Hi!"</span>;</div><div class="line"><span class="built_in">console</span>.log(ncz); <span class="comment">// "Hi!"</span></div><div class="line"><span class="built_in">console</span>.log(<span class="string">"ncz"</span> <span class="keyword">in</span> <span class="built_in">window</span>); <span class="comment">// false</span></div></pre></td></tr></table></figure>
<p>这里,对<code>RegExp</code>的<code>let</code>声明创建了一个变量绑定,它隐藏了全局的<code>RegExp</code>。这意味着<code>window.RegExp</code>和<code>RegExp</code>不相等,并且这对全局作用域不会造成任何问题。同样的,对<code>ncz</code>的<code>const</code>声明创建了一个变量绑定,也同样不会创建一个全局对象上的属性。这个特性使得在全局作用域中使用<code>let</code>和<code>const</code>更加安全——如果你不想给全局对象添加属性的话。</p>
<p>如果你想要一段代码在全局对象上可访问,你可能依然想要在全局作用域中使用<code>var</code>。如果你想要跨框架或窗口访问代码的话,这种情况就挺常见了。</p>
<h2 id="1-5-块绑定的最佳实践"><a href="#1-5-块绑定的最佳实践" class="headerlink" title="1.5 块绑定的最佳实践"></a>1.5 块绑定的最佳实践</h2><p>在ECMAScript发展的过程中,有一个广为传播的思想,就是你应该用<code>let</code>来取代<code>var</code>作为默认的变量声明方式。对很多JavaScript开发者来说,<code>let</code>的表现正如他们期望<code>var</code>所应该的那样,因此这种直接的替换符合逻辑上的感觉。这时,你需要对那些对修改保护有需求的变量使用<code>const</code>。</p>
<p>不过,随着更多的开发者转向ECMAScript 6,一个可选的方式受到了欢迎:默认使用<code>const</code>,只有当你知道一个变量的值会被改变的时候使用<code>let</code>。根本原因是大部分变量在初始化后不会被改变,而出乎意料的值的改变是bug之源。这个观点有大量的支持者,如果你采用ECMAScript 6的话,这个观点值得你在代码中践行。</p>
<h2 id="1-6-小结"><a href="#1-6-小结" class="headerlink" title="1.6 小结"></a>1.6 小结</h2><p><code>let</code>和<code>const</code>的块变量绑定把块级词法作用域引入到了JavaScript。这些变量不会被提升并且只存在于它们声明处所在的块中。它提供了更加类似其他语言的表现,因为变量现在可以在它们需要的地方声明,造成出乎意料的错误的可能性也小了。不过有一个副作用,就是你不能再在变量声明之前访问到这个变量了,使用类似<code>typeof</code>这样安全的操作符也不行。尝试去在块绑定变量声明前访问它会导致错误,因为绑定变量存在于暂时性死区(TDZ)中。</p>
<p>在很多情况下,<code>let</code>和<code>const</code>和<code>var</code>的表现很近似,不过,在循环上有所不同。无论是对<code>let</code>还是<code>const</code>,<code>for-in</code>和<code>for-of</code>循环会在每次循环迭代的时候创建一个新的绑定变量。这意味着在循环体中创建的函数可以访问到当前迭代中循环绑定变量的值,而不是访问到在循环结束后的值(<code>var</code>的行为)。同样,对在<code>for</code>循环中的<code>let</code>声明也是如此。如果尝试在<code>for</code>循环中使用<code>const</code>声明,可能会致使错误。</p>
<p>现阶段对块变量绑定的最佳实践是默认使用<code>const</code>,只有当你知道一个变量的值需要改变的时候才去使用<code>let</code>。这保证了代码最低限度的改变,而这对阻止特定类型错误有所帮助。</p>
<hr>
<p>原文地址:<a href="https://github.com/nzakas/understandinges6/blob/master/manuscript/01-Block-Bindings.md">Understanding ECMAScript 6: Block Bindings</a></p>
]]></content>
<summary type="html">
以前,变量声明方式是JavaScript编程中一个令人困惑的部分。在大部分类C语言里,变量(或者说作用域)在声明的地方生成。但是在JavaScript中,情况就不是这样了。变量实际上创建的地方取决于你声明它的方式,而ECMAScript 6提供了一些可选的方式来让你更容易地控制作用域。本章将会告诉你为什么经典的`var`声明会令人感到困惑,并且会介绍ECMAScript 6中的块级作用域,然后会给出如何使用它们的最佳实践。
</summary>
<category term="JavaScript" scheme="https://ontides.github.io/tags/JavaScript/"/>
<category term="翻译" scheme="https://ontides.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>JavaScript中的this</title>
<link href="https://ontides.github.io/2016/11/08/JavaScript%E4%B8%AD%E7%9A%84this/"/>
<id>https://ontides.github.io/2016/11/08/JavaScript中的this/</id>
<published>2016-11-07T16:20:31.000Z</published>
<updated>2017-02-21T03:42:21.000Z</updated>
<content type="html"><![CDATA[<p>学习JavaScript的时候,this曾让我困惑不已,因为不清楚this所指向的是什么,在阅读一些代码的时候总是迷迷糊糊,不知所云。弄懂this后,看JavaScript代码的时候比原来更清晰,对语言本身也有了更深的理解。在JavaScript中,this非常重要但又特别容易弄错,所以我在此总结一下this的点点滴滴,希望能给那些对this还不明白的朋友们一点帮助吧。</p>
<hr>
<h2 id="一、this的起源"><a href="#一、this的起源" class="headerlink" title="一、this的起源"></a>一、this的起源</h2><p>了解<code>this</code>是怎么来的,对更好地理解<code>this</code>还是很重要的。</p>
<p>当一个函数被调用的时候,也就是执行流进入一个函数的时候,会自动创建一个被称为执行上下文的记录(其实这个时候也就产生了作用域链),这个记录里包含了各种必要信息,比如函数的调用方式,函数的参数(也就是<code>arguments</code>)等,<code>this</code>就包含在这个信息里。</p>
<h2 id="二、对this的误解"><a href="#二、对this的误解" class="headerlink" title="二、对this的误解"></a>二、对this的误解</h2><p>我刚接触this的时候,我简单地觉得this就是指向函数所在的对象。比如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> myObj = {</div><div class="line"> <span class="attr">message</span>: <span class="string">"Hi, Ontides!"</span>,</div><div class="line"> <span class="attr">say</span>: <span class="function"><span class="keyword">function</span> <span class="title">say</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.message);</div><div class="line"> }</div><div class="line">};</div><div class="line">myObj.say(); <span class="comment">//Hi, Ontides!</span></div></pre></td></tr></table></figure>
<p>这里确实如所预想的那样,打印出了“Hi, Ontides”,但是,函数并不一定在一个对象中,如果是一个全局函数,那么它里面的this指向就不能简单地理解为“指向函数所在的对象”那么了。</p>
<p>实际上,JavaScript中this的指向和this所在函数定义的位置没有任何关系(ES5及以前,因为ES6中的箭头函数不是这样,稍后会说)。我们之所以会认为this和函数所声明的地方有关,是因为在JavaScript中的作用域遵循词法作用域,因此,我们很容易使用词法作用域的分析方式去分析this的具体指向。实际上,<strong>this的处理方式和动态作用域的处理方式相同</strong>。this的指向取决于函数调用的位置,而不是函数定义的位置。</p>
<p>##三、this真正的指向</p>
<p>上面提到,函数调用的位置决定了this绑定的对象。在JavaScript中,对应函数四种调用方式,this的绑定一共有四种方式,分别为隐式绑定,显式绑定、new绑定和默认绑定。</p>
<h3 id="1-隐式绑定"><a href="#1-隐式绑定" class="headerlink" title="1. 隐式绑定"></a>1. 隐式绑定</h3><p>当函数的调用位置具有上下文对象,或者说函数是在一个对象下调用的时候,this会被隐式绑定到这个上下文对象上。如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</div><div class="line">}</div><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> <span class="attr">a</span>: <span class="number">2</span>,</div><div class="line"> <span class="attr">foo</span>: foo</div><div class="line">};</div><div class="line"><span class="comment">//这里this指向了obj</span></div><div class="line">obj.foo() <span class="comment">//2</span></div></pre></td></tr></table></figure>
<h3 id="2-显式绑定"><a href="#2-显式绑定" class="headerlink" title="2. 显式绑定"></a>2. 显式绑定</h3><p>当一个函数使用<code>call</code>或者<code>apply</code>的方式调用的时候,this会被显式地绑定到<code>call</code>或者<code>apply</code>所指定的对象。如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a = <span class="number">3</span>;</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</div><div class="line">}</div><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> <span class="attr">a</span>: <span class="number">2</span></div><div class="line">};</div><div class="line">foo.call(obj); <span class="comment">//2</span></div></pre></td></tr></table></figure>
<p>这里因为函数foo再调用的时候使用<code>.call()</code>的方式调用,所以this指向了obj而不是默认的全局对象。</p>
<h3 id="3-new绑定"><a href="#3-new绑定" class="headerlink" title="3. new绑定"></a>3. new绑定</h3><p>当对函数进行构造调用的时候(即使用new操作符进行调用),this的指向会发生改变。为了更好阐述new绑定,这里先阐述一下对函数构造调用的的过程。</p>
<p>当对一个函数尽兴构造调用的时候,会经历以下步骤:</p>
<ol>
<li>创建一个新的对象</li>
<li>将构造函数的作用域赋给新的对象</li>
<li>执行构造函数中的代码</li>
<li>返回新对象</li>
</ol>
<p>在步骤2中this被绑定到了这个新对象。</p>
<h3 id="4-默认绑定"><a href="#4-默认绑定" class="headerlink" title="4. 默认绑定"></a>4. 默认绑定</h3><p>当前几种绑定都不适用的情况下JavaScript引擎会对this执行默认绑定。这里的函数调用方式是独立函数调用,如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.name);</div><div class="line">}</div><div class="line">foo(); <span class="comment">//1</span></div></pre></td></tr></table></figure>
<p>在默认绑定的情况下this被绑定到了全局对象,因为全局变量a是全局对象的一个属性,因此这里输出了a中的内容。</p>
<p>这里需要注意的是,再严格模式下,默认绑定将不会绑定到全局对象上,而会绑定到<code>undefined</code>,如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="meta">"use strict"</span>;</div><div class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</div><div class="line">}</div><div class="line">foo(); <span class="comment">//TypeError: undefined is not an object</span></div></pre></td></tr></table></figure>
<p>这里抛出了类型错误的提示,因为this被绑定到<code>undefined</code>,而<code>undefined</code>不是一个对象。</p>
<h2 id="四、this绑定优先级"><a href="#四、this绑定优先级" class="headerlink" title="四、this绑定优先级"></a>四、this绑定优先级</h2><p>this绑定一共四条规则,那么在判断this绑定的时候应该怎样运用规则呢?通过this绑定的优先级来判断。</p>
<p>优先级从高到低分别为 new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。</p>
<h2 id="五、值得注意的几个点"><a href="#五、值得注意的几个点" class="headerlink" title="五、值得注意的几个点"></a>五、值得注意的几个点</h2><h3 id="1-小心隐性绑定"><a href="#1-小心隐性绑定" class="headerlink" title="1. 小心隐性绑定"></a>1. 小心隐性绑定</h3><p>有些情况一些函数看起来像是隐性绑定,而实际上是应用了默认绑定。如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a = <span class="number">3</span>;</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</div><div class="line">}</div><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> <span class="attr">a</span>: <span class="number">2</span>,</div><div class="line"> <span class="attr">foo</span>: foo</div><div class="line">};</div><div class="line"><span class="keyword">var</span> bar = obj.foo;</div><div class="line">obj.foo(); <span class="comment">//2</span></div><div class="line">bar(); <span class="comment">//3</span></div></pre></td></tr></table></figure>
<p>这里虽然把<code>obj.foo</code>赋给了<code>bar</code>,但是实际上只是把对<code>foo</code>的引用赋给了<code>bar</code>,因此这里<code>bar</code>的调用只是普通的独立函数调用。</p>
<p>还有一种更为常见而容易出错的情况发生在传入回调函数时:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a = <span class="number">3</span>;</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</div><div class="line">}</div><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> <span class="attr">a</span>: <span class="number">2</span>,</div><div class="line"> <span class="attr">foo</span>: foo</div><div class="line">};</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">doFoo</span>(<span class="params">fn</span>)</span>{</div><div class="line"> fn();</div><div class="line">}</div><div class="line">doFoo(obj.foo); <span class="comment">//3</span></div></pre></td></tr></table></figure>
<p>同样,如果理解函数名是对函数的引用的话,这个结果就容易理解了呃。传递到doFoo的参数是函数foo,函数的调用方式是独立调用,因此这里的this并不适用隐形绑定规则,而是采用默认绑定的规则。</p>
<h3 id="2-箭头函数"><a href="#2-箭头函数" class="headerlink" title="2. 箭头函数"></a>2. 箭头函数</h3><p>ES6中的箭头函数不适用与上面四个规则。</p>
<p>关于箭头有两点需要注意:</p>
<ol>
<li><p>箭头函数中的this由函数所在的外部作用域来决定的</p>
<p> 这里箭头函数中this遵循词法作用域的规则。如:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> <span class="attr">a</span>: <span class="number">1</span>,</div><div class="line"> <span class="attr">foo</span>: <span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> setTimeout(<span class="function"><span class="params">()</span>=></span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</div><div class="line"> },<span class="number">30</span>);</div><div class="line"> }</div><div class="line">}</div><div class="line">obj.foo(); <span class="comment">//1</span></div></pre></td></tr></table></figure>
<p> 这里定时器中的this并没有指向全局对象,是绑定到obj,可以看出是继承了外层函数中的this的绑定对象。</p>
</li>
<li><p>箭头函数的this绑定无法修改</p>
<p> 箭头函数使用自己独特的this绑定原则,不能通过<code>.call()</code>等方式改变this指向。</p>
</li>
</ol>
]]></content>
<summary type="html">
学习JavaScript的时候,this曾让我困惑不已,因为不清楚this所指向的是什么,在阅读一些代码的时候总是迷迷糊糊,不知所云。弄懂this后,看JavaScript代码的时候比原来更清晰,对语言本身也有了更深的理解。在JavaScript中,this非常重要但又特别容易弄错,所以我在此总结一下this的点点滴滴,希望能给那些对this还不明白的朋友们一点帮助吧。
</summary>
<category term="JavaScript" scheme="https://ontides.github.io/tags/JavaScript/"/>
</entry>
<entry>
<title>[译]《Understanding ECMAScript 6》——Introduction</title>
<link href="https://ontides.github.io/2016/10/17/%E8%AF%91-%E3%80%8AUnderstanding-ECMAScript-6%E3%80%8B%E2%80%94%E2%80%94Introduction/"/>
<id>https://ontides.github.io/2016/10/17/译-《Understanding-ECMAScript-6》——Introduction/</id>
<published>2016-10-16T16:49:08.000Z</published>
<updated>2017-02-19T06:29:25.000Z</updated>
<content type="html"><![CDATA[<h1 id="《理解-ECMAScript-6》——简介"><a href="#《理解-ECMAScript-6》——简介" class="headerlink" title="《理解 ECMAScript 6》——简介"></a>《理解 ECMAScript 6》——简介</h1><p>JavaScript的核心语言特性是在一个叫做ECMA-262的标准中定义的。在这个标准中定义的语言叫做ECMAScript。你所知道的在浏览器中和Node.js中的JavaScript其实是ECMAScript的父集。浏览器和Node.js通过增加对象和方法来增加更多的功能,不过语言的核心还是在ECMAScript中定义的。ECMA-262标准的不断发展对作为整体的JavaScript的成功至关重要,而这本书包含了这个语言近来最重要的更新——ECMAScript6的诸多改变。</p>
<h2 id="一、走向-ECMAScript-6"><a href="#一、走向-ECMAScript-6" class="headerlink" title="一、走向 ECMAScript 6"></a>一、走向 ECMAScript 6</h2><p>2007年,JavaScript处在一个发展的关键时刻。当饱受欢迎的Ajax引领着动态Web应用的新时代时,JavaScript自从1999年第三版的ECMA-262发布后一直没有变化。作为负责推动ECMAScript发展进程的委员会,TC-39为 ECMAScript 4 制定了大量的规范草案。新的特性包括新语法、模块、类、类的继承、对象私有变量,以及可选类型注释等等。</p>
<p>ECMAScript 4中大量的改动造成了TC-39内部的分歧,一些成员觉得第四版想要做的太多了。来自雅虎、谷歌和微软的核心成员为下一版本的ECMAScirpt给出了一个替代性的提议,这个版本最初叫做 ECMAScript 3.1。“3.1”主要是为了表明它只是为现有标准增加一些功能的版本。</p>
<p>ECMAScirpt 3.1 提供了非常少的语法改变,相反,它主要专注于属性,原生JSON对象的支持和给现有的对象增加方法。虽然起初尝试着去整合ECMAScript 3.1和ECMAScript 4,但是由于两方阵营对语言的发展的意见无法达成一致,这个尝试最终失败了。</p>
<p>到了2008年的时候,Brendan Eich,JavaScript的创造者,宣布TC-39将会专注于制定ECMAScript 3.1标准。他们会在下个版本的ECMAScript标准化前搁置ECMAScript 4中主要语法和特性的修改,并且委员会中所有成员都会努力将ECMAScript 3.1和ECMAScript 4中最精华的部分融合到一个名为“ECMAScript Harmony(和谐)”的版本中。</p>
<p>最终,ECMAScript3.1成为了ECMA-262的第5个版本的标准,也被称为ECMAScript 5。委员会为了避免和现有已经搁置的规范同名,没有发布ECMAScript 4标准,随后工作便专向ECMAScript Harmony,ECMAScript 6成为了在“和谐”精神下的第一个发布的标准。</p>
<p>ECMAScript 6(简称ES6)的所有特性在2015年的时候完全完成,并且被正式赋予了“ECMAscript 2015”的名字(不过在本书中依然还会用ECMAScript 6来指代它,因为这个名字对开发者最为熟悉)。ES6的语言特性有着广泛的变动,如全新的对象和模式,语法本身,现有对象的新方法。ECMAScript 6中最激动人心的是,它所有的改变都是为了解决开发者们实际遇到的问题。</p>
<h2 id="二、关于这本书"><a href="#二、关于这本书" class="headerlink" title="二、关于这本书"></a>二、关于这本书</h2><p>良好地理解ECMAScript 6特性是所有JavaScript开发者们前行重要的一步。包含在ECMAScript 6中的语言特性代表了在可预见的未来搭建JavaScript应用的基础,这也正是这本书所要讲述的。我希望你能通过阅读这本书学习ECMAScript 6的新特性,并在在你需要的时候开始使用它们。</p>
<h3 id="浏览器和Node-js的兼容性"><a href="#浏览器和Node-js的兼容性" class="headerlink" title="浏览器和Node.js的兼容性"></a>浏览器和Node.js的兼容性</h3><p>很多的JavaScript环境,如网页浏览器和Node.js,都正在积极地努力实现ECMAScript 6.这本书不会去阐述各个环境实现不完全的兼容性问题,而专注阐明于那些在规范中定义为正确的行为。因此,你的JavaScript环境的行为可能和这本书中描述的不同。</p>
<h3 id="本书适用人群"><a href="#本书适用人群" class="headerlink" title="本书适用人群"></a>本书适用人群</h3><p>这本书面向那些熟悉JavaScript和ECMAScript5的读者。如果要对语言有深层次的认识的话不一定要使用这本书,这本书的目的是帮助你理解ECMAScript5和ECMAScript6之间的不同。更准确地说,这本书的目标读者是那些在浏览器或者Node.js环境中编程并且想学习语言最新进展的中高级JavaScript开发者。</p>
<p>这本书不适合那些从未写过JavaScript的初学者。要理解这本书,你需要拥有对语言有一个很好的基本理解。</p>
<h3 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h3><p>这本书十三章中的每一章各包含了ECMAScript 6中的一个方面。为了让你更加深刻地理解这些改变,很多章节都从讨论ECMAScript 6 想要解决的问题开始,并且所有章节都包含了代码示例来帮助你学习新的语法和概念。</p>
<p><strong>第1章:块绑定是怎样工作的</strong> 讨论用以替代<code>var</code>的<code>let</code>和<code>const</code>。</p>
<p><strong>第2章:字符串和正则表达式</strong> 包含了新增加的字符串操控和检查功能,还有模板字符串的介绍。</p>
<p><strong>第3章:ECMAScript 6中的函数</strong> 讨论了有关函数的一些变动,包含了箭头函数形式(arrow function form)、默认值参数(default parameters)、剩余参数(rest parameters)等等。</p>
<p><strong>第4章:对象函数的扩展</strong> 介绍了对象创建,修改,使用方式的变动。讨论话题包含对象字面量语法的改变和新的反射方法。</p>
<p><strong>第5章:为更简单的数据访问解构</strong> 介绍了对象和数组的解构(destructuring),它让你能够用简明的语法解构对象和数组</p>
<p><strong>第6章:symbols和symbol属性</strong> 介绍了symbols的概念,这是定义属性的一个全新的方法。symbols是一个新的原始类型,它可以用来遮掩(obscure)(但不是隐藏(hide))对象属性和方法。</p>
<p><strong>第7章:Sets和Maps</strong> 详细阐述了全新的集合类型:<code>Set</code>、<code>WeakSet</code>、<code>Map</code> 和 <code>WeakMap</code>。这些类型通过增加语义,删除重复内容和特别为JavaScript设计的内存管理的方式扩展了数组。</p>
<p><strong>第8章:迭代器和生成器</strong> 讨论了语言中新增的迭代器和生成器。这些特性让你用有效的方式处理数据,而这在之前版本的JavaScript中难以做到</p>
<p><strong>第9章:介绍JavaScript中的类</strong> 介绍了JavaScript中第一个正式的“类”的概念。那些其他语言转到JavaScript的人常常对此(ES5及更早的JavaScript中没有“类”)感到困惑,而JavaScript类的语法的加入让JavaScript对其他人和爱好者们更加简明而友好。</p>
<p><strong>第10章:改进功能的数组</strong> 详述了原生数组的变动和在JavaScript中使用它们的全新方式</p>
<p><strong>第11章:Promises和异步编程</strong> 介绍了作为这个语言一部分的promises。promises作为基础特性已经被第三方类库广泛地支持,而ECMAScript 6开始将它规范化并默认地支持了它。</p>
<p><strong>第12章:代理和反射API</strong> 介绍了规范化的JavaScript反射(Reflection)API和新的代理对象,代理对象能让你拦截对一个对象的所有操作。代理让开发者拥有对对象史无前例的控制,也因此让定义全新交互模式有了无限可能。</p>
<p><strong>第13章:用模块封装代码</strong> 详细阐述了JavaScript官方的模块形式,目的是为了让这些模块能取代以前出现各种各样的模块定义形式</p>
<p><strong>附录A:一些细小的ECMAScript 6变动</strong> 包含了其他在ECMAScript 6中的变动,通常是那些你不常使用的或者那些不适合放到前几章的主要话题中的变动。</p>
<p><strong>附录B:理解ECMAScript 7(2016)</strong> 阐述了应用到ECMAScript 7标准中的两个新增项,它们不拥有ECMAScript 6那样有影响力。</p>
<h3 id="使用约定"><a href="#使用约定" class="headerlink" title="使用约定"></a>使用约定</h3><p>本书将使用如下的排版约定:</p>
<ul>
<li><em>斜体</em> 表示新的术语</li>
<li><code>Constant width</code> 表示一段代码或者文件名</li>
</ul>
<p>此外,长代码示例将会放在固定宽度的代码块中,如</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doSomething</span><span class="hljs-params">()</span> {</span>
<span class="hljs-comment">// empty</span>
}`</pre>
在一个代码块中, 在 `console.log()` 右边的注释代表代码运行时你将在浏览器或者Node.js控制台看到的输出结果,比如:
<pre class="prettyprint">`console.log(<span class="hljs-string">"Hi"</span>); <span class="hljs-comment">// "Hi"</span>`</pre>
如果一个代码块中的某一行抛出了错误,会在该行代码的右边看到如下的注释:
<pre class="prettyprint">`doSomething(); <span class="hljs-comment">// error!</span>
</code></pre><h3 id="帮助与支持"><a href="#帮助与支持" class="headerlink" title="帮助与支持"></a>帮助与支持</h3><p>你可以通过访问 <a href="https://github.com/nzakas/understandinges6">https://github.com/nzakas/understandinges6</a> 提出问题,给出修正的建议,反对书中的内容</p>
<p>你读了本书后如果有任何疑问,请给我的邮件列表发送消息 <a href="http://groups.google.com/group/zakasbooks">http://groups.google.com/group/zakasbooks</a>.</p>
<h2 id="三、感谢"><a href="#三、感谢" class="headerlink" title="三、感谢"></a>三、感谢</h2><p>感谢 Jennifer Griffith-Delgado, Alison Law,和No Starch出版社的所有人对这本书的支持和帮助。他们对我在生病期间缓慢的写作速度给予了理解和耐心,这是我永远也不会忘记的。</p>
<p>I我很感谢作为技术编辑 Juriy Zaytsev 的关注,还有 Axel Rauschmayer 博士的反馈和几段谈话,这些谈话让我对书中所讨论的概念的理解更加清晰。</p>
<p>感谢在Github上为这本书提交修改的每一个人:ShMcK, Ronen Elster, Rick Waldron, blacktail, Paul Salaets, Lonniebiz, Igor Skuhar, jakub-g, David Chang, Kevin Sweeney, Kyle Simpson, Peter Bakondy, Philip Borisov, Shaun Hickson, Steven Foote, kavun, Dan Kielp, Darren Huskie, Jakub Narębski, Jamund Ferguson, Josh Lubaway, Marián Rusnák, Nikolas Poniros, Robin Pokorný, Roman Lo, Yang Su, alexyans, robertd, 404, Aaron Dandy, AbdulFattah Popoola, Adam Richeimer, Ahmad Ali, Aleksandar Djindjic, Arjunkumar, Ben Regenspan, Carlo Costantini, Dmitri Suvorov, Kyle Pollock, Mallory, Erik Sundahl, Ethan Brown, Eugene Zubarev, Francesco Pongiluppi, Jake Champion, Jeremy Caney, Joe Eames, Juriy Zaytsev, Kale Worsley, Kevin Lozandier, Lewis Ellis, Mohsen Azimi, Navaneeth Kesavan, Nick Bottomley, Niels Dequeker, Pahlevi Fikri Auliya, Prayag Verma, Raj Anand, Ross Gerbasi, Roy Ling, Sarbbottam Bandyopadhyay和Shidhin.</p>
<p>还有,感谢每一位在Patreon: Casey Visco上支持这本书的人。</p>
<hr>
<p>原文地址:<a href="https://github.com/nzakas/understandinges6/blob/master/manuscript/00-Introduction.md">Understanding ECMAScript 6: Introduction</a></p>
]]></content>
<summary type="html">
JavaScript的核心语言特性是在一个叫做ECMA-262的标准中定义的。在这个标准中定义的语言叫做ECMAScript。你所知道的在浏览器中和Node.js中的JavaScript其实是ECMAScript的父集。浏览器和Node.js通过增加对象和方法来增加更多的功能,不过语言的核心还是在ECMAScript中定义的。ECMA-262标准的不断发展对作为整体的JavaScript的成功至关重要,而这本书包含了这个语言近来最重要的更新——ECMAScript6的诸多改变。
</summary>
<category term="JavaScript" scheme="https://ontides.github.io/tags/JavaScript/"/>
<category term="翻译" scheme="https://ontides.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>细说JavaScript作用域和闭包</title>
<link href="https://ontides.github.io/2016/10/05/%E7%BB%86%E8%AF%B4JavaScript%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E9%97%AD%E5%8C%85/"/>
<id>https://ontides.github.io/2016/10/05/细说JavaScript作用域和闭包/</id>
<published>2016-10-05T02:36:19.000Z</published>
<updated>2017-02-21T02:45:37.000Z</updated>
<content type="html"><![CDATA[<p>文章开头说点有用没用的,还记得最开始接触JavaScript时,只是会用<code>document.getElementById("")</code>这种DOM操作来做点简单特效,但仅仅如此已经让我欣喜。后来的知道了原来可以利用ajax技术来实现无刷新获取服务端数据,这让我开始觉得前端还是有好多事可以做的,在之后因为老师的一个项目的关系接触了AngularJS,便开始了AngularJS的学习。AngularJS让我深刻地认识到在前后端分离的时候,前端原来可以做到这么多东西,诸如模块,路由,依赖注入,双向绑定这样的词汇让我感到陌生而不知所措。即便是我已经在那个项目中使用AngularJS中上面所说的那些特性,我对AngularJS还是充满了困惑——我不清楚AngularJS是如何实现的。</p>
<p>审视自己对JavaScript浅薄认识,我意识到自己应该去先把js语言本身好好学习一下。确实,在较为仔细地学习了ECMAScript后,我发现,我开始理解模块的实现,对如<code>.apply()</code>,<code>.bind()</code>等用法的了解让我对如何写出更好的js代码有了更多的理解。当然,现在我也只是入门水平,还需要不断学习和提高。</p>
<p>这篇文章包含着我对javascript作用域和闭包的理解,写的过程也是整理和归纳的过程,以后也会用这种方式来归纳总结自己所学所想,希望自己能不断进步吧,如果文章能给其他人带来点用处,我也会很开心的。</p>
<h2 id="一、JavaScript有哪些作用域类型"><a href="#一、JavaScript有哪些作用域类型" class="headerlink" title="一、JavaScript有哪些作用域类型"></a>一、JavaScript有哪些作用域类型</h2><p>对于一门编程语言来说,作用域主要有两种模型,即词法作用域(Lexical Scope)和动态作用域(Dynamic Scope)。而词法作用域,即静态作用域(Static Scope),被目前JavaScript在内的大部分编程语言所采用,而动态作用域只有Bash脚本等少数编程语言在使用。</p>
<p>词法作用域就是定义在词法阶段的作用域,当词法分析器处理代码的时候会保持作用域不变。在表现上,词法作用域的函数中遇到既不是形式参数也不是函数内部定义的局部变量的变量时,会自动去函数定义时的环境中查询(即沿作用域链)。</p>
<p>还是举两个例子吧:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//这是词法作用域</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(a); <span class="comment">//2</span></div><div class="line">}</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">var</span> a = <span class="number">3</span>;</div><div class="line"> foo();</div><div class="line">}</div><div class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line">bar();</div></pre></td></tr></table></figure></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//这是动态作用域</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(a); <span class="comment">//3</span></div><div class="line">}</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">var</span> a = <span class="number">3</span>;</div><div class="line"> foo();</div><div class="line">}</div><div class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line">bar();</div></pre></td></tr></table></figure>
<h2 id="二、作用域与标识符查询"><a href="#二、作用域与标识符查询" class="headerlink" title="二、作用域与标识符查询"></a>二、作用域与标识符查询</h2><p>作用域和表示符查询紧密相关,表示符正是依靠这样一套严密的作用域规则而得以有条理地查询。而对于作用域,需要理解几个词:执行环境,变量对象,活动对象,作用域链。</p>
<h3 id="执行环境与作用域链"><a href="#执行环境与作用域链" class="headerlink" title="执行环境与作用域链"></a>执行环境与作用域链</h3><p>首先解释执行环境吧,执行环境定义了在这个执行环境中变量或者函数有权访问的数据,决定了它们各自的行为。在js中执行环境的结构由全局执行环境和一级级内部嵌套的子执行环境组成。其中全局执行环境由ECMAScript实现所在的宿主环境决定,在浏览器中为window对象,在Node中是global对象。每个函数都包含着自己的执行环境,一个个执行环境组成了一个执行环境栈,当执行流进入一个函数时,该函数的执行环境会被推入环境栈中,函数执行之后该环境又会被从环境栈中弹出,这便是ECMAScript执行流的机制。</p>
<p>每个执行环境都包含一个变量对象,它保存着环境中定义的所有变量和函数。当一个执行环境的代码执行的时候会为它的变量对象创建一个由当前执行环境沿着环境栈到全局执行环境的作用域链,作用域链保证了对当前执行环境有权访问的所有变量和函数的 <strong>有序</strong> 访问。如果当前执行环境是一个函数,那么该函数的活动对象就会成为这个执行环境的变量对象。</p>
<p>而对除了全局执行环境外的执行对象,活动对象最前端是arguments对象,而作用域链沿着一层层的被包含环境的变量对象沿伸到全局执行环境的变量对象。</p>
<h3 id="标识符查询"><a href="#标识符查询" class="headerlink" title="标识符查询"></a>标识符查询</h3><p>了解了执行环境和作用域链,标识符的查询就很好理解了,js引擎在遇到一个标识符的时候根据作用域链沿着变量对象从前端到后端进行查询(其实就是从子作用域逐层向父作用域查询),一旦遇到匹配的标识符便会<strong>立即停止</strong>。如:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> a = <span class="number">1</span>;</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line"> <span class="built_in">console</span>.log(a);</div><div class="line"> }</div><div class="line"> a = <span class="number">3</span>;</div><div class="line"> bar();</div><div class="line">}</div><div class="line">foo(); <span class="comment">//2</span></div></pre></td></tr></table></figure></p>
<p>该例中在console.log对a进行RHS查询时,在bar函数的作用域内便查询到了标识符a,因此便立即停止标识符查询,所以访问不到foo函数的标识符a。这种现象是标识符的遮蔽效应,在多层的嵌套作用域内可以定义同名的标识符,遮蔽效应使得内部的标识符可以遮蔽外层表示符。</p>
<h2 id="三、可以形成独立作用域的结构"><a href="#三、可以形成独立作用域的结构" class="headerlink" title="三、可以形成独立作用域的结构"></a>三、可以形成独立作用域的结构</h2><p>在JavaScript中,可以形成独立作用域的结构有两类,函数作用域和块作用域。先说说函数作用域吧。</p>
<h3 id="函数作用域"><a href="#函数作用域" class="headerlink" title="函数作用域"></a>函数作用域</h3><p>函数作用域是js中最常见的作用域,每一个函数都拥有一个作用域,而属于这个函数的变量都能在整个函数的范围内访问(当然也能访问),但是在函数外则无法访问函数内的任何变量——除非你在函数执行时把一个闭包返回到函数体外。这种函数内部变量对外的隐藏作用使得同级作用域同名标识符之间的冲突得到避免,这样,也促成了模块机制的良好运行。</p>
<h4 id="利用函数内部对外部的隐藏"><a href="#利用函数内部对外部的隐藏" class="headerlink" title="利用函数内部对外部的隐藏"></a>利用函数内部对外部的隐藏</h4><p>如果想创建一个封闭的作用域,让这个作用域内的变量不被外部访问,利用<strong>立即执行函数表达式</strong>(IIFE)便可实现。如:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</div><div class="line">(<span class="function"><span class="keyword">function</span> <span class="title">IIFE</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line"> <span class="built_in">console</span>.log(a); <span class="comment">//2</span></div><div class="line">})(); <span class="comment">//立即执行</span></div><div class="line"><span class="built_in">console</span>.log(a); <span class="comment">//1</span></div></pre></td></tr></table></figure></p>
<p>这个函数表达式在声明后立即执行,这样函数体内的语句都得到了执行且对外部的变量没有影响。其实,JS中的模块也利用了这点。JS模块还是放到最后说吧。</p>
<h4 id="匿名函数"><a href="#匿名函数" class="headerlink" title="匿名函数"></a>匿名函数</h4><p>JavaScript中的函数表达式存在具名函数表达式和匿名函数表达式两种,而函数声明则必须具名。匿名函数有什么用呢?让我们不用去冥思苦想标识符怎么取。如上面的例子中的IIFE函数,该函数即使去掉函数名,程序也可以正常运行,因为它的函数名在这种情况起到作用不大。</p>
<p>虽然匿名函数写起来十分便捷,但是基于以下原因,始终给每个函数命名是值得推荐的。</p>
<ol>
<li>在没有函数名的情况下,函数的递归调用等需要引用自身的时候,将会不得不用<code>arguments.callee</code>进行引用,而这在ES5之后便不被推荐使用了——甚至在严格模式下会抛出TypeError错误。</li>
<li>函数名的省略使得代码可读性下降</li>
</ol>
<p>说到了匿名函数,不得不提闭包,由于闭包内容较多,将在后面专门说明。</p>
<h3 id="块作用域"><a href="#块作用域" class="headerlink" title="块作用域"></a>块作用域</h3><p>除了最常见的函数作用域,JavaScript中的块作用域也可以创建出一个独立的作用域。可是,在 Nicholas C.Zakas 著的《Professional JavaScript for Web Developers(3rd Edition)》中说道:</p>
<blockquote>
<h4 id="No-Block-Level-Scopes"><a href="#No-Block-Level-Scopes" class="headerlink" title="No Block-Level Scopes"></a>No Block-Level Scopes</h4><p>JavaScript’s lack of block-level scopes is a common source of confusion. In other C-like languages, code blocks enclosed by brackets have their own scope (more accurately described as their own execution context in ECMAScript), allowing conditional definition of variables. </p>
</blockquote>
<p>Nicholas 之所以说JavaScript没有块级作用域是因为他没把<code>with</code>和<code>try/catch</code>作为块级作用域看待,他在书中把这两个情况作为延长作用域链的手段。实际上,通过<code>with</code>和<code>try/catch</code>创建的独立作用域也算是块级作用域的形式,除了这两种外还可以利用ES6中的<code>let</code>和<code>const</code>也可以形成块级作用域。</p>
<h4 id="with"><a href="#with" class="headerlink" title="with"></a>with</h4><p>通过with可以创建出的作用域仅在with声明中使用。如:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> location = {</div><div class="line"> <span class="attr">say</span>: <span class="string">'hello world'</span></div><div class="line">};</div><div class="line"><span class="keyword">with</span> (location){</div><div class="line"> <span class="built_in">console</span>.log(say);<span class="comment">//hello world</span></div><div class="line">}</div><div class="line"><span class="built_in">console</span>.log(say);<span class="comment">//抛出ReferenceError错误</span></div></pre></td></tr></table></figure></p>
<h4 id="try-catch"><a href="#try-catch" class="headerlink" title="try/catch"></a>try/catch</h4><p><code>try/catch</code>的catch分句会创建一个块级作用域,其中声明的变量只能在内部使用。如:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">try</span>{</div><div class="line"> <span class="keyword">throw</span> {</div><div class="line"> <span class="attr">say</span>: <span class="string">'hello world'</span></div><div class="line"> }</div><div class="line">}</div><div class="line"><span class="keyword">catch</span>(error){</div><div class="line"> <span class="built_in">console</span>.log(error.say);<span class="comment">//hello world</span></div><div class="line">}</div><div class="line"><span class="built_in">console</span>.log(error.say);<span class="comment">////抛出ReferenceError错误</span></div></pre></td></tr></table></figure></p>
<h4 id="let-const"><a href="#let-const" class="headerlink" title="let/const"></a>let/const</h4><p>ES6中引入的let和const关键字可以将变量绑定到任意由{}包含的代码块中。<br>以下例子可以看出用let声明变量和用var声明变量的区别:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//变量用var声明</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">1</span>; i < <span class="number">5</span>; i++){</div><div class="line"> <span class="built_in">console</span>.log(i);</div><div class="line">}</div><div class="line"><span class="built_in">console</span>.log(i);<span class="comment">//5</span></div><div class="line"></div><div class="line"><span class="comment">//变量用let声明</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">1</span>; j < <span class="number">5</span>; j++){</div><div class="line"> <span class="built_in">console</span>.log(j);</div><div class="line">}</div><div class="line"><span class="built_in">console</span>.log(j);<span class="comment">//抛出ReferenceError错误</span></div></pre></td></tr></table></figure></p>
<p>当然,也可以直接绑定在块中,如:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="keyword">let</span> a = <span class="number">10</span>;</div><div class="line"> <span class="built_in">console</span>.log(a);<span class="comment">//10</span></div><div class="line">}</div><div class="line"><span class="built_in">console</span>.log(a);抛出<span class="built_in">ReferenceError</span>错误</div></pre></td></tr></table></figure></p>
<p>const关键字定义的常量和let一样,能将其定义的常量绑定到{}包含的块级作用域中,就再举例子了。</p>
<h2 id="四、提升"><a href="#四、提升" class="headerlink" title="四、提升"></a>四、提升</h2><p>提升的概念比较简单,但是如果对js语言只是浅尝辄止的话,可能会理解不清。这里通过提升的表现,优先级和原因来简单说明js中的提升。</p>
<h3 id="提升的表现"><a href="#提升的表现" class="headerlink" title="提升的表现"></a>提升的表现</h3><p>提升是变量或函数在同个作用域内表现出的可以先使用后定义的现象。先来看看变量提升:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="meta">"use strict"</span>;<span class="comment">//开启ES5的严格模式</span></div><div class="line">a = <span class="number">2</span>;</div><div class="line"><span class="keyword">var</span> a;</div><div class="line"><span class="built_in">console</span>.log(a);<span class="comment">//2</span></div></pre></td></tr></table></figure></p>
<p>再看看函数提升:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="meta">"use strict"</span>;</div><div class="line">foo(<span class="number">1</span>);<span class="comment">//2</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">n</span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(n+<span class="number">1</span>);</div><div class="line">}</div></pre></td></tr></table></figure></p>
<h3 id="提升的优先级"><a href="#提升的优先级" class="headerlink" title="提升的优先级"></a>提升的优先级</h3><p>提升具有优先级,当一个作用域里对同个标识符既使用了变量声明,也使用了函数声明,那么函数声明会优先被提升。如下面的例子。<br><a tag="example"></a><br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">foo();<span class="comment">//1</span></div><div class="line"><span class="keyword">var</span> foo;</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="number">1</span>);</div><div class="line">}</div><div class="line">foo = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="number">2</span>);</div><div class="line">}</div></pre></td></tr></table></figure></p>
<h3 id="提升的深层原因"><a href="#提升的深层原因" class="headerlink" title="提升的深层原因"></a>提升的深层原因</h3><p>提升之所以存在,是因为JavaScript引擎在对代码解释前会进行预编译。在编译阶段,有一部分的工作就是找到所有的函数声明,并用合适的作用域把它们关联起来,这也是词法作用域的核心部分。有了预编译,在解释执行时,不再检测变量的声明,引擎在对变量进行LHS或RHS查询时会直接向编译阶段生成的作用域取得数据。</p>
<p>也就是说对于<code>var a = 1’;</code>这个语句来说,js引擎会识别为两个声明,即<code>var a</code> 和<code>a = 1</code>,他们分别在编译阶段和执行阶段处理。</p>
<h2 id="五、跨越词法作用域的两种机制"><a href="#五、跨越词法作用域的两种机制" class="headerlink" title="五、跨越词法作用域的两种机制"></a>五、跨越词法作用域的两种机制</h2><p>虽然JavaScript采用的是词法作用域,但是如果真的想要在代码执行的时候修改作用域的话也是有办法的,因为js中存在两个”bug”来做到这点。这两个”bug”是传说中的<code>eval</code>还有之前提到过的<code>with</code>。由于这两个”bug”,js的作用域应该算是不完全的词法作用域。</p>
<h3 id="eval"><a href="#eval" class="headerlink" title="eval"></a>eval</h3><p>eval可能是js中最强大的函数了,它接受一个参数即一个字符串,在执行时会把这个字符串作为实际的ECMA语句插入到原位置进行解析执行,正如下面例子所示。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">str</span>)</span>{</div><div class="line"> <span class="built_in">eval</span>(str);</div><div class="line"> <span class="built_in">console</span>.log(a);</div><div class="line">}</div><div class="line">a = <span class="number">1</span>;</div><div class="line"><span class="comment">//修改了foo函数体内的词法作用域</span></div><div class="line">foo(<span class="string">'var a = 2;'</span>);<span class="comment">//2</span></div></pre></td></tr></table></figure></p>
<p>因为在js编译器预编译的时候,eval()中的语句并不会被执行,所以,eval()中的变量或者函数不会被提升。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">foo();<span class="comment">//抛出ReferenceError错误</span></div><div class="line"><span class="built_in">eval</span>(<span class="string">"function foo(){console.log('1');}"</span>);</div></pre></td></tr></table></figure></p>
<p>当然,如果变量/函数的定义和使用都在eval中,那么里面的变量对于里面的调用来说是有提升的,比如:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">eval</span>(<span class="string">"foo();function foo(){console.log('1');}"</span>);<span class="comment">//1</span></div></pre></td></tr></table></figure></p>
<p>JavaScript中还有一些类似<code>eval()</code>处理方式的函数,比如<code>new Function(..)</code>,<code>setInterval(..)</code>和<code>setTimeout(..)</code>等等。</p>
<h3 id="with-1"><a href="#with-1" class="headerlink" title="with"></a>with</h3><p>with语句同样可以在执行阶段修改作用域。<br>如<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> <span class="attr">a</span>: <span class="string">'a'</span>,</div><div class="line"> <span class="attr">b</span>: <span class="string">'b'</span>,</div><div class="line"> <span class="attr">c</span>: <span class="string">'c'</span></div><div class="line">};</div><div class="line"><span class="keyword">with</span> (obj){</div><div class="line"> <span class="built_in">console</span>.log(a+b+c);<span class="comment">//abc</span></div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>在with语句的代码块里面,a,b,c来自obj的三个属性,这个在js预编译的时候也是不能判断的,因此with语句中的变量也不能在词法阶段确定。</p>
<h2 id="六、闭包与模块机制"><a href="#六、闭包与模块机制" class="headerlink" title="六、闭包与模块机制"></a>六、闭包与模块机制</h2><h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>说起闭包,在我刚接触JavaScript的时候听到这个词的时候感觉它特别神秘——从这个奇怪的名字就感觉到了神秘感。直到深入了解后才发现,闭包,原来是这样。</p>
<h4 id="什么是闭包"><a href="#什么是闭包" class="headerlink" title="什么是闭包"></a>什么是闭包</h4><p>当一个函数能够保存自己所在的词法作用域的时,便产生了闭包——无论这个是在当前词法作用域中还是当前词法作用域外。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> a = <span class="number">2</span>;</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(a);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> bar;</div><div class="line">}</div><div class="line"><span class="keyword">var</span> baz = foo();</div><div class="line">baz();<span class="comment">//2</span></div></pre></td></tr></table></figure></p>
<p>在上面的例子中,在调用foo函数时bar函数保存着foo函数的局部变量a,在bar函数被返回到foo函数外面的时候,便产生了闭包,闭包里保存着bar所在的词法作用域(包含bar函数和foo函数的所有变量以及全局对象的所有属性),故调用baz函数的时候能够正常返回a中的值2。</p>
<h4 id="闭包相关的问题"><a href="#闭包相关的问题" class="headerlink" title="闭包相关的问题"></a>闭包相关的问题</h4><h5 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h5><p>首先看下面的例子:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">1</span>; i <= <span class="number">5</span>; i++){</div><div class="line"> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">timer</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(i);</div><div class="line"> },<span class="number">1000</span>);</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>这个例子中可以看出这段代码预期是按顺序分别输出1~5的数字。而实际上,每次都输出6。</p>
<p>现仔细造成这个”出乎意料“的现象的原因:setTimeout函数中传进来的timer函数由于作用域闭包的原因,保存着对<strong>同一个</strong>变量 i 的引用,而在循环结束后i的值为6,又由于js的异步性,在过1000ms之后,循环已经处理结束,因此,结果会输出5个6。</p>
<h5 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h5><p>只需对上面代码进行一些改进即可解决,代码如下:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">1</span>; i <= <span class="number">5</span>; i++){</div><div class="line"> (<span class="function"><span class="keyword">function</span>(<span class="params">i</span>)</span>{</div><div class="line"> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">timer</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(i);</div><div class="line"> },<span class="number">1000</span>);</div><div class="line"> })(i)</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>上面代码通过创建一个自执行的函数表达式来得到一个独立的作用域,再把外部的i作为参数传进函数体,因为函数的参数传递会创建一个副本,所以每个timer中保存不同的i的副本,问题就得到解决了。</p>
<h3 id="模块机制"><a href="#模块机制" class="headerlink" title="模块机制"></a>模块机制</h3><p>JavaScript中的模块模式正是充分利用了作用域闭包的能力而实现的。下面由简入深地描述js中的模块机制。</p>
<h4 id="简单的模块"><a href="#简单的模块" class="headerlink" title="简单的模块"></a>简单的模块</h4><p>有了闭包的知识的话,下面的模块代码相信能很快看懂。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">myModule</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> message = <span class="string">"hello,Ontides"</span>;</div><div class="line"> <span class="keyword">var</span> resp = <span class="string">"Hi,"</span>;</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">sayHello</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(message);</div><div class="line"> }</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">getResponse</span>(<span class="params">name</span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(resp+name);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> {</div><div class="line"> <span class="attr">sayHello</span>: sayHello,</div><div class="line"> <span class="attr">getResponse</span>: getResponse</div><div class="line"> };</div><div class="line">}</div><div class="line"><span class="keyword">var</span> foo = myModule();</div><div class="line"></div><div class="line">foo.sayHello(); <span class="comment">//Hello,Ontides</span></div><div class="line">foo.getResponse(<span class="string">"Scott"</span>); <span class="comment">//Hi,Scott</span></div></pre></td></tr></table></figure></p>
<h4 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h4><p>将上面代码改变一下,可以实现单例模式:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> foo = (<span class="function"><span class="keyword">function</span> <span class="title">myModule</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> message = <span class="string">"hello,Ontides"</span>;</div><div class="line"> <span class="keyword">var</span> resp = <span class="string">"Hi,"</span>;</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">sayHello</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(message);</div><div class="line"> }</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">getResponse</span>(<span class="params">name</span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(resp+name);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> {</div><div class="line"> <span class="attr">sayHello</span>: sayHello,</div><div class="line"> <span class="attr">getResponse</span>: getResponse</div><div class="line"> };</div><div class="line">})();</div><div class="line">foo.sayHello(); <span class="comment">//Hello,Ontides</span></div><div class="line">foo.getResponse(<span class="string">"Scott"</span>); <span class="comment">//Hi,Scott</span></div></pre></td></tr></table></figure></p>
<h4 id="现代模块机制"><a href="#现代模块机制" class="headerlink" title="现代模块机制"></a>现代模块机制</h4><p>现在实现的模块通常需要一个模块管理器,其一般实现如下:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> myModules = (<span class="function"><span class="keyword">function</span> <span class="title">Manager</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="keyword">var</span> modules = {};</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">define</span>(<span class="params">name, deps, impl</span>)</span>{</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < deps.length; i++){</div><div class="line"> deps[i] = modules[deps[i]]; <span class="comment">//获取模块依赖</span></div><div class="line"> }</div><div class="line"> modules[name] = impl.apply(impl, deps);<span class="comment">//将依赖注入到定义的模块中</span></div><div class="line"> }</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">get</span>(<span class="params">name</span>)</span>{</div><div class="line"> <span class="keyword">return</span> modules[name];</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> {</div><div class="line"> <span class="attr">define</span>: define,</div><div class="line"> <span class="attr">get</span>: get</div><div class="line"> }</div><div class="line">})();</div></pre></td></tr></table></figure></p>
<p>这个模块管理器的实现中,<code>deps[i] = modules[deps[i]];</code>语句根据目标定义模块所需要的依赖从modules中查询,而<code>modules[name] = impl.apply(impl, deps);</code>语句则将目标依赖注入到定义模块中。</p>
<p>通过上面的模块管理器,可以轻松创建模块,管理模块之间的依赖。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line">myModules.define(<span class="string">"bar"</span>,[],<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">sayHello</span>(<span class="params">name</span>)</span>{</div><div class="line"> <span class="keyword">return</span> <span class="string">"Hello,"</span>+name;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span>{</div><div class="line"> <span class="attr">sayHello</span>: sayHello</div><div class="line"> }</div><div class="line">});</div><div class="line">myModules.define(<span class="string">"foo"</span>,[<span class="string">"bar"</span>],<span class="function"><span class="keyword">function</span>(<span class="params">bar</span>)</span>{</div><div class="line"> <span class="keyword">var</span> person = <span class="string">"Ontides"</span>;</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">awsome</span>(<span class="params"></span>)</span>{</div><div class="line"> <span class="built_in">console</span>.log(bar.sayHello(person).toUpperCase());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span>{</div><div class="line"> <span class="attr">awsome</span>: awsome</div><div class="line"> }</div><div class="line">});</div><div class="line"><span class="keyword">var</span> bar = myModules.get(<span class="string">"bar"</span>);</div><div class="line"><span class="keyword">var</span> foo = myModules.get(<span class="string">"foo"</span>);</div><div class="line"><span class="built_in">console</span>.log(bar.sayHello(<span class="string">"Ontides"</span>));<span class="comment">//Hello,Ontides</span></div><div class="line">foo.awsome();<span class="comment">//HELLO,ONTIDES</span></div></pre></td></tr></table></figure></p>
]]></content>
<summary type="html">
对于一门编程语言来说,作用域主要有两种模型,即词法作用域(Lexical Scope)和动态作用域(Dynamic Scope)。而词法作用域,即静态作用域(Static Scope),被目前JavaScript在内的大部分编程语言所采用,而动态作用域只有Bash脚本等少数编程语言在使用。
</summary>
<category term="JavaScript" scheme="https://ontides.github.io/tags/JavaScript/"/>
</entry>
<entry>
<title>JavaScript中的LHS查询和RHS查询</title>
<link href="https://ontides.github.io/2016/10/03/JavaScript%E4%B8%AD%E7%9A%84LHS%E6%9F%A5%E8%AF%A2%E5%92%8CRHS%E6%9F%A5%E8%AF%A2/"/>
<id>https://ontides.github.io/2016/10/03/JavaScript中的LHS查询和RHS查询/</id>
<published>2016-10-03T02:22:13.000Z</published>
<updated>2017-02-19T06:28:59.000Z</updated>
<content type="html"><![CDATA[<p>JavaScript中在预编译后执行代码时对变量的查询分为LHS(Left-Hand-Side)查询和RHS(Right-Hand-Side)查询。</p>
<hr>
<h3 id="一、LHS和RHS有什么区别?"><a href="#一、LHS和RHS有什么区别?" class="headerlink" title="一、LHS和RHS有什么区别?"></a>一、LHS和RHS有什么区别?</h3><p>这里的L和R是指赋值时候“=” 的左侧还是右侧,也就是说一个是做被赋值,一个是取值。 </p>
<p>正如下面这个例子,对变量a的引用是一个LHS引用,而对值2的引用是RHS引用</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">var a = 2;</div></pre></td></tr></table></figure>
<p>当然,在js中赋值操作不仅仅限于“=”这种显式赋值。还有一些隐式的赋值,比如:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">function foo(a){</div><div class="line"> ...</div><div class="line">}</div><div class="line">foo(2);</div></pre></td></tr></table></figure>
<p>这个例子中,执行函数foo的时候对foo进行了RHS引用,而对foo函数进行调用的时候隐藏着一个将2赋值给形式参数a的过程,即<code>a=2</code>,这里有对a的LHS引用。</p>
<h3 id="二、为什么要区分LHS引用和RHS引用?"><a href="#二、为什么要区分LHS引用和RHS引用?" class="headerlink" title="二、为什么要区分LHS引用和RHS引用?"></a>二、为什么要区分LHS引用和RHS引用?</h3><p>这两种不同的引用方式的在对没有声明的变量的处理上是不同的。而这个不同之处对于我们编写代码和分析JS引擎报的错误是很有用处的。</p>
<ul>
<li><p>当对一个变量执行RHS查询时,如果遍历该变量所在处的词法作用域未能找到这个变量,JS引擎就会抛出 ReferenceError 错误如果成功查询到了这个变量,但是对这个变量执行不合理操作,比如对一个非数组的变量执行下标取值,JS引擎就会抛出 TypeError 错误。</p>
</li>
<li><p>当对一个变量执行LHS查询时,同样在遍历作用域后无法找到该变量,在非ES5的严格模式下,系统就会自动在全局作用域中创建一个同名变量,并将引用转移到该新建的全局变量中。而在ES5的严格模式下,LHS查询失败时JS引擎会抛出一个同RHS一样的 ReferenceError 错误。</p>
</li>
</ul>
<p>因此,对LHS查询和RHS查询的仔细区分和理解无论是对JS执行过程本身的理解还是分析错误都是有所好处的。</p>
]]></content>
<summary type="html">
JavaScript中在预编译后执行代码时对变量的查询分为LHS(Left-Hand-Side)查询和RHS(Right-Hand-Side)查询。
</summary>
<category term="JavaScript" scheme="https://ontides.github.io/tags/JavaScript/"/>
</entry>
</feed>