-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
605 lines (337 loc) · 691 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>GeekLeng</title>
<subtitle>Front-end Dev Engineer</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://geek-lhj.github.io/"/>
<updated>2020-04-11T08:04:53.088Z</updated>
<id>https://geek-lhj.github.io/</id>
<author>
<name>GeekLeng</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>JavaScript设计模式设计原则</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/</id>
<published>2019-10-10T00:15:01.000Z</published>
<updated>2020-04-11T08:04:53.088Z</updated>
<content type="html"><![CDATA[<p>JavaScript设计模式设计的三大设计原则:单一职责原则(SRP)、最少知识原则(LKP)、开放封闭原则(OCP);</p><a id="more"></a><h2 id="三-设计原则"><a href="#三-设计原则" class="headerlink" title="三. 设计原则"></a>三. 设计原则</h2><h3 id="3-1-单一职责原则-SRP"><a href="#3-1-单一职责原则-SRP" class="headerlink" title="3.1 单一职责原则(SRP)"></a>3.1 单一职责原则(SRP)</h3><blockquote><p>SRP 原则体现为:一个对象(方法)只做一件事情;</p></blockquote><p>单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏;</p><h4 id="3-1-1-设计模式中的-SRP-原则"><a href="#3-1-1-设计模式中的-SRP-原则" class="headerlink" title="3.1.1 设计模式中的 SRP 原则"></a>3.1.1 设计模式中的 SRP 原则</h4><p>SRP 原则在很多设计模式中都有着广泛的运用,例如代理模式、迭代器模式、单例模式和装饰者模式。</p><blockquote><ol><li>代理模式:图片预加载示例,增加虚拟代理的方式,把预加载图片的职责放到代理对象中,而本体仅仅负责往页面中添加 img 标签,这也是它最原始的职责;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// myImage 负责往页面中添加 img 标签:</span></span><br><span class="line"><span class="keyword">var</span> myImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> imgNode = <span class="built_in">document</span>.createElement( <span class="string">'img'</span> );</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( imgNode );</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>{</span><br><span class="line"> imgNode.src = src;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="comment">// proxyImage 负责预加载图片,并在预加载完成之后把请求交给本体 myImage:</span></span><br><span class="line"><span class="keyword">var</span> proxyImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> img = <span class="keyword">new</span> Image;</span><br><span class="line"> img.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> myImage.setSrc( <span class="keyword">this</span>.src );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>{</span><br><span class="line"> myImage.setSrc( <span class="string">'file:// /C:/Users/svenzeng/Desktop/loading.gif'</span> );</span><br><span class="line"> img.src = src;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line">proxyImage.setSrc( <span class="string">'http:// imgcache.qq.com/music/photo/000GGDys0yA0Nk.jpg'</span> );</span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>迭代器模式,实例:先遍历一个集合,然后往页面中添加一些 div,这些 div 的 innerHTML 分别对应集合里的元素;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a. 基本实现</span></span><br><span class="line"><span class="keyword">var</span> appendDiv = <span class="function"><span class="keyword">function</span>(<span class="params"> data </span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = data.length; i < l; i++ ){</span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = data[ i ];</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">appendDiv( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span> ] );</span><br><span class="line"><span class="comment">// b. SRP 原则实现</span></span><br><span class="line"><span class="keyword">var</span> each = <span class="function"><span class="keyword">function</span>(<span class="params"> obj, callback </span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> value, i = <span class="number">0</span>, length = obj.length, isArray = isArraylike( obj ); <span class="comment">// isArraylike 函数未实现,可以翻阅 jQuery 源代码</span></span><br><span class="line"> <span class="keyword">if</span> ( isArray ) { <span class="comment">// 迭代类数组</span></span><br><span class="line"> <span class="keyword">for</span> ( ; i < length; i++ ) {</span><br><span class="line"> callback.call( obj[ i ], i, obj[ i ] );</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">for</span> ( i <span class="keyword">in</span> obj ) { <span class="comment">// 迭代 object 对象</span></span><br><span class="line"> value = callback.call( obj[ i ], i, obj[ i ] );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> obj;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> appendDiv = <span class="function"><span class="keyword">function</span>(<span class="params"> data </span>)</span>{</span><br><span class="line"> each( data, <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = n;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> });</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">appendDiv( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span> ] );</span><br><span class="line">appendDiv({<span class="attr">a</span>:<span class="number">1</span>,<span class="attr">b</span>:<span class="number">2</span>,<span class="attr">c</span>:<span class="number">3</span>,<span class="attr">d</span>:<span class="number">4</span>} );</span><br></pre></td></tr></table></figure><blockquote><ol start="3"><li>单例模式:惰性单例创建唯一一个登录窗 div 的示例;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a. 基本实现</span></span><br><span class="line"><span class="keyword">var</span> createLoginLayer = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> div;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !div ){</span><br><span class="line"> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = <span class="string">'我是登录浮窗'</span>;</span><br><span class="line"> div.style.display = <span class="string">'none'</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> div;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="comment">// b. SRP 原则实现</span></span><br><span class="line"><span class="keyword">var</span> getSingle = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{ <span class="comment">// 获取单例</span></span><br><span class="line"> <span class="keyword">var</span> result;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> result || ( result = fn .apply(<span class="keyword">this</span>, <span class="built_in">arguments</span> ) );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> createLoginLayer = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 创建登录浮窗</span></span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = <span class="string">'我是登录浮窗'</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> <span class="keyword">return</span> div;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> createSingleLoginLayer = getSingle( createLoginLayer );</span><br><span class="line"><span class="keyword">var</span> loginLayer1 = createSingleLoginLayer();</span><br><span class="line"><span class="keyword">var</span> loginLayer2 = createSingleLoginLayer();</span><br><span class="line">alert ( loginLayer1 === loginLayer2 ); <span class="comment">// 输出: true</span></span><br></pre></td></tr></table></figure><blockquote><ol start="4"><li>装饰者模式:装饰函数实现;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.after = <span class="function"><span class="keyword">function</span>(<span class="params"> afterfn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> __self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ret = __self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> afterfn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="3-1-2-何时应该分离职责"><a href="#3-1-2-何时应该分离职责" class="headerlink" title="3.1.2 何时应该分离职责"></a>3.1.2 何时应该分离职责</h4><p>要明确的是,并不是所有的职责都应该一一分离。一方面,如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们;另一方面,职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们,在代码需要重构的时候再进行分离也不迟。</p><h4 id="3-1-3-违反-SRP-原则"><a href="#3-1-3-违反-SRP-原则" class="headerlink" title="3.1.3 违反 SRP 原则"></a>3.1.3 违反 SRP 原则</h4><p>在常规思维中,总是习惯性地把一组相关的行为放到一起,如何正确地分离职责不是一件容易的事情。一方面,我们受设计原则的指导, 另一方面, 我们未必要在任何时候都一成不变地遵守原则;在方便性与稳定性之间要有一些取舍。具体是选择方便性还是稳定性,并没有标准答案,而是要取决于具体的应用环境。</p><h4 id="3-1-4-SRP-原则的优缺点"><a href="#3-1-4-SRP-原则的优缺点" class="headerlink" title="3.1.4 SRP 原则的优缺点"></a>3.1.4 SRP 原则的优缺点</h4><p>SRP 原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。但 SRP 原则也有一些缺点,最明显的是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。</p><h3 id="3-2-最少知识原则-LKP"><a href="#3-2-最少知识原则-LKP" class="headerlink" title="3.2 最少知识原则(LKP)"></a>3.2 最少知识原则(LKP)</h3><blockquote><p>最少知识原则( LKP):一个软件实体应当尽可能少地与其他实体发生相互作用;</p></blockquote><h4 id="3-2-1-减少对象之间的联系"><a href="#3-2-1-减少对象之间的联系" class="headerlink" title="3.2.1 减少对象之间的联系"></a>3.2.1 减少对象之间的联系</h4><p>最少知识原则要求在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三者对象来转发这些请求。</p><h4 id="3-2-2-设计模式中的最少知识原则"><a href="#3-2-2-设计模式中的最少知识原则" class="headerlink" title="3.2.2 设计模式中的最少知识原则"></a>3.2.2 设计模式中的最少知识原则</h4><p>最少知识原则在设计模式中体现得最多的地方是中介者模式和外观模式,如下所示:</p><blockquote><ol><li>中介者模式:</li></ol></blockquote><p>如博彩公司示例,博彩公司作为中介,每个人都只和博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就赔给博彩公司。中介者模式很好地体现了最少知识原则,通过增加一个中介者对象,让所有的相关对象都通过中介者对象来通信,而不是互相引用。所以,当一个对象发生改变时,只需要通知中介者对象即可。</p><blockquote><ol start="2"><li>外观模式:主要是为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使子系统更加容易使用;</li></ol></blockquote><p><img src="/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/%E5%A4%96%E8%A7%82%E6%A8%A1%E5%BC%8F.png" alt="外观模式"></p><p>外观模式的作用是对客户屏蔽一组子系统的复杂性,外观模式对客户提供一个简单易用的高层接口,高层接口会把客户的请求转发给子系统来完成具体的功能实现。大多数客户都可以通过请求外观接口来达到访问子系统的目的,如果外观不能满足客户的个性化需求,那么客户也可以选择越过外观来直接访问子系统;</p><h4 id="3-2-3-封装在最少知识原则中的体现"><a href="#3-2-3-封装在最少知识原则中的体现" class="headerlink" title="3.2.3 封装在最少知识原则中的体现"></a>3.2.3 封装在最少知识原则中的体现</h4><p>封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细节隐藏起来,只暴露必要的接口 API 供外界访问。对象之间难免产生联系,当一个对象必须引用另外一个对象的时候,我们可以让对象只暴露必要的接口,让对象之间的联系限制在最小的范围之内。把变量的可见性限制在一个尽可能小的范围内,这个变量对其他不相关模块的影响就越小,变量被改写和发生冲突的机会也越小。这也是广义的最少知识原的一种体现。</p><blockquote><p>实例:具有缓存效果的计算乘积的函数;</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> mult = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> cache = {};</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> );</span><br><span class="line"> <span class="keyword">if</span> ( cache[ args ] ){</span><br><span class="line"> <span class="keyword">return</span> cache[ args ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){</span><br><span class="line"> a = a * <span class="built_in">arguments</span>[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cache[ args ] = a;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line">mult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ); <span class="comment">// 输出: 6</span></span><br></pre></td></tr></table></figure><h3 id="3-3-开放封闭原则-OCP"><a href="#3-3-开放封闭原则-OCP" class="headerlink" title="3.3 开放封闭原则(OCP)"></a>3.3 开放封闭原则(OCP)</h3><blockquote><p>开放封闭原则( OCP):软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改;</p></blockquote><p>开放封闭原则的思想:当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。</p><h4 id="3-3-1-扩展-window-onload-函数"><a href="#3-3-1-扩展-window-onload-函数" class="headerlink" title="3.3.1 扩展 window.onload 函数"></a>3.3.1 扩展 window.onload 函数</h4><p>通过增加代码,而不是修改原代码的方式,来给 <code>window.onload</code> 函数添加新的功能,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用装饰函数实现函数功能扩展</span></span><br><span class="line"><span class="built_in">Function</span>.prototype.after = <span class="function"><span class="keyword">function</span>(<span class="params"> afterfn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> __self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ret = __self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> afterfn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="built_in">window</span>.onload = ( <span class="built_in">window</span>.onload || <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{} ).after(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="built_in">document</span>.getElementsByTagName( <span class="string">'*'</span> ).length );</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h4 id="3-3-2-用对象的多态性消除条件分支"><a href="#3-3-2-用对象的多态性消除条件分支" class="headerlink" title="3.3.2 用对象的多态性消除条件分支"></a>3.3.2 用对象的多态性消除条件分支</h4><p>过多的条件分支语句是造成程序违反开放封闭原则的一个常见原因,每当需要增加一个新的 if 语句时,都要被迫改动原函数。因此当一大片的 <code>if-else</code> 或者 <code>swtich-case</code> 语句时,第一时间考虑能否利用对象的多态性来重构代码,例如让动物发出叫声的例子,每增加一种动物,就需要改动 <code>makeSound</code> 函数的内部实现:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a. 使用 if-else 基本实现</span></span><br><span class="line"><span class="keyword">var</span> makeSound = <span class="function"><span class="keyword">function</span>(<span class="params"> animal </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( animal <span class="keyword">instanceof</span> Duck ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'嘎嘎嘎'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( animal <span class="keyword">instanceof</span> Chicken ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'咯咯咯'</span> );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> Duck = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line"><span class="keyword">var</span> Chicken = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line">makeSound( <span class="keyword">new</span> Duck() ); <span class="comment">// 输出:嘎嘎嘎</span></span><br><span class="line">makeSound( <span class="keyword">new</span> Chicken() ); <span class="comment">// 输出:咯咯咯</span></span><br><span class="line"><span class="comment">// b. 利用对象的多态性重构代码</span></span><br><span class="line"><span class="keyword">var</span> makeSound = <span class="function"><span class="keyword">function</span>(<span class="params"> animal </span>)</span>{</span><br><span class="line"> animal.sound();</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> Duck = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line">Duck.prototype.sound = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'嘎嘎嘎'</span> );</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> Chicken = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line">Chicken.prototype.sound = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'咯咯咯'</span> );</span><br><span class="line">};</span><br><span class="line">makeSound( <span class="keyword">new</span> Duck() ); <span class="comment">// 嘎嘎嘎</span></span><br><span class="line">makeSound( <span class="keyword">new</span> Chicken() ); <span class="comment">// 咯咯咯</span></span><br></pre></td></tr></table></figure><h4 id="3-3-3-找出变化的地方"><a href="#3-3-3-找出变化的地方" class="headerlink" title="3.3.3 找出变化的地方"></a>3.3.3 找出变化的地方</h4><p>开放封闭原则是一个看起来比较虚幻的原则,并没有实际的模板教导怎样地实现它;但开发中能找到一些让程序尽量遵守开放封闭原则的规律,最明显的就是找出程序中将要发生变化的地方,然后把变化封装起来。通过封装变化的方式,可以把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,只需要替换那些容易变化的部分,而稳定的部分是不需要改变的;</p><h4 id="3-3-4-设计模式中的开放封闭原则"><a href="#3-3-4-设计模式中的开放封闭原则" class="headerlink" title="3.3.4 设计模式中的开放封闭原则"></a>3.3.4 设计模式中的开放封闭原则</h4><p>开放封闭原则在设计模式中应用很广泛,如之前的装饰者模式示例,还有发布订阅模式、模板方法模式、策略模式、代理模式、职责链模式,如下所示:</p><blockquote><ol><li>发布订阅模式</li></ol></blockquote><p>发布订阅模式用来降低多个对象之间的依赖关系,它可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。当有新的订阅者出现时,发布者的代码不需要进行任何修改;同样当发布者需要改变时,也不会影响到之前的订阅者。</p><blockquote><ol start="2"><li>模板方法模式</li></ol></blockquote><p>模板方法模式是一种典型的通过封装变化来提高系统扩展性的设计模式,在一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以把这部分逻辑抽出来放到父类的模板方法里面;而子类的方法具体怎么实现则是可变的,于是把这部分变化的逻辑封装到子类中。通过增加新的子类,便能给系统增加新的功能,并不需要改动抽象父类以及其他的子类,这也是符合开放封闭原则的。</p><blockquote><ol start="3"><li>策略模式</li></ol></blockquote><p>策略模式和模板方法模式在大多数情况下可以相互替换使用,其中模板方法模式基于继承的思想,而策略模式则偏重于组合和委托。策略模式将各种算法都封装成单独的策略类,这些策略类可以被交换使用。策略和使用策略的客户代码可以分别独立进行修改而互不影响。我们增加一个新的策略类也非常方便,完全不用修改之前的代码。</p><blockquote><ol start="4"><li>代理模式</li></ol></blockquote><p>代理模式的图片预加载示例中,代理函数负责图片预加载,在图片预加载完成之后,再将请求转交给原来的 <code>myImage</code> 函数, <code>myImage</code> 在这个过程中不需要任何改动,预加载图片的功能和给图片设置 src 的功能被隔离在两个函数里,它们可以单独改变而互不影响。 <code>myImage</code> 不知晓代理的存在,它可以继续专注于自己的职责——给图片设置 <code>src</code> 属性;</p><blockquote><ol start="5"><li>职责链模式</li></ol></blockquote><p>职责链模式的订单示例中,当增加一个新类型的订单函数时,不需要改动原有的订单函数代码,只需要在链条中增加一个新的节点。</p><h4 id="3-3-5-开放封闭原则的相对性"><a href="#3-3-5-开放封闭原则的相对性" class="headerlink" title="3.3.5 开放封闭原则的相对性"></a>3.3.5 开放封闭原则的相对性</h4><p>实际开发中,让程序保持完全封闭是不容易做到,并且有一些代码是无论如何也不能完全封闭的,总会存在一些无法对其封闭的变化。因此我们可以做到的有下面两点:</p><ul><li>挑选出最容易发生变化的地方,然后构造抽象来封闭这些变化。</li><li>在不可避免发生修改的时候,尽量修改那些相对容易修改的地方。如一个开源库,修改它提供的配置文件,总比修改它的源代码来得简单。</li></ul><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>JavaScript设计模式设计的三大设计原则:单一职责原则(SRP)、最少知识原则(LKP)、开放封闭原则(OCP);</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="设计原则" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"/>
</entry>
<entry>
<title>JavaScript设计模式(十四)适配器模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-14-%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-14-%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:14:01.000Z</published>
<updated>2020-04-11T08:05:11.851Z</updated>
<content type="html"><![CDATA[<p>适配器模式的作用是解决两个软件实体间的接口不兼容的问题;</p><a id="more"></a><h3 id="14-适配器模式"><a href="#14-适配器模式" class="headerlink" title="14 适配器模式"></a>14 适配器模式</h3><p>适配器模式的作用是解决两个软件实体间的接口不兼容的问题;</p><h4 id="14-1-适配器模式实例"><a href="#14-1-适配器模式实例" class="headerlink" title="14.1 适配器模式实例"></a>14.1 适配器模式实例</h4><p>在之前的地图的例子中,使用多态的思想,实现同一个操作在不同的对象产生不同的结果,如下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> googleMap = {</span><br><span class="line"> show: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始渲染谷歌地图'</span> );}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> baiduMap = {</span><br><span class="line"> show: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始渲染百度地图'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> renderMap = <span class="function"><span class="keyword">function</span>(<span class="params"> map </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( map.show <span class="keyword">instanceof</span> <span class="built_in">Function</span> ){ map.show(); }</span><br><span class="line">};</span><br><span class="line">renderMap( googleMap ); <span class="comment">// 输出:开始渲染谷歌地图</span></span><br><span class="line">renderMap( baiduMap ); <span class="comment">// 输出:开始渲染百度地图</span></span><br></pre></td></tr></table></figure><p>上面代码运行的关键在于 googleMap 和 baiduMap 提供了一致的 <code>show</code> 方法,若第三方接口方法不在约定中(如不是 <code>show</code> 方法),那么需要增加一个适配函数来解决问题,如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> googleMap = {</span><br><span class="line"> show: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始渲染谷歌地图'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> baiduMap = {</span><br><span class="line"> display: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始渲染百度地图'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> baiduMapAdapter = {</span><br><span class="line"> show: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">return</span> baiduMap.display();}</span><br><span class="line">};</span><br><span class="line">renderMap( googleMap ); <span class="comment">// 输出:开始渲染谷歌地图</span></span><br><span class="line">renderMap( baiduMapAdapter ); <span class="comment">// 输出:开始渲染百度地图</span></span><br></pre></td></tr></table></figure><h4 id="14-2-适配器模式小结"><a href="#14-2-适配器模式小结" class="headerlink" title="14.2 适配器模式小结"></a>14.2 适配器模式小结</h4><p>适配器模式是一对相对简单的模式,在提到的设计模式中,有一些模式跟适配器模式的结构非常相似,比如装饰者模式、代理模式和外观模式。这几种模式都属于“<code>包装模式</code>”,都是由一个对象来包装另一个对象,它们的关键区别是模式的意图;</p><p>下面对这几种 <code>包装模式</code> 进行介绍:</p><ul><li>适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。</li><li>装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次。</li><li>外观模式的作用和适配器比较相似,可以把外观模式看成一组对象的适配器,但外观模式最显著的特点是定义了一个新的接口。</li></ul><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>适配器模式的作用是解决两个软件实体间的接口不兼容的问题;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="适配器模式" scheme="https://geek-lhj.github.io/tags/%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(十三)状态模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-13-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-13-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:13:01.000Z</published>
<updated>2020-04-11T08:05:19.661Z</updated>
<content type="html"><![CDATA[<p>状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变;</p><a id="more"></a><h3 id="13-状态模式"><a href="#13-状态模式" class="headerlink" title="13 状态模式"></a>13 状态模式</h3><p>状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变;</p><h4 id="13-1-初识状态模式-电灯程序"><a href="#13-1-初识状态模式-电灯程序" class="headerlink" title="13.1 初识状态模式-电灯程序"></a>13.1 初识状态模式-电灯程序</h4><blockquote><ol><li>开关控制电灯的打开关闭状态;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Light = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.state = <span class="string">'off'</span>; <span class="comment">// 给电灯设置初始状态 off</span></span><br><span class="line"> <span class="keyword">this</span>.button = <span class="literal">null</span>; <span class="comment">// 电灯开关按钮</span></span><br><span class="line">};</span><br><span class="line"><span class="comment">// 定义 Light.prototype.init 方法</span></span><br><span class="line">Light.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> button = <span class="built_in">document</span>.createElement( <span class="string">'button'</span> ), self = <span class="keyword">this</span>;</span><br><span class="line"> button.innerHTML = <span class="string">'开关'</span>;</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="built_in">document</span>.body.appendChild( button );</span><br><span class="line"> <span class="keyword">this</span>.button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ self.buttonWasPressed(); }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 开关按下操作</span></span><br><span class="line">Light.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( <span class="keyword">this</span>.state === <span class="string">'off'</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'开灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.state = <span class="string">'on'</span>;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( <span class="keyword">this</span>.state === <span class="string">'on'</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'关灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.state = <span class="string">'off'</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> light = <span class="keyword">new</span> Light();</span><br><span class="line">light.init();</span><br></pre></td></tr></table></figure><p>上面的例子使用一个变量 <code>state</code> 来记录按钮的当前状态,在事件发生时,再根据这个状态来决定下一步的行为;不过当电灯的状态增加时(如强光,弱光状态等),需要手动修改 <code>buttonWasPressed</code> 函数,这样就是违反程序的<code>开放-封闭原则</code>,状态之间的切换关系是在 <code>buttonWasPressed</code> 函数增加 <code>if-else</code> 判断,当状态很多时, <code>buttonWasPressed</code> 函数会更加难以阅读和维护;</p><blockquote><ol start="2"><li>状态模式改进电灯程序:</li></ol></blockquote><p>状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以 <code>button</code> 被按下的的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为;</p><p><img src="/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-13-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%E7%94%B5%E7%81%AF.png" alt="状态模式电灯"></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 首先将定义 3 个状态类,分别是 offLightState(关灯状态)、WeakLightState(弱光状态)、 strongLightState(强光状态) ,每个类都有一个原型方法 buttonWasPressed,代表在各自状态下点击按钮发送的行为;</span></span><br><span class="line"><span class="keyword">var</span> OffLightState = <span class="function"><span class="keyword">function</span>(<span class="params"> light </span>)</span>{ <span class="keyword">this</span>.light = light; };</span><br><span class="line">OffLightState.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'弱光'</span> ); <span class="comment">// offLightState 对应的行为</span></span><br><span class="line"> <span class="keyword">this</span>.light.setState( <span class="keyword">this</span>.light.weakLightState ); <span class="comment">// 切换状态到 weakLightState</span></span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> WeakLightState = <span class="function"><span class="keyword">function</span>(<span class="params"> light </span>)</span>{ <span class="keyword">this</span>.light = light; };</span><br><span class="line">WeakLightState.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'强光'</span> ); <span class="comment">// weakLightState 对应的行为</span></span><br><span class="line"> <span class="keyword">this</span>.light.setState( <span class="keyword">this</span>.light.strongLightState ); <span class="comment">// 切换状态到 strongLightState</span></span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> StrongLightState = <span class="function"><span class="keyword">function</span>(<span class="params"> light </span>)</span>{ <span class="keyword">this</span>.light = light; };</span><br><span class="line">StrongLightState.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'关灯'</span> ); <span class="comment">// strongLightState 对应的行为</span></span><br><span class="line"> <span class="keyword">this</span>.light.setState( <span class="keyword">this</span>.light.offLightState ); <span class="comment">// 切换状态到 offLightState</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// Light 类:在构造函数里为每个状态类都创建一个状态对象</span></span><br><span class="line"><span class="keyword">var</span> Light = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.offLightState = <span class="keyword">new</span> OffLightState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.weakLightState = <span class="keyword">new</span> WeakLightState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.strongLightState = <span class="keyword">new</span> StrongLightState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// Light 初始化方法</span></span><br><span class="line">Light.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> button = <span class="built_in">document</span>.createElement( <span class="string">'button'</span> ), self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="built_in">document</span>.body.appendChild( button );</span><br><span class="line"> <span class="keyword">this</span>.button.innerHTML = <span class="string">'开关'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.offLightState; <span class="comment">// 设置当前电灯状态为关灯</span></span><br><span class="line"> <span class="keyword">this</span>.button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> self.currState.buttonWasPressed(); <span class="comment">// 按钮被按下的事件请求委托给当前持有的状态对象去执行</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 实现 Light.prototype.setState 方法:状态对象可以通过这个方法来切换 light 对象的状态,状态的切换规律事先被完好定义在各个状态类中;</span></span><br><span class="line">Light.prototype.setState = <span class="function"><span class="keyword">function</span>(<span class="params"> newState </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.currState = newState;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 测试效果</span></span><br><span class="line"><span class="keyword">var</span> light = <span class="keyword">new</span> Light();</span><br><span class="line">light.init();</span><br></pre></td></tr></table></figure><p>状态模式可以使每一种状态和它对应的行为之间的关系局部化,这些行为被分散和封装在各自对应的状态类之中,便于阅读和管理代码;状态之间的切换都被分布在状态类内部,这使得我们无需编写过多的 <code>if-else</code> 条件分支语言来控制状态之间的转换;</p><p>上面例子中若需要为 <code>light</code> 对象增加一种新的状态时,只需要增加一个新的状态类,再稍稍改变一些现有的代码即可;若现在 light 对象多了一种超强光的状态,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 先增加 SuperStrongLightState 类;</span></span><br><span class="line"><span class="keyword">var</span> SuperStrongLightState = <span class="function"><span class="keyword">function</span>(<span class="params"> light </span>)</span>{ <span class="keyword">this</span>.light = light; };</span><br><span class="line">SuperStrongLightState.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'关灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.light.setState( <span class="keyword">this</span>.light.offLightState );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 再在 Light 构造函数里新增一个 superStrongLightState 对象:</span></span><br><span class="line"><span class="keyword">var</span> Light = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.offLightState = <span class="keyword">new</span> OffLightState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.weakLightState = <span class="keyword">new</span> WeakLightState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.strongLightState = <span class="keyword">new</span> StrongLightState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.superStrongLightState = <span class="keyword">new</span> SuperStrongLightState( <span class="keyword">this</span> ); <span class="comment">// 新增 superStrongLightState 对象</span></span><br><span class="line"> <span class="keyword">this</span>.button = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 最后改变状态类之间的切换规则,从 StrongLightState---->OffLightState 变为 StrongLightState---->SuperStrongLightState ---->OffLightState:</span></span><br><span class="line">StrongLightState.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'超强光'</span> ); <span class="comment">// strongLightState 对应的行为</span></span><br><span class="line"> <span class="keyword">this</span>.light.setState( <span class="keyword">this</span>.light.superStrongLightState ); <span class="comment">// 切换状态到 offLightState</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="13-2-状态模式的通用结构"><a href="#13-2-状态模式的通用结构" class="headerlink" title="13.2 状态模式的通用结构"></a>13.2 状态模式的通用结构</h4><p>在电灯的例子中,首先定义了 <code>Light</code> 类, Light类在这里也被称为上下文( <code>Context</code> );随后在 <code>Light</code> 的构造函数中创建每一个状态类的实例对象, <code>Context</code> 将持有这些状态对象的引用,以便把请求委托给状态对象;用户的请求,即点击 <code>button</code> 的动作也是实现在 <code>Context</code> 中的,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Light = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.offLightState = <span class="keyword">new</span> OffLightState( <span class="keyword">this</span> ); <span class="comment">// 持有状态对象的引用</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">Light.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> button = <span class="built_in">document</span>.createElement( <span class="string">'button'</span> ), self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="built_in">document</span>.body.appendChild( button );</span><br><span class="line"> <span class="keyword">this</span>.button.innerHTML = <span class="string">'开关'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.offLightState; <span class="comment">// 设置默认初始状态</span></span><br><span class="line"> <span class="keyword">this</span>.button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 定义用户的请求动作 </span></span><br><span class="line"> self.currState.buttonWasPressed();</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>接下来要编写各种状态类, <code>light</code> 对象被传入状态类的构造函数,状态对象也需要持有 <code>light</code> 对象的引用,以便调用 <code>light</code> 中的方法或者直接操作 <code>light</code> 对象:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> OffLightState = <span class="function"><span class="keyword">function</span>(<span class="params"> light </span>)</span>{ <span class="keyword">this</span>.light = light; };</span><br><span class="line">OffLightState.prototype.buttonWasPressed = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'弱光'</span> );</span><br><span class="line"> <span class="keyword">this</span>.light.setState( <span class="keyword">this</span>.light.weakLightState );</span><br><span class="line">};</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h4 id="13-3-状态模式示例——文件上传"><a href="#13-3-状态模式示例——文件上传" class="headerlink" title="13.3 状态模式示例——文件上传"></a>13.3 状态模式示例——文件上传</h4><p>文件上传中,包括有扫描、正在上传、暂停、上传成功、上传失败这几种状态,点击同一个按钮,在上传中和<br>暂停状态下的行为表现是不一样的,如上传中,点击按钮暂停,暂停中,点击按钮继续播放;</p><p>文件上传中,设置 暂停/继续 和 删除两个按钮,点击这两个按钮的发生行为如下:</p><ul><li>文件在扫描状态中,是不能进行任何操作的,既不能暂停也不能删除文件,只能等待扫描完成。扫描完成之后,根据文件的 md5 值判断,若确认该文件已经存在于服务器,则直接跳到上传完成状态。如果该文件的大小超过允许上传的最大值,或者该文件已经损坏,则跳往上传失败状态。剩下的情况下才进入上传中状态;</li><li>上传过程中可以点击暂停按钮来暂停上传,暂停后点击同一个按钮会继续上传;</li><li>扫描和上传过程中,点击删除按钮无效,只有在暂停、上传完成、上传失败之后,才能删除文件;</li></ul><blockquote><ol><li>文件上传基本实现:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 上传是一个异步的过程,定义全局函数 window.external.upload 来通知上传进度,把当前的文件状态作为参数state 传入函数中</span></span><br><span class="line"><span class="built_in">window</span>.external.upload = <span class="function"><span class="keyword">function</span>(<span class="params"> state </span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( state ); <span class="comment">// 可能为 sign、 uploading、 done、 error</span></span><br><span class="line">};</span><br><span class="line"><span class="comment">// 上传的插件对象</span></span><br><span class="line"><span class="keyword">var</span> plugin = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> plugin = <span class="built_in">document</span>.createElement( <span class="string">'embed'</span> );</span><br><span class="line"> plugin.style.display = <span class="string">'none'</span>;</span><br><span class="line"> plugin.type = <span class="string">'application/txftn-webkit'</span>;</span><br><span class="line"> plugin.sign = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始文件扫描'</span> ); }</span><br><span class="line"> plugin.pause = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'暂停文件上传'</span> ); };</span><br><span class="line"> plugin.uploading = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始文件上传'</span> ); };</span><br><span class="line"> plugin.del = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'删除文件上传'</span> ); }</span><br><span class="line"> plugin.done = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'文件上传完成'</span> ); }</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( plugin );</span><br><span class="line"> <span class="keyword">return</span> plugin;</span><br><span class="line">})();</span><br><span class="line"><span class="comment">// 定义控制上传过程的对象 Upload 类</span></span><br><span class="line"><span class="keyword">var</span> Upload = <span class="function"><span class="keyword">function</span>(<span class="params"> fileName </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.plugin = plugin;</span><br><span class="line"> <span class="keyword">this</span>.fileName = fileName;</span><br><span class="line"> <span class="keyword">this</span>.button1 = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">this</span>.button2 = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">this</span>.state = <span class="string">'sign'</span>; <span class="comment">// 设置初始状态为 waiting</span></span><br><span class="line">};</span><br><span class="line"><span class="comment">// 初始化 Upload 类函数</span></span><br><span class="line">Upload.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> that = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.dom = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> <span class="keyword">this</span>.dom.innerHTML =</span><br><span class="line"> <span class="string">'<span>文件名称:'</span>+ <span class="keyword">this</span>.fileName +<span class="string">'</span>\</span></span><br><span class="line"><span class="string"> <button data-action="button1">扫描中</button>\</span></span><br><span class="line"><span class="string"> <button data-action="button2">删除</button>'</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( <span class="keyword">this</span>.dom );</span><br><span class="line"> <span class="keyword">this</span>.button1 = <span class="keyword">this</span>.dom.querySelector( <span class="string">'[data-action="button1"]'</span> ); <span class="comment">// 第一个按钮</span></span><br><span class="line"> <span class="keyword">this</span>.button2 = <span class="keyword">this</span>.dom.querySelector( <span class="string">'[data-action="button2"]'</span> ); <span class="comment">// 第二个按钮</span></span><br><span class="line"> <span class="keyword">this</span>.bindEvent();</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 两个按钮分别绑定点击事件</span></span><br><span class="line">Upload.prototype.bindEvent = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.button1.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( self.state === <span class="string">'sign'</span> ){ <span class="comment">// 扫描状态下,任何操作无效</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'扫描中,点击无效...'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( self.state === <span class="string">'uploading'</span> ){ <span class="comment">// 上传中,点击切换到暂停</span></span><br><span class="line"> self.changeState( <span class="string">'pause'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( self.state === <span class="string">'pause'</span> ){ <span class="comment">// 暂停中,点击切换到上传中</span></span><br><span class="line"> self.changeState( <span class="string">'uploading'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( self.state === <span class="string">'done'</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'文件已完成上传, 点击无效'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( self.state === <span class="string">'error'</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'文件上传失败, 点击无效'</span> );</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">this</span>.button2.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( self.state === <span class="string">'done'</span> || self.state === <span class="string">'error'</span> || self.state === <span class="string">'pause'</span> ){</span><br><span class="line"> <span class="comment">// 上传完成、上传失败和暂停状态下可以删除</span></span><br><span class="line"> self.changeState( <span class="string">'del'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( self.state === <span class="string">'sign'</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'文件正在扫描中,不能删除'</span> );</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> ( self.state === <span class="string">'uploading'</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'文件正在上传中,不能删除'</span> );</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">};</span><br><span class="line"><span class="comment">// Upload.prototype.changeState 方法,负责切换状态之后的具体行为:</span></span><br><span class="line">Upload.prototype.changeState = <span class="function"><span class="keyword">function</span>(<span class="params"> state </span>)</span>{</span><br><span class="line"> <span class="keyword">switch</span>( state ){</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'sign'</span>:</span><br><span class="line"> <span class="keyword">this</span>.plugin.sign();</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'扫描中,任何操作无效'</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'uploading'</span>:</span><br><span class="line"> <span class="keyword">this</span>.plugin.uploading();</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'正在上传,点击暂停'</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'pause'</span>:</span><br><span class="line"> <span class="keyword">this</span>.plugin.pause();</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'已暂停,点击继续上传'</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'done'</span>:</span><br><span class="line"> <span class="keyword">this</span>.plugin.done();</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'上传完成'</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'error'</span>:</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'上传失败'</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'del'</span>:</span><br><span class="line"> <span class="keyword">this</span>.plugin.del();</span><br><span class="line"> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'删除完成'</span> );</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.state = state;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 测试上传文件</span></span><br><span class="line"><span class="keyword">var</span> uploadObj = <span class="keyword">new</span> Upload( <span class="string">'JavaScript 设计模式与开发实践'</span> );</span><br><span class="line">uploadObj.init();</span><br><span class="line"><span class="built_in">window</span>.external.upload = <span class="function"><span class="keyword">function</span>(<span class="params"> state </span>)</span>{ <span class="comment">// 插件调用 JavaScript 的方法</span></span><br><span class="line"> uploadObj.changeState( state );</span><br><span class="line">};</span><br><span class="line"><span class="built_in">window</span>.external.upload( <span class="string">'sign'</span> ); <span class="comment">// 文件开始扫描</span></span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">window</span>.external.upload( <span class="string">'uploading'</span> ); <span class="comment">// 1 秒后开始上传</span></span><br><span class="line">}, <span class="number">1000</span> );</span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">window</span>.external.upload( <span class="string">'done'</span> ); <span class="comment">// 5 秒后上传完成</span></span><br><span class="line">}, <span class="number">5000</span> );</span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>状态模式重构文件上传程序:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一步:提供 window.external.upload 函数</span></span><br><span class="line"><span class="built_in">window</span>.external.upload = <span class="function"><span class="keyword">function</span>(<span class="params"> state </span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( state ); <span class="comment">// 可能为 sign、 uploading、 done、 error</span></span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> plugin = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> plugin = <span class="built_in">document</span>.createElement( <span class="string">'embed'</span> );</span><br><span class="line"> plugin.style.display = <span class="string">'none'</span>;</span><br><span class="line"> plugin.type = <span class="string">'application/txftn-webkit'</span>;</span><br><span class="line"> plugin.sign = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始文件扫描'</span> ); }</span><br><span class="line"> plugin.pause = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'暂停文件上传'</span> ); };</span><br><span class="line"> plugin.uploading = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始文件上传'</span> ); };</span><br><span class="line"> plugin.del = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'删除文件上传'</span> ); }</span><br><span class="line"> plugin.done = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'文件上传完成'</span> ); }</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( plugin );</span><br><span class="line"> <span class="keyword">return</span> plugin;</span><br><span class="line">})();</span><br><span class="line"><span class="comment">// 第二步:改造 Upload 构造函数,在构造函数中为每种状态子类都创建一个实例对象</span></span><br><span class="line"><span class="keyword">var</span> Upload = <span class="function"><span class="keyword">function</span>(<span class="params"> fileName </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.plugin = plugin;</span><br><span class="line"> <span class="keyword">this</span>.fileName = fileName;</span><br><span class="line"> <span class="keyword">this</span>.button1 = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">this</span>.button2 = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">this</span>.signState = <span class="keyword">new</span> SignState( <span class="keyword">this</span> ); <span class="comment">// 设置初始状态为 waiting</span></span><br><span class="line"> <span class="keyword">this</span>.uploadingState = <span class="keyword">new</span> UploadingState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.pauseState = <span class="keyword">new</span> PauseState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.doneState = <span class="keyword">new</span> DoneState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.errorState = <span class="keyword">new</span> ErrorState( <span class="keyword">this</span> );</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.signState; <span class="comment">// 设置当前状态</span></span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第三步:实现 Upload.prototype.init 方法</span></span><br><span class="line">Upload.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> that = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.dom = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> <span class="keyword">this</span>.dom.innerHTML =</span><br><span class="line"> <span class="string">'<span>文件名称:'</span>+ <span class="keyword">this</span>.fileName +<span class="string">'</span>\</span></span><br><span class="line"><span class="string"> <button data-action="button1">扫描中</button>\</span></span><br><span class="line"><span class="string"> <button data-action="button2">删除</button>'</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( <span class="keyword">this</span>.dom );</span><br><span class="line"> <span class="keyword">this</span>.button1 = <span class="keyword">this</span>.dom.querySelector( <span class="string">'[data-action="button1"]'</span> );</span><br><span class="line"> <span class="keyword">this</span>.button2 = <span class="keyword">this</span>.dom.querySelector( <span class="string">'[data-action="button2"]'</span> );</span><br><span class="line"> <span class="keyword">this</span>.bindEvent();</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第四步:负责具体的按钮事件实现</span></span><br><span class="line">Upload.prototype.bindEvent = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.button1.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> self.currState.clickHandler1();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.button2.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> self.currState.clickHandler2();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">Upload.prototype.sign = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.plugin.sign();</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.signState;</span><br><span class="line">};</span><br><span class="line">Upload.prototype.uploading = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'正在上传,点击暂停'</span>;</span><br><span class="line"> <span class="keyword">this</span>.plugin.uploading();</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.uploadingState;</span><br><span class="line">};</span><br><span class="line">Upload.prototype.pause = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'已暂停,点击继续上传'</span>;</span><br><span class="line"> <span class="keyword">this</span>.plugin.pause();</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.pauseState;</span><br><span class="line">};</span><br><span class="line">Upload.prototype.done = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'上传完成'</span>;</span><br><span class="line"> <span class="keyword">this</span>.plugin.done();</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.doneState;</span><br><span class="line">};</span><br><span class="line">Upload.prototype.error = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.button1.innerHTML = <span class="string">'上传失败'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.errorState;</span><br><span class="line">};</span><br><span class="line">Upload.prototype.del = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.plugin.del();</span><br><span class="line"> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第五步:编写各个状态类的实现</span></span><br><span class="line"><span class="keyword">var</span> StateFactory = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> State = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line"> State.prototype.clickHandler1 = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'子类必须重写父类的 clickHandler1 方法'</span> );</span><br><span class="line"> }</span><br><span class="line"> State.prototype.clickHandler2 = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'子类必须重写父类的 clickHandler2 方法'</span> );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"> param </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> F = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadObj </span>)</span>{ <span class="keyword">this</span>.uploadObj = uploadObj; };</span><br><span class="line"> F.prototype = <span class="keyword">new</span> State();</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> param ){</span><br><span class="line"> F.prototype[ i ] = param[ i ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> F;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="keyword">var</span> SignState = StateFactory({</span><br><span class="line"> clickHandler1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'扫描中,点击无效...'</span> ); },</span><br><span class="line"> clickHandler2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'文件正在上传中,不能删除'</span> ); }</span><br><span class="line">});</span><br><span class="line"><span class="keyword">var</span> UploadingState = StateFactory({</span><br><span class="line"> clickHandler1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">this</span>.uploadObj.pause(); },</span><br><span class="line"> clickHandler2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'文件正在上传中,不能删除'</span> ); }</span><br><span class="line">});</span><br><span class="line"><span class="keyword">var</span> PauseState = StateFactory({</span><br><span class="line"> clickHandler1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">this</span>.uploadObj.uploading(); },</span><br><span class="line"> clickHandler2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">this</span>.uploadObj.del(); }</span><br><span class="line">});</span><br><span class="line"><span class="keyword">var</span> DoneState = StateFactory({</span><br><span class="line"> clickHandler1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'文件已完成上传, 点击无效'</span> ); },</span><br><span class="line"> clickHandler2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">this</span>.uploadObj.del(); }</span><br><span class="line">});</span><br><span class="line"><span class="keyword">var</span> ErrorState = StateFactory({</span><br><span class="line"> clickHandler1: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'文件上传失败, 点击无效'</span> ); },</span><br><span class="line"> clickHandler2: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">this</span>.uploadObj.del(); }</span><br><span class="line">});</span><br><span class="line"><span class="comment">// 最后测试</span></span><br><span class="line"><span class="keyword">var</span> uploadObj = <span class="keyword">new</span> Upload( <span class="string">'JavaScript 设计模式与开发实践'</span> );</span><br><span class="line">uploadObj.init();</span><br><span class="line"><span class="built_in">window</span>.external.upload = <span class="function"><span class="keyword">function</span>(<span class="params"> state </span>)</span>{ uploadObj[ state ](); };</span><br><span class="line"><span class="built_in">window</span>.external.upload( <span class="string">'sign'</span> );</span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">window</span>.external.upload( <span class="string">'uploading'</span> ); <span class="comment">// 1 秒后开始上传</span></span><br><span class="line">}, <span class="number">1000</span> );</span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">window</span>.external.upload( <span class="string">'done'</span> ); <span class="comment">// 5 秒后上传完成</span></span><br><span class="line">}, <span class="number">5000</span> );</span><br></pre></td></tr></table></figure><h4 id="13-4-状态模式的优缺点及性能优化点"><a href="#13-4-状态模式的优缺点及性能优化点" class="headerlink" title="13.4 状态模式的优缺点及性能优化点"></a>13.4 状态模式的优缺点及性能优化点</h4><blockquote><ol><li>状态模式的优点:</li></ol></blockquote><ul><li>状态模式定义了状态与行为之间的关系,并将它们封装在一个类里,通过增加新的状态类,很容易增加新的状态和转换;</li><li>避免 <code>Context</code> 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 <code>Context</code> 中原本过多的条件分支;</li><li>用对象代替字符串来记录当前状态,使得状态的切换更加一目了然;</li><li><code>Context</code> 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响;</li></ul><blockquote><ol start="2"><li>状态模式的缺点:</li></ol></blockquote><p>状态模式会在系统中定义许多状态类,并且生成许多对象;同时由于逻辑分散在状态类中,虽然减少了 <code>if-else</code>分支语句,但也造成了逻辑分散的问题;</p><blockquote><ol start="3"><li>状态模式性能优化点:</li></ol></blockquote><ul><li>有两种选择来管理 <code>state</code> 对象的创建和销毁,第一种是仅当 <code>state</code> 对象被需要时才创建并随后销毁,能有效的节省内存;另一种是一开始就创建好所有的状态对象,并且始终不销毁它们,适用于状态的改变很频繁的场景中;</li><li>本章的例子中,为每个 <code>Context</code> 对象都创建了一组 <code>state</code> 对象,实际上这些 <code>state</code> 对象之间是可以共享的,各 <code>Context</code> 对象可以共享一个 <code>state</code> 对象,这也是享元模式的应用场景之一;</li></ul><h4 id="13-5-状态模式和策略模式的关系"><a href="#13-5-状态模式和策略模式的关系" class="headerlink" title="13.5 状态模式和策略模式的关系"></a>13.5 状态模式和策略模式的关系</h4><p>状态模式和策略模式都封装了一系列的算法或者行为,它们的类图看起来来几乎一模一样,但在意图上有很大不同,因此它们是两种迥然不同的模式;策略模式和状态模式的相同点是都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行;区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以必须熟知这些策略类的作用,以便可以随时主动切换算法;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部,因此我们不需要了解这些细节;</p><h4 id="13-6-JavaScript-版本的状态机"><a href="#13-6-JavaScript-版本的状态机" class="headerlink" title="13.6 JavaScript 版本的状态机"></a>13.6 JavaScript 版本的状态机</h4><p>前面示例都是模拟传统面向对象语言的状态模式实现,为每种状态都定义一个状态子类,然后在 <code>Context</code> 中持有这些状态对象的引用,以便把 <code>currState</code> 设置为当前的状态对象;在 JavaScript 这种“无类”语言中,没有规定让状态对象一定要从类中创建而来。另外, JavaScript 可以非常方便地使用委托技术,并不需要事先让一个对象持有另一个对象。</p><p>下面的状态机选择了通过 <code>Function.prototype.call</code> 方法直接把请求委托给某个字面量对象来执行:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方式1</span></span><br><span class="line"><span class="keyword">var</span> Light = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.currState = FSM.off; <span class="comment">// 设置当前状态</span></span><br><span class="line"> <span class="keyword">this</span>.button = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">Light.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> button = <span class="built_in">document</span>.createElement( <span class="string">'button'</span> ), self = <span class="keyword">this</span>;</span><br><span class="line"> button.innerHTML = <span class="string">'已关灯'</span>;</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="built_in">document</span>.body.appendChild( button );</span><br><span class="line"> <span class="keyword">this</span>.button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> self.currState.buttonWasPressed.call( self ); <span class="comment">// 把请求委托给 FSM 状态机</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> FSM = {</span><br><span class="line"> off: {</span><br><span class="line"> buttonWasPressed: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'关灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.button.innerHTML = <span class="string">'下一次按我是开灯'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = FSM.on;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> on: {</span><br><span class="line"> buttonWasPressed: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'开灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.button.innerHTML = <span class="string">'下一次按我是关灯'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = FSM.off;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> light = <span class="keyword">new</span> Light();</span><br><span class="line">light.init();</span><br><span class="line"><span class="comment">// 方式2:利用下面的 delegate 函数来完成这个状态机编写。这是面向对象设计和闭包互换的一个例子,前者把变量保存为对象的属性,而后者把变量封闭在闭包形成的环境</span></span><br><span class="line"><span class="keyword">var</span> delegate = <span class="function"><span class="keyword">function</span>(<span class="params"> client, delegation </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> buttonWasPressed: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 将客户的操作委托给 delegation 对象</span></span><br><span class="line"> <span class="keyword">return</span> delegation.buttonWasPressed.apply( client, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> FSM = {</span><br><span class="line"> off: {</span><br><span class="line"> buttonWasPressed: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'关灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.button.innerHTML = <span class="string">'下一次按我是开灯'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.onState;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> on: {</span><br><span class="line"> buttonWasPressed: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'开灯'</span> );</span><br><span class="line"> <span class="keyword">this</span>.button.innerHTML = <span class="string">'下一次按我是关灯'</span>;</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.offState;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> Light = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.offState = delegate( <span class="keyword">this</span>, FSM.off );</span><br><span class="line"> <span class="keyword">this</span>.onState = delegate( <span class="keyword">this</span>, FSM.on );</span><br><span class="line"> <span class="keyword">this</span>.currState = <span class="keyword">this</span>.offState; <span class="comment">// 设置初始状态为关闭状态</span></span><br><span class="line"> <span class="keyword">this</span>.button = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">Light.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> button = <span class="built_in">document</span>.createElement( <span class="string">'button'</span> ), self = <span class="keyword">this</span>;</span><br><span class="line"> button.innerHTML = <span class="string">'已关灯'</span>;</span><br><span class="line"> <span class="keyword">this</span>.button = <span class="built_in">document</span>.body.appendChild( button );</span><br><span class="line"> <span class="keyword">this</span>.button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ self.currState.buttonWasPressed(); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> light = <span class="keyword">new</span> Light();</span><br><span class="line">light.init();</span><br></pre></td></tr></table></figure><h4 id="13-7-状态模式小结"><a href="#13-7-状态模式小结" class="headerlink" title="13.7 状态模式小结"></a>13.7 状态模式小结</h4><p>状态模式是非常有效的模式之一,通过状态模式重构代码之后,会让代码会变得清晰。虽然状态模式一开始并不是非常容易理解,但有必须去好好掌握这种设计模式。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="状态模式" scheme="https://geek-lhj.github.io/tags/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(十二)装饰者模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-12-%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-12-%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:12:01.000Z</published>
<updated>2020-04-11T08:05:23.786Z</updated>
<content type="html"><![CDATA[<p>装饰者模式定义为给对象动态地增加职责的方式;装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责;</p><a id="more"></a><h3 id="12-装饰者模式"><a href="#12-装饰者模式" class="headerlink" title="12 装饰者模式"></a>12 装饰者模式</h3><p>装饰者模式定义为给对象动态地增加职责的方式;装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责;</p><h4 id="12-1-装饰函数"><a href="#12-1-装饰函数" class="headerlink" title="12.1 装饰函数"></a>12.1 装饰函数</h4><p>在 JavaScript 中可以很方便地给某个对象扩展属性和方法,但却很难在不改动某个函数源代码的情况下,给该函数添加一些额外的功能通过保存原引用的方式就可以改写某个函数,如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ alert (<span class="number">1</span>); }</span><br><span class="line"><span class="keyword">var</span> _onload = <span class="built_in">window</span>.onload || <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> _onload();</span><br><span class="line"> alert (<span class="number">2</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样的代码符合<strong>开放-封闭</strong>原则的,在增加新功能的时候,确实没有修改原来的 <code>window.onload</code> 代码,但是这种方式存在以下两个问题:</p><ul><li>必须维护 <code>_onload</code> 这个中间变量,如果函数的装饰链较长,或者需要装饰的函数变多,这些中间变量的数量也会越来越多;</li><li>其实还遇到了 <code>this</code> 被劫持的问题,遇到该问题时需要 用<code>Function.prototype.apply()</code> 手动设置 <code>this</code> 指向,如下<code>document.getElementById</code> 方法;<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"button"</span>></span><span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> _getElementById = <span class="built_in">document</span>.getElementById;</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span></span><br><span class="line"> alert (1);</span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> _getElementById.apply( <span class="built_in">document</span>, <span class="built_in">arguments</span> );</span></span><br><span class="line"> }</span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> button = <span class="built_in">document</span>.getElementById( <span class="string">'button'</span> );</span></span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure></li></ul><h4 id="12-2-用-AOP-面向切面编程-装饰函数"><a href="#12-2-用-AOP-面向切面编程-装饰函数" class="headerlink" title="12.2 用 AOP (面向切面编程)装饰函数"></a>12.2 用 AOP (面向切面编程)装饰函数</h4><blockquote><ol><li><code>Function.prototype.before</code> 方法和<code>Function.prototype.after</code> 方法实现:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.before = <span class="function"><span class="keyword">function</span>(<span class="params"> beforefn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> __self = <span class="keyword">this</span>; <span class="comment">// 保存原函数的引用</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 返回包含了原函数和新函数的"代理"函数</span></span><br><span class="line"> beforefn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// 执行新函数,且保证 this 不被劫持,新函数接受的参数</span></span><br><span class="line"> <span class="comment">// 也会被原封不动地传入原函数,新函数在原函数之前执行</span></span><br><span class="line"> <span class="keyword">return</span> __self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// 执行原函数并返回原函数的执行结果,</span></span><br><span class="line"> <span class="comment">// 并且保证 this 不被劫持</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="built_in">Function</span>.prototype.after = <span class="function"><span class="keyword">function</span>(<span class="params"> afterfn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> __self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ret = __self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> afterfn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>2.用 AOP 装饰函数修改上面例子:</p></blockquote><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"button"</span>></span><span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">Function</span>.prototype.before = <span class="function"><span class="keyword">function</span>(<span class="params"> beforefn </span>)</span>{</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">var</span> __self = <span class="keyword">this</span>;</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span><span class="params">()</span></span>{</span></span><br><span class="line"><span class="javascript"> beforefn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> __self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById = <span class="built_in">document</span>.getElementById.before(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ alert (<span class="number">1</span>); });</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> button = <span class="built_in">document</span>.getElementById( <span class="string">'button'</span> );</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log( button );</span></span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><h4 id="12-3-AOP-的应用实例"><a href="#12-3-AOP-的应用实例" class="headerlink" title="12.3 AOP 的应用实例"></a>12.3 AOP 的应用实例</h4><h4 id="12-4-装饰者模式和代理模式"><a href="#12-4-装饰者模式和代理模式" class="headerlink" title="12.4 装饰者模式和代理模式"></a>12.4 装饰者模式和代理模式</h4><p>代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。</p><ul><li>代理模式:当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者,本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情;代理模式强调一种关系( Proxy 与它的实体之间的关系),这种关系可以静态的表达,在一开始就可以被确定;</li><li>装饰者模式:为对象动态加入行为;装饰者模式用于一开始不能确定对象的全部功能时;代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链;</li></ul><h4 id="12-5-装饰者模式小结"><a href="#12-5-装饰者模式小结" class="headerlink" title="12.5 装饰者模式小结"></a>12.5 装饰者模式小结</h4><p>装饰函数是 JavaScript 中独特的装饰者模式,这种模式在实际开发中非常有用;同时在框架开发中也十分有用,通过装饰者模式为框架里的函数提供的一些稳定而方便移植的功能,这些个性化的功能可以在框架之外动态装饰上去,这样能避免为了让框架拥有更多的功能,而去使用一些 if、 else 语句预测用户的实际需要;</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>装饰者模式定义为给对象动态地增加职责的方式;装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="装饰者模式" scheme="https://geek-lhj.github.io/tags/%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(十一)中介者模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-11-%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-11-%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:11:01.000Z</published>
<updated>2020-04-11T08:05:28.804Z</updated>
<content type="html"><![CDATA[<p>待上传</p><a id="more"></a><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>待上传</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="中介者模式" scheme="https://geek-lhj.github.io/tags/%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(十)职责链模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-10-%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-10-%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:10:01.000Z</published>
<updated>2020-04-11T08:05:34.052Z</updated>
<content type="html"><![CDATA[<p>职责链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止;</p><a id="more"></a><h3 id="10-职责链模式"><a href="#10-职责链模式" class="headerlink" title="10 职责链模式"></a>10 职责链模式</h3><blockquote><p>职责链模式的定义:</p></blockquote><p>使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止;</p><blockquote><p>职责链模式最大优点:</p></blockquote><p>请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系;</p><h4 id="10-1-职责链模式实例"><a href="#10-1-职责链模式实例" class="headerlink" title="10.1 职责链模式实例"></a>10.1 职责链模式实例</h4><p>假设一个售卖手机的电商网站,经过分别交纳 500 元定金和 200 元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用<br>户会收到 100 元的商城优惠券, 200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到;</p><p>相关字段定义:</p><ul><li><code>orderType</code> 表示订单类型,code 的值为 1 的时候是 500 元定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户;</li><li><code>pay</code>:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式;</li><li><code>stock</code> :表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用户不受此限制;</li></ul><p>这个流程代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> order = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( orderType === <span class="number">1</span> ){ <span class="comment">// 500 元定金购买模式</span></span><br><span class="line"> <span class="keyword">if</span> ( pay === <span class="literal">true</span> ){ <span class="comment">// 已支付定金</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'500 元定金预购, 得到 100 优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{ <span class="comment">// 未支付定金,降级到普通购买模式</span></span><br><span class="line"> <span class="keyword">if</span> ( stock > <span class="number">0</span> ){ <span class="comment">// 用于普通购买的手机还有库存</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ( orderType === <span class="number">2</span> ){ <span class="comment">// 200 元定金购买模式</span></span><br><span class="line"> <span class="keyword">if</span> ( pay === <span class="literal">true</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'200 元定金预购, 得到 50 优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span> ( stock > <span class="number">0</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ( orderType === <span class="number">3</span> ){</span><br><span class="line"> <span class="keyword">if</span> ( stock > <span class="number">0</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">order( <span class="number">1</span> , <span class="literal">true</span>, <span class="number">500</span>); <span class="comment">// 输出: 500 元定金预购, 得到 100 优惠券</span></span><br></pre></td></tr></table></figure><p>此代码阅读起来很难,当项目运行后,若增加其他优惠政策,修改起来也很困难;</p><h4 id="10-2-用职责链模式重构代码"><a href="#10-2-用职责链模式重构代码" class="headerlink" title="10.2 用职责链模式重构代码"></a>10.2 用职责链模式重构代码</h4><p>先把 500 元订单、 200 元订单以及普通购买分成 3个函数;接下来把 <code>orderType、 pay、 stock</code> 这 3 个字段当作参数传递给 500 元订单函数,如果该函数不符合处理条件,则把这个请求传递给后面的 200 元订单函数,如果 200 元订单函数依然不能处理该请求,则继续传递请求给普通购买函数,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 500 元订单</span></span><br><span class="line"><span class="keyword">var</span> order500 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( orderType === <span class="number">1</span> && pay === <span class="literal">true</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'500 元定金预购, 得到 100 优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> order200( orderType, pay, stock ); <span class="comment">// 将请求传递给 200 元订单</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 200 元订单</span></span><br><span class="line"><span class="keyword">var</span> order200 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( orderType === <span class="number">2</span> && pay === <span class="literal">true</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'200 元定金预购, 得到 50 优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> orderNormal( orderType, pay, stock ); <span class="comment">// 将请求传递给普通订单</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 普通购买订单</span></span><br><span class="line"><span class="keyword">var</span> orderNormal = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( stock > <span class="number">0</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'普通购买, 无优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 测试:</span></span><br><span class="line">order500( <span class="number">1</span> , <span class="literal">true</span>, <span class="number">500</span>); <span class="comment">// 输出: 500 元定金预购, 得到 100 优惠券</span></span><br><span class="line">order500( <span class="number">1</span>, <span class="literal">false</span>, <span class="number">500</span> ); <span class="comment">// 输出:普通购买, 无优惠券</span></span><br><span class="line">order500( <span class="number">2</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出: 200 元定金预购, 得到 500 优惠券</span></span><br><span class="line">order500( <span class="number">3</span>, <span class="literal">false</span>, <span class="number">500</span> ); <span class="comment">// 输出:普通购买, 无优惠券</span></span><br><span class="line">order500( <span class="number">3</span>, <span class="literal">false</span>, <span class="number">0</span> ); <span class="comment">// 输出:手机库存不足</span></span><br></pre></td></tr></table></figure><p>此代码相对于上一个实现逻辑更加清晰,不过请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函<br>数之中(如其中的 order200 和 order500 耦合在一起),当增加其他优惠时,必须修改整条链,这违反了<strong>开放-封闭原则</strong>;</p><h4 id="2-10-3-灵活可拆分的职责链节点"><a href="#2-10-3-灵活可拆分的职责链节点" class="headerlink" title="2.10.3 灵活可拆分的职责链节点"></a>2.10.3 灵活可拆分的职责链节点</h4><blockquote><ol><li>改写一下 3 种购买模式的节点函数,约定若某个节点不能处理请求,则返回一个特定的字符串 <code>nextSuccessor</code>来表示该请求需要继续往后面传递,代码如下:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> order500 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( orderType === <span class="number">1</span> && pay === <span class="literal">true</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'500 元定金预购,得到 100 优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'nextSuccessor'</span>; <span class="comment">// 不用知道下一个节点是谁,反正把请求往后面传递</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> order200 = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( orderType === <span class="number">2</span> && pay === <span class="literal">true</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'200 元定金预购,得到 50 优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'nextSuccessor'</span>; <span class="comment">// 不用知道下一个节点是谁,反正把请求往后面传递</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> orderNormal = <span class="function"><span class="keyword">function</span>(<span class="params"> orderType, pay, stock </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( stock > <span class="number">0</span> ){</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'普通购买,无优惠券'</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'手机库存不足'</span> );</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>再定义一个构造函数 Chain ,在 <code>new Chain</code> 的时候传递的参数即为需要被包装的函数, 同时还拥有一个实例属性 <code>this.successor</code> ,表示在链中的下一个节点:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Chain = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.fn = fn;</span><br><span class="line"> <span class="keyword">this</span>.successor = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">Chain.prototype.setNextSuccessor = <span class="function"><span class="keyword">function</span>(<span class="params"> successor </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.successor = successor;</span><br><span class="line">};</span><br><span class="line">Chain.prototype.passRequest = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ret = <span class="keyword">this</span>.fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> <span class="keyword">if</span> ( ret === <span class="string">'nextSuccessor'</span> ){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.successor && <span class="keyword">this</span>.successor.passRequest.apply( <span class="keyword">this</span>.successor, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><ol start="3"><li>测试结果</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 把 3 个订单函数分别包装成职责链的节点:</span></span><br><span class="line"><span class="keyword">var</span> chainOrder500 = <span class="keyword">new</span> Chain( order500 );</span><br><span class="line"><span class="keyword">var</span> chainOrder200 = <span class="keyword">new</span> Chain( order200 );</span><br><span class="line"><span class="keyword">var</span> chainOrderNormal = <span class="keyword">new</span> Chain( orderNormal );</span><br><span class="line"><span class="comment">// 指定节点在职责链中的顺序:</span></span><br><span class="line">chainOrder500.setNextSuccessor( chainOrder200 );</span><br><span class="line">chainOrder200.setNextSuccessor( chainOrderNormal );</span><br><span class="line"><span class="comment">// 最后把请求传递给第一个节点:</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">1</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出: 500 元定金预购,得到 100 优惠券</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">2</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出: 200 元定金预购,得到 50 优惠券</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">3</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出:普通购买,无优惠券</span></span><br><span class="line">chainOrder500.passRequest( <span class="number">1</span>, <span class="literal">false</span>, <span class="number">0</span> ); <span class="comment">// 输出:手机库存不足</span></span><br></pre></td></tr></table></figure><blockquote><ol start="4"><li>自由灵活地增加、移除和修改链中的节点顺序,若新增 300 元的优惠政策,修改如下:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> order300 = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 具体实现略 };</span></span><br><span class="line">chainOrder300= <span class="keyword">new</span> Chain( order300 );</span><br><span class="line">chainOrder500.setNextSuccessor( chainOrder300);</span><br><span class="line">chainOrder300.setNextSuccessor( chainOrder200);</span><br></pre></td></tr></table></figure><h4 id="10-4-异步的职责链"><a href="#10-4-异步的职责链" class="headerlink" title="10.4 异步的职责链"></a>10.4 异步的职责链</h4><p>实际开发中经常会遇到一些异步的问题,比如在节点函数中发起一个 ajax 异步请求,异步请求返回的结果才能决定是否继续在职责链中 <code>passRequest</code> ,因而 Chain 新增原型方法 <code>Chain.prototype.next</code>,如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Chain.prototype.next= <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.successor && <span class="keyword">this</span>.successor.passRequest.apply( <span class="keyword">this</span>.successor, <span class="built_in">arguments</span> );</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="10-5-职责链模式的优缺点"><a href="#10-5-职责链模式的优缺点" class="headerlink" title="10.5 职责链模式的优缺点"></a>10.5 职责链模式的优缺点</h4><blockquote><p>职责链模式的优点:</p></blockquote><ul><li>使用了职责链模式之后,链中的节点对象可以灵活地拆分重组,如增加和删除节点;</li><li>职责链模式还可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递;</li></ul><blockquote><p>职责链模式的缺点:</p></blockquote><p>职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗;</p><h4 id="10-6-用-AOP-面向切面编程-实现职责链"><a href="#10-6-用-AOP-面向切面编程-实现职责链" class="headerlink" title="10.6 用 AOP (面向切面编程)实现职责链"></a>10.6 用 AOP (面向切面编程)实现职责链</h4><p>利用 JavaScript 的函数式特性,有一种更加方便的方法来创建职责链;下面改写之前高阶函数中的<code>Function.prototype.after</code> 函数,使得第一个函数返回 <code>nextSuccessor</code> 时,将请求继续传递给下一个函数,其中约定返回字符串 <code>nextSuccessor</code> 或者 <code>false</code> 中的一个;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.after = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ret = self.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> <span class="keyword">if</span> ( ret === <span class="string">'nextSuccessor'</span> ){</span><br><span class="line"> <span class="keyword">return</span> fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> order = order500yuan.after( order200yuan ).after( orderNormal );</span><br><span class="line">order( <span class="number">1</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出: 500 元定金预购,得到 100 优惠券</span></span><br><span class="line">order( <span class="number">2</span>, <span class="literal">true</span>, <span class="number">500</span> ); <span class="comment">// 输出: 200 元定金预购,得到 50 优惠券</span></span><br><span class="line">order( <span class="number">1</span>, <span class="literal">false</span>, <span class="number">500</span> ); <span class="comment">// 输出:普通购买,无优惠券</span></span><br></pre></td></tr></table></figure><h4 id="10-7-职责链模式小结"><a href="#10-7-职责链模式小结" class="headerlink" title="10.7 职责链模式小结"></a>10.7 职责链模式小结</h4><p>职责链模式可以很好地帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。职责链中的节点数量和顺序是可以自由变化的,我们可以在运行时决定链中包含哪些节点;职责链模式的应用有很多,如作用域链、原型链、 DOM 节点的事件冒泡等,职责链模式还可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>职责链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="职责链模式" scheme="https://geek-lhj.github.io/tags/%E8%81%8C%E8%B4%A3%E9%93%BE%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(九)享元模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-9-%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-9-%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:09:01.000Z</published>
<updated>2020-04-11T08:05:38.767Z</updated>
<content type="html"><![CDATA[<p>享元( flyweight)模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象;</p><a id="more"></a><h3 id="9-享元模式"><a href="#9-享元模式" class="headerlink" title="9 享元模式"></a>9 享元模式</h3><p>享元( flyweight)模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象;</p><h4 id="9-1-享元模式简单示例"><a href="#9-1-享元模式简单示例" class="headerlink" title="9.1 享元模式简单示例"></a>9.1 享元模式简单示例</h4><p>假设目前加工好了50件男士外套和50件女士外套,需要使用塑料模特拍照,正常情况下需要 50 个男模特和 50 个女模特,然后让他们每人分别穿上一件外套来拍照。不使用享元模式的情况下,在程序里也许会这样写:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Model = <span class="function"><span class="keyword">function</span>(<span class="params"> sex, underwear</span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.sex = sex;</span><br><span class="line"> <span class="keyword">this</span>.underwear= underwear;</span><br><span class="line">};</span><br><span class="line">Model.prototype.takePhoto = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'sex= '</span> + <span class="keyword">this</span>.sex + <span class="string">' underwear='</span> + <span class="keyword">this</span>.underwear);</span><br><span class="line">};</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">1</span>; i <= <span class="number">50</span>; i++ ){</span><br><span class="line"> <span class="keyword">var</span> maleModel = <span class="keyword">new</span> Model( <span class="string">'male'</span>, <span class="string">'underwear'</span> + i );</span><br><span class="line"> maleModel.takePhoto();</span><br><span class="line">};</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> j = <span class="number">1</span>; j <= <span class="number">50</span>; j++ ){</span><br><span class="line"> <span class="keyword">var</span> femaleModel= <span class="keyword">new</span> Model( <span class="string">'female'</span>, <span class="string">'underwear'</span> + j );</span><br><span class="line"> femaleModel.takePhoto();</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>考虑一下如何优化这个场景,其实男模特和女模特各自有一个就足够,代码调整如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Model = <span class="function"><span class="keyword">function</span>(<span class="params"> sex </span>)</span>{ <span class="keyword">this</span>.sex = sex; };</span><br><span class="line">Model.prototype.takePhoto = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'sex= '</span> + <span class="keyword">this</span>.sex + <span class="string">' underwear='</span> + <span class="keyword">this</span>.underwear);</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> maleModel = <span class="keyword">new</span> Model( <span class="string">'male'</span> ), femaleModel = <span class="keyword">new</span> Model( <span class="string">'female'</span> );</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">1</span>; i <= <span class="number">50</span>; i++ ){</span><br><span class="line"> maleModel.underwear = <span class="string">'underwear'</span> + i;</span><br><span class="line"> maleModel.takePhoto();</span><br><span class="line">};</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> j = <span class="number">1</span>; j <= <span class="number">50</span>; j++ ){</span><br><span class="line"> femaleModel.underwear = <span class="string">'underwear'</span> + j;</span><br><span class="line"> femaleModel.takePhoto();</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="9-2-内部状态与外部状态"><a href="#9-2-内部状态与外部状态" class="headerlink" title="9.2 内部状态与外部状态"></a>9.2 内部状态与外部状态</h4><p>享元模式要求将对象的属性划分为<strong>内部状态与外部状态</strong>(状态在这里通常指属性),享元模式的目标是尽量减少共享对象的数量;</p><blockquote><p>1.如何划分内部状态和外部状态:</p></blockquote><ul><li>内部状态存储于对象内部;</li><li>内部状态可以被一些对象共享;</li><li>内部状态独立于具体的场景,通常不会改变;</li><li>外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享;</li></ul><p>这样便可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象;组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量,因此享元模式是一种用时间换空间的优化模式;</p><blockquote><ol start="2"><li>分析上面的例子:</li></ol></blockquote><p>在上面的例子中,性别是内部状态,外套是外部状态,通过区分这两种状态,大大减少了系统中的对象数量。通常来讲,内部状态有多少种组合,系统中便最多存在多少个对象,因为性别通常只有男女两种,所以最多只需要 2 个对象;</p><p>在上面的例子中,存在的一些问题以及解决方法:</p><ul><li>通过构造函数显式 <code>new</code> 出了男女两个 <code>model</code> 对象,在其他系统中,并不是一开始就需要所有的共享对象;因此通过一个对象工厂来解决,只有当某种共享对象被真正需要时,它才从工厂中被创建出来;</li><li>给 <code>model</code> 对象手动设置了 <code>underwear</code> 外部状态,在更复杂的系统中,这不是一个最好的方式,因为外部状态可能会相当复杂,它们与共享对象的联系会变得困难;因此用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来;</li></ul><h4 id="9-3-文件上传的例子"><a href="#9-3-文件上传的例子" class="headerlink" title="9.3 文件上传的例子"></a>9.3 文件上传的例子</h4><p>实现多个文件的上传,上传成功文件后展示文件的信息,并支持删除文件的功能;</p><blockquote><ol><li>文件上传基本实现代码:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义 Upload 构造函数,它接受 3 个参数,分别是插件类型、文件名和文件大小</span></span><br><span class="line"><span class="keyword">var</span> Upload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType, fileName, fileSize </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.uploadType = uploadType;</span><br><span class="line"> <span class="keyword">this</span>.fileName = fileName;</span><br><span class="line"> <span class="keyword">this</span>.fileSize = fileSize;</span><br><span class="line"> <span class="keyword">this</span>.dom= <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// upload 对象init函数</span></span><br><span class="line">Upload.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> that = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">this</span>.id = id;</span><br><span class="line"> <span class="keyword">this</span>.dom = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> <span class="keyword">this</span>.dom.innerHTML =</span><br><span class="line"> <span class="string">'<span>文件名称:'</span>+ <span class="keyword">this</span>.fileName +<span class="string">', 文件大小: '</span>+ <span class="keyword">this</span>.fileSize +<span class="string">'</span>'</span> +</span><br><span class="line"> <span class="string">'<button class="delFile">删除</button>'</span>;</span><br><span class="line"> <span class="keyword">this</span>.dom.querySelector( <span class="string">'.delFile'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ that.delFile(); }</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( <span class="keyword">this</span>.dom );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// upload 对象删除文件的功能</span></span><br><span class="line">Upload.prototype.delFile = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( <span class="keyword">this</span>.fileSize < <span class="number">3000</span> ){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( <span class="built_in">window</span>.confirm( <span class="string">'确定要删除该文件吗? '</span> + <span class="keyword">this</span>.fileName ) ){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 当选择了文件并确认上传后,调用 Window 下的一个全局函数 startUpload,用户选择的文件列表被组合成一个数组 files 塞进该函数的参数列表里,代码如下:</span></span><br><span class="line"><span class="keyword">var</span> id = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">window</span>.startUpload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType, files </span>)</span>{ <span class="comment">// uploadType 区分是控件还是 flash</span></span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file; file = files[ i++ ]; ){</span><br><span class="line"> <span class="keyword">var</span> uploadObj = <span class="keyword">new</span> Upload( uploadType, file.fileName, file.fileSize );</span><br><span class="line"> uploadObj.init( id++ ); <span class="comment">// 给 upload 对象设置一个唯一的 id</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 插件类型上传文件</span></span><br><span class="line">startUpload( <span class="string">'plugin'</span>, [</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'1.txt'</span>, <span class="attr">fileSize</span>: <span class="number">1000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'2.html'</span>, <span class="attr">fileSize</span>: <span class="number">3000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'3.txt'</span>, <span class="attr">fileSize</span>: <span class="number">5000</span> }</span><br><span class="line">]);</span><br><span class="line"><span class="comment">// Flash类型上传文件</span></span><br><span class="line">startUpload( <span class="string">'flash'</span>, [</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'4.txt'</span>, <span class="attr">fileSize</span>: <span class="number">1000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'5.html'</span>, <span class="attr">fileSize</span>: <span class="number">3000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'6.txt'</span>, <span class="attr">fileSize</span>: <span class="number">5000</span> }</span><br><span class="line">]);</span><br></pre></td></tr></table></figure><p>该方式的文件上传中,若一次性上传很多个文件时,每一个文件对应一个上传对象,这种对象爆炸的问题会使得浏览器崩溃;</p><blockquote><ol start="2"><li>享元模式重构文件上传:</li></ol></blockquote><p>在文件上传的例子里, upload 对象必须依赖 <code>uploadType</code> 属性才能工作,这是因为插件上传、Flash 上传、表单上传的实际工作原理有很大的区别,它们各自调用的接口也是完全不一样的,因此 <code>uploadType</code> 作为内部状态,把其他的外部状态从构造函数中抽离出来,Upload 构造函数中只保留 <code>uploadType</code> 参数;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Upload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType</span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.uploadType = uploadType;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>同时 <code>Upload.prototype.init</code> 函数也不再需要,因为 upload 对象初始化的工作被放在了 <code>uploadManager.add</code> 函数里面,接下来只需要定义 <code>Upload.prototype.del</code> 函数即可:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Upload.prototype.delFile = <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>{</span><br><span class="line"> uploadManager.setExternalState( id, <span class="keyword">this</span> ); <span class="comment">// 表示把当前 id 对应的对象的外部状态都组装到共享对象中</span></span><br><span class="line"> <span class="keyword">if</span> ( <span class="keyword">this</span>.fileSize < <span class="number">3000</span> ){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( <span class="built_in">window</span>.confirm( <span class="string">'确定要删除该文件吗? '</span> + <span class="keyword">this</span>.fileName ) ){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.dom.parentNode.removeChild( <span class="keyword">this</span>.dom );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>工厂进行对象实例化</strong>:定义一个工厂来创建 upload 对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> UploadFactory = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> createdFlyWeightObjs = {};</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> create: <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType</span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( createdFlyWeightObjs [ uploadType] ){</span><br><span class="line"> <span class="keyword">return</span> createdFlyWeightObjs [ uploadType];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> createdFlyWeightObjs [ uploadType] = <span class="keyword">new</span> Upload( uploadType);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><p><strong>管理器封装外部状态</strong>: <code>uploadManager</code> 对象负责向 <code>UploadFactory</code> 提交创建对象的请求,并用一个 <code>uploadDatabase</code> 对象保存所有 <code>upload</code> 对象的外部状态,以便在程序运行过程中给 <code>upload</code> 共享对象设置外部状态,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> uploadManager = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> uploadDatabase = {};</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="comment">// 创建上传文件函数</span></span><br><span class="line"> add: <span class="function"><span class="keyword">function</span>(<span class="params"> id, uploadType, fileName, fileSize </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> flyWeightObj = UploadFactory.create( uploadType );</span><br><span class="line"> <span class="keyword">var</span> dom = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> dom.innerHTML =</span><br><span class="line"> <span class="string">'<span>文件名称:'</span>+ fileName +<span class="string">', 文件大小: '</span>+ fileSize +<span class="string">'</span>'</span> +</span><br><span class="line"> <span class="string">'<button class="delFile">删除</button>'</span>;</span><br><span class="line"> dom.querySelector( <span class="string">'.delFile'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ flyWeightObj.delFile( id ); }</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( dom );</span><br><span class="line"> uploadDatabase[ id ] = { <span class="attr">fileName</span>: fileName, <span class="attr">fileSize</span>: fileSize, <span class="attr">dom</span>: dom };</span><br><span class="line"> <span class="keyword">return</span> flyWeightObj ;</span><br><span class="line"> },</span><br><span class="line"> setExternalState: <span class="function"><span class="keyword">function</span>(<span class="params"> id, flyWeightObj </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> uploadData = uploadDatabase[ id ];</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> uploadData ){ flyWeightObj[ i ] = uploadData[ i ]; }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><p>接着是触发上传动作的 <code>startUpload</code> 函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> id = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">window</span>.startUpload = <span class="function"><span class="keyword">function</span>(<span class="params"> uploadType, files </span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file; file = files[ i++ ]; ){</span><br><span class="line"> <span class="keyword">var</span> uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize );</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>最后测试,运行下面的代码后,可以发现运行结果跟用享元模式重构之前一致:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 插件类型上传文件</span></span><br><span class="line">startUpload( <span class="string">'plugin'</span>, [</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'1.txt'</span>, <span class="attr">fileSize</span>: <span class="number">1000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'2.html'</span>, <span class="attr">fileSize</span>: <span class="number">3000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'3.txt'</span>, <span class="attr">fileSize</span>: <span class="number">5000</span> }</span><br><span class="line">]);</span><br><span class="line"><span class="comment">// Flash类型上传文件</span></span><br><span class="line">startUpload( <span class="string">'flash'</span>, [</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'4.txt'</span>, <span class="attr">fileSize</span>: <span class="number">1000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'5.html'</span>, <span class="attr">fileSize</span>: <span class="number">3000</span> },</span><br><span class="line"> { <span class="attr">fileName</span>: <span class="string">'6.txt'</span>, <span class="attr">fileSize</span>: <span class="number">5000</span> }</span><br><span class="line">]);</span><br></pre></td></tr></table></figure><h4 id="9-4-享元模式的适用性"><a href="#9-4-享元模式的适用性" class="headerlink" title="9.4 享元模式的适用性"></a>9.4 享元模式的适用性</h4><p>享元模式的适用场景:</p><ul><li>一个程序中使用了大量的相似对象;</li><li>由于使用了大量对象,造成很大的内存开销;</li><li>对象的大多数状态都可以变为外部状态;</li><li>剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象;</li></ul><h4 id="9-5-再谈内部状态和外部状态"><a href="#9-5-再谈内部状态和外部状态" class="headerlink" title="9.5 再谈内部状态和外部状态"></a>9.5 再谈内部状态和外部状态</h4><p>实现享元模式的关键是把内部状态和外部状态分离开来。有多少种内部状态的组合,系统中便最多存在多少个共享对象,而外部状态储存在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象;现在来考虑两种极端的情况,即对象没有外部状态和没有内部状态的时候;</p><p><strong>没有内部状态的享元:</strong></p><p><strong>没有外部状态的享元:</strong></p><h4 id="2-9-6-对象池"><a href="#2-9-6-对象池" class="headerlink" title="2.9.6 对象池"></a>2.9.6 对象池</h4><p>对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接 <code>new</code> ,而是转从对象池里获取。如果对象池里没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后, 再进入池子等待被下次获取。</p><p><strong>对象池实现:</strong></p><p>假设在一个地图应用中, 地图上经常会出现一些标志地名的小气泡,当搜索附近地图的时候,页面里出现了 2 个小气泡。当我再搜索附近的其他地点时,页面中出现了 6 个小气泡。按照对象池的思想,在第二次搜索开始之前,并不会把第一次创建的2 个小气泡删除掉,而是把它们放进对象池;这样在第二次的搜索结果页面里,只需要再创建 4 个小气泡而不是 6 个;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 定义一个获取小气泡节点的工厂,作为对象池的数组成为私有属性被包含在工厂闭包,该工厂的 create 方法表示获取一个 div 节点, recover 方法表示回收一个 div 节点:var toolTipFactory = (function(){</span></span><br><span class="line"><span class="keyword">var</span> toolTipFactory = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> toolTipPool = []; <span class="comment">// toolTip 对象池</span></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> create: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( toolTipPool.length === <span class="number">0</span> ){ <span class="comment">// 如果对象池为空</span></span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> ); <span class="comment">// 创建一个 dom</span></span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> <span class="keyword">return</span> div;</span><br><span class="line"> }<span class="keyword">else</span>{ <span class="comment">// 如果对象池里不为空</span></span><br><span class="line"> <span class="keyword">return</span> toolTipPool.shift(); <span class="comment">// 则从对象池中取出一个 dom</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> recover: <span class="function"><span class="keyword">function</span>(<span class="params"> tooltipDom </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> toolTipPool.push( tooltipDom ); <span class="comment">// 对象池回收 dom</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="comment">// 2. 创建 2 个小气泡节点,并用一个数组 ary 来记录它们</span></span><br><span class="line"><span class="keyword">var</span> ary = [];</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, str; str = [ <span class="string">'A'</span>, <span class="string">'B'</span> ][ i++ ]; ){</span><br><span class="line"> <span class="keyword">var</span> toolTip = toolTipFactory.create();</span><br><span class="line"> toolTip.innerHTML = str;</span><br><span class="line"> ary.push( toolTip );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 3. 假设地图需要开始重新绘制,在此之前要把这两个节点回收进对象池:</span></span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, toolTip; toolTip = ary[ i++ ]; ){</span><br><span class="line"> toolTipFactory.recover( toolTip );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 4. 再创建 6 个小气泡:</span></span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, str; str = [ <span class="string">'A'</span>, <span class="string">'B'</span>, <span class="string">'C'</span>, <span class="string">'D'</span>, <span class="string">'E'</span>, <span class="string">'F'</span> ][ i++ ]; ){</span><br><span class="line"> <span class="keyword">var</span> toolTip = toolTipFactory.create();</span><br><span class="line"> toolTip.innerHTML = str;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="9-6-享元模式小结"><a href="#9-6-享元模式小结" class="headerlink" title="9.6 享元模式小结"></a>9.6 享元模式小结</h4><p>享元模式是为解决性能问题而生的模式,在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题;</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>享元( flyweight)模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="享元模式" scheme="https://geek-lhj.github.io/tags/%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(八)模板方法模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8-%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-8-%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:08:01.000Z</published>
<updated>2020-04-11T08:05:44.506Z</updated>
<content type="html"><![CDATA[<p>待发布</p><a id="more"></a><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>待发布</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="模板方法模式" scheme="https://geek-lhj.github.io/tags/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(七)组合模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7-%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-7-%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:07:01.000Z</published>
<updated>2020-04-11T08:05:50.995Z</updated>
<content type="html"><![CDATA[<p>组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的;</p><a id="more"></a><h3 id="7-组合模式"><a href="#7-组合模式" class="headerlink" title="7 组合模式"></a>7 组合模式</h3><p>组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的;</p><h4 id="7-1-组合模式的用途"><a href="#7-1-组合模式的用途" class="headerlink" title="7.1 组合模式的用途"></a>7.1 组合模式的用途</h4><p>组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构,如宏命令的例子,通过遍历该树形结构,调用组合对象的 execute 方法,程序会递归调用组合对象下面的叶对象的 execute 方法;组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性,只要约定对象上拥有可执行的 execute 方法即可;</p><h4 id="7-2-请求在树中传递的过程"><a href="#7-2-请求在树中传递的过程" class="headerlink" title="7.2 请求在树中传递的过程"></a>7.2 请求在树中传递的过程</h4><p>以宏命令为例,请求从树最顶端的对象往下传递,如果当前处理请求的对象是叶对象(普通子命令),叶对象自身会对请求作出相应的处理;如果当前处理请求的对象是组合对象(宏命令),组合对象则会遍历它属下的子节点,将请求继续传递给这些子节点。总之,如果子节点是叶对象,叶对象自身会处理这个请求,而如果子节点还是组合对象,请求会继续往下传递。叶对象下面不会再有其他子节点,一个叶对象就是树的这条枝叶的尽头,组合对象下面可能还会有子节点;</p><p><img src="https://upload-images.jianshu.io/upload_images/14756387-8be455a4b15a1a03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="_宏命令例子_1576393930_5178.png"></p><h4 id="7-3-透明性带来的安全问题"><a href="#7-3-透明性带来的安全问题" class="headerlink" title="7.3 透明性带来的安全问题"></a>7.3 透明性带来的安全问题</h4><p>组合模式的透明性使得发起请求的客户不用去顾忌树中组合对象和叶对象的区别,但它们在本质上有是区别的,组合对象可以拥有子节点,叶对象下面就没有子节点,解决方案通常是给叶对象也增加 add 方法,并且在调用这个方法时,抛出一个异常来及时提醒客户:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 组合对象</span></span><br><span class="line"><span class="keyword">var</span> MacroCommand = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> commandsList: [],</span><br><span class="line"> add: <span class="function"><span class="keyword">function</span>(<span class="params"> command </span>)</span>{ <span class="keyword">this</span>.commandsList.push( command ); },</span><br><span class="line"> execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, command; command = <span class="keyword">this</span>.commandsList[ i++ ]; ){</span><br><span class="line"> command.execute();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 叶对象</span></span><br><span class="line"><span class="keyword">var</span> openTvCommand = {</span><br><span class="line"> execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'打开电视'</span> ); },</span><br><span class="line"> add: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'叶对象不能添加子节点'</span> );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> macroCommand = MacroCommand();</span><br><span class="line">macroCommand.add( openTvCommand );</span><br><span class="line">openTvCommand.add( macroCommand ) <span class="comment">// Uncaught Error: 叶对象不能添加子节点</span></span><br></pre></td></tr></table></figure><h4 id="7-4-组合模式的例子——扫描文件夹"><a href="#7-4-组合模式的例子——扫描文件夹" class="headerlink" title="7.4 组合模式的例子——扫描文件夹"></a>7.4 组合模式的例子——扫描文件夹</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/******************************* Folder ******************************/</span></span><br><span class="line"><span class="keyword">var</span> Folder = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.files = [];</span><br><span class="line">};</span><br><span class="line">Folder.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"> file </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.files.push( file );</span><br><span class="line">};</span><br><span class="line">Folder.prototype.scan = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'开始扫描文件夹: '</span> + <span class="keyword">this</span>.name );</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file, files = <span class="keyword">this</span>.files; file = files[ i++ ]; ){</span><br><span class="line"> file.scan();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">/******************************* File ******************************/</span></span><br><span class="line"><span class="keyword">var</span> File = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line">};</span><br><span class="line">File.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'文件下面不能再添加文件'</span> );</span><br><span class="line">};</span><br><span class="line">File.prototype.scan = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'开始扫描文件: '</span> + <span class="keyword">this</span>.name );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 创建一些文件夹和文件对象, 并且让它们组合成一棵树</span></span><br><span class="line"><span class="keyword">var</span> folder = <span class="keyword">new</span> Folder( <span class="string">'测试文件夹'</span> );</span><br><span class="line"><span class="keyword">var</span> file = <span class="keyword">new</span> File( <span class="string">'JavaScript 设计模式与开发实践'</span> );</span><br><span class="line">folder.add( file );</span><br><span class="line"><span class="comment">// 操作树的最顶端对象,进行扫描整个文件夹的操作</span></span><br><span class="line">folder.scan();</span><br></pre></td></tr></table></figure><h4 id="7-5-组合模式的注意点"><a href="#7-5-组合模式的注意点" class="headerlink" title="7.5 组合模式的注意点"></a>7.5 组合模式的注意点</h4><ol><li>组合模式不是父子关系:组合模式是一种 HAS-A(聚合)的关系,而不是 IS-A。组合对象包含一组叶对象,但 Leaf 并不是 Composite 的子类。组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口;</li><li>对叶对象操作的一致性:组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性;</li><li>双向映射关系:假如存在叶对象处在多个组合对象的情况,那么在调用的时候,该叶对象的命令会执行多次,这种复合情况下必须给父节点和子节点建立双向映射关系,一个简单的方法是给组合对象和叶对象都增加集合来保存对方的引用。但这种相互间的引用相当复杂,而且对象之间产生了过多的耦合性,修改或者删除一个对象都变得困难,此时可以引入中介者模式来管理这些对象;</li><li>用职责链模式提高组合模式性能:在组合模式中,如果树的结构比较复杂,节点数量很多,在遍历树的过程中,性能方面也许表现得不够理想,在实际操作中避免遍历整棵树,借助职责链模式进行解决,职责链模式一般需要手动去设置链条,但在组合模式中,父对象和子对象之间实际上形成了天然的职责链。让请求顺着链条从父对象往子对象传递,或者是反过来从子对象往父对象传递,直到遇到可以处理该请求的对象为止,这也是职责链模式的经典运用场景之一。</li></ol><h4 id="7-6-引用父对象"><a href="#7-6-引用父对象" class="headerlink" title="7.6 引用父对象"></a>7.6 引用父对象</h4><p>之前示例中组合模式的树结构是从上至下的,但有时候需要在子节点上保持对父节点的引用,比如在组合模式中<br>使用职责链时,有可能需要让请求从子节点往父节点上冒泡传递。还有当删除某个文件的时候,实际上是从这个文件所在的上层文件夹中删除该文件的。</p><blockquote><p>实例:改写扫描文件夹的代码,增加删除功能</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 改写 Folder 类和 File 类,在这两个类的构造函数中增加 this.parent 属性,并且在调用 add 方法的时候,正确设置文件或者文件夹的父节点:</span></span><br><span class="line"><span class="keyword">var</span> Folder = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.parent = <span class="literal">null</span>; <span class="comment">// 增加 this.parent 属性</span></span><br><span class="line"> <span class="keyword">this</span>.files = [];</span><br><span class="line">};</span><br><span class="line">Folder.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"> file </span>)</span>{</span><br><span class="line"> file.parent = <span class="keyword">this</span>; <span class="comment">//设置父对象</span></span><br><span class="line"> <span class="keyword">this</span>.files.push( file );</span><br><span class="line">};</span><br><span class="line">Folder.prototype.scan = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'开始扫描文件夹: '</span> + <span class="keyword">this</span>.name );</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, file, files = <span class="keyword">this</span>.files; file = files[ i++ ]; ){</span><br><span class="line"> file.scan();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 增加移除文件夹方法 Folder.prototype.remove</span></span><br><span class="line">Folder.prototype.remove = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !<span class="keyword">this</span>.parent ){ <span class="comment">// 根节点或者树外的游离节点</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> files = <span class="keyword">this</span>.parent.files, l = files.length - <span class="number">1</span>; l >=<span class="number">0</span>; l-- ){</span><br><span class="line"> <span class="keyword">var</span> file = files[ l ];</span><br><span class="line"> <span class="keyword">if</span> ( file === <span class="keyword">this</span> ){ files.splice( l, <span class="number">1</span> ); }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// File 类的实现基本一致:</span></span><br><span class="line"><span class="keyword">var</span> File = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.parent = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">File.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>( <span class="string">'不能添加在文件下面'</span> ); };</span><br><span class="line">File.prototype.scan = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始扫描文件: '</span> + <span class="keyword">this</span>.name ); };</span><br><span class="line">File.prototype.remove = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !<span class="keyword">this</span>.parent ){ <span class="comment">// 根节点或者树外的游离节点</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> files = <span class="keyword">this</span>.parent.files, l = files.length - <span class="number">1</span>; l >=<span class="number">0</span>; l-- ){</span><br><span class="line"> <span class="keyword">var</span> file = files[ l ];</span><br><span class="line"> <span class="keyword">if</span> ( file === <span class="keyword">this</span> ){ files.splice( l, <span class="number">1</span> ); }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 测试一下移除文件功能:</span></span><br><span class="line"><span class="keyword">var</span> folder = <span class="keyword">new</span> Folder( <span class="string">'学习资料'</span> );</span><br><span class="line"><span class="keyword">var</span> folder1 = <span class="keyword">new</span> Folder( <span class="string">'JavaScript'</span> );</span><br><span class="line"><span class="keyword">var</span> file1 = <span class="keyword">new</span> Folder ( <span class="string">'深入浅出 Node.js'</span> );</span><br><span class="line">folder1.add( <span class="keyword">new</span> File( <span class="string">'JavaScript 设计模式与开发实践'</span> ) );</span><br><span class="line">folder.add( folder1 );</span><br><span class="line">folder.add( file1 );</span><br><span class="line">folder1.remove(); <span class="comment">//移除文件夹</span></span><br><span class="line">folder.scan();</span><br></pre></td></tr></table></figure><h4 id="7-7-使用组合模式场景"><a href="#7-7-使用组合模式场景" class="headerlink" title="7.7 使用组合模式场景"></a>7.7 使用组合模式场景</h4><ul><li>表示对象的<strong>部分-整体层次结构</strong>。组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且<strong>符合开放-封闭原则</strong>。</li><li>客户希望<strong>统一对待</strong>树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆 if、 else 语句来分别处理它们。组合对象和叶对象会各自做自己正确的事情,这是组合模式最重要的能力。</li></ul><h4 id="7-8-组合模式小结"><a href="#7-8-组合模式小结" class="headerlink" title="7.8 组合模式小结"></a>7.8 组合模式小结</h4><p>组合模式可以让我们使用树形方式创建对象的结构。我们可以把相同的操作应用在组合对象和单个对象上。在大多数情况下都可以忽略掉组合对象和单个对象之间的差别,从而用一致的方式来处理它们;但在使用了组合模式的系统中,每个对象看起来都与其他对象差不多。它们的区别只有在运行的时候会才会显现出来,这会使代码难以理解,并且组合模式会创建了太多的对象;</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="组合模式" scheme="https://geek-lhj.github.io/tags/%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(六)命令模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-6-%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-6-%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:06:01.000Z</published>
<updated>2020-04-11T08:05:55.457Z</updated>
<content type="html"><![CDATA[<p>命令模式的命令指的是一个执行某些特定事情的指令;</p><a id="more"></a><h3 id="6-命令模式"><a href="#6-命令模式" class="headerlink" title="6 命令模式"></a>6 命令模式</h3><h4 id="6-1-命令模式的用途"><a href="#6-1-命令模式的用途" class="headerlink" title="6.1 命令模式的用途"></a>6.1 命令模式的用途</h4><p>命令模式的命令指的是一个执行某些特定事情的指令;</p><p><strong>命令模式的应用场景</strong>:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发图送者和请求接收者能够消除彼此之间的耦合关系;</p><h4 id="6-2-命令模式的例子——菜单程序"><a href="#6-2-命令模式的例子——菜单程序" class="headerlink" title="6.2 命令模式的例子——菜单程序"></a>6.2 命令模式的例子——菜单程序</h4><p>实现一个点击不同按钮调用不同方法的功能 (模拟传统面向对象语言的命令模式实现):</p><ol><li><p>按钮绘制:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"button1"</span>></span>点击按钮 1<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"button2"</span>></span>点击按钮 2<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"button3"</span>></span>点击按钮 3<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> button1 = <span class="built_in">document</span>.getElementById( <span class="string">'button1'</span> ),</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> button2 = <span class="built_in">document</span>.getElementById( <span class="string">'button2'</span> ),</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> button3 = <span class="built_in">document</span>.getElementById( <span class="string">'button3'</span> );</span></span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure></li><li><p>定义 <code>setCommand</code> 函数, <code>setCommand</code> 函数负责往按钮上面安装命令。约定点击按钮会执行某个 <code>command</code> 命令,执行命令的动作被约定为调用 <code>command</code> 对象的 <code>execute()</code> 方法;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> setCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> button, command </span>)</span>{</span><br><span class="line"> button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ command.execute(); }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></li><li><p>编写点击按钮之后的具体行为:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> MenuBar = {</span><br><span class="line"> refresh: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'刷新菜单目录'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> SubMenu = {</span><br><span class="line"> add: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'增加子菜单'</span> ); },</span><br><span class="line"> del: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'删除子菜单'</span> ); }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></li><li><p>封装行为在命令类中:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> RefreshMenuBarCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> receiver </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver = receiver;</span><br><span class="line">};</span><br><span class="line">RefreshMenuBarCommand.prototype.execute = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver.refresh();</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> AddSubMenuCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> receiver </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver = receiver;</span><br><span class="line">};</span><br><span class="line">AddSubMenuCommand.prototype.execute = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver.add();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> DelSubMenuCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> receiver </span>)</span>{ <span class="keyword">this</span>.receiver = receiver; };</span><br><span class="line">DelSubMenuCommand.prototype.execute = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'删除子菜单'</span> ); };</span><br></pre></td></tr></table></figure></li><li><p>把命令接收者传入到 <code>command</code> 对象中,并且把 <code>command</code> 对象安装到 <code>button</code> 上面:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> refreshMenuBarCommand = <span class="keyword">new</span> RefreshMenuBarCommand( MenuBar );</span><br><span class="line"><span class="keyword">var</span> addSubMenuCommand = <span class="keyword">new</span> AddSubMenuCommand( SubMenu );</span><br><span class="line"><span class="keyword">var</span> delSubMenuCommand = <span class="keyword">new</span> DelSubMenuCommand( SubMenu );</span><br><span class="line"></span><br><span class="line">setCommand( button1, refreshMenuBarCommand );</span><br><span class="line">setCommand( button2, addSubMenuCommand );</span><br><span class="line">setCommand( button3, delSubMenuCommand );</span><br></pre></td></tr></table></figure></li></ol><h4 id="6-3-JavaScript-中的命令模式"><a href="#6-3-JavaScript-中的命令模式" class="headerlink" title="6.3 JavaScript 中的命令模式"></a>6.3 JavaScript 中的命令模式</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> bindClick = <span class="function"><span class="keyword">function</span>(<span class="params"> button, func </span>)</span>{ button.onclick = func; };</span><br><span class="line"><span class="keyword">var</span> MenuBar = {</span><br><span class="line"> refresh: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'刷新菜单界面'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> SubMenu = {</span><br><span class="line"> add: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'增加子菜单'</span> ); },</span><br><span class="line"> del: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'删除子菜单'</span> ); }</span><br><span class="line">};</span><br><span class="line">bindClick( button1, MenuBar.refresh );</span><br><span class="line">bindClick( button2, SubMenu.add );</span><br><span class="line">bindClick( button3, SubMenu.del );</span><br></pre></td></tr></table></figure><p>JavaScript 作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了 JavaScript 语言之中;运算块不一定要封装在 <code>command.execute</code> 方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能。</p><h4 id="6-4-撤消命令"><a href="#6-4-撤消命令" class="headerlink" title="6.4 撤消命令"></a>6.4 撤消命令</h4><p>撤销操作的实现一般是给命令对象增加一个名为 unexecude 或者 undo 的方法,在该方法里执行 execute 的反向操作。在 command.execute 方法让小球开始真正运动之前,我们需要先记录小球的当前位置,在 unexecude 或者 undo 操作中,再让小球回到刚刚记录下的位置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ball = <span class="built_in">document</span>.getElementById( <span class="string">'ball'</span> );</span><br><span class="line"><span class="keyword">var</span> pos = <span class="built_in">document</span>.getElementById( <span class="string">'pos'</span> );</span><br><span class="line"><span class="keyword">var</span> moveBtn = <span class="built_in">document</span>.getElementById( <span class="string">'moveBtn'</span> );</span><br><span class="line"><span class="keyword">var</span> cancelBtn = <span class="built_in">document</span>.getElementById( <span class="string">'cancelBtn'</span> );</span><br><span class="line"><span class="keyword">var</span> MoveCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> receiver, pos </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver = receiver;</span><br><span class="line"> <span class="keyword">this</span>.pos = pos;</span><br><span class="line"> <span class="keyword">this</span>.oldPos = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">MoveCommand.prototype.execute = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver.start( <span class="string">'left'</span>, <span class="keyword">this</span>.pos, <span class="number">1000</span>, <span class="string">'strongEaseOut'</span> );</span><br><span class="line"> <span class="keyword">this</span>.oldPos = <span class="keyword">this</span>.receiver.dom.getBoundingClientRect()[ <span class="keyword">this</span>.receiver.propertyName ];</span><br><span class="line"> <span class="comment">// 记录小球开始移动前的位置</span></span><br><span class="line">};</span><br><span class="line">MoveCommand.prototype.undo = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.receiver.start( <span class="string">'left'</span>, <span class="keyword">this</span>.oldPos, <span class="number">1000</span>, <span class="string">'strongEaseOut'</span> );</span><br><span class="line"> <span class="comment">// 回到小球移动前记录的位置</span></span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> moveCommand;</span><br><span class="line">moveBtn.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> animate = <span class="keyword">new</span> Animate( ball );</span><br><span class="line"> moveCommand = <span class="keyword">new</span> MoveCommand( animate, pos.value );</span><br><span class="line"> moveCommand.execute();</span><br><span class="line">};</span><br><span class="line">cancelBtn.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> moveCommand.undo(); <span class="comment">// 撤销命令</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="5-宏命令"><a href="#5-宏命令" class="headerlink" title="5 宏命令"></a>5 宏命令</h4><p>宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。宏命令对象包含了一组具体的子命令对象,不管是宏命令对象,还是子命令对象,都有一个 execute 方法负责执行命令:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> closeDoorCommand = {</span><br><span class="line"> execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'关门'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> openPcCommand = {</span><br><span class="line"> execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开电脑'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> openQQCommand = {</span><br><span class="line"> execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'登录 QQ'</span> ); }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> MacroCommand = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> commandsList: [],</span><br><span class="line"> add: <span class="function"><span class="keyword">function</span>(<span class="params"> command </span>)</span>{ <span class="keyword">this</span>.commandsList.push( command ); },</span><br><span class="line"> execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, command; command = <span class="keyword">this</span>.commandsList[ i++ ]; ){</span><br><span class="line"> command.execute();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> macroCommand = MacroCommand();</span><br><span class="line">macroCommand.add( closeDoorCommand );</span><br><span class="line">macroCommand.add( openPcCommand );</span><br><span class="line">macroCommand.add( openQQCommand );</span><br><span class="line">macroCommand.execute();</span><br></pre></td></tr></table></figure><h4 id="6-命令模式小结"><a href="#6-命令模式小结" class="headerlink" title="6 命令模式小结"></a>6 命令模式小结</h4><p> JavaScript 可以用高阶函数非常方便地实现命令模式,命令模式在 JavaScript 语言中是一种隐形的模式。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>命令模式的命令指的是一个执行某些特定事情的指令;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="命令模式" scheme="https://geek-lhj.github.io/tags/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(五)发布订阅模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-5-%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-5-%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:05:01.000Z</published>
<updated>2020-04-11T08:06:00.124Z</updated>
<content type="html"><![CDATA[<p>发布订阅模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知;</p><a id="more"></a><h3 id="5-发布订阅模式-观察者模式"><a href="#5-发布订阅模式-观察者模式" class="headerlink" title="5 发布订阅模式(观察者模式)"></a>5 发布订阅模式(观察者模式)</h3><p>发布订阅模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知;</p><h4 id="5-1-发布--订阅模式的作用"><a href="#5-1-发布--订阅模式的作用" class="headerlink" title="5.1 发布- 订阅模式的作用"></a>5.1 发布- 订阅模式的作用</h4><p>发布—订阅模式的应用都非常之广泛,首先看一个现实中的例子,小明最近在看房子,到了某个售楼处之后才被告知,该楼盘的房子早已售罄,小明离开之前,把电话号码留在了售楼处。售楼处答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们;</p><p>在这个例子中使用发布—订阅模式有着显而易见的优点:</p><ol><li>购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者会通知这些消息订阅者。这点说明发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案;</li><li>购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留在售楼处,售楼处不关心购房者的任何情况,同时售楼处的任何变动也不会影响购买者,只要售楼处记得发短信这件事情。这点说明发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就可以自由地改变它们。</li></ol><h4 id="5-2-发布订阅模式的实现"><a href="#5-2-发布订阅模式的实现" class="headerlink" title="5.2 发布订阅模式的实现"></a>5.2 发布订阅模式的实现</h4><blockquote><ol><li>实现上述发布订阅模式实例步骤:</li></ol></blockquote><ul><li>首先要指定好谁充当发布者(比如售楼处);</li><li>然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册);</li><li>最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,挨个发短信)。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> salesOffices = {}; <span class="comment">// 定义售楼处</span></span><br><span class="line">salesOffices.clientList = []; <span class="comment">// 缓存列表,存放订阅者的回调函数</span></span><br><span class="line">salesOffices.listen = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{ <span class="comment">// 增加订阅者</span></span><br><span class="line"> <span class="keyword">this</span>.clientList.push( fn ); <span class="comment">// 订阅的消息添加进缓存列表</span></span><br><span class="line">};</span><br><span class="line">salesOffices.trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 发布消息</span></span><br><span class="line"> <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = <span class="keyword">this</span>.clientList[ i++ ]; ){</span><br><span class="line"> f n.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// (2) // arguments 是发布消息时带上的参数</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 测试数据</span></span><br><span class="line">salesOffices.listen( <span class="function"><span class="keyword">function</span>(<span class="params"> price, squareMeter </span>)</span>{ <span class="comment">// 小明订阅消息</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price );</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'squareMeter= '</span> + squareMeter );</span><br><span class="line">});</span><br><span class="line">salesOffices.listen( <span class="function"><span class="keyword">function</span>(<span class="params"> price, squareMeter </span>)</span>{ <span class="comment">// 小红订阅消息</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price );</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'squareMeter= '</span> + squareMeter );</span><br><span class="line">});</span><br><span class="line">salesOffices.trigger( <span class="number">2000000</span>, <span class="number">88</span> ); <span class="comment">// 输出: 200 万, 88 平方米</span></span><br><span class="line">salesOffices.trigger( <span class="number">3000000</span>, <span class="number">110</span> ); <span class="comment">// 输出: 300 万, 110 平方米</span></span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>发布订阅模式的通用实现</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一步:把发布订阅的功能提取出来,放在一个单独的对象内</span></span><br><span class="line"><span class="keyword">var</span> event = {</span><br><span class="line"> clientList: [],</span><br><span class="line"> listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !<span class="keyword">this</span>.clientList[ key ] ){</span><br><span class="line"> <span class="keyword">this</span>.clientList[ key ] = [];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.clientList[ key ].push( fn ); <span class="comment">// 订阅的消息添加进缓存列表</span></span><br><span class="line"> },</span><br><span class="line"> trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), <span class="comment">// (1);</span></span><br><span class="line"> fns = <span class="keyword">this</span>.clientList[ key ];</span><br><span class="line"> <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> ){ <span class="comment">// 如果没有绑定对应的消息</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; ){</span><br><span class="line"> fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// (2) // arguments 是 trigger 时带上的参数</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第二步:定义 installEvent 函数给所有的对象都动态安装发布—订阅功能</span></span><br><span class="line"><span class="keyword">var</span> installEvent = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> event ){</span><br><span class="line"> obj[ i ] = event[ i ];</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 测试:给售楼处对象 salesOffices 动态增加发布—订阅功能</span></span><br><span class="line"><span class="keyword">var</span> salesOffices = {};</span><br><span class="line">installEvent( salesOffices );</span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>{ <span class="comment">// 小明订阅消息</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price );</span><br><span class="line">});</span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter100'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>{ <span class="comment">// 小红订阅消息</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price );</span><br><span class="line">});</span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 输出: 2000000</span></span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter100'</span>, <span class="number">3000000</span> ); <span class="comment">// 输出: 3000000</span></span><br></pre></td></tr></table></figure><h4 id="5-3-取消订阅的事件"><a href="#5-3-取消订阅的事件" class="headerlink" title="5.3 取消订阅的事件"></a>5.3 取消订阅的事件</h4><p>有时也许需要取消订阅事件的功能,因此给 <code>event</code> 对象增加 <code>remove</code> 方法,如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">event.remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> fns = <span class="keyword">this</span>.clientList[ key ];</span><br><span class="line"> <span class="keyword">if</span> ( !fns ){ <span class="comment">// 如果 key 对应的消息没有被人订阅,则直接返回</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( !fn ){ <span class="comment">// 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅</span></span><br><span class="line"> fns && ( fns.length = <span class="number">0</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> l = fns.length - <span class="number">1</span>; l >=<span class="number">0</span>; l-- ){ <span class="comment">// 反向遍历订阅的回调函数列表</span></span><br><span class="line"> <span class="keyword">var</span> _fn = fns[ l ];</span><br><span class="line"> <span class="keyword">if</span> ( _fn === fn ){</span><br><span class="line"> fns.splice( l, <span class="number">1</span> ); <span class="comment">// 删除订阅者的回调函数</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> salesOffices = {};</span><br><span class="line"><span class="keyword">var</span> installEvent = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> event ){</span><br><span class="line"> obj[ i ] = event[ i ];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">installEvent( salesOffices );</span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, fn1 = <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>{ <span class="comment">// 小明订阅消息</span></span><br><span class="line"><span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price );</span><br><span class="line">});</span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, fn2 = <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>{ <span class="comment">// 小红订阅消息</span></span><br><span class="line"><span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price );</span><br><span class="line">});</span><br><span class="line">salesOffices.remove( <span class="string">'squareMeter88'</span>, fn1 ); <span class="comment">// 删除小明的订阅</span></span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 输出: 2000000</span></span><br></pre></td></tr></table></figure><h4 id="5-4-网站登录实例-P116"><a href="#5-4-网站登录实例-P116" class="headerlink" title="5.4 网站登录实例(P116)"></a>5.4 网站登录实例(P116)</h4><h4 id="5-5-全局的发布订阅对象"><a href="#5-5-全局的发布订阅对象" class="headerlink" title="5.5 全局的发布订阅对象"></a>5.5 全局的发布订阅对象</h4><p>刚刚实现的发布订阅模式,给售楼处对象和登录对象都添加了订阅和发布的功能,这里还存在两个小问题:</p><ol><li>给每个发布者对象都添加了 <code>listen</code> 和 <code>trigger</code> 方法,以及一个缓存列表 <code>clientList</code> ,这其实是一种资源浪费;</li><li>订阅者跟售楼处对象还是存在一定的耦合性,订阅者至少要知道售楼处对象的名字是 <code>salesOffices</code> ,才能顺利的订阅到事件;</li></ol><p>实际上,订阅者没必要亲自去售楼处,只需要把订阅的请求交给中介公司,而各大房产公司也只需要通过中介公司来发布房子信息。为了保证订阅者和发布者能顺利通信,订阅者和发布者都必须知道这个中介公司。因此,发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者, Event 作为一个类似“中介者”的角色,把订阅者和发布者联系起来。见如下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Event = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> clientList = {}, listen, trigger, remove;</span><br><span class="line"> listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !clientList[ key ] ){</span><br><span class="line"> clientList[ key ] = [];</span><br><span class="line"> }</span><br><span class="line"> clientList[ key ].push( fn );</span><br><span class="line"> };</span><br><span class="line"> trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ),</span><br><span class="line"> fns = clientList[ key ];</span><br><span class="line"> <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> ){</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; ){</span><br><span class="line"> fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> fns = clientList[ key ];</span><br><span class="line"> <span class="keyword">if</span> ( !fns ){</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( !fn ){</span><br><span class="line"> fns && ( fns.length = <span class="number">0</span> );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> l = fns.length - <span class="number">1</span>; l >=<span class="number">0</span>; l-- ){</span><br><span class="line"> <span class="keyword">var</span> _fn = fns[ l ];</span><br><span class="line"> <span class="keyword">if</span> ( _fn === fn ){</span><br><span class="line"> fns.splice( l, <span class="number">1</span> );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> listen: listen,</span><br><span class="line"> trigger: trigger,</span><br><span class="line"> remove: remove</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line">Event.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>{ <span class="comment">// 小红订阅消息</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出: '价格=2000000'</span></span><br><span class="line">});</span><br><span class="line">Event.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 售楼处发布消息</span></span><br></pre></td></tr></table></figure><h4 id="5-6-模块间通信"><a href="#5-6-模块间通信" class="headerlink" title="5.6 模块间通信"></a>5.6 模块间通信</h4><p>我们利用上一节中实现的发布订阅模式的全局 <code>Event</code> 对象可以在两个封装良好的模块中进行通信,而这两个模块可以完全不知道对方的存在。比如现在有两个模块, a 模块里面有一个按钮,每次点击按钮之后, b 模块里的 div 中会显示按钮的总点击次数,我们用全局发布—订阅模式完成下面的代码,使得 a 模块和 b 模块可以在保持封装性的前提下进行通信。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"count"</span>></span>点我<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"show"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/JavaScript"</span>></span></span><br><span class="line"><span class="actionscript"> <span class="keyword">var</span> a = (<span class="function"><span class="keyword">function</span><span class="params">()</span></span>{</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">var</span> count = <span class="number">0</span>;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> button = <span class="built_in">document</span>.getElementById( <span class="string">'count'</span> );</span></span><br><span class="line"><span class="actionscript"> button.onclick = <span class="function"><span class="keyword">function</span><span class="params">()</span></span>{</span></span><br><span class="line"><span class="actionscript"> Event.trigger( <span class="string">'add'</span>, count++ );</span></span><br><span class="line"> }</span><br><span class="line"> })();</span><br><span class="line"><span class="actionscript"> <span class="keyword">var</span> b = (<span class="function"><span class="keyword">function</span><span class="params">()</span></span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> div = <span class="built_in">document</span>.getElementById( <span class="string">'show'</span> );</span></span><br><span class="line"><span class="actionscript"> Event.listen( <span class="string">'add'</span>, <span class="function"><span class="keyword">function</span><span class="params">( count )</span></span>{</span></span><br><span class="line"> div.innerHTML = count;</span><br><span class="line"> });</span><br><span class="line"> })();</span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><h4 id="5-7-JavaScript-实现发布订阅模式的便利性"><a href="#5-7-JavaScript-实现发布订阅模式的便利性" class="headerlink" title="5.7 JavaScript 实现发布订阅模式的便利性"></a>5.7 JavaScript 实现发布订阅模式的便利性</h4><p>在 JavaScript 中,无需去选择使用推模型还是拉模型。推模型是指在事件发生时,发布者一次性把所有更改的状态和数据都推送给订阅者。拉模型不同的地方是,发布者仅仅通知订阅者事件已经发生了,此外发布者要提供一些公开的接口供订阅者来主动拉取数据。拉模型的好处是可以让订阅者“按需获取”,但同时有可能让发布者变成一个“门户大开”的对象,同时增加了代码量和复杂度。刚好在 <code>JavaScript</code> 中, <code>arguments</code> 可以很方便地表示参数列表,所以我们一般都会选择推模型,使用 <code>Function.prototype.apply</code> 方法把所有参数都推送给订阅者。</p><h4 id="5-8-发布订阅模式小结"><a href="#5-8-发布订阅模式小结" class="headerlink" title="5.8 发布订阅模式小结"></a>5.8 发布订阅模式小结</h4><p>发布订阅模式是一种非常重要的模式,在实际开发中非常有用。既可以用在异步编程中,也可以帮助完成更松耦合的代码编写。发布订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。 从架构上来看,无论是 MVC 还是 MVVM,都少不了发布—订阅模式的参与,而且 JavaScript 本身也是一门基于事件驱动的语言。</p><p>发布订阅模式的优点一为时间上的解耦,二为对象之间的解耦。发布订阅模式缺点就是创建订阅者本身要消耗一定的时间和内存,若订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中;若过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>发布订阅模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="发布订阅模式" scheme="https://geek-lhj.github.io/tags/%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(四)迭代器模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-4-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-4-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:04:01.000Z</published>
<updated>2020-04-11T08:06:05.434Z</updated>
<content type="html"><![CDATA[<p>迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。</p><a id="more"></a><h3 id="4-迭代器模式"><a href="#4-迭代器模式" class="headerlink" title="4 迭代器模式"></a>4 迭代器模式</h3><p>迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。</p><h4 id="4-1-each-函数"><a href="#4-1-each-函数" class="headerlink" title="4.1 each 函数"></a>4.1 each 函数</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> each = <span class="function"><span class="keyword">function</span>(<span class="params"> ary, callback </span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = ary.length; i < l; i++ ){</span><br><span class="line"> callback.call( ary[i], i, ary[ i ] ); <span class="comment">// 把下标和元素当作参数传给 callback 函数</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">each( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ], <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>{</span><br><span class="line"> alert ( [ i, n ] );</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h4 id="4-2-内部迭代器和外部迭代器"><a href="#4-2-内部迭代器和外部迭代器" class="headerlink" title="4.2 内部迭代器和外部迭代器"></a>4.2 内部迭代器和外部迭代器</h4><ul><li>内部迭代器:外界不用关心迭代器内部的实现,跟迭代器的交互也仅仅是一次初始调用,如 each 函数;</li><li>外部迭代器:必须显式地请求迭代下一个元素;增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,可以手工控制迭代的过程或者顺序;</li></ul><blockquote><p>实现判断 2 个数组里元素的值是否完全相等</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 内部迭代器实现</span></span><br><span class="line"><span class="keyword">var</span> compare = <span class="function"><span class="keyword">function</span>(<span class="params"> ary1, ary2 </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( ary1.length !== ary2.length ){</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'ary1 和 ary2 不相等'</span> );</span><br><span class="line"> }</span><br><span class="line"> each( ary1, <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( n !== ary2[ i ] ){</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'ary1 和 ary2 不相等'</span> );</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> alert ( <span class="string">'ary1 和 ary2 相等'</span> );</span><br><span class="line">};</span><br><span class="line">compare( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ], [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span> ] ); <span class="comment">// throw new Error ( 'ary1 和 ary2 不相等' );</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2.外部迭代器实现</span></span><br><span class="line"><span class="keyword">var</span> Iterator = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> current = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">var</span> next = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ current += <span class="number">1</span>; };</span><br><span class="line"> <span class="keyword">var</span> isDone = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">return</span> current >= obj.length; };</span><br><span class="line"> <span class="keyword">var</span> getCurrItem = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="keyword">return</span> obj[ current ]; };</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> next: next,</span><br><span class="line"> isDone: isDone,</span><br><span class="line"> getCurrItem: getCurrItem</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> compare = <span class="function"><span class="keyword">function</span>(<span class="params"> iterator1, iterator2 </span>)</span>{</span><br><span class="line"> <span class="keyword">while</span>( !iterator1.isDone() && !iterator2.isDone() ){</span><br><span class="line"> <span class="keyword">if</span> ( iterator1.getCurrItem() !== iterator2.getCurrItem() ){</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'iterator1 和 iterator2 不相等'</span> );</span><br><span class="line"> }</span><br><span class="line"> iterator1.next();</span><br><span class="line"> iterator2.next();</span><br><span class="line"> }</span><br><span class="line"> alert ( <span class="string">'iterator1 和 iterator2 相等'</span> );</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> iterator1 = Iterator( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] );</span><br><span class="line"><span class="keyword">var</span> iterator2 = Iterator( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] );</span><br><span class="line">compare( iterator1, iterator2 ); <span class="comment">// 输出: iterator1 和 iterator2 相等</span></span><br></pre></td></tr></table></figure><h4 id="4-3-迭代器模式的应用举例"><a href="#4-3-迭代器模式的应用举例" class="headerlink" title="4.3 迭代器模式的应用举例"></a>4.3 迭代器模式的应用举例</h4><p>文件上传模块中,根据不同的浏览器获取相应的上传组件对象:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 最初代码,使用 if-else 一个个进行判断</span></span><br><span class="line"><span class="keyword">var</span> getUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ActiveXObject(<span class="string">"TXFTNActiveX.FTNUpload"</span>); <span class="comment">// IE 上传控件</span></span><br><span class="line"> }<span class="keyword">catch</span>(e){</span><br><span class="line"> <span class="keyword">if</span> ( supportFlash() ){ <span class="comment">// supportFlash 函数未提供</span></span><br><span class="line"> <span class="keyword">var</span> str = <span class="string">'<object type="application/x-shockwave-flash"></object>'</span>;</span><br><span class="line"> <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">var</span> str = <span class="string">'<input name="file" type="file"/>'</span>; <span class="comment">// 表单上传</span></span><br><span class="line"> <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 2. 迭代器:每种获取 upload 对象的方法都封装在各自的函数,使用一个迭代器,迭代获取这些 upload 对象,直到获取到一个可用的为止;每种 upload 对象约定若该函数里面的 upload 对象是可用的,则让函数返回该对象,反之返回 false,提示迭代器继续往后面进行迭代;</span></span><br><span class="line"><span class="keyword">var</span> getActiveUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ActiveXObject( <span class="string">"TXFTNActiveX.FTNUpload"</span> ); <span class="comment">// IE 上传控件</span></span><br><span class="line"> }<span class="keyword">catch</span>(e){</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> getFlashUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( supportFlash() ){ <span class="comment">// supportFlash 函数未提供</span></span><br><span class="line"> <span class="keyword">var</span> str = <span class="string">'<object type="application/x-shockwave-flash"></object>'</span>;</span><br><span class="line"> <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> getFormUpladObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> str = <span class="string">'<input name="file" type="file" class="ui-file"/>'</span>; <span class="comment">// 表单上传</span></span><br><span class="line"> <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 迭代器代码</span></span><br><span class="line"><span class="keyword">var</span> iteratorUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = <span class="built_in">arguments</span>[ i++ ]; ){</span><br><span class="line"> <span class="keyword">var</span> uploadObj = fn();</span><br><span class="line"> <span class="keyword">if</span> ( uploadObj !== <span class="literal">false</span> ){</span><br><span class="line"> <span class="keyword">return</span> uploadObj;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> uploadObj = iteratorUploadObj( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );</span><br></pre></td></tr></table></figure><h4 id="4-4-迭代器模式小结"><a href="#4-4-迭代器模式小结" class="headerlink" title="4.4 迭代器模式小结"></a>4.4 迭代器模式小结</h4><p>迭代器模式是一种相对简单的模式,目前的绝大部分语言都内置了迭代器。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="迭代器模式" scheme="https://geek-lhj.github.io/tags/%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(三)代理模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-3-%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-3-%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:03:01.000Z</published>
<updated>2020-04-11T08:06:10.524Z</updated>
<content type="html"><![CDATA[<p>代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式分为保护代理和虚拟代理,保护代理用于控制不同权限的对象对目标对象的访问,虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建;</p><a id="more"></a><h3 id="3-代理模式"><a href="#3-代理模式" class="headerlink" title="3 代理模式"></a>3 代理模式</h3><p>代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式分为保护代理和虚拟代理,保护代理用于控制不同权限的对象对目标对象的访问,虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建;</p><h4 id="3-1-虚拟代理实现图片预加载"><a href="#3-1-虚拟代理实现图片预加载" class="headerlink" title="3.1 虚拟代理实现图片预加载"></a>3.1 虚拟代理实现图片预加载</h4><blockquote><ol><li>虚拟代理实现图片预加载<br>图片预加载中先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这种场景就很适合使用虚拟代理;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> imgNode = <span class="built_in">document</span>.createElement( <span class="string">'img'</span> );</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( imgNode );</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>{</span><br><span class="line"> imgNode.src = src;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="comment">// 代理对象,在图片被真正加载好之前,页面中将出现一张占位的图 loading.gif, 来提示图片正在加载</span></span><br><span class="line"><span class="keyword">var</span> proxyImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> img = <span class="keyword">new</span> Image;</span><br><span class="line"> img.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> myImage.setSrc( <span class="keyword">this</span>.src );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>{</span><br><span class="line"> myImage.setSrc( <span class="string">'file:///C:/Users/admin/Desktop/loading.gif'</span> );</span><br><span class="line"> img.src = src;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line">proxyImage.setSrc( <span class="string">'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg'</span> );</span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>不用代理的预加载图片函数实现:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> MyImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> imgNode = <span class="built_in">document</span>.createElement( <span class="string">'img'</span> );</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( imgNode );</span><br><span class="line"> <span class="keyword">var</span> img = <span class="keyword">new</span> Image;</span><br><span class="line"> img.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> imgNode.src = img.src;</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>{</span><br><span class="line"> imgNode.src = <span class="string">'file:// /C:/Users/svenzeng/Desktop/loading.gif'</span>;</span><br><span class="line"> img.src = src;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line">MyImage.setSrc( <span class="string">'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg'</span> );</span><br></pre></td></tr></table></figure><p>上段代码中的 MyImage 对象除了负责给 img 节点设置 src 外,还要负责预加载图片。在处理其中一个职责时,有可能因为其强耦合性影响另外一个职责的实现;在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放—封闭原则;假如之后只是从网络上获取一些体积很小的图片,或者根本不再需要预加载,我们希望把预加载图片的这段代码从 MyImage 对象里删掉,这时候就不得不改动 MyImage 对象了。而实际上需要的只是给 img 节点设置 src,预加载图片只是一个锦上添花的功能。如果能把这个操作放在另一个对象里面,自然是一个非常好的方法;于是代理的作用在这里就体现出来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体 MyImage ;</p><p><strong>单一职责原则</strong>:就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏;</p><h4 id="3-2-虚拟代理合并-HTTP-请求"><a href="#3-2-虚拟代理合并-HTTP-请求" class="headerlink" title="3.2 虚拟代理合并 HTTP 请求"></a>3.2 虚拟代理合并 HTTP 请求</h4><p>使用代理函数合并 HTTP 请求实现:使用代理函数收集一段时间之内的请求,最后一次性发送给服务器;</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">id</span>=<span class="string">"1"</span>></span><span class="tag"></<span class="name">input</span>></span>1</span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">id</span>=<span class="string">"2"</span>></span><span class="tag"></<span class="name">input</span>></span>2</span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">id</span>=<span class="string">"3"</span>></span><span class="tag"></<span class="name">input</span>></span>3</span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> synchronousFile = <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'开始同步文件, id 为: '</span> + id ); };</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> proxySynchronousFile = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> cache = [], <span class="comment">// 保存一段时间内需要同步的 ID</span></span><br><span class="line"> timer; <span class="comment">// 定时器</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>{</span><br><span class="line"> cache.push( id );</span><br><span class="line"> <span class="keyword">if</span> ( timer ){ <span class="comment">// 保证不会覆盖已经启动的定时器</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> timer = setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> synchronousFile( cache.join( <span class="string">','</span> ) ); <span class="comment">// 2 秒后发送需要同步的 ID 集合</span></span><br><span class="line"> clearTimeout( timer ); <span class="comment">// 清空定时器</span></span><br><span class="line"> timer = <span class="literal">null</span>;</span><br><span class="line"> cache.length = <span class="number">0</span>; <span class="comment">// 清空 ID 集合</span></span><br><span class="line"> }, <span class="number">2000</span> );</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> checkbox = <span class="built_in">document</span>.getElementsByTagName( <span class="string">'input'</span> );</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, c; c = checkbox[ i++ ]; ){</span><br><span class="line"> c.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( <span class="keyword">this</span>.checked === <span class="literal">true</span> ){</span><br><span class="line"> proxySynchronousFile( <span class="keyword">this</span>.id );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="3-3-缓存代理"><a href="#3-3-缓存代理" class="headerlink" title="3.3 缓存代理"></a>3.3 缓存代理</h4><blockquote><p>缓存代理实例:计算乘积;</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 乘积函数</span></span><br><span class="line"><span class="keyword">var</span> mult = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){</span><br><span class="line"> a = a * <span class="built_in">arguments</span>[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 缓存代理函数</span></span><br><span class="line"><span class="keyword">var</span> proxyMult = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> cache = {};</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> );</span><br><span class="line"> <span class="keyword">if</span> ( args <span class="keyword">in</span> cache ){</span><br><span class="line"> <span class="keyword">return</span> cache[ args ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cache[ args ] = mult.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line">proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ); <span class="comment">// 输出: 24</span></span><br><span class="line">proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ); <span class="comment">// 输出: 24</span></span><br></pre></td></tr></table></figure><blockquote><p>缓存代理用于ajax异步请求数据</p></blockquote><p>在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。</p><h4 id="3-4-高阶函数动态创建代理"><a href="#3-4-高阶函数动态创建代理" class="headerlink" title="3.4 高阶函数动态创建代理"></a>3.4 高阶函数动态创建代理</h4><p>实例:为乘法、加法、减法等创建缓存代理:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**************** 计算乘积 *****************/</span></span><br><span class="line"><span class="keyword">var</span> mult = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){ a = a * <span class="built_in">arguments</span>[i]; }</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line">};</span><br><span class="line"><span class="comment">/**************** 计算加和 *****************/</span></span><br><span class="line"><span class="keyword">var</span> plus = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){ a = a + <span class="built_in">arguments</span>[i]; }</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line">};</span><br><span class="line"><span class="comment">/**************** 创建缓存代理的工厂 *****************/</span></span><br><span class="line"><span class="keyword">var</span> createProxyFactory = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> cache = {};</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> );</span><br><span class="line"> <span class="keyword">if</span> ( args <span class="keyword">in</span> cache ){</span><br><span class="line"> <span class="keyword">return</span> cache[ args ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cache[ args ] = fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> proxyMult = createProxyFactory( mult ), proxyPlus = createProxyFactory( plus );</span><br><span class="line">alert ( proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ) ); <span class="comment">// 输出: 24</span></span><br><span class="line">alert ( proxyPlus( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ) ); <span class="comment">// 输出: 10</span></span><br></pre></td></tr></table></figure><h4 id="3-5-其他代理模式的应用"><a href="#3-5-其他代理模式的应用" class="headerlink" title="3.5 其他代理模式的应用"></a>3.5 其他代理模式的应用</h4><ul><li>防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。</li><li>远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另一个虚拟机中的对象。</li><li>保护代理:用于对象应该有不同访问权限的情况。</li><li>智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。</li><li>写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体, DLL(操作系统中的动态链接库)是其典型运用场景。</li></ul><h4 id="3-6-代理模式小结"><a href="#3-6-代理模式小结" class="headerlink" title="3.6 代理模式小结"></a>3.6 代理模式小结</h4><p>代理模式在 JavaScript 开发中最常用的是虚拟代理和缓存代理。一般在编写代码的时候不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理模式代码。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式分为保护代理和虚拟代理,保护代理用于控制不同权限的对象对目标对象的访问,虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="代理模式" scheme="https://geek-lhj.github.io/tags/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(二)策略模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2-%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-2-%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:02:01.000Z</published>
<updated>2020-04-11T08:06:14.018Z</updated>
<content type="html"><![CDATA[<p>策略模式定义: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换;</p><a id="more"></a><h3 id="2-策略模式"><a href="#2-策略模式" class="headerlink" title="2 策略模式"></a>2 策略模式</h3><p>策略模式定义: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换;</p><h4 id="2-1-策略模式"><a href="#2-1-策略模式" class="headerlink" title="2.1 策略模式"></a>2.1 策略模式</h4><p>策略模式的目的就是将算法的使用与算法的实现分离开来,将不变的部分和变化的部分隔开是每个设计模式的主题;</p><p>一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类 Context, Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用;</p><blockquote><p>实例:积分模式(基础分乘上对于的等级倍数)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 最简单代码实现</span></span><br><span class="line"><span class="keyword">var</span> calculateBonus = <span class="function"><span class="keyword">function</span>(<span class="params"> level, base </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( level === <span class="string">'S'</span> ){ <span class="keyword">return</span> base * <span class="number">4</span>; }</span><br><span class="line"> <span class="keyword">if</span> ( level === <span class="string">'A'</span> ){ <span class="keyword">return</span> base * <span class="number">3</span>; }</span><br><span class="line">};</span><br><span class="line">calculateBonus( <span class="string">'B'</span>, <span class="number">10</span> ); <span class="comment">// 输出: 40</span></span><br><span class="line">calculateBonus( <span class="string">'S'</span>, <span class="number">5</span> ); <span class="comment">// 输出: 15</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 模仿传统面向对象语言中的策略模式实现</span></span><br><span class="line"><span class="keyword">var</span> performanceS = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line">performanceS.prototype.calculate = <span class="function"><span class="keyword">function</span>(<span class="params"> base </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> base * <span class="number">4</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> performanceA = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line"> performanceA.prototype.calculate = <span class="function"><span class="keyword">function</span>(<span class="params"> base </span>)</span>{</span><br><span class="line"><span class="keyword">return</span> base * <span class="number">3</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 定义总分类 Bonus:</span></span><br><span class="line"><span class="keyword">var</span> Bonus = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.base = <span class="literal">null</span>; <span class="comment">// 原始值</span></span><br><span class="line"> <span class="keyword">this</span>.strategy = <span class="literal">null</span>; <span class="comment">// 等级对应的策略对象</span></span><br><span class="line">};</span><br><span class="line">Bonus.prototype.setSalary = <span class="function"><span class="keyword">function</span>(<span class="params"> base </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.base = base; <span class="comment">// 设置基础值</span></span><br><span class="line">};</span><br><span class="line">Bonus.prototype.setStrategy = <span class="function"><span class="keyword">function</span>(<span class="params"> strategy </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.strategy = strategy; <span class="comment">// 设置等级对应的策略对象</span></span><br><span class="line">};</span><br><span class="line">Bonus.prototype.getBonus = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 取得总分数</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.strategy.calculate( <span class="keyword">this</span>.base ); <span class="comment">// 把计算分数的操作委托给对应的策略对象</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="2-2-JavaScript-版本的策略模式"><a href="#2-2-JavaScript-版本的策略模式" class="headerlink" title="2.2 JavaScript 版本的策略模式"></a>2.2 JavaScript 版本的策略模式</h4><p>在 JavaScript 语言中,函数也是对象,所以更简单和直接的做法是把 <code>strategy</code> 直接定义为函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> strategies = {</span><br><span class="line"> <span class="string">"S"</span>: <span class="function"><span class="keyword">function</span>(<span class="params"> base </span>)</span>{ <span class="keyword">return</span> base * <span class="number">4</span>; },</span><br><span class="line"> <span class="string">"A"</span>: <span class="function"><span class="keyword">function</span>(<span class="params"> base </span>)</span>{ <span class="keyword">return</span> base * <span class="number">3</span>; },</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> calculateBonus = <span class="function"><span class="keyword">function</span>(<span class="params"> level, base </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> strategies[ level ]( base );</span><br><span class="line">};</span><br><span class="line"><span class="built_in">console</span>.log( calculateBonus( <span class="string">'S'</span>, <span class="number">10</span> ) ); <span class="comment">// 输出: 40</span></span><br><span class="line"><span class="built_in">console</span>.log( calculateBonus( <span class="string">'A'</span>, <span class="number">5</span> ) ); <span class="comment">// 输出: 15</span></span><br></pre></td></tr></table></figure><h4 id="2-3-策略模式实现表单校验"><a href="#2-3-策略模式实现表单校验" class="headerlink" title="2.3 策略模式实现表单校验"></a>2.3 策略模式实现表单校验</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 步骤1. 封装策略对象</span></span><br><span class="line"><span class="keyword">var</span> strategies = {</span><br><span class="line"> isNonEmpty: <span class="function"><span class="keyword">function</span>(<span class="params"> value, errorMsg </span>)</span>{ <span class="comment">// 不为空</span></span><br><span class="line"> <span class="keyword">if</span> ( value === <span class="string">''</span> ){ <span class="keyword">return</span> errorMsg ; }</span><br><span class="line"> },</span><br><span class="line"> minLength: <span class="function"><span class="keyword">function</span>(<span class="params"> value, length, errorMsg </span>)</span>{ <span class="comment">// 限制最小长度</span></span><br><span class="line"> <span class="keyword">if</span> ( value.length < length ){ <span class="keyword">return</span> errorMsg; }</span><br><span class="line"> },</span><br><span class="line"> isMobile: <span class="function"><span class="keyword">function</span>(<span class="params"> value, errorMsg </span>)</span>{ <span class="comment">// 手机号码格式</span></span><br><span class="line"> <span class="keyword">if</span> ( !<span class="regexp">/(^1[3|5|8][0-9]{9}$)/</span>.test( value ) ){</span><br><span class="line"> <span class="keyword">return</span> errorMsg;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 步骤2. 实现 Validator 类(作为 Context,负责接收用户的请求并委托给 strategy 对象)</span></span><br><span class="line"><span class="keyword">var</span> Validator = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.cache = []; <span class="comment">// 保存校验规则</span></span><br><span class="line">};</span><br><span class="line">Validator.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"> dom, rule, errorMsg </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> ary = rule.split( <span class="string">':'</span> ); <span class="comment">// 把 strategy 和参数分开</span></span><br><span class="line"> <span class="keyword">this</span>.cache.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 把校验的步骤用空函数包装起来,并且放入 cache</span></span><br><span class="line"> <span class="keyword">var</span> strategy = ary.shift(); <span class="comment">// 用户挑选的 strategy</span></span><br><span class="line"> ary.unshift( dom.value ); <span class="comment">// 把 input 的 value 添加进参数列表</span></span><br><span class="line"> ary.push( errorMsg ); <span class="comment">// 把 errorMsg 添加进参数列表</span></span><br><span class="line"> <span class="keyword">return</span> strategies[ strategy ].apply( dom, ary );</span><br><span class="line"> });</span><br><span class="line">};</span><br><span class="line">Validator.prototype.start = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, validatorFunc; validatorFunc = <span class="keyword">this</span>.cache[ i++ ]; ){</span><br><span class="line"> <span class="keyword">var</span> msg = validatorFunc(); <span class="comment">// 开始校验,并取得校验后的返回信息</span></span><br><span class="line"> <span class="keyword">if</span> ( msg ){ <span class="comment">// 如果有确切的返回值,说明校验没有通过</span></span><br><span class="line"> <span class="keyword">return</span> msg;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 步骤3. 向 Validator 类发送校验的请求</span></span><br><span class="line"><span class="keyword">var</span> validataFunc = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> validator = <span class="keyword">new</span> Validator(); <span class="comment">// 创建一个 validator 对象</span></span><br><span class="line"> <span class="comment">/***************添加一些校验规则****************/</span></span><br><span class="line"> validator.add( registerForm.userName, <span class="string">'isNonEmpty'</span>, <span class="string">'用户名不能为空'</span> );</span><br><span class="line"> validator.add( registerForm.password, <span class="string">'minLength:6'</span>, <span class="string">'密码长度不能少于 6 位'</span> );</span><br><span class="line"> validator.add( registerForm.phoneNumber, <span class="string">'isMobile'</span>, <span class="string">'手机号码格式不正确'</span> );</span><br><span class="line"> <span class="keyword">var</span> errorMsg = validator.start(); <span class="comment">// 获得校验结果</span></span><br><span class="line"> <span class="keyword">return</span> errorMsg; <span class="comment">// 返回校验结果</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> registerForm = <span class="built_in">document</span>.getElementById( <span class="string">'registerForm'</span> );</span><br><span class="line">registerForm.onsubmit = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> errorMsg = validataFunc(); <span class="comment">// 如果 errorMsg 有确切的返回值,说明未通过校验</span></span><br><span class="line"> <span class="keyword">if</span> ( errorMsg ){</span><br><span class="line"> alert ( errorMsg );</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 阻止表单提交</span></span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="2-4-策略模式的优缺点"><a href="#2-4-策略模式的优缺点" class="headerlink" title="2.4 策略模式的优缺点"></a>2.4 策略模式的优缺点</h4><blockquote><p>策略模式的优点:</p></blockquote><ul><li>策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;</li><li>策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展;</li><li>策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作;</li><li>在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案;</li></ul><blockquote><p>策略模式的缺点:</p></blockquote><ul><li>使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context 中要好;</li><li>使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy ;由于 strategy 要向客户暴露它的所有实现,这是违反最少知识原则;</li></ul><h4 id="2-5-一等函数对象与策略模式"><a href="#2-5-一等函数对象与策略模式" class="headerlink" title="2.5 一等函数对象与策略模式"></a>2.5 一等函数对象与策略模式</h4><p>之前的策略模式示例中,既有模拟传统面向对象语言的版本,也有针对 <code>JavaScript</code> 语言的特有实现。在以类为中心的传统面向对象语言中,不同的算法或者行为被封装在各个策略类中, <code>Context</code> 将请求委托给这些策略对象,这些策略对象会根据请求返回不同的执行结果,这样便能表现出对象的多态性;</p><p>在 JavaScript 中,除了使用类来封装算法和行为之外,直接使用函数也是一种选择。这些“算法”可以被封装到函数中并且四处传递,也就是常说的“高阶函数”。实际上在 <code>JavaScript</code> 这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函数发出“调用”的消息时,不同的函数会返回不同的执行结果;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> S = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>{ <span class="keyword">return</span> salary * <span class="number">4</span>; };</span><br><span class="line"><span class="keyword">var</span> A = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>{ <span class="keyword">return</span> salary * <span class="number">3</span>; };</span><br><span class="line"><span class="keyword">var</span> B = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>{ <span class="keyword">return</span> salary * <span class="number">2</span>; };</span><br><span class="line"><span class="keyword">var</span> calculateBonus = <span class="function"><span class="keyword">function</span>(<span class="params"> func, salary </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> func( salary );</span><br><span class="line">};</span><br><span class="line">calculateBonus( S, <span class="number">10</span> ); <span class="comment">// 输出: 40</span></span><br></pre></td></tr></table></figure><h4 id="2-6-策略模式小结"><a href="#2-6-策略模式小结" class="headerlink" title="2.6 策略模式小结"></a>2.6 策略模式小结</h4><p>本小节既有接近传统面向对象语言的策略模式实现,也有更适合 JavaScript 语言的策略模式版本。在 JavaScript 语言的策略模式中,策略类往往被函数所代替,这时策略模式就成为一种“隐形”的模式。</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>策略模式定义: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="策略模式" scheme="https://geek-lhj.github.io/tags/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式(一)单例模式</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-1-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-1-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</id>
<published>2019-10-10T00:01:01.000Z</published>
<updated>2020-04-11T08:06:33.805Z</updated>
<content type="html"><![CDATA[<p>单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点;</p><a id="more"></a><h3 id="1-单例模式"><a href="#1-单例模式" class="headerlink" title="1 单例模式"></a>1 单例模式</h3><p>单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点;</p><h4 id="1-1-实现单例模式"><a href="#1-1-实现单例模式" class="headerlink" title="1.1 实现单例模式"></a>1.1 实现单例模式</h4><p>用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Singleton = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.instance = <span class="literal">null</span>;</span><br><span class="line">};</span><br><span class="line">Singleton.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> alert ( <span class="keyword">this</span>.name );</span><br><span class="line">};</span><br><span class="line">Singleton.getInstance = <span class="function"><span class="keyword">function</span>(<span class="params"> name </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !<span class="keyword">this</span>.instance ){</span><br><span class="line"> <span class="keyword">this</span>.instance = <span class="keyword">new</span> Singleton( name );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.instance;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> a = Singleton.getInstance( <span class="string">'sven1'</span> );</span><br><span class="line"><span class="keyword">var</span> b = Singleton.getInstance( <span class="string">'sven2'</span> );</span><br><span class="line">alert ( a === b ); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h4 id="1-2-透明的单例模式"><a href="#1-2-透明的单例模式" class="headerlink" title="1.2 透明的单例模式"></a>1.2 透明的单例模式</h4><p>实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> CreateDiv = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> instance;</span><br><span class="line"> <span class="keyword">var</span> CreateDiv = <span class="function"><span class="keyword">function</span>(<span class="params"> html </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( instance ){</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.html = html;</span><br><span class="line"> <span class="keyword">this</span>.init();</span><br><span class="line"> <span class="keyword">return</span> instance = <span class="keyword">this</span>;</span><br><span class="line"> };</span><br><span class="line"> CreateDiv.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = <span class="keyword">this</span>.html;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> CreateDiv;</span><br><span class="line">})();</span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> CreateDiv( <span class="string">'sven1'</span> );</span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> CreateDiv( <span class="string">'sven2'</span> );</span><br><span class="line">alert ( a === b ); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>在这段代码中, CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始化 init 方法,第二是保证只有一个对象。虽然目前还没有接触过“单一职责原则”的概念,但可以明确的是,这是一种不好的做法,至少这个构造函数看起来很奇怪。</p><h4 id="1-3-用代理实现单例模式(也是缓存代理的应用)"><a href="#1-3-用代理实现单例模式(也是缓存代理的应用)" class="headerlink" title="1.3 用代理实现单例模式(也是缓存代理的应用)"></a>1.3 用代理实现单例模式(也是缓存代理的应用)</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> CreateDiv = <span class="function"><span class="keyword">function</span>(<span class="params"> html </span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.html = html;</span><br><span class="line"> <span class="keyword">this</span>.init();</span><br><span class="line">};</span><br><span class="line">CreateDiv.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = <span class="keyword">this</span>.html;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 引入代理类 proxySingletonCreateDiv:</span></span><br><span class="line"><span class="keyword">var</span> ProxySingletonCreateDiv = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> instance;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"> html </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !instance ){</span><br><span class="line"> instance = <span class="keyword">new</span> CreateDiv( html );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> ProxySingletonCreateDiv( <span class="string">'sven1'</span> );</span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> ProxySingletonCreateDiv( <span class="string">'sven2'</span> );</span><br><span class="line">alert ( a === b );</span><br></pre></td></tr></table></figure><h4 id="1-4-JavaScript-中的单例模式"><a href="#1-4-JavaScript-中的单例模式" class="headerlink" title="1.4 JavaScript 中的单例模式"></a>1.4 JavaScript 中的单例模式</h4><p>前几种单例模式的实现是接近传统面向对象语言中的实现,单例对象是从“类”中创建而来。如在 Java 中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的;而JavaScript 其实是一门无类( class-free)语言,因此,生搬单例模式的概念并无意义,JavaScript中只需要创建一个<code>唯一</code>的对象即可;</p><p>JavaScript 开发中经常会把全局变量当成单例来使用, 但全局变量存在很多问题,很容易造成命名空间污染,因而需要尽量减少全局变量使用:</p><ol><li>适当使用命名空间,以对象字面量的方式:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> namespace1 = {</span><br><span class="line"> a: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> alert (<span class="number">1</span>);</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure></li><li>使用闭包封装私有变量:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> user = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> name = <span class="string">'sven'</span>, age = <span class="number">29</span>;</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> getUserInfo: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> name + <span class="string">'-'</span> + age;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br></pre></td></tr></table></figure></li></ol><h4 id="1-5-惰性单例"><a href="#1-5-惰性单例" class="headerlink" title="1.5 惰性单例"></a>1.5 惰性单例</h4><p>惰性单例指的是在需要的时候才创建对象实例;</p><blockquote><p>实例:创建登录浮窗,实现单例模式,并且在点击的时候才创建</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> createLoginLayer = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> div;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( !div ){</span><br><span class="line"> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = <span class="string">'我是登录浮窗'</span>;</span><br><span class="line"> div.style.display = <span class="string">'none'</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> div;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="built_in">document</span>.getElementById( <span class="string">'loginBtn'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> loginLayer = createLoginLayer();</span><br><span class="line"> loginLayer.style.display = <span class="string">'block'</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="1-6-通用的惰性单例"><a href="#1-6-通用的惰性单例" class="headerlink" title="1.6 通用的惰性单例"></a>1.6 通用的惰性单例</h4><p>上部分代码违反单一职责原则的,创建对象和管理单例的逻辑都放在 <code>createLoginLayer</code> 对象内部,下次需要创建页面中唯一的 <code>iframe</code> ,或者 <code>script</code> 标签,就得把 <code>createLoginLayer</code> 函数几乎照抄一遍;因而考虑把不变的部分隔离出,即为管理单例的逻辑,用一个变量来标志是否创建过对象,若是则在下次直接返回这个已经创建好的对象,再把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在 <code>getSingle</code> 函数内部,创建对象的方法 <code>fn</code> 被当成参数动态传入 <code>getSingle</code> 函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> getSingle = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> result;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> result || ( result = fn .apply(<span class="keyword">this</span>, <span class="built_in">arguments</span> ) );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> createLoginLayer = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> );</span><br><span class="line"> div.innerHTML = <span class="string">'登录浮窗'</span>;</span><br><span class="line"> div.style.display = <span class="string">'none'</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild( div );</span><br><span class="line"> <span class="keyword">return</span> div;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> createSingleLoginLayer = getSingle( createLoginLayer );</span><br><span class="line"><span class="built_in">document</span>.getElementById( <span class="string">'loginBtn'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> loginLayer = createSingleLoginLayer();</span><br><span class="line"> loginLayer.style.display = <span class="string">'block'</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="1-7-单例模式小结"><a href="#1-7-单例模式小结" class="headerlink" title="1.7 单例模式小结"></a>1.7 单例模式小结</h4><p>单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。更奇妙的是,创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力;</p><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol>]]></content>
<summary type="html">
<p>单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点;</p>
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="单例模式" scheme="https://geek-lhj.github.io/tags/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>JavaScript设计模式基础知识</title>
<link href="https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
<id>https://geek-lhj.github.io/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</id>
<published>2019-10-10T00:00:01.000Z</published>
<updated>2020-04-11T08:05:03.942Z</updated>
<content type="html"><![CDATA[<p><img src="/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5.png" alt="JavaScript设计模式与开发实践"></p><!-- 设计模式是在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案,通俗说设计模式是在某种场合下对某个问题的一种解决方案。模式是一些经过了大量实际项目验证的优秀解决方案,熟悉这些模式的程序员会对某些模式的理解也许形成了条件反射,当合适的场景出现时,就可以很快地找到某种模式作为解决方案;所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”,一个程序的设计总是可以分为可变的部分和不变的部分,将可变的封装起来;模式应该用在正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道是否算正确应用; --><a id="more"></a><blockquote><p>本文主要参考了<a href="https://book.douban.com/subject/26382780/" target="_blank" rel="noopener">《JavaScript设计模式和开发实践》</a>一书</p></blockquote><blockquote><p>系列链接</p></blockquote><ol><li><a href="/2020/10/10/JavaScript设计模式基础知识">JavaScript 设计模式基础知识</a></li><li><a href="/2020/10/10/JavaScript设计模式-1-单例模式">JavaScript 设计模式(一)单例模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-2-策略模式">JavaScript 设计模式(二)策略模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-3-代理模式">JavaScript 设计模式(三)代理模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-4-迭代器模式">JavaScript 设计模式(四)迭代器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-5-发布订阅模式">JavaScript 设计模式(五)发布订阅模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-6-命令模式">JavaScript 设计模式(六)命令模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-7-组合模式">JavaScript 设计模式(七)组合模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-8-模板方法模式">JavaScript 设计模式(八)模板方法模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-9-享元模式">JavaScript 设计模式(九)享元模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-10-职责链模式">JavaScript 设计模式(十)职责链模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-11-中介者模式">JavaScript 设计模式(十一)中介者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-12-装饰者模式">JavaScript 设计模式(十二)装饰者模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-13-状态模式">JavaScript 设计模式(十三)状态模式</a></li><li><a href="/2020/10/10/JavaScript设计模式-14-适配器模式">JavaScript 设计模式(十四)适配器模式</a></li><li><a href="/2020/10/10/JavaScript设计模式设计原则">JavaScript 设计模式(下)设计原则</a></li><li><a href="https://github.com/Geek-LHJ/JavaScript-design-pattern" target="_blank" rel="noopener">JavaScript 设计模式练习代码</a></li></ol><blockquote><p>设计模式的一些知识:</p></blockquote><ul><li><strong>设计模式的定义</strong>:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案,通俗说设计模式是在某种场合下对某个问题的一种解决方案;</li><li><strong>学习模式的作用</strong>:模式是一些经过了大量实际项目验证的优秀解决方案,熟悉这些模式的程序员会对某些模式的理解也许形成了条件反射,当合适的场景出现时,就可以很快地找到某种模式作为解决方案;</li><li><strong>设计模式的适用性</strong>:所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”,一个程序的设计总是可以分为可变的部分和不变的部分,将可变的封装起来;模式应该用在正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道是否算正确应用;</li><li><strong>分辨模式的关键是意图而不是结构</strong>:有很多模式的类图和结构确实很相似,但这不太重要,辨别模式的关键是这个模式出现的场景,以及为我们解决了什么问题,例如区别代理模式和装饰者模式,策略模式和状态模式,策略模式和智能命令模式;</li></ul><h2 id="一-基础知识"><a href="#一-基础知识" class="headerlink" title="一. 基础知识"></a>一. 基础知识</h2><p>JavaScript 没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承;</p><h3 id="1-1-面向对象的JavaScript"><a href="#1-1-面向对象的JavaScript" class="headerlink" title="1.1 面向对象的JavaScript"></a>1.1 面向对象的JavaScript</h3><h4 id="1-1-1-动态类型语言和鸭子类型"><a href="#1-1-1-动态类型语言和鸭子类型" class="headerlink" title="1.1.1 动态类型语言和鸭子类型"></a>1.1.1 动态类型语言和鸭子类型</h4><blockquote><p>静态类型语言: 编译时便已确定变量的类型;</p></blockquote><p><strong>优点</strong>:编译时就能发现类型不匹配的错误,编辑器可以帮助提前避免程序在运行期间有可能发生的一些错误;</p><p><strong>缺点</strong>:依照强契约来编写程序,为每个变量规定数据类型,类型的声明也会增加更多的代码;</p><blockquote><p>动态类型语言:到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型;</p></blockquote><p><strong>优点</strong>:编写的代码数量更少,整体代码量越少,专注于逻辑表达;</p><p><strong>缺点</strong>:无法保证变量的类型,从而在程序的运行期有可能发生跟类型相关的错误;</p><blockquote><p>鸭子类型:只关注对象的行为,而不关注对象本身,也就是关注 <strong>HAS-A</strong>, 而不是 <strong>IS-A</strong>;</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> duck = {</span><br><span class="line"> duckSinging: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'嘎嘎嘎'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> chicken = {</span><br><span class="line"> duckSinging: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="built_in">console</span>.log( <span class="string">'嘎嘎嘎'</span> ); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> choir = [];</span><br><span class="line"><span class="keyword">var</span> joinChoir = <span class="function"><span class="keyword">function</span>(<span class="params"> animal </span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( animal && <span class="keyword">typeof</span> animal.duckSinging === <span class="string">'function'</span> ){</span><br><span class="line"> choir.push( animal );</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'恭喜加入, 已有成员数量:'</span> + choir.length );</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line">joinChoir( duck );</span><br><span class="line">joinChoir( chicken );</span><br></pre></td></tr></table></figure><p>在动态类型语言的面向对象设计中,鸭子类型的概念至关重要。利用鸭子类型的思想,不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“<strong>面向接口编程,而不是面向实现编程</strong>”;例如,一个对象若有 <strong>push</strong> 和 <strong>pop</strong> 方法,并且这些方法提供了正确的实现,它就可以被当作栈来使用。一个对象如果有 <strong>length</strong> 属性,也可以依照下标来存取属性(最好还要拥有 <strong>slice</strong> 和 <strong>splice</strong> 等方法),这个对象就可以被当作数组来使用;</p><h4 id="1-1-2-多态"><a href="#1-1-2-多态" class="headerlink" title="1.1.2 多态"></a>1.1.2 多态</h4><blockquote><p>含义:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。</p></blockquote><p>多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与 “可能改变的事物”分离开来;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> makeSound = <span class="function"><span class="keyword">function</span>(<span class="params"> animal </span>)</span>{</span><br><span class="line"> animal.sound();</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> Duck = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line">Duck.prototype.sound = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'嘎嘎嘎'</span> );</span><br><span class="line">};</span><br><span class="line">makeSound( <span class="keyword">new</span> Duck() ); <span class="comment">// 嘎嘎嘎</span></span><br><span class="line"><span class="keyword">var</span> Chicken = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</span><br><span class="line">Chicken.prototype.sound = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'咯咯咯'</span> );</span><br><span class="line">};</span><br><span class="line">makeSound( <span class="keyword">new</span> Chicken() ); <span class="comment">// 咯咯咯</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>多态最根本的作用</strong>就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句;</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> googleMap = {</span><br><span class="line"> show: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="built_in">console</span>.log(<span class="string">"开始渲染谷歌地图"</span>); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> baiduMap = {</span><br><span class="line"> show: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="built_in">console</span>.log(<span class="string">"开始渲染百度地图"</span>); }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> renderMap = <span class="function"><span class="keyword">function</span>(<span class="params">map</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (map.show <span class="keyword">instanceof</span> <span class="built_in">Function</span>) { map.show(); }</span><br><span class="line">};</span><br><span class="line">renderMap( googleMap ); <span class="comment">// 输出:开始渲染谷歌地图</span></span><br><span class="line">renderMap( baiduMap ); <span class="comment">// 输出:开始渲染百度地图</span></span><br></pre></td></tr></table></figure><p>绝大部分设计模式的实现都离不开多态性的思想,例如:</p><ul><li><strong>命令模式</strong>:请求被封装在一些命令对象中,这使得命令的调用者和命令的接收者可以完全解耦开来,当调用命令的 <code>execute</code> 方法时,不同的命令会做不同的事情,从而会产生不同的执行结果。而做这些事情的过程是早已被封装在命令对象内部的,作为调用命令的客户,根本不必去关心命令执行的具体过程。</li><li><strong>组合模式</strong>:多态性使得客户可以完全忽略组合对象和叶节点对象之前的区别,这正是组合模式最大的作用所在。对组合对象和叶节点对象发出同一个消息的时候,它们会各自做自己应该做的事情,组合对象把消息继续转发给下面的叶节点对象,叶节点对象则会对这些消息作出真实的反馈。</li><li><strong>策略模式</strong>: <code>Context</code> 并没有执行算法的能力,而是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出“计算”的消息时,它们会返回各自不同的计算结果。</li></ul><p><code>JavaScript</code> 是将函数作为一等对象的语言,函数本身也是对象,函数用来封装行为并且能够被四处传递。当对一些函数发出“调用”的消息时,这些函数会返回不同的执行结果,这是“多态性”的一种体现,也是很多设计模式在 <code>JavaScript</code> 中可以用高阶函数来代替实现的原因。</p><h4 id="1-1-3-封装"><a href="#1-1-3-封装" class="headerlink" title="1.1.3 封装"></a>1.1.3 封装</h4><p>封装的目的是将信息隐藏,封装包括封装数据、封装实现、封装类型和封装变化;</p><ol><li>封装数据:JavaScript 并没有提供对这些关键字的支持,只能依赖变量的作用域来实现封装特性;ECMAScript 6 中可以使用<code>let</code>和<code>Symbol</code>实现;</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用函数来创建作用域</span></span><br><span class="line"><span class="keyword">var</span> myObject = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> __name = <span class="string">'sven'</span>; <span class="comment">// 私有( private)变量</span></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> getName: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 公开( public)方法</span></span><br><span class="line"> <span class="keyword">return</span> __name;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="built_in">console</span>.log( myObject.getName() ); <span class="comment">// 输出: sven</span></span><br><span class="line"><span class="built_in">console</span>.log( myObject.__name ) <span class="comment">// 输出: undefined</span></span><br></pre></td></tr></table></figure><ol start="2"><li>封装实现细节:封装使得对象内部的变化对其他对象而言是透明的,不可见的。对象对它自己的行为负责。其他对象或者用户都不关心它的内部实现。封装使得对象之间的耦合变松散,对象之间只通过暴露的 API 接口来通信。当我们修改一个对象时,可以随意地修改它的内部实现,只要对外的接口没有变化,就不会影响到程序的其他功能;</li></ol><blockquote><p>如迭代器,其作用是在不暴露一个聚合对象的内部表示的前提下,提供一种方式来顺序访问这个聚合对象。如编写了一个 <code>each</code> 函数,它的作用就是遍历一个聚合对象,使用这个 <code>each</code> 函数的人不用关心它的内部是怎样实现的,只要它提供的功能正确便可以。即使 <code>each</code> 函数修改了内部源代码,只要对外的接口或者调用方式没有变化,用户就不用关心它内部实现的改变;</p></blockquote><ol start="3"><li>封装类型:是静态类型语言中一种重要的封装方式。一般而言,封装类型是通过抽象类和接口来进行的。把对象的真正类型隐藏在抽象类或者接口之后,相比对象的类型,客户更关心对象的行为。在许多静态语言的设计模式中,想方设法地去隐藏对象的类型,也是促使这些模式诞生的原因之一。比如工厂方法模式、组合模式等;</li></ol><blockquote><p>在 JavaScript 中,并没有对抽象类和接口的支持。 JavaScript 本身也是一门类型模糊的语言。在封装类型方面, JavaScript 没有能力,也没有必要做得更多。对于 JavaScript 的设计模式实现来说,不区分类型是一种失色,也可以说是一种解脱。</p></blockquote><ol start="4"><li>封装变化:封装变化是设计模式角度出发的更重要的层面体现;</li></ol><blockquote><p>《设计模式》一书中共归纳总结了 23种设计模式。从意图上区分, 这 23 种设计模式分别被划分为创建型模式、结构型模式和行为型模式。拿创建型模式来说,要创建一个对象,是一种抽象行为,而具体创建什么对象则是可以变化的,创建型模式的目的就是封装创建对象的变化。而结构型模式封装的是对象之间的组合关系。行为型模式封装的是对象的行为变化。</p></blockquote><blockquote><p>通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易。这可以最大程度地保证程序的稳定性和可扩展性。</p></blockquote><h4 id="1-1-4-原型模式和基于原型继承的JavaScript对象系统"><a href="#1-1-4-原型模式和基于原型继承的JavaScript对象系统" class="headerlink" title="1.1.4 原型模式和基于原型继承的JavaScript对象系统"></a>1.1.4 原型模式和基于原型继承的JavaScript对象系统</h4><p>JavaScript 也同样遵守这些原型编程的基本规则:</p><ul><li>所有的数据都是对象:<ul><li>JavaScript根对象是 <code>Object.prototype</code> 对象,可以利用 ECMAScript 5 提供的 <code>Object.getPrototypeOf</code> 来查看这两个对象的原型:<code>console.log( Object.getPrototypeOf( {} ) === Object.prototype ); // 输出: true</code></li></ul></li><li>要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它:<ul><li>JavaScript中显式地调用 <code>var obj1 = new Object()</code>或者 <code>var obj2 = {}</code>,引擎内部会从<code>Object.prototype</code> 上面克隆一个对象出来</li></ul></li><li>对象会记住它的原型:<ul><li>JavaScript 的对象有一个名为<strong>proto</strong>的隐藏属性,该对象的<code>__proto__</code>属性默认会指向它的构造器的原型对象,即<code>{Constructor}.prototype</code>;</li></ul></li><li>如果对象无法响应某个请求,它会把这个请求委托给它自己的原型;</li></ul><h3 id="1-2-this、-call-和-apply"><a href="#1-2-this、-call-和-apply" class="headerlink" title="1.2 this、 call 和 apply"></a>1.2 this、 call 和 apply</h3><h4 id="1-2-1-this-指向"><a href="#1-2-1-this-指向" class="headerlink" title="1.2.1 this 指向"></a>1.2.1 this 指向</h4><p>JavaScript 的 <code>this</code> 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境,即函数中的 <code>this</code> 基本都指向函数的调用者;</p><p>this 的指向大致可以分为以下 4 种:</p><ul><li>作为对象的方法调用:当函数作为对象的方法被调用时, this 指向该对象;<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = {</span><br><span class="line"> a: <span class="number">1</span>,</span><br><span class="line"> getA: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> alert ( <span class="keyword">this</span> === obj ); <span class="comment">// 输出: true</span></span><br><span class="line"> alert ( <span class="keyword">this</span>.a ); <span class="comment">// 输出: 1</span></span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">obj.getA();</span><br></pre></td></tr></table></figure></li><li>作为普通函数调用:this 总是指向全局对象,在浏览器的 JavaScript 里,这个全局对象是 window 对象;<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.name = <span class="string">'globalName'</span>;</span><br><span class="line"><span class="keyword">var</span> myObject = {</span><br><span class="line"> name: <span class="string">'sven'</span>,</span><br><span class="line"> getName: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> getName = myObject.getName;</span><br><span class="line"><span class="built_in">console</span>.log( getName() ); <span class="comment">// globalName</span></span><br></pre></td></tr></table></figure></li><li>构造器调用:构造器里的 <code>this</code> 就指向返回的这个对象;当 <code>new</code> 调用构造器时,且构造器显式地返回了一个 <code>object</code> 类型的对象,那么最终会返回这个对象;</li><li><code>Function.prototype.call</code> 或 <code>Function.prototype.apply</code> 调用:动态地改变传入函数的 this;<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj1 = {</span><br><span class="line"> name: <span class="string">'aaa'</span>,</span><br><span class="line"> getName: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> obj2 = {</span><br><span class="line"> name: <span class="string">'bbb'</span></span><br><span class="line">};</span><br><span class="line"><span class="built_in">console</span>.log( obj1.getName() ); <span class="comment">// 输出: aaa</span></span><br><span class="line"><span class="built_in">console</span>.log( obj1.getName.call( obj2 ) ); <span class="comment">// 输出: bbb</span></span><br></pre></td></tr></table></figure></li></ul><p>例子: 改写<code>document.getElementById</code>方法(注意其中的<code>this</code>指向<code>document</code>)</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById = (<span class="function"><span class="keyword">function</span>(<span class="params"> func </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> func.apply( <span class="built_in">document</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line">})( <span class="built_in">document</span>.getElementById );</span><br><span class="line"><span class="keyword">var</span> getId = <span class="built_in">document</span>.getElementById;</span><br><span class="line"><span class="keyword">var</span> div = getId( <span class="string">'div1'</span> );</span><br><span class="line">alert (div.id); <span class="comment">// 输出: div1</span></span><br></pre></td></tr></table></figure><h4 id="1-2-2-call-和-apply"><a href="#1-2-2-call-和-apply" class="headerlink" title="1.2.2 call 和 apply"></a>1.2.2 call 和 apply</h4><blockquote><ol><li>call和apply的区别:传入参数形式的不同;</li></ol></blockquote><ul><li>apply: 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组, apply 方法把这个集合中的元素作为参数传递给被调用的函数;<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> func = <span class="function"><span class="keyword">function</span>(<span class="params"> a, b, c </span>)</span>{</span><br><span class="line"> alert ( [ a, b, c ] ); <span class="comment">// 输出 [ 1, 2, 3 ]</span></span><br><span class="line">};</span><br><span class="line">func.apply( <span class="literal">null</span>, [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] );</span><br></pre></td></tr></table></figure></li><li>call:call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,<br>从第二个参数开始往后,每个参数被依次传入函数:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> func = <span class="function"><span class="keyword">function</span>(<span class="params"> a, b, c </span>)</span>{</span><br><span class="line"> alert ( [ a, b, c ] ); <span class="comment">// 输出 [ 1, 2, 3 ]</span></span><br><span class="line">};</span><br><span class="line">func.call( <span class="literal">null</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> );</span><br></pre></td></tr></table></figure></li></ul><blockquote><ol start="2"><li>call和apply的用途:改变函数内部的 this 指向;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj1 = { <span class="attr">name</span>: <span class="string">'aaa'</span> };</span><br><span class="line"><span class="keyword">var</span> obj2 = { <span class="attr">name</span>: <span class="string">'bbb'</span> };</span><br><span class="line"><span class="built_in">window</span>.name = <span class="string">'window'</span>;</span><br><span class="line"><span class="keyword">var</span> getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> alert ( <span class="keyword">this</span>.name );</span><br><span class="line">};</span><br><span class="line">getName(); <span class="comment">// 输出: window</span></span><br><span class="line">getName.call( obj1 ); <span class="comment">// 输出: aaa</span></span><br><span class="line">getName.call( obj2 ); <span class="comment">// 输出: bbb</span></span><br></pre></td></tr></table></figure><blockquote><ol start="3"><li><code>Function.prototype.bind</code> 函数模拟实现如下:</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.bind = <span class="function"><span class="keyword">function</span>(<span class="params"> context </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>; <span class="comment">// 保存原函数</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 返回一个新的函数</span></span><br><span class="line"> <span class="keyword">return</span> self.apply( context, <span class="built_in">arguments</span> ); <span class="comment">// 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> obj = { <span class="attr">name</span>: <span class="string">'sven'</span> };</span><br><span class="line"><span class="keyword">var</span> func = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> alert ( <span class="keyword">this</span>.name ); <span class="comment">// 输出: sven</span></span><br><span class="line">}.bind( obj);</span><br><span class="line">func();</span><br></pre></td></tr></table></figure><p>通过 <code>Function.prototype.bind</code> 来“包装” func 函数,并且传入一个对象 <code>context</code> 当作参数,这个 <code>context</code> 对象就是我们想修正的 <code>this</code> 对象。在 <code>Function.prototype.bind</code> 的内部实现中,先把 <code>func</code> 函数的引用保存起来,然后返回一个新的函数。当在将来执行 <code>func</code> 函数时,实际上先执行的是这个刚刚返回的新函数。在新<br>函数内部, <code>self.apply( context, arguments )</code>这句代码才是执行原来的 <code>func</code> 函数,并且指定 context对象为 func 函数体内的 this;</p><p><strong><code>Function.prototype.bind</code> 函数升级版本</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.bind = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>, <span class="comment">// 保存原函数</span></span><br><span class="line"> context = [].shift.call( <span class="built_in">arguments</span> ), <span class="comment">// 需要绑定的 this 上下文</span></span><br><span class="line"> args = [].slice.call( <span class="built_in">arguments</span> ); <span class="comment">// 剩余的参数转成数组</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 返回一个新的函数</span></span><br><span class="line"> <span class="keyword">return</span> self.apply( context, [].concat.call( args, [].slice.call( <span class="built_in">arguments</span> ) ) );</span><br><span class="line"> <span class="comment">// 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this</span></span><br><span class="line"> <span class="comment">// 并且组合两次分别传入的参数,作为新函数的参数</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> obj = { <span class="attr">name</span>: <span class="string">'aaa'</span> };</span><br><span class="line"><span class="keyword">var</span> func = <span class="function"><span class="keyword">function</span>(<span class="params"> a, b, c, d </span>)</span>{</span><br><span class="line"> alert ( <span class="keyword">this</span>.name ); <span class="comment">// 输出: aaa</span></span><br><span class="line"> alert ( [ a, b, c, d ] ) <span class="comment">// 输出: [ 1, 2, 3, 4 ]</span></span><br><span class="line">}.bind( obj, <span class="number">1</span>, <span class="number">2</span> );</span><br><span class="line">func( <span class="number">3</span>, <span class="number">4</span> );</span><br></pre></td></tr></table></figure><blockquote><ol start="4"><li>借用其他对象的方法:</li></ol></blockquote><p>函数的参数列表 arguments 是一个类数组对象,虽然它也有“下标”,但它并非真正的数组,所以也不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这种情况下常会借用 <code>Array.prototype</code> 对象上的方法。比如想往 <code>arguments</code> 中添加一个新的元素,通常会借用 <code>Array.prototype.push</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="built_in">Array</span>.prototype.push.call( <span class="built_in">arguments</span>, <span class="number">3</span> );</span><br><span class="line"><span class="built_in">console</span>.log ( <span class="built_in">arguments</span> ); <span class="comment">// 输出[1,2,3]</span></span><br><span class="line">})( <span class="number">1</span>, <span class="number">2</span> );</span><br></pre></td></tr></table></figure><p>在操作 arguments 的时候,我们经常非常频繁地找 <code>Array.prototype</code> 对象借用方法;</p><h3 id="1-3-闭包和高阶函数"><a href="#1-3-闭包和高阶函数" class="headerlink" title="1.3 闭包和高阶函数"></a>1.3 闭包和高阶函数</h3><h4 id="1-3-1-闭包的作用"><a href="#1-3-1-闭包的作用" class="headerlink" title="1.3.1 闭包的作用"></a>1.3.1 闭包的作用</h4><p>闭包的形成与变量的作用域以及变量的生存周期密切相关,因而需要了解<code>变量的作用域</code>和<code>变量的生存周期</code>;</p><blockquote><ol><li>封装变量:闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量”;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 例如:mult 函数接受 number 类型的参数,并返回这些参数的乘积,并对该函数加入缓存机制;</span></span><br><span class="line"><span class="keyword">var</span> cache = {};</span><br><span class="line"><span class="keyword">var</span> mult = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> );</span><br><span class="line"> <span class="keyword">if</span> ( cache[ args ] ){</span><br><span class="line"> <span class="keyword">return</span> cache[ args ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){</span><br><span class="line"> a = a * <span class="built_in">arguments</span>[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cache[ args ] = a;</span><br><span class="line">};</span><br><span class="line">alert ( mult( <span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span> ) ); <span class="comment">// 输出: 6</span></span><br><span class="line">alert ( mult( <span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span> ) ); <span class="comment">// 输出: 6</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式2:使用闭包避免 cache 变量和 mult 函数一起平行地暴露在全局作用域下</span></span><br><span class="line"><span class="keyword">var</span> mult = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> cache = {};</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> );</span><br><span class="line"> <span class="keyword">if</span> ( args <span class="keyword">in</span> cache ){</span><br><span class="line"> <span class="keyword">return</span> cache[ args ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){</span><br><span class="line"> a = a * <span class="built_in">arguments</span>[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cache[ args ] = a;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 提炼其中的乘积函数</span></span><br><span class="line"><span class="keyword">var</span> mult = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> cache = {};</span><br><span class="line"> <span class="keyword">var</span> calculate = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{ <span class="comment">// 封闭 calculate 函数</span></span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){</span><br><span class="line"> a = a * <span class="built_in">arguments</span>[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> );</span><br><span class="line"> <span class="keyword">if</span> ( args <span class="keyword">in</span> cache ){</span><br><span class="line"> <span class="keyword">return</span> cache[ args ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cache[ args ] = calculate.apply( <span class="literal">null</span>, <span class="built_in">arguments</span> );</span><br><span class="line"> }</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>延续局部变量的寿命:避免局部变量在函数调用后销毁;</li></ol></blockquote><h4 id="1-3-2-高阶函数"><a href="#1-3-2-高阶函数" class="headerlink" title="1.3.2 高阶函数"></a>1.3.2 高阶函数</h4><p>满足可以作为参数被传递或可以作为返回值输出的函数称为高阶函数;</p><blockquote><ol><li>实例:判断数据的类型;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方式1. 基本实现</span></span><br><span class="line"><span class="keyword">var</span> isString = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Object</span>.prototype.toString.call( obj ) === <span class="string">'[object String]'</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> isArray = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Object</span>.prototype.toString.call( obj ) === <span class="string">'[object Array]'</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> isNumber = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Object</span>.prototype.toString.call( obj ) === <span class="string">'[object Number]'</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 方式2:字符串作为参数,进行函数封装</span></span><br><span class="line"><span class="keyword">var</span> isType = <span class="function"><span class="keyword">function</span>(<span class="params"> type </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Object</span>.prototype.toString.call( obj ) === <span class="string">'[object '</span>+ type +<span class="string">']'</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> isString = isType( <span class="string">'String'</span> );</span><br><span class="line"><span class="keyword">var</span> isArray = isType( <span class="string">'Array'</span> );</span><br><span class="line"><span class="keyword">var</span> isNumber = isType( <span class="string">'Number'</span> );</span><br><span class="line"><span class="built_in">console</span>.log( isArray( [ <span class="number">1</span>, <span class="number">2</span> ] ) ); <span class="comment">// 输出: true</span></span><br><span class="line"><span class="comment">// 方式3:用循环语句,来批量注册 isType 函数</span></span><br><span class="line"><span class="keyword">var</span> Type = {};</span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, type; type = [ <span class="string">'String'</span>, <span class="string">'Array'</span>, <span class="string">'Number'</span> ][ i++ ]; ){</span><br><span class="line"> (<span class="function"><span class="keyword">function</span>(<span class="params"> type </span>)</span>{</span><br><span class="line"> Type[ <span class="string">'is'</span> + type ] = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Object</span>.prototype.toString.call( obj ) === <span class="string">'[object '</span>+ type +<span class="string">']'</span>;</span><br><span class="line"> }</span><br><span class="line"> })( type )</span><br><span class="line">};</span><br><span class="line">Type.isArray( [] ); <span class="comment">// 输出: true</span></span><br><span class="line">Type.isString( <span class="string">"str"</span> ); <span class="comment">// 输出: true</span></span><br></pre></td></tr></table></figure><blockquote><ol start="2"><li>实例:单例模式中既把函数当作参数传递,又让函数执行后返回了另外一个函数;</li></ol></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> getSingle = <span class="function"><span class="keyword">function</span> (<span class="params"> fn </span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> ret;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> ret || ( ret = fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ) );</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><ol start="3"><li>高阶函数的其他应用:</li></ol></blockquote><ul><li>函数柯里化(function currying):currying 又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值;<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实例:计算总数,只需要最后一次返回整个结果;</span></span><br><span class="line"><span class="keyword">var</span> currying = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> args = [];</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span> ( <span class="built_in">arguments</span>.length === <span class="number">0</span> ){</span><br><span class="line"> <span class="keyword">return</span> fn.apply( <span class="keyword">this</span>, args );</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> [].push.apply( args, <span class="built_in">arguments</span> );</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">arguments</span>.callee;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> cost = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> money = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i < l; i++ ){</span><br><span class="line"> money += <span class="built_in">arguments</span>[ i ];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> money;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"><span class="keyword">var</span> cost = currying( cost ); <span class="comment">// 转化成 currying 函数</span></span><br><span class="line">cost( <span class="number">100</span> ); <span class="comment">// 未真正求值</span></span><br><span class="line">cost( <span class="number">200</span> ); <span class="comment">// 未真正求值</span></span><br><span class="line">alert ( cost() ); <span class="comment">// 求值并输出: 400</span></span><br></pre></td></tr></table></figure></li></ul>]]></content>
<summary type="html">
<p><img src="/2019/10/10/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5.png" alt="JavaScript设计模式与开发实践"></p>
<!--
设计模式是在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案,通俗说设计模式是在某种场合下对某个问题的一种解决方案。模式是一些经过了大量实际项目验证的优秀解决方案,熟悉这些模式的程序员会对某些模式的理解也许形成了条件反射,当合适的场景出现时,就可以很快地找到某种模式作为解决方案;所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”,一个程序的设计总是可以分为可变的部分和不变的部分,将可变的封装起来;模式应该用在正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道是否算正确应用; -->
</summary>
<category term="JavaScript" scheme="https://geek-lhj.github.io/categories/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/categories/JavaScript/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="JavaScript" scheme="https://geek-lhj.github.io/tags/JavaScript/"/>
<category term="设计模式" scheme="https://geek-lhj.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="基础知识" scheme="https://geek-lhj.github.io/tags/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
</entry>
<entry>
<title>Git教程笔记</title>
<link href="https://geek-lhj.github.io/2019/03/10/Git%E6%95%99%E7%A8%8B%E7%AC%94%E8%AE%B0/"/>
<id>https://geek-lhj.github.io/2019/03/10/Git%E6%95%99%E7%A8%8B%E7%AC%94%E8%AE%B0/</id>
<published>2019-03-10T10:38:58.000Z</published>
<updated>2020-01-19T08:20:32.683Z</updated>
<content type="html"><![CDATA[<p>Git是目前世界上最先进的分布式版本控制系统,该文档是菜鸟教程上面关于Git教程方面的知识,包括了使用 Git 前的相关知识,在不同系统中安装配置 Git,Git的相关操作(基本操作、分支管理、操作远程库等知识),具体查看文档;</p><a id="more"></a><h1 id="Git教程笔记1"><a href="#Git教程笔记1" class="headerlink" title="Git教程笔记1"></a>Git教程笔记1</h1><blockquote><p><a href="https://www.runoob.com/git/git-tutorial.html" target="_blank" rel="noopener">传送门</a></p></blockquote><p>文章目录:</p><ul><li><a href="#1-Git安装配置">1.Git安装配置</a></li><li><a href="#2-Git工作流程">2.Git工作流程</a></li><li><a href="#3-Git工作区、暂存区和版本库">3.Git工作区、暂存区和版本库</a></li><li><a href="#4-Git创建仓库">4.Git创建仓库</a></li><li><a href="#5-Git基本操作">5.Git基本操作</a></li><li><a href="#6-Git分支管理">6.Git分支管理</a></li><li><a href="#7-Git查看提交历史">7.Git查看提交历史</a></li><li><a href="#8-Git标签">8.Git标签</a></li><li><a href="#9-Git&Github">9.Git&Github</a></li><li><a href="#10-GitCODING">10.GitCODING</a></li><li><a href="#11-Git服务器搭建(没有动手测试)">11.Git服务器搭建(没有动手测试)</a></li></ul><h2 id="1-Git安装配置"><a href="#1-Git安装配置" class="headerlink" title="1.Git安装配置"></a>1.Git安装配置</h2><h3 id="1-1-Linux-Windows-Mac-平台上安装-略"><a href="#1-1-Linux-Windows-Mac-平台上安装-略" class="headerlink" title="1.1 Linux | Windows | Mac 平台上安装(略)"></a>1.1 Linux | Windows | Mac 平台上安装(略)</h3><p>Git 各平台安装包下载地址为:<a href="http://git-scm.com/downloads" target="_blank" rel="noopener">http://git-scm.com/downloads</a></p><h3 id="1-2-Git-配置"><a href="#1-2-Git-配置" class="headerlink" title="1.2 Git 配置"></a>1.2 Git 配置</h3><p>Git 提供了一个叫做 <code>git config</code> 的工具,专门用来配置或读取相应的工作环境变量,这些环境变量,决定了 Git 在各个环节的具体工作方式和行为;</p><p>这些环境变量存放在以下三个不同的地方:</p><ul><li><code>/etc/gitconfig</code> 文件:系统中对所有用户都普遍适用的配置;若使用 <code>git config</code> 时用 <code>--system</code> 选项,读写的就是这个文件;</li><li><code>~/.gitconfig</code> 文件:用户目录下的配置文件只适用于该用户;若使用 <code>git config</code> 时用 <code>--global</code> 选项,读写的就是这个文件;</li><li>当前项目的 <code>Git</code> 目录中的配置文件(也就是工作目录中的 <code>.git/config</code> 文件):这里的配置仅仅针对当前项目有效;每一个级别的配置都会覆盖上层的相同配置,所以 <code>.git/config</code> 里的配置会覆盖 <code>/etc/gitconfig</code> 中的同名变量;</li></ul><h4 id="1-2-1-用户信息"><a href="#1-2-1-用户信息" class="headerlink" title="1.2.1 用户信息"></a>1.2.1 用户信息</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置个人的用户名称和电子邮件地址</span></span><br><span class="line">$ git config --global user.name <span class="string">"runoob"</span></span><br><span class="line">$ git config --global user.email <span class="built_in">test</span>@runoob.com</span><br></pre></td></tr></table></figure><p>如果用了 <code>--global</code> 选项,那么更改的配置文件就是位于你<code>用户主目录</code>下的那个,以后你所有的项目都会默认使用这里配置的用户信息;<br>如果要在<code>某个特定的项目中</code>使用其他名字或者电邮,只要去掉 <code>--global</code> 选项重新配置即可,新的设定保存在当前项目的 <code>.git/config</code> 文件里;</p><p><strong>在项目中遇到了这样的问题,只要切换了不同项目(用户名称和电子邮件不同),就需要配置 SSH 秘钥才能成功上传代码,解决该问题方式就是对特定的项目中使用其他名字或者电邮,不加 –global 选项</strong></p><h4 id="1-2-2-文本编辑器"><a href="#1-2-2-文本编辑器" class="headerlink" title="1.2.2 文本编辑器"></a>1.2.2 文本编辑器</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Git默认使用的文本编辑器是 `Vi` 或者 `Vim`,如配置 Emacs 文本编辑器</span></span><br><span class="line">$ git config --global core.editor emacs</span><br></pre></td></tr></table></figure><h4 id="1-2-3-差异分析工具"><a href="#1-2-3-差异分析工具" class="headerlink" title="1.2.3 差异分析工具"></a>1.2.3 差异分析工具</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在解决合并冲突时使用哪种差异分析工具。比如要改用 vimdiff</span></span><br><span class="line">$ git config --global merge.tool vimdiff</span><br></pre></td></tr></table></figure><p>Git 可以理解 <code>kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,和 opendiff</code> 等合并工具的输出信息;当然你也可以指定使用自己开发的工具,具体怎么做可以参阅第七章;</p><h4 id="1-2-4-查看配置信息"><a href="#1-2-4-查看配置信息" class="headerlink" title="1.2.4 查看配置信息"></a>1.2.4 查看配置信息</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 要检查已有的配置信息,使用 git config --list 命令;若看到重复的变量名,那就说明它们来自不同的配置文件(比如 /etc/gitconfig 和 ~/.gitconfig)</span></span><br><span class="line">$ git config --list</span><br><span class="line"><span class="comment"># 也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可</span></span><br><span class="line">$ git config user.name</span><br></pre></td></tr></table></figure><h2 id="2-Git工作流程"><a href="#2-Git工作流程" class="headerlink" title="2.Git工作流程"></a>2.Git工作流程</h2><p> <code>Git</code> 的一般工作流程如下:</p><ol><li>克隆 <code>Git</code> 资源作为工作目录;</li><li>在克隆的资源上添加或修改文件;</li><li>如果其他人修改了,你可以更新资源;</li><li>在提交前查看修改;</li><li>提交修改;</li><li>在修改完成后,如果发现错误,可以撤回提交并再次修改并提交;</li></ol><p><img src="/2019/03/10/Git%E6%95%99%E7%A8%8B%E7%AC%94%E8%AE%B0/1.png" alt="Git工作流程图"></p><h2 id="3-Git工作区、暂存区和版本库"><a href="#3-Git工作区、暂存区和版本库" class="headerlink" title="3.Git工作区、暂存区和版本库"></a>3.Git工作区、暂存区和版本库</h2><h3 id="3-1-三者的基本概念"><a href="#3-1-三者的基本概念" class="headerlink" title="3.1 三者的基本概念"></a>3.1 三者的基本概念</h3><ul><li><strong>工作区</strong>:就是你在电脑里能看到的目录;</li><li><strong>暂存区</strong>:英文叫 <code>stage或index</code> ;一般存放在 “<code>.git目录下</code>“ 下的<code>index</code>文件(<code>.git/index</code>)中,所以我们把暂存区有时也叫作索引(index);</li><li><strong>版本库</strong>:工作区有一个隐藏目录 <code>.git</code> ,这个不算工作区,而是Git的版本库;</li></ul><h3 id="3-2-三者之间的关系"><a href="#3-2-三者之间的关系" class="headerlink" title="3.2 三者之间的关系"></a>3.2 三者之间的关系</h3><p><img src="/2019/03/10/Git%E6%95%99%E7%A8%8B%E7%AC%94%E8%AE%B0/2.png" alt="工作区暂存区版本库的关系图"></p><p>图中左侧为<code>工作区</code>,右侧为<code>版本库</code>。在版本库中标记为 “<code>index</code>“ 的区域是<code>暂存区</code>(<code>stage, index</code>),标记为 “master” 的是 master 分支所代表的目录树;图中我们可以看出此时 “<code>HEAD</code>“ 实际是指向 <code>master</code> 分支的一个”游标”;所以图示的命令中出现 <code>HEAD</code> 的地方可以用 <code>master</code> 来替换;图中的 <code>objects</code> 标识的区域为 Git 的对象库,实际位于 “<code>.git/objects</code>“ 目录下,里面包含了创建的各种对象及内容。</p><h2 id="4-Git创建仓库"><a href="#4-Git创建仓库" class="headerlink" title="4.Git创建仓库"></a>4.Git创建仓库</h2><h3 id="4-1-git-init"><a href="#4-1-git-init" class="headerlink" title="4.1 git init"></a>4.1 git init</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用当前目录作为Git仓库,我们只需使它初始化,命令执行完后会在当前目录生成一个 .git 目录,该目录包含了资源的所有元数据,其他的项目目录保持不变</span></span><br><span class="line">git init</span><br><span class="line"><span class="comment"># 指定目录作为Git仓库</span></span><br><span class="line">git init newrepo</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 以下命令将目录下以 .c 结尾及 README 文件提交到仓库中</span></span><br><span class="line">$ git add *.c</span><br><span class="line">$ git add README</span><br><span class="line">$ git commit -m <span class="string">'初始化项目版本'</span></span><br></pre></td></tr></table></figure><h3 id="4-2-git-clone"><a href="#4-2-git-clone" class="headerlink" title="4.2 git clone"></a>4.2 git clone</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 从现有 Git 仓库中拷贝项目,repo:Git 仓库;directory:本地目录。</span></span><br><span class="line">git <span class="built_in">clone</span> <repo></span><br><span class="line"><span class="comment"># 如果需要克隆到指定的目录,可以使用以下命令格式</span></span><br><span class="line">git <span class="built_in">clone</span> <repo> <directory></span><br></pre></td></tr></table></figure><h2 id="5-Git基本操作"><a href="#5-Git基本操作" class="headerlink" title="5.Git基本操作"></a>5.Git基本操作</h2><p>Git 的工作就是创建和保存你项目的快照及与之后的快照进行对比;</p><h3 id="5-1-获取与创建项目命令"><a href="#5-1-获取与创建项目命令" class="headerlink" title="5.1 获取与创建项目命令"></a>5.1 获取与创建项目命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 用 git init 在目录中创建新的 Git 仓库</span></span><br><span class="line">$ git init</span><br><span class="line"><span class="comment"># 用 git clone 拷贝一个 Git 仓库到本地</span></span><br><span class="line">$ git <span class="built_in">clone</span> [url] [dirName]</span><br></pre></td></tr></table></figure><h3 id="5-2-基本快照"><a href="#5-2-基本快照" class="headerlink" title="5.2 基本快照"></a>5.2 基本快照</h3><h4 id="5-1-1-git-add"><a href="#5-1-1-git-add" class="headerlink" title="5.1.1 git add"></a>5.1.1 git add</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># git add 命令可将该文件添加到缓存, *或. 表示添加所有新增文件,也可以是具体的文件</span></span><br><span class="line">$ git add *</span><br></pre></td></tr></table></figure><h4 id="5-1-2-git-status"><a href="#5-1-2-git-status" class="headerlink" title="5.1.2 git status"></a>5.1.2 git status</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># git status 命令用于查看项目的当前状态,查看在上次提交之后是否有修改,-s 表示以简短的结果输出</span></span><br><span class="line">$ git status -s</span><br></pre></td></tr></table></figure><h4 id="5-1-3-git-diff"><a href="#5-1-3-git-diff" class="headerlink" title="5.1.3 git diff"></a>5.1.3 git diff</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看执行 git status 的结果的详细信息,显示已写入缓存与已修改但尚未写入缓存的改动的区别</span></span><br><span class="line">$ git diff <span class="comment"># 尚未缓存的改动</span></span><br><span class="line">$ git diff --cached <span class="comment"># 查看已缓存的改动</span></span><br><span class="line">$ git diff HEAD <span class="comment"># 查看已缓存的与未缓存的所有改动</span></span><br><span class="line">$ git diff --<span class="built_in">stat</span> <span class="comment"># 显示摘要而非整个 diff</span></span><br></pre></td></tr></table></figure><h4 id="5-1-4-git-commit"><a href="#5-1-4-git-commit" class="headerlink" title="5.1.4 git commit"></a>5.1.4 git commit</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 注意 Git 为你的每一个提交都记录你的名字与电子邮箱地址,所以第一步需要配置用户名和邮箱地址,注意 --global 参数的使用</span></span><br><span class="line">$ git config --global user.name <span class="string">'runoob'</span></span><br><span class="line">$ git config --global user.email <span class="built_in">test</span>@runoob.com</span><br><span class="line"><span class="comment"># 使用 git add 命令将想要快照的内容写入缓存区</span></span><br><span class="line">$ git add .</span><br><span class="line"><span class="comment"># 执行 git commit 将缓存区内容添加到仓库中,使用 -m 选项以在命令行中提供提交注释</span></span><br><span class="line">$ git commit -m <span class="string">'提交的注释信息'</span></span><br><span class="line"><span class="comment"># 若觉得 git add 提交缓存的流程繁琐,Git 也允许你用 -a 选项跳过这一步</span></span><br><span class="line">git commit -am <span class="string">'提交的注释信息(此前不需要 git add )'</span></span><br></pre></td></tr></table></figure><h4 id="5-1-5-git-reset-HEAD"><a href="#5-1-5-git-reset-HEAD" class="headerlink" title="5.1.5 git reset HEAD"></a>5.1.5 git reset HEAD</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 取消已缓存的文件内容;执行 git reset HEAD 以取消之前 git add 添加,但不希望包含在下一提交快照中的缓存</span></span><br><span class="line">$ git reset HEAD [fileName]</span><br></pre></td></tr></table></figure><h4 id="5-1-6-git-rm"><a href="#5-1-6-git-rm" class="headerlink" title="5.1.6 git rm"></a>5.1.6 git rm</h4><p>若只是简单地从工作目录中手工删除文件,运行 <code>git status</code> 时就会在 <code>Changes not staged for commit</code> 的提示;要从 <code>Git</code> 中移除某个文件,就必须要从已跟踪文件清单中移除,然后提交:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除工作区目录文件,从已跟踪文件清单中移除</span></span><br><span class="line">$ git rm <file></span><br><span class="line"><span class="comment"># 若删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f</span></span><br><span class="line">$ git rm -f <file></span><br><span class="line"><span class="comment"># 若把文件从暂存区域移除,但仍然希望保留在当前工作目录中;换句话说,仅是从跟踪清单中删除,使用 --cached 选项</span></span><br><span class="line">$ git rm --cached <file></span><br><span class="line"><span class="comment"># 递归删除,即如果后面跟的是一个目录做为参数,则会递归删除整个目录中的所有子目录和文件</span></span><br><span class="line">$ git rm –r *</span><br></pre></td></tr></table></figure><h4 id="5-1-7-git-mv"><a href="#5-1-7-git-mv" class="headerlink" title="5.1.7 git mv"></a>5.1.7 git mv</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># git mv 命令用于移动或重命名一个文件、目录、软连接</span></span><br><span class="line">$ git mv [filename1] [filename2]</span><br></pre></td></tr></table></figure><h2 id="6-Git分支管理"><a href="#6-Git分支管理" class="headerlink" title="6.Git分支管理"></a>6.Git分支管理</h2><ul><li>创建分支命令:<code>git branch (branchname)</code>;</li><li>切换分支命令:<code>git checkout (branchname)</code>;</li><li>合并分支命令:<code>git merge</code>;</li></ul><h3 id="6-1-列出、新建、切换分支"><a href="#6-1-列出、新建、切换分支" class="headerlink" title="6.1 列出、新建、切换分支"></a>6.1 列出、新建、切换分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出分支基本命令,不要加参数</span></span><br><span class="line">$ git branch</span><br><span class="line"><span class="comment"># 加上参数就是表示创建一个分支,分支名就是参数值</span></span><br><span class="line">$ git branch (branchname)</span><br><span class="line"><span class="comment"># 切换到某一分支下</span></span><br><span class="line">$ git checkout (branch)</span><br><span class="line"><span class="comment"># 创建新分支并立即切换到该分支下</span></span><br><span class="line">$ git checkout -b (branchname)</span><br></pre></td></tr></table></figure><h3 id="6-2-删除分支"><a href="#6-2-删除分支" class="headerlink" title="6.2 删除分支"></a>6.2 删除分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除分支命令</span></span><br><span class="line">$ git branch -d (branchname)</span><br></pre></td></tr></table></figure><h3 id="6-3-分支合并"><a href="#6-3-分支合并" class="headerlink" title="6.3 分支合并"></a>6.3 分支合并</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用以下命令将任何分支合并到当前分支中去</span></span><br><span class="line">$ git merge (branchname)</span><br></pre></td></tr></table></figure><h3 id="6-4-合并冲突"><a href="#6-4-合并冲突" class="headerlink" title="6.4 合并冲突"></a>6.4 合并冲突</h3><p>在 Git 中,若合并冲突出现了,我们需要手动去修改它,在修改完成后,我们可以用 <code>git add</code> 要告诉 <code>Git</code> 文件冲突已经解决,然后再使用 <code>git commit</code> 提交结果;</p><h2 id="7-Git查看提交历史"><a href="#7-Git查看提交历史" class="headerlink" title="7. Git查看提交历史"></a>7. Git查看提交历史</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用 git log 命令列出历史提交记录</span></span><br><span class="line">$ git <span class="built_in">log</span></span><br><span class="line"><span class="comment"># 可以用 --oneline 选项来查看历史记录的简洁的版本</span></span><br><span class="line">$ git <span class="built_in">log</span> --oneline</span><br><span class="line"><span class="comment"># 还可以用 --graph 选项,查看历史中什么时候出现了分支、合并,开启了拓扑图选项</span></span><br><span class="line">$ git <span class="built_in">log</span> --graph</span><br><span class="line"><span class="comment"># 也可以用 --reverse 参数来逆向显示所有日志</span></span><br><span class="line">$ git <span class="built_in">log</span> --reverse --oneline</span><br><span class="line"><span class="comment"># 只想查找指定用户的提交日志可以使用命令:git log --author , 例如,比方说我们要找 Git 源码中 Linus 提交的部分</span></span><br><span class="line">$ git <span class="built_in">log</span> --author=Linus --oneline -5</span><br><span class="line"><span class="comment"># 如果要指定日期,可以执行几个选项:--since 和 --before,但是你也可以用 --until 和 --after ; 例如,如果我要看 Git 项目中三周前且在四月十八日之后的所有提交,我可以执行这个(还用了 --no-merges 选项以隐藏合并提交)</span></span><br><span class="line">$ git <span class="built_in">log</span> --oneline --before={3.weeks.ago} --after={2010-04-18} --no-merges</span><br></pre></td></tr></table></figure><h2 id="8-Git标签"><a href="#8-Git标签" class="headerlink" title="8.Git标签"></a>8.Git标签</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 如果项目达到一个重要的阶段,并希望永远记住那个特别的提交快照,使用 `git tag` 给它打上标签; -a 选项意为"创建一个带注解的标签"; -m 选项为添加的标签注释内容</span></span><br><span class="line">$ git tag -a v1.0</span><br><span class="line"><span class="comment"># 查看项目中的标签</span></span><br><span class="line">$ git <span class="built_in">log</span> --decorate</span><br><span class="line"><span class="comment"># 若忘了给某个提交打标签,又将它发布了,我们可以给它追加标签, 85fc7e7 为那次修改发布的哈希值</span></span><br><span class="line">$ git tag -a v0.9 85fc7e7</span><br><span class="line">$ git <span class="built_in">log</span> --oneline --decorate --graph</span><br><span class="line"><span class="comment"># 指定标签信息命令</span></span><br><span class="line">$ git tag -a <tagname> -m <span class="string">"标签注释内容"</span></span><br><span class="line"><span class="comment"># PGP签名标签命令:</span></span><br><span class="line">$ git tag -s <tagname> -m <span class="string">"标签注释内容"</span></span><br></pre></td></tr></table></figure><h2 id="9-Git-amp-Github"><a href="#9-Git-amp-Github" class="headerlink" title="9.Git&Github"></a>9.Git&Github</h2><h3 id="9-1-添加远程库"><a href="#9-1-添加远程库" class="headerlink" title="9.1 添加远程库"></a>9.1 添加远程库</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 要添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用,命令格式如下:</span></span><br><span class="line">$ git remote add [shortname] [url]</span><br></pre></td></tr></table></figure><p>配置 <code>SSH</code> 秘钥连接 <code>GitHub</code> 远程库步骤:</p><ol><li>执行以下命令生成 <code>SSH Key</code>:<code>$ ssh-keygen -t rsa -C "[email protected]"</code>;其中邮件名称是你在 <code>Github</code> 上注册的邮箱;</li><li>接着会要求确认路径和输入密码,这使用默认的一路回车就行,会在<code>~/</code> 目录下生成 <code>.ssh</code> 文件夹,如<code>C:\Users\<username>\.ssh</code>,进去打开 <code>id_rsa.pub</code>,复制里面的 <code>key</code> ;</li><li>回到 <code>github</code> 上,进入 <code>Account => Settings</code>(账户配置),新增 <code>SSH key</code>;</li><li>为了验证是否成功,输入以下命令:<code>$ ssh -T [email protected]</code>;</li></ol><h3 id="9-2-查看当前的远程库"><a href="#9-2-查看当前的远程库" class="headerlink" title="9.2 查看当前的远程库"></a>9.2 查看当前的远程库</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 要查看当前配置有哪些远程仓库,可以用命令</span></span><br><span class="line">$ git remote</span><br><span class="line"><span class="comment"># 执行时加上 -v 参数,你还可以看到每个别名的实际链接地址</span></span><br><span class="line">$ git remote -v</span><br></pre></td></tr></table></figure><h3 id="9-3-提取远程仓库"><a href="#9-3-提取远程仓库" class="headerlink" title="9.3 提取远程仓库"></a>9.3 提取远程仓库</h3><p>Git 有两个命令用来提取远程仓库的更新:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 从远程仓库下载新分支与数据,该命令执行完后需要执行git merge 远程分支到你所在的分支</span></span><br><span class="line">$ git fetch</span><br><span class="line"><span class="comment"># 2. 从远端仓库提取数据并尝试合并到当前分支,该命令就是在执行 git fetch 之后紧接着执行 git merge 远程分支到你所在的任意分支</span></span><br><span class="line">$ git merge</span><br></pre></td></tr></table></figure><p>假设你配置好了一个远程仓库,并且你想要提取更新的数据,你可以首先执行 <code>git fetch [alias]</code> 告诉 Git 去获取它有你没有的数据,然后你可以执行 <code>git merge [alias]/[branch]</code> 以将服务器上的任何更新(假设有人这时候推送到服务器了)合并到你的当前分支;</p><h4 id="9-3-1-推送到远程仓库"><a href="#9-3-1-推送到远程仓库" class="headerlink" title="9.3.1 推送到远程仓库"></a>9.3.1 推送到远程仓库</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 推送你的新分支与数据到某个远端仓库命令,该命令将你的 [branch] 分支推送成为 [alias] 远程仓库上的 [branch] 分支</span></span><br><span class="line">$ git push [<span class="built_in">alias</span>] [branch]</span><br></pre></td></tr></table></figure><h4 id="9-3-2-删除远程仓库"><a href="#9-3-2-删除远程仓库" class="headerlink" title="9.3.2 删除远程仓库"></a>9.3.2 删除远程仓库</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除远程仓库你可以使用命令:</span></span><br><span class="line">$ git remote rm [<span class="built_in">alias</span>]</span><br><span class="line"><span class="comment"># 例如:在原有的远程仓库上新增一个,然后再删除</span></span><br><span class="line"><span class="comment"># 1. 先查看当前配置有哪些远程仓库</span></span><br><span class="line">$ git remote -v</span><br><span class="line"><span class="comment"># 2. 添加仓库 origin2,再查看远程仓库</span></span><br><span class="line">$ git remote add origin2 [email protected]:tianqixin/runoob-git-test.git</span><br><span class="line">$ git remote -v</span><br><span class="line"><span class="comment"># 删除仓库 origin2,再查看远程仓库</span></span><br><span class="line">$ git remote rm origin2</span><br><span class="line">$ git remote -v</span><br></pre></td></tr></table></figure><h2 id="10-GitCODING"><a href="#10-GitCODING" class="headerlink" title="10.GitCODING"></a>10.GitCODING</h2><p>由于 GitHub 的访问速度太慢,常还会出现丢包、失去连接等情况;因而使用国内服务器的托管平台 <code>CODING</code>;</p><p>将 <code>GitHub</code> 上的代码迁移到 <code>CODING</code> 的步骤:</p><ol><li>创建 CODING 项目;</li><li>将 GitHub 代码 Pull 到本地;</li><li>本地关联 CODING 仓库,Push 代码到 CODING;<ul><li>首先执行命令查看当前配置的远程仓库:<code>git remote -v</code>;</li><li>接下来执行以下命令,来关联 <code>CODING</code> 远程仓库,要先删除现有的仓库关联,在将仓库关联到 <code>CODING</code> 的地址,并且将代码 <code>Push</code> 到 <code>master</code> 分支:</li></ul></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ git remote rm origin</span><br><span class="line">$ git remote add origin https://git.coding.net/xxx/xxx.git</span><br><span class="line">$ git push -u origin master</span><br></pre></td></tr></table></figure><p><code>CODING</code> 仓库的免密码 <code>Push/Pull</code>:配置<code>SSH Key</code>;</p><h2 id="11-Git服务器搭建-没有动手测试"><a href="#11-Git服务器搭建-没有动手测试" class="headerlink" title="11.Git服务器搭建(没有动手测试)"></a>11.Git服务器搭建(没有动手测试)</h2><p><code>Github</code> 公开的项目是免费的,但是如果你不想让其他人看到你的项目就需要收费,因而我们就需要自己搭建一台Git服务器作为私有仓库使用;接下来我们将以 <code>Centos</code> 为例搭建 <code>Git</code> 服务器:</p><h3 id="11-1-安装Git"><a href="#11-1-安装Git" class="headerlink" title="11.1 安装Git"></a>11.1 安装Git</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-devel</span><br><span class="line">$ yum install git</span><br><span class="line"><span class="comment"># 接下来我们 创建一个git用户组和用户,用来运行git服务</span></span><br><span class="line">$ groupadd git</span><br><span class="line">$ useradd git -g git</span><br></pre></td></tr></table></figure><h3 id="11-2-创建证书登录"><a href="#11-2-创建证书登录" class="headerlink" title="11.2 创建证书登录"></a>11.2 创建证书登录</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 收集所有需要登录的用户的公钥,公钥位于id_rsa.pub文件中,把我们的公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个,若没有就创建它;</span></span><br><span class="line">$ <span class="built_in">cd</span> /home/git/</span><br><span class="line">$ mkdir .ssh</span><br><span class="line">$ chmod 755 .ssh</span><br><span class="line">$ touch .ssh/authorized_keys</span><br><span class="line">$ chmod 644 .ssh/authorized_keys</span><br></pre></td></tr></table></figure><h3 id="11-3-初始化Git仓库"><a href="#11-3-初始化Git仓库" class="headerlink" title="11.3 初始化Git仓库"></a>11.3 初始化Git仓库</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 首先选定一个目录作为Git仓库,假定是/home/gitrepo/runoob.git,在/home/gitrepo目录下输入命令</span></span><br><span class="line">$ <span class="built_in">cd</span> /home</span><br><span class="line">$ mkdir gitrepo</span><br><span class="line">$ chown git:git gitrepo/</span><br><span class="line">$ <span class="built_in">cd</span> gitrepo</span><br><span class="line"></span><br><span class="line">$ git init --bare runoob.git</span><br><span class="line">Initialized empty Git repository <span class="keyword">in</span> /home/gitrepo/runoob.git/</span><br><span class="line"><span class="comment"># 以上命令Git创建一个空仓库,服务器上的Git仓库通常都以.git结尾。然后,把仓库所属用户改为git</span></span><br><span class="line">$ chown -R git:git runoob.git</span><br></pre></td></tr></table></figure><h3 id="11-4-克隆仓库"><a href="#11-4-克隆仓库" class="headerlink" title="11.4 克隆仓库"></a>11.4 克隆仓库</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 192.168.45.4 为 Git 所在服务器 ip ,需要将其修改为你自己的 Git 服务 ip;</span></span><br><span class="line">$ git <span class="built_in">clone</span> [email protected]:/home/gitrepo/runoob.git</span><br></pre></td></tr></table></figure><p>这样我们的 Git 服务器安装就完成;</p>]]></content>
<summary type="html">
<p>Git是目前世界上最先进的分布式版本控制系统,该文档是菜鸟教程上面关于Git教程方面的知识,包括了使用 Git 前的相关知识,在不同系统中安装配置 Git,Git的相关操作(基本操作、分支管理、操作远程库等知识),具体查看文档;</p>
</summary>
<category term="Git" scheme="https://geek-lhj.github.io/categories/Git/"/>
<category term="官方文档" scheme="https://geek-lhj.github.io/categories/Git/%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3/"/>
<category term="Git" scheme="https://geek-lhj.github.io/tags/Git/"/>
<category term="Git操作文档" scheme="https://geek-lhj.github.io/tags/Git%E6%93%8D%E4%BD%9C%E6%96%87%E6%A1%A3/"/>
</entry>
<entry>
<title>Vuex官方文档学习笔记</title>
<link href="https://geek-lhj.github.io/2019/03/06/Vuex%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<id>https://geek-lhj.github.io/2019/03/06/Vuex%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</id>
<published>2019-03-06T10:38:58.000Z</published>
<updated>2020-01-17T14:45:56.180Z</updated>
<content type="html"><![CDATA[<p>在 Vue 的学习中,Vuex 是一个专为 Vue 开发的应用程序的状态管理模式,当我们构建一个中大型的单页面应用程序时,Vuex可以更好的帮助我们在组件外部统一管理状态,Vuex具体使用方法查看文档笔记;</p><a id="more"></a><h1 id="Vuex官方文档学习笔记"><a href="#Vuex官方文档学习笔记" class="headerlink" title="Vuex官方文档学习笔记"></a>Vuex官方文档学习笔记</h1><blockquote><p>学习链接:<a href="https://vuex.vuejs.org/zh/" target="_blank" rel="noopener">Vuex官方文档</a></p></blockquote><h2 id="1-安装"><a href="#1-安装" class="headerlink" title="1. 安装"></a>1. 安装</h2><ol><li>在一个模块化的打包系统中,先下载 <code>vuex</code> 第三方模块,再显式地通过 <code>Vue.use()</code> 来安装 <code>Vuex</code> :</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm install vuex --save</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">yarn add vuex</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> Vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line">Vue.use(Vuex)</span><br></pre></td></tr></table></figure><h3 id="1-1-Vuex-是什么?"><a href="#1-1-Vuex-是什么?" class="headerlink" title="1.1 Vuex 是什么?"></a>1.1 Vuex 是什么?</h3><p><code>Vuex</code> 是一个专为 <code>Vue.js</code> 应用程序开发的<strong>状态管理模式</strong>;状态管理模式介绍如下:</p><ol><li>状态自管理应用包含以下几个部分:<ul><li><code>state</code>:驱动应用的数据源;</li><li><code>view</code>:以声明方式将 <code>state</code> 映射到视图;</li><li><code>actions</code>:响应在 <code>view</code> 上的用户输入导致的状态变化;</li></ul></li><li><code>Vuex</code> 的基本思想是把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为;另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护;</li><li><code>Vuex</code> 是专门为 <code>Vue.js</code> 设计的状态管理库,以利用 <code>Vue.js</code> 的细粒度数据响应机制来进行高效的状态更新;</li></ol><p><img src="/2019/03/06/Vuex%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/1.png" alt="图片"></p><h3 id="1-2-开始"><a href="#1-2-开始" class="headerlink" title="1.2 开始"></a>1.2 开始</h3><p>每一个 <code>Vuex</code> 应用的核心就是 <code>store</code> (仓库),它包含着你的应用中大部分的状态 <code>state</code> ;</p><p>Vuex 和单纯的全局对象有以下两点不同:</p><ol><li>Vuex 的状态存储是响应式的;当 Vue 组件从 <code>store</code> 中读取状态的时候,若 <code>store</code> 中的状态发生变化,那么相应的组件也会相应地得到高效更新;</li><li>不能直接改变 <code>store</code> 中的状态;改变 <code>store</code> 中的状态的唯一途径就是显式地 提交 (commit) <code>mutation</code> ,而非直接改变 <code>store.state.count</code>;这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用,代码如下:</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个 store,提供一个初始 state 对象和一些 mutation</span></span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> count: <span class="number">0</span></span><br><span class="line"> },</span><br><span class="line"> mutations: {</span><br><span class="line"> increment (state) {</span><br><span class="line"> state.count++</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="comment">// 通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:</span></span><br><span class="line">store.commit(<span class="string">'increment'</span>)</span><br><span class="line"><span class="built_in">console</span>.log(store.state.count) <span class="comment">// -> 1</span></span><br></pre></td></tr></table></figure><h2 id="2-核心概念"><a href="#2-核心概念" class="headerlink" title="2. 核心概念"></a>2. 核心概念</h2><h3 id="2-1-State"><a href="#2-1-State" class="headerlink" title="2.1 State"></a>2.1 State</h3><h4 id="2-1-1-单一状态树"><a href="#2-1-1-单一状态树" class="headerlink" title="2.1.1 单一状态树"></a>2.1.1 单一状态树</h4><p><code>Vuex</code> 使用<strong>单一状态树</strong>——用一个对象就包含了全部的应用层级状态;每个应用将仅仅包含一个 <code>store</code> 实例;单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。</p><h4 id="2-1-2-在-Vue-组件中获得-Vuex-状态"><a href="#2-1-2-在-Vue-组件中获得-Vuex-状态" class="headerlink" title="2.1.2 在 Vue 组件中获得 Vuex 状态"></a>2.1.2 在 Vue 组件中获得 Vuex 状态</h4><p><code>Vuex</code> 的状态存储是响应式的,从 <code>store</code> 实例中读取状态最简单的方法就是在<strong>计算属性</strong>中返回某个状态,如代码中每当 <code>store.state.count</code> 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个 Counter 组件</span></span><br><span class="line"><span class="keyword">const</span> Counter = {</span><br><span class="line"> template: <span class="string">`<div>{{ count }}</div>`</span>,</span><br><span class="line"> computed: {</span><br><span class="line"> count () {</span><br><span class="line"> <span class="keyword">return</span> store.state.count</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 这种模式导致组件依赖全局状态单例,解决办法如下</span></span><br></pre></td></tr></table></figure><p><code>Vuex</code> 通过 <code>store</code> 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 <code>Vue.use(Vuex)</code>),通过在根实例中注册 <code>store</code> 选项,该 <code>store</code> 实例会注入到根组件下的所有子组件中,且子组件能通过 <code>this.$store</code> 访问到,更新下 <code>Counter</code> 的实现:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在根实例 app.vue 中进行注册 store选项;</span></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Vue({</span><br><span class="line"> el: <span class="string">'#app'</span>,</span><br><span class="line"> <span class="comment">// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件</span></span><br><span class="line"> store,</span><br><span class="line"> components: { Counter },</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <div class="app"></span></span><br><span class="line"><span class="string"> <counter></counter></span></span><br><span class="line"><span class="string"> </div></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">})</span><br><span class="line"><span class="comment">// 子组件访 Counter 问该数据</span></span><br><span class="line"><span class="keyword">const</span> Counter = {</span><br><span class="line"> template: <span class="string">`<div>{{ count }}</div>`</span>,</span><br><span class="line"> computed: {</span><br><span class="line"> count () {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.$store.state.count</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-1-3-mapState-辅助函数"><a href="#2-1-3-mapState-辅助函数" class="headerlink" title="2.1.3 mapState 辅助函数"></a>2.1.3 mapState 辅助函数</h4><p>当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余,可以使用 <code>mapState</code> 辅助函数帮助我们生成计算属性;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在单独构建的版本中辅助函数为 Vuex.mapState</span></span><br><span class="line"><span class="keyword">import</span> { mapState } <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> computed: mapState({</span><br><span class="line"> <span class="comment">// 箭头函数可使代码更简练</span></span><br><span class="line"> count: <span class="function"><span class="params">state</span> =></span> state.count,</span><br><span class="line"> <span class="comment">// 传字符串参数 'count' 等同于 `state => state.count`</span></span><br><span class="line"> countAlias: <span class="string">'count'</span>,</span><br><span class="line"> <span class="comment">// 为了能够使用 `this` 获取局部状态,必须使用常规函数</span></span><br><span class="line"> countPlusLocalState (state) {</span><br><span class="line"> <span class="keyword">return</span> state.count + <span class="keyword">this</span>.localCount</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当映射的计算属性的名称与 <code>state</code> 的子节点名称相同时,我们也可以给 <code>mapState</code> 传一个字符串数组:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">computed: mapState([</span><br><span class="line"> <span class="comment">// 映射 this.count 为 store.state.count</span></span><br><span class="line"> <span class="string">'count'</span></span><br><span class="line">])</span><br></pre></td></tr></table></figure><h4 id="2-1-4-对象展开运算符"><a href="#2-1-4-对象展开运算符" class="headerlink" title="2.1.4 对象展开运算符"></a>2.1.4 对象展开运算符</h4><p><code>mapState</code> 函数返回的是一个对象;我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 <code>computed</code> 属性:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">computed: {</span><br><span class="line"> localComputed () { <span class="comment">/* ... */</span> },</span><br><span class="line"> <span class="comment">// 使用对象展开运算符将此对象混入到外部对象中</span></span><br><span class="line"> ...mapState({</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-1-5-组件仍然保有局部状态"><a href="#2-1-5-组件仍然保有局部状态" class="headerlink" title="2.1.5 组件仍然保有局部状态"></a>2.1.5 组件仍然保有局部状态</h4><p>使用 <code>Vuex</code> 并不意味着你需要将所有的状态放入 <code>Vuex</code> ;如果有些状态严格属于单个组件,最好还是作为组件的局部状态;你应该根据你的应用开发需要进行权衡和确定。</p><h3 id="2-2-Getter"><a href="#2-2-Getter" class="headerlink" title="2.2 Getter"></a>2.2 Getter</h3><h4 id="2-2-1-从-store-中的-state-中派生出一些状态"><a href="#2-2-1-从-store-中的-state-中派生出一些状态" class="headerlink" title="2.2.1 从 store 中的 state 中派生出一些状态"></a>2.2.1 从 store 中的 state 中派生出一些状态</h4><p>例如:对列表进行数据过滤并计算个数,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 过滤函数获取todo.done 为true的值</span></span><br><span class="line">computed: {</span><br><span class="line"> doneTodosCount () {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.$store.state.todos.filter(<span class="function"><span class="params">todo</span> =></span> todo.done).length</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>Vuex</code> 允许我们在 <code>store</code> 中定义“<code>getter</code>”(可以认为是 <code>store</code> 的计算属性);就像计算属性一样, <code>getter</code> 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算, <code>Getter</code> 接受 <code>state</code> 作为其第一个参数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> todos: [</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">text</span>: <span class="string">'...'</span>, <span class="attr">done</span>: <span class="literal">true</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">text</span>: <span class="string">'...'</span>, <span class="attr">done</span>: <span class="literal">false</span> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> getters: {</span><br><span class="line"> doneTodos: <span class="function"><span class="params">state</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> state.todos.filter(<span class="function"><span class="params">todo</span> =></span> todo.done)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-2-2-通过属性访问"><a href="#2-2-2-通过属性访问" class="headerlink" title="2.2.2 通过属性访问"></a>2.2.2 通过属性访问</h4><p><code>Getter</code> 会暴露为 <code>store.getters</code> 对象,你可以以属性的形式访问这些值:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">store.getters.doneTodos <span class="comment">// -> [{ id: 1, text: '...', done: true }]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Getter 也可以接受其他 getter 作为第二个参数</span></span><br><span class="line">getters: {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> doneTodosCount: <span class="function">(<span class="params">state, getters</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> getters.doneTodos.length</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">store.getters.doneTodosCount <span class="comment">// -> 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 还可以很容易地在任何组件中使用它</span></span><br><span class="line">computed: {</span><br><span class="line"> doneTodosCount () {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.$store.getters.doneTodosCount</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意: <code>getter</code> 在通过属性访问时是作为 <code>Vue</code> 的响应式系统的一部分缓存其中的。</p><h4 id="2-2-3-通过方法访问"><a href="#2-2-3-通过方法访问" class="headerlink" title="2.2.3 通过方法访问"></a>2.2.3 通过方法访问</h4><p>还可以通过让 <code>getter</code> 返回一个函数,来实现给 <code>getter</code> 传参;在你对 <code>store</code> 里的数组进行查询时非常有用。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">getters: {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> getTodoById: <span class="function">(<span class="params">state</span>) =></span> <span class="function">(<span class="params">id</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> state.todos.find(<span class="function"><span class="params">todo</span> =></span> todo.id === id)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">store.getters.getTodoById(<span class="number">2</span>) <span class="comment">// -> { id: 2, text: '...', done: false }</span></span><br></pre></td></tr></table></figure><p>注意: <code>getter</code> 在通过方法访问时,每次都会去进行调用,而不会缓存结果。</p><h4 id="2-2-4-mapGetters-辅助函数"><a href="#2-2-4-mapGetters-辅助函数" class="headerlink" title="2.2.4 mapGetters 辅助函数"></a>2.2.4 mapGetters 辅助函数</h4><p><code>mapGetters</code> 辅助函数仅仅是将 <code>store</code> 中的 <code>getter</code> 映射到局部计算属性:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { mapGetters } <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> computed: {</span><br><span class="line"> <span class="comment">// 使用对象展开运算符将 getter 混入 computed 对象中</span></span><br><span class="line"> ...mapGetters([</span><br><span class="line"> <span class="string">'doneTodosCount'</span>,</span><br><span class="line"> <span class="string">'anotherGetter'</span>,</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> ])</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果想将一个 <code>getter</code> 属性另取一个名字,要使用对象形式:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mapGetters({</span><br><span class="line"> <span class="comment">// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`</span></span><br><span class="line"> doneCount: <span class="string">'doneTodosCount'</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="2-3-Mutation"><a href="#2-3-Mutation" class="headerlink" title="2.3 Mutation"></a>2.3 Mutation</h3><h4 id="2-3-1-修改-store-中的状态"><a href="#2-3-1-修改-store-中的状态" class="headerlink" title="2.3.1 修改 store 中的状态"></a>2.3.1 修改 store 中的状态</h4><p>更改 <code>Vuex</code> 的 <code>store</code> 中的状态的唯一方法是提交 <code>mutation</code> ;每个 <code>mutation</code> 都有一个字符串的 <code>事件类型 (type)</code> 和 一个 <code>回调函数 (handler)</code>;这个回调函数就是我们实际进行状态更改的地方,并且它会接受 <code>state</code> 作为第一个参数,如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> count: <span class="number">1</span></span><br><span class="line"> },</span><br><span class="line"> mutations: {</span><br><span class="line"> increment (state) {</span><br><span class="line"> <span class="comment">// 变更状态</span></span><br><span class="line"> state.count++</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>要调用此 <code>mutation handler</code>,需要以相应的 <code>type</code> 调用 <code>store.commit</code> 方法:<br><code>store.commit('increment')</code></p><h4 id="2-3-2-提交载荷(Payload)——额外参数"><a href="#2-3-2-提交载荷(Payload)——额外参数" class="headerlink" title="2.3.2 提交载荷(Payload)——额外参数"></a>2.3.2 提交载荷(Payload)——额外参数</h4><p>可以向 <code>store.commit</code> 传入额外的参数,即 <code>mutation</code> 的 载荷(payload),例:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">mutations: {</span><br><span class="line"> increment (state, n) {</span><br><span class="line"> state.count += n</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">store.commit(<span class="string">'increment'</span>, <span class="number">10</span>)</span><br></pre></td></tr></table></figure><p>载荷还可以是一个对象,这样可以包含多个字段并且记录的 <code>mutation</code> 会更易读;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">mutations: {</span><br><span class="line"> increment (state, payload) {</span><br><span class="line"> state.count += payload.amount</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">store.commit(<span class="string">'increment'</span>, {</span><br><span class="line"> amount: <span class="number">10</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-3-3-对象风格的提交方式"><a href="#2-3-3-对象风格的提交方式" class="headerlink" title="2.3.3 对象风格的提交方式"></a>2.3.3 对象风格的提交方式</h4><p>提交 <code>mutation</code> 的另一种方式是直接使用包含 <code>type</code> 属性的对象,使用对象风格的提交方式,整个对象都作为载荷传给 <code>mutation</code> 函数,因此 <code>handler</code> 保持不变:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义在store中的函数</span></span><br><span class="line">mutations: {</span><br><span class="line"> increment (state, payload) {</span><br><span class="line"> state.count += payload.amount</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 调用mutation handler</span></span><br><span class="line">store.commit({</span><br><span class="line"> type: <span class="string">'increment'</span>,</span><br><span class="line"> amount: <span class="number">10</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-3-4-Mutation-需遵守-Vue-的响应规则"><a href="#2-3-4-Mutation-需遵守-Vue-的响应规则" class="headerlink" title="2.3.4 Mutation 需遵守 Vue 的响应规则"></a>2.3.4 Mutation 需遵守 Vue 的响应规则</h4><ol><li>最好提前在你的 <code>store</code> 中初始化好所有所需属性。</li><li>当需要在对象上添加新属性时,你应该使用 <code>Vue.set(obj, 'newProp', 123)</code>, 或者 <code>以新对象替换老对象</code>;例如:<code>state.obj = { ...state.obj, newProp: 123 }</code></li></ol><h4 id="2-3-5-使用常量替代-Mutation-事件类型"><a href="#2-3-5-使用常量替代-Mutation-事件类型" class="headerlink" title="2.3.5 使用常量替代 Mutation 事件类型"></a>2.3.5 使用常量替代 Mutation 事件类型</h4><p>使用常量替代 <code>mutation</code> 事件类型在各种 <code>Flux</code> 实现中是很常见的模式;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// mutation-types.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> SOME_MUTATION = <span class="string">'SOME_MUTATION'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// store.js</span></span><br><span class="line"><span class="keyword">import</span> Vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"><span class="keyword">import</span> { SOME_MUTATION } <span class="keyword">from</span> <span class="string">'./mutation-types'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> state: { ... },</span><br><span class="line"> mutations: {</span><br><span class="line"> <span class="comment">// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名</span></span><br><span class="line"> [SOME_MUTATION] (state) {</span><br><span class="line"> <span class="comment">// mutate state</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-3-6-Mutation-必须是同步函数-重要原则"><a href="#2-3-6-Mutation-必须是同步函数-重要原则" class="headerlink" title="2.3.6 Mutation 必须是同步函数(重要原则)"></a>2.3.6 Mutation 必须是同步函数(重要原则)</h4><p>参考下面的例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mutations: {</span><br><span class="line"> someMutation (state) {</span><br><span class="line"> api.callAsyncMethod(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> state.count++</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-3-7-在组件中提交-Mutation"><a href="#2-3-7-在组件中提交-Mutation" class="headerlink" title="2.3.7 在组件中提交 Mutation"></a>2.3.7 在组件中提交 Mutation</h4><p>可以在组件中使用 <code>this.$store.commit('xxx')</code> 提交 <code>mutation</code> ,或者使用 <code>mapMutations</code> 辅助函数将组件中的 <code>methods</code> 映射为 <code>store.commit</code> 调用(需要在根节点注入 <code>store</code> ):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { mapMutations } <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> methods: {</span><br><span class="line"> ...mapMutations([</span><br><span class="line"> <span class="string">'increment'</span>, <span class="comment">// 将 `this.increment()` 映射为 `this.$store.commit('increment')`</span></span><br><span class="line"> <span class="comment">// `mapMutations` 也支持载荷:</span></span><br><span class="line"> <span class="string">'incrementBy'</span> <span class="comment">// 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`</span></span><br><span class="line"> ]),</span><br><span class="line"> ...mapMutations({</span><br><span class="line"> add: <span class="string">'increment'</span> <span class="comment">// 将 `this.add()` 映射为 `this.$store.commit('increment')`</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-3-8-Vuex-中-mutation-都是同步事务"><a href="#2-3-8-Vuex-中-mutation-都是同步事务" class="headerlink" title="2.3.8 Vuex 中 mutation 都是同步事务"></a>2.3.8 Vuex 中 <code>mutation</code> 都是同步事务</h4><p>在 <code>mutation</code> 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 <code>mutation</code> 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念,为了处理异步操作,来看一看 <code>Action</code>的功能;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">store.commit(<span class="string">'increment'</span>)</span><br><span class="line"><span class="comment">// 任何由 "increment" 导致的状态变更都应该在此刻完成。</span></span><br></pre></td></tr></table></figure><h3 id="2-4-Action"><a href="#2-4-Action" class="headerlink" title="2.4 Action"></a>2.4 Action</h3><p><code>Action</code> 类似于 <code>mutation</code> ,不同在于:</p><ol><li>Action 提交的是 <code>mutation</code> ,而不是直接变更状态;</li><li>Action 可以包含任意异步操作;</li></ol><p><code>Action</code> 函数接受一个与 <code>store</code> 实例具有相同方法和属性的 <code>context</code> 对象;因此你可以调用 <code>context.commit</code>提交一个 <code>mutation</code> ,或者通过 <code>context.state</code> 和 <code>context.getters</code> 来获取 <code>state</code> 和 <code>getters</code>;</p><p>注册一个简单的 <code>action</code> 例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> state: {</span><br><span class="line"> count: <span class="number">0</span></span><br><span class="line"> },</span><br><span class="line"> mutations: {</span><br><span class="line"> increment (state) {</span><br><span class="line"> state.count++</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> actions: {</span><br><span class="line"> increment (context) {</span><br><span class="line"> context.commit(<span class="string">'increment'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-4-1-分发-Action"><a href="#2-4-1-分发-Action" class="headerlink" title="2.4.1 分发 Action"></a>2.4.1 分发 Action</h4><p><code>Action</code> 通过 <code>store.dispatch</code> 方法触发: <code>store.dispatch('increment')</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Action 还可以在 action 内部执行异步操作(区别于mutation 必须同步执行):</span></span><br><span class="line">actions: {</span><br><span class="line"> incrementAsync ({ commit }) {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> commit(<span class="string">'increment'</span>)</span><br><span class="line"> }, <span class="number">1000</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Actions 支持同样的载荷方式和对象方式进行分发:</span></span><br><span class="line"><span class="comment">// 以载荷形式分发</span></span><br><span class="line">store.dispatch(<span class="string">'incrementAsync'</span>, {</span><br><span class="line"> amount: <span class="number">10</span></span><br><span class="line">})</span><br><span class="line"><span class="comment">// 以对象形式分发</span></span><br><span class="line">store.dispatch({</span><br><span class="line"> type: <span class="string">'incrementAsync'</span>,</span><br><span class="line"> amount: <span class="number">10</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>购物车示例,涉及到<code>调用异步 API</code> 和<code>分发多重 mutation</code> :</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">actions: {</span><br><span class="line"> checkout ({ commit, state }, products) {</span><br><span class="line"> <span class="comment">// 把当前购物车的物品备份起来</span></span><br><span class="line"> <span class="keyword">const</span> savedCartItems = [...state.cart.added]</span><br><span class="line"> <span class="comment">// 发出结账请求,然后乐观地清空购物车</span></span><br><span class="line"> commit(types.CHECKOUT_REQUEST)</span><br><span class="line"> <span class="comment">// 购物 API 接受一个成功回调和一个失败回调</span></span><br><span class="line"> shop.buyProducts(</span><br><span class="line"> products,</span><br><span class="line"> <span class="comment">// 成功操作</span></span><br><span class="line"> () => commit(types.CHECKOUT_SUCCESS),</span><br><span class="line"> <span class="comment">// 失败操作</span></span><br><span class="line"> () => commit(types.CHECKOUT_FAILURE, savedCartItems)</span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-4-2-在组件中分发-Action"><a href="#2-4-2-在组件中分发-Action" class="headerlink" title="2.4.2 在组件中分发 Action"></a>2.4.2 在组件中分发 Action</h4><p>在组件中使用 <code>this.$store.dispatch('xxx')</code> 分发 <code>action</code> ,或者使用 <code>mapActions</code> 辅助函数将组件的 <code>methods</code> 映射为 <code>store.dispatch</code> 调用(需要先在根节点注入 <code>store</code> ):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { mapActions } <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> methods: {</span><br><span class="line"> ...mapActions([</span><br><span class="line"> <span class="string">'increment'</span>, <span class="comment">// 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`</span></span><br><span class="line"> <span class="comment">// `mapActions` 也支持载荷:</span></span><br><span class="line"> <span class="string">'incrementBy'</span> <span class="comment">// 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`</span></span><br><span class="line"> ]),</span><br><span class="line"> ...mapActions({</span><br><span class="line"> add: <span class="string">'increment'</span> <span class="comment">// 将 `this.add()` 映射为 `this.$store.dispatch('increment')`</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-4-3-组合-Action"><a href="#2-4-3-组合-Action" class="headerlink" title="2.4.3 组合 Action"></a>2.4.3 组合 Action</h4><p><code>Action</code> 通常是异步的,那么如何知道 <code>action</code> 什么时候结束呢?首先要明白 <code>store.dispatch</code> 可以处理被触发的 <code>action</code> 的处理函数返回的 <code>Promise</code> ,并且 <code>store.dispatch</code> 仍旧返回 <code>Promise</code> :</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">actions: {</span><br><span class="line"> actionA ({ commit }) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> commit(<span class="string">'someMutation'</span>)</span><br><span class="line"> resolve()</span><br><span class="line"> }, <span class="number">1000</span>)</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以直接调用,使用 promise 语法</span></span><br><span class="line">store.dispatch(<span class="string">'actionA'</span>).then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在另外一个 action 中也可以如下:</span></span><br><span class="line">actions: {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> actionB ({ dispatch, commit }) {</span><br><span class="line"> <span class="keyword">return</span> dispatch(<span class="string">'actionA'</span>).then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> commit(<span class="string">'someOtherMutation'</span>)</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 若利用 async / await,我们可以如下组合 action:</span></span><br><span class="line"><span class="comment">// 假设 getData() 和 getOtherData() 返回的是 Promise</span></span><br><span class="line">actions: {</span><br><span class="line"> <span class="keyword">async</span> actionA ({ commit }) {</span><br><span class="line"> commit(<span class="string">'gotData'</span>, <span class="keyword">await</span> getData())</span><br><span class="line"> },</span><br><span class="line"> <span class="keyword">async</span> actionB ({ dispatch, commit }) {</span><br><span class="line"> <span class="keyword">await</span> dispatch(<span class="string">'actionA'</span>) <span class="comment">// 等待 actionA 完成</span></span><br><span class="line"> commit(<span class="string">'gotOtherData'</span>, <span class="keyword">await</span> getOtherData())</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>一个 <code>store.dispatch</code> 在不同模块中可以触发多个 <code>action</code> 函数;在这种情况下,只有当所有触发函数完成后,返回的 <code>Promise</code> 才会执行;</p><h3 id="2-5-Module"><a href="#2-5-Module" class="headerlink" title="2.5 Module"></a>2.5 Module</h3><p>由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时, <code>store</code> 对象就有可能变得相当臃肿;为了解决以上问题, <code>Vuex</code> 允许我们将 <code>store</code> 分割成模块( <code>module</code> ),每个模块拥有自己的 <code>state; mutation; action; getter; 嵌套子模块</code>;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> moduleA = {</span><br><span class="line"> state: { ... },</span><br><span class="line"> mutations: { ... },</span><br><span class="line"> actions: { ... },</span><br><span class="line"> getters: { ... }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> moduleB = {</span><br><span class="line"> state: { ... },</span><br><span class="line"> mutations: { ... },</span><br><span class="line"> actions: { ... }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> modules: {</span><br><span class="line"> a: moduleA,</span><br><span class="line"> b: moduleB</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">store.state.a <span class="comment">// -> moduleA 的状态</span></span><br><span class="line">store.state.b <span class="comment">// -> moduleB 的状态</span></span><br></pre></td></tr></table></figure><h4 id="2-5-1-模块的局部状态"><a href="#2-5-1-模块的局部状态" class="headerlink" title="2.5.1 模块的局部状态"></a>2.5.1 模块的局部状态</h4><p>对于模块内部的 <code>mutation</code> 和 <code>getter</code> ,接收的第一个参数是<strong>模块的局部状态对象</strong>;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> moduleA = {</span><br><span class="line"> state: { <span class="attr">count</span>: <span class="number">0</span> },</span><br><span class="line"> mutations: {</span><br><span class="line"> increment (state) {</span><br><span class="line"> <span class="comment">// 这里的 `state` 对象是模块的局部状态</span></span><br><span class="line"> state.count++</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> getters: {</span><br><span class="line"> doubleCount (state) {</span><br><span class="line"> <span class="keyword">return</span> state.count * <span class="number">2</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>同样,对于模块内部的 <code>action</code> ,局部状态通过 <code>context.state</code> 暴露出来,根节点状态则为 <code>context.rootState</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> moduleA = {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> actions: {</span><br><span class="line"> incrementIfOddOnRootSum ({ state, commit, rootState }) {</span><br><span class="line"> <span class="keyword">if</span> ((state.count + rootState.count) % <span class="number">2</span> === <span class="number">1</span>) {</span><br><span class="line"> commit(<span class="string">'increment'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对于模块内部的 <code>getter</code> ,根节点状态会作为第三个参数暴露出来:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> moduleA = {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> getters: {</span><br><span class="line"> sumWithRootCount (state, getters, rootState) {</span><br><span class="line"> <span class="keyword">return</span> state.count + rootState.count</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="2-5-2-命名空间"><a href="#2-5-2-命名空间" class="headerlink" title="2.5.2 命名空间"></a>2.5.2 命名空间</h4><p>默认情况下,模块内部的 <code>action、mutation 和 getter</code> 是注册在全局命名空间的——这样使得多个模块能够对同一 <code>mutation</code> 或 <code>action</code> 作出响应;</p><p>若希望模块具有更高的封装度和复用性,可以通过添加 <code>namespaced: true</code> 的方式使其成为带命名空间的模块;当模块被注册后,它的所有 <code>getter; action; mutation</code> 都会自动根据模块注册的路径调整命名。例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> modules: {</span><br><span class="line"> account: {</span><br><span class="line"> namespaced: <span class="literal">true</span>,</span><br><span class="line"> <span class="comment">// 模块内容(module assets)</span></span><br><span class="line"> state: { ... }, <span class="comment">// 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响</span></span><br><span class="line"> getters: {</span><br><span class="line"> isAdmin () { ... } <span class="comment">// -> getters['account/isAdmin']</span></span><br><span class="line"> },</span><br><span class="line"> actions: {</span><br><span class="line"> login () { ... } <span class="comment">// -> dispatch('account/login')</span></span><br><span class="line"> },</span><br><span class="line"> mutations: {</span><br><span class="line"> login () { ... } <span class="comment">// -> commit('account/login')</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 嵌套模块</span></span><br><span class="line"> modules: {</span><br><span class="line"> <span class="comment">// 继承父模块的命名空间</span></span><br><span class="line"> myPage: {</span><br><span class="line"> state: { ... },</span><br><span class="line"> getters: {</span><br><span class="line"> profile () { ... } <span class="comment">// -> getters['account/profile']</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 进一步嵌套命名空间</span></span><br><span class="line"> posts: {</span><br><span class="line"> namespaced: <span class="literal">true</span>,</span><br><span class="line"> state: { ... },</span><br><span class="line"> getters: {</span><br><span class="line"> popular () { ... } <span class="comment">// -> getters['account/posts/popular']</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-5-3-模块动态注册"><a href="#2-5-3-模块动态注册" class="headerlink" title="2.5.3 模块动态注册"></a>2.5.3 模块动态注册</h4><p>使用 <code>store.registerModule</code> 方法注册模块,注册后就可以通过 <code>store.state.myModule</code> 和 <code>store.state.nested.myModule</code> 访问模块的状态:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注册模块 `myModule`</span></span><br><span class="line">store.registerModule(<span class="string">'myModule'</span>, {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">})</span><br><span class="line"><span class="comment">// 注册嵌套模块 `nested/myModule`</span></span><br><span class="line">store.registerModule([<span class="string">'nested'</span>, <span class="string">'myModule'</span>], {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="2-5-4-模块重用"><a href="#2-5-4-模块重用" class="headerlink" title="2.5.4 模块重用"></a>2.5.4 模块重用</h4><p>有时需要创建一个模块的多个实例,如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 <code>store</code> 或模块间数据互相污染的问题;实际上这和 <code>Vue</code> 组件内的 <code>data</code> 是同样的问题:解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> MyReusableModule = {</span><br><span class="line"> state () {</span><br><span class="line"> <span class="comment">// 使用函数返回数据</span></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> foo: <span class="string">'bar'</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// mutation, action 和 getter 等等...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="3-项目结构"><a href="#3-项目结构" class="headerlink" title="3. 项目结构"></a>3. 项目结构</h2><p>规定了一些需要遵守的规则:</p><ol><li>应用层级的状态应该集中到单个 <code>store</code> 对象中;</li><li>提交 mutation 是更改状态的唯一方法,并且这个过程是同步的;</li><li>异步逻辑都应该封装到 action 里面;</li></ol><p>下面是项目结构示例:</p><figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">├── index.html</span><br><span class="line">├── main.js</span><br><span class="line">├── api</span><br><span class="line">│ └── ... # 抽取出API请求</span><br><span class="line">├── components</span><br><span class="line">│ ├── App.vue</span><br><span class="line">│ └── ...</span><br><span class="line">└── store</span><br><span class="line"> ├── index.js # 我们组装模块并导出 store 的地方</span><br><span class="line"> ├── actions.js # 根级别的 action</span><br><span class="line"> ├── mutations.js # 根级别的 mutation</span><br><span class="line"> └── modules</span><br><span class="line"> ├── cart.js # 购物车模块</span><br><span class="line"> └── products.js # 产品模块</span><br></pre></td></tr></table></figure><h2 id="4-未-插件"><a href="#4-未-插件" class="headerlink" title="4. (未)插件"></a>4. (未)插件</h2><h2 id="5-严格模式"><a href="#5-严格模式" class="headerlink" title="5. 严格模式"></a>5. 严格模式</h2><p>开启严格模式,仅需在创建 <code>store</code> 的时候传入 <code>strict: true</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> strict: <span class="literal">true</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>在严格模式下,无论何时发生了状态变更且不是由 <code>mutation</code> 函数引起的,将会抛出错误;这能保证所有的状态变更都能被调试工具跟踪到;</p><h3 id="5-1-开发环境与发布环境"><a href="#5-1-开发环境与发布环境" class="headerlink" title="5.1 开发环境与发布环境"></a>5.1 开发环境与发布环境</h3><p><strong>不要在发布环境下启用严格模式</strong> 严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失,处理如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> strict: process.env.NODE_ENV !== <span class="string">'production'</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="6-表单处理"><a href="#6-表单处理" class="headerlink" title="6. 表单处理"></a>6. 表单处理</h2><p>当在严格模式中使用 <code>Vuex</code> 时,在属于 <code>Vuex</code> 的 <code>state</code> 上使用 <code>v-model</code> 会比较棘手:<code><input v-model="obj.message"></code>;用“Vuex 的思维”去解决这个问题的方法是:给 <code><input></code> 中绑定 <code>value</code> ,然后侦听 <code>input</code> 或者 <code>change</code> 事件,在事件回调中调用 <code>action</code> :</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">input</span> <span class="attr">:value</span>=<span class="string">"message"</span> @<span class="attr">input</span>=<span class="string">"updateMessage"</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">computed: {</span><br><span class="line"> ...mapState({</span><br><span class="line"> message: <span class="function"><span class="params">state</span> =></span> state.obj.message</span><br><span class="line"> })</span><br><span class="line">},</span><br><span class="line">methods: {</span><br><span class="line"> updateMessage (e) {</span><br><span class="line"> <span class="keyword">this</span>.$store.commit(<span class="string">'updateMessage'</span>, e.target.value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下面是 mutation 函数:</span></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">mutations: {</span><br><span class="line"> updateMessage (state, message) {</span><br><span class="line"> state.obj.message = message</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="6-1-双向绑定的计算属性"><a href="#6-1-双向绑定的计算属性" class="headerlink" title="6.1 双向绑定的计算属性"></a>6.1 双向绑定的计算属性</h3><p>使用带有 <code>setter</code> 的双向绑定计算属性:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">input</span> <span class="attr">v-model</span>=<span class="string">"message"</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">computed: {</span><br><span class="line"> message: {</span><br><span class="line"> <span class="keyword">get</span> () {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.$store.state.obj.message</span><br><span class="line"> },</span><br><span class="line"> <span class="keyword">set</span> (value) {</span><br><span class="line"> <span class="keyword">this</span>.$store.commit(<span class="string">'updateMessage'</span>, value)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-未-测试"><a href="#7-未-测试" class="headerlink" title="7. (未)测试"></a>7. (未)测试</h2><h2 id="8-未-热重载"><a href="#8-未-热重载" class="headerlink" title="8. (未)热重载"></a>8. (未)热重载</h2>]]></content>
<summary type="html">
<p>在 Vue 的学习中,Vuex 是一个专为 Vue 开发的应用程序的状态管理模式,当我们构建一个中大型的单页面应用程序时,Vuex可以更好的帮助我们在组件外部统一管理状态,Vuex具体使用方法查看文档笔记;</p>
</summary>
<category term="Vue" scheme="https://geek-lhj.github.io/categories/Vue/"/>
<category term="Vuex" scheme="https://geek-lhj.github.io/categories/Vue/Vuex/"/>
<category term="官方文档" scheme="https://geek-lhj.github.io/categories/Vue/Vuex/%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3/"/>
<category term="Vue" scheme="https://geek-lhj.github.io/tags/Vue/"/>
<category term="Vuex" scheme="https://geek-lhj.github.io/tags/Vuex/"/>
<category term="状态管理器" scheme="https://geek-lhj.github.io/tags/%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E5%99%A8/"/>
</entry>
<entry>
<title>VueRouterAPI官方参考文档笔记</title>
<link href="https://geek-lhj.github.io/2019/03/05/VueRouterAPI%E5%AE%98%E6%96%B9%E5%8F%82%E8%80%83%E6%96%87%E6%A1%A3%E7%AC%94%E8%AE%B0/"/>
<id>https://geek-lhj.github.io/2019/03/05/VueRouterAPI%E5%AE%98%E6%96%B9%E5%8F%82%E8%80%83%E6%96%87%E6%A1%A3%E7%AC%94%E8%AE%B0/</id>
<published>2019-03-05T10:38:58.000Z</published>
<updated>2020-01-17T14:45:56.184Z</updated>
<content type="html"><![CDATA[<p>在 Vue 的学习中,VueRouter 是 Vue.js 官方的路由管理器,该文档是对 VueRouter 官方API文档的知识的学习笔记,包含 VueRouter 具体的API接口的使用方法,具体查看文档;</p><a id="more"></a><h1 id="VueRouterAPI官方参考文档笔记"><a href="#VueRouterAPI官方参考文档笔记" class="headerlink" title="VueRouterAPI官方参考文档笔记"></a>VueRouterAPI官方参考文档笔记</h1><p><a href="https://router.vuejs.org/zh/api/#router-link" target="_blank" rel="noopener">VueRouterAPI文档</a></p><h2 id="1-Router-基本API"><a href="#1-Router-基本API" class="headerlink" title="1. Router 基本API"></a>1. Router 基本API</h2><h3 id="1-1-lt-router-link-gt"><a href="#1-1-lt-router-link-gt" class="headerlink" title="1.1 <router-link>"></a>1.1 <router-link></h3><p><code><router-link></code> 组件支持用户在具有路由功能的应用中 (点击) 导航;该组件 <code>to</code> 属性指定目标地址,默认渲染成带有正确链接的 <code><a></code> 标签,可以通过配置 <code>tag</code> 属性生成别的标签;当目标路由成功激活时,链接元素自动设置一个表示激活的 <code>CSS</code> 类名。</p><h4 id="将激活-class-应用在外层元素"><a href="#将激活-class-应用在外层元素" class="headerlink" title="将激活 class 应用在外层元素"></a>将激活 class 应用在外层元素</h4><p>激活 <code>class</code> 应用在外层元素,而不是 <code><a></code> 标签本身,那么可以用 <code><router-link></code> 渲染外层元素,包裹着内层的原生 <code><a></code> 标签:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">tag</span>=<span class="string">"li"</span> <span class="attr">to</span>=<span class="string">"/foo"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span>></span>/foo<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"><span class="tag"></<span class="name">router-link</span>></span></span><br></pre></td></tr></table></figure><p><code><a></code> 将作为真实的链接 (它会获得正确的 <code>href</code> 的),而 “激活时的 <code>CSS</code> 类名” 则设置到外层的 <code><li></code> 上;</p><h3 id="1-2-lt-router-link-gt-Props"><a href="#1-2-lt-router-link-gt-Props" class="headerlink" title="1.2 <router-link> Props"></a>1.2 <router-link> Props</h3><h4 id="to"><a href="#to" class="headerlink" title="to"></a>to</h4><table><thead><tr><th>类型</th><th>required</th><th>功能描述</th></tr></thead><tbody><tr><td>string 或 Location</td><td>true</td><td>表示目标路由的链接。当被点击后,内部会立刻把 <code>to</code> 的值传到 <code>router.push()</code>,所以这个值可以是一个字符串或者是描述目标位置的对象。</td></tr></tbody></table><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 字符串 --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">to</span>=<span class="string">"home"</span>></span>Home<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"><span class="comment"><!-- 渲染结果 --></span></span><br><span class="line"><span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"home"</span>></span>Home<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"><span class="comment"><!-- 使用 v-bind 的 JS 表达式 --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">v-bind:to</span>=<span class="string">"'home'"</span>></span>Home<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"><span class="comment"><!-- 不写 v-bind 也可以,就像绑定别的属性一样 --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"'home'"</span>></span>Home<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"><span class="comment"><!-- 同上 --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"{ path: 'home' }"</span>></span>Home<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"><span class="comment"><!-- 命名的路由 --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"{ name: 'user', params: { userId: 123 }}"</span>></span>User<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"><span class="comment"><!-- 带查询参数,下面的结果为 /register?plan=private --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"{ path: 'register', query: { plan: 'private' }}"</span>></span>Register<span class="tag"></<span class="name">router-link</span>></span></span><br></pre></td></tr></table></figure><h4 id="replace"><a href="#replace" class="headerlink" title="replace"></a>replace</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>boolean</td><td>false</td><td>设置 <code>replace</code> 属性的话,当点击时,会调用 <code>router.replace()</code> 而不是 <code>router.push()</code>,于是导航后不会留下 <code>history</code> 记录;</td></tr></tbody></table><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"{ path: '/abc'}"</span> <span class="attr">replace</span>></span><span class="tag"></<span class="name">router-link</span>></span></span><br></pre></td></tr></table></figure><h4 id="append"><a href="#append" class="headerlink" title="append"></a>append</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>boolean</td><td>false</td><td>设置 <code>append</code> 属性后,则在当前 <code>(相对)</code>路径前添加<code>基路径</code>。例如,我们从 <code>/a</code> 导航到一个相对路径 <code>b</code>,如果没有配置 <code>append</code> ,则路径为 <code>/b</code>,如果配了,则为 <code>/a/b</code> ;</td></tr></tbody></table><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"{ path: 'relative/path'}"</span> <span class="attr">append</span>></span><span class="tag"></<span class="name">router-link</span>></span></span><br></pre></td></tr></table></figure><h4 id="tag"><a href="#tag" class="headerlink" title="tag"></a>tag</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>boolean</td><td>‘a’</td><td>若想要 <code><router-link></code> 渲染成某种标签,例如 <code><li></code>; 于是使用 <code>tag prop</code> 类指定何种标签,同样它还是会监听点击,触发导航;</td></tr></tbody></table><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">to</span>=<span class="string">"/foo"</span> <span class="attr">tag</span>=<span class="string">"li"</span>></span>foo<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"><span class="comment"><!-- 渲染结果 --></span></span><br><span class="line"><span class="tag"><<span class="name">li</span>></span>foo<span class="tag"></<span class="name">li</span>></span></span><br></pre></td></tr></table></figure><h4 id="active-class"><a href="#active-class" class="headerlink" title="active-class"></a>active-class</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>“router-link-active”</td><td>设置 链接激活时使用的 <code>CSS</code> 类名;默认值可以通过路由的构造选项 <code>linkActiveClass</code> 来全局配置;</td></tr></tbody></table><h4 id="exact"><a href="#exact" class="headerlink" title="exact"></a>exact</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>boolean</td><td>false</td><td>“是否激活” 默认类名的依据是 <code>inclusive match</code> (全包含匹配);如果当前的路径是 <code>/a</code> 开头的,那么 <code><router-link to="/a"></code> 也会被设置 CSS 类名;按照这个规则,每个路由都会激活<code><router-link to="/"></code>,因而想要链接使用 “<code>exact 匹配模式</code>“,则使用 <code>exact</code> 属性;</td></tr></tbody></table><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 这个链接只会在地址为 / 的时候被激活 --></span></span><br><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">to</span>=<span class="string">"/"</span> <span class="attr">exact</span>></span></span><br></pre></td></tr></table></figure><h4 id="event"><a href="#event" class="headerlink" title="event"></a>event</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string 或 Array<code><string></code></td><td>‘click’</td><td>声明可以用来触发导航的事件;可以是一个字符串或是一个包含字符串的数组;</td></tr></tbody></table><h4 id="exact-active-class"><a href="#exact-active-class" class="headerlink" title="exact-active-class"></a>exact-active-class</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>“router-link-exact-active”</td><td>配置当链接被精确匹配的时候应该激活的 <code>class</code> ;注意默认值也是可以通过路由构造函数选项 <code>linkExactActiveClass</code> 进行全局配置的;</td></tr></tbody></table><h3 id="1-3-lt-router-view-gt"><a href="#1-3-lt-router-view-gt" class="headerlink" title="1.3 <router-view>"></a>1.3 <router-view></h3><p><code><router-view></code> 组件是一个 <code>functional</code> 组件,渲染路径匹配到的视图组件;<code><router-view></code> 渲染的组件还可以内嵌自己的 <code><router-view></code>,根据嵌套路径,渲染嵌套组件;<br>因为<code><router-view></code>也是个组件,所以可以配合 <code><transition></code> 和 <code><keep-alive></code> 使用,如两个结合一起用,要确保在内层使用 <code><keep-alive></code>:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">transition</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">keep-alive</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">router-view</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">keep-alive</span>></span></span><br><span class="line"><span class="tag"></<span class="name">transition</span>></span></span><br></pre></td></tr></table></figure><h3 id="1-4-lt-router-view-gt-Props"><a href="#1-4-lt-router-view-gt-Props" class="headerlink" title="1.4 <router-view> Props"></a>1.4 <router-view> Props</h3><h4 id="name"><a href="#name" class="headerlink" title="name"></a>name</h4><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>“default”</td><td>若 <code><router-view></code>设置了名称,则会渲染对应的路由配置中 <code>components</code> 下的相应组件;</td></tr></tbody></table><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-view</span> <span class="attr">class</span>=<span class="string">"view one"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"><<span class="name">router-view</span> <span class="attr">class</span>=<span class="string">"view two"</span> <span class="attr">name</span>=<span class="string">"a"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"><<span class="name">router-view</span> <span class="attr">class</span>=<span class="string">"view three"</span> <span class="attr">name</span>=<span class="string">"b"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'/'</span>,</span><br><span class="line"> components: {</span><br><span class="line"> <span class="keyword">default</span>: Foo,</span><br><span class="line"> a: Bar,</span><br><span class="line"> b: Baz</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="2-Router-构建选项"><a href="#2-Router-构建选项" class="headerlink" title="2. Router 构建选项"></a>2. Router 构建选项</h2><h3 id="routes"><a href="#routes" class="headerlink" title="routes"></a>routes</h3><table><thead><tr><th>类型</th><th>功能描述</th></tr></thead><tbody><tr><td>Array<code><RouteConfig></code></td><td>RouteConfig 的类型定义如下:</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">declare type RouteConfig = {</span><br><span class="line"> path: string;</span><br><span class="line"> component?: Component;</span><br><span class="line"> name?: string; <span class="comment">// 命名路由</span></span><br><span class="line"> components?: { [name: string]: Component }; <span class="comment">// 命名视图组件</span></span><br><span class="line"> redirect?: string | Location | <span class="built_in">Function</span>;</span><br><span class="line"> props?: boolean | <span class="built_in">Object</span> | <span class="built_in">Function</span>;</span><br><span class="line"> alias?: string | <span class="built_in">Array</span><string>;</span><br><span class="line"> children?: <span class="built_in">Array</span><RouteConfig>; <span class="comment">// 嵌套路由</span></span><br><span class="line"> beforeEnter?: <span class="function">(<span class="params">to: Route, <span class="keyword">from</span>: Route, next: <span class="built_in">Function</span></span>) =></span> <span class="keyword">void</span>;</span><br><span class="line"> meta?: any;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2.6.0+</span></span><br><span class="line"> caseSensitive?: boolean; <span class="comment">// 匹配规则是否大小写敏感?(默认值:false)</span></span><br><span class="line"> pathToRegexpOptions?: <span class="built_in">Object</span>; <span class="comment">// 编译正则的选项</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="mode"><a href="#mode" class="headerlink" title="mode"></a>mode</h3><table><thead><tr><th>类型</th><th>默认值</th><th>可选值</th></tr></thead><tbody><tr><td>string</td><td>“hash” (浏览器环境) 或 “abstract” (Node.js 环境)</td><td>“hash” 或 “history” 或 “abstract”</td></tr></tbody></table><p>配置路由模式介绍:</p><ul><li>hash: 使用 <code>URL hash</code> 值来作路由。支持所有浏览器,包括不支持 <code>HTML5 History Api</code> 的浏览器;</li><li>history: 依赖 <code>HTML5 History API</code> 和服务器配置;查看 <code>HTML5 History</code> 模式;</li><li>abstract: 支持所有 <code>JavaScript</code> 运行环境,如 <code>Node.js</code> 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式;</li></ul><h3 id="base"><a href="#base" class="headerlink" title="base"></a>base</h3><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>“/“</td><td>应用的基路径。例如,如果整个单页应用服务在 <code>/app/</code> 下,然后 <code>base</code> 就应该设为 <code>"/app/"</code>;</td></tr></tbody></table><h3 id="linkActiveClass"><a href="#linkActiveClass" class="headerlink" title="linkActiveClass"></a>linkActiveClass</h3><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>“router-link-active”</td><td>全局配置 <code><router-link></code> 的默认<code>“激活 class 类名”</code>;</td></tr></tbody></table><h3 id="linkExactActiveClass"><a href="#linkExactActiveClass" class="headerlink" title="linkExactActiveClass"></a>linkExactActiveClass</h3><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>“router-link-exact-active”</td><td>全局配置 <router-link> 精确激活的默认的 <code>class</code> ;</router-link></td></tr></tbody></table><h3 id="scrollBehavior"><a href="#scrollBehavior" class="headerlink" title="scrollBehavior"></a>scrollBehavior</h3><table><thead><tr><th>类型</th><th>签名</th></tr></thead><tbody><tr><td>Function</td><td><a href="https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html" target="_blank" rel="noopener">滚动行为</a></td></tr></tbody></table><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">type PositionDescriptor =</span><br><span class="line"> { <span class="attr">x</span>: number, <span class="attr">y</span>: number } |</span><br><span class="line"> { <span class="attr">selector</span>: string } |</span><br><span class="line"> ?{}</span><br><span class="line"></span><br><span class="line">type scrollBehaviorHandler = (</span><br><span class="line"> to: Route,</span><br><span class="line"> <span class="keyword">from</span>: Route,</span><br><span class="line"> savedPosition?: { <span class="attr">x</span>: number, <span class="attr">y</span>: number }</span><br><span class="line">) => PositionDescriptor | <span class="built_in">Promise</span><PositionDescriptor></span><br></pre></td></tr></table></figure><h3 id="parseQuery-stringifyQuery"><a href="#parseQuery-stringifyQuery" class="headerlink" title="parseQuery / stringifyQuery"></a>parseQuery / stringifyQuery</h3><table><thead><tr><th>类型</th><th>功能描述</th></tr></thead><tbody><tr><td>Function</td><td>提供自定义查询字符串的解析/反解析函数;覆盖默认行为。</td></tr></tbody></table><h3 id="fallback"><a href="#fallback" class="headerlink" title="fallback"></a>fallback</h3><table><thead><tr><th>类型</th><th>默认值</th><th>功能描述</th></tr></thead><tbody><tr><td>boolean</td><td>true</td><td>当浏览器不支持 <code>history.pushState</code> 控制路由是否应该回退到 <code>hash</code> 模式;默认值为 <code>true</code>;</td></tr></tbody></table><p>在 <code>IE9</code> 中,设置为 <code>false</code> 会使得每个 <code>router-link</code> 导航都触发整页刷新;它可用于工作在 <code>IE9</code> 下的服务端渲染应用,因为一个 <code>hash</code> 模式的 <code>URL</code> 并不支持服务端渲染;</p><h2 id="3-Router-实例属性"><a href="#3-Router-实例属性" class="headerlink" title="3. Router 实例属性"></a>3. Router 实例属性</h2><h3 id="router-app"><a href="#router-app" class="headerlink" title="router.app"></a>router.app</h3><table><thead><tr><th>类型</th><th>功能描述</th></tr></thead><tbody><tr><td>Vue instance</td><td>配置了 <code>router</code> 的 <code>Vue</code> 根实例;</td></tr></tbody></table><h3 id="router-mode"><a href="#router-mode" class="headerlink" title="router.mode"></a>router.mode</h3><table><thead><tr><th>类型</th><th>功能描述</th></tr></thead><tbody><tr><td>string</td><td>路由使用的模式(”hash” 或 “history” 或 “abstract”);</td></tr></tbody></table><h3 id="router-currentRoute"><a href="#router-currentRoute" class="headerlink" title="router.currentRoute"></a>router.currentRoute</h3><table><thead><tr><th>类型</th><th>功能描述</th></tr></thead><tbody><tr><td>Route</td><td>当前路由对应的路由信息对象;</td></tr></tbody></table><h2 id="4-Router-实例方法"><a href="#4-Router-实例方法" class="headerlink" title="4. Router 实例方法"></a>4. Router 实例方法</h2><h3 id="router-beforeEach"><a href="#router-beforeEach" class="headerlink" title="router.beforeEach"></a>router.beforeEach</h3><h3 id="router-beforeResolve"><a href="#router-beforeResolve" class="headerlink" title="router.beforeResolve"></a>router.beforeResolve</h3><h3 id="router-afterEach"><a href="#router-afterEach" class="headerlink" title="router.afterEach"></a>router.afterEach</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 增加全局的导航守卫,参考导航守卫;这三个方法都返回一个移除已注册的守卫/钩子的函数;</span></span><br><span class="line">router.beforeEach(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> <span class="comment">/* must call `next` */</span></span><br><span class="line">})</span><br><span class="line">router.beforeResolve(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> <span class="comment">/* must call `next` */</span></span><br><span class="line">})</span><br><span class="line">router.afterEach(<span class="function">(<span class="params">to, <span class="keyword">from</span></span>) =></span> {})</span><br></pre></td></tr></table></figure><h3 id="router-push"><a href="#router-push" class="headerlink" title="router.push"></a>router.push</h3><h3 id="router-replace"><a href="#router-replace" class="headerlink" title="router.replace"></a>router.replace</h3><h3 id="router-go"><a href="#router-go" class="headerlink" title="router.go"></a>router.go</h3><h3 id="router-back"><a href="#router-back" class="headerlink" title="router.back"></a>router.back</h3><h3 id="router-forward"><a href="#router-forward" class="headerlink" title="router.forward"></a>router.forward</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 动态的导航到一个新 URL,参考编程式导航。</span></span><br><span class="line">router.push(location, onComplete?, onAbort?)</span><br><span class="line">router.replace(location, onComplete?, onAbort?)</span><br><span class="line">router.go(n)</span><br><span class="line">router.back()</span><br><span class="line">router.forward()</span><br></pre></td></tr></table></figure><h3 id="router-getMatchedComponents"><a href="#router-getMatchedComponents" class="headerlink" title="router.getMatchedComponents"></a>router.getMatchedComponents</h3><ul><li>函数签名:<code>const matchedComponents: Array<Component> = router.getMatchedComponents(location?)</code>;</li><li>功能:返回目标位置或是当前路由匹配的组件数组 (是数组的定义/构造类,不是实例)。通常在服务端渲染的数据预加载时使用;</li></ul><h3 id="router-resolve"><a href="#router-resolve" class="headerlink" title="router.resolve"></a>router.resolve</h3><ul><li>函数签名:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> resolved: {</span><br><span class="line"> location: Location;</span><br><span class="line"> route: Route;</span><br><span class="line"> href: string;</span><br><span class="line">} = router.resolve(location, current?, append?)</span><br></pre></td></tr></table></figure></li><li>功能:解析目标位置 (格式和 <code><router-link></code> 的 <code>to prop</code> 一样);<ul><li><code>current</code> 是当前默认的路由 (通常你不需要改变它);</li><li><code>append</code> 允许你在 current 路由上附加路径 (如同 <code>router-link</code>);</li></ul></li></ul><h3 id="router-addRoutes"><a href="#router-addRoutes" class="headerlink" title="router.addRoutes"></a>router.addRoutes</h3><ul><li>函数签名:<code>router.addRoutes(routes: Array<RouteConfig>)</code>;</li><li>功能:动态添加更多的路由规则;参数必须是一个符合 <code>routes</code> 选项要求的数组;</li></ul><h3 id="router-onReady"><a href="#router-onReady" class="headerlink" title="router.onReady"></a>router.onReady</h3><ul><li>函数签名:<code>router.onReady(callback, [errorCallback])</code>;</li><li>功能:该方法把一个回调排队,在路由完成初始导航时调用,这意味着它可以解析所有的异步进入钩子和路由初始化相关联的异步组件;这可以有效确保服务端渲染时服务端和客户端输出的一致。</li></ul><h3 id="router-onError"><a href="#router-onError" class="headerlink" title="router.onError"></a>router.onError</h3><ul><li>函数签名:<code>router.onError(callback)</code>;</li><li>功能:注册一个回调,该回调会在路由导航过程中出错时被调用;注意被调用的错误必须是下列情形中的一种:<ul><li>错误在一个路由守卫函数中被同步抛出;</li><li>错误在一个路由守卫函数中通过调用 next(err) 的方式异步捕获并处理;</li><li>渲染一个路由的过程中,需要尝试解析一个异步组件时发生错误;</li></ul></li></ul><h2 id="5-路由对象"><a href="#5-路由对象" class="headerlink" title="5. 路由对象"></a>5. 路由对象</h2><p>一个路由对象 (<code>route object</code>) 表示当前激活的路由的状态信息,包含了当前 <code>URL</code> 解析得到的信息,还有 URL 匹配到的路由记录 (<code>route records</code>);路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象;</p><p>路由对象出现在多个地方:</p><ol><li>在组件内,即 <code>this.$route</code>;</li><li>在 <code>$route</code> 观察者回调内;</li><li><code>router.match(location)</code> 的返回值;</li><li>导航守卫的参数:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.beforeEach(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> <span class="comment">// `to` 和 `from` 都是路由对象</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure></li><li><code>scrollBehavior</code> 方法的参数:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> scrollBehavior (to, <span class="keyword">from</span>, savedPosition) {</span><br><span class="line"> <span class="comment">// `to` 和 `from` 都是路由对象</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure></li></ol><h3 id="路由对象属性"><a href="#路由对象属性" class="headerlink" title="路由对象属性"></a>路由对象属性</h3><table><thead><tr><th>路由对象属性名</th><th>类型</th><th>功能描述</th></tr></thead><tbody><tr><td>$route.path</td><td>string</td><td>字符串,对应当前路由的路径,总是解析为绝对路径,如 <code>"/foo/bar"</code></td></tr><tr><td>$route.params</td><td>Object</td><td>一个 <code>key/value</code> 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象;</td></tr><tr><td>$route.query</td><td>Object</td><td>一个 <code>key/value</code> 对象,表示 <code>URL</code> 查询参数。例如,对于路径 <code>/foo?user=1</code>,则有 <code>$route.query.user == 1</code>,如果没有查询参数,则是个空对象;</td></tr><tr><td>$route.hash</td><td>string</td><td>当前路由的 <code>hash</code> 值 (带 <code>#</code>) ,如果没有 <code>hash</code> 值,则为空字符串;</td></tr><tr><td>$route.fullPath</td><td>string</td><td>完成解析后的 <code>URL</code> ,包含查询参数和 <code>hash</code> 的完整路径;</td></tr><tr><td>$route.matched</td><td>Array<code><RouteRecord></code></td><td>一个数组,包含当前路由的所有嵌套路径片段的路由记录;路由记录就是 <code>routes</code> 配置数组中的对象副本 (还有在 <code>children</code> 数组);</td></tr><tr><td>$route.name</td><td>–</td><td>当前路由的名称,如果有的话;查看<a href="https://router.vuejs.org/zh/guide/essentials/named-routes.html" target="_blank" rel="noopener">命名路由</a></td></tr><tr><td>$route.redirectedFrom</td><td>–</td><td>如果存在重定向,即为重定向来源的路由的名字;查看<a href="https://router.vuejs.org/zh/guide/essentials/redirect-and-alias.html#%E9%87%8D%E5%AE%9A%E5%90%91" target="_blank" rel="noopener">重定向和别名</a></td></tr></tbody></table><h2 id="6-组件注入"><a href="#6-组件注入" class="headerlink" title="6. 组件注入"></a>6. 组件注入</h2><h3 id="注入的属性"><a href="#注入的属性" class="headerlink" title="注入的属性"></a>注入的属性</h3><p>通过在 <code>Vue</code> 根实例的 <code>router</code> 配置传入 <code>router</code> 实例,下面这些属性成员会被注入到每个子组件:</p><ul><li><code>this.$router</code>:<code>router</code> 实例;</li><li><code>this.$route</code>:当前激活的 <code>路由信息对象</code>;这个属性是只读的,里面的属性是 <code>immutable</code> (不可变) 的,不过你可以 <code>watch</code> (监测变化) 它;</li></ul><h3 id="增加的组件配置选项"><a href="#增加的组件配置选项" class="headerlink" title="增加的组件配置选项"></a>增加的组件配置选项</h3><ul><li>beforeRouteEnter</li><li>beforeRouteUpdate</li><li>beforeRouteLeave</li></ul><p>查看<a href="https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E7%BB%84%E4%BB%B6%E5%86%85%E7%9A%84%E5%AE%88%E5%8D%AB" target="_blank" rel="noopener">组件内的守卫</a></p>]]></content>
<summary type="html">
<p>在 Vue 的学习中,VueRouter 是 Vue.js 官方的路由管理器,该文档是对 VueRouter 官方API文档的知识的学习笔记,包含 VueRouter 具体的API接口的使用方法,具体查看文档;</p>
</summary>
<category term="Vue" scheme="https://geek-lhj.github.io/categories/Vue/"/>
<category term="VueRouter" scheme="https://geek-lhj.github.io/categories/Vue/VueRouter/"/>
<category term="官方API文档" scheme="https://geek-lhj.github.io/categories/Vue/VueRouter/%E5%AE%98%E6%96%B9API%E6%96%87%E6%A1%A3/"/>
<category term="VueRouter" scheme="https://geek-lhj.github.io/tags/VueRouter/"/>
<category term="Vue" scheme="https://geek-lhj.github.io/tags/Vue/"/>
<category term="路由API" scheme="https://geek-lhj.github.io/tags/%E8%B7%AF%E7%94%B1API/"/>
</entry>
<entry>
<title>VueRouter官方文档笔记</title>
<link href="https://geek-lhj.github.io/2019/03/05/VueRouter%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E7%AC%94%E8%AE%B0/"/>
<id>https://geek-lhj.github.io/2019/03/05/VueRouter%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E7%AC%94%E8%AE%B0/</id>
<published>2019-03-05T08:38:58.000Z</published>
<updated>2020-01-17T14:45:56.180Z</updated>
<content type="html"><![CDATA[<p>在 Vue 的学习中,VueRouter 是 Vue.js 官方的路由管理器,该文档是对 VueRouter 官方文档的知识的学习笔记,包括了 VueRouter 的基本使用以及进阶部分的知识,具体查看文档;</p><a id="more"></a><h1 id="VueRouter官方文档笔记"><a href="#VueRouter官方文档笔记" class="headerlink" title="VueRouter官方文档笔记"></a>VueRouter官方文档笔记</h1><p><a href="https://router.vuejs.org/zh/" target="_blank" rel="noopener">VueRouter 官方文档</a></p><h2 id="1-起步"><a href="#1-起步" class="headerlink" title="1. 起步"></a>1. 起步</h2><h3 id="1-1-html-框架"><a href="#1-1-html-框架" class="headerlink" title="1.1 html 框架"></a>1.1 html 框架</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://unpkg.com/vue/dist/vue.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://unpkg.com/vue-router/dist/vue-router.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>Hello App!<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span></span><br><span class="line"> <span class="comment"><!-- 使用 router-link 组件来导航. --></span></span><br><span class="line"> <span class="comment"><!-- 通过传入 `to` 属性指定链接. --></span></span><br><span class="line"> <span class="comment"><!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --></span></span><br><span class="line"> <span class="tag"><<span class="name">router-link</span> <span class="attr">to</span>=<span class="string">"/foo"</span>></span>Go to Foo<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">router-link</span> <span class="attr">to</span>=<span class="string">"/bar"</span>></span>Go to Bar<span class="tag"></<span class="name">router-link</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="comment"><!-- 路由出口 --></span></span><br><span class="line"> <span class="comment"><!-- 路由匹配到的组件将渲染在这里 --></span></span><br><span class="line"> <span class="tag"><<span class="name">router-view</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h3 id="1-2-JavaScript-代码"><a href="#1-2-JavaScript-代码" class="headerlink" title="1.2 JavaScript 代码"></a>1.2 JavaScript 代码</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)</span></span><br><span class="line"><span class="comment">// 1. 定义 (路由) 组件。</span></span><br><span class="line"><span class="comment">// 可以从其他文件 import 进来</span></span><br><span class="line"><span class="keyword">const</span> Foo = { <span class="attr">template</span>: <span class="string">'<div>foo</div>'</span> }</span><br><span class="line"><span class="keyword">const</span> Bar = { <span class="attr">template</span>: <span class="string">'<div>bar</div>'</span> }</span><br><span class="line"><span class="comment">// 2. 定义路由</span></span><br><span class="line"><span class="comment">// 每个路由应该映射一个组件。 其中"component" 可以是</span></span><br><span class="line"><span class="comment">// 通过 Vue.extend() 创建的组件构造器,</span></span><br><span class="line"><span class="comment">// 或者,只是一个组件配置对象。</span></span><br><span class="line"><span class="comment">// 我们晚点再讨论嵌套路由。</span></span><br><span class="line"><span class="keyword">const</span> routes = [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/foo'</span>, <span class="attr">component</span>: Foo },</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/bar'</span>, <span class="attr">component</span>: Bar }</span><br><span class="line">]</span><br><span class="line"><span class="comment">// 3. 创建 router 实例,然后传 `routes` 配置</span></span><br><span class="line"><span class="comment">// 你还可以传别的配置参数, 不过先这么简单着吧。</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes <span class="comment">// (缩写) 相当于 routes: routes</span></span><br><span class="line">})</span><br><span class="line"><span class="comment">// 4. 创建和挂载根实例。</span></span><br><span class="line"><span class="comment">// 记得要通过 router 配置参数注入路由,</span></span><br><span class="line"><span class="comment">// 从而让整个应用都有路由功能</span></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Vue({</span><br><span class="line"> router</span><br><span class="line">}).$mount(<span class="string">'#app'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在,应用已经启动了!</span></span><br></pre></td></tr></table></figure><p>通过注入路由器<code>router</code>对象到<code>Vue</code>对象中,我们可以在Vue的任何组件内通过 <code>this.$router</code> 访问路由器,也可以通过 <code>this.$route</code> 访问当前路由:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Home.vue</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> computed: {</span><br><span class="line"> username () {</span><br><span class="line"> <span class="comment">// 我们很快就会看到 `params` 是什么</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.$route.params.username</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> methods: {</span><br><span class="line"> goBack () {</span><br><span class="line"> <span class="built_in">window</span>.history.length > <span class="number">1</span> ? <span class="keyword">this</span>.$router.go(<span class="number">-1</span>) : <span class="keyword">this</span>.$router.push(<span class="string">'/'</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="2-动态路由匹配(params-参数)"><a href="#2-动态路由匹配(params-参数)" class="headerlink" title="2. 动态路由匹配(params 参数)"></a>2. 动态路由匹配(params 参数)</h2><p>有时需要把某种模式匹配到的所有路由,全都映射到同个组件,可以在 <code>vue-router</code> 的路由路径中使用“动态路径参数”(<code>dynamic segment</code>) 来达到这个效果;例如,有一个 <code>User</code> 组件,对所有 <code>ID</code> 各不相同的用户,都要使用这个组件来渲染:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注册路由</span></span><br><span class="line">routes: [</span><br><span class="line"> <span class="comment">// 动态路径参数 以冒号开头</span></span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/user/:id'</span>, <span class="attr">component</span>: User }</span><br><span class="line"> ]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用$route.params 获取url携带的信息;除此,$route 对象还提供了其它有用的信息,例如,$route.query (如果 URL 中有查询参数)、$route.hash 等等</span></span><br><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> template: <span class="string">'<div>User {{ $route.params.id }}</div>'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以在一个路由中设置多段“路径参数”,对应的值都会设置到 <code>$route.params</code> 中,例如:</p><table><thead><tr><th>模式</th><th>匹配路径</th><th>$route.params</th></tr></thead><tbody><tr><td>/user/:username</td><td>/user/evan</td><td>{ username: ‘evan’ }</td></tr><tr><td>/user/:username/post/:post_id</td><td>/user/evan/post/123</td><td>{ username: ‘evan’, post_id: 123 }</td></tr></tbody></table><h3 id="2-1-响应路由参数的变化-路由参数改变,组件的复用"><a href="#2-1-响应路由参数的变化-路由参数改变,组件的复用" class="headerlink" title="2.1 响应路由参数的变化(路由参数改变,组件的复用)"></a>2.1 响应路由参数的变化(路由参数改变,组件的复用)</h3><p>若使用路由参数时,例如从 <code>/user/foo</code> 导航到 <code>/user/bar</code>,原来的组件实例会被复用;因为两个路由都渲染同个组件,复用显得更加高效;这也意味着组件的生命周期钩子不会再被调用,复用组件时,想对路由参数的变化作出响应的话,你可以简单地 <code>watch</code> (监测变化) <code>$route</code> 对象:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// watch (监测变化) $route 对象</span></span><br><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> template: <span class="string">'...'</span>,</span><br><span class="line"> watch: {</span><br><span class="line"> <span class="string">'$route'</span> (to, <span class="keyword">from</span>) {</span><br><span class="line"> <span class="comment">// 对路由变化作出响应...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者使用 Router进阶 中引入的 beforeRouteUpdate 导航守卫:</span></span><br><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> template: <span class="string">'...'</span>,</span><br><span class="line"> beforeRouteUpdate (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> <span class="comment">// react to route changes...</span></span><br><span class="line"> <span class="comment">// don't forget to call next()</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-2-高级匹配模式"><a href="#2-2-高级匹配模式" class="headerlink" title="2.2 高级匹配模式"></a>2.2 高级匹配模式</h3><p><code>vue-router</code> 使用 <a href="https://github.com/pillarjs/path-to-regexp" target="_blank" rel="noopener">path-to-regexp</a> 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配;</p><h3 id="2-3-匹配优先级"><a href="#2-3-匹配优先级" class="headerlink" title="2.3 匹配优先级"></a>2.3 匹配优先级</h3><p>同一个路径可以匹配多个路由,此时匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。</p><h2 id="3-嵌套路由"><a href="#3-嵌套路由" class="headerlink" title="3. 嵌套路由"></a>3. 嵌套路由</h2><p>下面是一个嵌套路由的实例,从设计的结构到实现的代码:</p><figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">/user/foo/profile /user/foo/posts</span><br><span class="line">+------------------+ +-----------------+</span><br><span class="line">| User | | User |</span><br><span class="line">| +--------------+ | | +-------------+ |</span><br><span class="line">| | Profile | | +------------> | | Posts | |</span><br><span class="line">| | | | | | | |</span><br><span class="line">| +--------------+ | | +-------------+ |</span><br><span class="line">+------------------+ +-----------------+</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 根节点app,这里的 <router-view> 是最顶层的出口,渲染最高级路由匹配到的组件</span></span><br><span class="line"><div id=<span class="string">"app"</span>></span><br><span class="line"> <router-view><span class="xml"><span class="tag"></<span class="name">router-view</span>></span></span></span><br><span class="line"><<span class="regexp">/div></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义动态路由参数,并在组件中使用</span></span><br><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> template: <span class="string">'<div>User {{ $route.params.id }}</div>'</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/user/:id'</span>, <span class="attr">component</span>: User }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 User 组件的模板添加一个 <router-view>【一个被渲染组件同样可以包含自己的嵌套<router-view>】</span></span><br><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <div class="user"></span></span><br><span class="line"><span class="string"> <h2>User {{ $route.params.id }}</h2></span></span><br><span class="line"><span class="string"> <router-view></router-view></span></span><br><span class="line"><span class="string"> </div></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置:</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/user/:id'</span>, <span class="attr">component</span>: User,</span><br><span class="line"> children: [</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 当 /user/:id/profile 匹配成功,</span></span><br><span class="line"> <span class="comment">// UserProfile 会被渲染在 User 的 <router-view> 中</span></span><br><span class="line"> path: <span class="string">'profile'</span>,</span><br><span class="line"> component: UserProfile</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 当 /user/:id/posts 匹配成功</span></span><br><span class="line"> <span class="comment">// UserPosts 会被渲染在 User 的 <router-view> 中</span></span><br><span class="line"> path: <span class="string">'posts'</span>,</span><br><span class="line"> component: UserPosts</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>注意:以 <code>/</code> 开头的嵌套路径会被当作根路径; 这让你充分的使用嵌套组件而无须设置嵌套的路径。</p><h2 id="4-编程式的导航"><a href="#4-编程式的导航" class="headerlink" title="4. 编程式的导航"></a>4. 编程式的导航</h2><h3 id="4-1-router-push"><a href="#4-1-router-push" class="headerlink" title="4.1 router.push()"></a>4.1 router.push()</h3><p>声明式导航 | 编程式导航<br>— | — | —<br><code><router-link :to="..."></code> | <code>router.push(...)</code></p><p>当点击 <code><router-link></code> 时,这个方法会在内部调用<code>router.push(...)</code>,因而两个是等同关系的;</p><p><strong>编程式导航语法</strong>: <code>router.push(location, onComplete?, onAbort?)</code></p><p>在 <code>router.push</code> 或 <code>router.replace</code> 中提供 <code>onComplete</code> 和 <code>onAbort</code> 回调作为第二个和第三个可选参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用;</p><p><strong>注意:在 Vue 实例内部,你可以通过 <code>$router</code> 访问<code>路由实例</code>;因此可以使用 <code>this.$router.push</code>方法;</strong></p><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 字符串</span></span><br><span class="line">router.push(<span class="string">'home'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对象</span></span><br><span class="line">router.push({ <span class="attr">path</span>: <span class="string">'home'</span> })</span><br><span class="line"></span><br><span class="line"><span class="comment">// 命名的路由</span></span><br><span class="line">router.push({ <span class="attr">name</span>: <span class="string">'user'</span>, <span class="attr">params</span>: { <span class="attr">userId</span>: <span class="number">123</span> }})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 带查询参数,变成 /register?plan=private</span></span><br><span class="line">router.push({ <span class="attr">path</span>: <span class="string">'register'</span>, <span class="attr">query</span>: { <span class="attr">plan</span>: <span class="string">'private'</span> }})</span><br></pre></td></tr></table></figure><p><strong>注意:如果提供了 <code>path,params</code> 会被忽略,上述例子中的 <code>query</code> 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 <code>name</code> 或手写完整的带有参数的 <code>path</code> ,同样的规则也适用于 <code>router-link</code> 组件的 <code>to</code> 属性;</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> userId = <span class="number">123</span></span><br><span class="line">router.push({ <span class="attr">name</span>: <span class="string">'user'</span>, <span class="attr">params</span>: { userId }}) <span class="comment">// -> /user/123</span></span><br><span class="line">router.push({ <span class="attr">path</span>: <span class="string">`/user/<span class="subst">${userId}</span>`</span> }) <span class="comment">// -> /user/123</span></span><br><span class="line"><span class="comment">// 这里的 params 不生效</span></span><br><span class="line">router.push({ <span class="attr">path</span>: <span class="string">'/user'</span>, <span class="attr">params</span>: { userId }}) <span class="comment">// -> /user</span></span><br></pre></td></tr></table></figure><h3 id="4-2-router-replace"><a href="#4-2-router-replace" class="headerlink" title="4.2 router.replace()"></a>4.2 router.replace()</h3><p>声明式导航 | 编程式导航<br>— | — | —<br><code><router-link :to="..." replace></code> | <code>router.replace(location, onComplete?, onAbort?)</code></p><p>该方法跟 <code>router.push</code> 很像,唯一的不同就是,它不会向 <code>history</code> 添加新记录,而是跟它的方法名一样是替换掉当前的 <code>history</code> 记录。</p><h3 id="4-3-router-go"><a href="#4-3-router-go" class="headerlink" title="4.3 router.go()"></a>4.3 router.go()</h3><p>所有语法:<code>router.go(n)</code>; 这个方法的参数是一个整数,意思是在 <code>history</code> 记录中向前或者后退多少步,类似 <code>window.history.go(n)</code>;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在浏览器记录中前进一步,等同于 history.forward()</span></span><br><span class="line">router.go(<span class="number">1</span>)</span><br><span class="line"><span class="comment">// 后退一步记录,等同于 history.back()</span></span><br><span class="line">router.go(<span class="number">-1</span>)</span><br><span class="line"><span class="comment">// 前进 3 步记录</span></span><br><span class="line">router.go(<span class="number">3</span>)</span><br><span class="line"><span class="comment">// 如果 history 记录不够用,那就默默地失败呗</span></span><br><span class="line">router.go(<span class="number">-100</span>)</span><br><span class="line">router.go(<span class="number">100</span>)</span><br></pre></td></tr></table></figure><h3 id="4-4-操作-History"><a href="#4-4-操作-History" class="headerlink" title="4.4 操作 History"></a>4.4 操作 History</h3><p><code>router.push</code>、 <code>router.replace</code> 和 <code>router.go</code> 跟 <code>window.history.pushState</code>、 <code>window.history.replaceState</code> 和 <code>window.history.go</code>好像, 实际上它们确实是效仿 <code>window.history API</code> 的,参考浏览器历史纪录API接口 <a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API" target="_blank" rel="noopener">Browser History APIs</a>;</p><p>同时,<code>Vue Router</code> 的导航方法 (<code>push、 replace、 go</code>) 在各类路由模式 (<code>history、 hash 和 abstract</code>) 下表现一致;</p><h2 id="5-命名路由"><a href="#5-命名路由" class="headerlink" title="5. 命名路由"></a>5. 命名路由</h2><p>在创建 <code>Router</code> 实例的时候,在 <code>routes</code> 配置中给某个路由设置名称(<code>name</code>属性);</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'/user/:userId'</span>,</span><br><span class="line"> name: <span class="string">'user'</span>,</span><br><span class="line"> component: User</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>要链接到一个命名路由,可以给 <code><router-link /></code> 的 <code>to</code> 属性传一个对象,如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-link</span> <span class="attr">:to</span>=<span class="string">"{ name: 'user', params: { userId: 123 }}"</span>></span>User<span class="tag"></<span class="name">router-link</span>></span></span><br></pre></td></tr></table></figure><p>调用 <code>router.push()</code> 也是一样的道理:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">router.push({ <span class="attr">name</span>: <span class="string">'user'</span>, <span class="attr">params</span>: { <span class="attr">userId</span>: <span class="number">123</span> }})</span><br></pre></td></tr></table></figure><h2 id="6-命名视图"><a href="#6-命名视图" class="headerlink" title="6. 命名视图"></a>6. 命名视图</h2><p>若想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 <code>sidebar</code> (侧导航) 和 <code>main</code> (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 <code>router-view</code> 没有设置名字,那么默认为 <code>default</code>;</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">router-view</span> <span class="attr">class</span>=<span class="string">"view one"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"><<span class="name">router-view</span> <span class="attr">class</span>=<span class="string">"view two"</span> <span class="attr">name</span>=<span class="string">"a"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"><<span class="name">router-view</span> <span class="attr">class</span>=<span class="string">"view three"</span> <span class="attr">name</span>=<span class="string">"b"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br></pre></td></tr></table></figure><p>其中一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 <code>components</code> 配置 (带上 s):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'/'</span>,</span><br><span class="line"> components: {</span><br><span class="line"> <span class="keyword">default</span>: Foo,</span><br><span class="line"> a: Bar,</span><br><span class="line"> b: Baz</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="6-1-嵌套命名视图"><a href="#6-1-嵌套命名视图" class="headerlink" title="6.1 嵌套命名视图"></a>6.1 嵌套命名视图</h3><p>对于命名视图创建嵌套视图的复杂布局,也需要命名用到的嵌套 <code>router-view</code> 组件,如下面板为例:</p><figure class="highlight m"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">/settings/emails /settings/profile</span><br><span class="line">+-----------------------------------+ +------------------------------+</span><br><span class="line">| UserSettings | | UserSettings |</span><br><span class="line">| +-----+-------------------------+ | | +-----+--------------------+ |</span><br><span class="line">| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |</span><br><span class="line">| | +-------------------------+ | | | +--------------------+ |</span><br><span class="line">| | | | | | | | UserProfilePreview | |</span><br><span class="line">| +-----+-------------------------+ | | +-----+--------------------+ |</span><br><span class="line">+-----------------------------------+ +------------------------------+</span><br></pre></td></tr></table></figure><ul><li><code>Nav</code> 只是一个常规组件;</li><li><code>UserSettings</code> 是一个视图组件;</li><li><code>UserEmailsSubscriptions、UserProfile、UserProfilePreview</code> 是嵌套的视图组件;</li></ul><p><code>UserSettings</code> 组件的 <code><template></code> 部分代码:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- UserSettings.vue --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>User Settings<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">NavBar</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">router-view</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">router-view</span> <span class="attr">name</span>=<span class="string">"helper"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>路由配置规则代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> path: <span class="string">'/settings'</span>,</span><br><span class="line"> <span class="comment">// 你也可以在顶级路由就配置命名视图</span></span><br><span class="line"> component: UserSettings,</span><br><span class="line"> children: [{</span><br><span class="line"> path: <span class="string">'emails'</span>,</span><br><span class="line"> component: UserEmailsSubscriptions</span><br><span class="line"> }, {</span><br><span class="line"> path: <span class="string">'profile'</span>,</span><br><span class="line"> components: {</span><br><span class="line"> <span class="keyword">default</span>: UserProfile,</span><br><span class="line"> helper: UserProfilePreview</span><br><span class="line"> }</span><br><span class="line"> }]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-重定向和别名"><a href="#7-重定向和别名" class="headerlink" title="7 重定向和别名"></a>7 重定向和别名</h2><h3 id="7-1-重定向"><a href="#7-1-重定向" class="headerlink" title="7.1 重定向"></a>7.1 重定向</h3><p>重定向也是通过 <code>routes</code> 配置来完成,设置<code>redirect</code>属性的值;下面例子是从 <code>/a</code> 重定向到 <code>/b</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/a'</span>, <span class="attr">redirect</span>: <span class="string">'/b'</span> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 重定向的目标也可以是一个命名的路由:</span></span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/b'</span>, <span class="attr">redirect</span>: { <span class="attr">name</span>: <span class="string">'foo'</span> }}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 也可以是一个方法,动态返回重定向目标:</span></span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/c'</span>, <span class="attr">redirect</span>: <span class="function"><span class="params">to</span> =></span> {</span><br><span class="line"> <span class="comment">// 方法接收 目标路由 作为参数</span></span><br><span class="line"> <span class="comment">// return 重定向的 字符串路径/路径对象</span></span><br><span class="line"> }}</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p><strong>注意:<a href="https://router.vuejs.org/zh/guide/advanced/navigation-guards.html" target="_blank" rel="noopener">导航守卫</a>并没有应用在跳转路由上,而仅仅应用在其目标上;在下面这个例子中,为 /a 路由添加一个 <code>beforeEach</code> 或 <code>beforeLeave</code> 守卫并不会有任何效果;</strong></p><h3 id="7-2-别名"><a href="#7-2-别名" class="headerlink" title="7.2 别名"></a>7.2 别名</h3><p>别名的含义:例如 <code>/a</code> 的别名是 <code>/b</code>,意味着,当用户访问 <code>/b</code> 时, <code>URL</code> 会保持为 <code>/b</code>,但是路由匹配则为 <code>/a</code>,就像用户访问 <code>/a</code> 一样;“别名”的功能让你可以自由地将 <code>UI</code> 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/a'</span>, <span class="attr">component</span>: A, <span class="attr">alias</span>: <span class="string">'/b'</span> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="8-路由组件传参"><a href="#8-路由组件传参" class="headerlink" title="8. 路由组件传参"></a>8. 路由组件传参</h2><p>在组件中使用 <code>$route</code> 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性;因而使用 <code>props</code> 将组件和路由解耦,<strong>取代与 <code>$route</code> 的耦合</strong>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> template: <span class="string">'<div>User {{ $route.params.id }}</div>'</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/user/:id'</span>, <span class="attr">component</span>: User }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p><strong>通过 props 解耦:</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> User = {</span><br><span class="line"> props: [<span class="string">'id'</span>],</span><br><span class="line"> template: <span class="string">'<div>User {{ id }}</div>'</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/user/:id'</span>, <span class="attr">component</span>: User, <span class="attr">props</span>: <span class="literal">true</span> },</span><br><span class="line"> <span class="comment">// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:</span></span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'/user/:id'</span>,</span><br><span class="line"> components: { <span class="attr">default</span>: User, <span class="attr">sidebar</span>: Sidebar },</span><br><span class="line"> props: { <span class="attr">default</span>: <span class="literal">true</span>, <span class="attr">sidebar</span>: <span class="literal">false</span> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="8-1-布尔模式"><a href="#8-1-布尔模式" class="headerlink" title="8.1 布尔模式"></a>8.1 布尔模式</h3><p>如果 <code>props</code> 被设置为 <code>true</code> ,<code>route.params</code> 将会被设置为组件属性;</p><h3 id="8-2-对象模式"><a href="#8-2-对象模式" class="headerlink" title="8.2 对象模式"></a>8.2 对象模式</h3><p>如果 <code>props</code> 是一个对象,它会被按原样设置为组件属性;当 <code>props</code> 是静态的时候有用;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/promotion/from-newsletter'</span>, <span class="attr">component</span>: Promotion, <span class="attr">props</span>: { <span class="attr">newsletterPopup</span>: <span class="literal">false</span> } }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="8-3-函数模式"><a href="#8-3-函数模式" class="headerlink" title="8.3 函数模式"></a>8.3 函数模式</h3><p>可以创建一个函数返回 <code>props</code> ,这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等;</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'/search'</span>, <span class="attr">component</span>: SearchUser, <span class="attr">props</span>: <span class="function">(<span class="params">route</span>) =></span> ({ <span class="attr">query</span>: route.query.q }) }</span><br><span class="line"> ]</span><br><span class="line">})</span><br><span class="line"><span class="comment">// URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件;</span></span><br></pre></td></tr></table></figure><h2 id="9-HTML5-History-模式"><a href="#9-HTML5-History-模式" class="headerlink" title="9. HTML5 History 模式"></a>9. HTML5 History 模式</h2><p><code>vue-router</code> 默认 <code>hash</code> 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载;若不想要很丑的 <code>hash</code> ,我们可以用路由的 <code>history</code> 模式,这种模式充分利用 <code>history.pushState API</code> 来完成 URL 跳转而无须重新加载页面:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> mode: <span class="string">'history'</span>,</span><br><span class="line"> routes: [...]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>当使用 <code>history</code> 模式时, <code>URL</code> 就像正常的 url,例如 <code>http://yoursite.com/user/id</code>;不过这种模式需要后台配置支持;要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 <code>index.html</code> 页面,这个页面就是你 app 依赖的页面;</p><h3 id="9-1-后端配置例子"><a href="#9-1-后端配置例子" class="headerlink" title="9.1 后端配置例子"></a>9.1 后端配置例子</h3><p><strong>原生 Node.js</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>)</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"><span class="keyword">const</span> httpPort = <span class="number">80</span></span><br><span class="line">http.createServer(<span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> fs.readFile(<span class="string">'index.htm'</span>, <span class="string">'utf-8'</span>, (err, content) => {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'We cannot open "index.htm" file.'</span>)</span><br><span class="line"> }</span><br><span class="line"> res.writeHead(<span class="number">200</span>, {</span><br><span class="line"> <span class="string">'Content-Type'</span>: <span class="string">'text/html; charset=utf-8'</span></span><br><span class="line"> })</span><br><span class="line"> res.end(content)</span><br><span class="line"> })</span><br><span class="line">}).listen(httpPort, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Server listening on: http://localhost:%s'</span>, httpPort)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="9-2-警告"><a href="#9-2-警告" class="headerlink" title="9.2 警告"></a>9.2 警告</h3><p>这么做以后,你的服务器就不再返回 <code>404</code> 错误页面,因为对于所有路径都会返回 <code>index.html</code> 文件;为避免这种情况,应该在 <code>Vue</code> 应用里面覆盖所有的路由情况,然后在给出一个 404 页面:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> mode: <span class="string">'history'</span>,</span><br><span class="line"> routes: [</span><br><span class="line"> { <span class="attr">path</span>: <span class="string">'*'</span>, <span class="attr">component</span>: NotFoundComponent }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="10-导航守卫"><a href="#10-导航守卫" class="headerlink" title="10. 导航守卫"></a>10. 导航守卫</h2><p><code>vue-router</code> 提供的导航守卫主要用来通过<code>跳转或取消</code>的方式守卫导航,<code>参数或查询的改变</code>并不会触发<code>进入/离开的导航守卫</code>;可以通过观察 <code>$route</code> 对象来应对这些变化,或使用 <code>beforeRouteUpdate</code> 的组件内守卫;</p><h3 id="10-1-全局守卫"><a href="#10-1-全局守卫" class="headerlink" title="10.1 全局守卫"></a>10.1 全局守卫</h3><p>使用 <code>router.beforeEach</code> 注册一个全局前置守卫:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({ ... })</span><br><span class="line">router.beforeEach(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><ul><li><code>to: Route</code>: 即将要进入的目标 <code>路由对象</code> ;</li><li><code>from: Route</code>: 当前导航正要离开的路由;</li><li><code>next: Function</code>: 一定要调用该方法来 <code>resolve</code> 这个钩子;执行效果依赖 <code>next</code> 方法的调用参数;<ul><li><code>next()</code>: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 <code>confirmed</code> (确认的);</li><li><code>next(false)</code>: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。</li><li><code>next('/')</code> 或者 next<code>({ path: '/' })</code>: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 <code>next</code> 传递任意位置对象,且允许设置诸如 <code>replace: true、name: 'home'</code> 之类的选项以及任何用在 <code>router-link</code> 的 <code>to prop</code> 或 <code>router.push</code> 中的选项。</li><li><code>next(error)</code>: (2.4.0+) 如果传入 <code>next</code> 的参数是一个 <code>Error</code> 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。</li></ul></li></ul><p><strong>确保要调用 <code>next</code> 方法,否则钩子就不会被 <code>resolved</code> ;</strong></p><h3 id="10-2-全局解析守卫-2-5-0-新增"><a href="#10-2-全局解析守卫-2-5-0-新增" class="headerlink" title="10.2 全局解析守卫(2.5.0 新增)"></a>10.2 全局解析守卫(2.5.0 新增)</h3><p>用 <code>router.beforeResolve</code> 注册一个全局守卫;这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。</p><h3 id="10-3-全局后置钩子"><a href="#10-3-全局后置钩子" class="headerlink" title="10.3 全局后置钩子"></a>10.3 全局后置钩子</h3><p>用 <code>router.afterEach</code> 注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 <code>next</code> 函数也不会改变导航本身:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.afterEach(<span class="function">(<span class="params">to, <span class="keyword">from</span></span>) =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="10-4-路由独享的守卫"><a href="#10-4-路由独享的守卫" class="headerlink" title="10.4 路由独享的守卫"></a>10.4 路由独享的守卫</h3><p>可以在路由配置上直接定义 <code>beforeEnter</code> 守卫,参数与全局前置守卫的方法参数是一样的:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'/foo'</span>,</span><br><span class="line"> component: Foo,</span><br><span class="line"> beforeEnter: <span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="10-5-组件内的守卫"><a href="#10-5-组件内的守卫" class="headerlink" title="10.5 组件内的守卫"></a>10.5 组件内的守卫</h3><p>可以在路由组件内直接定义以下路由导航守卫:</p><ul><li>beforeRouteEnter</li><li>beforeRouteUpdate (2.2 新增)</li><li>beforeRouteLeave</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Foo = {</span><br><span class="line"> template: <span class="string">`...`</span>,</span><br><span class="line"> beforeRouteEnter (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> <span class="comment">// 在渲染该组件的对应路由被 confirm 前调用</span></span><br><span class="line"> <span class="comment">// 不!能!获取组件实例 `this`</span></span><br><span class="line"> <span class="comment">// 因为当守卫执行前,组件实例还没被创建</span></span><br><span class="line"> },</span><br><span class="line"> beforeRouteUpdate (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> <span class="comment">// 在当前路由改变,但是该组件被复用时调用</span></span><br><span class="line"> <span class="comment">// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,</span></span><br><span class="line"> <span class="comment">// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。</span></span><br><span class="line"> <span class="comment">// 可以访问组件实例 `this`</span></span><br><span class="line"> },</span><br><span class="line"> beforeRouteLeave (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> <span class="comment">// 导航离开该组件的对应路由时调用</span></span><br><span class="line"> <span class="comment">// 可以访问组件实例 `this`</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>beforeRouteEnter</strong>:</p><p><code>beforeRouteEnter</code> 守卫 <strong>不能</strong> 访问 <code>this</code> ,是因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建;不过可以通过传一个回调给 <code>next</code> 来访问组件实例;在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">beforeRouteEnter (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> next(<span class="function"><span class="params">vm</span> =></span> {</span><br><span class="line"> <span class="comment">// 通过 `vm` 访问组件实例</span></span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>beforeRouteUpdate</strong>:</p><p>注意 <code>beforeRouteEnter</code> 是支持给 <code>next</code> 传递回调的唯一守卫;对于 <code>beforeRouteUpdate</code> 和 <code>beforeRouteLeave</code> 来说, <code>this</code> 已经可用了,所以不支持传递回调:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">beforeRouteUpdate (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> <span class="comment">// just use `this`</span></span><br><span class="line"> <span class="keyword">this</span>.name = to.params.name</span><br><span class="line"> next()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>beforeRouteLeave</strong>:<br>这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 <code>next(false)</code> 来取消。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">beforeRouteLeave (to, <span class="keyword">from</span> , next) {</span><br><span class="line"> <span class="keyword">const</span> answer = <span class="built_in">window</span>.confirm(<span class="string">'Do you really want to leave? you have unsaved changes!'</span>)</span><br><span class="line"> <span class="keyword">if</span> (answer) {</span><br><span class="line"> next()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> next(<span class="literal">false</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="10-6-完整的导航解析流程"><a href="#10-6-完整的导航解析流程" class="headerlink" title="10.6 完整的导航解析流程"></a>10.6 完整的导航解析流程</h3><ol><li>导航被触发;</li><li>在失活的组件里调用离开守卫;</li><li>调用全局的 <code>beforeEach</code> 守卫;</li><li>在重用的组件里调用 <code>beforeRouteUpdate</code> 守卫 (2.2+);</li><li>在路由配置里调用 <code>beforeEnter</code> ;</li><li>解析异步路由组件;</li><li>在被激活的组件里调用 <code>beforeRouteEnter</code> ;</li><li>调用全局的 <code>beforeResolve</code> 守卫 (2.5+);</li><li>导航被确认;</li><li>调用全局的 <code>afterEach</code> 钩子;</li><li>触发 DOM 更新;</li><li>用创建好的实例调用 <code>beforeRouteEnter</code> 守卫中传给 <code>next</code> 的回调函数;</li></ol><h2 id="11-路由元信息"><a href="#11-路由元信息" class="headerlink" title="11. 路由元信息"></a>11. 路由元信息</h2><p>定义路由的时候可以配置 <code>meta</code> 字段:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'/foo'</span>,</span><br><span class="line"> component: Foo,</span><br><span class="line"> children: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">'bar'</span>,</span><br><span class="line"> component: Bar,</span><br><span class="line"> <span class="comment">// a meta field</span></span><br><span class="line"> meta: { <span class="attr">requiresAuth</span>: <span class="literal">true</span> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>在<code>routes</code> 配置中的每个路由对象为 <code>路由记录</code>;路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录;一个路由匹配到的所有路由记录会暴露为 <code>$route</code> 对象 (还有在导航守卫中的路由对象) 的 <code>$route.matched</code> 数组;因此,我们需要遍历 <code>$route.matched</code> 来检查路由记录中的 <code>meta</code> 字段,如下面例子展示在全局导航守卫中检查元字段:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">router.beforeEach(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (to.matched.some(<span class="function"><span class="params">record</span> =></span> record.meta.requiresAuth)) {</span><br><span class="line"> <span class="comment">// this route requires auth, check if logged in</span></span><br><span class="line"> <span class="comment">// if not, redirect to login page.</span></span><br><span class="line"> <span class="keyword">if</span> (!auth.loggedIn()) {</span><br><span class="line"> next({</span><br><span class="line"> path: <span class="string">'/login'</span>,</span><br><span class="line"> query: { <span class="attr">redirect</span>: to.fullPath }</span><br><span class="line"> })</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> next()</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> next() <span class="comment">// 确保一定要调用 next()</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="12-过渡动效"><a href="#12-过渡动效" class="headerlink" title="12. 过渡动效"></a>12. 过渡动效</h2><p>在 <code><router-view></code> 动态组件中使用 <code><transition></code> 组件给它添加一些过渡效果:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><transition></span><br><span class="line"> <router-view><span class="xml"><span class="tag"></<span class="name">router-view</span>></span></span></span><br><span class="line"><<span class="regexp">/transition></span></span><br></pre></td></tr></table></figure><p><a href="https://cn.vuejs.org/v2/guide/transitions.html" target="_blank" rel="noopener">Transition 的所有功能</a> 都能使用;</p><h3 id="12-1-单个路由的过渡"><a href="#12-1-单个路由的过渡" class="headerlink" title="12.1 单个路由的过渡"></a>12.1 单个路由的过渡</h3><p>在 <code><router-view></code> 动态组件中使用 <code><transition></code> 组件会给所有路由设置一样的过渡效果;若想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 <code><transition></code> 并设置不同的 <code>name</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Foo = {</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <transition name="slide"></span></span><br><span class="line"><span class="string"> <div class="foo">...</div></span></span><br><span class="line"><span class="string"> </transition></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Bar = {</span><br><span class="line"> template: <span class="string">`</span></span><br><span class="line"><span class="string"> <transition name="fade"></span></span><br><span class="line"><span class="string"> <div class="bar">...</div></span></span><br><span class="line"><span class="string"> </transition></span></span><br><span class="line"><span class="string"> `</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="12-2-基于路由的动态过渡"><a href="#12-2-基于路由的动态过渡" class="headerlink" title="12.2 基于路由的动态过渡"></a>12.2 基于路由的动态过渡</h3><p>可以基于当前路由与目标路由的变化关系,动态设置过渡效果:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 使用动态的 transition name --></span></span><br><span class="line"><span class="tag"><<span class="name">transition</span> <span class="attr">:name</span>=<span class="string">"transitionName"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">router-view</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"></<span class="name">transition</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 接着在父组件内</span></span><br><span class="line"><span class="comment">// watch $route 决定使用哪种过渡</span></span><br><span class="line">watch: {</span><br><span class="line"> <span class="string">'$route'</span> (to, <span class="keyword">from</span>) {</span><br><span class="line"> <span class="keyword">const</span> toDepth = to.path.split(<span class="string">'/'</span>).length</span><br><span class="line"> <span class="keyword">const</span> fromDepth = <span class="keyword">from</span>.path.split(<span class="string">'/'</span>).length</span><br><span class="line"> <span class="keyword">this</span>.transitionName = toDepth < fromDepth ? <span class="string">'slide-right'</span> : <span class="string">'slide-left'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="13-数据获取"><a href="#13-数据获取" class="headerlink" title="13. 数据获取"></a>13. 数据获取</h2><p>进入某个路由后,需要从服务器获取数据;例如,在渲染用户信息时,你需要从服务器获取用户的数据;可以通过下面两种方式来实现:</p><ul><li><strong>导航完成之后获取</strong>:先完成导航,然后在接下来的组件生命周期钩子中获取数据;在数据获取期间显示“加载中”之类的指示;</li><li><strong>导航完成之前获取</strong>:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航;</li></ul><h3 id="13-1-导航完成后获取数据"><a href="#13-1-导航完成后获取数据" class="headerlink" title="13.1 导航完成后获取数据"></a>13.1 导航完成后获取数据</h3><p>当使用这种方式时,我们使用导航和渲染组件,然后在组件的 <code>created</code> 钩子中获取数据;这让我们有机会在数据获取期间展示一个 <code>loading</code> 状态,还可以在不同视图间展示不同的 <code>loading</code> 状态:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">template</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"post"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"loading"</span> <span class="attr">v-if</span>=<span class="string">"loading"</span>></span></span><br><span class="line"> Loading...</span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">"error"</span> <span class="attr">class</span>=<span class="string">"error"</span>></span></span><br><span class="line"> {{ error }}</span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">"post"</span> <span class="attr">class</span>=<span class="string">"content"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h2</span>></span>{{ post.title }}<span class="tag"></<span class="name">h2</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>{{ post.body }}<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">template</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> data () {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> loading: <span class="literal">false</span>,</span><br><span class="line"> post: <span class="literal">null</span>,</span><br><span class="line"> error: <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> created () {</span><br><span class="line"> <span class="comment">// 组件创建完后获取数据,</span></span><br><span class="line"> <span class="comment">// 此时 data 已经被 observed 了</span></span><br><span class="line"> <span class="keyword">this</span>.fetchData()</span><br><span class="line"> },</span><br><span class="line"> watch: {</span><br><span class="line"> <span class="comment">// 如果路由有变化,会再次执行该方法</span></span><br><span class="line"> <span class="string">'$route'</span>: <span class="string">'fetchData'</span></span><br><span class="line"> },</span><br><span class="line"> methods: {</span><br><span class="line"> fetchData () {</span><br><span class="line"> <span class="keyword">this</span>.error = <span class="keyword">this</span>.post = <span class="literal">null</span></span><br><span class="line"> <span class="keyword">this</span>.loading = <span class="literal">true</span></span><br><span class="line"> <span class="comment">// replace getPost with your data fetching util / API wrapper</span></span><br><span class="line"> getPost(<span class="keyword">this</span>.$route.params.id, (err, post) => {</span><br><span class="line"> <span class="keyword">this</span>.loading = <span class="literal">false</span></span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">this</span>.error = err.toString()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>.post = post</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="13-2-在导航完成前获取数据"><a href="#13-2-在导航完成前获取数据" class="headerlink" title="13.2 在导航完成前获取数据"></a>13.2 在导航完成前获取数据</h3><p>通过这种方式,是在导航转入新的路由前获取数据;一般在接下来的组件的 <code>beforeRouteEnter</code> 守卫中获取数据,当数据获取成功后只调用 <code>next</code> 方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> data () {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> post: <span class="literal">null</span>,</span><br><span class="line"> error: <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> beforeRouteEnter (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> getPost(to.params.id, (err, post) => {</span><br><span class="line"> next(<span class="function"><span class="params">vm</span> =></span> vm.setData(err, post))</span><br><span class="line"> })</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 路由改变前,组件就已经渲染完了</span></span><br><span class="line"> <span class="comment">// 逻辑稍稍不同</span></span><br><span class="line"> beforeRouteUpdate (to, <span class="keyword">from</span>, next) {</span><br><span class="line"> <span class="keyword">this</span>.post = <span class="literal">null</span></span><br><span class="line"> getPost(to.params.id, (err, post) => {</span><br><span class="line"> <span class="keyword">this</span>.setData(err, post)</span><br><span class="line"> next()</span><br><span class="line"> })</span><br><span class="line"> },</span><br><span class="line"> methods: {</span><br><span class="line"> setData (err, post) {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">this</span>.error = err.toString()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>.post = post</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示;如果数据获取失败,同样有必要展示一些全局的错误提醒。</p><h2 id="14-滚动行为"><a href="#14-滚动行为" class="headerlink" title="14. 滚动行为"></a>14. 滚动行为</h2><p>使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样;在 <code>vue-router</code> 中可以自定义路由切换时页面如何滚动;</p><p><strong>注意: 这个功能只在支持 <code>history.pushState</code> 的浏览器中可用;</strong></p><p>在创建 <code>Router</code> 实例时提供一个 <code>scrollBehavior</code> 方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> VueRouter({</span><br><span class="line"> routes: [...],</span><br><span class="line"> <span class="comment">// 该方法接收 to 和 from 路由对象;第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用;</span></span><br><span class="line"> scrollBehavior (to, <span class="keyword">from</span>, savedPosition) {</span><br><span class="line"> <span class="comment">// return 期望滚动到哪个的位置</span></span><br><span class="line"> <span class="comment">// 返回滚动位置的对象信息格式如下:</span></span><br><span class="line"> <span class="comment">// { x: number, y: number }</span></span><br><span class="line"> <span class="comment">// { selector: string, offset? : { x: number, y: number }}</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>下面对该方法的使用举例:</p><ol><li><p>所有路由导航,简单地让页面滚动到顶部:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">scrollBehavior (to, <span class="keyword">from</span>, savedPosition) {</span><br><span class="line"> <span class="keyword">return</span> { <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>返回 <code>savedPosition</code> ,在按下 <code>后退/前进</code> 按钮时,就会像浏览器的原生表现那样:</p></li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">scrollBehavior (to, <span class="keyword">from</span>, savedPosition) {</span><br><span class="line"> <span class="keyword">if</span> (savedPosition) {</span><br><span class="line"> <span class="keyword">return</span> savedPosition</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> { <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>模拟<strong>滚动到锚点</strong>的行为:</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">scrollBehavior (to, <span class="keyword">from</span>, savedPosition) {</span><br><span class="line"> <span class="keyword">if</span> (to.hash) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> selector: to.hash</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="14-1-异步滚动"><a href="#14-1-异步滚动" class="headerlink" title="14.1 异步滚动"></a>14.1 异步滚动</h3><p>返回一个 <code>Promise</code> 来得出预期的位置描述:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">scrollBehavior (to, <span class="keyword">from</span>, savedPosition) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> resolve({ <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> })</span><br><span class="line"> }, <span class="number">500</span>)</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="15-路由懒加载"><a href="#15-路由懒加载" class="headerlink" title="15. 路由懒加载"></a>15. 路由懒加载</h2><p>结合 <code>Vue</code> 的<a href="https://cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6" target="_blank" rel="noopener">异步组件</a>和 Webpack 的代码分割功能,轻松实现路由组件的懒加载:</p><ol><li>将异步组件定义为返回一个 <code>Promise</code> 的工厂函数 (该函数返回的 <code>Promise</code> 应该 <code>resolve</code> 组件本身):<code>const Foo = () => Promise.resolve({ /* 组件定义对象 */ })</code>;</li><li>在 Webpack 2 中,我们可以使用<code>动态 import</code>语法来定义代码分块点 (split point):<code>import('./Foo.vue') // 返回 Promise</code>;</li><li>结合上述两者就是如何定义一个能够被 Webpack 自动代码分割的异步组件:<code>const Foo = () => import('./Foo.vue')</code>;</li></ol><h3 id="15-1-把组件按组分块"><a href="#15-1-把组件按组分块" class="headerlink" title="15.1 把组件按组分块"></a>15.1 把组件按组分块</h3><p>把某个路由下的所有组件都打包在同个异步块 (<code>chunk</code>) 中。只需要使用 <a href="https://webpack.js.org/guides/code-splitting/" target="_blank" rel="noopener">命名 chunk</a>,一个特殊的注释语法来提供 <code>chunk name</code> (需要 Webpack > 2.4);Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Foo = <span class="function"><span class="params">()</span> =></span> <span class="keyword">import</span>(<span class="comment">/* webpackChunkName: "group-foo" */</span> <span class="string">'./Foo.vue'</span>)</span><br><span class="line"><span class="keyword">const</span> Bar = <span class="function"><span class="params">()</span> =></span> <span class="keyword">import</span>(<span class="comment">/* webpackChunkName: "group-foo" */</span> <span class="string">'./Bar.vue'</span>)</span><br><span class="line"><span class="keyword">const</span> Baz = <span class="function"><span class="params">()</span> =></span> <span class="keyword">import</span>(<span class="comment">/* webpackChunkName: "group-foo" */</span> <span class="string">'./Baz.vue'</span>)</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>在 Vue 的学习中,VueRouter 是 Vue.js 官方的路由管理器,该文档是对 VueRouter 官方文档的知识的学习笔记,包括了 VueRouter 的基本使用以及进阶部分的知识,具体查看文档;</p>
</summary>
<category term="Vue" scheme="https://geek-lhj.github.io/categories/Vue/"/>
<category term="VueRouter" scheme="https://geek-lhj.github.io/categories/Vue/VueRouter/"/>
<category term="官方文档" scheme="https://geek-lhj.github.io/categories/Vue/VueRouter/%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3/"/>
<category term="VueRouter" scheme="https://geek-lhj.github.io/tags/VueRouter/"/>
<category term="Vue" scheme="https://geek-lhj.github.io/tags/Vue/"/>
<category term="路由" scheme="https://geek-lhj.github.io/tags/%E8%B7%AF%E7%94%B1/"/>
</entry>
</feed>