-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
566 lines (345 loc) · 334 KB
/
Copy pathatom.xml
File metadata and controls
566 lines (345 loc) · 334 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>瞧,这个人</title>
<link href="/atom.xml" rel="self"/>
<link href="https://foreverwang.github.io/"/>
<updated>2020-06-21T11:08:05.115Z</updated>
<id>https://foreverwang.github.io/</id>
<author>
<name>foreverwang</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>事件循环</title>
<link href="https://foreverwang.github.io/2019/09/01/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/"/>
<id>https://foreverwang.github.io/2019/09/01/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/</id>
<published>2019-09-01T04:20:48.000Z</published>
<updated>2020-06-21T11:08:05.115Z</updated>
<content type="html"><![CDATA[<p>说在前头:事件循环和js语言本身没有关系,和js引擎也没有关系。</p><p>先看一个题目:</p><pre><code>setTimeout(()=>{ console.log(1);},10)for (let i=0; i<100000000; i++) {}console.log(2);setTimeout(()=>{ console.log(3);},0)Promise.resolve().then(function(){ console.log(4);})console.log(5);</code></pre><p>答案是 2 5 4 1 3 或 2 5 4 3 1。 这取决于这个for循环执行耗时。</p><a id="more"></a><p>先思考一个问题,我们的javascript代码是如何在浏览器或者是Node环境跑起来的。真正执行javascript代码的是谁?<br>保证我们代码有序执行的都有哪些参与者?</p><p>执行javascript代码的是javascript引擎(Engine),比如chrome里是V8,safari里是javascriptCore。要想执行javascript代码只有引擎是不行的,还得有宿主环境,也叫javascript运行时环境(Runtime),比如浏览器或者Node。宿主给引擎一段js代码,引擎才开始执行js代码。</p><p>在浏览器里js代码的执行是多个角色参与,看图:<br><img src="https://user-images.githubusercontent.com/25282685/64170563-fd2a7800-ce82-11e9-847b-c286b3b263b8.png" alt="image"></p><ul><li>Call stack (js引擎内)</li><li>Web API (浏览器提供)</li><li>Callback queue (事件队列对应的回调队列)</li></ul><h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>所有的宿主环境都有一个共同点就是都实现了一个叫 <code>Event Loop(事件循环)</code>的内置机制。它来通过多次调用javascript引擎来调度程序中多个模块的执行顺序。</p><p>Event Loop有一个简单的工作机制——就是去监视Call Stack和Callback Queue。 如果调用栈为空,它将从Callback队列中取出第一个事件回调,并将其推送到调用栈,调用栈开始顺序执行代码。<br>而Callback Queue里的回调是宿主环境在一定的时机推入的。</p><p>现在我们看开头那断代码,这里我们先不考虑Promise:</p><pre><code>// 标号1setTimeout(()=>{ console.log(1);},10)//标号5for (let i=0; i<100000000; i++) {}//标号2console.log(2);//标号3setTimeout(()=>{ console.log(3);},0)//标号4console.log(5);</code></pre><p>当宿主环境将这段代码给到js引擎时,引擎创建一个常驻内存的调用栈开始顺序执行代码。<br>这段代码可能是一段script内的代码也可能是一个模块代码,我们暂且将其用一个名为main函数指代。<br>最初调用栈的栈里只有一个main。随着代码顺序执行:</p><ol><li>标号1的setTimeout函数入栈,执行完出栈</li><li>标号2的console.log函数入栈,执行完出栈</li><li>标号3的setTimeout函数入栈,执行完出栈</li><li>标号4console.log函数入栈,执行完出栈</li><li>此时main也执行完了,出栈,调用栈空了</li></ol><p>注:这里说的函数入栈即所得函数上下文入栈。</p><p>接下来关键Event Loop该登场了,Event Loop检测到调用栈空了,说明当前迭代已经执行完毕,就会去Callback Queue 取队首的事件回调,并将其推入调用栈,下一轮迭代就开始了。</p><p>到这里我们好像忽略了一件事情,Callback Queue 里有东西吗?有的话是什么时候有的呢?<br>我们再看第一次迭代中的步骤1,setTimeout函数是浏览器提供的api, js引擎告诉浏览器10ms后给我做点事(传入的回调),此时浏览器就开启了一个定时器,并保存下这个回调函数,定时器时间到了后,将这个回调推入Callback Queue。</p><p>所以说标号5(示例代码中, 下同)的for循环执行耗时,会影响标号1和标号3两个setTimeout的回调被推入Callback Queue的顺序,进而影响其被执行的顺序。</p><p>如果在执行标号3的setTimeout时,标号1的setTimeout时间已到,则标号1的回调会先于标号3的回调被推入Callback Queue。所以在第二次迭代时,从Callback Queue队首取到的是<br>标号1的回调,接下来的执行过程:</p><ol><li>console.log(1) 入栈,执行完出栈</li><li>调用栈为空,Event Loop从Callback Queue取出此时队首的事件回调并推入调用栈(开启时间循环第三次迭代)</li><li>console.log(3) 入栈,执行完出栈</li><li>此时调用栈为空,Callback Queue也为空,js引擎就可以先休息会了。</li></ol><p>我在mbp的chrome 运行上面代码打印顺序是: </p><blockquote><p>2 5 1 3</p></blockquote><p>我们可以把for循环的次数调小至100,再次查看打印顺序,来验证上面的论证。此时的打印顺序是: </p><blockquote><p>2 5 3 1</p></blockquote><h2 id="宏任务和微任务"><a href="#宏任务和微任务" class="headerlink" title="宏任务和微任务"></a>宏任务和微任务</h2><p>在上面例子中的setTimeout是由浏览器这个宿主提供的api,这个异步任务也是有其发起的。</p><blockquote><p>宿主环境发起的任务叫宏任务。</p></blockquote><p>在ES5及更早版本,javascript本身是没有异步执行代码的能力的。宿主环境把一段代码给js引擎,引擎就把这段代码顺次执行了,而这个任务就是宿主发起的任务。<br>ES6引入了Promise,这使得不用依赖宿主环境,javascript引擎自己也可以发起任务了。</p><blockquote><p>javascript引擎发起的任务叫微任务(对应ES规范里的<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues" target="_blank" rel="noopener">job</a>)</p></blockquote><p>微任务通常来说就是需要在当前宏任务执行结束后立即执行的任务。所有的微任务会在下一个宏任务执行之前执行完毕。</p><p>我们再看开头的带有promise的例子,这里我们简化一下:</p><pre><code> // 标号1setTimeout(()=>{ console.log(3);},0)// 标号2Promise.resolve().then(function(){ console.log(4);})// 标号3console.log(5);</code></pre><ol><li>标号1的timeout入栈,执行完毕出栈;同时浏览器开一个定时器,立即将该回调函数推入Callback Queue</li><li>标号2的promise入栈,执行完毕出栈;同时javascript引擎发起了一个微任务,promise的then回调会被推入本轮迭代的队尾。</li><li>标号3的console.log(5)入栈,执行完毕出栈</li><li>关键的一步来了,正常是本轮迭代已经执行完毕,栈也为空了,但是本轮迭代还有微任务的回调队列即标号2的promise的then回调,上面我们说了微任务永远会在本轮迭代完成,故先执行console.log(4);</li><li>此时本轮迭代的调用栈真的为空了,Event Loop从Callback Queue队首去取回调:console.log(3);</li></ol><p>故打印顺序:</p><blockquote><p>5 4 3 </p></blockquote><p>可以看到一个宏观任务可能会包含一个或多个微观任务,而我们的任务队列可以看做是一个二维数组:第一纬是宏观任务,第二维则是每一个宏观任务包含的微观任务。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="http://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues" target="_blank" rel="noopener">ES规范:Jobs and Job Queues</a></li><li><a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loops" target="_blank" rel="noopener">H5规范:event-loops</a></li><li><a href="http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D" target="_blank" rel="noopener">event-loops可视化学习工具</a></li><li><a href="https://www.oschina.net/translate/how-does-javascript-actually-work-part-4" target="_blank" rel="noopener">参考文章</a></li><li><a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ" target="_blank" rel="noopener">javascriptConf 2014演讲视频</a></li></ul>]]></content>
<summary type="html">
<p>说在前头:事件循环和js语言本身没有关系,和js引擎也没有关系。</p>
<p>先看一个题目:</p>
<pre><code>setTimeout(()=&gt;{
console.log(1);
},10)
for (let i=0; i&lt;100000000; i++) {}
console.log(2);
setTimeout(()=&gt;{
console.log(3);
},0)
Promise.resolve().then(function(){
console.log(4);
})
console.log(5);
</code></pre><p>答案是 2 5 4 1 3 或 2 5 4 3 1。 这取决于这个for循环执行耗时。</p>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="javascript" scheme="https://foreverwang.github.io/tags/javascript/"/>
<category term="事件循环" scheme="https://foreverwang.github.io/tags/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/"/>
<category term="异步" scheme="https://foreverwang.github.io/tags/%E5%BC%82%E6%AD%A5/"/>
</entry>
<entry>
<title>你真的懂vue的数据绑定原理了吗</title>
<link href="https://foreverwang.github.io/2019/07/05/%E4%BD%A0%E7%9C%9F%E7%9A%84%E6%87%82vue%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86%E4%BA%86%E5%90%97/"/>
<id>https://foreverwang.github.io/2019/07/05/%E4%BD%A0%E7%9C%9F%E7%9A%84%E6%87%82vue%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86%E4%BA%86%E5%90%97/</id>
<published>2019-07-05T08:21:50.000Z</published>
<updated>2020-06-21T11:08:05.116Z</updated>
<content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>数据绑定有很多种实现方案,vue中的实现只是众多实现中的一种。都知道vue数据绑定是通过Object.defineProperty() 实现,但是在面试过程中,真正清楚vue数据绑定具体实现的不多,而网上各种vue原理解析的文章很多是二手三手的解读,千篇一律很多关键点都没有说到,本文主要是整理下自己的理解,并试图让不完全懂vue数据绑定原理实现的人看了能彻底懂。</p><h3 id="数据绑定最终达到的效果"><a href="#数据绑定最终达到的效果" class="headerlink" title="数据绑定最终达到的效果"></a>数据绑定最终达到的效果</h3><p>先了解数据绑定绑定的是什么?是UI。<br>数据绑定要达到的效果就是:UI渲染的时候使用数据对象(data)里的数据初始化UI,当UI中使用到的数据变化时,这个数据对应的UI会同步更新,而无需开发者手动修改DOM。<br>下面行文中,数据我们用一个命名为data的对象代指;而UI对应开发者直接接触的就是描述DOM部分的代码, 可能是一段html模板,也可能是一段jsx。</p><a id="more"></a><h3 id="数据绑定的实现"><a href="#数据绑定的实现" class="headerlink" title="数据绑定的实现"></a>数据绑定的实现</h3><h4 id="先上一个张原理图"><a href="#先上一个张原理图" class="headerlink" title="先上一个张原理图"></a>先上一个张原理图</h4><p><img src="https://github-production-user-asset-6210df.s3.amazonaws.com/25282685/60658801-8246e000-9e86-11e9-89aa-ebb5ede00830.png" alt="image"></p><h4 id="知识储备(如果已经熟练掌握可跳过):"><a href="#知识储备(如果已经熟练掌握可跳过):" class="headerlink" title="知识储备(如果已经熟练掌握可跳过):"></a>知识储备(如果已经熟练掌握可跳过):</h4><ul><li><p>访问器属性和Object.defineProperty() </p><p> defineProperty() 可以定义属性的getter和setter方法,当访问访问器属性的时候会调用该属性的getter方法,这样我们就可以知道全部的数据访问,并在此时做一些额外的工作。</p><pre><code>var data = { a: 1 }Object.defineProperty(data,'_a', { get() { console.log(`属性_a被访问了,在这里可以劫持这个数据访问的操作,返回我们想返回的值,并做些额外操作`); return this.a; }, set(newVal) { console.log(`属性_a被赋值了,我们可以在此时劫持改操作`); this.a = newVal }})console.log(data._a)// 1, 属性_a被访问了...data._a = 2; //属性_a被赋值了...console.log(data._a); //2, 属性_a被访问了..</code></pre></li></ul><p>定义了getter setter的属性即成了 访问器属性。上例中属性 ‘_a’就是访问器属性。 当我们访问访问器属性的时候会先调用我们定义的getter方法,当赋值访问器属性的时候会先调用我们定义的setter方法。</p><p>这里可能有人会疑问,为什么不直接通过Object.defineProperty 定义属性’a’,而是定义了一个和属性’a’关联的新属性’_a’ ? 有这个疑问的同学建议在浏览器控制台执行以下上面代码(将被定义的属性改成’a’),可能就一下明白了: 这样当我们访问属性a的时候会造成死循环,因为我们将属性a定义为访问器属性,当访问属性a的时候会调用getter方法getter 方法内执行 <code>return this.a</code>时又访问了属性a,导致又调用了setter方法,然后死循环。</p><p>你可能还是有疑惑,如果我们想将一个对象的属性转成访问器属性那岂不是只能新增属性,那这对数据使用方十分不友好。答案是我们可以通过闭包变量实现不增加新属性而将原属性定义成访问器属性, 而我们还是访问原始的属性。具体做法看下边。</p><ul><li><p>闭包变量</p><pre><code>var data = {a:3};function defineReactive(data, key, val) { Object.defineProperty(obj, key, { get() { return val; }, set(newVal) { val = newVal; } })}defineReactive(data, 'a', obj['a'])</code></pre><p>这里我们就将属性’a’定义成了访问器属性。这里方法名defineReactive直译过来叫「定义响应式」,也比较好理解:正是访问器属性的setter和getter可以劫持属性的读写操作,我们可以响应这些操作,故叫定义响应式,这样的数据也叫响应式数据。</p></li></ul><p>示例中defineReactive 内部 变量val即是一个闭包变量,函数调用结束后不会被释放,因为在getter中有引用。所以当我们重新赋值属性a的时候 会将新值赋给闭包变量val,再次访问属性a的时候执行getter方法 返回闭包变量val的值。</p><ul><li>发布/订阅模式</li></ul><p>发布/订阅模式是一个再常见不过的模式了,这里不做赘述。下文提到的依赖收集及通知依赖即是发布订阅模式。</p><h4 id="原理解读"><a href="#原理解读" class="headerlink" title="原理解读"></a>原理解读</h4><p>现在回到上面的原理图。这里相对于源码我做了最大可能的精简,只保留了核心角色,这有是为了降低我们的理解负担。</p><p>首先看到这里有两个角色: 数据(data), 和订阅者(watcher)。<br>最开始我们对data做了一个操作:定义响应式即定义getter和setter。 这是vue整个数据绑定实现的最关键的一步。</p><p>我们的数据变成了响应式这意味着,数据的访问和修改我们都能知道,并在这个时机做一些操作。当访问某个数据(比如data对象的a属性)时,会触发这个数据的getter方法,我们在此将用到这个数据的订阅者(watcher)存起来;当这个数据发生变化时即修改其值时会触发该数据的setter方法,我们在此通知这个数据的订阅者们,订阅者们收到变化后会更新自己。</p><p>这里我们说一下订阅者(watcher)。</p><p>订阅者自身都有一个更新自己的方法(update),当接收到通知后会调用自身update 方法,更新自己。</p><p>每个数据会有多个订阅者,UI中每当一处用到了该数据,就会添加一个该数据的订阅者。<br>举个小栗子:<br>我们的data对象如下:</p><pre><code>data = {a:1}</code></pre><p>在模板中我们有一处用到了这个数据:</p><pre><code><div> 第一次使用数据a:{{a}} </div></code></pre><p>当我们解析模板的时候访问到数据a, 就会触发数据a的getter,在其getter里我们将添加一个订阅者至数据a的订阅者列表。这里的订阅者就是这个div标签,前面我们说了每一个订阅者都有一个更新自己的方法。 当数据a发生变化是会触发其setter,在setter里我们通知数据a的订阅者们,这些订阅者们收到通知的时候再调用自己的更新方法。</p><p>注意:在vue 1.x 里面当触发数据更新的时候是直接通知到其订阅者使其订阅者更新,这个更新粒度是节点级的。而vue2.x里加入了虚拟DOM,这个流程发生一点变化:状态侦测不再细化到某个具体节点,而是某个组件组件内部通过通过虚拟DOM来渲染视图。不过数据绑定的原理并没有变化。关于虚拟DOM,我会另开<a href>一篇</a>。</p><h3 id="关于源码"><a href="#关于源码" class="headerlink" title="关于源码"></a>关于源码</h3><p>这里并不会逐行解读源码,这类的文章网上一大片,而且篇幅会很长,我们这里只是结合源码的实现对上文做些补充,以及对源码里的我认为的巧妙之处单独说下。</p><p>通过上面或许已经大致能明白vue里数据绑定的实现了。而此时你去看源码的时候,会发现在有一些差异。在源码中数据绑定相关的还多了Observer类、Dep类。</p><p>Observer类。其实我觉得这个命名不好,它所做的事情就是给所有数据添加getter和setter将其改写成响应式数据,所以我这里直接叫「定义响应式」语义上会更准确,对应我们上图中defineReactive。Observer在通俗意义上更多的是指观察者模式里的观察者,而发布订阅模式即是观察者模式的一个变体,所以还容易造成理解上困扰。(比如我第一次看网上的原理图看到Observer时,还以为数据添加订阅者是在这个类里操作的)。</p><p>Dep类。它是和Watcher紧密关联的。 它是作用是用来为每个数据存放其订阅者,当这个数据变化了再通知这些订阅者们更新。可以看到Dep就是一个中介者。以租房中介为例:某个数据是一个房东,Dep是中介,租房者们是一个个watcher。当有人租房的时候都找Dep,当房东要收钱的时候告诉中介,中介负责通知各个租客交房租。</p><p>这里又一次用到了闭包变量:</p><pre><code>function defineReactive(data, key, val) { let dep = new Dep(); //闭包变量 Object.defineProperty(obj, key, { get() { dep.depend(); //添加订阅者 return val; }, set(newVal) { val = newVal; dep.notify(); //通知订阅者更新 } })}let data = {a: 1};defineReactive(data, 'a', obj['a']);</code></pre><p>这里我们调用defineReactive方法将data的属性a定义响应式,这里产生一个闭包变量dep, 而其是和属性a绑定的,后续读写属性a会调用属性a的getter/setter,会用到dep 变量。每访问一次属性a都对会对a添加一个订阅者。</p><p>看defineReactive方法,你可能会有疑问的:dep.depend() 具体添加的是什么?答案是Watcher实例。但此时你可能还有疑问:在定义dep.depend 方法的时候我们还不知道watcher长什么样呢,因为watcher是有多种类型的(比如更新文本值、更新指令值等),当我们在需要添加的那一刻才知道订阅者长什么样。那怎么做到 dep 可以不事先关心 watcher的具体实现,在需要添加的地方又能自动将watcher添加到dep里呢? </p><p>直线的思维是当我需要添加watcher的时候,调用一下dep.depend(watcher)。单这样有一个问题: 当我们在外部调用dep.depend(watcher),就要知道每一个数据其对应的dep如何访问到,这可以定义一个全局对象来存放每个数据的dep,但这样其实并不优雅,封装性不够好。所以我们看到vue 源码里用一个闭包变量来存放每个数据对应的dep,而闭包变量在外部我们访问不到。 接下来我们就看一下vue 里是怎么做的,十分之巧妙。</p><p>下面为便于理解用的是伪代码,并不是vue 里源代码。</p><p>Dep的depend方法</p><pre><code>depend() { if (Dep.target) { this.addSub(Dep.target); }}</code></pre><p>depend方法判断了一个条件 Dep.target如果存在则添加将Dep.target添加到订阅者队列里。</p><p>Watcher里get方法(会在constructor 调用)<br> //为数据添加订阅者<br> get() {<br> Dep.target = this;<br> let value = this.getter(vm,expOrFn,cb); // 会触发数据的getter 方法, 可以先不关心它具体做了啥<br> Dep.target = null;<br> return value;<br> }</p><p>当解析到UI里用到了某一个数据后,我们就new Watcher(vm,expOrFn,cb)得到一个watcher实例, Watcher的constructor 会执行其get方法,该方法里先将Dep.target赋值为当前watcher实例,接着去访问这个数据,就会触发这个数据的getter方法,在数据的getter 里我们调用dep.deepend方法,deepend方法内将Dep.target(此时就是watcher实例)添加到当前数据的订阅者队列。 然后再将Dep.target置空。</p><p>我们再回顾下前面的定义响应式的方法对每个数据定义的getter:</p><pre><code>function defineReactive(data, key, val) { let dep = new Dep(); //闭包变量 Object.defineProperty(obj, key, { get() { dep.depend(); //添加订阅者 return val; }, set(newVal) { //.... } })}</code></pre><p>这就保证了只有通过new Watcher去访问的数据才会被添加到该数据的订阅者队列。<br>比如我们在代码里写 <code>data.a</code> 这样去访问属性a时,由于Dep.target是null,所以不会被添加无意义的订阅者。</p><p>至此数据绑定原理基本上讲完了。 下面附上数据绑定相关部分精简代码实现的完整版:</p><p>// Observer</p><pre><code>/** * Observer会将数据对象的所有属性都转换为getter/setter的形式来收集依赖(前面我们说的订阅者),并当属性值变化时通知这些依赖 */export default class Observer() { constructor(value) { this.value = value; if (!Array.isArray(value)) { this.walk(value); } // 注意数组的处理方式略有不同,这里不暂且不包括数组的处理 } walk(obj) { const keys = Objects.keys(obj); for(let i=0, l = keys.length; i<l; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } }}// defineReactivefunction defineReactive(data, key, val) { // 递归子属性 if (typeof val === 'object') { new Observer(val); } let dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configulable: true, get() { dep.depend(); return val; }, set(newVal) { if (val === newVal) { return; } val = newVal; dep.notify(); } })}</code></pre><p>// Dep</p><pre><code>export default class Dep { constructor() { this.subs = []; } addSub(sub) { this.subs.push(sub); } removeSub(sub) { remove(this,subs, sub); } depend() { if (Dep.target) { this.addSub(Dep.target); } } notify() { const subs = this.subs.slices(); for (let i=0,l=subs.length; i<l; i++) { subs[i].updates(); } }}function remove(arr, item) { if (arr && arr.length) { const index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1) } }}</code></pre><p>// Watcher</p><pre><code>export default class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm; this.getter = parsePath(expOrFn); this.cb = cb; this.value = this.get(); } get() { Dep.target = this; let value = this.getter.call(this.vm, this.vm); Dep.target = null; return value; } update() { const oldValue= this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); }}const bailRe = /[^λw.$]/function parsePath(path) { if (bailRe.test(path)) { return; } const segments = path.split('.'); return function (obj) { for (let i=0, l=segments.length; i < l; i++) { if (!obj) return; obj = obj[segments[i]]; } return obj; }}</code></pre><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><ul><li>vue2.6<a href="https://github.com/vuejs/vue/tree/2.6" target="_blank" rel="noopener">源码</a></li><li>最后精简版源码部分来自《深入浅出Vue.js》</li></ul>]]></content>
<summary type="html">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>数据绑定有很多种实现方案,vue中的实现只是众多实现中的一种。都知道vue数据绑定是通过Object.defineProperty() 实现,但是在面试过程中,真正清楚vue数据绑定具体实现的不多,而网上各种vue原理解析的文章很多是二手三手的解读,千篇一律很多关键点都没有说到,本文主要是整理下自己的理解,并试图让不完全懂vue数据绑定原理实现的人看了能彻底懂。</p>
<h3 id="数据绑定最终达到的效果"><a href="#数据绑定最终达到的效果" class="headerlink" title="数据绑定最终达到的效果"></a>数据绑定最终达到的效果</h3><p>先了解数据绑定绑定的是什么?是UI。<br>数据绑定要达到的效果就是:UI渲染的时候使用数据对象(data)里的数据初始化UI,当UI中使用到的数据变化时,这个数据对应的UI会同步更新,而无需开发者手动修改DOM。<br>下面行文中,数据我们用一个命名为data的对象代指;而UI对应开发者直接接触的就是描述DOM部分的代码, 可能是一段html模板,也可能是一段jsx。</p>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="vue" scheme="https://foreverwang.github.io/categories/javascript/vue/"/>
<category term="vue" scheme="https://foreverwang.github.io/tags/vue/"/>
<category term="数据绑定原理" scheme="https://foreverwang.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86/"/>
</entry>
<entry>
<title>少有人走的路</title>
<link href="https://foreverwang.github.io/2019/03/30/%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF/"/>
<id>https://foreverwang.github.io/2019/03/30/%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF/</id>
<published>2019-03-30T16:00:00.000Z</published>
<updated>2020-06-21T11:08:05.116Z</updated>
<content type="html"><![CDATA[<p>这是一篇《少有人走的路》的读书总结。作者斯科特·派克,美国精神科医生,畅销书作家,心灵导师。这是一本关于心智成熟的书。这本书诞生的背景,1970年代,美国笼罩在经济危机和越南战争的阴霾下,整个国民精神萎靡不振。这本书在当时风靡一时,成了美国人民的精神食粮,帮助人们走出那些阴暗的时刻。</p><p>「心智成熟」的定义是什么呢?不妨先保留这个疑问。</p><p>开篇派克写道:Life is difficult. (人生苦难重重)。当我们理解并接受这一点的时候,就不会对人生的苦难耿耿于怀。然而大多数人不愿意正视人生的问题和苦难,他们总是习惯于怨天尤人,抱怨命运的不公,埋怨自己为什么这么倒霉。面对并解决问题的过程正是心智成熟的过程。<br><a id="more"></a><br>问题给我们带来痛苦,而逃避问题的趋向,正是很多心理疾病的根源。那么有没有解决问题的什么通用手段呢?派克的答案是–自律。</p><h3 id="什么是自律?"><a href="#什么是自律?" class="headerlink" title="什么是自律?"></a>什么是自律?</h3><p>自律包含四个原则:延迟满足,承担责任,尊重事实,保持平衡。</p><h4 id="首先延迟满足"><a href="#首先延迟满足" class="headerlink" title="首先延迟满足"></a>首先延迟满足</h4><p>书中举了吃蛋糕的例子。吃蛋糕的时候是先吃奶油还是先吃蛋糕代表了先甜后苦还是先苦后甜(当然这并不绝对)。举个我们更能切身体会的例子。上学的时候,你是习惯先写完作业再愉快的玩耍,还是先玩够了,最后不得不写的时候才去写? 前者就是推迟满足感。再如,面对一天的工作,你是习惯于先去做那些简单琐碎的任务,拖到最后再去做哪些艰巨却重要的任务,还是相反?</p><p>延迟满足感,意味着不贪图暂时的安逸,重新设置人生快乐和痛苦的次序:先面对问题并感受痛苦,然后解决问题并享受更大的快乐。</p><p>为什么有的人能够拥有足够的自制力,能避一时的安逸,先苦后甜,而相当数量的人不懂得推迟满足感?派克认为,家庭教育在这方面起着主要作用(后面统一说家教对个人的影响)。</p><h4 id="第二点承担责任"><a href="#第二点承担责任" class="headerlink" title="第二点承担责任"></a>第二点承担责任</h4><p>我的理解,这里主要指对自己负责任。我们必须面对属于自己的问题。而很多人习惯于推卸责任,出了问题总是能把原因归结于其他人,认为这「不是我的问题」,因此应该由他人解决问题,自己却不去做任何努力。</p><p>派克举了一个他随军队驻扎冲绳岛做心理咨询时的一个案例。有个美军军官酗酒成瘾,问题严重。他否认自己饮酒成性,也认为酗酒不是他的个人问题。他说:”在冲绳岛,我们晚上无事可做,生活实在无聊,除了喝酒,还能做什么呢?”<br>派克问他喜欢读书吗?他说当然喜欢;又问那晚上为什么不以读书替代酗酒?答营房太吵,没心思读;问那为什么不去图书馆呢, 答太远了;问难道图书馆比酒吧还远吗?这时军官说,其实他也不怎么喜欢读书。<br>于是派克又问喜欢钓鱼吗?回答和对读书的回答如出一辙,总之各种借口,最后又不得不说其实自己也没那么喜欢钓鱼。</p><p>这个军官认为在冲绳这个小岛,人们整天只能靠喝酒打发时间,很多人如此,不是他自己的问题,即便总是饮酒过量,以至于违反军规,惹了很多麻烦。<br>这位军官总是始终不肯承认酗酒是他的个人问题,也不接受派克的建议:只要凭借毅力和决心,再加上别人的帮助,就能彻底解决问题。最终也不肯接受建议,继续酗酒,最终被开除军职。</p><p>书中提到两个概念:神经官能症和人格失调症。</p><p>神经官能症:为自己强加责任,认为错在自己。人格失调症相反,总是把错误归咎于他人,不愿意承担责任。上面提到的军官就是有一定程度的人格失调症,把问题的原因归咎于外界因素, 不肯承担责任,去改变现状。<br>这两种症状都是责任边界的模糊。神经官能症其实少见,人格失调症则很常见。</p><p>回看我们自己。是否有人总是抱怨自己的工作无聊,没有意义,自己却从来不想如何做些自己认为意义的事情,或者也不去尝试换工作。 是否有人总是抱怨工作加班太多,从来不去思考如何提升效率从而少加班,或者去换一个加班少的工作。</p><p>这是不是我们对自己的不负责呢?没有认清我们的责任边界,没有认识到自己本可以解决问题从而使自己走出困境。</p><h4 id="第三点:尊重事实"><a href="#第三点:尊重事实" class="headerlink" title="第三点:尊重事实"></a>第三点:尊重事实</h4><p>我们对某事物的态度,并不应该是一成不变的。一方面,我们的认知是在不断完善和修正的,另一方面我们所处的客观环境也是在发生变化的。而人们倾向于维护自己陈旧的观念,保持前后的一致性。然而这并没有意义,此时所谓的坚持如一不过是抱残守缺,脱离现实。 派克把认知比喻作人生的地图,我们需要不断的完善和修正自己的地图。</p><p>对于尊重事实这一点,可以概括为:实事求是,以发展的眼光看问题。观察身边的人其实能做到事事实事求是的人少之又少。比如我有时候会从不同的同事口中听到对同一个同事完全不同的反馈。</p><h4 id="第四点:保持平衡"><a href="#第四点:保持平衡" class="headerlink" title="第四点:保持平衡"></a>第四点:保持平衡</h4><p>书中说道,自律是艰难而复杂的工作,你需要用于足够的勇气和判断力。既要以追求诚实为己任,也需要保留部分事实和真相;既要承担责任,也要拒绝不改承担的责任;为使人生规范、高效、务实,必须学会推迟满足感,把眼光放远,又要尽可能过好眼下的生活,通过适当的努力让人生的快乐多于痛苦。这就是所说的保持平衡,自律本身也需要约束。</p><p>派克以生气为例来说解释持平衡。 我们心理或生理上受到侵犯,或者某人某事让我们伤心失望,我们就会感到生气。要获得生存,生气是一项必不可少的反击方式。从来不生气的人,更容易遭受欺凌和压制,甚至最终被摧毁和消灭。必要的生气,可以使我们更好的生存。我们收到侵犯,不见得是侵犯者对我们怀有敌意。有时他们即便果真有意而为,我们也要适当约束情绪。正面冲突只会是的处境更加不利。大脑的高级中枢–判断力,必须约束低级种树–情绪。在这个复杂多变的世界里,我们不但要有生气能力,还要具备即便生气也可抑制其爆发的能力。我们要以不同的方式,恰当的表达情绪:有时需要委婉,有时需要直接,有时需要心平气和,有时需要火冒三丈。表达生气还要注意时机和场合。</p><p>派克又说:保持平衡的最高原则是’放弃’。书中提到心理学家埃里克·艾瑞克森曾列举出人生八种危机。人生的各个阶段会出现各种危机,只有放弃过去时的观念和习惯,才能顺利进入人生的下一阶段。不少人不敢面对现实,后者无法放弃早已过时的过去,以至于无法克服心理危机和精神危机,只能止步不前。</p><p>以上就是派克所认为的自律的四个原则了。听完觉得心好累啊,怎样能做到自律呢?自律的原动力有是什么呢?为什么有的人能做到高度自律,有的人则不能,人与人之间的差距是怎么拉开的?</p><h3 id="答案是–爱,爱是自律的原动力"><a href="#答案是–爱,爱是自律的原动力" class="headerlink" title="答案是–爱,爱是自律的原动力"></a>答案是–爱,爱是自律的原动力</h3><p>爱的定义有很多种:肉体之爱,精神之爱,手足之爱….。派克对爱的定义是:为了促进自我和他人的心智成熟而具有的一种自我完善的意愿。</p><p>对于爱的定义总是很抽象。那么我们先来看看派克认为哪些不是爱。</p><h4 id="坠入情网不是爱"><a href="#坠入情网不是爱" class="headerlink" title="坠入情网不是爱"></a>坠入情网不是爱</h4><p>那是生理本能反应,是性冲动。因为坠入情网,不是出于主观意愿,不是有计划有意识的选择。你完全可能爱上某个与你毫不相称的人,甚至不愿接受对方身上的缺点,可你却对对方产生深深的依恋。与此同时,另一个人各方面都很出色,你却毫无感觉。 坠入情网是人类内正在的性的需求和外在的性的刺激产生的生理和心理反应,意义在于增加人类生殖机会,促进物种繁衍和生存。</p><h4 id="过分依赖也不是爱"><a href="#过分依赖也不是爱" class="headerlink" title="过分依赖也不是爱"></a>过分依赖也不是爱</h4><p>有的人没有别人的关心和照顾,就认为人生不够完整,以至于无法正常生活,这就是心理学上的”依赖性”,过分的依赖只能导致病态。这里我们就得区分病态的依赖和正常的对于依赖的渴望。人人都有依赖的需求和渴望,都希望有更强大的人关心自己。 不管我们看起多么强大,尽管我们竭力表现出无需关心的样子,但是不得不承认,我们内心深处都渴望过依赖他人的感觉。我们承认这种感觉的合理性,同时我们要警惕他不让他控制我们的生活。假如他牢牢控制我们的一言一行,控制我们的一切感受和需要,那么它不再是单纯的渴望,而是会成为过分依赖的心里问题。心理学上叫”消极性依赖人格失调”。</p><p>“消极性依赖人格失调”的患者只是苦思如何获得他人的爱,甚至没有精力去爱别人,其人生价值依赖于同别人的情感关系。有的情侣一旦分手就无法生活,甚至可能会轻生,然而没过几天他就完全好了,因为又找到了新的恋爱对象。这就是对恋爱对象的病态依赖。依赖性过强的人,总是把失去伴侣当成及其恐怖的事情,他们丝毫不肯降低对他人的依赖性,不肯给对方更多的自由。在消极性依赖婚姻中,夫妻角色分工格外严格,不论做什么,总以过分依赖的心态为起点,致使婚姻变成可怕的陷阱。所谓的爱,也不过是依赖,而自由和独立并不存在。有些依赖性过强的人,婚后甚至宁可放弃婚前的本领和技能。<br>书中提到一个例子:有人女人婚后突然不会开车了。她不是没有学过开车,而是婚后发生的某次事故,使她对开车产生恐惧。她的恐惧足以把丈夫拴在身边,毕竟没有丈夫她哪儿也去不了。丈夫也必须承担起购物的责任,或在她外出购物时充当司机。</p><h4 id="自我牺牲也不是爱"><a href="#自我牺牲也不是爱" class="headerlink" title="自我牺牲也不是爱"></a>自我牺牲也不是爱</h4><p>派克有个病例,有位牧师的家人都出现了心理问题,妻子患上抑郁症,两个儿子也都大学辍学。牧师非常苦恼,不知问题出在哪,他认为自己很爱家人,为家人操碎了心,工作生活都一人包揽。听起来这位牧师很伟大,为了家庭付出很多。而派克却指出问题出在牧师本身,他的大包大揽,极力的付出,只是在满足自己好人形象的心里需求,根本没把妻子和儿子的心智成熟当回事。后来在派克的建议下,扮演起”坏人”,不在对家人的请求有求必应,不再替妻子和儿子收拾家务,打扫卫生…而是让他们自行负担。不就后家人的状况都有了明显的好转,一个儿子回到了大学,一个儿子找了工作,妻子也感受到了独立的好处,心灵由此得到了成长,牧师本人则大大提高了工作效率,感受到真正的快乐。</p><p>自我牺牲严重了就是「被虐待狂」。无论是别人受苦,还是自己受苦,患者都会感受到某种生理快感。</p><h4 id="爱,不是感觉"><a href="#爱,不是感觉" class="headerlink" title="爱,不是感觉"></a>爱,不是感觉</h4><p>我们再看作者对于爱的定义:爱是为了促进自我和他人的心智成熟而具有的一种自我完善的意愿。爱是实际行动,是真正的付出。</p><p>可是什么因素导致了人与人之间「爱」的能力的差异呢?为什么面对同样的处境有的人能坚强面对,从而变的更加强大,有的人则形成理疾病。派克认为是父母的教育起了决定性因素。父母不自律,不能给予孩子足够的关爱,是导致人们日后各种心理疾病的重要原因。即便我们过了父母教育的年龄,意识到这一点对我们仍然有指导意义。我们可以观察自己的性格优缺点,和好坏习惯,是否和从小的父母教育有关?清楚了问题的源头,对于认识问题自身也是有用的。不清楚问题的源头,我们更容易对问题视而不见,反之,我们更能坦然面对问题。</p><p>举个例子吧,比如从小家庭教育比较严苛,父母经常打骂的孩子,缺少父母的关爱,更容易变的’自卑’。从小自我价值感会比较弱,认为自己时间并不值钱,自律的动力也不足。</p><p>(写的有点累了… 前面主要是自己的总结思考,写到这后面就有点为了成文而凑篇幅了,写不动了,所以要结尾这篇文章了…)</p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>以上讲的 自律和爱其实只是这本书的前半部分。 后半部分主要是将意识和潜意识的。后面的观点并不十分赞同,所以也没什么好总结的。 最后派克把心智等同于意识,认为心智的成熟就是意识的成长,同时意识的成长又是对潜意识的重新认识。这一观点是带有宗教色彩的,派克认为潜意识就是我们每个人的上帝云云。</p><p>如果问我这本书值不值得买来细度呢? 如果读书对你来说是一件轻松的事情(阅读速度),推荐阅读;如果不是则不推荐。</p><p>整体这本书里的观点对我而言有一些指导意义, 但是本书的论述略显啰嗦,有些篇幅反复的在说同一个观点,书中举得很多例子说服力也不强。这也可能和翻译的水平有关。书中也有些硬伤,比如把各种心理疾病归因于家庭教育,把潜意识说成是上帝的化身,滥用物理学’熵’的概念。</p><p>更讽刺的是,作者派克在书中以心灵导师的身份教导读者要自律,自己却没有做到:酗酒,吸食大麻,婚外情和家人关系不和谐等。还真是: 讲过很多道理,以然过不好这一生。</p><p>最后我试图回答一下开头的问题:心智成熟的旅程就是直面人生问题,不断克服人生困难,让自己和他人变的更好的过程。</p>]]></content>
<summary type="html">
<p>这是一篇《少有人走的路》的读书总结。作者斯科特·派克,美国精神科医生,畅销书作家,心灵导师。这是一本关于心智成熟的书。这本书诞生的背景,1970年代,美国笼罩在经济危机和越南战争的阴霾下,整个国民精神萎靡不振。这本书在当时风靡一时,成了美国人民的精神食粮,帮助人们走出那些阴暗的时刻。</p>
<p>「心智成熟」的定义是什么呢?不妨先保留这个疑问。</p>
<p>开篇派克写道:Life is difficult. (人生苦难重重)。当我们理解并接受这一点的时候,就不会对人生的苦难耿耿于怀。然而大多数人不愿意正视人生的问题和苦难,他们总是习惯于怨天尤人,抱怨命运的不公,埋怨自己为什么这么倒霉。面对并解决问题的过程正是心智成熟的过程。<br>
</summary>
<category term="读书" scheme="https://foreverwang.github.io/categories/%E8%AF%BB%E4%B9%A6/"/>
<category term="心智成熟之路" scheme="https://foreverwang.github.io/tags/%E5%BF%83%E6%99%BA%E6%88%90%E7%86%9F%E4%B9%8B%E8%B7%AF/"/>
<category term="自律" scheme="https://foreverwang.github.io/tags/%E8%87%AA%E5%BE%8B/"/>
</entry>
<entry>
<title>flex</title>
<link href="https://foreverwang.github.io/2018/10/22/%E4%BD%A0%E5%8F%AF%E8%83%BD%E8%BF%98%E4%B8%8D%E5%A4%9F%E6%87%82flex%E5%B8%83%E5%B1%80/"/>
<id>https://foreverwang.github.io/2018/10/22/%E4%BD%A0%E5%8F%AF%E8%83%BD%E8%BF%98%E4%B8%8D%E5%A4%9F%E6%87%82flex%E5%B8%83%E5%B1%80/</id>
<published>2018-10-22T08:21:50.000Z</published>
<updated>2020-06-21T11:08:05.116Z</updated>
<content type="html"><![CDATA[<h3 id="flex"><a href="#flex" class="headerlink" title="flex"></a>flex</h3><h4 id="flex布局中基本概念"><a href="#flex布局中基本概念" class="headerlink" title="flex布局中基本概念"></a>flex布局中基本概念</h4><ul><li>两种元素类型:flex容器(flex container)、flex项目(flex item 容器成员)。</li><li>两个轴:主轴(main axis)、交叉轴(cross axis)。</li><li>占据的空间:项目占据的主轴空间(main size)、项目占据的交叉轴空间(cross size)</li><li><p>注意:设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。</p><a id="more"></a></li></ul><h4 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h4><ul><li>容器属性6个<ul><li>display: flex</li><li>flex-direction</li><li>justify-content</li><li>flex-wrap (wrap-reverse)</li><li>align-items (定义了项目在交叉轴上如何对齐,baseline | stretch)</li><li>align-content (定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。stretch(默认值):轴线占满整个交叉轴。)</li><li>flex-flow (flex-direction和flex-wrap的简写,默认值: row nowrap)</li></ul></li><li>项目属性6个<ul><li>align-self(可覆盖align-items属性)</li><li>flex-grow (默认值0)</li><li>flex-shrink (默认值1)</li><li>flex-basis(默认值为auto,即项目的本来大小)</li><li>order</li><li>flex (flex-grow、flex-shrink、flex-basis的简写,默认值:0 1 auto, 后两个属性可选。用两个快捷属性:auto(1 1 auto)、 none(0 0 auto),建议优先使用这两个属性,因为浏览器会推算相关值)</li></ul></li></ul><h4 id="flex-布局核心属性"><a href="#flex-布局核心属性" class="headerlink" title="flex 布局核心属性"></a>flex 布局核心属性</h4><ul><li>flex-basis<ul><li>auto : 优先设置的宽度,没设置则内容宽度</li><li>content: 一律内容宽度,不管有没有设置宽度</li><li>长度或百分比:没啥好说的</li></ul></li><li>flex-grow:剩余空间按比例分配,与项目自身所占空间无关</li><li>flex-shrink:超出空间按比例挤压项目,项目自身所占空间参与权重计算</li></ul><h4 id="计算剩余空间"><a href="#计算剩余空间" class="headerlink" title="计算剩余空间"></a>计算剩余空间</h4><blockquote><p>剩余空间 = 容器所占主轴空间 - 所有项所占主轴空间总和</p></blockquote><p>剩余空间 > 0,说明有剩余空间可分配,即可伸缩项<code>有条件扩展</code>,如何扩展看每一项的 <code>flex-grow</code>属性;<br>剩余空间 < 0 我们叫<code>超出空间</code>更方便理解,说明可伸缩项占主轴空间总和大于容器占主轴的空间,即可伸缩项目<code>需要收缩才内适应</code>容器空间,如何收缩看每一项的<code>flex-shrink</code>属性。</p><h4 id="伸缩项扩展计算公式"><a href="#伸缩项扩展计算公式" class="headerlink" title="伸缩项扩展计算公式"></a>伸缩项扩展计算公式</h4><blockquote><p>伸缩项flex-grow权重值/各伸缩项flex-grow值总和 * 剩余空间</p></blockquote><h4 id="伸缩项收缩计算公式(注意了,这里很多人搞错了)"><a href="#伸缩项收缩计算公式(注意了,这里很多人搞错了)" class="headerlink" title="伸缩项收缩计算公式(注意了,这里很多人搞错了)"></a>伸缩项收缩计算公式(注意了,这里很多人搞错了)</h4><blockquote><p>(伸缩项flex-shrink权重值 <em> 该伸缩项flex-basis)/ (各伸缩项flex-shrink </em> 各伸缩项flex-basis 总和) * 超出空间</p></blockquote><p>注意,与flex-grow在扩展时简单地按比例分配不同,除考虑flex-shrink本身,也要考虑flex-basis。假设每一项flex-shrink都是默认值1,那其实就是按照每一项flex-basis的占比进行收缩。</p><h4 id="易忘点"><a href="#易忘点" class="headerlink" title="易忘点"></a>易忘点</h4><ul><li>align-self比align-items属性多了一个auto,其余一样。 align-self:auto(默认值),继承父元素的align-items。</li><li>flex属性是三个属性的简写:flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto; <ul><li>flex: 1 即 flex-grow:1; flex-shrink: 0; flex-basis: auto</li><li>该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)</li></ul></li><li><p>flex-basis: 计算剩余空间是根据该属性值计算的。默认值:auto,项目的本来大小。</p><ul><li>flex-basis:auto; 如果项目设置了width属性值,则flex-basis实际值等于width值。</li><li>flex-basis: length 优先级大于width/ height值</li></ul></li><li><p>flex-shrink 和 flex-grow的计算规则稍有不同,主要是避免被挤压没了</p></li></ul>]]></content>
<summary type="html">
<h3 id="flex"><a href="#flex" class="headerlink" title="flex"></a>flex</h3><h4 id="flex布局中基本概念"><a href="#flex布局中基本概念" class="headerlink" title="flex布局中基本概念"></a>flex布局中基本概念</h4><ul>
<li>两种元素类型:flex容器(flex container)、flex项目(flex item 容器成员)。</li>
<li>两个轴:主轴(main axis)、交叉轴(cross axis)。</li>
<li>占据的空间:项目占据的主轴空间(main size)、项目占据的交叉轴空间(cross size)</li>
<li><p>注意:设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。</p>
</summary>
<category term="布局" scheme="https://foreverwang.github.io/categories/%E5%B8%83%E5%B1%80/"/>
<category term="flex" scheme="https://foreverwang.github.io/tags/flex/"/>
</entry>
<entry>
<title>当我们学习es6的时候我们在学什么</title>
<link href="https://foreverwang.github.io/2018/04/21/%E5%BD%93%E6%88%91%E4%BB%AC%E5%AD%A6%E4%B9%A0es6%E7%9A%84%E6%97%B6%E5%80%99%E6%88%91%E4%BB%AC%E5%9C%A8%E5%AD%A6%E4%BB%80%E4%B9%88/"/>
<id>https://foreverwang.github.io/2018/04/21/%E5%BD%93%E6%88%91%E4%BB%AC%E5%AD%A6%E4%B9%A0es6%E7%9A%84%E6%97%B6%E5%80%99%E6%88%91%E4%BB%AC%E5%9C%A8%E5%AD%A6%E4%BB%80%E4%B9%88/</id>
<published>2018-04-21T08:21:50.000Z</published>
<updated>2020-06-21T11:08:05.116Z</updated>
<summary type="html">
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="javascript核心" scheme="https://foreverwang.github.io/tags/javascript%E6%A0%B8%E5%BF%83/"/>
<category term="es6" scheme="https://foreverwang.github.io/tags/es6/"/>
</entry>
<entry>
<title>javascript朝花夕拾之继承</title>
<link href="https://foreverwang.github.io/2018/04/05/javascript%E6%9C%9D%E8%8A%B1%E5%A4%95%E6%8B%BE%E4%B9%8B%E7%BB%A7%E6%89%BF/"/>
<id>https://foreverwang.github.io/2018/04/05/javascript%E6%9C%9D%E8%8A%B1%E5%A4%95%E6%8B%BE%E4%B9%8B%E7%BB%A7%E6%89%BF/</id>
<published>2018-04-05T14:16:08.000Z</published>
<updated>2020-06-21T11:08:05.115Z</updated>
<content type="html"><![CDATA[<p>从实现角度,在OO语言中的继承特性,有三种实现方案,包括基于类(class-based)、基于原型(prototype-based)和基于元类(metaclass-based)。</p><p>从形式上,许多OO语言都支持两种类型继承方式:接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。js 中函数没有签名,故js 中无法实现接口继承。js 中只支持实现继承,而其实现继承主要是依靠原型链来实现的。</p><h3 id="构造函数,原型对象,实例对象的关系"><a href="#构造函数,原型对象,实例对象的关系" class="headerlink" title="构造函数,原型对象,实例对象的关系"></a>构造函数,原型对象,实例对象的关系</h3><p>每一个构造函数都有一个<code>prototype</code>属性指向其原型对象;每一个原型对象都包含一个指向构造函数的指针(或者说属性)<code>constructor</code>;而实例对象都包含一个指向原型对象的内部指针(内部属性) <code>[[prototype]]</code>。</p><p>对象实例是无法直接通过实例属性来访问原型对象的,但是我们可以间接方式显式的访问其原型对象。 通过在实例上访问其constructor 属性可以引用到实例的构造函数,再访问构造函数的prototype属性访问原型对象。</p><a id="more"></a><p>代码示例:</p><pre><code>function A() {}var instance = new A; </code></pre><p>通过instance访问原型对象: instance.constructor.prototype 。<br>对象实例内部属性[[prototype]]对程序而言是不可见的,即在代码里不能直接访问,供引擎来访问。 但在浏览器实现中每个对象都有一个<code>__proto__</code> 属性,可以在代码层面直接访问原型对象。</p><h3 id="基于原型链的继承"><a href="#基于原型链的继承" class="headerlink" title="基于原型链的继承"></a>基于原型链的继承</h3><p>上面我们理解了原型对象和构造函数及实例对象的关系。 如果我们让原型对象等于另一个类型的实例。 此时原型对象就包含一个指向另一个原型对象的指针,相应的另一个原型对象也包含一个指向另一个构造函数的指针。假如另一个原型对象又是另一个类型的对象的实例,那么上述关系依然存在,如此层层递进,就构成了实例和原型的链条,这就是<code>原型链</code>的概念。</p><blockquote><p>实现原型链的基本模式,让一个原型对象等于一个对象的实例。</p></blockquote><p>代码如下:</p><pre><code class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">SuperType</span>(<span class="params"></span>) </span>{ <span class="keyword">this</span>.property = <span class="literal">true</span>;}SuperType.prototype.getSuperValue = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ <span class="keyword">return</span> <span class="keyword">this</span>.property; };<span class="function"><span class="keyword">function</span> <span class="title">SubType</span>(<span class="params"></span>) </span>{ <span class="keyword">this</span>.subProperty = <span class="literal">false</span>;}SubType.prototype = <span class="keyword">new</span> SuperType();SubType.prototype.getSubValue = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{ ruturn <span class="keyword">this</span>.subProperty;}<span class="keyword">var</span> instance = <span class="keyword">new</span> SubType();<span class="built_in">console</span>.log(instance.getSuperValue()); <span class="comment">//true</span></code></pre><p>引用关系图如下:</p><p><img src="https://ws1.sinaimg.cn/large/d16dcf79gy1fpxkbh9blaj20ve0eqmz9.jpg" alt="继承关系图"></p><h3 id="原型链的问题"><a href="#原型链的问题" class="headerlink" title="原型链的问题"></a>原型链的问题</h3><p>原型链虽然很强大,可以用它实现继承有两个明显的问题。</p><p>1.当原型对象中包含引用类型值时,会有问题。 由于原型对象的属性被所有实例共享,当一个实例去修改一个共享自原型上的引用类型的属性时,会影响到所有其他实例。示例代码如下:</p><pre><code class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">SuperType</span>(<span class="params"></span>) </span>{ <span class="keyword">this</span>.colors = [<span class="string">"red"</span>, <span class="string">"blue"</span>, <span class="string">"green"</span>];}<span class="function"><span class="keyword">function</span> <span class="title">SubType</span>(<span class="params"></span>) </span>{}SubType.prototype = <span class="keyword">new</span> SuperType();<span class="keyword">var</span> instance1 = <span class="keyword">new</span> SubType();instance1.colors.push(<span class="string">"black"</span>);<span class="keyword">var</span> instance2 = <span class="keyword">new</span> SubType(); <span class="built_in">console</span>.log(instance2.colors); <span class="comment">//"red,blue,green,black"</span></code></pre><p>由于color属性是继承自SubType的原型,是引用类型值,当instance1修改其color属性时,实际上是修改的SubType原型里的color属性,由于instance2也继承了原型的color属性(其没有实例属性color),所以instance2.color指向的color属性已经是被instance 修改过的了。</p><p>本来我们将属性放到构造函数里是为了其私有性考虑,可见这里的问题实质是属性的私有性被破坏了。</p><ol start="2"><li>创建子类实例时,不能向父类构造函数中传参。</li></ol><p>解决以上两个问题,我们可以通过 “类抄写”(或叫”借用构造函数”)的方式来实现。 (具体叫什么不重要,名字只是一个代号,方便我们交流,我们要做的就是知道他是什么)。</p><h3 id="借用构造函数(类抄写)"><a href="#借用构造函数(类抄写)" class="headerlink" title="借用构造函数(类抄写)"></a>借用构造函数(类抄写)</h3><p>这种方法具体做法是:在子类构造函数内部通过call或apply方法调用父类构造函数。这样就可以在实现属性继承的同时,既能传参,又能保证实例属性的私有行。</p><pre><code class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">SuperType</span>(<span class="params"></span>) </span>{ <span class="keyword">this</span>.colors = [<span class="string">"red"</span>, <span class="string">"blue"</span>, <span class="string">"green"</span>];}<span class="function"><span class="keyword">function</span> <span class="title">SubType</span>(<span class="params"></span>) </span>{ SuperType.call(<span class="keyword">this</span>); <span class="comment">//如果要传参可以用apply</span> <span class="comment">//SuperType.apply(this,arguments)</span>}<span class="keyword">var</span> instance1 = <span class="keyword">new</span> SubType();instance1.colors.push(<span class="string">"black"</span>);alert(instance1.colors); <span class="comment">//"red,blue,green,black"</span><span class="keyword">var</span> instance2 = <span class="keyword">new</span> SubType();alert(instance2.colors); <span class="comment">//"red,blue,green"</span></code></pre><p>在SubType子类里通过SuperType.call(this) 调用父类,当实例化子类时,就将父类实例上的实例方法都赋值给了子类实例。</p><p>类抄写也有其问题,方法都在构造函数中定义,函数无法复用,浪费内存。<br>同时,在父类原型的属性对子类而言也是不可见的。所以实际应用中也很少单独使用借用构造函数的技术。而更常见的是将原型链和借用构造函数结合到一块,从而发挥两者之长的一种继承模式,及所谓的<code>组合继承</code>。这里就不展开细说了。</p><h3 id="使用Object-create-实现原型继承"><a href="#使用Object-create-实现原型继承" class="headerlink" title="使用Object.create()实现原型继承"></a>使用Object.create()实现原型继承</h3><p>ES5中新增Object.create() 来实现原型式继承。他使得原型继承不必依赖于构造函数,不用创建特定的对象类型。<br>这里我们只需要关心Object.create() 只有一个参数时即可(第二个参数和继承无关)。他返回一个新的对象,新的对象的原型对象即传入的Object.create()第一个参数。</p><h3 id="原型继承的本质"><a href="#原型继承的本质" class="headerlink" title="原型继承的本质"></a>原型继承的本质</h3><p>原型其实也是一个对象实例。 原型的含义:如果构造函数有一个原型对象A,则有构造函数创建的实例都必然复制自A(先不考虑这里具体的复制实现问题)。</p><p>“原型也是对象实例” 这是一个最关键的性质。这使得很自然的理解原型对象也有其自己的原型对象,即原型对象的原型对象。这是原型继承和类继承体系在本质上的不同。 对于类继承体系,类不必是”对象”,因此也不具备对象的性质。 举例来说,“类”可以是一个内存块,也可能是一段描述文本,而不必是一个有对象特性(如可以调用方法或存取属性)的结构,(其实es6的class并不是通常意义上的class他还是对象)。</p><p>原型继承是一个典型的<code>以时间换空间</code>的解决方案。由于子类中直接读写一个成员而有无法存取到该成员时,将会回溯原型链以查找该成员的名字,因此直接结果是:继承层次中临近的成员访问更快,而试图访问一个不存在的成员时最耗时。</p><p>而现实的对象系统,我们其实更希望基类实现尽可能多的功能,希望通过较多的继承层次来使得类的粒度变小以便于控制。 从这里来看,访问更多的层次以及访问父类成员是复杂对象系统的基本特性。</p><p>而js 里更多层次的继承往往承意味着属性的存取要经过更多层次的查找,更多的cpu消耗。这跟js 创立初期其所运行的环境有关,空间占用是关键,时间消耗次要的多。而在现在硬件条件下的js运行环境这些问题已经不再明显,甚至时间消耗是在意的。</p><h3 id="扩展-默认的原型"><a href="#扩展-默认的原型" class="headerlink" title="扩展-默认的原型"></a>扩展-默认的原型</h3><p>这里借用一张网图:<br><img src="https://ws1.sinaimg.cn/large/d16dcf79ly1fpy2d5hfiqj20qy0z6tk1.jpg" alt="继承关系图"></p><p>理解上图有几个关键事实:</p><blockquote><p>原型对象也是一个实例对象<br><br>构造函数也是一个实例对象</p></blockquote><p>而实例对象都有自己的原型对象 ,通过<strong>proto</strong>属性访问(浏览器环境)。<br>构造函数的默认的原型对象是一个空的对象(empty objects),它是一个由Object创建的实例对象,所以其<strong> proto </strong> 指向Object.prototype。<br>Foo.prototype.<strong> proto </strong> === Objtct.prototype。</p><p>而构造函数又是谁创建的呢?所有的构造函数(包括内置对象,但除了Function它自身)都是由Function构造函数创建的,故构造函数也是个实例对象也有其原型,Foo.<strong> proto </strong> === Function.prototype。</p><p>同时Object也是Function 创建的,故Object.<strong> proto </strong> === Function.prototype。而Function.prototype 是一个Object创建的空的对象,故Function.prototype.<strong> proto </strong> === Object.prototype。</p><p>我们发现原型对象的源头是Object.prototype,那么Object.prototype 有自己的原型对象吗?有: Object.prototype.<strong> proto </strong> === null。</p><h3 id="新世界-ES6"><a href="#新世界-ES6" class="headerlink" title="新世界 - ES6"></a>新世界 - ES6</h3><p>在es6 之前,生成实例对象的方法是通过构造函数。而在es6不一样了,ES6 提供了更接近传统语言的class写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。通过extends关键字来实现继承。 这使得定义对象模板语法上更简洁了。然而它只不过是ES5的语法糖而已,javascript基于原型(prototype-based)继承的对象系统的实质是没有变的。 </p><p>ES6非语法糖层面的改变,那应该是其静态方法可以被子类继承。其他还有吗?有的话告诉我。</p><p>关于es6 class 详情看<a href="http://es6.ruanyifeng.com/#docs/class-extends" target="_blank" rel="noopener">这里</a>。</p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li>javascript 高级程序设计</li><li>javascript语言精髓与编程实践</li><li>ECMAScript 6 入门</li></ul>]]></content>
<summary type="html">
<p>从实现角度,在OO语言中的继承特性,有三种实现方案,包括基于类(class-based)、基于原型(prototype-based)和基于元类(metaclass-based)。</p>
<p>从形式上,许多OO语言都支持两种类型继承方式:接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。js 中函数没有签名,故js 中无法实现接口继承。js 中只支持实现继承,而其实现继承主要是依靠原型链来实现的。</p>
<h3 id="构造函数,原型对象,实例对象的关系"><a href="#构造函数,原型对象,实例对象的关系" class="headerlink" title="构造函数,原型对象,实例对象的关系"></a>构造函数,原型对象,实例对象的关系</h3><p>每一个构造函数都有一个<code>prototype</code>属性指向其原型对象;每一个原型对象都包含一个指向构造函数的指针(或者说属性)<code>constructor</code>;而实例对象都包含一个指向原型对象的内部指针(内部属性) <code>[[prototype]]</code>。</p>
<p>对象实例是无法直接通过实例属性来访问原型对象的,但是我们可以间接方式显式的访问其原型对象。 通过在实例上访问其constructor 属性可以引用到实例的构造函数,再访问构造函数的prototype属性访问原型对象。</p>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="javascript" scheme="https://foreverwang.github.io/tags/javascript/"/>
<category term="继承" scheme="https://foreverwang.github.io/tags/%E7%BB%A7%E6%89%BF/"/>
</entry>
<entry>
<title>测试travis 自动部署</title>
<link href="https://foreverwang.github.io/2018/03/25/%E6%B5%8B%E8%AF%95travis%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2/"/>
<id>https://foreverwang.github.io/2018/03/25/%E6%B5%8B%E8%AF%95travis%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2/</id>
<published>2018-03-25T08:21:50.000Z</published>
<updated>2020-06-21T11:08:05.117Z</updated>
<content type="html"><![CDATA[<h2 id="自动化部署"><a href="#自动化部署" class="headerlink" title="自动化部署"></a>自动化部署</h2><p>hexo 是一套基于nodejs的静态博客生成系统,这往往需要有一套本地的hexo环境,将你本地编写的markdown 文件 生成静态网站需要的文件。<br>这使得写博客依赖于这套环境,如果你换个电脑,要么重新装一遍环境,要么没法发布。</p><p>这里说的自动化部署最终效果是我在github上在线编写一片博文,提交后就会自动构建生成博客文件并上传到指定的git仓库,构建部署完成,博客就发布生效了。<br>可以做到只要有网络你就可以发布博客。而且以后你只需要关心写博客,不需要关心构建部署。</p><p>具体细节不赘述,原理就是利用了 travis 和 github 的webhook。 github 上提交时出发travis 的自动构建部署。 之所以选travis 是因为他免费。</p><a id="more"></a><h2 id="问题记录"><a href="#问题记录" class="headerlink" title="问题记录"></a>问题记录</h2><h3 id="travis-login-报错"><a href="#travis-login-报错" class="headerlink" title="travis login 报错"></a>travis login 报错</h3><ul><li><p>报错信息</p><pre><code>Last Exception An error occurred running `travis login`: TypeError: no implicit conversion of nil into String from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/json/common.rb:155:in `initialize' ...</code></pre><ul><li><p>解决方案: 升级ruby 版本至2.5</p><pre><code># 安装rvmcurl -L get.rvm.io | bash -s stablesource ~/.rvm/scripts/rvm</code></pre></li></ul></li></ul><h3 id="ssh-权限问题"><a href="#ssh-权限问题" class="headerlink" title="ssh 权限问题"></a>ssh 权限问题</h3><ul><li><p>报错信息</p><pre><code>Warning: Permanently added the RSA host key for IP address '192.30.253.113' to the list of known hosts.To github.com:foreverwang/foreverwang.github.io.git</code></pre></li></ul><p>大概是要报github 这个ip 地址加入到known hosts 文件,这个电脑在第一次访问时会自己添加,不知道怎么在travis的服务器上添加。其实到这一步travis 服务器已经能通过ssh方式连接我的github仓库了。但是优于这个问题的存在,导致在hexo deploy时 无法push到仓库。尝试解决未果。</p><ul><li><p>解决方案: 改用github token 方式连接 ok了</p><p> 具体配置信息看<a href="https://github.com/foreverwang/foreverwang.github.io/blob/hexo/.travis.yml" target="_blank" rel="noopener">这里</a></p></li></ul><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="http://kchen.cc/2016/11/12/hexo-instructions/" target="_blank" rel="noopener">http://kchen.cc/2016/11/12/hexo-instructions/</a></li><li><a href="https://blog.csdn.net/woblog/article/details/51319364" target="_blank" rel="noopener">https://blog.csdn.net/woblog/article/details/51319364</a></li></ul><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>是时候改用github actions了: <a href="https://hexo.fluid-dev.com/posts/actions-deploy/" target="_blank" rel="noopener">https://hexo.fluid-dev.com/posts/actions-deploy/</a></p>]]></content>
<summary type="html">
<h2 id="自动化部署"><a href="#自动化部署" class="headerlink" title="自动化部署"></a>自动化部署</h2><p>hexo 是一套基于nodejs的静态博客生成系统,这往往需要有一套本地的hexo环境,将你本地编写的markdown 文件 生成静态网站需要的文件。<br>这使得写博客依赖于这套环境,如果你换个电脑,要么重新装一遍环境,要么没法发布。</p>
<p>这里说的自动化部署最终效果是我在github上在线编写一片博文,提交后就会自动构建生成博客文件并上传到指定的git仓库,构建部署完成,博客就发布生效了。<br>可以做到只要有网络你就可以发布博客。而且以后你只需要关心写博客,不需要关心构建部署。</p>
<p>具体细节不赘述,原理就是利用了 travis 和 github 的webhook。 github 上提交时出发travis 的自动构建部署。 之所以选travis 是因为他免费。</p>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
</entry>
<entry>
<title>非对称加密(公钥加密)及其应用</title>
<link href="https://foreverwang.github.io/2018/02/23/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E5%8F%8A%E5%85%B6%E5%BA%94%E7%94%A8/"/>
<id>https://foreverwang.github.io/2018/02/23/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E5%8F%8A%E5%85%B6%E5%BA%94%E7%94%A8/</id>
<published>2018-02-23T04:20:48.000Z</published>
<updated>2020-06-21T11:08:05.119Z</updated>
<content type="html"><![CDATA[<h3 id="什么是非对称加密"><a href="#什么是非对称加密" class="headerlink" title="什么是非对称加密"></a>什么是非对称加密</h3><p><a href="https://zh.wikipedia.org/wiki/ %E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86" target="_blank" rel="noopener">维基百科</a>已经解释的非常清楚了(只看对公开密钥加密的解释这部分,引用如下)。</p><blockquote><p>公开密钥加密(英语:Public-key cryptography),也称为非对称加密(英语:asymmetric cryptography),是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密的时候,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另外一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥为私钥,必须由用户自行严格秘密保管,绝不通过任何途径向任何人提供,也不会透露给要通信的另一方,即使他被信任。</p></blockquote><p>维基上说的说的有点啰嗦,精简一下就是:</p><a id="more"></a><blockquote><p>加密解密通过一个秘钥对即公开密钥和私有密钥完成的。<br><br>用公钥加密明文得到的密文,只能通过对应的私钥解密得到明文。<br>(类似私钥加密明文得到密的文,只能通过对应的公钥解密得到明文)<br><br> 密钥对是数学相关的。</p></blockquote><p>在现实世界上可作比拟的例子是,一个传统保管箱,开门和关门都是使用同一把钥匙,这是对称加密;而一个公开的邮箱,投递口是任何人都可以寄信进去的,这可视为公钥;而只有邮箱主人拥有钥匙可以打开邮箱,这就视为私钥。这就是非对称加密。(以下说的公钥加密和对对称加密是一回事)</p><h3 id="与对称加密的比较"><a href="#与对称加密的比较" class="headerlink" title="与对称加密的比较"></a>与对称加密的比较</h3><p>对称加密的问题在于通信双方要事先交换密钥,而事先传递密钥的过程 存在密钥泄露的风险。密钥一旦泄露,数据就不安全了。<br>而非对称加密,密钥对的私钥是保密的不需要事先传递或公开的,所以安全性更高。</p><p>注意,看网上的博文很多人对这里的理解是不太透彻的: 认为对称加密的问题在于加解密用同样的密钥,所以导致对称加密不安全的。 </p><blockquote><p>加解密使用同样的密钥-这是对称加密的概念也是其形式,把对称加密安全性差归咎于此不太妥(要强扯也┑( ̄Д  ̄)┍)。</p></blockquote><p> 对称加密的安全性低,问题在于两点: </p><ul><li>由于加密解密用同样的密钥,其密钥需要通信双方事先交换(或单向传递),密钥交换的过程存在泄漏的风险。</li><li>由于密钥通信双方都知道,通信接收方可以泄漏发送方的密钥给其他人。 </li></ul><p>而以上两个问题非对称加密都解决了:</p><ul><li>加密解密需要一对密钥来完成,其中私钥是不公开也不需要事先传递的,所以泄漏的概率更小。 </li><li><p>公钥加密的信息只能通过其对应的私钥来解密,私钥是属于通信双方中某一方且保密的,而公钥是公开的,所以不存在泄漏对方密钥的风险。 </p><p>所以,在保证密钥不被泄漏的前提下对称加密和非对称加密是一样安全的。 但是,对称加密中,通信双方在事后有泄漏对方密钥的可能。 如果再加一个条件通信双方的发送者对每一个接收者使用不同的密钥,这样就能保证对称加密和非对称加密一样安全了。 但是这两点都比较难保证:1.传递密钥过程的安全性依赖传递环境。2.需要每个人都要有很多很多密钥,不同的接收方用不同的密钥,密钥难以保管。</p></li></ul><p>所以这个时候非对称加密华丽登场了。<br>下面我们通过对具体的某一种非对称加密算法的学习来理解非对称加密的数学原理。这里我们选用应用最广泛的非对称加密算法之一:RSA加密算法。</p><h3 id="RSA算法"><a href="#RSA算法" class="headerlink" title="RSA算法"></a>RSA算法</h3><p>RSA算法的数学基础是:欧拉定理,此处不做解释。公钥加密算法根据其所依据的数学难题一般分为三类:大整数因式分解问题类、离散对数问题类,椭圆曲线类。RSA属于大数分解类,也是最好理解的。</p><p>下面直接介绍RSA算法的基础步骤;</p><h4 id="1-生成公钥(PK)和私钥-SK"><a href="#1-生成公钥(PK)和私钥-SK" class="headerlink" title="1. 生成公钥(PK)和私钥(SK)"></a>1. 生成公钥(PK)和私钥(SK)</h4><ul><li>(1) 随意选两个大的素数p和q,p不等于q</li><li>(2) 将p、q两个素数相乘得到n,即n=pq</li><li>(3) 计算n的欧拉函数φ(n):根据公式 φ(n)=(p-1)(q-1)</li><li>(4) 选择一个整数e,作为密钥,使得e和φ(n)互质,且e < φ(n)</li><li><p>(5) 计算e对于φ(n)的模反元素d: 根据公式 ed mod φ(n) = 1 ,计算出d的值,作为另一个密钥。<br>所谓”模反元素”就是指有一个整数d,可以使得ed被φ(n)除的余数为1。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ed ≡ 1 (mod φ(n)) ==> </span><br><span class="line">ed - 1 = kφ(n) //求解二元一次方程 k 从1带入试就行了</span><br></pre></td></tr></table></figure></li><li><p>(6) 通过以上步骤计算出 n 、e 、d 3个数据,其中(n、e)作为公钥,(n、d)作为私钥(也可以互换)</p></li><li>(7) 生成公钥和私钥后就可以公开公钥了。</li></ul><h4 id="2-用公钥加密信息"><a href="#2-用公钥加密信息" class="headerlink" title="2. 用公钥加密信息"></a>2. 用公钥加密信息</h4><p>信息发送方收到接收方公钥PK后,就可以用PK对数据加密。加密步骤如下。 其中明文为M,加密后的密文为C,公钥(n,e)</p><blockquote><p>加密: M^e mod n = C</p></blockquote><h4 id="3-用私钥解密信息"><a href="#3-用私钥解密信息" class="headerlink" title="3. 用私钥解密信息"></a>3. 用私钥解密信息</h4><p>接收方持有私钥(N,D),在接收到密文C后,通过私钥对其解密,得到明文M:</p><blockquote><p>解密: C^d mod n = M </p></blockquote><h3 id="RSA-安全性分析"><a href="#RSA-安全性分析" class="headerlink" title="RSA 安全性分析"></a>RSA 安全性分析</h3><p>通常到这里我们会有个疑问? 公钥和私钥存在一定的数学关系, 公钥公开,那么有没有可能通过公钥推导出私钥呢? 如果能那不就不安全了吗。 答案是:能,但是很难。<br>问题变成 能否在已知 n和e的前提下,推导出d ?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ed ≡ 1 (mod φ(n)) //等价于 ed mod φ(n) = 1 </span><br><span class="line">φ(n)=(p-1)(q-1)</span><br><span class="line">n=pq</span><br></pre></td></tr></table></figure><p>结论:如果n可以被因数分解,d就可以算出。<br>但是,大整数的因式分解是很困难的,目前除了暴力破解,还没有更好的有效方式。</p><p>所以整数n的因式分解难度决定了RSA算法的可靠度。</p><blockquote><p> RSA可靠性就是建立在分解两个大素数乘积的十分困难上。</p></blockquote><p>这个难度是个什么概念呢?<a href="http://blog.csdn.net/woyaokaoyan/article/details/5169098" target="_blank" rel="noopener">引用</a></p><blockquote><p>RSA算法的破解与密钥的长度有关,最常见的破解方法是因式分解,如果密钥的长度小于等于256位,一台较快的电脑可以在几个小时内成功分解其因子。位数越高因式分解所需时间也越长。1999年,一台Cray超级电脑用了5个月时间分解了512位长的密钥。而目前典型密钥长度是1028位。在512位RSA算法破解10年之后,一群研究人员报告(PDF)他们因式分解了768位RSA算法。他们是在2009年12月9日分解了768位、232数位数字的RSA-768密钥。分解一个768位RSA密钥所需时间是512位的数千倍,而1024位所需时间则是768位的一千多倍,因此在短时间内1024位仍然是安全的。研究人员表示1024位密钥预计也将会在10年内攻破,因此在未来三到四年内应逐步淘汰1024位RSA密钥。</p></blockquote><p>思考一下大整数n 因式分解为什么难?有多难?</p><h3 id="RSA-算法实例"><a href="#RSA-算法实例" class="headerlink" title="RSA 算法实例"></a>RSA 算法实例</h3><h4 id="生成公钥私钥"><a href="#生成公钥私钥" class="headerlink" title="生成公钥私钥"></a>生成公钥私钥</h4><ul><li>随机取大质数p=11,q=13,那n=11*13=143,φ(N)=(p-1)(q-1) = 120 </li><li>取一个e=7,由 ed mod φ(n) = 1 计算出d=103。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">公钥(n,e) 即 (143, 7)</span><br><span class="line">私钥 (n,d) 即 (143, 103)</span><br></pre></td></tr></table></figure></li></ul><h4 id="用公钥加密"><a href="#用公钥加密" class="headerlink" title="用公钥加密"></a>用公钥加密</h4><p>由于手工计算,为了使计算量小一点,我们将上面的公钥和私钥交换: 即公钥 (143, 103),私钥(143, 7)。取明文 m =2,加密过程如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">密文 c = m^e mod n</span><br><span class="line"> = 2^103 mod 143 </span><br><span class="line"> = 10141204801825835211973625643008 mod 143 </span><br><span class="line"> = 63</span><br></pre></td></tr></table></figure><h4 id="用私钥解密"><a href="#用私钥解密" class="headerlink" title="用私钥解密"></a>用私钥解密</h4><p>收到密文c = 63,通过 私钥(143,7)进行解密,解密过程如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">明文 m = c^d mod n </span><br><span class="line"> = 63^7 mod 143</span><br><span class="line"> = 3938980639167 mod 143 </span><br><span class="line"> = 2</span><br></pre></td></tr></table></figure><p>至此,我们用RSA算法进行了一次加密解密,其实很简单,只是计算量可能会比较大。 </p><p>常见的公钥加密算法除了RSA。还有ElGamal、背包算法、椭圆曲线加密算法(英语:Elliptic Curve Cryptography, ECC)等。下面我们说下非对称加密的应用。</p><h3 id="非对称加密的应用"><a href="#非对称加密的应用" class="headerlink" title="非对称加密的应用"></a>非对称加密的应用</h3><p>先插入一个概念: <code>信息系统安全的基本属性</code>。</p><blockquote><p>信息系统安全的基本属性: 保密性、完整性、可用性、不可抵赖性。</p></blockquote><ul><li>保密性:是应用系统的信息不被泄露给非授权的用户。常用保密技术:最小授权原则,信息加密等。</li><li>完整性:完整性是信息未经授权不能进行改变的特性。完整性是一种面向信息的安全性。</li><li>可用性: 可用性是应用系统面向用户的安全性能,不做赘述。</li><li>不可抵赖性:不可抵赖性也称作不可否认性,在应用系统的信息交互过程中,确信参与者的真实同一性。即所有参与者都不可能否认或抵赖曾经完成的操作和承诺。利用信息源证据可以防止发信方不真实地否认已发送信息,利用递交接收证据可以防止收信方事后否认已经接收的信息。</li></ul><p>加密技术也是用来加强信息安全的,接下来说的几个非对称加密实际应用也是如此。<br>回归正题,接下来说非对称加密最广泛的应用之一:数字签名。</p><h4 id="数字签名"><a href="#数字签名" class="headerlink" title="数字签名"></a>数字签名</h4><blockquote><p>数字签名在ISO7498—2标准中定义为:“附加在数据单元上的一些数据,或是对数据单元所作的密码变换,这种数据和变换允许数据单元的接收者用以确认数据单元来源和数据单元的完整性,并保护数据,防止被人(例如接收者)进行伪造”</p></blockquote><p>首先搞清楚数字签名解决的是什么问题? 然后看看什么是数字签名?最后分析下数字签名为什么能解决这些问题?</p><h5 id="数字签名解决什么问题?"><a href="#数字签名解决什么问题?" class="headerlink" title="数字签名解决什么问题?"></a>数字签名解决什么问题?</h5><p>纸质书信或文件根据亲笔签名或印章来证明其真实性,及防抵赖。对应上述信息安全的 完整性和不可抵赖性。 而数字签名则可以看做是对在网络中传输的报文的签名,作用自然也是为了保证信息的完整性及不可抵赖性。 </p><p>那下面我们看下数字签名是如何做到证明信息的完整性及防抵赖的?</p><h5 id="数字签名的实现方法?"><a href="#数字签名的实现方法?" class="headerlink" title="数字签名的实现方法?"></a>数字签名的实现方法?</h5><p>建立在公钥密码技术上的数字签名方法有很多,有RSA签名、DSA签名和椭圆曲线数字签名算法(ECDSA)等等。下面对RSA签名进行详细分析。</p><p>这里需要理解<a href="https://baike.baidu.com/item/%E5%93%88%E5%B8%8C%E5%80%BC" target="_blank" rel="noopener">哈希-hash</a>的概念。简单说hash 就是计算机可以对任意内容计算出一个固定长度的值,且不会重复。</p><p>直接看图:</p><p><img src="https://ws1.sinaimg.cn/large/d16dcf79ly1fobiaii5v2j22fk12812h.jpg" alt="数字签名"></p><p> (1)发送方采用某种摘要算法从报文中生成一个128位的散列值(称为报文摘要);</p><p> (2)发送方用RSA算法和自己的私钥对这个散列值进行加密,产生一个摘要密文,这就是发送方的数字签名;</p><p> (3)将这个加密后的数字签名作为报文的附件和报文一起发送给接收方:</p><p> (4)接收方从接收到的原始报文中采用相同的摘要算法计算出128位的散列值;</p><p> (5)报文的接收方用RSA算法和发送方的公钥对报文附加的数字签名进行解密;</p><p> (6)如果两个散列值相同,那么接收方就能确认报文是由发送方签名的。</p><p>最常用的摘要算法叫做MD5(Message Digest 5)。MD5采用单向Hash函数将任意长度的“字节串”变换成一个128位的散列值,并且它是一个不可逆的字符串变换算法。</p><h5 id="数字签名为什么能保证信息完整性及不可抵赖性?"><a href="#数字签名为什么能保证信息完整性及不可抵赖性?" class="headerlink" title="数字签名为什么能保证信息完整性及不可抵赖性?"></a>数字签名为什么能保证信息完整性及不可抵赖性?</h5><h6 id="信息的完整性"><a href="#信息的完整性" class="headerlink" title="信息的完整性"></a>信息的完整性</h6><p>如果报文在网络传输过程中被修改,接收方收到此报文后,使用相同的摘要算法将计算出不同的报文摘要,这就保证了接收方可以判断报文自签名后到收到为止,是否被修改过。</p><h6 id="信息的不可抵赖性"><a href="#信息的不可抵赖性" class="headerlink" title="信息的不可抵赖性"></a>信息的不可抵赖性</h6><p>如果发送方A想让接收方误认为此报文是由发送方B签名发送的,由于发送方A不知道发送方B的私钥,所以接收方用发送方B的公钥对发送方A加密的报文摘要进行解密时,也将得出不同的报文摘要,这就保证了接收方可以判断报文是否是由指定的签名者发送。同时也可以看出,当两个散列值相同时,发送方B无法否认这个报文是他签名发送的。</p><h5 id="关于数字签名的一点问题"><a href="#关于数字签名的一点问题" class="headerlink" title="关于数字签名的一点问题"></a>关于数字签名的一点问题</h5><p>关于数字签名就说完了。但有个疑问:传输过程中原文没有被加密吗?是的。上面是无保密机制的数字签名。具有保密机制的RSA签名机制自己看<a href="http://www.elecfans.com/baike/tongxingjishu/chungshuwang/20110710205779.html" target="_blank" rel="noopener">这边文章</a>吧 。</p><h4 id="数字证书"><a href="#数字证书" class="headerlink" title="数字证书"></a>数字证书</h4><p>关于上面数字签名有一个问题:信息接收方如何确认拿到的公钥就是期望的发送方的公钥? </p><p>举个例子:</p><p>小红与小绿写信。 小绿写完信后 用自己的私钥对信件摘要生成 ‘数字签名’ 连同信件一块发给小红。 小红收到信件后用小绿的公钥解密信件里的数字签名得到信件摘要。 这个过程没有问题。</p><p>但是有一天,第三者-小强出现了。小强偷偷将小红电脑里的小绿的公钥换成他自己的了,但此时小红还以为那是小绿的公钥。 因此,小强就可以冒充小绿,用自己的私钥对信件摘要进行 ‘数字签名’,发给小红。小红用小强掉包后的公钥对数字签名解密,还以为对方是小绿。</p><p>后来小红感觉不对劲,他发现自己无法确定公钥是否真的是小绿的。于是她想了一个办法,让小绿找“证书中心”(certificate authority,简称CA) 为自己的公钥做认证。认证机构用自己的私钥对小绿的公钥和一些相关信息一起加密,生成‘数字证书’。</p><p>小绿拿到‘数字证书’后,再给苏珊写信,只要在签名的同时,再附上数字证书就行了。<br>小红收信后,用CA的公钥解开数字证书,就可以拿到小绿的公钥了<br>,然后就能验证数字签名是否是小绿的。</p><p>这里我们先大概知道CA是一个第三方机构,用来对个人身份认证的,而认证的形式就是‘数字证书’。<br>这个例子中忽略了一个关键细节,如何确定证书的真实性呢? 接下来我们细细介绍。</p><h4 id="https-实例分析"><a href="#https-实例分析" class="headerlink" title="https 实例分析"></a>https 实例分析</h4><p>https 是数字证书的应用之一。我们通过对https的从简介绍来理解数字证书。 </p><h5 id="CA"><a href="#CA" class="headerlink" title="CA"></a>CA</h5><p>CA(certificate authority)证书授权中心。 受信任的第三方机构。 CA为每个使用公开密钥的用户发放一个数字证书。证书生成的过程可以认为就是CA用自己的私钥对公钥持有方的公钥及一些相关信息进行数字签名。</p><blockquote><p>数字证书是一个经证书授权中心(CA)数字签名的包含公钥拥有者信息以及公钥的文件。</p></blockquote><p>数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公钥。</p><h5 id="根证书"><a href="#根证书" class="headerlink" title="根证书"></a>根证书</h5><p>而CA的公钥叫做根证书。 这些根证书是预先被安装在浏览器里的,用来解密服务器的证书的。</p><h5 id="https-访问"><a href="#https-访问" class="headerlink" title="https 访问"></a>https 访问</h5><p>通过https建立了一个安全 Web 事务之后,现代的浏览器都会自动获取所连接服务器的数字证书。如果服务器没有证书,安全连接就会失败。<br>浏览器预先安装了一些CA的根证书。浏览器用对应的根证书去解密网站服务器的数字证书获取到网站服务器的公钥。然后浏览器发送一个随机字符串给服务器,服务器用自己的私钥加密这个随机字符串后返回给浏览器,浏览器通过获得的公钥解密后比较这个随机字符串是否和发出去的一样。 如果一样,则说明服务器的公钥和证书里的公钥一致,则证明服务器就是服务器。</p><p>当然真实的https过程 比这个复杂很多,这里只是为了方便理解证书在非对称加密应用中的作用,忽略了很多技术细节。</p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>静静的学习完了,我们再来说下时下最热的加密币。加密币里的交易安全基础就是非对称加密算法。就以币王BTC(比特币)来说。</p><blockquote><p>比特币钱包: 是用来存放私钥和公钥的,而不是存放币的。<br> <br>比特币地址: 钱包公钥的哈希值(更短,方便保存和传播),用来收币的。<br> <br>比特币使用了基于secp256k1椭圆曲线加密算法(非对称加密算法的一种)。<br> <br>一笔交易实质就是一个地址的一定数量的比特币转移到另一个地址。而交易过程安全则是由其非对称加密算法保障。</p></blockquote><p>最最后总结下本文:</p><ul><li>什么是非对称加密</li><li>RSA算法的原理和用法</li><li>什么是数字签名</li><li>什么是数字证书及数字证书的作用</li><li>又要上班了。</li></ul><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p><a href="http://www.elecfans.com/baike/tongxingjishu/chungshuwang/20110710205779.html" target="_blank" rel="noopener">数字签名技术原理介绍</a></p>]]></content>
<summary type="html">
<h3 id="什么是非对称加密"><a href="#什么是非对称加密" class="headerlink" title="什么是非对称加密"></a>什么是非对称加密</h3><p><a href="https://zh.wikipedia.org/wiki/ %E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86" target="_blank" rel="noopener">维基百科</a>已经解释的非常清楚了(只看对公开密钥加密的解释这部分,引用如下)。</p>
<blockquote>
<p>公开密钥加密(英语:Public-key cryptography),也称为非对称加密(英语:asymmetric cryptography),是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密的时候,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另外一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥为私钥,必须由用户自行严格秘密保管,绝不通过任何途径向任何人提供,也不会透露给要通信的另一方,即使他被信任。</p>
</blockquote>
<p>维基上说的说的有点啰嗦,精简一下就是:</p>
</summary>
<category term="RSA" scheme="https://foreverwang.github.io/tags/RSA/"/>
<category term="非对称加密" scheme="https://foreverwang.github.io/tags/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
<category term="数字签名" scheme="https://foreverwang.github.io/tags/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D/"/>
</entry>
<entry>
<title>理解redux</title>
<link href="https://foreverwang.github.io/2017/12/06/%E7%90%86%E8%A7%A3redux/"/>
<id>https://foreverwang.github.io/2017/12/06/%E7%90%86%E8%A7%A3redux/</id>
<published>2017-12-06T07:38:51.000Z</published>
<updated>2020-06-21T11:08:05.118Z</updated>
<content type="html"><![CDATA[<h3 id="redux简单介绍"><a href="#redux简单介绍" class="headerlink" title="redux简单介绍"></a>redux简单介绍</h3><p>前面<a href>理解了flux</a>,本篇来理解redux。<br>redux的官方介绍 </p><blockquote><p>Redux is a predictable state container for JavaScript apps.</p></blockquote><p>翻译一下:redux 是javascript 应用状态容器,他提供可预测化的状态管理。<br>师出flux,作用和flux一样,作为应用的状态管理层。其核心思想也是单向数据流。</p><p>我们先看一个redux 最简单的原生使用实例;然后再胡乱解读一通redux概念;最后通过源码来看下其实现原理。</p><a id="more"></a><h3 id="redux如何使用-代码示例"><a href="#redux如何使用-代码示例" class="headerlink" title="redux如何使用-代码示例"></a>redux如何使用-代码示例</h3><p><a href="https://github.com/reactjs/redux/blob/master/examples/counter-vanilla/index.html" target="_blank" rel="noopener">Counter Vanilla</a> 这是redux 官方一个原生代码使用redux的实例,非常好懂。 例子是一个计数器,点击+号 完成加一操作,点击 -号,完成减一操作。</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><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></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">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Redux basic example<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcss.com/redux/3.7.2/redux.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span></span><br><span class="line"> Clicked: <span class="tag"><<span class="name">span</span> <span class="attr">id</span>=<span class="string">"value"</span>></span>0<span class="tag"></<span class="name">span</span>></span> times</span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"increment"</span>></span>+<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">"decrement"</span>></span>-<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">"incrementIfOdd"</span>></span>Increment if odd<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">"incrementAsync"</span>></span>Increment async<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <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">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="comment">//counter函数是一个reducer,现在只需要知道reducer就是操作应用数据的方法</span></span></span><br><span class="line"><span class="javascript"> <span class="function"><span class="keyword">function</span> <span class="title">counter</span>(<span class="params">state, action</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (<span class="keyword">typeof</span> state === <span class="string">'undefined'</span>) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="number">0</span></span></span><br><span class="line"> }</span><br><span class="line"><span class="javascript"> <span class="keyword">switch</span> (action.type) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">case</span> <span class="string">'INCREMENT'</span>:</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> state + <span class="number">1</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">case</span> <span class="string">'DECREMENT'</span>:</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> state - <span class="number">1</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">default</span>:</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> state</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="javascript"> <span class="comment">//实例化一个 store ,store里可以持久化存储应用的数据 state</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">//并且对外暴露几个方法</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">//其中 getState()用来获取应用的数据</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">//其中 subscribe()用来添加订阅者,供数据发生变化时通知</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">//其中 dispatch(action) 触发一个动作用来改变数据 </span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> store = Redux.createStore(counter)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> valueEl = <span class="built_in">document</span>.getElementById(<span class="string">'value'</span>)</span></span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//render函数 将数据(state)呈现到view</span></span></span><br><span class="line"><span class="javascript"> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"> valueEl.innerHTML = store.getState().toString()</span><br><span class="line"> }</span><br><span class="line"> render()</span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//将render 订阅store,数据变了的时候view重新渲染</span></span></span><br><span class="line"> store.subscribe(render)</span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//通过dispatch触发一个操作 来修改数据</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'increment'</span>)</span></span><br><span class="line"><span class="javascript"> .addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> store.dispatch({ <span class="attr">type</span>: <span class="string">'INCREMENT'</span> })</span></span><br><span class="line"> })</span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'decrement'</span>)</span></span><br><span class="line"><span class="javascript"> .addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> store.dispatch({ <span class="attr">type</span>: <span class="string">'DECREMENT'</span> })</span></span><br><span class="line"> })</span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'incrementIfOdd'</span>)</span></span><br><span class="line"><span class="javascript"> .addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"> if (store.getState() % 2 !== 0) {</span><br><span class="line"><span class="javascript"> store.dispatch({ <span class="attr">type</span>: <span class="string">'INCREMENT'</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="string">'incrementAsync'</span>)</span></span><br><span class="line"><span class="javascript"> .addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> setTimeout(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> store.dispatch({ <span class="attr">type</span>: <span class="string">'INCREMENT'</span> })</span></span><br><span class="line"> }, 1000)</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">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>这个例子很简单。 </p><ul><li>数据存储在store;</li><li>view(render函数)订阅store,store变化的时候会通知view 进行刷新;</li><li>通过store.dispatch 来修改数据</li></ul><p>我们的应用无非就是这么几部分:</p><ul><li>用户界面(呈现数据);</li><li>应用数据;</li><li>操作数据和响应用户操作的方法。 </li></ul><p>这些即对应MVC架构的view、model、controller。<br>在flux、redux里,也有这些角色,那他们和MVC架构有何不同呢?那就是 flux、redux强制单向数据流来使得数据可预测。 下面我们来看redux的概念和结构。</p><h3 id="redux-结构"><a href="#redux-结构" class="headerlink" title="redux 结构"></a>redux 结构</h3><p>上面这个例子中,状态变化的数据流向是下边这样的:</p><blockquote></blockquote><pre><code> dispatchactions --> store | reducers --> new state --> view | | <-----------------------------------------------</code></pre><p>首先创通过createStore(counter)建了一个store。<br>点击加号的过程:触发一个加一的action: store.dispatch({ type: ‘INCREMENT’ }),dispatch 会把这个action分发到reducer(这里是counter)里。reducer 接收当前的应用状态state,返回更改后的state。 到这里数据就完成了一次修改。 由于我们的view 事先订阅了store(store.subscribe(render)),此时store 会通知view state 变了,view 刷新(调用render方法)。</p><h4 id="action"><a href="#action" class="headerlink" title="action"></a>action</h4><h5 id="action概念"><a href="#action概念" class="headerlink" title="action概念"></a>action概念</h5><p>和 flux里的概念一样,用来描述发生了什么。<br>是一个普通对象,包含一个type属性来描述这个动作, 和其他数据属性。<br>比如 { type: ‘INCREMENT’, num: 1},描述一个增加的动作,增加值为1。</p><h5 id="action-扩展"><a href="#action-扩展" class="headerlink" title="action 扩展"></a>action 扩展</h5><ul><li><p>createAction</p><p> 也和flux 里一样,是一个生成action的函数,输入action中变化的部分作为参数,输出一个action对象.因为同样的action 在应用中可能多次用到,通过createAction函数可以减少重复代码,提高代码复用。</p></li><li><p>bindActionCreators </p><p> redux里还提供了bindActionCreators。action只是描述一个行为(好比一个事件event),他本身是不会产生任何影响的,想要触发一个action 让这个描述变成现实,是通过dispatch 方法来触发的(dispatch一个action 好比触发一个event)。而bindActionCreators 就是对dispatch 行为的一个封装,是的业务方触发一个action时可以像普通函数一样调用,而不感知dispatch 的存在。更具体的下面解读源码再说。</p></li></ul><h4 id="reducer"><a href="#reducer" class="headerlink" title="reducer"></a>reducer</h4><p>reducer 是纯函数。<br>上面我们说了让一个action产生影响是通过dispatch(action)来触发的。 而这里的dispatch 只是触发而已,具体干活的就是reducer,dispatch内部调度reducer。<br>reducer是一个函数,接收当前state和action 返回新的state。</p><p>reducer 和 dispatch 共同充当了flux里的dispatcher(registerCallback,dispatch)角色。</p><h5 id="reducer扩展"><a href="#reducer扩展" class="headerlink" title="reducer扩展"></a>reducer扩展</h5><ul><li>combineReducers 方法<br>实际应用中有不止一个reducer。redux里提供了一个combineReducers()方法来组织这些reducer。具体下边源码分析再说。</li></ul><h4 id="store"><a href="#store" class="headerlink" title="store"></a>store</h4><p>上面我们知道了,我们使用action 来描述发生了什么,使用reducer来根据action更新state。</p><p>而store 就是把action 和reducer结合起来的对象,并持有state。store负责:</p><ul><li>维持应用的state,并提供getState()方法获取state</li><li>提供 dispatch(action) 方法更新 state;</li><li>提供 subscribe(listener) / unsubscribe(listener) 来注册和解绑监听者。 </li></ul><p>Redux 应用只有一个单一的 store,当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。</p><h3 id="redux-源码解读-基于redux3-7"><a href="#redux-源码解读-基于redux3-7" class="headerlink" title="redux 源码解读(基于redux3.7)"></a>redux 源码解读(基于redux3.7)</h3><h4 id="先看下代码结构"><a href="#先看下代码结构" class="headerlink" title="先看下代码结构"></a>先看下代码结构</h4><p>redux 对外暴露五个方法,分别对应五个同名js文件:<br><figure class="highlight javascript"><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">createStore.js <span class="comment">//保存应用状态 定义内部方法,输出对外暴露的方法</span></span><br><span class="line">combineReducers.js <span class="comment">//将多个reducer 合并成一个</span></span><br><span class="line">bindActionCreators.js <span class="comment">//把actionCreator 包了一层dispatch,使用时可以像普通函数一样调用actionCreator</span></span><br><span class="line">applyMiddleware.js <span class="comment">//组合串联middleware</span></span><br><span class="line">compose.js <span class="comment">//工具方法,将middleware中间件方法组合成一个调用链</span></span><br></pre></td></tr></table></figure></p><h4 id="createStore-js"><a href="#createStore-js" class="headerlink" title="createStore.js"></a>createStore.js</h4><p>这是redux 最核心概念的实现部分。createStore方法的作用是创建一个store,并维持state。下面我们就通过源码看下store 是如何维持state的。</p><p>createStore方法接受三个参数:reducer [, preloadedState, enhancer],<br>返回一个store对象,包含4个方法:</p><ul><li>dispatch //触发action</li><li>subscribe// 添加监听者</li><li>getState //获取当前state</li><li>replaceReducer </li><li>observable</li></ul><p>以下源码为方便阅读,去掉了原注释,和异常抛错信息。注释信息为解读信息。</p><figure class="highlight javascript"><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><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> $$observable <span class="keyword">from</span> <span class="string">'symbol-observable'</span></span><br><span class="line"><span class="keyword">import</span> ActionTypes <span class="keyword">from</span> <span class="string">'./utils/actionTypes'</span></span><br><span class="line"><span class="keyword">import</span> isPlainObject <span class="keyword">from</span> <span class="string">'./utils/isPlainObject'</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * [createStore description] 通过createStore方法创建一个store,store来维护和持有应用的state</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{Function}</span> </span>reducer 函数 接收当前state和action为参数返回新的state</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{any}</span> </span>preloadedState 默认的初始化state,如果你的reducer是通过combineReducers生成的一个顶层reducer,</span></span><br><span class="line"><span class="comment"> * 那初始state的key值和combineReducers的key是一一对应的</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">{Function}</span> </span>enhancer store增强器 通常添加中间件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return <span class="type">{Store}</span> </span>store,允许你读取state,dispatch actions以及注册订阅者</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">createStore</span>(<span class="params">reducer, preloadedState, enhancer</span>) </span>{</span><br><span class="line"> <span class="comment">//允许使用方灵活传参:可以不传默认state 直接传 enhancer参数</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> preloadedState === <span class="string">'function'</span> && <span class="keyword">typeof</span> enhancer === <span class="string">'undefined'</span>) {</span><br><span class="line"> enhancer = preloadedState</span><br><span class="line"> preloadedState = <span class="literal">undefined</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> enhancer !== <span class="string">'undefined'</span>) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> enhancer !== <span class="string">'function'</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">'Expected the enhancer to be a function.'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//这行代码到 applyMiddleWare时说明</span></span><br><span class="line"> <span class="keyword">return</span> enhancer(createStore)(reducer, preloadedState)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> reducer !== <span class="string">'function'</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">'Expected the reducer to be a function.'</span>)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//这里是一些函数内的变量,由于闭包特性,这些变量在调用createStore函数后不会被销毁(因为导出的对象方法里面有这些变量的引用)。</span></span><br><span class="line"> <span class="comment">//这里指的注意的一点是有两个listeners队列:currentListeners 和 nextListeners;这一点有点厉害了,下面介绍</span></span><br><span class="line"> <span class="keyword">let</span> currentReducer = reducer </span><br><span class="line"> <span class="keyword">let</span> currentState = preloadedState</span><br><span class="line"> <span class="keyword">let</span> currentListeners = []</span><br><span class="line"> <span class="keyword">let</span> nextListeners = currentListeners</span><br><span class="line"> <span class="keyword">let</span> isDispatching = <span class="literal">false</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//nextListeners是currentListeners的拷贝,我们修改(subscribe)都是对nextListeners的修改</span></span><br><span class="line"> <span class="comment">//在flush listeners 之前 nextListeners 同步 currentListeners</span></span><br><span class="line"> <span class="comment">//这么做可以保证 每次dispatch listener 过程中 subscribe/unsubscribe listerens不会影响当前dispatch 队列,改变值发生在nextListeners,下一次dispatch时生效。 </span></span><br><span class="line"> <span class="comment">//TODO:我只知道这里这么做有点屌,具体这么做有啥用,我还得研究下</span></span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">ensureCanMutateNextListeners</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">//nextListeners === currentListener 说明listeners已经被flush; </span></span><br><span class="line"> <span class="comment">//nextListeners !== currentListener 说明subscribe(listener)后 还未被dispatch</span></span><br><span class="line"> <span class="keyword">if</span> (nextListeners === currentListeners) {</span><br><span class="line"> nextListeners = currentListeners.slice()</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"> * 读取state </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns <span class="type">{any}</span> </span>返回应用的当前state树</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">getState</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (isDispatching) {</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">return</span> currentState</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">listener</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> listener !== <span class="string">'function'</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">'Expected listener to be a function.'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isDispatching) {</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">let</span> isSubscribed = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"> ensureCanMutateNextListeners()</span><br><span class="line"> nextListeners.push(listener)</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="title">unsubscribe</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!isSubscribed) {</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (isDispatching) {</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"> isSubscribed = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"> ensureCanMutateNextListeners()</span><br><span class="line"> <span class="keyword">const</span> index = nextListeners.indexOf(listener)</span><br><span class="line"> nextListeners.splice(index, <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">//唯一改变store里数据(state)的方式就是调用store.dispatch</span></span><br><span class="line"> <span class="comment">//dispatch 做了两件事情:1调用 reducer 更改数据 2 flush 所有listeners</span></span><br><span class="line"> <span class="comment">//这里和flux有点不同, flux里 观察数据的变化,以及数据变化后的通知,这些需要你自己去实现 这一环节的观察者模式。</span></span><br><span class="line"> <span class="comment">//redux自身实现了这一环节的观察者模式,提供了 store.subscribe方法添加listeners,dispatch的时候会 flush这些listeners </span></span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">dispatch</span>(<span class="params">action</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!isPlainObject(action)) {</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">if</span> (<span class="keyword">typeof</span> action.type === <span class="string">'undefined'</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="comment">// reducer内部不允许再次调用dispatch,否则抛出异常 防止死循环?</span></span><br><span class="line"> <span class="keyword">if</span> (isDispatching) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Reducers may not dispatch actions.'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> isDispatching = <span class="literal">true</span></span><br><span class="line"> currentState = currentReducer(currentState, action)</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> isDispatching = <span class="literal">false</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">const</span> listeners = currentListeners = nextListeners</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < listeners.length; i++) {</span><br><span class="line"> <span class="keyword">const</span> listener = listeners[i]</span><br><span class="line"> listener()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> action</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 顾名思义,就是替换当前store在用的reducer</span></span><br><span class="line"> <span class="comment">// 有什么用呢?你想动态加载某些reducer时,加载后用该方法替换reducer; 如果你想为redux实现热更新机制,也需要该方法</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">replaceReducer</span>(<span class="params">nextReducer</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> nextReducer !== <span class="string">'function'</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">'Expected the nextReducer to be a function.'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> currentReducer = nextReducer</span><br><span class="line"> dispatch({ <span class="attr">type</span>: ActionTypes.REPLACE })</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="function"><span class="keyword">function</span> <span class="title">observable</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> outerSubscribe = subscribe</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> subscribe(observer) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> observer !== <span class="string">'object'</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'Expected the observer to be an object.'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">observeState</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (observer.next) {</span><br><span class="line"> observer.next(getState())</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> observeState()</span><br><span class="line"> <span class="keyword">const</span> unsubscribe = outerSubscribe(observeState)</span><br><span class="line"> <span class="keyword">return</span> { unsubscribe }</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> [$$observable]() {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</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"> dispatch({ <span class="attr">type</span>: ActionTypes.INIT })</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> dispatch,</span><br><span class="line"> subscribe,</span><br><span class="line"> getState,</span><br><span class="line"> replaceReducer,</span><br><span class="line"> [$$observable]: observable</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里最值得关注的就是currentListeners 和nextListeners这两个listener队列及ensureCanMutateNextListeners方法。每次修改的是nextListeners。每次flush listeners时 将nextListeners和currentListeners同步。</p><h4 id="bindActionCreators-js"><a href="#bindActionCreators-js" class="headerlink" title="bindActionCreators.js"></a>bindActionCreators.js</h4><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//bindActionCreators 用法</span></span><br><span class="line">bindActionCreators({ <span class="comment">//第一个参数也可以是一个 actionCreator函数</span></span><br><span class="line"> addCreator, <span class="comment">//属性值是actionCreator函数</span></span><br><span class="line"> anotherCreator,</span><br><span class="line"> ...</span><br><span class="line">},dispatch)</span><br><span class="line"></span><br><span class="line"><span class="comment">//bindActionCreator</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bindActionCreator</span>(<span class="params">actionCreator, dispatch</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> dispatch(actionCreator.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="comment">//bindActionCreators返回值</span></span><br><span class="line">=> boundActionCreators = {</span><br><span class="line"> addCreator: bindActionCreator(addCreator, dispatch),</span><br><span class="line"> anotherCreator: bindActionCreator(anotherCreator, dispatch),</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line">or </span><br><span class="line">=> </span><br><span class="line"></span><br><span class="line">bindActionCreator(actionCreators, dispatch) => <span class="function"><span class="keyword">function</span> (<span class="params">...arg</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> dispatch(actionCreator(...arg))) </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//actionCreator</span></span><br><span class="line">actionCreator() => action (对象),用的时候dispatch(actionCreator())</span><br></pre></td></tr></table></figure><p>bindActionCreators 主要是把actionCreator 包了一层dispatch,使用时可以像普通函数一样调用actionCreator。使得对redux 无感知。</p><h4 id="applyMiddleWare"><a href="#applyMiddleWare" class="headerlink" title="applyMiddleWare"></a>applyMiddleWare</h4><figure class="highlight javascript"><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><br><span class="line"><span class="keyword">import</span> compose <span class="keyword">from</span> <span class="string">'./compose'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">applyMiddleware</span>(<span class="params">...middlewares</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function">(<span class="params">createStore</span>) =></span> <span class="function">(<span class="params">...args</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> store = createStore(...args)</span><br><span class="line"> <span class="keyword">let</span> dispatch = store.dispatch</span><br><span class="line"> <span class="keyword">let</span> chain = []</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> middlewareAPI = {</span><br><span class="line"> getState: store.getState,</span><br><span class="line"> dispatch: <span class="function">(<span class="params">...args</span>) =></span> dispatch(...args)</span><br><span class="line"> }</span><br><span class="line"> chain = middlewares.map(<span class="function"><span class="params">middleware</span> =></span> middleware(middlewareAPI))</span><br><span class="line"> dispatch = compose(...chain)(store.dispatch)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> ...store,</span><br><span class="line"> dispatch</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="compose-js"><a href="#compose-js" class="headerlink" title="compose.js"></a>compose.js</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">compose</span>(<span class="params">...funcs</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (funcs.length === <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="params">arg</span> =></span> arg</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (funcs.length === <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> funcs[<span class="number">0</span>]</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> funcs.reduce(<span class="function">(<span class="params">a, b</span>) =></span> <span class="function">(<span class="params">...args</span>) =></span> a(b(...args)))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="https://github.com/reactjs/redux" target="_blank" rel="noopener">github</a></li><li><a href="http://cn.redux.js.org/index.html" target="_blank" rel="noopener">中文文档</a></li><li><a href="http://www.jianshu.com/p/7ae531a8b299?from=timeline" target="_blank" rel="noopener">源码解读</a></li><li><a href="https://github.com/react-guide/redux-tutorial-cn#redux-tutorial" target="_blank" rel="noopener">极简教程</a></li><li><a href>极简代码示例</a></li></ul>]]></content>
<summary type="html">
<h3 id="redux简单介绍"><a href="#redux简单介绍" class="headerlink" title="redux简单介绍"></a>redux简单介绍</h3><p>前面<a href>理解了flux</a>,本篇来理解redux。<br>redux的官方介绍 </p>
<blockquote>
<p>Redux is a predictable state container for JavaScript apps.</p>
</blockquote>
<p>翻译一下:redux 是javascript 应用状态容器,他提供可预测化的状态管理。<br>师出flux,作用和flux一样,作为应用的状态管理层。其核心思想也是单向数据流。</p>
<p>我们先看一个redux 最简单的原生使用实例;然后再胡乱解读一通redux概念;最后通过源码来看下其实现原理。</p>
</summary>
<category term="前端状态管理" scheme="https://foreverwang.github.io/categories/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86/"/>
<category term="数据流" scheme="https://foreverwang.github.io/tags/%E6%95%B0%E6%8D%AE%E6%B5%81/"/>
<category term="redux" scheme="https://foreverwang.github.io/tags/redux/"/>
</entry>
<entry>
<title>《深度工作》笔记</title>
<link href="https://foreverwang.github.io/2017/10/08/%E3%80%8A%E6%B7%B1%E5%BA%A6%E5%B7%A5%E4%BD%9C%E3%80%8B%E7%AC%94%E8%AE%B0/"/>
<id>https://foreverwang.github.io/2017/10/08/%E3%80%8A%E6%B7%B1%E5%BA%A6%E5%B7%A5%E4%BD%9C%E3%80%8B%E7%AC%94%E8%AE%B0/</id>
<published>2017-10-08T14:54:47.000Z</published>
<updated>2020-06-21T11:08:05.115Z</updated>
<content type="html"><![CDATA[<p>“深度工作”是现代人的一种非常重要的能力。<br>随着网络智能手机的普及,在给我们生活带来便利的同时也增加了使我们分心的因素。在纷杂碎片化严重的现代人生活中专注力就显得格外重要。<br>与深度工作相对的是浮浅工作。</p><a id="more"></a><h3 id="影响深度工作的因素"><a href="#影响深度工作的因素" class="headerlink" title="影响深度工作的因素"></a>影响深度工作的因素</h3><ul><li>开放式的工作空间:更容易受周边环境的打扰。-外因</li><li>“邮件系统、钉钉” 等频繁的打断。-外因</li><li>度量黑洞:脑力工作不好度量,通过表面上的忙碌,拉长工作时间,不断加班。-外因</li><li>最小阻力原则:心里倾向:当我们面前有两件以上事情要选择的时候,我们总是选择阻力最小更容易完成的事情,把难度的事情一直拖,很容易陷入“浮浅工作” 。 -内因</li><li>社交网络的成瘾: 微信、微博等。 -内因</li></ul><h3 id="四种深度工作模式"><a href="#四种深度工作模式" class="headerlink" title="四种深度工作模式"></a>四种深度工作模式</h3><ul><li>禁欲者模式:与世隔绝,通常稀世天才们可以做到。</li><li>双峰模式:将时间按照一定周期划分成深度工作和浮浅工作。在时间上集中处理浮浅工作,其他时间深度工作。注意:深度工作的单位至少是一天。</li><li>节奏模式:每天有规律的进行深度工作,从几点到几点必须专心的干一件是。一天内至少有一次深度工作和浮浅工作的切换。 比如好多大佬早起工作。</li><li>记着模式:随时可以进入深度工作,需要训练。</li></ul><h3 id="深度工作的六个技巧"><a href="#深度工作的六个技巧" class="headerlink" title="深度工作的六个技巧"></a>深度工作的六个技巧</h3><ul><li>刻意练习:明确的目的,针对需要练习的点不断练习,不断增加难度,逃离舒适区。练习中要及时获得反馈。</li><li>批处理浮浅工作。</li><li>获得意义感:认知重塑。当我们做的事情和我们的价值观相符合的时候就会有意义感。</li><li>改变环境:比如去图书馆看书。</li><li>适度休息:</li><li>工具优化:工具分两类-娱乐消遣类工具和生产力类工具。列出自己用了哪些生产力工具。</li></ul><h3 id="训练深度专注力:先肌肉一样训练"><a href="#训练深度专注力:先肌肉一样训练" class="headerlink" title="训练深度专注力:先肌肉一样训练"></a>训练深度专注力:先肌肉一样训练</h3><ul><li>定量化:番茄工作法,不断的锻炼。看自己最多可以连续专注工作多长时间,如看书。可以先定个目标:连续深度看书一小时。</li><li>划分优先级:深度工作只给优先级高的工作,其他给浮浅工作。</li></ul><h3 id="感想"><a href="#感想" class="headerlink" title="感想"></a>感想</h3><p>现在自己的工作就感觉到深度工作的时间太少了,总感觉一天忙忙碌碌,最后好像也没干什么重要的事情。<br>原因就在于一天过多的浮浅工作,不断的被别人打断。</p><p>以后</p><ul><li>每天制定个深度工作的时间段(2到3小时),争取这段时间内不要看钉钉等被打扰的东西。</li><li>早起,早上9点到10点半这段时间可以用来学习</li><li>连续专注看书目标 1小时–> 2小时。 不看手机。</li></ul><p>深度工作的能力是在现代社会中的一个很大的优势。</p>]]></content>
<summary type="html">
<p>“深度工作”是现代人的一种非常重要的能力。<br>随着网络智能手机的普及,在给我们生活带来便利的同时也增加了使我们分心的因素。在纷杂碎片化严重的现代人生活中专注力就显得格外重要。<br>与深度工作相对的是浮浅工作。</p>
</summary>
<category term="读书笔记" scheme="https://foreverwang.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<category term="深度工作" scheme="https://foreverwang.github.io/tags/%E6%B7%B1%E5%BA%A6%E5%B7%A5%E4%BD%9C/"/>
</entry>
<entry>
<title>前端MVC</title>
<link href="https://foreverwang.github.io/2017/10/05/%E5%89%8D%E7%AB%AFMVC/"/>
<id>https://foreverwang.github.io/2017/10/05/%E5%89%8D%E7%AB%AFMVC/</id>
<published>2017-10-05T07:49:16.000Z</published>
<updated>2020-06-21T11:08:05.116Z</updated>
<content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>本文是个人对前端MVC的理解。当我去查MVC资料的时候发现,一千篇文章有一千种MVC,搞的我很纠结。于是研究总结为本文。如果要先一句话介绍什么是MVC,那么:</p><blockquote><p>MVC是开发GUI(图形用户界面)程序的一种架构模式。</p></blockquote><a id="more"></a><p>是的,我这里想要搞清的就是<code>前端MVC</code>(包括web前端,客户端开发),不涉及服务端MVC。 </p><h3 id="说说历史"><a href="#说说历史" class="headerlink" title="说说历史"></a>说说历史</h3><p>MVC模式最初是在1979年由 TrygveReenskaug在研究Smalltalk-80期间设计出来的。本文称之为经典MVC(以和现代的一千种MVC相区别)。</p><p>这么多年过去了,如今的软件环境和当年已大不相同,所以我们今天所看到的MVC已不同往日。比如经典MVC中说</p><blockquote><p>“view永远不应该知道用户输入,比如鼠标操作和按键。” –经典MVC</p></blockquote><p>显然在今天的前端开发中是做不到的,用户输入必须通过监听view上的事件来获得。</p><p>因为当年View的功能及其弱,比如鼠标的光标都需要程序自己绘制。经典MVC中,Controller要做的事情多数是派发用户输入给不同的View,并且在必要的时候从View中获取Editor来更改Model,而Web以及绝大多数现在的UI系统中,Controller的这部分职责已经被系统实现了。即现在的view 系统实现的更强大了,比如view自身能响应事件并带有一些事件数据。</p><blockquote><p>经典MVC中称”controller是用户和系统之间的链接”,也就不难理解解了。</p></blockquote><p>所以,如今 view.onclick = … ,在当年就是 mouse.onclick = … </p><p>知道了当年和现在软件环境的差异后,那么我们就重点关注那些些没变的东西就好了,也没要去纠结 Smalltalk-80 MVC最初的实现了。变的是环境,不变的是思想。</p><blockquote><p>MVC强制将业务数据(Model)与用户界面(View)隔离,Controller管理业务逻辑和用户输入。</p></blockquote><h3 id="MVC"><a href="#MVC" class="headerlink" title="MVC"></a>MVC</h3><p>接下来介绍 MVC的各个部件:</p><h4 id="Model"><a href="#Model" class="headerlink" title="Model"></a>Model</h4><p>Model 代表特定领域数据??,不了解用户界面(view 和controller);当一个model 发生变化时,他会通知他的观察者(view);</p><h4 id="View"><a href="#View" class="headerlink" title="View"></a>View</h4><p>View 描绘的是Model的当前状态。view 通过观察者模式观察 model以了解model何时更新。</p><h4 id="Controller"><a href="#Controller" class="headerlink" title="Controller"></a>Controller</h4><p>在view-controller 对中作用是处理用户交互(如按键和点击等动作),为view 做决定。</p><h4 id="MVC的依赖关系"><a href="#MVC的依赖关系" class="headerlink" title="MVC的依赖关系"></a>MVC的依赖关系</h4><p><img src="https://ws1.sinaimg.cn/large/006tKfTcgy1fk7cgu66arj30kk0cwq40.jpg" alt="mvc依赖关系图"></p><p>Controller和View 都依赖Model: view 的数据来源为model,controller 调用model的 数据处理方法。 controller和view互相依赖,在一些网上的资料Controller和View之间的依赖关系可能不一样,有些是单向依赖,有些是双向依赖,这个其实关系不大,后面会看到它们的依赖关系都是为了把处理用户行为触发的事件处理权交给Controller。</p><h4 id="MVC调用关系"><a href="#MVC调用关系" class="headerlink" title="MVC调用关系"></a>MVC调用关系</h4><p><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1fk7cx1rbn7j30j80kkgni.jpg" alt="MVC调用关系"></p><ul><li>用户交互来源主要是view (或者输入url),view 捕获到操作后,将处理权委托给controller,controller对来自view 的数据预处理,决定调用那个model的那个接口(应用逻辑);然后有model 执行相应的业务逻辑; model 更新后,通过观察者模式通知view ;view 接收到通知后向model请求最新数据(或者model主动传递)更新自己。</li></ul><h4 id="MVC实例"><a href="#MVC实例" class="headerlink" title="MVC实例"></a>MVC实例</h4><pre><code>http://jsfiddle.net/uVBvq/</code></pre><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><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">form</span> <span class="attr">action</span>=<span class="string">""</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">id</span>=<span class="string">"num"</span>></span><span class="tag"></<span class="name">span</span>></span><span class="tag"><<span class="name">br</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">"button"</span> <span class="attr">id</span>=<span class="string">"increase"</span>></span>+<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">type</span>=<span class="string">"button"</span> <span class="attr">id</span>=<span class="string">"decrease"</span>></span>-<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">form</span>></span></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="javascript"><span class="keyword">var</span> myapp = {};</span></span><br><span class="line"></span><br><span class="line"><span class="javascript">myapp.Model = <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> val = <span class="number">0</span>;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.add = <span class="function"><span class="keyword">function</span> (<span class="params">v</span>) </span>{</span></span><br><span class="line"><span class="xml"> if (val <span class="tag">< <span class="attr">100</span>) <span class="attr">val</span> += <span class="string">v;</span></span></span></span><br><span class="line"> };</span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.sub = <span class="function"><span class="keyword">function</span> (<span class="params">v</span>) </span>{</span></span><br><span class="line"> if (val > 0) val -= v;</span><br><span class="line"> };</span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.getVal = <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">return</span> val;</span></span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//model拥有 view 的引用?</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> views = [];</span></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.register = <span class="function"><span class="keyword">function</span> (<span class="params">view</span>) </span>{</span></span><br><span class="line"> views.push(view);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//model 变化时通知model更新</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.notify = <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">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < views.length; i++) {</span></span><br><span class="line"><span class="javascript"> views[i].render(<span class="keyword">this</span>);</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="javascript"><span class="comment">//view 拥有controller的引用</span></span></span><br><span class="line"><span class="javascript">myapp.View = <span class="function"><span class="keyword">function</span> (<span class="params">controller</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> $incBtn = $(<span class="string">'#increase'</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> $decBtn = $(<span class="string">'#decrease'</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> $num = $(<span class="string">'#num'</span>);</span></span><br><span class="line"></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.render = <span class="function"><span class="keyword">function</span> (<span class="params">model</span>) </span>{</span></span><br><span class="line"><span class="javascript"> $num.text(model.getVal() + <span class="string">'px'</span>);</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> $incBtn.click(controller.increase);</span><br><span class="line"> $decBtn.click(controller.decrease);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"><span class="javascript">myapp.Controller = <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> model = <span class="literal">null</span>;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> view = <span class="literal">null</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.init = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="comment">//controller初始化 model 和view</span></span></span><br><span class="line"><span class="javascript"> model = <span class="keyword">new</span> myapp.Model();</span></span><br><span class="line"><span class="javascript"> view = <span class="keyword">new</span> myapp.View(<span class="keyword">this</span>);</span></span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//view 向model 注册自己</span></span></span><br><span class="line"> model.register(view);</span><br><span class="line"> model.notify();</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"><span class="javascript"> <span class="comment">//controller 负责更新model </span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.increase = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"> model.add(1);</span><br><span class="line"> model.notify();</span><br><span class="line"> };</span><br><span class="line"><span class="javascript"> <span class="keyword">this</span>.decrease = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"> model.sub(1);</span><br><span class="line"> model.notify();</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="javascript"><span class="comment">//外界只接触到controller </span></span></span><br><span class="line"><span class="javascript">$(<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> controller = <span class="keyword">new</span> myapp.Controller();</span></span><br><span class="line"> controller.init();</span><br><span class="line">});</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><h3 id="MVC优缺点"><a href="#MVC优缺点" class="headerlink" title="MVC优缺点"></a>MVC优缺点</h3><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>耦合性降低:视图和模型分离,利于项目工程化(分工等)。</li><li>Model可复用性增强: 因为Model是独立于view的。</li></ul><h4 id="缺点-(以下直接引用)"><a href="#缺点-(以下直接引用)" class="headerlink" title="缺点 (以下直接引用)"></a>缺点 (<a href="https://github.com/livoras/blog/issues/11" target="_blank" rel="noopener">以下直接引用</a>)</h4><ul><li>Controller测试困难。因为视图同步操作是由View自己执行,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,应用逻辑正确性是无法验证的:Model更新的时候,无法对View的更新操作进行断言。</li><li>View无法组件化。View是强依赖特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的Domain Model是不一样的。 </li></ul><h3 id="MVC其他"><a href="#MVC其他" class="headerlink" title="MVC其他"></a>MVC其他</h3><h4 id="ios里的MVC(todo)"><a href="#ios里的MVC(todo)" class="headerlink" title="ios里的MVC(todo)"></a>ios里的MVC(todo)</h4><p>kvo …</p><h4 id="服务端MVC"><a href="#服务端MVC" class="headerlink" title="服务端MVC"></a>服务端MVC</h4><p>服务端也有MVC,但是经典的MVC模式只是解决客户端图形界面应用程序的问题,而对服务端无效,服务端MVC的变种也有自己的一个名字 MVC Model2 这里不介绍了。</p><h3 id="MVC衍生–MVP"><a href="#MVC衍生–MVP" class="headerlink" title="MVC衍生–MVP"></a>MVC衍生–MVP</h3><p>顺便把mvp说一下吧。MVP是MVC的衍生品。MVP有两种:</p><ul><li>Passive View</li><li>Supervising Controller</li></ul><p>常见的是Passive View(被动视图),这里也只说这种。</p><h4 id="MVP依赖关系"><a href="#MVP依赖关系" class="headerlink" title="MVP依赖关系"></a>MVP依赖关系</h4><p><img src="https://ws2.sinaimg.cn/large/006tKfTcgy1fk7e9b9vhjj30ii0b60tn.jpg" alt="MVP依赖关系"></p><p>MVP打破了View原来对于Model的依赖,其余的依赖关系和MVC模式一致。</p><h4 id="MVP(Passive-View)的调用关系"><a href="#MVP(Passive-View)的调用关系" class="headerlink" title="MVP(Passive View)的调用关系"></a>MVP(Passive View)的调用关系</h4><p><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1fk7edqp1frj310u0t8jts.jpg" alt="MVP(Passive View)的调用关系"></p><p>view和model 完全解耦后,model更新是如何同步view的呢? 是通过presenter。<br>mvc 中是model更新后通知view更新。mvp中是model更新后,通过观察者告知presenter而不是view了。presenter获取到Model变更的消息以后,通过View提供的接口更新界面。</p><h4 id="MVP(Passive-View)的优缺点(以下直接引用)"><a href="#MVP(Passive-View)的优缺点(以下直接引用)" class="headerlink" title="MVP(Passive View)的优缺点(以下直接引用)"></a>MVP(Passive View)的优缺点(<a href="https://github.com/livoras/blog/issues/11" target="_blank" rel="noopener">以下直接引用</a>)</h4><h5 id="优点:"><a href="#优点:" class="headerlink" title="优点:"></a>优点:</h5><ul><li>便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。这里根据上面的例子给出了Presenter的单元测试样例。</li><li>View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。</li></ul><h5 id="缺点:"><a href="#缺点:" class="headerlink" title="缺点:"></a>缺点:</h5><ul><li>Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。</li></ul><p>//todo: 待实例验证优缺点 </p><h3 id="资料"><a href="#资料" class="headerlink" title="资料"></a>资料</h3><ul><li>winter <a href="http://www.cnblogs.com/winter-cn/p/4285171.html" target="_blank" rel="noopener">http://www.cnblogs.com/winter-cn/p/4285171.html</a></li><li>EFE <a href="http://efe.baidu.com/blog/mvc-deformation/" target="_blank" rel="noopener">http://efe.baidu.com/blog/mvc-deformation/</a></li><li><a href="https://github.com/livoras/blog/issues/11" target="_blank" rel="noopener">https://github.com/livoras/blog/issues/11</a></li><li><a href="https://speakerdeck.com/jaceju/understanding-the-mvc-mvp-mvvm-in-javascript" target="_blank" rel="noopener">https://speakerdeck.com/jaceju/understanding-the-mvc-mvp-mvvm-in-javascript</a></li></ul>]]></content>
<summary type="html">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>本文是个人对前端MVC的理解。当我去查MVC资料的时候发现,一千篇文章有一千种MVC,搞的我很纠结。于是研究总结为本文。如果要先一句话介绍什么是MVC,那么:</p>
<blockquote>
<p>MVC是开发GUI(图形用户界面)程序的一种架构模式。</p>
</blockquote>
</summary>
<category term="架构模式" scheme="https://foreverwang.github.io/categories/%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F/"/>
<category term="设计模式" scheme="https://foreverwang.github.io/categories/%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="MVC" scheme="https://foreverwang.github.io/tags/MVC/"/>
<category term="MVP" scheme="https://foreverwang.github.io/tags/MVP/"/>
</entry>
<entry>
<title>理解Flux</title>
<link href="https://foreverwang.github.io/2017/09/16/%E7%90%86%E8%A7%A3Flux/"/>
<id>https://foreverwang.github.io/2017/09/16/%E7%90%86%E8%A7%A3Flux/</id>
<published>2017-09-16T13:16:08.000Z</published>
<updated>2020-06-21T11:08:05.117Z</updated>
<content type="html"><![CDATA[<h3 id="什么是flux"><a href="#什么是flux" class="headerlink" title="什么是flux"></a>什么是flux</h3><p>flux是一种架构模式,用来指导(或约束)我们的软件结构。其核心是“单向数据流模式”。同MVC架构是同样的存在。<br>(举例 :React 说自己是 MVC 里面 V 的部分,那么 Flux 就相当于添加 M 和 C 的部分)</p><h3 id="单向数据流"><a href="#单向数据流" class="headerlink" title="单向数据流"></a>单向数据流</h3><p>Flux 的核心“单向数据流“的结构和流程:</p><blockquote><p> Action –> Dispatcher –> Store –> View</p></blockquote><a id="more"></a><ul><li>首先要有 action,通过定义一些 action creator方法 根据需要创建 Action。</li><li>View 层通过用户交互(比如 onClick)会触发 Action</li><li>Dispatcher 会分发 所触发的 Action 到所有注册的 Store 的回调函数。</li><li>Store 回调函数根据接收的 Action 更新自身数据之后会触发一个 change 事件通知 View 数据更改了</li><li>View 会监听这个 change 事件,拿到对应的新数据并 更新组件 UI(如调用 setState)</li></ul><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><ul><li>View: 视图层 (视图组件)</li><li>Action(动作):视图层发出的消息 :包括消息类型和消息的数据。</li><li>Dispatcher(派发器):用来接收Actions、执行回调函数。</li><li>Store:包含应用状态和逻辑。</li></ul><h4 id="Action-(每个Store有一堆)"><a href="#Action-(每个Store有一堆)" class="headerlink" title="Action (每个Store有一堆)"></a>Action (每个Store有一堆)</h4><p>每个Action都是一个对象,包含一个actionType属性(说明动作的类型)和一些其他属性(用来传递数据)。</p><h4 id="Dispatcher-全局一个"><a href="#Dispatcher-全局一个" class="headerlink" title="Dispatcher (全局一个)"></a>Dispatcher (全局一个)</h4><p>Dispatcher 是中心枢纽,其<code>作用</code>是将 Action 派发到 Store,管理所有数据流向。Dispatcher <code>本质</code>上就是一个事件系统。<br>你可以把它看作一个路由器,负责在 View 和 Store 之间,建立 Action 的正确传递路线。<br>以Facebook 的 Dispatcher 库为例,Dispatcher有两个核心方法:</p><ul><li>dispatch:派发action到store 注册的回调。</li><li>register: store 会调用该方法注册其回调,回调里对不同的action做处理。</li></ul><p>Dispatcher 只用来派发 Action,不应该有其他逻辑。且Dispatcher 只能有一个,而且是全局的。<br>各Store在dispatcher上注册自己的回调,这样dispatcher上就有一张回调注册表,与各Store建立联系。</p><h4 id="Store(多个)-响应action-事件"><a href="#Store(多个)-响应action-事件" class="headerlink" title="Store(多个) 响应action(事件)"></a>Store(多个) 响应action(事件)</h4><p>Stores 包含应用的状态和逻辑,不同的 Store 管理应用中不同部分的状态。<br>Store响应 Dispatched Actions (被分发的事件)。<br>重点:应用中唯一知道如何更新数据的就是 Store。<br>store在dispatcher上注册的回调接受一个action参数,回调里面是一个switch语句,根据action的type分发给具体state更新方法,store更新完毕后,通过广播事件来告诉view某些状态变了,对应的view取新的状态更新自己。</p><h4 id="view-(多个)"><a href="#view-(多个)" class="headerlink" title="view (多个)"></a>view (多个)</h4><p>接收用户操作触发action。 action通过dispatcher派发到store里处理更新数据状态,并发出改变的通知,view获取新的状态更新自己。</p><h3 id="有什么好处?"><a href="#有什么好处?" class="headerlink" title="有什么好处?"></a>有什么好处?</h3><p>这里引用尤雨溪知乎的一个回答:</p><blockquote><ol><li>视图组件变得很薄,只包含了渲染逻辑和触发 action 这两个职责,即所谓 “dumb components”。(<code>个人理解:数据层和view层分离</code>)</li><li>要理解一个 store 可能发生的状态变化,只需要看它所注册的 actions 回调就可以。(<code>个人理解:和1说的一回事,关注点分离后,是的结构更清晰,代码可读性和可维护性提高</code>)</li><li>任何状态的变化都必须通过 action 触发,而 action 又必须通过 dispatcher 走,所以整个应用的每一次状态变化都会从同一个地方流过。其实 Flux 和传统 MVC 最不一样的就在这里了。React 在宣传的时候一直强调的一点就是 “理解你的应用的状态变化是很困难的 (managing state changing over time is hard)”,Flux 的意义就在于强制让所有的状态变化都必须留下一笔记录,这样就可以利用这个来做各种 debug 工具、历史回滚等等。(<code>个人理解:使数据可预测</code>)</li></ol></blockquote><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p>具体实例可以参考阮老师的这边文章 :<a href="http://www.ruanyifeng.com/blog/2016/01/flux.html" target="_blank" rel="noopener">Flux 架构入门教程</a></p><p>这篇总结的非常到位,但是前提是得你先大概理解了flux:<a href="http://www.ayqy.net/blog/flux/" target="_blank" rel="noopener">flux总结</a></p><p>facebook 官方提供的 <a href="https://github.com/facebook/flux/blob/master/src/Dispatcher.js" target="_blank" rel="noopener">dispatcher</a></p>]]></content>
<summary type="html">
<h3 id="什么是flux"><a href="#什么是flux" class="headerlink" title="什么是flux"></a>什么是flux</h3><p>flux是一种架构模式,用来指导(或约束)我们的软件结构。其核心是“单向数据流模式”。同MVC架构是同样的存在。<br>(举例 :React 说自己是 MVC 里面 V 的部分,那么 Flux 就相当于添加 M 和 C 的部分)</p>
<h3 id="单向数据流"><a href="#单向数据流" class="headerlink" title="单向数据流"></a>单向数据流</h3><p>Flux 的核心“单向数据流“的结构和流程:</p>
<blockquote>
<p> Action –&gt; Dispatcher –&gt; Store –&gt; View</p>
</blockquote>
</summary>
<category term="前端状态管理" scheme="https://foreverwang.github.io/categories/%E5%89%8D%E7%AB%AF%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86/"/>
<category term="flux" scheme="https://foreverwang.github.io/tags/flux/"/>
<category term="数据流" scheme="https://foreverwang.github.io/tags/%E6%95%B0%E6%8D%AE%E6%B5%81/"/>
</entry>
<entry>
<title>设计模式之工厂模式</title>
<link href="https://foreverwang.github.io/2017/08/27/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/"/>
<id>https://foreverwang.github.io/2017/08/27/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</id>
<published>2017-08-27T09:13:22.000Z</published>
<updated>2020-06-21T11:08:05.118Z</updated>
<content type="html"><![CDATA[<h2 id="设计模式-创建型之工厂模式"><a href="#设计模式-创建型之工厂模式" class="headerlink" title="设计模式-创建型之工厂模式"></a>设计模式-创建型之工厂模式</h2><p>创建型设计模式共六种:</p><ul><li>简单工厂模式</li><li>工厂方法模式</li><li>抽象工厂模式</li><li>建造者模式</li><li>原型模式</li><li>单例模式</li></ul><p>本篇讲三个工厂模式: 简单工厂、工厂方法、抽象工厂。</p><a id="more"></a><p>创建型设计模式是用来创建对象的,在面向对象的编程中最常用的一种创建对象方式是用new操作符产生一个对象实例。<br>创建型模式对类的实例化过程进行了抽象,能够将对象的创建与对象的使用过程分离。</p><h3 id="简单工厂模式"><a href="#简单工厂模式" class="headerlink" title="简单工厂模式"></a>简单工厂模式</h3><h4 id="模式定义"><a href="#模式定义" class="headerlink" title="模式定义"></a>模式定义</h4><p>简单工厂模式:又称静态工厂模式。在该模式中可以通过参数的不同返回不同的实例。简单工厂模式专门定义一个类来负责创建其他类的实例。被创建的实例通常都具有共同的分类。</p><h4 id="模式的结构"><a href="#模式的结构" class="headerlink" title="模式的结构"></a>模式的结构</h4><ul><li>Factory-工厂角色: 负责实现创建所有实例的内部逻辑。</li><li>Product-抽象产品角色: 创建的所有对象的父类,负责描述所有实例所共有的公共接口。</li><li>ConcreteProduct-具体产品角色:创建的目标,所有创建的对象都充当这个角色的某个具体类的实例。</li></ul><p>当然,实际应用中可能没有这么严格的三个角色。但 <code>Factory</code>和 <code>ConcreteProduct</code>是必须的。 <br></p><p>简单工厂的核心定义是:有一个工厂方法(也可以是工厂类)根据接收的参数 实例化具体的类,并且返回这个类的实例。</p><h4 id="实例分析"><a href="#实例分析" class="headerlink" title="实例分析"></a>实例分析</h4><p>这里用一个生产宝马汽车的例子。现在有两个型号的宝马车:BMW320和BMW523。<br>假设此时没有宝马工厂。用户需要这个这两个型号的车的时候需要 自己去制造生产。代码如下:</p><p>没有工厂的时候:</p><figure class="highlight java"><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">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW320</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> BMW320 {</span><br><span class="line"> System.out.println(<span class="string">'生产320型号的宝马车'</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW523</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> BMW523 {</span><br><span class="line"> System.out.println(<span class="string">'生产320型号的宝马车'</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Customer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> BMW320 bmw320 = <span class="keyword">new</span> BMW320();</span><br><span class="line"> BMW523 bmw523 = <span class="keyword">new</span> BMW523();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样客户也造出了汽车,似乎也没啥毛病。问题在于:客户需要知道怎么去创建一款车(具体new 哪个类) 这样客户和车是耦合在一起的。但车型有很多的时候,客户就需要知道每一个型号的车对应哪个类。为了解耦,我们引入简单那工厂模式 :创建一个工厂,将创建具体那个型号的宝马车的操作细节放到工厂里,客户直接使用工厂的创建产品的方法,传入具体的型号就好了。</p><p> 有了简单工厂:</p><p> 产品类<br><figure class="highlight java"><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">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW320</span> <span class="keyword">extends</span> <span class="title">BMW</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BMW320</span><span class="params">()</span> </span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->BMW320"</span>); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW523</span> <span class="keyword">extends</span> <span class="title">BMW</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BMW523</span><span class="params">()</span></span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->BMW523"</span>); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>工厂类 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMWFactory</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> BMW <span class="title">createBMW</span> <span class="params">(<span class="keyword">int</span> type)</span> </span>{</span><br><span class="line"> <span class="keyword">switch</span> (type) {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span>: <span class="number">320</span> </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BMW320();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span>: <span class="number">523</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BMW523();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">break</span>; </span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>客户类</p><figure class="highlight java"><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">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Customer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(Sting[] args)</span> </span>{</span><br><span class="line"> BMWFactory factory = <span class="keyword">new</span> BMWFactory();</span><br><span class="line"> BMW bmw320 = factory.createBMW(<span class="number">320</span>);</span><br><span class="line"> BMW bmw523 = factory.createBMW(<span class="number">523</span>); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到 具体实例化某一个class 的操作放到了工厂类里,客户只需要调用工厂方法并传参就OK了。</p><h4 id="模式分析"><a href="#模式分析" class="headerlink" title="模式分析"></a>模式分析</h4><h5 id="简单工厂的优点:"><a href="#简单工厂的优点:" class="headerlink" title="简单工厂的优点:"></a>简单工厂的优点:</h5><ul><li>客户端更方便了:无需记忆众多复杂的类名,只需要知道具体产品类所对应的参数。</li><li>责任分割: 创建产品的逻辑都放在工厂,客户端仅仅是消费产品,从而免去了直接创建产品对象的责任。</li></ul><h5 id="简单工厂的缺点:"><a href="#简单工厂的缺点:" class="headerlink" title="简单工厂的缺点:"></a>简单工厂的缺点:</h5><ul><li>违背开闭原则的 对修改关闭。当需要新增加一类产品的时候,除了增加创建该类产品的代码外还需要修改工厂类。</li></ul><h5 id="简单工厂适用场景"><a href="#简单工厂适用场景" class="headerlink" title="简单工厂适用场景"></a>简单工厂适用场景</h5><ul><li>工厂类负责创建的对象比较少:由于创建的对象少,不会造成工厂法法中的业务逻辑泰国复杂。</li></ul><h3 id="工厂方法"><a href="#工厂方法" class="headerlink" title="工厂方法"></a>工厂方法</h3><p>上面我们知道了,当产品种类多了后,工厂就会很庞大,且新增一类产品就要改工厂类。此时工厂方法模式就上场了。</p><h4 id="模式定义-1"><a href="#模式定义-1" class="headerlink" title="模式定义"></a>模式定义</h4><p>在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将<code>产品类的实例化操作延迟到工厂子类</code>中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。</p><h4 id="模式的结构-1"><a href="#模式的结构-1" class="headerlink" title="模式的结构"></a>模式的结构</h4><ul><li>Factory-抽象工厂类:<ul><li>声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。</li></ul></li><li>ConcreteFactory-具体工厂类:<ul><li>它实现了抽象产品接口,一类工厂生产一类产品。</li></ul></li><li>Product-抽象产品类:<ul><li>定义产品的接口。可以是抽象类或接口。</li></ul></li><li>ConcreteProduct-具体产品类:<ul><li>实现抽象产品类的接口,产品和工厂一一对相应。</li></ul></li></ul><h4 id="实例分析-1"><a href="#实例分析-1" class="headerlink" title="实例分析"></a>实例分析</h4><p>还是接着上边简单工厂造宝马车的例子。</p><p>产品类,和简单工厂里一样<br><figure class="highlight java"><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="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BMW</span><span class="params">()</span></span>{ </span><br><span class="line"> </span><br><span class="line"> } </span><br><span class="line"> } </span><br><span class="line"> <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW320</span> <span class="keyword">extends</span> <span class="title">BMW</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BMW320</span><span class="params">()</span> </span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->BMW320"</span>); </span><br><span class="line"> } </span><br><span class="line"> } </span><br><span class="line"> <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BMW523</span> <span class="keyword">extends</span> <span class="title">BMW</span></span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BMW523</span><span class="params">()</span></span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->BMW523"</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><span class="line">``` java</span><br><span class="line"> <span class="class"><span class="keyword">interface</span> <span class="title">FactoryBMW</span> </span>{ </span><br><span class="line"> <span class="function">BMW <span class="title">createBMW</span><span class="params">()</span></span>; </span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FactoryBMW320</span> <span class="keyword">implements</span> <span class="title">FactoryBMW</span></span>{ </span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> BMW320 <span class="title">createBMW</span><span class="params">()</span> </span>{ </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BMW320(); </span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> } </span><br><span class="line"> <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FactoryBMW523</span> <span class="keyword">implements</span> <span class="title">FactoryBMW</span> </span>{ </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> BMW523 <span class="title">createBMW</span><span class="params">()</span> </span>{ </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BMW523(); </span><br><span class="line"> } </span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>客户类:客户生产某类产品 直接实例化具体的工厂。</p><figure class="highlight java"><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">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Customer</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{ </span><br><span class="line"> FactoryBMW320 factoryBMW320 = <span class="keyword">new</span> FactoryBMW320(); <span class="comment">//可通过配置文件实现 </span></span><br><span class="line"> BMW320 bmw320 = factoryBMW320.createBMW(); </span><br><span class="line"> </span><br><span class="line"> FactoryBMW523 factoryBMW523 = <span class="keyword">new</span> FactoryBMW523(); </span><br><span class="line"> BMW523 bmw523 = factoryBMW523.createBMW(); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="模式分析-1"><a href="#模式分析-1" class="headerlink" title="模式分析"></a>模式分析</h4><p>工厂方法模式是简单工厂模式的进一步抽象。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。工厂方法模式可以允许在不修改工厂角色的情况下引进新产品。<br></p><p>基于工厂角色和产品角色的<code>多态性</code>设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。</p><h5 id="工厂方法模式优点"><a href="#工厂方法模式优点" class="headerlink" title="工厂方法模式优点"></a>工厂方法模式优点</h5><ul><li><code>符合开闭原则</code>(克服了简单工厂的缺点)。引入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。</li></ul><h5 id="工厂方法模式的缺点"><a href="#工厂方法模式的缺点" class="headerlink" title="工厂方法模式的缺点"></a>工厂方法模式的缺点</h5><ul><li>系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。</li></ul><h5 id="工厂方法模式的适用场景"><a href="#工厂方法模式的适用场景" class="headerlink" title="工厂方法模式的适用场景"></a>工厂方法模式的适用场景</h5><ul><li>当需要的产品种类多变,系统需要有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。</li></ul><h5 id="工厂方法模式疑问"><a href="#工厂方法模式疑问" class="headerlink" title="工厂方法模式疑问"></a>工厂方法模式疑问</h5><p>到这里我是有点疑问的:工厂方法模式中,客户端就得知道生产某类产品对应的工厂类,当产品种类多的时候,那不就面临没有任何工厂模式时同样的问题?<br>这个问题可以通过配置文件解决。通过配置文件来存储具体工厂类ConcreteFactory的类名,更换新的具体工厂时无须修改源代码,系统扩展更为方便。</p><p>那么问题又来了:那我不用工厂方法模式,我生产具体的产品(new ConcreteProduct)时,也通过配置文件来存储产品类的类名,那不就不需要工厂类了吗? 那工厂方法模式的意义是啥呢?我的理解是 :实际工作中,工厂类 不只是简单的new ConcreteProduct(), 同时还做一些初始化操作,比如处理前置依赖等。把这些封装在工厂类内,使得代码封装程度更高。</p><h3 id="抽象工厂"><a href="#抽象工厂" class="headerlink" title="抽象工厂"></a>抽象工厂</h3><p>为了更好的理解抽象工厂模式,先说两个概念:</p><ul><li><p>产品等级结构 :产品等级结构即产品的<code>继承结构</code>,如一个抽象类是电视机,其子类有海尔电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。</p></li><li><p>产品族 :产品族是指由同一个工厂生产的,位于<code>不同产品等级结构</code>中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。</p></li></ul><p>当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是<code>多个位于不同产品等级结构</code>中属于不同类型的具体产品时需要使用抽象工厂模式。</p><h4 id="模式的结构-2"><a href="#模式的结构-2" class="headerlink" title="模式的结构"></a>模式的结构</h4><ul><li><p>Factory-抽象工厂类</p><ul><li>定义了一组用于创建一组产品的方法,每个方法对应一种产品。</li></ul></li><li><p>ConcreteFactory-具体工厂类</p><ul><li>实现抽象工厂中定义的创建一组产品的方法,生产一组产品。一对多。</li></ul></li><li><p>Product-抽象产品类</p><ul><li>定义产品接口</li></ul></li><li><p>ConcreteProduct-具体产品类:它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。 </p></li></ul><p>抽象工厂中声明了多个工厂方法,用于创建不同类型的产品。</p><h4 id="实例分析-2"><a href="#实例分析-2" class="headerlink" title="实例分析"></a>实例分析</h4><p>这里改造一下上面生产宝马汽车的例子。现在我们不是生产汽车了而是生产汽车配件。现需要两种汽车配件:汽车发动机,汽车空调。还是有两个宝马型号的汽车 BMW320、BMW523。</p><p>产品类<br><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//发动机以及型号 </span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Engine</span> </span>{ </span><br><span class="line"> </span><br><span class="line">} </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EngineA</span> <span class="keyword">extends</span> <span class="title">Engine</span></span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">EngineA</span><span class="params">()</span></span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->EngineA"</span>); </span><br><span class="line"> } </span><br><span class="line">} </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EngineBextends</span> <span class="title">Engine</span></span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">EngineB</span><span class="params">()</span></span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->EngineB"</span>); </span><br><span class="line"> } </span><br><span class="line">} </span><br><span class="line"> </span><br><span class="line"><span class="comment">//空调以及型号 </span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Aircondition</span> </span>{ </span><br><span class="line"> </span><br><span class="line">} </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AirconditionA</span> <span class="keyword">extends</span> <span class="title">Aircondition</span></span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AirconditionA</span><span class="params">()</span></span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->AirconditionA"</span>); </span><br><span class="line"> } </span><br><span class="line">} </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AirconditionB</span> <span class="keyword">extends</span> <span class="title">Aircondition</span></span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AirconditionB</span><span class="params">()</span></span>{ </span><br><span class="line"> System.out.println(<span class="string">"制造-->AirconditionB"</span>); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p> 创建工厂类</p><figure class="highlight java"><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">//创建工厂的接口 </span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">AbstractFactory</span> </span>{ </span><br><span class="line"> <span class="comment">//制造发动机 </span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Engine <span class="title">createEngine</span><span class="params">()</span></span>; </span><br><span class="line"> <span class="comment">//制造空调 </span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Aircondition <span class="title">createAircondition</span><span class="params">()</span></span>; </span><br><span class="line">} </span><br><span class="line"></span><br><span class="line"><span class="comment">//为宝马320系列生产配件 </span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FactoryBMW320</span> <span class="keyword">implements</span> <span class="title">AbstractFactory</span></span>{ </span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> Engine <span class="title">createEngine</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> EngineA(); </span><br><span class="line"> } </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> Aircondition <span class="title">createAircondition</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> AirconditionA(); </span><br><span class="line"> } </span><br><span class="line">} </span><br><span class="line"></span><br><span class="line"><span class="comment">//宝马523系列配件 </span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FactoryBMW523</span> <span class="keyword">implements</span> <span class="title">AbstractFactory</span> </span>{ </span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> Engine <span class="title">createEngine</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> EngineB(); </span><br><span class="line"> } </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> Aircondition <span class="title">createAircondition</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> AirconditionB(); </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 class="comment">//客户端 </span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Customer</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span></span>{ </span><br><span class="line"> <span class="comment">//生产宝马320系列配件 </span></span><br><span class="line"> FactoryBMW320 factoryBMW320 = <span class="keyword">new</span> FactoryBMW320(); </span><br><span class="line"> factoryBMW320.createEngine(); </span><br><span class="line"> factoryBMW320.createAircondition(); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//生产宝马523系列配件 </span></span><br><span class="line"> FactoryBMW523 factoryBMW523 = <span class="keyword">new</span> FactoryBMW523(); </span><br><span class="line"> factoryBMW320.createEngine(); </span><br><span class="line"> factoryBMW320.createAircondition(); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="模式分析-2"><a href="#模式分析-2" class="headerlink" title="模式分析"></a>模式分析</h4><h5 id="抽象工厂模式的优点"><a href="#抽象工厂模式的优点" class="headerlink" title="抽象工厂模式的优点"></a>抽象工厂模式的优点</h5><ul><li>首先是该模式适应了某种业务场景</li><li>符合“开闭原则”:增加新的具体工厂和产品族很方便,无须修改已有系统。</li></ul><h5 id="抽象工厂模式的缺点"><a href="#抽象工厂模式的缺点" class="headerlink" title="抽象工厂模式的缺点"></a>抽象工厂模式的缺点</h5><ul><li>在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其<code>所有子类的修改</code>,显然会带来较大的不便。</li><li>开闭原则的倾斜性(增加新的工厂和产品族容易,<code>增加新的产品等级结构</code>麻烦)</li></ul><h5 id="抽象工厂模式的适用场景"><a href="#抽象工厂模式的适用场景" class="headerlink" title="抽象工厂模式的适用场景"></a>抽象工厂模式的适用场景</h5><ul><li><p>当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是<code>多个位于不同产品等级结构</code>中属于不同类型的具体产品时需要使用抽象工厂模式。</p></li><li><p>一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。</p></li><li>系统提供一个产品类的库,所有的 <code>产品以同样的接口</code>出现,从而使客户端不依赖于具体实现。</li></ul><h5 id="与工厂方法的区别"><a href="#与工厂方法的区别" class="headerlink" title="与工厂方法的区别"></a>与工厂方法的区别</h5><p>抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。</p><h3 id="工厂模式的退化"><a href="#工厂模式的退化" class="headerlink" title="工厂模式的退化"></a>工厂模式的退化</h3><ul><li>当<code>抽象工厂模式</code>中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成<code>工厂方法模式</code>;</li><li>当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成<code>简单工厂模式</code>。</li></ul><p>三个工厂模式 完。</p><p><a href="http://blog.csdn.net/jason0539/article/details/44956775" target="_blank" rel="noopener">http://blog.csdn.net/jason0539/article/details/44956775</a></p><p><a href="https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/creational.html" target="_blank" rel="noopener">https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/creational.html</a></p>]]></content>
<summary type="html">
<h2 id="设计模式-创建型之工厂模式"><a href="#设计模式-创建型之工厂模式" class="headerlink" title="设计模式-创建型之工厂模式"></a>设计模式-创建型之工厂模式</h2><p>创建型设计模式共六种:</p>
<ul>
<li>简单工厂模式</li>
<li>工厂方法模式</li>
<li>抽象工厂模式</li>
<li>建造者模式</li>
<li>原型模式</li>
<li>单例模式</li>
</ul>
<p>本篇讲三个工厂模式: 简单工厂、工厂方法、抽象工厂。</p>
</summary>
<category term="设计模式" scheme="https://foreverwang.github.io/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="设计模式" scheme="https://foreverwang.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="工厂模式" scheme="https://foreverwang.github.io/tags/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/"/>
<category term="创建型模式" scheme="https://foreverwang.github.io/tags/%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>随笔:《王者荣耀》-论网游成瘾</title>
<link href="https://foreverwang.github.io/2017/07/16/%E9%9A%8F%E7%AC%94%EF%BC%9A%E3%80%8A%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80%E3%80%8B-%E8%AE%BA%E7%BD%91%E6%B8%B8%E6%88%90%E7%98%BE/"/>
<id>https://foreverwang.github.io/2017/07/16/%E9%9A%8F%E7%AC%94%EF%BC%9A%E3%80%8A%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80%E3%80%8B-%E8%AE%BA%E7%BD%91%E6%B8%B8%E6%88%90%E7%98%BE/</id>
<published>2017-07-16T16:34:35.000Z</published>
<updated>2020-06-21T11:08:05.118Z</updated>
<content type="html"><![CDATA[<h2 id="随笔:游戏成瘾"><a href="#随笔:游戏成瘾" class="headerlink" title="随笔:游戏成瘾"></a>随笔:游戏成瘾</h2><p>以前一直不明白那些网瘾少年为什么网络游戏如此痴迷。都是些虚虚假假的游戏,有什么好沉迷的。大概正是因为我没有深入玩过任何一款网络游戏所以才有这个疑惑。就好像一直不明白传销的那些人如何被洗脑的一样,怎么会那么啥?</p><p>入手《王者荣耀》初衷是想探探网络游戏究竟是什么如此令人着迷。也断断续续的完了好长时间了。来做个记录吧。</p><a id="more"></a><h4 id="着迷的并不是游戏本身"><a href="#着迷的并不是游戏本身" class="headerlink" title="着迷的并不是游戏本身"></a>着迷的并不是游戏本身</h4><p>着迷的并不是游戏本身,而是游戏设计者在游戏中设计的很多”陷阱”。一下从简说下我的感受吧。这些陷阱包括:好胜心,贪婪,虚荣心…</p><ul><li>好胜心:我们在杀掉他人并取得胜利的时候会有爽快感觉。</li><li>贪婪:我们在赢了之后还想再赢,再输了之后还想赢回来</li><li>虚荣心:我们行不断提升段位,获得更多的MVP成就数据,因为这些能被游戏好友、微信好友看到。</li></ul><h4 id="吸引你的更多的是游戏本身之外的东西"><a href="#吸引你的更多的是游戏本身之外的东西" class="headerlink" title="吸引你的更多的是游戏本身之外的东西"></a>吸引你的更多的是游戏本身之外的东西</h4><ul><li>如果这个游戏没有了段位设定,还会有这么多人如此痴迷吗?</li><li>如果这个游戏里不是基于微信或qq帐号体系的,甚至游戏里不能互相交流也永远不知道谁是谁 还会有这么多人如此痴迷?</li></ul><h4 id="网游的根本目标是盈利"><a href="#网游的根本目标是盈利" class="headerlink" title="网游的根本目标是盈利"></a>网游的根本目标是盈利</h4><p>游戏策划者在游戏里设计那么多陷阱,最终是为了实现盈利的目标。首先要有人玩,有很多人玩,然后这其中有部分人愿意花钱买单就行了。<br>举几个例子:</p><h5 id="首冲"><a href="#首冲" class="headerlink" title="首冲"></a>首冲</h5><p>游戏里首冲一般都有奖励,这个是先建立你的支付习惯。</p><h5 id="抽奖概率"><a href="#抽奖概率" class="headerlink" title="抽奖概率"></a>抽奖概率</h5><p>每个玩家每个阶段中奖概率肯定不一样。通常前期中奖概率会高一点,好引诱你冲更多的值。后面会根据玩家的行为做概率调整,比如判定大拿玩家和普通玩家的概率,对不同阶段的概率都会有波动调整。最终为了让你花钱或化更多的钱来参与。</p><h5 id="匹配机制"><a href="#匹配机制" class="headerlink" title="匹配机制"></a>匹配机制</h5><p>这也是影响有游戏声明周期的关键环节。<br>在保证游戏的平衡性大前提下,根据你的游戏记录适当倾斜下难易程度,比如当你连赢几把的时候,肯定就要给你匹配些强对手和弱队友了。让你不那么容易的升到顶级。实时证明王者荣耀这个匹配做的很极致。</p><h4 id="社交属性"><a href="#社交属性" class="headerlink" title="社交属性"></a>社交属性</h4><p>当然网友还有一大属性是社交属性,先不说这点了…</p><h5 id="等等吧"><a href="#等等吧" class="headerlink" title="等等吧"></a>等等吧</h5><p>不断增加英雄,赛季重置等 这都是延长游戏生命周期的又一手段。<br>增加皮肤,抽奖等都是为了增加盈利…</p><h3 id="最初我们为什么玩游戏?"><a href="#最初我们为什么玩游戏?" class="headerlink" title="最初我们为什么玩游戏?"></a>最初我们为什么玩游戏?</h3><p>这里不说职业玩家,就说普通玩家。本来我们玩游戏是为了放松,休闲。然而我发现每次我玩完都会很累。当我们得到的结果不符合我们的目标预期的时候,我们就要注意了,肯定有坑。<br>然而游戏策划们就是利用人们的各种人性的弱点或者优点让你玩更久的游戏话更多的钱。而这一切都是你愿意的。</p><h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>没啥逻辑,想到啥就写了。总之手游大多是坑,他们都是为了赚钱。我已经不再主动玩这这个游戏了(好友邀请可以玩一把,这就是他的社交属性的体现了)。</p><h3 id="真正的结语"><a href="#真正的结语" class="headerlink" title="真正的结语"></a>真正的结语</h3><p>再想想这些网友的沉迷,往往并不是对有游戏的热爱和痴迷,而是更多的是对自己的暂时性失控。玩玩单机游戏吧,坑少些。<br>有个电影叫《爆裂鼓手》,其中男主的偏执最后算是一种痴迷吧。玩网友的沉迷算是一种痴迷吗?也算是吧,对职业玩家来说。也算是吧,只是并不是我们想要的。</p>]]></content>
<summary type="html">
<h2 id="随笔:游戏成瘾"><a href="#随笔:游戏成瘾" class="headerlink" title="随笔:游戏成瘾"></a>随笔:游戏成瘾</h2><p>以前一直不明白那些网瘾少年为什么网络游戏如此痴迷。都是些虚虚假假的游戏,有什么好沉迷的。大概正是因为我没有深入玩过任何一款网络游戏所以才有这个疑惑。就好像一直不明白传销的那些人如何被洗脑的一样,怎么会那么啥?</p>
<p>入手《王者荣耀》初衷是想探探网络游戏究竟是什么如此令人着迷。也断断续续的完了好长时间了。来做个记录吧。</p>
</summary>
<category term="随笔" scheme="https://foreverwang.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="随笔" scheme="https://foreverwang.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
<category term="游戏" scheme="https://foreverwang.github.io/tags/%E6%B8%B8%E6%88%8F/"/>
</entry>
<entry>
<title>深入理解JSX</title>
<link href="https://foreverwang.github.io/2017/07/09/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3JSX/"/>
<id>https://foreverwang.github.io/2017/07/09/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3JSX/</id>
<published>2017-07-09T16:06:54.000Z</published>
<updated>2020-06-21T11:08:05.117Z</updated>
<content type="html"><![CDATA[<h3 id="JSX-是啥"><a href="#JSX-是啥" class="headerlink" title="JSX 是啥?"></a>JSX 是啥?</h3><blockquote><p> JSX = JS + XML </p></blockquote><p>JSX提供了一种可以在JS里写XML的语法。</p><h3 id="JSX的运行环境"><a href="#JSX的运行环境" class="headerlink" title="JSX的运行环境"></a>JSX的运行环境</h3><blockquote><p>没有环境可以直接直接执行JSX代码 </p></blockquote><a id="more"></a><p>最终JSX代码在运行前都被转换成了JS。转换方式:</p><ul><li>静态编译器编译(工具babel)</li><li>运行期通过jsxtransform.js 进行转换。(已被facebook废弃,性能问题)</li></ul><p>思考:浏览器或者js引擎为什么不直接支持jsx? –>继续看下边</p><h3 id="JSX的由来"><a href="#JSX的由来" class="headerlink" title="JSX的由来"></a>JSX的由来</h3><p>JSX是伴随着facebook的javascript类库 react 的出现被发明的。jsx 的出现和虚拟DOM有直接关系的。</p><h5 id="一句话介绍虚拟DOM:"><a href="#一句话介绍虚拟DOM:" class="headerlink" title="一句话介绍虚拟DOM:"></a>一句话介绍虚拟DOM:</h5><blockquote><p>在内存中创建的描述DOM节点的js对象。为了减少对实际DOM的操作从而提升性能。(这里可以先不管什么是虚拟DOM)</p></blockquote><h5 id="如何创建虚拟DOM:"><a href="#如何创建虚拟DOM:" class="headerlink" title="如何创建虚拟DOM:"></a>如何创建虚拟DOM:</h5><figure class="highlight javascript"><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> child1 = React.createElement(<span class="string">'li'</span>, <span class="literal">null</span>, <span class="string">'First Text Content'</span>);</span><br><span class="line"><span class="keyword">var</span> child2 = React.createElement(<span class="string">'li'</span>, <span class="literal">null</span>, <span class="string">'Second Text Content'</span>);</span><br><span class="line"><span class="keyword">var</span> root = React.createElement(<span class="string">'ul'</span>, { <span class="attr">className</span>: <span class="string">'my-list'</span> }, child1, child2);</span><br></pre></td></tr></table></figure><p>==> ‘虚拟Dom对象 ‘ ==> </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="tag"><<span class="name">ul</span> <span class="attr">class</span>=<span class="string">"my-list"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span> 'First Text Content' <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span> 'Second Text Content' <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"><span class="tag"></<span class="name">ul</span>></span></span><br></pre></td></tr></table></figure><p>使用这样的机制,我们完全可以用JavaScript构建完整的界面DOM树,但是代码编写麻烦,可读性差(<code>IOS类似,ios是否可以借鉴jsx?</code>)。于是React发明了JSX,利用更友好的HTML语法来创建虚拟DOM:</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></pre></td><td class="code"><pre><span class="line"> var root =(</span><br><span class="line"> <span class="tag"><<span class="name">ul</span> <span class="attr">className</span>=<span class="string">"my-list"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span>First Text Content<span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span>Second Text Content<span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">ul</span>></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h5 id="一句话总结jsx存在的意义"><a href="#一句话总结jsx存在的意义" class="headerlink" title="一句话总结jsx存在的意义"></a>一句话总结jsx存在的意义</h5><blockquote><p>让我们更直观的愉快的写代码。</p></blockquote><p>前端界面的最基本功能在于展现数据,为此大多数框架都使用了模板引擎,就对应了自己的模板语法:如angular.js</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ng-if</span>=<span class="string">"person != null"</span>></span></span><br><span class="line"> Welcome back, <span class="tag"><<span class="name">b</span>></span>{{person.firstName}} {{person.lastName}}<span class="tag"></<span class="name">b</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 class="attr">ng-if</span>=<span class="string">"person == null"</span>></span></span><br><span class="line"> Please log in.</span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>jsx的优势之一就是不需要掌握一门模板语法。如果说掌握一种模板语言并不是很大的问题,那么其实由模板带来的架构复杂性则是让框架也变得复杂的重要原因:<br><a href="http://www.infoq.com/cn/articles/react-jsx-and-component" target="_blank" rel="noopener">http://www.infoq.com/cn/articles/react-jsx-and-component</a></p><p>React直接放弃了模板而发明了JSX。</p><h3 id="JSX的语法"><a href="#JSX的语法" class="headerlink" title="JSX的语法"></a>JSX的语法</h3><p>这里不会对jsx语法详细介绍,只介绍一些注意点。<a href="https://facebook.github.io/react/docs/jsx-in-depth.html" target="_blank" rel="noopener">详情</a></p><h4 id="一、通过-插入js表达式"><a href="#一、通过-插入js表达式" class="headerlink" title="一、通过{}插入js表达式"></a>一、通过{}插入js表达式</h4><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">var person = <span class="tag"><<span class="name">Person</span> <span class="attr">name</span>=<span class="string">{window.isLoggedIn</span> ? <span class="attr">window.name</span> <span class="attr">:</span> ''} /></span>;</span><br></pre></td></tr></table></figure><pre><code>这个功能就很强大了,你可以尽情的使用js的功能: </code></pre><h4 id="二、组件tag-大小写敏感"><a href="#二、组件tag-大小写敏感" class="headerlink" title="二、组件tag 大小写敏感"></a>二、组件tag 大小写敏感</h4><p>这里还不是一般的敏感,大小写是有限制的。所有对应证实节点的tag 要小写开头,所有组件节点tag 要大写开头。大小写是告诉转换工具(babel)要如何转换。我们看个例子:</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">React.render(<span class="tag"><<span class="name">div</span>></span>xxx<span class="tag"></<span class="name">div</span>></span>)</span><br><span class="line"></span><br><span class="line">React.render(<span class="tag"><<span class="name">App</span>/></span>)</span><br></pre></td></tr></table></figure><p> 转换后==></p><figure class="highlight javascript"><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">React.render(React.createElement(</span><br><span class="line"> <span class="string">"div"</span>, <span class="comment">//小写tag会编成字符串</span></span><br><span class="line"> <span class="literal">null</span>,</span><br><span class="line"> <span class="string">"xxx"</span></span><br><span class="line">));</span><br><span class="line"></span><br><span class="line">React.render(React.createElement(App, <span class="literal">null</span>));<span class="comment">//大写编成变量</span></span><br></pre></td></tr></table></figure><p>一句话总结标签大小写注意点: </p><blockquote><p>可以不用关心这些细节,这些都是给框架用的,只需要知道何时大写何时小写就好。</p></blockquote><h4 id="三、绑定事件"><a href="#三、绑定事件" class="headerlink" title="三、绑定事件"></a>三、绑定事件</h4><h5 id="绑定方式:"><a href="#绑定方式:" class="headerlink" title="绑定方式:"></a>绑定方式:</h5><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">div</span> <span class="attr">onClick</span>=<span class="string">{this.handler.bind(this)}</span>></span>Submit<span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h5 id="无需手动解绑事件"><a href="#无需手动解绑事件" class="headerlink" title="无需手动解绑事件"></a>无需手动解绑事件</h5><p>在JSX中你不需要关心什么时机去移除事件绑定,因为React会在对应的真实DOM节点移除时就自动解除了事件绑定。</p><h5 id="事件代理"><a href="#事件代理" class="headerlink" title="事件代理"></a>事件代理</h5><p>React并不会真正的绑定事件到每一个具体的元素上,而是采用事件代理的模式:在根节点document上为每种事件添加唯一的Listener,然后通过事件的target找到真实的触发元素。这样从触发元素到顶层节点之间的所有节点如果有绑定这个事件,React都会触发对应的事件处理函数。这就是所谓的React模拟事件系统。</p><h4 id="四、在JSX中使用样式"><a href="#四、在JSX中使用样式" class="headerlink" title="四、在JSX中使用样式"></a>四、在JSX中使用样式</h4><p>通常情况下我们应该把样式写到css文件里,当有时对于特定组件而言其样式比较简单且或固定,那么可将其直接写在jsx中。在jsx中通过style属性来定义,但和真实DOM不同的是,属性值不能是字符串而必须为对象。</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">div</span> <span class="attr">style</span>=<span class="string">{{color:</span> '#<span class="attr">ff0000</span>', <span class="attr">fontSize:</span> '<span class="attr">14px</span>'}}></span>Hello World.<span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>这里有两层大括号,外面的大括号是JSX的语法,变的大括号是js对象。</p><h5 id="属性名转驼峰"><a href="#属性名转驼峰" class="headerlink" title="属性名转驼峰"></a>属性名转驼峰</h5><p>在JSX中可以使用所有的的样式,基本上属性名的转换规范就是将其写成驼峰写法,如background-color –> backgroundColor。</p><h3 id="JSX-与babel"><a href="#JSX-与babel" class="headerlink" title="JSX 与babel"></a>JSX 与babel</h3><p>如何让babel将JSX传换成js时指定自定义方法名而不是React.createElement()。<br>这个babel支持配置:.babelrc文件 <a href="https://babeljs.io/docs/plugins/transform-react-jsx/" target="_blank" rel="noopener">详情</a></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"plugins"</span>: [</span><br><span class="line"> [<span class="string">"transform-react-jsx"</span>, {</span><br><span class="line"> <span class="attr">"pragma"</span>: <span class="string">"dom"</span> <span class="comment">// default pragma is React.createElement</span></span><br><span class="line"> }]</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h3 id="JSX-是啥"><a href="#JSX-是啥" class="headerlink" title="JSX 是啥?"></a>JSX 是啥?</h3><blockquote>
<p> JSX = JS + XML </p>
</blockquote>
<p>JSX提供了一种可以在JS里写XML的语法。</p>
<h3 id="JSX的运行环境"><a href="#JSX的运行环境" class="headerlink" title="JSX的运行环境"></a>JSX的运行环境</h3><blockquote>
<p>没有环境可以直接直接执行JSX代码 </p>
</blockquote>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="React" scheme="https://foreverwang.github.io/categories/javascript/React/"/>
<category term="JSX" scheme="https://foreverwang.github.io/tags/JSX/"/>
<category term="React" scheme="https://foreverwang.github.io/tags/React/"/>
</entry>
<entry>
<title>javascript函数节流(throttle)和函数防抖(debounce)及其实现</title>
<link href="https://foreverwang.github.io/2017/05/07/javascript%E5%87%BD%E6%95%B0%E8%8A%82%E6%B5%81(throttle)%E5%92%8C%E5%87%BD%E6%95%B0%E9%98%B2%E6%8A%96(debounce)%E5%8F%8A%E5%85%B6%E5%AE%9E%E7%8E%B0/"/>
<id>https://foreverwang.github.io/2017/05/07/javascript%E5%87%BD%E6%95%B0%E8%8A%82%E6%B5%81(throttle)%E5%92%8C%E5%87%BD%E6%95%B0%E9%98%B2%E6%8A%96(debounce)%E5%8F%8A%E5%85%B6%E5%AE%9E%E7%8E%B0/</id>
<published>2017-05-07T15:37:39.000Z</published>
<updated>2020-06-21T11:08:05.114Z</updated>
<content type="html"><![CDATA[<p>throttle(节流)和debounce(防抖)说来很相似:作用是防止一个函数内的某些代码在一定的时间间隔内高频率调用。当然两者也是有细微差别的,下面分别看下这两个概念。<br><a id="more"></a></p><h4 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h4><p>当遇到触发频率很高的事件时,其事件回调也会随之高频率的被调用,这样就可能会出现页面卡顿现象,为了解决这类问题,通常使用throttle(节流)或debounce(防抖)来控制事件回调主要逻辑代码被执行的频率。这些事件需要包括:</p><ul><li>鼠标事件:mousemove(拖曳)/mouseover(划过)/mouseWheel(滚屏) click(连续点击)</li><li>键盘事件:keypress(基于ajax的用户名唯一性校验)/keyup(文本输入检验、自动完成)/keydown(游戏中的射击)</li><li>window的resize/scroll事件(DOM元素动态定位)</li></ul><p>接下来看看具体概念和区别。</p><h4 id="概念及区别"><a href="#概念及区别" class="headerlink" title="概念及区别"></a>概念及区别</h4><p>函数在一定的<code>时间间隔</code>内只能被调用一次:</p><ul><li><p>debounce </p><blockquote><p>若在这段时间间隔内又想要调用函数,则将<code>重新计算</code>时间间隔。</p></blockquote></li><li><p>throttle</p><blockquote><p>若在这段时间间隔内又想调用该函数,则函数调用将会被提前阻止从而放弃这次调用。</p></blockquote><p>直到上一次调用函数后的时间间隔过后,下一次企图调用函数才能得逞。 </p><blockquote><p>注意:上面是说的是函数调用的频率,具体到业务里代码里可能不是具体某个函数而是函数内某些代码。</p></blockquote></li></ul><h4 id="打个比方"><a href="#打个比方" class="headerlink" title="打个比方"></a>打个比方</h4><p>如果上面的区别还是不太明了,那我们举个电梯的栗子吧:<br>假设电梯门在有人进入10s后会关闭,我们简称这一过程为–<code>关门指令</code>。每次有人键入电梯,电梯就<code>企图</code>创建一个<code>关门指令</code>。</p><ul><li><p>deboucce </p><blockquote><p>你进入电梯,一个<code>关门指令</code>被电梯创建;如果10s内又有人进入电梯,则这个10s的间隔会重新计算-相当于销毁上一个关门指令,重新创建一个;直到最后一个10s的间隔内没有人再进入了,电梯门才会被关闭,即最后一个关门指令生效。</p></blockquote></li><li><p>throttle</p><blockquote><p>你进入电梯,一个<code>关门指令</code>被电梯创建;如果10s内又有人进入电梯,则这段间隔内企图<code>10s后再关门</code>这个指令会被丢弃,即10s内只有第一个关门指令会生效。10s后不管还有么有人向上电梯,电梯门都将关闭。–这种电梯简直是反人类,大概只会出现在我这个例子里。</p></blockquote><p> 概念说了,比方也打了,接下来我们一步一步实现deboucce和throttle。</p><blockquote><p>再多说一句,其实你也不必非要严格区分这两个概念。你只要知道他俩都是用来避免连续高频触发一些代码的执行的。他俩的区别是debouce在指定的间隔内再次试图触发某些代码执行的时候 这个时间间隔会被重新计算,而throttle不会。然后能在具体的场景选择响应的方法就好了。</p></blockquote></li></ul><h4 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h4><p>这一部分我们通过一个实际场景一步一步分析来实现。<br><br>假设的场景:页面中有一个按钮,这个按钮点击后会执行页面跳转的动作。此时我们不希望快速点击会触发多次这个动作。</p><p>分析:</p><ul><li>因为页面跳转后通常我们是没有回调即收不到通知的,所以这里防止连续点击触发回调只能通过约定时间间隔来实现,这里我们用600ms作为这个时间间隔。</li><li>点击按钮后600ms内的点击无效。</li><li>这个事件间隔可以通过标志变量+setTimeout来实现;也可以通过前后点击的时间戳的差值来实现。这里我们先用第一种方式。</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"btn"</span>></span>我是一个按钮<span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>下面是只考虑实现上述需求,没有任何封装性。 </p><figure class="highlight javascript"><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="keyword">var</span> btn = <span class="built_in">document</span>.querySelector(<span class="string">'.btn'</span>);</span><br><span class="line"><span class="comment">//方案一:时间间隔:标志变量 + setTimeout</span></span><br><span class="line"><span class="keyword">var</span> canRun = <span class="literal">true</span>; <span class="comment">//标志变量</span></span><br><span class="line"><span class="keyword">var</span> immediate = <span class="literal">true</span>;<span class="comment">//第一次响应时是否需要执行响应的代码</span></span><br><span class="line">btn.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span>(!canRun){</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'点击太频繁'</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//只要函数被执行了就把标志变量的状态置反,直到约定的时间间隔到了再置回。</span></span><br><span class="line"> canRun = <span class="literal">false</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">handler</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'click'</span>);</span><br><span class="line"> canRun = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(immediate){</span><br><span class="line"> handler();<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"> immediate = <span class="literal">false</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> setTimeout(handler,<span class="number">600</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>标志变量canRun 初始值true;当click事件回调执行时先判断这个变量,如果为真则后面的代码会被执行。并且 标志变量canRun赋值为false;</li><li>后边的代码包括 根据immediate标志位(初始值true)是否立即调用handler;immediate为假则开一个定时器600ms后调用handler,handler会把标志变量置位true;</li><li>在上一次点击后的600ms内再次点击这时候canRun 为false,函数会直接返回,后边的代码不会被执行。</li></ul><p>功能很简单,上面就是实现了函数节流(throttle)的功能。加下来我们将throttle抽象一下封装成可供外部调用的throttle方法。</p><h4 id="throttle实现"><a href="#throttle实现" class="headerlink" title="throttle实现"></a>throttle实现</h4><p>分析:我们要实现的throttle方法只负责 是否能执行某些代码的逻辑实现。时间间隔、要执行的函数、是否立即执行 这些都是使用方决定的,即这些作为throttle方法的参数传入。<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">throlle</span>(<span class="params">handler,delay,immediate</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> canRun = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span>(!canRun){</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> canRun = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span>(immediate){</span><br><span class="line"> handler();</span><br><span class="line"> immediate = <span class="literal">false</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"> canRun = <span class="literal">true</span>;</span><br><span class="line"> }, delay);</span><br><span class="line"> }<span class="keyword">else</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"> handler(); <span class="comment">//todo:参数传递</span></span><br><span class="line"> canRun = <span class="literal">true</span>;</span><br><span class="line"> }, delay);</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><h4 id="debounce实现"><a href="#debounce实现" class="headerlink" title="debounce实现"></a>debounce实现</h4><p>分析:让我们回一下debounce和throttle的区别:debounce事件间隔会被重新计时,及在非空闲期内触发了回调,回调内会把定时器clear掉,重新创建一个定时器,事件间隔也即随之重新计算了。<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">debounce</span>(<span class="params">handler,delay,immediate</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> canRun = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">var</span> timer = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span>(!canRun){</span><br><span class="line"> clearTimeout(timer); <span class="comment">//唯一的区别就在这里</span></span><br><span class="line"> }</span><br><span class="line"> canRun = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span>(immediate){</span><br><span class="line"> handler();</span><br><span class="line"> immediate = <span class="literal">false</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"> canRun = <span class="literal">true</span>;</span><br><span class="line"> }, delay);</span><br><span class="line"> }<span class="keyword">else</span>{</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"> handler(); <span class="comment">//todo:参数传递</span></span><br><span class="line"> canRun = <span class="literal">true</span>;</span><br><span class="line"> }, delay);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>以上也只是一个简单的实现,还有些可以优化的地方。<br>除了用setTimeout来控制时间间隔,亦可以通过时间戳来实现:即比较前后两次出发的时间出之差是否大于阈值。</p>]]></content>
<summary type="html">
<p>throttle(节流)和debounce(防抖)说来很相似:作用是防止一个函数内的某些代码在一定的时间间隔内高频率调用。当然两者也是有细微差别的,下面分别看下这两个概念。<br>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="函数节流(throttle)" scheme="https://foreverwang.github.io/tags/%E5%87%BD%E6%95%B0%E8%8A%82%E6%B5%81%EF%BC%88throttle%EF%BC%89/"/>
<category term="函数防抖(debounce)" scheme="https://foreverwang.github.io/tags/%E5%87%BD%E6%95%B0%E9%98%B2%E6%8A%96%EF%BC%88debounce%EF%BC%89/"/>
</entry>
<entry>
<title>Javascript之执行上下文</title>
<link href="https://foreverwang.github.io/2017/04/09/Javascript%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/"/>
<id>https://foreverwang.github.io/2017/04/09/Javascript%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/</id>
<published>2017-04-09T08:21:50.000Z</published>
<updated>2020-06-21T11:08:05.114Z</updated>
<content type="html"><![CDATA[<h3 id="关键词"><a href="#关键词" class="headerlink" title="关键词"></a>关键词</h3><ul><li>执行期上下文(excution context简称EC):简称上下文(又叫执行环境)</li><li>作用域链 (scope chain)</li><li>变量对象(variable object简称VO)</li><li>活动对象(activation object简称AO)</li></ul><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>执行上下文是Javascript中最重要的一个概念。每一段代码的执行都与它息息相关。理解了它,才能真正理解我们写的的javascript代码是如何运行的。<br>ECMA-262(5.1)中写道:当控制器转入 ECMAScript的可执行代码时,控制器会进入(注:创建)一个执行上下文。<br><a id="more"></a></p><h4 id="可执行代码"><a href="#可执行代码" class="headerlink" title="可执行代码"></a>可执行代码</h4><p>这里先解释下上面提到的可执行代码,我们看一下ES5规范(以下从简了)</p><ul><li>全局代码:是指被作为ECMAScript程序处理的源代码文本。一个特定程序的全局代码不包括作为函数体被解析的源代码文本。</li><li>函数代码:是指作为函数体被解析的源代码文本。不包括作为其嵌套函数的 函数体 被解析的源代码文本。</li><li>Eval 代码 是指提供给eval内置函数的源代码文本。(现在应用比较少,这一点本文忽略)</li></ul><p>现在可以对上面关于上下文的说法换一种说法:<br></p><ul><li>当控制器转入 ECMAScript的全局代码,控制器会创建一个全局上下文。</li><li>当转入函数代码,控制器会创建一个函数上下文。</li></ul><h3 id="执行上下文"><a href="#执行上下文" class="headerlink" title="执行上下文"></a>执行上下文</h3><p>我们已经知道了什么时候会创建一个上下文,那接下来就要说下上下文具体是个什么东西。<br><br>ES5规范里说:执行上下文包含所有用于追踪与其相关的代码的执行进度的状态。<br><br>翻译一下是说:执行上下文里有代码执行时需要用到的东西。<br><br>现在问题就具体到了:执行上下文里都有什么?那么我们来看一下:</p><h4 id="执行上下文的组成"><a href="#执行上下文的组成" class="headerlink" title="执行上下文的组成"></a>执行上下文的组成</h4><table><thead><tr><th>组件</th><th>内部组成</th><th>作用</th></tr></thead><tbody><tr><td>变量对象(VO)</td><td>{vars:…,function declarations:…,arguments:…,…}</td><td>指定一个词法环境对象,其环境数据用于保存由该执行上下文内的变量声明 和 函数声明。</td></tr><tr><td>作用域链(scope chain)</td><td>[ variable object + [[scope]])</td><td>指定一个词法环境对象,用于解析该执行环境内的代码创建的标识符引用。</td></tr><tr><td>this指针</td><td></td><td>指定该执行上下文内的 ECMAScript代码中this关键字所关联的值。</td></tr></tbody></table><p>为了方便理解,一个执行上下文可以抽象为object。每一个执行上下文都有一系列的属性(我们称为上下文状态,即上面表格做左侧组件一列的属性)。接下来就对上面表格中的三个状态属性来一一说明。</p><h4 id="变量对象-(VO)"><a href="#变量对象-(VO)" class="headerlink" title="变量对象 (VO)"></a>变量对象 (VO)</h4><p>变量对象是与执行上下文相关的数据作用域(scope of data) ,用于存储被定义在上下文中的变量声明和函数声明(注意:不包括函数表达式<a href="http://www.cnblogs.com/TomXu/archive/2011/12/29/2290308.html" target="_blank" rel="noopener">详见</a>) 。<br><br>定义已经很明确了,我们通过具体的栗子看下全局上下文的VO:</p><figure class="highlight javascript"><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> foo = <span class="number">2</span>; </span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>{} <span class="comment">// 函数声明</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span> <span class="title">baz</span>(<span class="params"></span>) </span>{}); <span class="comment">// 函数表达式 </span></span><br><span class="line"><span class="built_in">console</span>.log(</span><br><span class="line"> <span class="keyword">this</span>.foo == foo, <span class="comment">// true</span></span><br><span class="line"> <span class="built_in">window</span>.bar == bar <span class="comment">// true</span></span><br><span class="line">); </span><br><span class="line"><span class="built_in">console</span>.log(baz); <span class="comment">// Uncaught ReferenceError: baz is not defined</span></span><br></pre></td></tr></table></figure><p>该例中全局上下文中变量对象会有以下属性: </p><table><thead><tr><th>global VO</th><th></th></tr></thead><tbody><tr><td>foo</td><td>undefined –> 10</td></tr><tr><td>bar</td><td>function…</td></tr><tr><td>built-ins(忽略)</td><td>一些内置的全局变量</td></tr></tbody></table><p>这是全局上下文中的变量对象,除了全局上下文还有函数上下文,那么函数上下文是否是一样的呢?答案是稍微有点不一样:函数内活动对象(AO)用作变量对象。接下来我们看下AO。<br></p><blockquote><p>在global全局上下文中,变量对象也是全局对象自身[global object],如浏览器中是window,此时我们可以通过全局对象的属性来指向全局变量,通过this来访问全局对象。然而在函数上下文内我们是无法直接访问这个对象的,this也不指向这个对象。<br></p></blockquote><h4 id="函数的活动对象(AO)"><a href="#函数的活动对象(AO)" class="headerlink" title="函数的活动对象(AO)"></a>函数的活动对象(AO)</h4><p>当函数被调用者激活(即函数调用),活动对象 就被创建了。它包含普通参数即形参(formal parameters) 与特殊参数(arguments)对象(具有索引属性的参数映射表)。活动对象在函数上下文中作为变量对象使用。</p><p> 还是举个栗子看下函数的活动对象:<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">x, y</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(z); <span class="comment">//undefined</span></span><br><span class="line"> <span class="keyword">var</span> z = <span class="number">3</span>;</span><br><span class="line"> <span class="built_in">console</span>.log(z);<span class="comment">//3</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>{} <span class="comment">// 函数声明</span></span><br><span class="line"> (<span class="function"><span class="keyword">function</span> <span class="title">baz</span>(<span class="params"></span>) </span>{}); <span class="comment">// 函数表达式</span></span><br><span class="line">}</span><br><span class="line">foo(<span class="number">1</span>, <span class="number">2</span>);</span><br></pre></td></tr></table></figure></p><p> 上面当foo被调用时,foo函数上下文的活动对象(AO)被创建,其内容如下表格:</p><table><thead><tr><th>Activation object</th><th></th></tr></thead><tbody><tr><td>x</td><td>1</td></tr><tr><td>y</td><td>2</td></tr><tr><td>arguments</td><td>{ {0:{x:1}, {1:{y:1}} }</td></tr><tr><td>z</td><td>undefined–>3</td></tr><tr><td>bar</td><td>funciton</td></tr></tbody></table><p>前面我们已经知道变量对象中不包含函数表达式,这里活动对象内也不包括函数表达式。</p><p>我们已经知道了VO和AO的内容组成了,那么他们是什么时候被创建的,又是什么时候改变的呢?<br></p><blockquote><p>VO|AO在每次创建上下文时作为上下文的组成部分被创建,并填入初始值;值的更新出现在代码执行阶段。<br></p></blockquote><p>上面两个例子中我们知道了,全局上下文中,我们定义的变量和声明的函数都作为全局上下文的变量对象的属性来保存,代码执行时标志符的查找也是从全局上下文的变量对象中查找。在某一个函数上下文中也类似,在当前上下文中的标志符会先在当前变量对象(函数上下文中是活动对象)中查找。但是当在当前上下文的变量对象找不到的时候会发生什么呢?答案是:完整的标志符解析是通过作用域链(scope chain)机制来完成的。(不容易啊,终于比较自然的把作用域链给引出来了)。那么我们来看一下什么是作用域链。</p><h4 id="作用域链(scope-chain)"><a href="#作用域链(scope-chain)" class="headerlink" title="作用域链(scope chain)"></a>作用域链(scope chain)</h4><p>作用域链是一个 对象列表(list of objects) ,用以检索上下文代码中出现的 标识符(identifiers) 。先说结果:</p><blockquote><p>scope chain = VO|AO + [[scope]] </p></blockquote><p>依然看个栗子:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">1</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{ </span><br><span class="line"> <span class="keyword">var</span> y = <span class="number">2</span>; </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(x + y);</span><br><span class="line"> } </span><br><span class="line"> <span class="keyword">return</span> bar; </span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">foo()(); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure></p><p>这段代码执行完,共创建了三个执行上下文:执行流进入这段全局代码会创建一个全局上下文,foo调用时创建foo上下文,bar被调用时创建bar上下文(foo()–>bar调用)。<br>这里我们暂且只看一下bar的上下文,我们已经知道一个上下文的数据域即一个上下文的变量对象,在函数上下文即是活动对象(AO),那我们就看下bar函数的活动对象。<br><br>当执行流进入bar函数,且函数体内代码执行之前,javascript引擎会创建这个函数的AO:</p><table><thead><tr><th>AO</th><th></th></tr></thead><tbody><tr><td>arguments</td><td>[callee: function, Symbol(Symbol.iterator): function]</td></tr></tbody></table><p>我们看到bar函数提内代码执行之前创建的这个AO并没有函数体中需要用到的变量x和y,那么当代码执行到console.log(x + y)时,x和y是从哪里读取的呢?不难看出:y存在创建的foo函数上下文的活动对象中;x存在创建的全局上下文的变量对象中。好像是函数内可以访问到函数祖父级上下文的变量对象里的东西。实际上也是这样的。而能访问到祖父上下文的变量对象,正是通过函数的一个内部属性–[[scope]]实现的。</p><h5 id="scope"><a href="#scope" class="headerlink" title="[[scope]]"></a>[[scope]]</h5><p>[[scope]]是ECMA262规定的对象的私有属性,理论上只有JS引擎可以访问。</p><ul><li>[[scope]]是所有祖父变量对象的层级链。</li><li>[[scope]]在函数创建时被存储--静态(不变的),直至函数销毁。</li></ul><p>拓展阅读<a href="http://www.cnblogs.com/TomXu/archive/2012/01/18/2312463.html" target="_blank" rel="noopener">例子</a>。</p><p>所以一个函数内代码执行时,函数内遇到的标志符就是这样先从当前上下文的活动对象内查找,若找不着继续查找父上下文的变量对象,直到查到全局上下文。而这也正是javascript中的<code>变量标志符查找机制</code>。</p><h5 id="标志符解析机制"><a href="#标志符解析机制" class="headerlink" title="标志符解析机制"></a>标志符解析机制</h5><p>我们直接看规范(ES3 10.1.4章节):</p><p>每个执行上下文都有一个与之相关联的作用域链(scope chain)。作用域链是一个由对象组成的链表,求值标志符的时候会对它搜索。当创建一个上下文时,根据当前上下文的代码类型(全局代码或函数代码)一个作用域链被创建,并用初始化对象填充(函数代码被填充为函数的[[scope]]属性值-接下来讲;全局代码初始化为空吧。当VO|AO被创建后,VO|AO被推到作用域链的前端)。一个上下文中代码执行时,其作用域链只会被 with 声明(见 12.10)和 catch 语句(见 12.14)所影响。标志符解析的具体规则:</p><ol><li>获取作用域链中的下一个对象。如果没有,转到步骤 5。</li><li>调用结果(1) 的 [[HasProperty]] 方法,把标识符(Identifier)作为属性名传递。</li><li>如果结果(2) 为true,返回一个引用类型的值,其基对象(base objecqit)是结果(1),其property name是该Identifier。<br>符。</li><li>转到步骤 1。</li><li>返回一个引用类型的值,其base object为 null,其property name 是Identifier。<br>求值标识符的结果总是一个引用类型的值,其成员名字组件与标识符字符串相等。</li></ol><blockquote><p>注:Reference(引用)类型的值是JS引擎使用的一种数据类型,它分为base object和property name两个部分。假设在JS代码中有obj.prop这样的表达式,那么解释成Reference类型,base object是对象obj,而property name是字符串”prop”。–winter</p></blockquote><h4 id="this"><a href="#this" class="headerlink" title="this"></a>this</h4><p>至此上下文里还有this没说,我的总结是:this通常指向激活当前上下文的那个对象。this值在进入上下文时确定,并且在上下文运行期间不能被改变。<br><br><figure class="highlight javascript"><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">console</span>.log(<span class="keyword">this</span>);<span class="comment">// global object</span></span><br><span class="line"><span class="keyword">var</span> obj = { <span class="attr">a</span>: <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="keyword">this</span>)</span><br><span class="line"> }</span><br><span class="line"> }; </span><br><span class="line"> obj.a();<span class="comment">//obj 可以理解为:obj.a的上下文是被obj这个对象激活的。</span></span><br></pre></td></tr></table></figure></p><p>当然this绑定还有一些其他规则。详见另一篇博文<a href="https://foreverwang.github.io/2017/03/27/Javascript%E8%AF%AD%E8%A8%80%E4%B9%8B%E8%9C%9C%E6%B1%81this/">javascript之蜜汁this</a>。</p><h4 id="总结一下上下文:"><a href="#总结一下上下文:" class="headerlink" title="总结一下上下文:"></a>总结一下上下文:<br></h4><p>上下文分为全局上下文和函数上下文。<br><br>全局上下文没啥好总结的,我们就看在函数上下文的整个生命周期。</p><p>执行流进入一个函数时,引擎不是简单的立即执行函数体内的代码,可分为两个阶段:</p><ul><li><p>第一阶段:上下文创建阶段</p><ul><li>一个函数上下文被创建,同时作为上下文的一部分的作用域链也被创建,并被初始化为函数[[scope]]属性的值。</li><li>接着javascript引擎创建当前上下文的活动对象(AO),并且将AO推入作用域链的最前端。</li><li>确定this的值。</li></ul></li><li><p>第二阶段:代码逐行执行–变量赋值、函数引用等 。</p></li></ul><h4 id="上下文栈(EC)"><a href="#上下文栈(EC)" class="headerlink" title="上下文栈(EC)"></a>上下文栈(EC)</h4><p>以上部分我们对单个上下文的创建和组成做了详细的说明。而一段代码的执行往往是涉及到很多个上下文。而在代码执行过程中这些上下文之间是什么关系呢? <br><br>活动的上下文在逻辑上组成一个上下文栈。栈底是全局上下文,而栈顶是当前激活的上下文。<br><br>当js引擎执行全局代码前,会首先创建一个全局上下文。全局上下文创建完毕后,全局代码开始逐行被执行。代码执行过程中当一个函数被调用时,此时引擎会创建一个函数上下文,并且将其<code>推入</code>到上下文栈顶。引擎总是执行当前在栈顶的上下文的代码,函数执行完毕,上下文栈将该函数上下文<code>弹出</code>,控制权返回给之前的上下文。ECMAScript程序中的执行流正式被这个机制控制者。</p><h5 id="相关推荐:"><a href="#相关推荐:" class="headerlink" title="相关推荐:"></a>相关推荐:</h5><ul><li><a href="http://www.cnblogs.com/TomXu/archive/2012/01/12/2308594.html" target="_blank" rel="noopener">大叔的深入理解JavaScript系列(10):JavaScript核心</a></li><li><a href="http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/" target="_blank" rel="noopener">What is the Execution Context & Stack in JavaScript?</a> <a href="http://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/" target="_blank" rel="noopener">(译文)</a></li><li><a href="http://www.cnblogs.com/winter-cn/archive/2008/07/07/1237168.html" target="_blank" rel="noopener"> winter的JavaScript中的[[scope]]和Scope Chain</a></li></ul><h5 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h5><p>本文主要参考了<a href="http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf" target="_blank" rel="noopener">ES3规范的第10章节Execution Contexts</a>。行文上部分参考上面两篇博文。写的过程中,发现把这个主题涉及的内容很连贯的串起来还是不容易的。而网上的关于该主题的博文也没有(我没找到)很好的把这些内容连贯的串起来的。所以整理此文。不管怎样,至少自己梳理来了一边,更清晰了。<br>执行上下文大概就这些东西了吧。这大概也是整个javascript这门语言最核心且没有之一的东西了。</p>]]></content>
<summary type="html">
<h3 id="关键词"><a href="#关键词" class="headerlink" title="关键词"></a>关键词</h3><ul>
<li>执行期上下文(excution context简称EC):简称上下文(又叫执行环境)</li>
<li>作用域链 (scope chain)</li>
<li>变量对象(variable object简称VO)</li>
<li>活动对象(activation object简称AO)</li>
</ul>
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>执行上下文是Javascript中最重要的一个概念。每一段代码的执行都与它息息相关。理解了它,才能真正理解我们写的的javascript代码是如何运行的。<br>ECMA-262(5.1)中写道:当控制器转入 ECMAScript的可执行代码时,控制器会进入(注:创建)一个执行上下文。<br>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="作用域链" scheme="https://foreverwang.github.io/tags/%E4%BD%9C%E7%94%A8%E5%9F%9F%E9%93%BE/"/>
<category term="执行期上下文" scheme="https://foreverwang.github.io/tags/%E6%89%A7%E8%A1%8C%E6%9C%9F%E4%B8%8A%E4%B8%8B%E6%96%87/"/>
<category term="javascript核心" scheme="https://foreverwang.github.io/tags/javascript%E6%A0%B8%E5%BF%83/"/>
</entry>
<entry>
<title>Javascript语言之蜜汁this</title>
<link href="https://foreverwang.github.io/2017/03/26/Javascript%E8%AF%AD%E8%A8%80%E4%B9%8B%E8%9C%9C%E6%B1%81this/"/>
<id>https://foreverwang.github.io/2017/03/26/Javascript%E8%AF%AD%E8%A8%80%E4%B9%8B%E8%9C%9C%E6%B1%81this/</id>
<published>2017-03-26T17:51:38.000Z</published>
<updated>2020-06-21T11:08:05.114Z</updated>
<content type="html"><![CDATA[<p>this关键字是Javascript中最重要的机制之一。关于介绍他的文章也比比皆是,而大多都浅尝辄止,浮于表面,不够系统。故整理此篇。</p><h3 id="先看几个题目"><a href="#先看几个题目" class="headerlink" title="先看几个题目"></a>先看几个题目</h3><p>题目1 //默认绑定 严格模式 非严格模式</p><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> a = <span class="string">'2'</span>;</span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="meta"> 'use strict'</span>;</span><br><span class="line"> foo(); </span><br><span class="line">})()</span><br></pre></td></tr></table></figure><a id="more"></a><p>题目2 //隐式绑定<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</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">var</span> obj2 = {</span><br><span class="line"> a: <span class="number">2</span>,</span><br><span class="line"> foo:foo</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> obj1 = {</span><br><span class="line"> a: <span class="number">1</span>,</span><br><span class="line"> obj2: obj2</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">obj1.obj2.foo()</span><br></pre></td></tr></table></figure></p><p>题目3 </p><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">var</span> o = {<span class="attr">a</span>:<span class="number">3</span>,<span class="attr">foo</span>:foo};</span><br><span class="line"><span class="keyword">var</span> p = {<span class="attr">a</span>:<span class="number">4</span>};</span><br><span class="line"></span><br><span class="line">(p.foo = o.foo)() </span><br><span class="line"></span><br><span class="line"><span class="comment">//知识点:赋值表达式的返回值</span></span><br></pre></td></tr></table></figure><p>题目4<br><figure class="highlight javascript"><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><br><span class="line"> a: <span class="number">1</span>,</span><br><span class="line"> foo: <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="keyword">this</span>.a);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> obj2 = { <span class="attr">a</span>: <span class="number">2</span> };</span><br><span class="line"><span class="keyword">var</span> obj3 = { <span class="attr">a</span>: <span class="number">3</span>};</span><br><span class="line">obj1.foo.bind(obj2).call(obj3)</span><br></pre></td></tr></table></figure></p><pre><code>//知识点: bind实现</code></pre><p> 上面4个题目的答案都是:2 。<br><br> 如果你都答对了,那可以继续往下看了,以下是正文。本文先说为什么要用this,再说怎么用this。</p><h3 id="为什么要用this"><a href="#为什么要用this" class="headerlink" title="为什么要用this?"></a>为什么要用this?</h3><p>先看以下这段简单的代码<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">speak</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> greeting = <span class="string">"Hello, I'm "</span> + <span class="keyword">this</span>.name;</span><br><span class="line"> <span class="built_in">console</span>.log( greeting );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> me = {</span><br><span class="line"> name: <span class="string">"小a"</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> you = {</span><br><span class="line"> name: <span class="string">"小b"</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">speak.call( me ); <span class="comment">// Hello, I'm 小a</span></span><br><span class="line">speak.call( you ); <span class="comment">// Hello, I'm 小b</span></span><br></pre></td></tr></table></figure></p><ul><li>以上代码可以在不同上下文对象(me 和 you)中重复使用speak函数。</li><li>如果不使用this,就要给 speak显示传入一个上下文对象(如下)</li></ul><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">speak</span>(<span class="params">content</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> greeting = <span class="string">"Hello, I'm "</span> + content.name;</span><br><span class="line"> <span class="built_in">console</span>.log( greeting );</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">speak(me); <span class="comment">// Hello, I'm 小a</span></span><br><span class="line">speak(you); <span class="comment">// Hello, I'm 小b</span></span><br></pre></td></tr></table></figure><p>显然,this提供了一种更优雅的方式来隐式传递一个对象的引用,因此可以使API设计得更加简洁且易于复用。<br>随着使用的模式越来越复杂,显示传递上下文对象会让代码变得越来越混乱,使用this则不会这样。</p><h3 id="this是什么?"><a href="#this是什么?" class="headerlink" title="this是什么?"></a>this是什么?</h3><p>说起this,这要看执行上下文是什么。javascript中根据可执行的种类分全局执行上下文和函数执行上下文(ES6有了块级上下文)。(关于执行上下文见另一篇<a href="https://foreverwang.github.io/2017/04/09/Javascript%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/">博文</a>)<br>在全局执行上下文中(在任何函数体外部)很简单,this指向全局对象(不管是否是严格模式下)。以下说的都是在函数上下文中的this。</p><h4 id="this常见误区"><a href="#this常见误区" class="headerlink" title="this常见误区"></a>this常见误区</h4><ul><li>this 不指向函数自身(就没必要附加例子了)</li><li><p>this 任何情况下也不指向函数的词法作用域(es6 箭头函数 本身没有this,那是他外部作用域的this) </p><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">this</span>.bar();</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">foo()<span class="comment">//undefined</span></span><br></pre></td></tr></table></figure><p> 这段代码试图通过this联通 foo和bar的词法作用域,从而让bar可以访问foo作用域里的变量a,当然这里是不能如愿的。不能使用this来引用一个词法作用域内部的东西。</p></li></ul><h4 id="this到底是啥"><a href="#this到底是啥" class="headerlink" title="this到底是啥"></a>this到底是啥</h4><p> this 是在运行时进行绑定的,并不是在编写时绑定,他的上下文取决于函数调用时的各种条件。<br> (this的值只和函数调用有关,和函数定义无关—这个说法是不对的,有两个例外:bind 和箭头函数,后面说)<br> 当一个函数被调用的时候,进入这个函数的执行上下文。执行上下文里包含:函数的活动对象(函数传入的参数等);作用域链 (函数调用栈等);this就是上下文的其中一个属性,会在函数执行过程中用到。</p><h3 id="函数作用域内this绑定规则(4个)"><a href="#函数作用域内this绑定规则(4个)" class="headerlink" title="函数作用域内this绑定规则(4个)"></a>函数作用域内this绑定规则(4个)</h3><p>先确定函数的调用位置,调用栈</p><h4 id="1-默认绑定"><a href="#1-默认绑定" class="headerlink" title="1.默认绑定"></a>1.默认绑定</h4><p>先说结论:</p><ul><li>什么是默认绑定: 非严格模式下,this指向全局对象;严格模式下,this为undefined</li><li>何时会走默认绑定:独立函数调用</li></ul><p>函数在不带任何修饰的进行调用(即独立函数调用)的时候,this会走默认绑定规则。<br><br>非严格模式下:this指向全局对象<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">foo();<span class="comment">//2</span></span><br></pre></td></tr></table></figure></p><p>严格模式下:this为undefined<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="meta"> 'use strict'</span>;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">foo()<span class="comment">//Cannot read property 'a' of undefined</span></span><br></pre></td></tr></table></figure></p><p>这里要注意一点, 对于默认绑定,决定this绑定对象的不是调用位置是否处于严格模式,而是函数体是否处于严格模式,这也是题目1中 看似是在严格模式下,this却绑定到了全局对象<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="comment">//foo函数体处于非严格模式</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> a = <span class="string">'2'</span>;</span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="meta"> 'use strict'</span>;</span><br><span class="line"> <span class="comment">//该立即执行的函数表达式内处于严格模式这里的this为undefined</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>);<span class="comment">//undefined</span></span><br><span class="line"> foo(); <span class="comment">//2</span></span><br><span class="line">})()</span><br></pre></td></tr></table></figure></p><h4 id="2-隐式绑定"><a href="#2-隐式绑定" class="headerlink" title="2.隐式绑定"></a>2.隐式绑定</h4><p>先说结论:<br><br>当函数被调用时,函数引用有上下文对象,隐式绑定规则会把函数调用中的this绑定到这个上下文对象<br>再看题目2<br><br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</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">var</span> obj2 = {</span><br><span class="line"> a: <span class="number">2</span>,</span><br><span class="line"> foo:foo</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> obj1 = {</span><br><span class="line"> a: <span class="number">1</span>,</span><br><span class="line"> obj2: obj2</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">obj1.obj2.foo() <span class="comment">//2</span></span><br><span class="line"><span class="comment">//对象属性引用链中只有最顶层(属性的直接调用方)会影响调用位置</span></span><br></pre></td></tr></table></figure></p><p><br><br> 隐式丢失(严格来说此处并没有绑定过,也没有丢失一说,只是看上去像是丢失了)<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><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"> foo: foo</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="keyword">var</span> bar = obj.foo;<span class="comment">//函数别名</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">bar();</span><br></pre></td></tr></table></figure></p><p> 虽然bar是obj.foo的一个引用,实际上,他引用的是foo本身,此时的bar调用 就是一个不带任何修饰的函数调用,因此应用了默认绑定。<br><br> 其实本例有两个关键点:</p><ul><li>赋值表达式的返回值(犀牛书6版81页): var bar = obj.foo;返回的是右值,右值指向foo的引用</li><li>不管如何引用,只看调用时 调用是bar() —> foo()</li></ul><p>此时再看题目3,就很清晰了</p><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">var</span> o = {<span class="attr">a</span>:<span class="number">3</span>,<span class="attr">foo</span>:foo};</span><br><span class="line"><span class="keyword">var</span> p = {<span class="attr">a</span>:<span class="number">4</span>};</span><br><span class="line"></span><br><span class="line"><span class="comment">//o.foo();//3</span></span><br><span class="line">(p.foo = o.foo)() <span class="comment">//p.foo = o.foo的返回值是等号的右值-->foo的引用,此处相当于直接调用foo()</span></span><br><span class="line"><span class="comment">//p.foo()//4</span></span><br></pre></td></tr></table></figure><h4 id="3-显示绑定"><a href="#3-显示绑定" class="headerlink" title="3.显示绑定"></a>3.显示绑定</h4><p>先说结论:</p><ul><li>函数调用时通过 apply 或者 call 硬绑定this 对象(apply和call的区别这里就不赘述了)</li><li>通过ES5的 Function.prototype.bind 返回一个绑定了this了的新函数</li></ul><p>看一个硬绑定的典型应用场景:接受不确定参数<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">sth</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="keyword">this</span>.a,sth );</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.a + sth;</span><br><span class="line">}</span><br><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">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> bar = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> foo.apply(obj,<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> b = bar(<span class="number">1</span>);</span><br><span class="line"><span class="built_in">console</span>.log(b);<span class="comment">// 2</span></span><br></pre></td></tr></table></figure></p><p>另一种方法是创建一个可复用的辅助函数(bind)<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">sth</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="keyword">this</span>.a,sth );</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.a + sth;</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="function"><span class="keyword">function</span> <span class="title">bind</span>(<span class="params">fn,obj</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> fn.apply(obj,<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> obj = {</span><br><span class="line"> a: <span class="number">1</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> bar = bind(foo,obj);</span><br><span class="line"><span class="keyword">var</span> b = bar(<span class="number">1</span>);</span><br><span class="line"><span class="built_in">console</span>.log(b);<span class="comment">//2</span></span><br></pre></td></tr></table></figure></p><p> 由于硬绑定是一种很常用的模式,ES5提供了内置的Function.prototype.bind方法:<br><br> <code>bind方法</code>(犀牛书第6版190页)将返回一个新的函数,以函数调用的方式调用新的函数将会把原始函数当做bind的第一个参数的方法来调用,传入新函数的任何实参都将传入原始函数。根据定义,用ES3很容易模拟bind方法,即上面代码中我们自定义的bind函数(这里只是把最基本的绑定this模拟了,参数柯里化等没有模拟,可以看MDN bind的polyfill写法,这里不贴了)。<br> <br><br>此时,再看题目4应该就很清晰了:<br><figure class="highlight javascript"><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><br><span class="line"> a: <span class="number">1</span>,</span><br><span class="line"> foo: <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="keyword">this</span>.a);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">var</span> obj2 = { <span class="attr">a</span>: <span class="number">2</span> };</span><br><span class="line"><span class="keyword">var</span> obj3 = { <span class="attr">a</span>: <span class="number">3</span>};</span><br><span class="line">obj1.foo.bind(obj2).call(obj3)</span><br></pre></td></tr></table></figure></p><p> 最后一行中 obj1.foo.bind(obj2) 等价于以下<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>)</span>{</span><br><span class="line"> obj1.foo.apply(obj2,<span class="built_in">arguments</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p> 所以foo.bind(obj2)后返回了一个新的函数,这个函数被调用的时候的this被绑定到obj2,再call(obj3) 也是改变不了这个事实。</p><p> bind方法和this的关系就是以上了,由于bind方法实在是很重要并且很好用,所以这里把bind方法说完。<br> <br></p><p> bind方法不仅是把函数绑定至一个对象,他还附带其他功能:<br> <br></p><ul><li>bind()的另一个常用的场景是使一个函数拥有<code>预设的初始参数</code>。<br>这些参数(如果有的话)作为bind()的第二个参数开始跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,调用绑定函数时传递给绑定函数的参数会跟在它们的后面。这个附带的应用即是函数式编程中的”柯里化”(currying)的一种。</li></ul><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">y,z</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.x + y + z;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> baz = fo.bind({<span class="attr">x</span>:<span class="number">1</span>},<span class="number">2</span>);</span><br><span class="line">baz(<span class="number">3</span>);</span><br></pre></td></tr></table></figure><p> bind方法返回一个函数对象我们叫<code>绑定函数</code>,绑定函数的length属性值是被调函数(绑定函数的目标函数)的形参个数减去绑定实参个数(length值不能小于0)。</p><ul><li>bind返回的函数被<code>用作构造函数</code>,将忽略传入bind的this,原始函数就会以构造函数的形式调用。bind时传入的实参会原封不动的传入原始函数,调用绑定函数是传入的参数紧跟其后。 </li></ul><h4 id="4-new-绑定"><a href="#4-new-绑定" class="headerlink" title="4.new 绑定"></a>4.new 绑定</h4><p>先看下new 一个构造函数都发生了什么:</p><ul><li>新建一个新对象(继承自Constructor.prototype)</li><li>将构造函数的作用域赋给新对象,因此this指向这个新对象</li><li>执行构造函数内的代码(为这个对象添加属性)</li><li>隐式返回这个对象(如果构造函数内没有显示return 非null对象值)</li></ul><p>很清晰,new 的时候 this绑定到 创建的新对象。通常是返回的那个对象实例。<br>多说一句,起始在js中没有构造函数一说,只有构造调用一说。 </p><h4 id="绑定优先级"><a href="#绑定优先级" class="headerlink" title="绑定优先级"></a>绑定优先级</h4><p>四种绑定规则说完了,那么问题来了,如果有多重规则同时作用时,优先级是怎样的呢?<br>先回顾一下四种规则:</p><ul><li>默认绑定(严格模式、非严格模式)</li><li>隐式绑定(对象属性调用)</li><li>显示绑定(apply/call、bind)</li><li>new 绑定</li></ul><p>直接说结论吧 <br><br>new 绑定 > 显示绑定 > 隐士绑定<br><br>在没有前三种绑定规则的时候就应用默认绑定。</p><p>至此,貌似事件处理函数中的 this还没说。事件处理函数又分DOM2级事件处理函数(addEventListener);和内联事件处理函数。前者指向绑定事件的DOM可以归到显示绑定吧,只是这一步引擎帮你做了。后者this指向全局对象属于默认绑定,不再赘述。</p><h3 id="绑定例外"><a href="#绑定例外" class="headerlink" title="绑定例外"></a>绑定例外</h3><p>在规则的世界里一切秩序井然,然而世事总有些例外,this绑定也不例外。</p><h4 id="被忽略的this"><a href="#被忽略的this" class="headerlink" title="被忽略的this"></a>被忽略的this</h4><p>当把 null或者undefined作为this的绑定对象传入 call,apply,bind,这些值在调用时会被忽略,通常实际应用的是默认绑定规则。<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">foo.call(<span class="literal">null</span>);<span class="comment">//2</span></span><br></pre></td></tr></table></figure></p><p>既然有这个机制,通常他就应该是有用的。那什么时候我们会用到null 、nudefined值呢?<br>当你只想用来传递参数而不关心this的话,这是个不错的选择</p><figure class="highlight javascript"><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">//使用apply(...)来展开一个数组,并当做参数传入一个函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">a,b</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a:'</span>+a,<span class="string">'b:'</span>+b);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//当然,ES6中我们可以用 ...操作符来代替apply展开数组了</span></span><br><span class="line"></span><br><span class="line">foo.apply(<span class="literal">null</span>,[<span class="number">2</span>,<span class="number">3</span>]) <span class="comment">//a:2,b:3</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//使用bind(...)对参数进行柯里化(预设一些参数)</span></span><br><span class="line"><span class="keyword">var</span> bar = foo.bind(<span class="literal">null</span>,<span class="number">2</span>);</span><br><span class="line">bar(<span class="number">3</span>);<span class="comment">//a:2,b:3</span></span><br></pre></td></tr></table></figure><p>至此,this讲解就接近尾声了–还有关于this的最后一点:this词法。</p><h3 id="this词法"><a href="#this词法" class="headerlink" title="this词法"></a>this词法</h3><p> 我们知道javascript使用了词法作用域,但javascript的this机制某种程度上很像动态作用域,因为this的值通常跟函数调用有关,而跟词法作用域无关。<br><br> 事情一直到ES6之前是这样的。ES6箭头函数使得this的值和词法作用域联系起来。</p><ul><li>箭头函数里this值不使用前面的任何规则,而是定义该箭头函数时执行上下文里this的值。</li><li>箭头函数的this绑定无法被修改 (new 也不行)</li><li>(实际上箭头函数内没有自己的this)</li></ul><p>又到了举个栗子的时候了:<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">return</span> <span class="function">(<span class="params">a</span>) =></span> {</span><br><span class="line"><span class="comment">//this继承自foo</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</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> obj1 = {<span class="attr">a</span>:<span class="number">2</span>};</span><br><span class="line"><span class="keyword">var</span> obj2 = {<span class="attr">a</span>:<span class="number">3</span>};</span><br><span class="line"><span class="keyword">var</span> bar = foo.call(obj1);</span><br><span class="line"> bar.call(obj2);<span class="comment">//2</span></span><br></pre></td></tr></table></figure></p><p>感觉这个箭头函数里的this值似曾相识?是的,在ES6之前,我们就经常使用一种和箭头函数一样的模式。<br><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</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">setTimeout(<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(self.a);</span><br><span class="line">},<span class="number">100</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">a</span>:<span class="number">2</span>};</span><br><span class="line">foo.call(obj);<span class="comment">//2</span></span><br></pre></td></tr></table></figure></p><p>(完)</p><p>关于this的细节就讲完了。this作为执行上下文的一个属性,由于他的规则比较多所以单独拿出来说。关于执行上下文的其他内容甚至更为重要,接下来建议拓展阅读另一篇博文<a href="https://foreverwang.github.io/2017/04/09/Javascript%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/">javascript之执行上下文</a>。<br>私以为搞透执行上下文,是真正理解javascript这门语言的必要条件。</p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="https://github.com/getify/You-Dont-Know-JS/tree/master/this%20%26%20object%20prototypes" target="_blank" rel="noopener">《你不知道的javascipt》</a></li><li>《javascript权威指南》</li></ul>]]></content>
<summary type="html">
<p>this关键字是Javascript中最重要的机制之一。关于介绍他的文章也比比皆是,而大多都浅尝辄止,浮于表面,不够系统。故整理此篇。</p>
<h3 id="先看几个题目"><a href="#先看几个题目" class="headerlink" title="先看几个题目"></a>先看几个题目</h3><p>题目1 //默认绑定 严格模式 非严格模式</p>
<figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.a);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> a = <span class="string">'2'</span>;</span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line"><span class="meta"> 'use strict'</span>;</span><br><span class="line"> foo(); </span><br><span class="line">&#125;)()</span><br></pre></td></tr></table></figure>
</summary>
<category term="javascript" scheme="https://foreverwang.github.io/categories/javascript/"/>
<category term="javascript核心" scheme="https://foreverwang.github.io/tags/javascript%E6%A0%B8%E5%BF%83/"/>
<category term="this" scheme="https://foreverwang.github.io/tags/this/"/>
</entry>
<entry>
<title>hexo + github pages搭建个人博客</title>
<link href="https://foreverwang.github.io/2017/02/23/hello-hexo/"/>
<id>https://foreverwang.github.io/2017/02/23/hello-hexo/</id>
<published>2017-02-23T04:20:48.000Z</published>
<updated>2020-06-21T11:08:05.114Z</updated>
<content type="html"><![CDATA[<p><a href="https://hexo.io/" target="_blank" rel="noopener">Hexo官网</a></p><h3 id="hexo报错解决"><a href="#hexo报错解决" class="headerlink" title="hexo报错解决"></a>hexo报错解决</h3><a id="more"></a><ul><li><p>hexo init 报错 </p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm uninstall dtrace-provider</span><br></pre></td></tr></table></figure></li><li><p>hero server 报错</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo --no-optional</span><br></pre></td></tr></table></figure><p> 通常这个命令能解决,(我这解决不了 就找到报错的地方,看了下这个报错没啥影响就注释掉了,不然强迫症)</p></li></ul><h3 id="github-pages"><a href="#github-pages" class="headerlink" title="github pages"></a>github pages</h3><h4 id="在github-新建仓库"><a href="#在github-新建仓库" class="headerlink" title="在github 新建仓库"></a>在github 新建仓库</h4><p>仓库命名: username.github.io, 此时就可以访问 username.github.io了</p><h4 id="github-pages-和-hexo-关联"><a href="#github-pages-和-hexo-关联" class="headerlink" title="github pages 和 hexo 关联"></a>github pages 和 hexo 关联</h4><ul><li>hexo -g 会生成一个静态网站(第一次会生成一个public目录),这个静态文件可以直接访问。</li><li>需要将hexo生成的静态网站,提交(git commit)到github上。</li></ul><h4 id="通过hexo-命令将静态站点push到github"><a href="#通过hexo-命令将静态站点push到github" class="headerlink" title="通过hexo 命令将静态站点push到github"></a>通过hexo 命令将静态站点push到github</h4><ul><li><p>安装插件hexo-deployer-git</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-deployer-git --save</span><br></pre></td></tr></table></figure></li><li><p>修改 hexo的 _config.yml</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">deploy:</span><br><span class="line"> <span class="built_in">type</span>: git</span><br><span class="line"> repo: https://github.com/username/username.github.io.git</span><br><span class="line"> branch: master</span><br></pre></td></tr></table></figure></li></ul><h4 id="更换hexo主题"><a href="#更换hexo主题" class="headerlink" title="更换hexo主题"></a>更换hexo主题</h4><ul><li><p>克隆主题到themes路径下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/theme-next/hexo-theme-next.git themes/next</span><br></pre></td></tr></table></figure><p>注意:主题文件夹放到themes下 文件夹命名和配置文件里保持一致</p></li><li><p>修改站点配置文件 _config.yml</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">theme: next</span><br></pre></td></tr></table></figure></li></ul><h4 id="hexo添加分类页面"><a href="#hexo添加分类页面" class="headerlink" title="hexo添加分类页面"></a>hexo添加分类页面</h4><ul><li><p>新建分类页</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new page categories</span><br></pre></td></tr></table></figure></li><li><p>在 source/categories 目录的 index.md 中修改:</p><pre><code>title: 分类date: 2015-12-02 12:44:45type: 'categories'</code></pre></li></ul><h4 id="hexo-常用命令"><a href="#hexo-常用命令" class="headerlink" title="hexo 常用命令"></a>hexo 常用命令</h4><ul><li><p>新建一篇博客</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new filename</span><br></pre></td></tr></table></figure></li></ul><ul><li><p>起本地服务</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo s</span><br></pre></td></tr></table></figure></li><li><p>生成静态站点</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo g</span><br></pre></td></tr></table></figure></li></ul><ul><li><p>发布</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo d</span><br></pre></td></tr></table></figure></li></ul><ul><li><p>清除本地缓存</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><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">hexo clean</span><br><span class="line">``` </span><br><span class="line"></span><br><span class="line"><span class="comment">#### 使用技巧</span></span><br><span class="line">* git deploy 面用户名和密码提交github </span><br><span class="line">在github 添加ssh key 公钥后 通过ssh 的方式提交代码</span><br><span class="line"></span><br><span class="line">```bash</span><br><span class="line">deploy:</span><br><span class="line"> <span class="built_in">type</span>: git</span><br><span class="line"> repo: git@github.com:xxx</span><br><span class="line"> branch: master</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<summary type="html">
<p><a href="https://hexo.io/" target="_blank" rel="noopener">Hexo官网</a></p>
<h3 id="hexo报错解决"><a href="#hexo报错解决" class="headerlink" title="hexo报错解决"></a>hexo报错解决</h3>
</summary>
<category term="Hexo" scheme="https://foreverwang.github.io/categories/Hexo/"/>
<category term="博客" scheme="https://foreverwang.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="hexo" scheme="https://foreverwang.github.io/tags/hexo/"/>
</entry>
</feed>