-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
532 lines (255 loc) · 537 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>项目搭建规范与配置</title>
<link href="/2021/08/01/%E9%A1%B9%E7%9B%AE%E6%90%AD%E5%BB%BA%E8%A7%84%E8%8C%83%E4%B8%8E%E9%85%8D%E7%BD%AE/"/>
<url>/2021/08/01/%E9%A1%B9%E7%9B%AE%E6%90%AD%E5%BB%BA%E8%A7%84%E8%8C%83%E4%B8%8E%E9%85%8D%E7%BD%AE/</url>
<content type="html"><![CDATA[<h1 id="项目搭建规范与配置"><a href="#项目搭建规范与配置" class="headerlink" title="项目搭建规范与配置"></a>项目搭建规范与配置</h1><p>统一代码规范的好处:</p><ul><li>规范的代码可以促进团队合作</li><li>规范的代码可以降低维护成本</li><li>规范的代码有助于 code review(代码审查)</li><li>养成代码规范的习惯,有助于程序员自身的成长</li></ul><p>当团队的成员都严格按照代码规范来写代码时,可以保证每个人的代码看起来都像是一个人写的,看别人的代码就像是在看自己的代码。更重要的是我们能够认识到规范的重要性,并坚持规范的开发习惯。</p><h4 id="如何制订代码规范"><a href="#如何制订代码规范" class="headerlink" title="如何制订代码规范"></a>如何制订代码规范</h4><ul><li><a href="https://github.com/airbnb/javascript">airbnb (101k star 英文版)</a>,<a href="https://github.com/lin-123/javascript">airbnb-中文版</a></li><li><a href="https://github.com/standard/standard/blob/master/docs/README-zhcn.md">standard (24.5k star) 中文版</a></li><li><a href="https://github.com/ecomfe/spec">百度前端编码规范 3.9k</a></li></ul><p>css 代码规范</p><ul><li><a href="https://github.com/fex-team/styleguide/blob/master/css.md">styleguide 2.3k</a></li><li><a href="https://github.com/ecomfe/spec/blob/master/css-style-guide.md">spec 3.9k</a></li></ul><h2 id="一-代码规范"><a href="#一-代码规范" class="headerlink" title="一. 代码规范"></a>一. 代码规范</h2><h3 id="1-1-集成editorconfig配置"><a href="#1-1-集成editorconfig配置" class="headerlink" title="1.1. 集成editorconfig配置"></a>1.1. 集成editorconfig配置</h3><p>EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。</p><p><strong>和Prettier一样,都是用来配置格式化你的代码的,这个格式化代码,要和你<code>lint</code>配置相符!否则会出现你格式化代码以后,却不能通过你的代码校验工具的检验</strong></p><p><code>.editorconfig</code>的自定义文件,用来定义项目的编码规范,编辑器的行为会与<code>.editorconfig</code>文件中定义的一致,并且其优先级比编辑器自身的设置要高,这在多人合作开发项目时十分有用而且必要。</p><p>有些编辑器默认支持editorConfig,如webstorm;而有些编辑器则需要安装editorConfig插件,如ATOM、Sublime、VS Code等。</p><p>当打开一个文件时,EditorConfig插件会在打开文件的目录和其每一级父目录查找.editorconfig文件。</p><p>EditorConfig的配置文件是从上往下读取的并且最近的EditorConfig配置文件会被最先读取. 匹配EditorConfig配置文件中的配置项会按照读取顺序被应用, 所以最近的配置文件中的配置项拥有优先权</p><p><strong>如果.editorconfig文件没有进行某些配置,则使用编辑器默认的设置</strong></p><p><strong>editorconfig文件是定义一些格式化规则(此规则并不会被vscode直接解析)</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs yaml"><span class="hljs-comment"># http://editorconfig.org</span><br><br><span class="hljs-string">root</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span><br><br>[<span class="hljs-string">*</span>] <span class="hljs-comment"># 表示所有文件适用</span><br><span class="hljs-string">charset</span> <span class="hljs-string">=</span> <span class="hljs-string">utf-8</span> <span class="hljs-comment"># 设置文件字符集为 utf-8</span><br><span class="hljs-string">indent_style</span> <span class="hljs-string">=</span> <span class="hljs-string">space</span> <span class="hljs-comment"># 缩进风格(tab | space)</span><br><span class="hljs-string">indent_size</span> <span class="hljs-string">=</span> <span class="hljs-number">2</span> <span class="hljs-comment"># 缩进大小</span><br><span class="hljs-string">end_of_line</span> <span class="hljs-string">=</span> <span class="hljs-string">lf</span> <span class="hljs-comment"># 控制换行类型(lf | cr | crlf)</span><br><span class="hljs-string">trim_trailing_whitespace</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 去除行首的任意空白字符</span><br><span class="hljs-string">insert_final_newline</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 始终在文件末尾插入一个新行</span><br><br>[<span class="hljs-string">*.md</span>] <span class="hljs-comment"># 表示仅 md 文件适用以下规则</span><br><span class="hljs-string">max_line_length</span> <span class="hljs-string">=</span> <span class="hljs-string">off</span><br><span class="hljs-string">trim_trailing_whitespace</span> <span class="hljs-string">=</span> <span class="hljs-literal">false</span><br></code></pre></td></tr></table></figure><p>VSCode需要安装一个插件:EditorConfig for VS Code</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsq2gh989yj30pj05ggmb.jpg" alt="image-20210722215138665"></p><h3 id="1-2-使用prettier工具"><a href="#1-2-使用prettier工具" class="headerlink" title="1.2. 使用prettier工具"></a>1.2. 使用prettier工具</h3><p>Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。</p><p>1.安装prettier</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm install prettier -D<br></code></pre></td></tr></table></figure><p>2.配置.prettierrc文件:</p><ul><li>useTabs:使用tab缩进还是空格缩进,选择false;</li><li>tabWidth:tab是空格的情况下,是几个空格,选择2个;</li><li>printWidth:当行字符的长度,推荐80,也有人喜欢100或者120;</li><li>singleQuote:使用单引号还是双引号,选择true,使用单引号;</li><li>trailingComma:在多行输入的尾逗号是否添加,设置为 <code>none</code>;</li><li>semi:语句末尾是否要加分号,默认值true,选择false表示不加;</li></ul><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><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"useTabs"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"tabWidth"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"printWidth"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">80</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"singleQuote"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"trailingComma"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"none"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"semi"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>3.创建.prettierignore忽略文件</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs gradle"><span class="hljs-regexp">/dist/</span>*<br>.local<br>.output.js<br><span class="hljs-regexp">/node_modules/</span>**<br><br>**<span class="hljs-comment">/*.svg</span><br><span class="hljs-comment">**/</span>*.sh<br><br><span class="hljs-regexp">/public/</span>*<br></code></pre></td></tr></table></figure><p>4.VSCode需要安装prettier的插件</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsq2acx21rj30ow057mxp.jpg" alt="image-20210722214543454"></p><p>5.测试prettier是否生效</p><ul><li>测试一:在代码中保存代码;</li><li>测试二:配置一次性修改的命令;</li></ul><p>在package.json中配置一个scripts:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">"prettier"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"prettier --write ."</span><br></code></pre></td></tr></table></figure><h3 id="1-3-使用ESLint检测"><a href="#1-3-使用ESLint检测" class="headerlink" title="1.3. 使用ESLint检测"></a>1.3. 使用ESLint检测</h3><p>ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。</p><p>Eslint 可以在运行代码前就发现一些语法错误和潜在的 bug,极大地减轻测试人员的压力,减少软件项目的除错成本。同时,Eslint 允许开发者通过 rules 定义自己的代码规范,所以非常适合用于制定团队代码规范。</p><p>ESlint 在默认情况下是不开启任何自定义规则校验,只对错误的 ES5 语法和标准的语法错误进行检测,比如 <code>const</code> 这种 ES6 语法,还有莫名其妙的分号(如下图)。</p><p>由于 Eslint 和 Prettier 存在一些相同的规则,当同一个规则设置不同时,就会出现很诡异的现象:使用 prettier 格式化的代码,无法通过 eslint 校验。</p><p>1.在创建Vue项目的时候,我们选择了ESLint,所以Vue会默认帮助我们配置需要的ESLint环境。</p><p>2.VSCode需要安装ESLint插件:</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsq2oq26odj30pw05faaq.jpg" alt="image-20210722215933360"></p><p>3.解决eslint和prettier冲突的问题:</p><p>安装插件:(vue在创建项目时,如果选择prettier,那么这两个插件会自动安装)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i eslint-plugin-prettier eslint-config-prettier -D<br></code></pre></td></tr></table></figure><p><code>.eslintrc.js</code>添加prettier插件:</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><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs json">extends<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-string">"plugin:vue/vue3-essential"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"eslint:recommended"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"@vue/typescript/recommended"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"@vue/prettier"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"@vue/prettier/@typescript-eslint"</span><span class="hljs-punctuation">,</span><br> 'plugin<span class="hljs-punctuation">:</span>prettier/recommended'<br><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br></code></pre></td></tr></table></figure><h3 id="1-4-git-Husky和eslint"><a href="#1-4-git-Husky和eslint" class="headerlink" title="1.4. git Husky和eslint"></a>1.4. git Husky和eslint</h3><p>虽然我们已经要求项目使用eslint了,但是不能保证组员提交代码之前都将eslint中的问题解决掉了:</p><ul><li><p>也就是我们希望保证代码仓库中的代码都是符合eslint规范的;</p></li><li><p>那么我们需要在组员执行 <code>git commit </code> 命令的时候对其进行校验,如果不符合eslint规范,那么自动通过规范进行修复;</p></li></ul><p>那么如何做到这一点呢?可以通过Husky工具:</p><ul><li>husky是一个git hook工具,可以帮助我们触发git提交的各个阶段:pre-commit、commit-msg、pre-push</li></ul><p>如何使用husky呢?</p><p>这里我们可以使用自动配置命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npx husky-init && npm install<br></code></pre></td></tr></table></figure><p>这里会做三件事:</p><p>1.安装husky相关的依赖:</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqq0o5jxmj30bb04qwen.jpg" alt="image-20210723112648927"></p><p>2.在项目目录下创建 <code>.husky</code> 文件夹:</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmake">npx huksy <span class="hljs-keyword">install</span><br></code></pre></td></tr></table></figure><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqq16zo75j307703mt8m.jpg" alt="image-20210723112719634"></p><p>3.在package.json中添加一个脚本:</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqq26phpxj30dj06fgm3.jpg" alt="image-20210723112817691"></p><p>接下来,我们需要去完成一个操作:在进行commit时,执行lint脚本:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">// 添加husky hook<br>npx husky add .husky/pre-commit <span class="hljs-string">"npm test"</span><br></code></pre></td></tr></table></figure><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqq3hn229j30nf04z74q.jpg" alt="image-20210723112932943"></p><p>这个时候我们执行git commit的时候会自动对代码进行lint校验。</p><h3 id="1-5-git-commit规范"><a href="#1-5-git-commit规范" class="headerlink" title="1.5. git commit规范"></a>1.5. git commit规范</h3><h4 id="1-5-1-代码提交风格"><a href="#1-5-1-代码提交风格" class="headerlink" title="1.5.1. 代码提交风格"></a>1.5.1. 代码提交风格</h4><p>通常我们的git commit会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqw17gaqjj30to0cj3zp.jpg"></p><p>但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen</p><ul><li>Commitizen 是一个帮助我们编写规范 commit message 的工具;</li></ul><p>1.安装Commitizen</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm install commitizen -D<br></code></pre></td></tr></table></figure><p>2.安装cz-conventional-changelog,并且初始化cz-conventional-changelog:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npx commitizen init cz-conventional-changelog --save-dev --save-exact<br></code></pre></td></tr></table></figure><p>这个命令会帮助我们安装cz-conventional-changelog:</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqvz2odi4j30ek00zmx2.jpg" alt="image-20210723145249096"></p><p>并且在package.json中进行配置:</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqvzftay5j30iu04k74d.jpg"></p><p>这个时候我们提交代码需要使用 <code>npx cz</code>:</p><ul><li>第一步是选择type,本次更新的类型</li></ul><table><thead><tr><th>Type</th><th>作用</th></tr></thead><tbody><tr><td>feat</td><td>新增特性 (feature)</td></tr><tr><td>fix</td><td>修复 Bug(bug fix)</td></tr><tr><td>docs</td><td>修改文档 (documentation)</td></tr><tr><td>style</td><td>代码格式修改(white-space, formatting, missing semi colons, etc)</td></tr><tr><td>refactor</td><td>代码重构(refactor)</td></tr><tr><td>perf</td><td>改善性能(A code change that improves performance)</td></tr><tr><td>test</td><td>测试(when adding missing tests)</td></tr><tr><td>build</td><td>变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等)</td></tr><tr><td>ci</td><td>更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等</td></tr><tr><td>chore</td><td>变更构建流程或辅助工具(比如更改测试环境)</td></tr><tr><td>revert</td><td>代码回退</td></tr></tbody></table><ul><li>第二步选择本次修改的范围(作用域)</li></ul><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqw8ca15oj30r600wmx4.jpg" alt="image-20210723150147510"></p><ul><li>第三步选择提交的信息</li></ul><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqw8mq3zlj60ni01hmx402.jpg" alt="image-20210723150204780"></p><ul><li>第四步提交详细的描述信息</li></ul><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqw8y05bjj30kt01fjrb.jpg" alt="image-20210723150223287"></p><ul><li>第五步是否是一次重大的更改</li></ul><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqw9z5vbij30bm00q744.jpg" alt="image-20210723150322122"></p><ul><li>第六步是否影响某个open issue</li></ul><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqwar8xp1j30fq00ya9x.jpg" alt="image-20210723150407822"></p><p>我们也可以在scripts中构建一个命令来执行 cz:</p><p><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gsqwc4gtkxj30e207174t.jpg" alt="image-20210723150526211"></p><h4 id="1-5-2-代码提交验证"><a href="#1-5-2-代码提交验证" class="headerlink" title="1.5.2. 代码提交验证"></a>1.5.2. 代码提交验证</h4><p>如果我们按照cz来规范了提交风格,但是依然有同事通过 <code>git commit</code> 按照不规范的格式提交应该怎么办呢?</p><ul><li>我们可以通过commitlint来限制提交;</li></ul><p>1.安装 @commitlint/config-conventional 和 @commitlint/cli</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i @commitlint/config-conventional @commitlint/cli -D<br></code></pre></td></tr></table></figure><p>2.在根目录创建commitlint.config.js文件,配置commitlint</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {<br> <span class="hljs-attr">extends</span>: [<span class="hljs-string">'@commitlint/config-conventional'</span>]<br>}<br></code></pre></td></tr></table></figure><p>3.使用husky生成commit-msg文件,验证提交信息:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>工程化</category>
</categories>
<tags>
<tag>工程化</tag>
</tags>
</entry>
<entry>
<title>通过snabbdom源码调试分析深入Virtual DOM原理</title>
<link href="/2021/06/03/%E9%80%9A%E8%BF%87snabbdom%E6%BA%90%E7%A0%81%E8%B0%83%E8%AF%95%E5%88%86%E6%9E%90%E6%B7%B1%E5%85%A5Virtual%20DOM%E5%8E%9F%E7%90%86/"/>
<url>/2021/06/03/%E9%80%9A%E8%BF%87snabbdom%E6%BA%90%E7%A0%81%E8%B0%83%E8%AF%95%E5%88%86%E6%9E%90%E6%B7%B1%E5%85%A5Virtual%20DOM%E5%8E%9F%E7%90%86/</url>
<content type="html"><![CDATA[<p>Vue和React等库的一个比较共同的地方是使用了Virtual DOM。为什么会选择使用Virtual DOM,而不是像JQuery一样,只是把DOM的操作进行封装。主要的原始是Virtual DOM将真实DOM转成JavaScript对象,可以通过diff算法,找到最少的需要更新的DOM元素进行更新。如果页面的数据量比较大的话,相对来说可以保证一个比较高的性能。对于Vue这些框架来说的话,使用上手相对来说比较简单。但是了解其中的核心原理,可能会在代码层面写出性能更优的代码。所有,这也是学习Virtual DOM的原因。</p><h2 id="Virtual-DOM是什么?"><a href="#Virtual-DOM是什么?" class="headerlink" title="Virtual DOM是什么?"></a>Virtual DOM是什么?</h2><ul><li><p>**Virtual DOM(虚拟 DOM)**,是由普通的 JS 对象来描述 DOM 对象,因为不是真实的 DOM 对象,所以叫 Virtual DOM</p></li><li><p>可以使用 Virtual DOM 来描述真实 DOM,如下示例</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs js">{<br> <span class="hljs-attr">sel</span>: <span class="hljs-string">"div"</span>,<br> <span class="hljs-attr">data</span>: {},<br> <span class="hljs-attr">children</span>: <span class="hljs-literal">undefined</span>,<br> <span class="hljs-attr">text</span>: <span class="hljs-string">"Hello Virtual DOM"</span>,<br> <span class="hljs-attr">elm</span>: <span class="hljs-literal">undefined</span>,<br> <span class="hljs-attr">key</span>: <span class="hljs-literal">undefined</span><br>}<br></code></pre></td></tr></table></figure></li></ul><h2 id="为什么使用-Virtual-DOM?"><a href="#为什么使用-Virtual-DOM?" class="headerlink" title="为什么使用 Virtual DOM?"></a>为什么使用 Virtual DOM?</h2><ul><li>手动操作 DOM 比较麻烦,还需要考虑浏览器兼容性问题,虽然有 jQuery 等库简化 DOM 操作,但是随着项目的复杂 DOM 操作复杂提升</li><li>Virtual DOM 的好处是当状态改变时不需要立即更新 DOM,只需要创建一个虚拟树来描述 DOM, Virtual DOM 内部将弄清楚如何有效(diff)的更新 DOM</li><li>参考 github 上 <a href="https://github.com/Matt-Esch/virtual-dom">virtual-dom</a> 的描述<ul><li>虚拟 DOM 可以维护程序的状态,跟踪上一次的状态</li><li>通过比较前后两次状态的差异更新真实 DOM</li></ul></li></ul><h2 id="虚拟-DOM-有什么作用?"><a href="#虚拟-DOM-有什么作用?" class="headerlink" title="虚拟 DOM 有什么作用?"></a>虚拟 DOM 有什么作用?</h2><ul><li><p>维护视图和状态的关系</p></li><li><p>复杂视图情况下提升渲染性能</p></li><li><p>除了渲染 DOM 以外,还可以实现 SSR(Nuxt.js/Next.js)、原生应用(Weex/React Native)、小程序(mpvue/uni-app)等</p><p> <img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/febdadc5-21d8-4939-bb4b-e56e04d8f3e7" alt="image-20200102104642121"></p></li></ul><h2 id="实现了Virtual-DOM-的库"><a href="#实现了Virtual-DOM-的库" class="headerlink" title="实现了Virtual DOM 的库"></a>实现了Virtual DOM 的库</h2><p>因为<strong>Virtual DOM</strong>的实现其实是更底层的,为了快速上手,我们可以借助github上的优秀开源<strong>Virtual DOM</strong>库,了解<strong>Virtual DOM</strong>的原理。</p><ul><li><a href="https://github.com/snabbdom/snabbdom">Snabbdom</a>一个简单,高效的virtual DOM库。<a href="https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js">在vue框架中,v-dom的核心也是基于本库进行二次开发的</a>。<ul><li>通过模块可扩展</li><li>源码使用 TypeScript 开发</li><li>最快的 Virtual DOM 之一</li></ul></li><li><a href="https://github.com/Matt-Esch/virtual-dom">virtual-dom</a></li></ul><h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><ul><li><a href="https://codesandbox.io/s/jq-demo-5i7qp">uery-demo</a></li><li><a href="https://codesandbox.io/s/snabbdom-demo-4hbyb">snabbdom-demo</a></li></ul><h1 id="Snabbdom-基本使用"><a href="#Snabbdom-基本使用" class="headerlink" title="Snabbdom 基本使用"></a>Snabbdom 基本使用</h1><p><a href="https://parceljs.org/getting_started.html">parcel</a>是 Web 应用打包工具,适用于经验不同的开发者。它利用多核处理提供了极快的速度,并且不需要任何配置。</p><ul><li><p>创建项目,并安装 <a href="https://parceljs.org/getting_started.html">parcel</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 创建项目目录</span><br>md snabbdom-demo<br><span class="hljs-comment"># 进入项目目录</span><br><span class="hljs-built_in">cd</span> snabbdom-demo<br><span class="hljs-comment"># 创建 package.json</span><br>npm init -y<br><span class="hljs-comment"># 本地安装 parcel</span><br>npm install parcel-bundler -D<br></code></pre></td></tr></table></figure></li><li><p>配置 package.json 的 scripts</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">"scripts"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"dev"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"parcel index.html --open"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"build"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"parcel build index.html"</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure></li><li><p>创建目录结构</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><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><code class="hljs txt">│ index.html<br>│ package.json<br>└─src<br> 01-basicusage.js<br></code></pre></td></tr></table></figure></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># --depth 表示克隆深度, 1 表示只克隆最新的版本. 因为如果项目迭代的版本很多, 克隆会很慢</span><br>git <span class="hljs-built_in">clone</span> -b v2.1.0 --depth=1 https://github.com/snabbdom/snabbdom.git<br></code></pre></td></tr></table></figure><h3 id="安装-Snabbdom"><a href="#安装-Snabbdom" class="headerlink" title="安装 Snabbdom"></a>安装 Snabbdom</h3><ul><li><p>安装 Snabbdom</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install [email protected]<br></code></pre></td></tr></table></figure></li></ul><h3 id="导入-Snabbdom"><a href="#导入-Snabbdom" class="headerlink" title="导入 Snabbdom"></a>导入 Snabbdom</h3><ul><li>Snabbdom 的两个核心函数 init 和 h() <ul><li>init() 是一个高阶函数,返回 patch() </li><li>h() 返回虚拟节点 VNode,这个函数我们在使用 Vue.js 的时候见过</li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">import</span> { init } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/init'</span><br><span class="hljs-keyword">import</span> { h } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/h'</span><br><span class="hljs-keyword">const</span> patch = <span class="hljs-title function_">init</span>([])<br></code></pre></td></tr></table></figure><blockquote><p>注意:此时运行的话会告诉我们找不到 init / h 模块,因为模块路径并不是 snabbdom/int,这个路径是在 package.json 中的 exports 字段设置的,而我们使用的打包工具不支持 exports 这个字段,webpack 4 也不支持,webpack 5 支持该字段。该字段在导入 snabbdom/init 的时候会补全路径成 snabbdom/build/package/init.js</p></blockquote><figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs n1ql">"exports": {<br> "./init": "./<span class="hljs-keyword">build</span>/package/init.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./h<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/h.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./helpers/attachto<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/helpers/attachto.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./hooks<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/hooks.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./htmldomapi<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/htmldomapi.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./<span class="hljs-keyword">is</span><span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/<span class="hljs-keyword">is</span>.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./jsx<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/jsx.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/attributes<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/attributes.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/class<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/class.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/<span class="hljs-keyword">dataset</span><span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/<span class="hljs-keyword">dataset</span>.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/eventlisteners<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/eventlisteners.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/hero<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/hero.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/module<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/module.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/props<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/props.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./modules/style<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/modules/style.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./thunk<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/thunk.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./tovnode<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/tovnode.js<span class="hljs-string">",</span><br><span class="hljs-string"> "</span>./vnode<span class="hljs-string">": "</span>./<span class="hljs-keyword">build</span>/package/vnode.js<span class="hljs-string">"</span><br><span class="hljs-string"> }</span><br></code></pre></td></tr></table></figure><ul><li>如果使用不支持 package.json 的 exports 字段的打包工具,我们应该把模块的路径写全<ul><li>查看安装的 snabbdom 的目录结构</li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">import</span> { h } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/h'</span><br><span class="hljs-keyword">import</span> { init } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/init'</span><br><span class="hljs-keyword">import</span> { classModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/modules/class'</span><br></code></pre></td></tr></table></figure><ul><li>回顾 Vue 中的 render 函数</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">new</span> <span class="hljs-title class_">Vue</span>({<br> router,<br> store,<br> <span class="hljs-attr">render</span>: <span class="hljs-function"><span class="hljs-params">h</span> =></span> <span class="hljs-title function_">h</span>(<span class="hljs-title class_">App</span>)<br>}).$mount(<span class="hljs-string">'#app'</span>)<br></code></pre></td></tr></table></figure><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">import</span> { h } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/h'</span><br><span class="hljs-keyword">import</span> { init } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/init'</span><br><br><span class="hljs-comment">// 使用 init() 函数创建 patch()</span><br><span class="hljs-comment">// init() 的参数是数组,将来可以传入模块,处理属性/样式/事件等</span><br><span class="hljs-keyword">let</span> patch = <span class="hljs-title function_">init</span>([])<br><br><span class="hljs-comment">// 使用 h() 函数创建 vnode</span><br><span class="hljs-keyword">let</span> vnode = <span class="hljs-title function_">h</span>(<span class="hljs-string">'div.cls'</span>, [<br> <span class="hljs-title function_">h</span>(<span class="hljs-string">'h1'</span>, <span class="hljs-string">'Hello Snabbdom'</span>),<br> <span class="hljs-title function_">h</span>(<span class="hljs-string">'p'</span>, <span class="hljs-string">'这是段落'</span>)<br>])<br><br><span class="hljs-keyword">const</span> app = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'#app'</span>)<br><span class="hljs-comment">// 把 vnode 渲染到空的 DOM 元素(替换)</span><br><span class="hljs-comment">// 会返回新的 vnode</span><br><span class="hljs-keyword">let</span> oldVnode = <span class="hljs-title function_">patch</span>(app, vnode)<br><br><span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> vnode = <span class="hljs-title function_">h</span>(<span class="hljs-string">'div.cls'</span>, [<br> <span class="hljs-title function_">h</span>(<span class="hljs-string">'h1'</span>, <span class="hljs-string">'Hello World'</span>),<br> <span class="hljs-title function_">h</span>(<span class="hljs-string">'p'</span>, <span class="hljs-string">'这是段落'</span>)<br> ])<br> <span class="hljs-comment">// 把老的视图更新到新的状态</span><br> oldVnode = <span class="hljs-title function_">patch</span>(oldVnode, vnode)<br> <span class="hljs-comment">// h('!') 是创建注释</span><br> <span class="hljs-title function_">patch</span>(oldVnode, <span class="hljs-title function_">h</span>(<span class="hljs-string">'!'</span>))<br>}, <span class="hljs-number">2000</span>)<br></code></pre></td></tr></table></figure><h2 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h2><p>Snabbdom 的核心库并不能处理 DOM 元素的属性/样式/事件等,如果需要处理的话,可以使用模块</p><h3 id="常用模块"><a href="#常用模块" class="headerlink" title="常用模块"></a>常用模块</h3><ul><li><p>官方提供了 6 个模块</p><ul><li>attributes<ul><li>设置 DOM 元素的属性,使用 <code>setAttribute</code>()</li><li>处理布尔类型的属性</li></ul></li><li>props<ul><li>和 <code>attributes</code> 模块相似,设置 DOM 元素的属性 <code>element[attr] = value</code></li><li>不处理布尔类型的属性</li></ul></li><li>class<ul><li>切换类样式</li><li>注意:给元素设置类样式是通过 <code>sel</code> 选择器</li></ul></li><li>dataset<ul><li>设置 <code>data-*</code> 的自定义属性</li></ul></li><li>eventlisteners<ul><li>注册和移除事件</li></ul></li><li>style<ul><li>设置行内样式,支持动画</li><li>delayed/remove/destroy</li></ul></li></ul></li></ul><h3 id="模块使用"><a href="#模块使用" class="headerlink" title="模块使用"></a>模块使用</h3><ul><li>模块使用步骤:<ul><li>导入需要的模块</li><li>init() 中注册模块</li><li>使用 h() 函数创建 VNode 的时候,可以把第二个参数设置为对象,其他参数往后移</li></ul></li></ul><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">import</span> { h } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/h'</span><br><span class="hljs-keyword">import</span> { init } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/init'</span><br><span class="hljs-comment">// 导入需要的模块</span><br><span class="hljs-keyword">import</span> { styleModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/modules/style'</span><br><span class="hljs-keyword">import</span> { eventListenersModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'snabbdom/build/package/modules/eventlisteners'</span><br><br><span class="hljs-comment">// 使用 init() 函数创建 patch()</span><br><span class="hljs-comment">// init() 的参数是数组,将来可以传入模块,处理属性/样式/事件等</span><br><span class="hljs-keyword">let</span> patch = <span class="hljs-title function_">init</span>([<br> <span class="hljs-comment">// 注册模块</span><br> styleModule,<br> eventListenersModule<br>])<br><br><span class="hljs-comment">// 使用 h() 函数创建 vnode</span><br><span class="hljs-keyword">let</span> vnode = <span class="hljs-title function_">h</span>(<span class="hljs-string">'div.cls'</span>, {<br> <span class="hljs-comment">// 设置 DOM 元素的行内样式</span><br> <span class="hljs-attr">style</span>: { <span class="hljs-attr">color</span>: <span class="hljs-string">'#DEDEDE'</span>, <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">'#181A1B'</span> },<br> <span class="hljs-comment">// 注册事件</span><br> <span class="hljs-attr">on</span>: { <span class="hljs-attr">click</span>: clickHandler }<br>}, [<br> <span class="hljs-title function_">h</span>(<span class="hljs-string">'h1'</span>, <span class="hljs-string">'Hello Snabbdom'</span>),<br> <span class="hljs-title function_">h</span>(<span class="hljs-string">'p'</span>, <span class="hljs-string">'这是段落'</span>)<br>])<br><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">clickHandler</span> () {<br> <span class="hljs-comment">// 此处的 this 指向对应的 vnode</span><br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">elm</span>.<span class="hljs-property">innerHTML</span>)<br>}<br></code></pre></td></tr></table></figure><h1 id="Snabbdom-源码解析"><a href="#Snabbdom-源码解析" class="headerlink" title="Snabbdom 源码解析"></a>Snabbdom 源码解析</h1><h3 id="Snabbdom-的核心"><a href="#Snabbdom-的核心" class="headerlink" title="Snabbdom 的核心"></a>Snabbdom 的核心</h3><ul><li>使用 h() 函数创建 JavaScript 对象(VNode)描述真实 DOM</li><li>init() 设置模块,创建 patch()</li><li>patch() 比较新旧两个 VNode</li><li>把变化的内容更新到真实 DOM 树上</li></ul><h3 id="Snabbdom-源码"><a href="#Snabbdom-源码" class="headerlink" title="Snabbdom 源码"></a>Snabbdom 源码</h3><ul><li><p>源码地址:</p><ul><li><a href="https://github.com/snabbdom/snabbdom">https://github.com/snabbdom/snabbdom</a></li></ul></li><li><p>src 目录结构</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs vim">├── package<br>│ ├── helpers<br>│ │ └── attachto.<span class="hljs-keyword">ts</span>定义了 vnode.<span class="hljs-keyword">ts</span> 中 AttachData 的数据结构<br>│ ├── modules<br>│ │ ├── attributes.<span class="hljs-keyword">ts</span><br>│ │ ├── class.<span class="hljs-keyword">ts</span><br>│ │ ├── dataset.<span class="hljs-keyword">ts</span><br>│ │ ├── eventlisteners.<span class="hljs-keyword">ts</span><br>│ │ ├── hero.<span class="hljs-keyword">ts</span>example 中使用到的自定义钩子<br>│ │ ├── module.<span class="hljs-keyword">ts</span>定义了模块中用到的钩子函数<br>│ │ ├── props.<span class="hljs-keyword">ts</span><br>│ │ └── style.<span class="hljs-keyword">ts</span><br>│ ├── h.<span class="hljs-keyword">ts</span>h() 函数,用来创建 VNode<br>│ ├── hooks.<span class="hljs-keyword">ts</span>所有钩子函数的定义<br>│ ├── htmldomapi.<span class="hljs-keyword">ts</span>对 DOM API 的包装<br>│ ├── init.<span class="hljs-keyword">ts</span>加载 modules、DOMAPI,返回 patch 函数<br>│ ├── <span class="hljs-keyword">is</span>.<span class="hljs-keyword">ts</span>判断数组和原始值的函数<br>│ ├── jsx-<span class="hljs-keyword">global</span>.<span class="hljs-keyword">ts</span>jsx 的类型声明文件<br>│ ├── jsx.<span class="hljs-keyword">ts</span>处理 jsx<br>│ ├── thunk.<span class="hljs-keyword">ts</span>优化处理,对复杂视图不可变值得优化<br>│ ├── tovnode.<span class="hljs-keyword">ts</span>DOM 转换成 VNode<br>│ ├── <span class="hljs-keyword">ts</span>-transform-js-extension.cjs<br>│ ├── tsconfig.json<span class="hljs-keyword">ts</span> 的编译配置文件<br>│ └── vnode.<span class="hljs-keyword">ts</span>虚拟节点定义<br></code></pre></td></tr></table></figure></li></ul><h2 id="h-函数"><a href="#h-函数" class="headerlink" title="h 函数"></a>h 函数</h2><ul><li><p>h() 函数介绍</p><ul><li><p>在使用 Vue 的时候见过 h() 函数</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">new</span> <span class="hljs-title class_">Vue</span>({<br> router,<br> store,<br> <span class="hljs-attr">render</span>: <span class="hljs-function"><span class="hljs-params">h</span> =></span> <span class="hljs-title function_">h</span>(<span class="hljs-title class_">App</span>)<br>}).$mount(<span class="hljs-string">'#app'</span>)<br></code></pre></td></tr></table></figure></li><li><p>h() 函数最早见于 <a href="https://github.com/hyperhype/hyperscript">hyperscript</a>,使用 JavaScript 创建超文本</p></li><li><p>Snabbdom 中的 h() 函数不是用来创建超文本,而是创建 VNode</p></li></ul></li><li><p>函数重载</p><ul><li><p>概念</p><ul><li><strong>参数个数</strong>或<strong>类型</strong>不同的函数</li><li>JavaScript 中没有重载的概念</li><li>TypeScript 中有重载,不过重载的实现还是通过代码调整参数</li></ul></li><li><p>重载的示意</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">add</span> (<span class="hljs-attr">a</span>: number, <span class="hljs-attr">b</span>: number) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a + b)<br>}<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">add</span> (<span class="hljs-attr">a</span>: number, <span class="hljs-attr">b</span>: number, <span class="hljs-attr">c</span>: number) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a + b + c)<br>}<br><span class="hljs-title function_">add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)<br><span class="hljs-title function_">add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<br></code></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">add</span> (<span class="hljs-attr">a</span>: number, <span class="hljs-attr">b</span>: number) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a + b)<br>}<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">add</span> (<span class="hljs-attr">a</span>: number, <span class="hljs-attr">b</span>: string) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a + b)<br>}<br><span class="hljs-title function_">add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)<br><span class="hljs-title function_">add</span>(<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>)<br></code></pre></td></tr></table></figure></li></ul></li><li><p>源码位置:src/package/h.ts</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// h 函数的重载</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">h</span> (<span class="hljs-attr">sel</span>: string): <span class="hljs-title class_">VNode</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">h</span> (<span class="hljs-attr">sel</span>: string, <span class="hljs-attr">data</span>: <span class="hljs-title class_">VNodeData</span> | <span class="hljs-literal">null</span>): <span class="hljs-title class_">VNode</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">h</span> (<span class="hljs-attr">sel</span>: string, <span class="hljs-attr">children</span>: <span class="hljs-title class_">VNodeChildren</span>): <span class="hljs-title class_">VNode</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">h</span> (<span class="hljs-attr">sel</span>: string, <span class="hljs-attr">data</span>: <span class="hljs-title class_">VNodeData</span> | <span class="hljs-literal">null</span>, <span class="hljs-attr">children</span>: <span class="hljs-title class_">VNodeChildren</span>): <span class="hljs-title class_">VNode</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">h</span> (<span class="hljs-attr">sel</span>: any, b?: any, c?: any): <span class="hljs-title class_">VNode</span> {<br> <span class="hljs-keyword">var</span> <span class="hljs-attr">data</span>: <span class="hljs-title class_">VNodeData</span> = {}<br> <span class="hljs-keyword">var</span> <span class="hljs-attr">children</span>: any<br> <span class="hljs-keyword">var</span> <span class="hljs-attr">text</span>: any<br> <span class="hljs-keyword">var</span> <span class="hljs-attr">i</span>: number<br> <span class="hljs-comment">// 处理参数,实现重载的机制</span><br> <span class="hljs-keyword">if</span> (c !== <span class="hljs-literal">undefined</span>) {<br> <span class="hljs-comment">// 处理三个参数的情况</span><br> <span class="hljs-comment">// sel、data、children/text</span><br> <span class="hljs-keyword">if</span> (b !== <span class="hljs-literal">null</span>) {<br> data = b<br> }<br> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">array</span>(c)) {<br> children = c<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">primitive</span>(c)) {<br> text = c<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (c && c.<span class="hljs-property">sel</span>) {<br> children = [c]<br> }<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (b !== <span class="hljs-literal">undefined</span> && b !== <span class="hljs-literal">null</span>) {<br> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">array</span>(b)) {<br> children = b<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">primitive</span>(b)) {<br> <span class="hljs-comment">// 如果 c 是字符串或者数字</span><br> text = b<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (b && b.<span class="hljs-property">sel</span>) {<br> <span class="hljs-comment">// 如果 b 是 VNode</span><br> children = [b]<br> } <span class="hljs-keyword">else</span> { data = b }<br> }<br> <span class="hljs-keyword">if</span> (children !== <span class="hljs-literal">undefined</span>) {<br> <span class="hljs-comment">// 处理 children 中的原始值(string/number)</span><br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < children.<span class="hljs-property">length</span>; ++i) {<br> <span class="hljs-comment">// 如果 child 是 string/number,创建文本节点</span><br> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">primitive</span>(children[i])) children[i] = <span class="hljs-title function_">vnode</span>(<span class="hljs-literal">undefined</span>, <span class="hljs-literal">undefined</span>, <span class="hljs-literal">undefined</span>, children[i], <span class="hljs-literal">undefined</span>)<br> }<br> }<br> <span class="hljs-keyword">if</span> (<br> sel[<span class="hljs-number">0</span>] === <span class="hljs-string">'s'</span> && sel[<span class="hljs-number">1</span>] === <span class="hljs-string">'v'</span> && sel[<span class="hljs-number">2</span>] === <span class="hljs-string">'g'</span> &&<br> (sel.<span class="hljs-property">length</span> === <span class="hljs-number">3</span> || sel[<span class="hljs-number">3</span>] === <span class="hljs-string">'.'</span> || sel[<span class="hljs-number">3</span>] === <span class="hljs-string">'#'</span>)<br> ) {<br> <span class="hljs-comment">// 如果是 svg,添加命名空间</span><br> <span class="hljs-title function_">addNS</span>(data, children, sel)<br> }<br> <span class="hljs-comment">// 返回 VNode</span><br> <span class="hljs-keyword">return</span> <span class="hljs-title function_">vnode</span>(sel, data, children, text, <span class="hljs-literal">undefined</span>)<br>};<br></code></pre></td></tr></table></figure></li></ul><h2 id="VNode"><a href="#VNode" class="headerlink" title="VNode"></a>VNode</h2><ul><li><p>一个 VNode 就是一个虚拟节点用来描述一个 DOM 元素,如果这个 VNode 有 children 就是 Virtual DOM </p></li><li><p>源码位置:src/package/vnode.ts</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">export</span> interface <span class="hljs-title class_">VNode</span> {<br> <span class="hljs-comment">// 选择器</span><br> <span class="hljs-attr">sel</span>: string | <span class="hljs-literal">undefined</span>;<br> <span class="hljs-comment">// 节点数据:属性/样式/事件等</span><br> <span class="hljs-attr">data</span>: <span class="hljs-title class_">VNodeData</span> | <span class="hljs-literal">undefined</span>;<br> <span class="hljs-comment">// 子节点,和 text 只能互斥</span><br> <span class="hljs-attr">children</span>: <span class="hljs-title class_">Array</span><<span class="hljs-title class_">VNode</span> | string> | <span class="hljs-literal">undefined</span>;<br> <span class="hljs-comment">// 记录 vnode 对应的真实 DOM</span><br> <span class="hljs-attr">elm</span>: <span class="hljs-title class_">Node</span> | <span class="hljs-literal">undefined</span>;<br> <span class="hljs-comment">// 节点中的内容,和 children 只能互斥</span><br> <span class="hljs-attr">text</span>: string | <span class="hljs-literal">undefined</span>;<br> <span class="hljs-comment">// 优化用</span><br> <span class="hljs-attr">key</span>: <span class="hljs-title class_">Key</span> | <span class="hljs-literal">undefined</span>;<br>}<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">vnode</span> (<span class="hljs-attr">sel</span>: string | <span class="hljs-literal">undefined</span>,<br> <span class="hljs-attr">data</span>: any | <span class="hljs-literal">undefined</span>,<br> <span class="hljs-attr">children</span>: <span class="hljs-title class_">Array</span><<span class="hljs-title class_">VNode</span> | string> | <span class="hljs-literal">undefined</span>,<br> <span class="hljs-attr">text</span>: string | <span class="hljs-literal">undefined</span>,<br> <span class="hljs-attr">elm</span>: <span class="hljs-title class_">Element</span> | <span class="hljs-title class_">Text</span> | <span class="hljs-literal">undefined</span>): <span class="hljs-title class_">VNode</span> {<br> <span class="hljs-keyword">const</span> key = data === <span class="hljs-literal">undefined</span> ? <span class="hljs-literal">undefined</span> : data.<span class="hljs-property">key</span><br> <span class="hljs-keyword">return</span> { sel, data, children, text, elm, key }<br>}<br></code></pre></td></tr></table></figure><h2 id="snabbdom"><a href="#snabbdom" class="headerlink" title="snabbdom"></a>snabbdom</h2><ul><li>patch(oldVnode, newVnode)</li><li>打补丁,把新节点中变化的内容渲染到真实 DOM,最后返回新节点作为下一次处理的旧节点</li><li>对比新旧 VNode 是否相同节点(节点的 key 和 sel 相同)</li><li>如果不是相同节点,删除之前的内容,重新渲染</li><li>如果是相同节点,再判断新的 VNode 是否有 text,如果有并且和 oldVnode 的 text 不同,直接更新文本内容</li><li>如果新的 VNode 有 children,判断子节点是否有变化,判断子节点的过程使用的就是 diff 算法</li><li>diff 过程只进行同层级比较</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/ad729f8b-8f17-498e-b887-563b071e18c0" alt="image-20200102103653779"></p><h3 id="init"><a href="#init" class="headerlink" title="init"></a>init</h3><ul><li><p><strong>功能:</strong>init(modules, domApi),返回 patch() 函数(高阶函数)</p></li><li><p>为什么要使用高阶函数?</p><ul><li>因为 patch() 函数再外部会调用多次,每次调用依赖一些参数,比如:modules/domApi/cbs</li><li>通过高阶函数让 init() 内部形成闭包,返回的 patch() 可以访问到 modules/domApi/cbs,而不需要重新创建</li></ul></li><li><p>init() 在返回 patch() 之前,首先收集了所有模块中的钩子函数存储到 cbs 对象中</p></li><li><p>源码位置:src/package/init.ts</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-attr">hooks</span>: <span class="hljs-title class_">Array</span><keyof <span class="hljs-title class_">Module</span>> = [<span class="hljs-string">'create'</span>, <span class="hljs-string">'update'</span>, <span class="hljs-string">'remove'</span>, <span class="hljs-string">'destroy'</span>, <span class="hljs-string">'pre'</span>, <span class="hljs-string">'post'</span>]<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">init</span> (<span class="hljs-attr">modules</span>: <span class="hljs-title class_">Array</span><<span class="hljs-title class_">Partial</span><<span class="hljs-title class_">Module</span>>>, domApi?: <span class="hljs-variable constant_">DOMAPI</span>) {<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">i</span>: number<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">j</span>: number<br> <span class="hljs-keyword">const</span> <span class="hljs-attr">cbs</span>: <span class="hljs-title class_">ModuleHooks</span> = {<br> <span class="hljs-attr">create</span>: [],<br> <span class="hljs-attr">update</span>: [],<br> <span class="hljs-attr">remove</span>: [],<br> <span class="hljs-attr">destroy</span>: [],<br> <span class="hljs-attr">pre</span>: [],<br> <span class="hljs-attr">post</span>: []<br> }<br> <span class="hljs-comment">// 初始化 api</span><br> <span class="hljs-keyword">const</span> <span class="hljs-attr">api</span>: <span class="hljs-variable constant_">DOMAPI</span> = domApi !== <span class="hljs-literal">undefined</span> ? domApi : htmlDomApi<br> <span class="hljs-comment">// 把传入的所有模块的钩子方法,统一存储到 cbs 对象中</span><br> <span class="hljs-comment">// 最终构建的 cbs 对象的形式 cbs = [ create: [fn1, fn2], update: [], ... ]</span><br><span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < hooks.<span class="hljs-property">length</span>; ++i) {<br> <span class="hljs-comment">// cbs['create'] = []</span><br> cbs[hooks[i]] = []<br> <span class="hljs-keyword">for</span> (j = <span class="hljs-number">0</span>; j < modules.<span class="hljs-property">length</span>; ++j) {<br> <span class="hljs-comment">// const hook = modules[0]['create']</span><br> <span class="hljs-keyword">const</span> hook = modules[j][hooks[i]]<br> <span class="hljs-keyword">if</span> (hook !== <span class="hljs-literal">undefined</span>) {<br> (cbs[hooks[i]] <span class="hljs-keyword">as</span> any[]).<span class="hljs-title function_">push</span>(hook)<br> }<br> }<br> }<br> ……<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">patch</span> (<span class="hljs-attr">oldVnode</span>: <span class="hljs-title class_">VNode</span> | <span class="hljs-title class_">Element</span>, <span class="hljs-attr">vnode</span>: <span class="hljs-title class_">VNode</span>): <span class="hljs-title class_">VNode</span> {<br> ……<br> }<br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="patch"><a href="#patch" class="headerlink" title="patch"></a>patch</h3><ul><li><p><strong>功能:</strong></p><ul><li>传入新旧 VNode,对比差异,把差异渲染到 DOM</li><li>返回新的 VNode,作为下一次 patch() 的 oldVnode</li></ul></li><li><p><strong>执行过程:</strong></p><ul><li>首先执行<strong>模块</strong>中的<strong>钩子</strong>函数 <code>pre</code></li><li>如果 oldVnode 和 vnode 相同(key 和 sel 相同)<ul><li>调用 patchVnode(),找节点的差异并更新 DOM</li></ul></li><li>如果 oldVnode 是 DOM 元素<ul><li>把 DOM 元素转换成 oldVnode</li><li>调用 createElm() 把 vnode 转换为真实 DOM,记录到 vnode.elm</li><li>把刚创建的 DOM 元素插入到 parent 中</li><li>移除老节点</li><li>触发<strong>用户</strong>设置的 <code>create </code> <strong>钩子</strong>函数</li></ul></li></ul></li><li><p>源码位置:src/package/init.ts</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">patch</span> (<span class="hljs-attr">oldVnode</span>: <span class="hljs-title class_">VNode</span> | <span class="hljs-title class_">Element</span>, <span class="hljs-attr">vnode</span>: <span class="hljs-title class_">VNode</span>): <span class="hljs-title class_">VNode</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">i</span>: number, <span class="hljs-attr">elm</span>: <span class="hljs-title class_">Node</span>, <span class="hljs-attr">parent</span>: <span class="hljs-title class_">Node</span><br> <span class="hljs-comment">// 保存新插入节点的队列,为了触发钩子函数</span><br> <span class="hljs-keyword">const</span> <span class="hljs-attr">insertedVnodeQueue</span>: <span class="hljs-title class_">VNodeQueue</span> = []<br> <span class="hljs-comment">// 执行模块的 pre 钩子函数</span><br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < cbs.<span class="hljs-property">pre</span>.<span class="hljs-property">length</span>; ++i) cbs.<span class="hljs-property">pre</span>[i]()<br><span class="hljs-comment">// 如果 oldVnode 不是 VNode,创建 VNode 并设置 elm </span><br> <span class="hljs-keyword">if</span> (!<span class="hljs-title function_">isVnode</span>(oldVnode)) {<br> <span class="hljs-comment">// 把 DOM 元素转换成空的 VNode</span><br> oldVnode = <span class="hljs-title function_">emptyNodeAt</span>(oldVnode)<br> }<br><span class="hljs-comment">// 如果新旧节点是相同节点(key 和 sel 相同)</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">sameVnode</span>(oldVnode, vnode)) {<br> <span class="hljs-comment">// 找节点的差异并更新 DOM</span><br> <span class="hljs-title function_">patchVnode</span>(oldVnode, vnode, insertedVnodeQueue)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 如果新旧节点不同,vnode 创建对应的 DOM</span><br> <span class="hljs-comment">// 获取当前的 DOM 元素</span><br> elm = oldVnode.<span class="hljs-property">elm</span>!<br> parent = api.<span class="hljs-title function_">parentNode</span>(elm) <span class="hljs-keyword">as</span> <span class="hljs-title class_">Node</span><br><span class="hljs-comment">// 触发 init/create 钩子函数,创建 DOM</span><br> <span class="hljs-title function_">createElm</span>(vnode, insertedVnodeQueue)<br><br> <span class="hljs-keyword">if</span> (parent !== <span class="hljs-literal">null</span>) {<br> <span class="hljs-comment">// 如果父节点不为空,把 vnode 对应的 DOM 插入到文档中</span><br> api.<span class="hljs-title function_">insertBefore</span>(parent, vnode.<span class="hljs-property">elm</span>!, api.<span class="hljs-title function_">nextSibling</span>(elm))<br> <span class="hljs-comment">// 移除老节点</span><br> <span class="hljs-title function_">removeVnodes</span>(parent, [oldVnode], <span class="hljs-number">0</span>, <span class="hljs-number">0</span>)<br> }<br> }<br><span class="hljs-comment">// 执行用户设置的 insert 钩子函数</span><br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < insertedVnodeQueue.<span class="hljs-property">length</span>; ++i) {<br> insertedVnodeQueue[i].<span class="hljs-property">data</span>!.<span class="hljs-property">hook</span>!.<span class="hljs-property">insert</span>!(insertedVnodeQueue[i])<br> }<br> <span class="hljs-comment">// 执行模块的 post 钩子函数</span><br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < cbs.<span class="hljs-property">post</span>.<span class="hljs-property">length</span>; ++i) cbs.<span class="hljs-property">post</span>[i]()<br> <span class="hljs-keyword">return</span> vnode<br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="createElm"><a href="#createElm" class="headerlink" title="createElm"></a>createElm</h3><ul><li><p><strong>功能:</strong></p><ul><li>createElm(vnode, insertedVnodeQueue),返回创建的 DOM 元素</li><li>创建 vnode 对应的 DOM 元素</li></ul></li><li><p><strong>执行过程:</strong></p><ul><li>首先触发<strong>用户</strong>设置的 <strong>init</strong> <strong>钩子</strong>函数</li><li>如果选择器是!,创建评论节点</li><li>如果选择器为空,创建文本节点</li><li>如果选择器不为空<ul><li>解析选择器,设置标签的 id 和 class 属性</li><li>执行<strong>模块</strong>的 <strong>create</strong> <strong>钩子</strong>函数</li><li>如果 vnode 有 children,创建子 vnode 对应的 DOM,追加到 DOM 树</li><li>如果 vnode 的 text 值是 string/number,创建文本节点并追击到 DOM 树</li><li>执行<strong>用户</strong>设置的 <strong>create</strong> <strong>钩子</strong>函数</li><li>如果有用户设置的 insert 钩子函数,把 vnode 添加到队列中</li></ul></li></ul></li><li><p>源码位置:src/package/init.ts</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createElm</span> (<span class="hljs-attr">vnode</span>: <span class="hljs-title class_">VNode</span>, <span class="hljs-attr">insertedVnodeQueue</span>: <span class="hljs-title class_">VNodeQueue</span>): <span class="hljs-title class_">Node</span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">i</span>: any<br> <span class="hljs-keyword">let</span> data = vnode.<span class="hljs-property">data</span><br> <br> <span class="hljs-keyword">if</span> (data !== <span class="hljs-literal">undefined</span>) {<br> <span class="hljs-comment">// 执行用户设置的 init 钩子函数</span><br> <span class="hljs-keyword">const</span> init = data.<span class="hljs-property">hook</span>?.<span class="hljs-property">init</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(init)) {<br> <span class="hljs-title function_">init</span>(vnode)<br> data = vnode.<span class="hljs-property">data</span><br> }<br> }<br> <span class="hljs-keyword">const</span> children = vnode.<span class="hljs-property">children</span><br> <span class="hljs-keyword">const</span> sel = vnode.<span class="hljs-property">sel</span><br> <span class="hljs-keyword">if</span> (sel === <span class="hljs-string">'!'</span>) {<br> <span class="hljs-comment">// 如果选择器是!,创建注释节点</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isUndef</span>(vnode.<span class="hljs-property">text</span>)) {<br> vnode.<span class="hljs-property">text</span> = <span class="hljs-string">''</span><br> }<br> vnode.<span class="hljs-property">elm</span> = api.<span class="hljs-title function_">createComment</span>(vnode.<span class="hljs-property">text</span>!)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (sel !== <span class="hljs-literal">undefined</span>) {<br> <span class="hljs-comment">// 如果选择器不为空</span><br> <span class="hljs-comment">// 解析选择器</span><br> <span class="hljs-comment">// Parse selector</span><br> <span class="hljs-keyword">const</span> hashIdx = sel.<span class="hljs-title function_">indexOf</span>(<span class="hljs-string">'#'</span>)<br> <span class="hljs-keyword">const</span> dotIdx = sel.<span class="hljs-title function_">indexOf</span>(<span class="hljs-string">'.'</span>, hashIdx)<br> <span class="hljs-keyword">const</span> hash = hashIdx > <span class="hljs-number">0</span> ? hashIdx : sel.<span class="hljs-property">length</span><br> <span class="hljs-keyword">const</span> dot = dotIdx > <span class="hljs-number">0</span> ? dotIdx : sel.<span class="hljs-property">length</span><br> <span class="hljs-keyword">const</span> tag = hashIdx !== -<span class="hljs-number">1</span> || dotIdx !== -<span class="hljs-number">1</span> ? sel.<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(hash, dot)) : sel<br> <span class="hljs-keyword">const</span> elm = vnode.<span class="hljs-property">elm</span> = <span class="hljs-title function_">isDef</span>(data) && <span class="hljs-title function_">isDef</span>(i = data.<span class="hljs-property">ns</span>)<br> ? api.<span class="hljs-title function_">createElementNS</span>(i, tag)<br> : api.<span class="hljs-title function_">createElement</span>(tag)<br> <span class="hljs-keyword">if</span> (hash < dot) elm.<span class="hljs-title function_">setAttribute</span>(<span class="hljs-string">'id'</span>, sel.<span class="hljs-title function_">slice</span>(hash + <span class="hljs-number">1</span>, dot))<br> <span class="hljs-keyword">if</span> (dotIdx > <span class="hljs-number">0</span>) elm.<span class="hljs-title function_">setAttribute</span>(<span class="hljs-string">'class'</span>, sel.<span class="hljs-title function_">slice</span>(dot + <span class="hljs-number">1</span>).<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/\./g</span>, <span class="hljs-string">' '</span>))<br> <span class="hljs-comment">// 执行模块的 create 钩子函数</span><br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < cbs.<span class="hljs-property">create</span>.<span class="hljs-property">length</span>; ++i) cbs.<span class="hljs-property">create</span>[i](emptyNode, vnode)<br> <span class="hljs-comment">// 如果 vnode 中有子节点,创建子 vnode 对应的 DOM 元素并追加到 DOM 树上</span><br> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">array</span>(children)) {<br> <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < children.<span class="hljs-property">length</span>; ++i) {<br> <span class="hljs-keyword">const</span> ch = children[i]<br> <span class="hljs-keyword">if</span> (ch != <span class="hljs-literal">null</span>) {<br> api.<span class="hljs-title function_">appendChild</span>(elm, <span class="hljs-title function_">createElm</span>(ch <span class="hljs-keyword">as</span> <span class="hljs-title class_">VNode</span>, insertedVnodeQueue))<br> }<br> }<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (is.<span class="hljs-title function_">primitive</span>(vnode.<span class="hljs-property">text</span>)) {<br> <span class="hljs-comment">// 如果 vnode 的 text 值是 string/number,创建文本节点并追加到 DOM 树</span><br> api.<span class="hljs-title function_">appendChild</span>(elm, api.<span class="hljs-title function_">createTextNode</span>(vnode.<span class="hljs-property">text</span>))<br> }<br> <span class="hljs-keyword">const</span> hook = vnode.<span class="hljs-property">data</span>!.<span class="hljs-property">hook</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(hook)) {<br> <span class="hljs-comment">// 执行用户传入的钩子 create</span><br> hook.<span class="hljs-property">create</span>?.(emptyNode, vnode)<br> <span class="hljs-keyword">if</span> (hook.<span class="hljs-property">insert</span>) {<br> <span class="hljs-comment">// 把 vnode 添加到队列中,为后续执行 insert 钩子做准备</span><br> insertedVnodeQueue.<span class="hljs-title function_">push</span>(vnode)<br> }<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 如果选择器为空,创建文本节点</span><br> vnode.<span class="hljs-property">elm</span> = api.<span class="hljs-title function_">createTextNode</span>(vnode.<span class="hljs-property">text</span>!)<br> }<br> <span class="hljs-comment">// 返回新创建的 DOM </span><br> <span class="hljs-keyword">return</span> vnode.<span class="hljs-property">elm</span><br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="patchVnode"><a href="#patchVnode" class="headerlink" title="patchVnode"></a>patchVnode</h3><ul><li><p><strong>功能:</strong></p><ul><li>patchVnode(oldVnode, vnode, insertedVnodeQueue)</li><li>对比 oldVnode 和 vnode 的差异,把差异渲染到 DOM</li></ul></li><li><p><strong>执行过程:</strong></p><ul><li>首先执行<strong>用户</strong>设置的 <strong>prepatch</strong> <strong>钩子</strong>函数</li><li>执行 create 钩子函数<ul><li>首先执行<strong>模块</strong>的 <strong>create</strong> <strong>钩子</strong>函数</li><li>然后执行<strong>用户</strong>设置的 <strong>create</strong> <strong>钩子</strong>函数</li></ul></li><li>如果 <strong>vnode.text</strong> 未定义<ul><li>如果 <code>oldVnode.children</code> 和 <code>vnode.children</code> 都有值<ul><li>调用 <code>updateChildren()</code></li><li>使用 diff 算法对比子节点,更新子节点</li></ul></li><li>如果 <code>vnode.children</code> 有值,<code>oldVnode.children</code> 无值<ul><li>清空 DOM 元素</li><li>调用 <code>addVnodes()</code>,批量添加子节点</li></ul></li><li>如果 <code>oldVnode.children</code> 有值,<code>vnode.children</code> 无值<ul><li>调用 <code>removeVnodes()</code>,批量移除子节点</li></ul></li><li>如果 <strong>oldVnode.text</strong> 有值<ul><li>清空 DOM 元素的内容</li></ul></li></ul></li><li>如果设置了 <code>vnode.text</code> 并且和和 <code>oldVnode.text</code> 不等<ul><li>如果老节点有子节点,全部移除</li><li>设置 DOM 元素的 <code>textContent</code> 为 <code>vnode.text</code></li></ul></li><li>最后执行用户<strong>设置的</strong> <strong>postpatch</strong> <strong>钩子</strong>函数</li></ul></li><li><p>源码位置:src/package/init.ts</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">patchVnode</span> (<span class="hljs-attr">oldVnode</span>: <span class="hljs-title class_">VNode</span>, <span class="hljs-attr">vnode</span>: <span class="hljs-title class_">VNode</span>, <span class="hljs-attr">insertedVnodeQueue</span>: <span class="hljs-title class_">VNodeQueue</span>) {<br> <span class="hljs-keyword">const</span> hook = vnode.<span class="hljs-property">data</span>?.<span class="hljs-property">hook</span><br> <span class="hljs-comment">// 首先执行用户设置的 prepatch 钩子函数</span><br> hook?.<span class="hljs-property">prepatch</span>?.(oldVnode, vnode)<br> <span class="hljs-keyword">const</span> elm = vnode.<span class="hljs-property">elm</span> = oldVnode.<span class="hljs-property">elm</span>!<br> <span class="hljs-keyword">const</span> oldCh = oldVnode.<span class="hljs-property">children</span> <span class="hljs-keyword">as</span> <span class="hljs-title class_">VNode</span>[]<br> <span class="hljs-keyword">const</span> ch = vnode.<span class="hljs-property">children</span> <span class="hljs-keyword">as</span> <span class="hljs-title class_">VNode</span>[]<br> <span class="hljs-comment">// 如果新老 vnode 相同返回</span><br> <span class="hljs-keyword">if</span> (oldVnode === vnode) <span class="hljs-keyword">return</span><br> <span class="hljs-keyword">if</span> (vnode.<span class="hljs-property">data</span> !== <span class="hljs-literal">undefined</span>) {<br> <span class="hljs-comment">// 执行模块的 update 钩子函数</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < cbs.<span class="hljs-property">update</span>.<span class="hljs-property">length</span>; ++i) cbs.<span class="hljs-property">update</span>[i](oldVnode, vnode)<br> <span class="hljs-comment">// 执行用户设置的 update 钩子函数</span><br> vnode.<span class="hljs-property">data</span>.<span class="hljs-property">hook</span>?.<span class="hljs-property">update</span>?.(oldVnode, vnode)<br> }<br> <span class="hljs-comment">// 如果 vnode.text 未定义</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isUndef</span>(vnode.<span class="hljs-property">text</span>)) {<br> <span class="hljs-comment">// 如果新老节点都有 children</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(oldCh) && <span class="hljs-title function_">isDef</span>(ch)) {<br> <span class="hljs-comment">// 调用 updateChildren 对比子节点,更新子节点</span><br> <span class="hljs-keyword">if</span> (oldCh !== ch) <span class="hljs-title function_">updateChildren</span>(elm, oldCh, ch, insertedVnodeQueue)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(ch)) {<br> <span class="hljs-comment">// 如果新节点有 children,老节点没有 children</span><br> <span class="hljs-comment">// 如果老节点有text,清空dom 元素的内容</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(oldVnode.<span class="hljs-property">text</span>)) api.<span class="hljs-title function_">setTextContent</span>(elm, <span class="hljs-string">''</span>)<br> <span class="hljs-comment">// 批量添加子节点</span><br> <span class="hljs-title function_">addVnodes</span>(elm, <span class="hljs-literal">null</span>, ch, <span class="hljs-number">0</span>, ch.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>, insertedVnodeQueue)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(oldCh)) {<br> <span class="hljs-comment">// 如果老节点有children,新节点没有children</span><br> <span class="hljs-comment">// 批量移除子节点</span><br> <span class="hljs-title function_">removeVnodes</span>(elm, oldCh, <span class="hljs-number">0</span>, oldCh.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(oldVnode.<span class="hljs-property">text</span>)) {<br> <span class="hljs-comment">// 如果老节点有 text,清空 DOM 元素</span><br> api.<span class="hljs-title function_">setTextContent</span>(elm, <span class="hljs-string">''</span>)<br> }<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (oldVnode.<span class="hljs-property">text</span> !== vnode.<span class="hljs-property">text</span>) {<br> <span class="hljs-comment">// 如果没有设置 vnode.text</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isDef</span>(oldCh)) {<br> <span class="hljs-comment">// 如果老节点有 children,移除</span><br> <span class="hljs-title function_">removeVnodes</span>(elm, oldCh, <span class="hljs-number">0</span>, oldCh.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>)<br> }<br> <span class="hljs-comment">// 设置 DOM 元素的 textContent 为 vnode.text</span><br> api.<span class="hljs-title function_">setTextContent</span>(elm, vnode.<span class="hljs-property">text</span>!)<br> }<br> <span class="hljs-comment">// 最后执行用户设置的 postpatch 钩子函数</span><br> hook?.<span class="hljs-property">postpatch</span>?.(oldVnode, vnode)<br> }<br></code></pre></td></tr></table></figure></li></ul><h3 id="updateChildren"><a href="#updateChildren" class="headerlink" title="updateChildren"></a>updateChildren</h3><ul><li><p><strong>功能:</strong></p><ul><li>diff 算法的核心,对比新旧节点的 children,更新 DOM</li></ul></li><li><p><strong>执行过程:</strong></p><ul><li>要对比两棵树的差异,我们可以取第一棵树的每一个节点依次和第二课树的每一个节点比较,但是这样的时间复杂度为 O(n^3)</li><li>在DOM 操作的时候我们很少很少会把一个父节点移动/更新到某一个子节点</li><li>因此只需要找<strong>同级别</strong>的子<strong>节点</strong>依次<strong>比较</strong>,然后再找下一级别的节点比较,这样算法的时间复杂度为 O(n)</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/357779ed-ff33-4b39-8552-0495725caa0e" alt="image-20200102103653779"></p><ul><li>在进行同级别节点比较的时候,首先会对新老节点数组的开始和结尾节点设置标记索引,遍历的过程中移动索引</li><li>在对<strong>开始和结束节点</strong>比较的时候,总共有四种情况<ul><li>oldStartVnode / newStartVnode (旧开始节点 / 新开始节点)</li><li>oldEndVnode / newEndVnode (旧结束节点 / 新结束节点)</li><li>oldStartVnode / oldEndVnode (旧开始节点 / 新结束节点)</li><li>oldEndVnode / newStartVnode (旧结束节点 / 新开始节点)</li></ul></li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/b94ef29b-0eaa-4735-b612-fd4ec1026ef1" alt="image-20200109184608649"></p><ul><li><p>开始节点和结束节点比较,这两种情况类似</p><ul><li>oldStartVnode / newStartVnode (旧开始节点 / 新开始节点)</li><li>oldEndVnode / newEndVnode (旧结束节点 / 新结束节点)</li></ul></li><li><p>如果 oldStartVnode 和 newStartVnode 是 sameVnode (key 和 sel 相同)</p><ul><li>调用 patchVnode() 对比和更新节点</li><li>把旧开始和新开始索引往后移动 oldStartIdx++ / oldEndIdx++</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/66cad44e-42ac-447b-9f42-fe4acbb4ca73" alt="image-20200103121812840"></p></li><li><p>oldStartVnode / newEndVnode (旧开始节点 / 新结束节点) 相同<br>- 调用 patchVnode() 对比和更新节点</p><ul><li>把 oldStartVnode 对应的 DOM 元素,移动到右边<br>- 更新索引</li></ul></li></ul><p> <img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/ec75dbc0-8de2-4356-a273-f1e2ac3b47c9" alt="image-20200103125428541"></p><ul><li><p>oldEndVnode / newStartVnode (旧结束节点 / 新开始节点) 相同</p><ul><li>调用 patchVnode() 对比和更新节点</li><li>把 oldEndVnode 对应的 DOM 元素,移动到左边</li><li>更新索引</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/eda67988-af62-4692-957a-5260a797ecdb" alt="image-20200103125735048"></p></li><li><p>如果不是以上四种情况</p><ul><li>遍历新节点,使用 newStartNode 的 key 在老节点数组中找相同节点</li><li>如果没有找到,说明 newStartNode 是新节点<ul><li>创建新节点对应的 DOM 元素,插入到 DOM 树中</li></ul></li><li>如果找到了<ul><li>判断新节点和找到的老节点的 sel 选择器是否相同</li><li>如果不相同,说明节点被修改了<ul><li>重新创建对应的 DOM 元素,插入到 DOM 树中</li></ul></li><li>如果相同,把 elmToMove 对应的 DOM 元素,移动到左边</li></ul></li></ul></li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/1aa9102c-f0bf-4941-9fc7-74a37b64f59e" alt="image-20200109184822439"></p><ul><li>循环结束<ul><li>当老节点的所有子节点先遍历完 (oldStartIdx > oldEndIdx),循环结束</li><li>新节点的所有子节点先遍历完 (newStartIdx > newEndIdx),循环结束</li></ul></li><li>如果老节点的数组先遍历完(oldStartIdx > oldEndIdx),说明新节点有剩余,把剩余节点批量插入到右边</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/0f238b79-9e8e-44f9-af92-18d6706621d0" alt="image-20200103150918335"><br></p><ul><li>如果新节点的数组先遍历完(newStartIdx > newEndIdx),说明老节点有剩余,把剩余节点批量删除</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/045f37de-be02-4881-830c-95f9970026e7" alt="image-20200109194751093"></p></li><li><p>源码位置:src/package/init.ts</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">updateChildren</span> (<span class="hljs-attr">parentElm</span>: <span class="hljs-title class_">Node</span>,<br> <span class="hljs-attr">oldCh</span>: <span class="hljs-title class_">VNode</span>[],<br> <span class="hljs-attr">newCh</span>: <span class="hljs-title class_">VNode</span>[],<br> <span class="hljs-attr">insertedVnodeQueue</span>: <span class="hljs-title class_">VNodeQueue</span>) {<br> <span class="hljs-keyword">let</span> oldStartIdx = <span class="hljs-number">0</span><br> <span class="hljs-keyword">let</span> newStartIdx = <span class="hljs-number">0</span><br> <span class="hljs-keyword">let</span> oldEndIdx = oldCh.<span class="hljs-property">length</span> - <span class="hljs-number">1</span><br> <span class="hljs-keyword">let</span> oldStartVnode = oldCh[<span class="hljs-number">0</span>]<br> <span class="hljs-keyword">let</span> oldEndVnode = oldCh[oldEndIdx]<br> <span class="hljs-keyword">let</span> newEndIdx = newCh.<span class="hljs-property">length</span> - <span class="hljs-number">1</span><br> <span class="hljs-keyword">let</span> newStartVnode = newCh[<span class="hljs-number">0</span>]<br> <span class="hljs-keyword">let</span> newEndVnode = newCh[newEndIdx]<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">oldKeyToIdx</span>: <span class="hljs-title class_">KeyToIndexMap</span> | <span class="hljs-literal">undefined</span><br> <span class="hljs-keyword">let</span> <span class="hljs-attr">idxInOld</span>: number<br> <span class="hljs-keyword">let</span> <span class="hljs-attr">elmToMove</span>: <span class="hljs-title class_">VNode</span><br> <span class="hljs-keyword">let</span> <span class="hljs-attr">before</span>: any<br><br> <span class="hljs-keyword">while</span> (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {<br> <span class="hljs-comment">// 索引变化后,可能会把节点设置为空</span><br> <span class="hljs-keyword">if</span> (oldStartVnode == <span class="hljs-literal">null</span>) {<br> <span class="hljs-comment">// 节点为空移动索引</span><br> oldStartVnode = oldCh[++oldStartIdx] <span class="hljs-comment">// Vnode might have been moved left</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (oldEndVnode == <span class="hljs-literal">null</span>) {<br> oldEndVnode = oldCh[--oldEndIdx]<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (newStartVnode == <span class="hljs-literal">null</span>) {<br> newStartVnode = newCh[++newStartIdx]<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (newEndVnode == <span class="hljs-literal">null</span>) {<br> newEndVnode = newCh[--newEndIdx]<br> <span class="hljs-comment">// 比较开始和结束节点的四种情况</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">sameVnode</span>(oldStartVnode, newStartVnode)) {<br> <span class="hljs-comment">// 1. 比较老开始节点和新的开始节点</span><br> <span class="hljs-title function_">patchVnode</span>(oldStartVnode, newStartVnode, insertedVnodeQueue)<br> oldStartVnode = oldCh[++oldStartIdx]<br> newStartVnode = newCh[++newStartIdx]<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">sameVnode</span>(oldEndVnode, newEndVnode)) {<br> <span class="hljs-comment">// 2. 比较老结束节点和新的结束节点</span><br> <span class="hljs-title function_">patchVnode</span>(oldEndVnode, newEndVnode, insertedVnodeQueue)<br> oldEndVnode = oldCh[--oldEndIdx]<br> newEndVnode = newCh[--newEndIdx]<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">sameVnode</span>(oldStartVnode, newEndVnode)) { <span class="hljs-comment">// Vnode moved right</span><br> <span class="hljs-comment">// 3. 比较老开始节点和新的结束节点</span><br> <span class="hljs-title function_">patchVnode</span>(oldStartVnode, newEndVnode, insertedVnodeQueue)<br> api.<span class="hljs-title function_">insertBefore</span>(parentElm, oldStartVnode.<span class="hljs-property">elm</span>!, api.<span class="hljs-title function_">nextSibling</span>(oldEndVnode.<span class="hljs-property">elm</span>!))<br> oldStartVnode = oldCh[++oldStartIdx]<br> newEndVnode = newCh[--newEndIdx]<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">sameVnode</span>(oldEndVnode, newStartVnode)) { <span class="hljs-comment">// Vnode moved left</span><br> <span class="hljs-comment">// 4. 比较老结束节点和新的开始节点</span><br> <span class="hljs-title function_">patchVnode</span>(oldEndVnode, newStartVnode, insertedVnodeQueue)<br> api.<span class="hljs-title function_">insertBefore</span>(parentElm, oldEndVnode.<span class="hljs-property">elm</span>!, oldStartVnode.<span class="hljs-property">elm</span>!)<br> oldEndVnode = oldCh[--oldEndIdx]<br> newStartVnode = newCh[++newStartIdx]<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 开始节点和结束节点都不相同</span><br> <span class="hljs-comment">// 使用 newStartNode 的 key 再老节点数组中找相同节点</span><br> <span class="hljs-comment">// 先设置记录 key 和 index 的对象</span><br> <span class="hljs-keyword">if</span> (oldKeyToIdx === <span class="hljs-literal">undefined</span>) {<br> oldKeyToIdx = <span class="hljs-title function_">createKeyToOldIdx</span>(oldCh, oldStartIdx, oldEndIdx)<br> }<br> <span class="hljs-comment">// 遍历 newStartVnode, 从老的节点中找相同 key 的 oldVnode 的索引</span><br> idxInOld = oldKeyToIdx[newStartVnode.<span class="hljs-property">key</span> <span class="hljs-keyword">as</span> string]<br> <span class="hljs-comment">// 如果是新的vnode</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title function_">isUndef</span>(idxInOld)) { <span class="hljs-comment">// New element</span><br> <span class="hljs-comment">// 如果没找到,newStartNode 是新节点</span><br> <span class="hljs-comment">// 创建元素插入 DOM 树</span><br> api.<span class="hljs-title function_">insertBefore</span>(parentElm, <span class="hljs-title function_">createElm</span>(newStartVnode, insertedVnodeQueue), oldStartVnode.<span class="hljs-property">elm</span>!)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 如果找到相同 key 相同的老节点,记录到 elmToMove 遍历</span><br> elmToMove = oldCh[idxInOld]<br> <span class="hljs-keyword">if</span> (elmToMove.<span class="hljs-property">sel</span> !== newStartVnode.<span class="hljs-property">sel</span>) {<br> <span class="hljs-comment">// 如果新旧节点的选择器不同</span><br> <span class="hljs-comment">// 创建新开始节点对应的 DOM 元素,插入到 DOM 树中</span><br> api.<span class="hljs-title function_">insertBefore</span>(parentElm, <span class="hljs-title function_">createElm</span>(newStartVnode, insertedVnodeQueue), oldStartVnode.<span class="hljs-property">elm</span>!)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 如果相同,patchVnode()</span><br> <span class="hljs-comment">// 把 elmToMove 对应的 DOM 元素,移动到左边</span><br> <span class="hljs-title function_">patchVnode</span>(elmToMove, newStartVnode, insertedVnodeQueue)<br> oldCh[idxInOld] = <span class="hljs-literal">undefined</span> <span class="hljs-keyword">as</span> any<br> api.<span class="hljs-title function_">insertBefore</span>(parentElm, elmToMove.<span class="hljs-property">elm</span>!, oldStartVnode.<span class="hljs-property">elm</span>!)<br> }<br> }<br> <span class="hljs-comment">// 重新给 newStartVnode 赋值,指向下一个新节点</span><br> newStartVnode = newCh[++newStartIdx]<br> }<br> }<br> <span class="hljs-comment">// 循环结束,老节点数组先遍历完成或者新节点数组先遍历完成</span><br> <span class="hljs-keyword">if</span> (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {<br> <span class="hljs-keyword">if</span> (oldStartIdx > oldEndIdx) {<br> <span class="hljs-comment">// 如果老节点数组先遍历完成,说明有新的节点剩余</span><br> <span class="hljs-comment">// 把剩余的新节点都插入到右边</span><br> before = newCh[newEndIdx + <span class="hljs-number">1</span>] == <span class="hljs-literal">null</span> ? <span class="hljs-literal">null</span> : newCh[newEndIdx + <span class="hljs-number">1</span>].<span class="hljs-property">elm</span><br> <span class="hljs-title function_">addVnodes</span>(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 如果新节点数组先遍历完成,说明老节点有剩余</span><br> <span class="hljs-comment">// 批量删除老节点</span><br> <span class="hljs-title function_">removeVnodes</span>(parentElm, oldCh, oldStartIdx, oldEndIdx)<br> }<br> }<br>}<br></code></pre></td></tr></table></figure></li></ul><h3 id="调试-updateChildren"><a href="#调试-updateChildren" class="headerlink" title="调试 updateChildren"></a>调试 updateChildren</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">ul</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">li</span>></span>首页<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">li</span>></span>微博<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">li</span>></span>视频<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br><span class="hljs-tag"></<span class="hljs-name">ul</span>></span><br><br><span class="hljs-tag"><<span class="hljs-name">ul</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">li</span>></span>首页<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">li</span>></span>视频<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">li</span>></span>微博<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br><span class="hljs-tag"></<span class="hljs-name">ul</span>></span><br></code></pre></td></tr></table></figure><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/c3b5d4bd-e2d8-45e0-95a6-cf7edf3ca1ad" alt="image-20200112120036948"></p>]]></content>
<categories>
<category>Vue</category>
</categories>
<tags>
<tag>Vue</tag>
</tags>
</entry>
<entry>
<title>Virtual DOM 及 Diff 算法小结</title>
<link href="/2021/05/11/Virtual%20DOM%20%E5%8F%8A%20Diff%20%E7%AE%97%E6%B3%95/"/>
<url>/2021/05/11/Virtual%20DOM%20%E5%8F%8A%20Diff%20%E7%AE%97%E6%B3%95/</url>
<content type="html"><![CDATA[<h2 id="Virtual-DOM-及-Diff-算法小结"><a href="#Virtual-DOM-及-Diff-算法小结" class="headerlink" title="Virtual DOM 及 Diff 算法小结"></a>Virtual DOM 及 Diff 算法小结</h2><h3 id="1-JSX-到底是什么"><a href="#1-JSX-到底是什么" class="headerlink" title="1. JSX 到底是什么"></a>1. JSX 到底是什么</h3><p>使用 React 就一定会写 JSX,JSX 到底是什么呢?它是一种 JavaScript 语法的扩展,React 使用它来描述用户界面长成什么样子。虽然它看起来非常像 HTML,但它确实是 JavaScript 。在 React 代码执行之前,Babel 会对将 JSX 编译为 React API.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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><code class="hljs react"><div className="container"><br> <h3>Hello React</h3><br> <p>React is great </p><br></div><br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">React.createElement(<br> "div",<br> {<br> className: "container"<br> },<br> React.createElement("h3", null, "Hello React"),<br> React.createElement("p", null, "React is great")<br>);<br></code></pre></td></tr></table></figure><p>从两种语法对比来看,JSX 语法的出现是为了让 React 开发人员编写用户界面代码更加轻松。</p><p><a href="https://babeljs.io/repl">Babel REPL</a></p><h3 id="2-DOM-操作问题"><a href="#2-DOM-操作问题" class="headerlink" title="2. DOM 操作问题"></a>2. DOM 操作问题</h3><p>在现代 web 应用程序中使用 JavaScript 操作 DOM 是必不可少的,但遗憾的是它比其他大多数 JavaScript 操作要慢的多。</p><p>大多数 JavaScript 框架对于 DOM 的更新远远超过其必须进行的更新,从而使得这种缓慢操作变得更糟。</p><p>例如假设你有包含十个项目的列表,你仅仅更改了列表中的第一项,大多数 JavaScript 框架会重建整个列表,这比必要的工作要多十倍。</p><p>更新效率低下已经成为严重问题,为了解决这个问题,React 普及了一种叫做 Virtual DOM 的东西,Virtual DOM 出现的目的就是为了提高 JavaScript 操作 DOM 对象的效率。</p><h3 id="3-什么是-Virtual-DOM"><a href="#3-什么是-Virtual-DOM" class="headerlink" title="3. 什么是 Virtual DOM"></a>3. 什么是 Virtual DOM</h3><p>在 React 中,每个 DOM 对象都有一个对应的 Virtual DOM 对象,它是 DOM 对象的 JavaScript 对象表现形式,其实就是使用 JavaScript 对象来描述 DOM 对象信息,比如 DOM 对象的类型是什么,它身上有哪些属性,它拥有哪些子元素。</p><p>可以把 Virtual DOM 对象理解为 DOM 对象的副本,但是它不能直接显示在屏幕上。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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><code class="hljs react"><div className="container"><br> <h3>Hello React</h3><br> <p>React is great </p><br></div><br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs react">{<br> type: "div",<br> props: { className: "container" },<br> children: [<br> {<br> type: "h3",<br> props: null,<br> children: [<br> {<br> type: "text",<br> props: {<br> textContent: "Hello React"<br> }<br> }<br> ]<br> },<br> {<br> type: "p",<br> props: null,<br> children: [<br> {<br> type: "text",<br> props: {<br> textContent: "React is great"<br> }<br> }<br> ]<br> }<br> ]<br>}<br></code></pre></td></tr></table></figure><h3 id="4-Virtual-DOM-如何提升效率"><a href="#4-Virtual-DOM-如何提升效率" class="headerlink" title="4. Virtual DOM 如何提升效率"></a>4. Virtual DOM 如何提升效率</h3><p>精准找出发生变化的 DOM 对象,只更新发生变化的部分。</p><p>在 React 第一次创建 DOM 对象后,会为每个 DOM 对象创建其对应的 Virtual DOM 对象,在 DOM 对象发生更新之前,React 会先更新所有的 Virtual DOM 对象,然后 React 会将更新后的 Virtual DOM 和 更新前的 Virtual DOM 进行比较,从而找出发生变化的部分,React 会将发生变化的部分更新到真实的 DOM 对象中,React 仅更新必要更新的部分。</p><p>Virtual DOM 对象的更新和比较仅发生在内存中,不会在视图中渲染任何内容,所以这一部分的性能损耗成本是微不足道的。</p><p><img src="/img/React/1.png"></p><figure class="highlight plaintext"><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><code class="hljs react"><div id="container"><br><p>Hello React</p><br></div><br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><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><code class="hljs react"><div id="container"><br><p>Hello Angular</p><br></div><br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs react">const before = {<br> type: "div",<br> props: { id: "container" },<br> children: [<br> {<br> type: "p",<br> props: null,<br> children: [<br> { type: "text", props: { textContent: "Hello React" } }<br> ]<br> }<br> ]<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs react">const after = {<br> type: "div",<br> props: { id: "container" },<br> children: [<br> {<br> type: "p",<br> props: null,<br> children: [<br> { type: "text", props: { textContent: "Hello Angular" } }<br> ]<br> }<br> ]<br>}<br></code></pre></td></tr></table></figure><h3 id="5-创建-Virtual-DOM"><a href="#5-创建-Virtual-DOM" class="headerlink" title="5. 创建 Virtual DOM"></a>5. 创建 Virtual DOM</h3><p>在 React 代码执行前,JSX 会被 Babel 转换为 React.createElement 方法的调用,在调用 createElement 方法时会传入元素的类型,元素的属性,以及元素的子元素,createElement 方法的返回值为构建好的 Virtual DOM 对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">{<br> type: "div",<br> props: null,<br> children: [{type: "text", props: {textContent: "Hello"}}]<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">/**<br> * 创建 Virtual DOM<br> * @param {string} type 类型<br> * @param {object | null} props 属性<br> * @param {createElement[]} children 子元素<br> * @return {object} Virtual DOM<br> */<br>function createElement (type, props, ...children) {<br>return {<br> type,<br> props,<br> children<br> } <br>}<br></code></pre></td></tr></table></figure><p>从 createElement 方法的第三个参数开始就都是子元素了,在定义 createElement 方法时,通过 <code>...children</code> 将所有的子元素放置到 children 数组中。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">const virtualDOM = (<br> <div className="container"><br> <h1>你好 Tiny React</h1><br> <h2>(编码必杀技)</h2><br> <div><br> 嵌套1 <div>嵌套 1.1</div><br> </div><br> <h3>(观察: 这个将会被改变)</h3><br> {2 == 1 && <div>如果2和1相等渲染当前内容</div>}<br> {2 == 2 && <div>2</div>}<br> <span>这是一段内容</span><br> <button onClick={() => alert("你好")}>点击我</button><br> <h3>这个将会被删除</h3><br> 2, 3<br> </div><br>)<br>console.log(virtualDOM)<br></code></pre></td></tr></table></figure><p>通过以上代码测试,发现返回的 Virtual DOM 存在一些问题,第一个问题是文本节点被直接放入到了数组中</p><p><img src="/img/React/2.png"></p><p>而我们期望是文本节点应该是这样的</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">children: [<br> {<br> type: "text",<br> props: {<br> textContent: "React is great"<br> }<br> }<br>]<br></code></pre></td></tr></table></figure><p>通过以下代码对 Virtual DOM 进行改造,重新构建 Virtual DOM。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 将原有 children 拷贝一份 不要在原有数组上进行操作</span><br><span class="hljs-keyword">const</span> childElements = [].<span class="hljs-title function_">concat</span>(...children).<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">child</span> =></span> {<br> <span class="hljs-comment">// 判断 child 是否是对象类型</span><br> <span class="hljs-keyword">if</span> (child <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">Object</span>) {<br> <span class="hljs-comment">// 如果是 什么都不需要做 直接返回即可</span><br> <span class="hljs-keyword">return</span> child<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 如果不是对象就是文本 手动调用 createElement 方法将文本转换为 Virtual DOM</span><br> <span class="hljs-keyword">return</span> <span class="hljs-title function_">createElement</span>(<span class="hljs-string">"text"</span>, { <span class="hljs-attr">textContent</span>: child })<br> }<br>})<br><span class="hljs-keyword">return</span> {<br> type,<br> props,<br> <span class="hljs-attr">children</span>: childElements<br>}<br></code></pre></td></tr></table></figure><p><img src="/img/React/3.png"></p><p>通过观察返回的 Virtual DOM,文本节点已经被转化成了对象类型的 Virtual DOM,但是布尔值也被当做文本节点被转化了,在 JSX 中,如果 Virtual DOM 被转化为了布尔值或者null,是不应该被更新到真实 DOM 中的,所以接下来要做的事情就是清除 Virtual DOM 中的布尔值和null。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs react">// 由于 map 方法无法从数据中刨除元素, 所以此处将 map 方法更改为 reduce 方法<br>const childElements = [].concat(...children).reduce((result, child) => {<br> // 判断子元素类型 刨除 null true false<br> if (child != null && child != false && child != true) {<br> if (child instanceof Object) {<br> result.push(child)<br> } else {<br> result.push(createElement("text", { textContent: child }))<br> }<br> }<br> // 将需要保留的 Virtual DOM 放入 result 数组<br> return result<br>}, [])<br></code></pre></td></tr></table></figure><p>在 React 组件中,可以通过 props.children 获取子元素,所以还需要将子元素存储在 props 对象中。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">return</span> {<br> type,<br> <span class="hljs-attr">props</span>: <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">assign</span>({ <span class="hljs-attr">children</span>: childElements }, props),<br> <span class="hljs-attr">children</span>: childElements<br>}<br></code></pre></td></tr></table></figure><h3 id="6-渲染-Virtual-DOM-对象为-DOM-对象"><a href="#6-渲染-Virtual-DOM-对象为-DOM-对象" class="headerlink" title="6. 渲染 Virtual DOM 对象为 DOM 对象"></a>6. 渲染 Virtual DOM 对象为 DOM 对象</h3><p>通过调用 render 方法可以将 Virtual DOM 对象更新为真实 DOM 对象。</p><p>在更新之前需要确定是否存在旧的 Virtual DOM,如果存在需要比对差异,如果不存在可以直接将 Virtual DOM 转换为 DOM 对象。 </p><p>目前先只考虑不存在旧的 Virtual DOM 的情况,就是说先直接将 Virtual DOM 对象更新为真实 DOM 对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// render.js<br>export default function render(virtualDOM, container, oldDOM = container.firstChild) {<br> // 在 diff 方法内部判断是否需要对比 对比也好 不对比也好 都在 diff 方法中进行操作 <br> diff(virtualDOM, container, oldDOM)<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diff.js<br>import mountElement from "./mountElement"<br><br>export default function diff(virtualDOM, container, oldDOM) {<br> // 判断 oldDOM 是否存在<br> if (!oldDOM) {<br> // 如果不存在 不需要对比 直接将 Virtual DOM 转换为真实 DOM<br> mountElement(virtualDOM, container)<br> }<br>}<br></code></pre></td></tr></table></figure><p>在进行 virtual DOM 转换之前还需要确定 Virtual DOM 的类 Component VS Native Element。</p><p>类型不同需要做不同的处理 如果是 Native Element 直接转换。</p><p>如果是组件 还需要得到组件实例对象 通过组件实例对象获取组件返回的 virtual DOM 然后再进行转换。</p><p>目前先只考虑 Native Element 的情况。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// mountElement.js<br>import mountNativeElement from "./mountNativeElement"<br><br>export default function mountElement(virtualDOM, container) {<br> // 通过调用 mountNativeElement 方法转换 Native Element<br> mountNativeElement(virtualDOM, container)<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// mountNativeElement.js<br>import createDOMElement from "./createDOMElement"<br><br>export default function mountNativeElement(virtualDOM, container) {<br> const newElement = createDOMElement(virtualDOM)<br> container.appendChild(newElement)<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs react">// createDOMElement.js<br>import mountElement from "./mountElement"<br>import updateElementNode from "./updateElementNode"<br><br>export default function createDOMElement(virtualDOM) {<br> let newElement = null<br> if (virtualDOM.type === "text") {<br> // 创建文本节点<br> newElement = document.createTextNode(virtualDOM.props.textContent)<br> } else {<br> // 创建元素节点<br> newElement = document.createElement(virtualDOM.type)<br> // 更新元素属性<br> updateElementNode(newElement, virtualDOM)<br> }<br> // 递归渲染子节点<br> virtualDOM.children.forEach(child => {<br> // 因为不确定子元素是 NativeElement 还是 Component 所以调用 mountElement 方法进行确定<br> mountElement(child, newElement)<br> })<br> return newElement<br>}<br><br></code></pre></td></tr></table></figure><h3 id="7-为元素节点添加属性"><a href="#7-为元素节点添加属性" class="headerlink" title="7. 为元素节点添加属性"></a>7. 为元素节点添加属性</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// createDOMElement.js<br>// 看看节点类型是文本类型还是元素类型<br>if (virtualDOM.type === "text") {<br> // 创建文本节点 设置节点内容<br> newElement = document.createTextNode(virtualDOM.props.textContent)<br>} else {<br> // 根据 Virtual DOM type 属性值创建 DOM 元素<br> newElement = document.createElement(virtualDOM.type)<br> // 为元素设置属性<br> updateElementNode(newElement, virtualDOM)<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">export default function updateElementNode(element, virtualDOM) {<br> // 获取要解析的 VirtualDOM 对象中的属性对象<br> const newProps = virtualDOM.props<br> // 将属性对象中的属性名称放到一个数组中并循环数组<br> Object.keys(newProps).forEach(propName => {<br> const newPropsValue = newProps[propName]<br> // 考虑属性名称是否以 on 开头 如果是就表示是个事件属性 onClick -> click<br> if (propName.slice(0, 2) === "on") {<br> const eventName = propName.toLowerCase().slice(2)<br> element.addEventListener(eventName, newPropsValue)<br> // 如果属性名称是 value 或者 checked 需要通过 [] 的形式添加<br> } else if (propName === "value" || propName === "checked") {<br> element[propName] = newPropsValue<br> // 刨除 children 因为它是子元素 不是属性<br> } else if (propName !== "children") {<br> // className 属性单独处理 不直接在元素上添加 class 属性是因为 class 是 JavaScript 中的关键字<br> if (propName === "className") {<br> element.setAttribute("class", newPropsValue)<br> } else {<br> // 普通属性<br> element.setAttribute(propName, newPropsValue)<br> }<br> }<br> })<br>}<br></code></pre></td></tr></table></figure><h3 id="8-渲染组件"><a href="#8-渲染组件" class="headerlink" title="8. 渲染组件"></a>8. 渲染组件</h3><h4 id="8-1-函数组件"><a href="#8-1-函数组件" class="headerlink" title="8.1 函数组件"></a>8.1 函数组件</h4><p>在渲染组件之前首先要明确的是,组件的 Virtual DOM 类型值为函数,函数组件和类组件都是这样的。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs react">// 原始组件<br>const Heart = () => <span>&hearts;</span><br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs react"><Heart /><br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// 组件的 Virtual DOM<br>{<br> type: f function() {},<br> props: {}<br> children: []<br>}<br></code></pre></td></tr></table></figure><p>在渲染组件时,要先将 Component 与 Native Element 区分开,如果是 Native Element 可以直接开始渲染,如果是组件,特别处理。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs react">// mountElement.js<br>export default function mountElement(virtualDOM, container) {<br> // 无论是类组件还是函数组件 其实本质山都是函数 <br> // 如果 Virtual DOM 的 type 属性值为函数 就说明当前这个 Virtual DOM 为组件<br> if (isFunction(virtualDOM)) {<br> // 如果是组件 调用 mountComponent 方法进行组件渲染<br> mountComponent(virtualDOM, container)<br> } else {<br> mountNativeElement(virtualDOM, container)<br> }<br>}<br><br>// Virtual DOM 是否为函数类型<br>export function isFunction(virtualDOM) {<br> return virtualDOM && typeof virtualDOM.type === "function"<br>}<br></code></pre></td></tr></table></figure><p>在 mountComponent 方法中再进行函数组件和类型的区分,然后再分别进行处理。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// mountComponent.js<br>import mountNativeElement from "./mountNativeElement"<br><br>export default function mountComponent(virtualDOM, container) {<br> // 存放组件调用后返回的 Virtual DOM 的容器<br> let nextVirtualDOM = null<br> // 区分函数型组件和类组件<br> if (isFunctionalComponent(virtualDOM)) {<br> // 函数组件 调用 buildFunctionalComponent 方法处理函数组件<br> nextVirtualDOM = buildFunctionalComponent(virtualDOM)<br> } else {<br> // 类组件<br> }<br> // 判断得到的 Virtual Dom 是否是组件<br> if (isFunction(nextVirtualDOM)) {<br> // 如果是组件 继续调用 mountComponent 解剖组件<br> mountComponent(nextVirtualDOM, container)<br> } else {<br> // 如果是 Navtive Element 就去渲染<br> mountNativeElement(nextVirtualDOM, container)<br> }<br>}<br><br>// Virtual DOM 是否为函数型组件<br>// 条件有两个: 1. Virtual DOM 的 type 属性值为函数 2. 函数的原型对象中不能有render方法<br>// 只有类组件的原型对象中有render方法 <br>export function isFunctionalComponent(virtualDOM) {<br> const type = virtualDOM && virtualDOM.type<br> return (<br> type && isFunction(virtualDOM) && !(type.prototype && type.prototype.render)<br> )<br>}<br><br>// 函数组件处理 <br>function buildFunctionalComponent(virtualDOM) {<br> // 通过 Virtual DOM 中的 type 属性获取到组件函数并调用<br> // 调用组件函数时将 Virtual DOM 对象中的 props 属性传递给组件函数 这样在组件中就可以通过 props 属性获取数据了<br> // 组件返回要渲染的 Virtual DOM<br> return virtualDOM && virtualDOM.type(virtualDOM.props || {})<br>}<br></code></pre></td></tr></table></figure><h4 id="8-2-类组件"><a href="#8-2-类组件" class="headerlink" title="8.2 类组件"></a>8.2 类组件</h4><p>类组件本身也是 Virtual DOM,可以通过 Virtual DOM 中的 type 属性值确定当前要渲染的组件是类组件还是函数组件。</p><p>在确定当前要渲染的组件为类组件以后,需要实例化类组件得到类组件实例对象,通过类组件实例对象调用类组件中的 render 方法,获取组件要渲染的 Virtual DOM。</p><p>类组件需要继承 Component 父类,子类需要通过 super 方法将自身的 props 属性传递给 Component 父类,父类会将 props 属性挂载为父类属性,子类继承了父类,自己本身也就自然拥有props属性了。这样做的好处是当 props 发生更新后,父类可以根据更新后的 props 帮助子类更新视图。</p><p>假设以下代码就是我们要渲染的类组件:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs react">class Alert extends TinyReact.Component {<br> constructor(props) {<br> // 将 props 传递给父类 子类继承父类的 props 子类自然就有 props 数据了<br> // 否则 props 仅仅是 constructor 函数的参数而已<br> // 将 props 传递给父类的好处是 当 props 发生更改时 父类可以帮助更新 props 更新组件视图<br> super(props)<br> this.state = {<br> title: "default title"<br> }<br> }<br> render() {<br> return (<br> <div><br> <h2>{this.state.title}</h2><br> <p>{this.props.message}</p><br> </div><br> )<br> }<br>}<br><br>TinyReact.render(<Alert message="Hello React" />, root)<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// Component.js 父类 Component 实现<br>export default class Component {<br> constructor(props) {<br> this.props = props<br> }<br>}<br></code></pre></td></tr></table></figure><p>在 mountComponent 方法中通过调用 buildStatefulComponent 方法得到类组件要渲染的 Virtual DOM</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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></pre></td><td class="code"><pre><code class="hljs react">// mountComponent.js<br>export default function mountComponent(virtualDOM, container) {<br> let nextVirtualDOM = null<br> // 区分函数型组件和类组件<br> if (isFunctionalComponent(virtualDOM)) {<br> // 函数组件<br> nextVirtualDOM = buildFunctionalComponent(virtualDOM)<br> } else {<br> // 类组件<br> nextVirtualDOM = buildStatefulComponent(virtualDOM)<br> }<br> // 判断得到的 Virtual Dom 是否是组件<br> if (isFunction(nextVirtualDOM)) {<br> mountComponent(nextVirtualDOM, container)<br> } else {<br> mountNativeElement(nextVirtualDOM, container)<br> }<br>}<br><br>// 处理类组件<br>function buildStatefulComponent(virtualDOM) {<br> // 实例化类组件 得到类组件实例对象 并将 props 属性传递进类组件<br> const component = new virtualDOM.type(virtualDOM.props)<br> // 调用类组件中的render方法得到要渲染的 Virtual DOM<br> const nextVirtualDOM = component.render()<br> // 返回要渲染的 Virtual DOM<br> return nextVirtualDOM<br>}<br></code></pre></td></tr></table></figure><h3 id="9-Virtual-DOM-比对"><a href="#9-Virtual-DOM-比对" class="headerlink" title="9. Virtual DOM 比对"></a>9. Virtual DOM 比对</h3><p>在进行 Virtual DOM 比对时,需要用到更新后的 Virtual DOM 和更新前的 Virtual DOM,更新后的 Virtual DOM 目前我们可以通过 render 方法进行传递,现在的问题是更新前的 Virtual DOM 要如何获取呢?</p><p>对于更新前的 Virtual DOM,对应的其实就是已经在页面中显示的真实 DOM 对象。既然是这样,那么我们在创建真实DOM对象时,就可以将 Virtual DOM 添加到真实 DOM 对象的属性中。在进行 Virtual DOM 对比之前,就可以通过真实 DOM 对象获取其对应的 Virtual DOM 对象了,其实就是通过render方法的第三个参数获取的,container.firstChild。</p><p>在创建真实 DOM 对象时为其添加对应的 Virtual DOM 对象</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// mountElement.js<br>import mountElement from "./mountElement"<br><br>export default function mountNativeElement(virtualDOM, container) {<br> // 将 Virtual DOM 挂载到真实 DOM 对象的属性中 方便在对比时获取其 Virtual DOM<br> newElement._virtualDOM = virtualDOM<br>}<br></code></pre></td></tr></table></figure><p><img src="/img/React/8.png"></p><h4 id="9-1-Virtual-DOM-类型相同"><a href="#9-1-Virtual-DOM-类型相同" class="headerlink" title="9.1 Virtual DOM 类型相同"></a>9.1 Virtual DOM 类型相同</h4><p>Virtual DOM 类型相同,如果是元素节点,就对比元素节点属性是否发生变化,如果是文本节点就对比文本节点内容是否发生变化</p><p>要实现对比,需要先从已存在 DOM 对象中获取其对应的 Virtual DOM 对象。</p><figure class="highlight plaintext"><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><code class="hljs react">// diff.js<br>// 获取未更新前的 Virtual DOM<br>const oldVirtualDOM = oldDOM && oldDOM._virtualDOM<br></code></pre></td></tr></table></figure><p>判断 oldVirtualDOM 是否存在, 如果存在则继续判断要对比的 Virtual DOM 类型是否相同,如果类型相同判断节点类型是否是文本,如果是文本节点对比,就调用 updateTextNode 方法,如果是元素节点对比就调用 setAttributeForElement 方法</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diff.js<br>else if (oldVirtualDOM && virtualDOM.type === oldVirtualDOM.type) {<br> if (virtualDOM.type === "text") {<br> // 文本节点 对比文本内容是否发生变化<br> updateTextNode(virtualDOM, oldVirtualDOM, oldDOM)<br> } else {<br> // 元素节点 对比元素属性是否发生变化<br> setAttributeForElement(oldDOM, virtualDOM, oldVirtualDOM)<br> }<br></code></pre></td></tr></table></figure><p>updateTextNode 方法用于对比文本节点内容是否发生变化,如果发生变化则更新真实 DOM 对象中的内容,既然真实 DOM 对象发生了变化,还要将最新的 Virtual DOM 同步给真实 DOM 对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">function updateTextNode(virtualDOM, oldVirtualDOM, oldDOM) {<br> // 如果文本节点内容不同<br> if (virtualDOM.props.textContent !== oldVirtualDOM.props.textContent) {<br> // 更新真实 DOM 对象中的内容<br> oldDOM.textContent = virtualDOM.props.textContent<br> }<br> // 同步真实 DOM 对应的 Virtual DOM<br> oldDOM._virtualDOM = virtualDOM<br>}<br></code></pre></td></tr></table></figure><p>setAttributeForElement 方法用于设置/更新元素节点属性</p><p>思路是先分别获取更新后的和更新前的 Virtual DOM 中的 props 属性,循环新 Virtual DOM 中的 props 属性,通过对比看一下新 Virtual DOM 中的属性值是否发生了变化,如果发生变化 需要将变化的值更新到真实 DOM 对象中</p><p>再循环未更新前的 Virtual DOM 对象,通过对比看看新的 Virtual DOM 中是否有被删除的属性,如果存在删除的属性 需要将 DOM 对象中对应的属性也删除掉</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// updateNodeElement.js<br>export default function updateNodeElement(<br> newElement,<br> virtualDOM,<br> oldVirtualDOM = {}<br>) {<br> // 获取节点对应的属性对象<br> const newProps = virtualDOM.props || {}<br> const oldProps = oldVirtualDOM.props || {}<br> Object.keys(newProps).forEach(propName => {<br> // 获取属性值<br> const newPropsValue = newProps[propName]<br> const oldPropsValue = oldProps[propName]<br> if (newPropsValue !== oldPropsValue) {<br> // 判断属性是否是否事件属性 onClick -> click<br> if (propName.slice(0, 2) === "on") {<br> // 事件名称<br> const eventName = propName.toLowerCase().slice(2)<br> // 为元素添加事件<br> newElement.addEventListener(eventName, newPropsValue)<br> // 删除原有的事件的事件处理函数<br> if (oldPropsValue) {<br> newElement.removeEventListener(eventName, oldPropsValue)<br> }<br> } else if (propName === "value" || propName === "checked") {<br> newElement[propName] = newPropsValue<br> } else if (propName !== "children") {<br> if (propName === "className") {<br> newElement.setAttribute("class", newPropsValue)<br> } else {<br> newElement.setAttribute(propName, newPropsValue)<br> }<br> }<br> }<br> })<br> // 判断属性被删除的情况<br> Object.keys(oldProps).forEach(propName => {<br> const newPropsValue = newProps[propName]<br> const oldPropsValue = oldProps[propName]<br> if (!newPropsValue) {<br> // 属性被删除了<br> if (propName.slice(0, 2) === "on") {<br> const eventName = propName.toLowerCase().slice(2)<br> newElement.removeEventListener(eventName, oldPropsValue)<br> } else if (propName !== "children") {<br> newElement.removeAttribute(propName)<br> }<br> }<br> })<br>}<br></code></pre></td></tr></table></figure><p>以上对比的仅仅是最上层元素,上层元素对比完成以后还需要递归对比子元素</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">else if (oldVirtualDOM && virtualDOM.type === oldVirtualDOM.type) {<br> // 递归对比 Virtual DOM 的子元素<br> virtualDOM.children.forEach((child, i) => {<br> diff(child, oldDOM, oldDOM.childNodes[i])<br> })<br> }<br></code></pre></td></tr></table></figure><p><img src="/img/React/7.png"></p><h4 id="9-2-Virtual-DOM-类型不同"><a href="#9-2-Virtual-DOM-类型不同" class="headerlink" title="9.2 Virtual DOM 类型不同"></a>9.2 Virtual DOM 类型不同</h4><p>当对比的元素节点类型不同时,就不需要继续对比了,直接使用新的 Virtual DOM 创建 DOM 对象,用新的 DOM 对象直接替换旧的 DOM 对象。当前这种情况要将组件刨除,组件要被单独处理。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diff.js<br>else if (<br> // 如果 Virtual DOM 类型不一样<br> virtualDOM.type !== oldVirtualDOM.type &&<br> // 并且 Virtual DOM 不是组件 因为组件要单独进行处理<br> typeof virtualDOM.type !== "function"<br>) {<br> // 根据 Virtual DOM 创建真实 DOM 元素<br> const newDOMElement = createDOMElement(virtualDOM)<br> // 用创建出来的真实 DOM 元素 替换旧的 DOM 元素<br> oldDOM.parentNode.replaceChild(newDOMElement, oldDOM)<br>} <br></code></pre></td></tr></table></figure><h4 id="9-3-删除节点"><a href="#9-3-删除节点" class="headerlink" title="9.3 删除节点"></a>9.3 删除节点</h4><p>删除节点发生在节点更新以后并且发生在同一个父节点下的所有子节点身上。</p><p>在节点更新完成以后,如果旧节点对象的数量多于新 VirtualDOM 节点的数量,就说明有节点需要被删除。</p><p><img src="/img/React/5.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// 获取就节点的数量<br>let oldChildNodes = oldDOM.childNodes<br>// 如果旧节点的数量多于要渲染的新节点的长度<br>if (oldChildNodes.length > virtualDOM.children.length) {<br> for (<br> let i = oldChildNodes.length - 1;<br> i > virtualDOM.children.length - 1;<br> i--<br> ) {<br> oldDOM.removeChild(oldChildNodes[i])<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="9-4-类组件状态更新"><a href="#9-4-类组件状态更新" class="headerlink" title="9.4 类组件状态更新"></a>9.4 类组件状态更新</h4><p>以下代码是要更新状态的类组件,在类组件的 state 对象中有默认的 title 状态,点击 change title 按钮调用 handleChange 方法,在 handleChange 方法中调用 this.setState 方法更改 title 的状态值。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">class Alert extends TinyReact.Component {<br> constructor(props) {<br> super(props)<br> this.state = {<br> title: "default title"<br> }<br> // 更改 handleChange 方法中的 this 指向 让 this 指向类实例对象<br> this.handleChange = this.handleChange.bind(this)<br> }<br> handleChange() {<br> // 调用父类中的 setState 方法更改状态<br> this.setState({<br> title: "changed title"<br> })<br> }<br> render() {<br> return (<br> <div><br> <h2>{this.state.title}</h2><br> <p>{this.props.message}</p><br> <button onClick={this.handleChange}>change title</button><br> </div><br> )<br> }<br>}<br></code></pre></td></tr></table></figure><p>setState 方法是定义在父类 Component 中的,该方法的作用是更改子类的 state,产生一个全新的 state 对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// Component.js<br>export default class Component {<br> constructor(props) {<br> this.props = props<br> }<br> setState (state) {<br> // setState 方法被子类调用 此处this指向子类实例对象<br> // 所以改变的是子类的 state 对象<br> this.state = Object.assign({}, this.state, state)<br> }<br>}<br></code></pre></td></tr></table></figure><p>现在子类已经可以调用父类的 setState 方法更改状态值了,当组件的 state 对象发生更改时,要调用 render 方法更新组件视图。</p><p>在更新组件之前,要使用更新的 Virtual DOM 对象和未更新的 Virtual DOM 进行对比找出更新的部分,达到 DOM 最小化操作的目的。</p><p>在 setState 方法中可以通过调用 this.render 方法获取更新后的 Virtual DOM,由于 setState 方法被子类调用,this 指向子类,所以此处调用的是子类的 render 方法。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// Component.js<br>setState(state) {<br> // setState 方法被子类调用 此处this指向子类<br> // 所以改变的是子类的 state<br> this.state = Object.assign({}, this.state, state)<br> // 通过调用 render 方法获取最新的 Virtual DOM<br> let virtualDOM = this.render()<br>}<br></code></pre></td></tr></table></figure><p>要实现对比,还需要获取未更新前的 Virtual DOM,按照之前的经验,我们可以从 DOM 对象中获取其对应的 Virtual DOM 对象,未更新前的 DOM 对象实际上就是现在在页面中显示的 DOM 对象,我们只要能获取到这个 DOM 对象就可以获取到其对应的 Virtual DOM 对象了。</p><p>页面中的 DOM 对象要怎样获取呢?页面中的 DOM 对象是通过 mountNativeElement 方法挂载到页面中的,所以我们只需要在这个方法中调用 Component 类中的方法就可以将 DOM 对象保存在 Component 类中了。在子类调用 setState 方法的时候,在 setState 方法中再调用另一个获取 DOM 对象的方法就可以获取到之前保存的 DOM 对象了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// Component.js<br>// 保存 DOM 对象的方法<br>setDOM(dom) {<br> this._dom = dom<br>}<br>// 获取 DOM 对象的方法<br>getDOM() {<br> return this._dom<br>}<br></code></pre></td></tr></table></figure><p>接下来我们要研究一下在 mountNativeElement 方法中如何才能调用到 setDOM 方法,要调用 setDOM 方法,必须要得到类的实例对象,所以目前的问题就是如何在 mountNativeElement 方法中得到类的实例对象,这个类指的不是Component类,因为我们在代码中并不是直接实例化的Component类,而是实例化的它的子类,由于子类继承了父类,所以在子类的实例对象中也是可以调用到 setDOM 方法的。</p><p>mountNativeElement 方法接收最新的 Virtual DOM 对象,如果这个 Virtual DOM 对象是类组件产生的,在产生这个 Virtual DOM 对象时一定会先得到这个类的实例对象,然后再调用实例对象下面的 render 方法进行获取。我们可以在那个时候将类组件实例对象添加到 Virtual DOM 对象的属性中,而这个 Virtual DOM 对象最终会传递给 mountNativeElement 方法,这样我们就可以在 mountNativeElement 方法中获取到组件的实例对象了,既然类组件的实例对象获取到了,我们就可以调用 setDOM 方法了。</p><p>在 buildClassComponent 方法中为 Virtual DOM 对象添加 component 属性, 值为类组件的实例对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">function buildClassComponent(virtualDOM) {<br> const component = new virtualDOM.type(virtualDOM.props)<br> const nextVirtualDOM = component.render()<br> nextVirtualDOM.component = component<br> return nextVirtualDOM<br>}<br></code></pre></td></tr></table></figure><p>在 mountNativeElement 方法中获取组件实例对象,通过实例调用调用 setDOM 方法保存 DOM 对象,方便在对比时通过它获取它的 Virtual DOM 对象</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">export default function mountNativeElement(virtualDOM, container) {<br> // 获取组件实例对象<br> const component = virtualDOM.component<br> // 如果组件实例对象存在<br> if (component) {<br> // 保存 DOM 对象<br> component.setDOM(newElement)<br> }<br>}<br></code></pre></td></tr></table></figure><p>接下来在 setState 方法中就可以调用 getDOM 方法获取 DOM 对象了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">setState(state) {<br> this.state = Object.assign({}, this.state, state)<br> let virtualDOM = this.render()<br> // 获取页面中正在显示的 DOM 对象 通过它可以获取其对象的 Virtual DOM 对象<br> let oldDOM = this.getDOM()<br>}<br></code></pre></td></tr></table></figure><p>现在更新前的 Virtual DOM 对象和更新后的 Virtual DOM 对象就都已经获取到了,接下来还要获取到真实 DOM 对象父级容器对象,因为在调用 diff 方法进行对比的时候需要用到</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">setState(state) {<br> this.state = Object.assign({}, this.state, state)<br> let virtualDOM = this.render()<br> let oldDOM = this.getDOM()<br> // 获取真实 DOM 对象父级容器对象<br> let container = oldDOM.parentNode<br>}<br></code></pre></td></tr></table></figure><p>接下来就可以调用 diff 方法进行比对了,比对后会按照我们之前写好的逻辑进行 DOM 对象更新,我们就可以在页面中看到效果了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">setState(state) {<br> this.state = Object.assign({}, this.state, state)<br> let virtualDOM = this.render()<br> let oldDOM = this.getDOM()<br> let container = oldDOM.parentNode<br> // 比对<br> diff(virtualDOM, container, oldDOM)<br> }<br></code></pre></td></tr></table></figure><h4 id="9-5-组件更新"><a href="#9-5-组件更新" class="headerlink" title="9.5 组件更新"></a>9.5 组件更新</h4><p>在 diff 方法中判断要更新的 Virtual DOM 是否是组件。</p><p>如果是组件再判断要更新的组件和未更新前的组件是否是同一个组件,如果不是同一个组件就不需要做组件更新操作,直接调用 mountElement 方法将组件返回的 Virtual DOM 添加到页面中。</p><p>如果是同一个组件,就执行更新组件操作,其实就是将最新的 props 传递到组件中,再调用组件的render方法获取组件返回的最新的 Virtual DOM 对象,再将 Virtual DOM 对象传递给 diff 方法,让 diff 方法找出差异,从而将差异更新到真实 DOM 对象中。</p><p>在更新组件的过程中还要在不同阶段调用其不同的组件生命周期函数。</p><p>在 diff 方法中判断要更新的 Virtual DOM 是否是组件,如果是组件又分为多种情况,新增 diffComponent 方法进行处理</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">else if (typeof virtualDOM.type === "function") {<br> // 要更新的是组件<br> // 1) 组件本身的 virtualDOM 对象 通过它可以获取到组件最新的 props<br> // 2) 要更新的组件的实例对象 通过它可以调用组件的生命周期函数 可以更新组件的 props 属性 可以获取到组件返回的最新的 Virtual DOM<br> // 3) 要更新的 DOM 象 在更新组件时 需要在已有DOM对象的身上进行修改 实现DOM最小化操作 获取旧的 Virtual DOM 对象<br> // 4) 如果要更新的组件和旧组件不是同一个组件 要直接将组件返回的 Virtual DOM 显示在页面中 此时需要 container 做为父级容器<br> diffComponent(virtualDOM, oldComponent, oldDOM, container)<br>}<br></code></pre></td></tr></table></figure><p>在 diffComponent 方法中判断要更新的组件是未更新前的组件是否是同一个组件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diffComponent.js<br>export default function diffComponent(virtualDOM, oldComponent, oldDOM, container) {<br> // 判断要更新的组件和未更新的组件是否是同一个组件 只需要确定两者使用的是否是同一个构造函数就可以了<br> if (isSameComponent(virtualDOM, oldComponent)) {<br> // 属同一个组件 做组件更新 <br> } else {<br> // 不是同一个组件 直接将组件内容显示在页面中<br> }<br>}<br>// virtualDOM.type 更新后的组件构造函数<br>// oldComponent.constructor 未更新前的组件构造函数<br>// 两者等价就表示是同一组件<br>function isSameComponent(virtualDOM, oldComponent) {<br> return oldComponent && virtualDOM.type === oldComponent.constructor<br>}<br></code></pre></td></tr></table></figure><p>如果不是同一个组件的话,就不需要执行更新组件的操作,直接将组件内容显示在页面中,替换原有内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diffComponent.js<br>else {<br> // 不是同一个组件 直接将组件内容显示在页面中<br> // 这里为 mountElement 方法新增了一个参数 oldDOM <br> // 作用是在将 DOM 对象插入到页面前 将页面中已存在的 DOM 对象删除 否则无论是旧DOM对象还是新DOM对象都会显示在页面中<br> mountElement(virtualDOM, container, oldDOM)<br>}<br></code></pre></td></tr></table></figure><p>在 mountNativeElement 方法中删除原有的旧 DOM 对象</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><code class="hljs javascript"><span class="hljs-comment">// mountNavtiveElement.js</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">mountNativeElement</span>(<span class="hljs-params">virtualDOM, container, oldDOM</span>) {<br> <span class="hljs-comment">// 如果旧的DOM对象存在 删除</span><br> <span class="hljs-keyword">if</span> (oldDOM) {<br> <span class="hljs-title function_">unmount</span>(oldDOM)<br> }<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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><code class="hljs react">// unmount.js<br>export default function unmount(node) {<br> node.remove()<br>}<br></code></pre></td></tr></table></figure><p>如果是同一个组件的话,需要执行组件更新操作,需要调用组件生命周期函数</p><p>先在 Component 类中添加生命周期函数,子类要使用的话直接覆盖就可以</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs react">// Component.js<br>export default class Component {<br> // 生命周期函数<br> componentWillMount() {}<br> componentDidMount() {}<br> componentWillReceiveProps(nextProps) {}<br> shouldComponentUpdate(nextProps, nextState) {<br> return nextProps != this.props || nextState != this.state<br> }<br> componentWillUpdate(nextProps, nextState) {}<br> componentDidUpdate(prevProps, preState) {}<br> componentWillUnmount() {}<br>}<br></code></pre></td></tr></table></figure><p>新建 updateComponent 方法用于更新组件操作,并在 if 成立后调用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diffComponent.js<br>if (isSameComponent(virtualDOM, oldComponent)) {<br> // 属同一个组件 做组件更新<br> updateComponent(virtualDOM, oldComponent, oldDOM, container)<br>}<br></code></pre></td></tr></table></figure><p>在 updateComponent 方法中调用组件的生命周期函数,更新组件获取最新 Virtual DOM,最终调用 diff 方法进行更新</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs react">import diff from "./diff"<br><br>export default function updateComponent(<br> virtualDOM,<br> oldComponent,<br> oldDOM,<br> container<br>) {<br> // 生命周期函数<br> oldComponent.componentWillReceiveProps(virtualDOM.props)<br> if (<br> // 调用 shouldComponentUpdate 生命周期函数判断是否要执行更新操作<br> oldComponent.shouldComponentUpdate(virtualDOM.props)<br> ) {<br> // 将未更新的 props 保存一份<br> let prevProps = oldComponent.props<br> // 生命周期函数<br> oldComponent.componentWillUpdate(virtualDOM.props)<br> // 更新组件的 props 属性 updateProps 方法定义在 Component 类型<br> oldComponent.updateProps(virtualDOM.props)<br> // 因为组件的 props 已经更新 所以调用 render 方法获取最新的 Virtual DOM<br> const nextVirtualDOM = oldComponent.render()<br> // 将组件实例对象挂载到 Virtual DOM 身上<br> nextVirtualDOM.component = oldComponent<br> // 调用diff方法更新视图<br> diff(nextVirtualDOM, container, oldDOM)<br> // 生命周期函数<br> oldComponent.componentDidUpdate(prevProps)<br> }<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// Component.js<br>export default class Component {<br> updateProps(props) {<br> this.props = props<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="10-ref-属性"><a href="#10-ref-属性" class="headerlink" title="10. ref 属性"></a>10. ref 属性</h3><p>为节点添加 ref 属性可以获取到这个节点的 DOM 对象,比如在 DemoRef 类中,为 input 元素添加了 ref 属性,目的是获取 input DOM 元素对象,在点击按钮时获取用户在文本框中输入的内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">class DemoRef extends TinyReact.Component {<br> handle() {<br> let value = this.input.value<br> console.log(value)<br> }<br> render() {<br> return (<br> <div><br> <input type="text" ref={input => (this.input = input)} /><br> <button onClick={this.handle.bind(this)}>按钮</button><br> </div><br> )<br> }<br>}<br></code></pre></td></tr></table></figure><p>实现思路是在创建节点时判断其 Virtual DOM 对象中是否有 ref 属性,如果有就调用 ref 属性中所存储的方法并且将创建出来的DOM对象作为参数传递给 ref 方法,这样在渲染组件节点的时候就可以拿到元素对象并将元素对象存储为组件属性了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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><code class="hljs react">// createDOMElement.js<br>if (virtualDOM.props && virtualDOM.props.ref) {<br> virtualDOM.props.ref(newElement)<br>}<br></code></pre></td></tr></table></figure><p>在类组件的身上也可以添加 ref 属性,目的是获取组件的实例对象,比如下列代码中,在 DemoRef 组件中渲染了 Alert 组件,在 Alert 组件中添加了 ref 属性,目的是在 DemoRef 组件中获取 Alert 组件实例对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">class DemoRef extends TinyReact.Component {<br> handle() {<br> let value = this.input.value<br> console.log(value)<br> console.log(this.alert)<br> }<br> componentDidMount() {<br> console.log("componentDidMount")<br> }<br> render() {<br> return (<br> <div><br> <input type="text" ref={input => (this.input = input)} /><br> <button onClick={this.handle.bind(this)}>按钮</button><br> <Alert ref={alert => (this.alert = alert)} /><br> </div><br> )<br> }<br>}<br></code></pre></td></tr></table></figure><p>实现思路是在 mountComponent 方法中,如果判断了当前处理的是类组件,就通过类组件返回的 Virtual DOM 对象中获取组件实例对象,判断组件实例对象中的 props 属性中是否存在 ref 属性,如果存在就调用 ref 方法并且将组件实例对象传递给 ref 方法。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// mountComponent.js<br>let component = null<br> if (isFunctionalComponent(virtualDOM)) {}<br>else {<br> // 类组件<br> nextVirtualDOM = buildStatefulComponent(virtualDOM)<br> // 获取组件实例对象<br> component = nextVirtualDOM.component<br> }<br>// 如果组件实例对象存在的话<br>if (component) {<br> // 判断组件实例对象身上是否有 props 属性 props 属性中是否有 ref 属性<br> if (component.props && component.props.ref) {<br> // 调用 ref 方法并传递组件实例对象<br> component.props.ref(component)<br> }<br> }<br><br></code></pre></td></tr></table></figure><p>代码走到这,顺便处理一下组件挂载完成的生命周期函数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><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><code class="hljs react">// 如果组件实例对象存在的话<br>if (component) {<br> component.componentDidMount()<br>}<br></code></pre></td></tr></table></figure><h3 id="11-key-属性"><a href="#11-key-属性" class="headerlink" title="11. key 属性"></a>11. key 属性</h3><p>在 React 中,渲染列表数据时通常会在被渲染的列表元素上添加 key 属性,key 属性就是数据的唯一标识,帮助 React 识别哪些数据被修改或者删除了,从而达到 DOM 最小化操作的目的。</p><p>key 属性不需要全局唯一,但是在同一个父节点下的兄弟节点之间必须是唯一的。</p><p>也就是说,在比对同一个父节点下类型相同的子节点时需要用到 key 属性。</p><h4 id="11-1-节点对比"><a href="#11-1-节点对比" class="headerlink" title="11.1 节点对比"></a>11.1 节点对比</h4><p>实现思路是在两个元素进行比对时,如果类型相同,就循环旧的 DOM 对象的子元素,查看其身上是否有key 属性,如果有就将这个子元素的 DOM 对象存储在一个 JavaScript 对象中,接着循环要渲染的 Virtual DOM 对象的子元素,在循环过程中获取到这个子元素的 key 属性,然后使用这个 key 属性到 JavaScript 对象中查找 DOM 对象,如果能够找到就说明这个元素是已经存在的,是不需要重新渲染的。如果通过key属性找不到这个元素,就说明这个元素是新增的是需要渲染的。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// diff.js<br>else if (oldVirtualDOM && virtualDOM.type === oldVirtualDOM.type) {<br> // 将拥有key属性的元素放入 keyedElements 对象中<br> let keyedElements = {}<br> for (let i = 0, len = oldDOM.childNodes.length; i < len; i++) {<br> let domElement = oldDOM.childNodes[i]<br> if (domElement.nodeType === 1) {<br> let key = domElement.getAttribute("key")<br> if (key) {<br> keyedElements[key] = domElement<br> }<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs react">// diff.js<br>// 看一看是否有找到了拥有 key 属性的元素<br>let hasNoKey = Object.keys(keyedElements).length === 0<br><br>// 如果没有找到拥有 key 属性的元素 就按照索引进行比较<br>if (hasNoKey) {<br> // 递归对比 Virtual DOM 的子元素<br> virtualDOM.children.forEach((child, i) => {<br> diff(child, oldDOM, oldDOM.childNodes[i])<br> })<br>} else {<br> // 使用key属性进行元素比较<br> virtualDOM.children.forEach((child, i) => {<br> // 获取要进行比对的元素的 key 属性<br> let key = child.props.key<br> // 如果 key 属性存在<br> if (key) {<br> // 到已存在的 DOM 元素对象中查找对应的 DOM 元素<br> let domElement = keyedElements[key]<br> // 如果找到元素就说明该元素已经存在 不需要重新渲染<br> if (domElement) {<br> // 虽然 DOM 元素不需要重新渲染 但是不能确定元素的位置就一定没有发生变化<br> // 所以还要查看一下元素的位置<br> // 看一下 oldDOM 对应的(i)子元素和 domElement 是否是同一个元素 如果不是就说明元素位置发生了变化<br> if (oldDOM.childNodes[i] && oldDOM.childNodes[i] !== domElement) {<br> // 元素位置发生了变化<br> // 将 domElement 插入到当前元素位置的前面 oldDOM.childNodes[i] 就是当前位置<br> // domElement 就被放入了当前位置<br> oldDOM.insertBefore(domElement, oldDOM.childNodes[i])<br> }<br> } else {<br> mountElement(child, oldDOM, oldDOM.childNodes[i])<br> }<br> }<br> })<br>}<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs react">// mountNativeElement.js<br>if (oldDOM) {<br> container.insertBefore(newElement, oldDOM)<br>} else {<br> // 将转换之后的DOM对象放置在页面中<br> container.appendChild(newElement)<br>}<br></code></pre></td></tr></table></figure><h4 id="11-2-节点卸载"><a href="#11-2-节点卸载" class="headerlink" title="11.2 节点卸载"></a>11.2 节点卸载</h4><p>在比对节点的过程中,如果旧节点的数量多于要渲染的新节点的数量就说明有节点被删除了,继续判断 keyedElements 对象中是否有元素,如果没有就使用索引方式删除,如果有就要使用 key 属性比对的方式进行删除。</p><p>实现思路是循环旧节点,在循环旧节点的过程中获取旧节点对应的 key 属性,然后根据 key 属性在新节点中查找这个旧节点,如果找到就说明这个节点没有被删除,如果没有找到,就说明节点被删除了,调用卸载节点的方法卸载节点即可。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs react">// 获取就节点的数量<br>let oldChildNodes = oldDOM.childNodes<br>// 如果旧节点的数量多于要渲染的新节点的长度<br>if (oldChildNodes.length > virtualDOM.children.length) {<br> if (hasNoKey) {<br> for (<br> let i = oldChildNodes.length - 1;<br> i >= virtualDOM.children.length;<br> i--<br> ) {<br> oldDOM.removeChild(oldChildNodes[i])<br> }<br> } else {<br> for (let i = 0; i < oldChildNodes.length; i++) {<br> let oldChild = oldChildNodes[i]<br> let oldChildKey = oldChild._virtualDOM.props.key<br> let found = false<br> for (let n = 0; n < virtualDOM.children.length; n++) {<br> if (oldChildKey === virtualDOM.children[n].props.key) {<br> found = true<br> break<br> }<br> }<br> if (!found) {<br> unmount(oldChild)<br> i--<br> }<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>卸载节点并不是说将节点直接删除就可以了,还需要考虑以下几种情况</p><ol><li>如果要删除的节点是文本节点的话可以直接删除</li><li>如果要删除的节点由组件生成,需要调用组件卸载生命周期函数</li><li>如果要删除的节点中包含了其他组件生成的节点,需要调用其他组件的卸载生命周期函数</li><li>如果要删除的节点身上有 ref 属性,还需要删除通过 ref 属性传递给组件的 DOM 节点对象</li><li>如果要删除的节点身上有事件,需要删除事件对应的事件处理函数</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs react">export default function unmount(dom) {<br> // 获取节点对应的 virtualDOM 对象<br> const virtualDOM = dom._virtualDOM<br> // 如果要删除的节点时文本<br> if (virtualDOM.type === "text") {<br> // 直接删除节点<br> dom.remove()<br> // 阻止程序向下运行<br> return<br> }<br> // 查看节点是否由组件生成<br> let component = virtualDOM.component<br> // 如果由组件生成<br> if (component) {<br> // 调用组件卸载生命周期函数<br> component.componentWillUnmount()<br> }<br> <br> // 如果节点具有 ref 属性 通过再次调用 ref 方法 将传递给组件的DOM对象删除<br> if (virtualDOM.props && virtualDOM.props.ref) {<br> virtualDOM.props.ref(null)<br> }<br><br> // 事件处理<br> Object.keys(virtualDOM.props).forEach(propName => {<br> if (propName.slice(0, 2) === "on") {<br> const eventName = propName.toLowerCase().slice(2)<br> const eventHandler = virtualDOM.props[propName]<br> dom.removeEventListener(eventName, eventHandler)<br> }<br> })<br><br> // 递归删除子节点<br> if (dom.childNodes.length > 0) {<br> for (let i = 0; i < dom.childNodes.length; i++) {<br> unmount(dom.childNodes[i])<br> i--<br> }<br> }<br> <br> dom.remove()<br>}<br><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
</tags>
</entry>
<entry>
<title>配置React开发格式化工具指南与避坑</title>
<link href="/2021/04/26/%E9%85%8D%E7%BD%AEReact%E5%BC%80%E5%8F%91%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%B7%A5%E5%85%B7%E6%8C%87%E5%8D%97%E4%B8%8E%E9%81%BF%E5%9D%91/"/>
<url>/2021/04/26/%E9%85%8D%E7%BD%AEReact%E5%BC%80%E5%8F%91%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%B7%A5%E5%85%B7%E6%8C%87%E5%8D%97%E4%B8%8E%E9%81%BF%E5%9D%91/</url>
<content type="html"><![CDATA[<h1 id="配置React开发格式化工具指南与避坑"><a href="#配置React开发格式化工具指南与避坑" class="headerlink" title="配置React开发格式化工具指南与避坑"></a>配置React开发格式化工具指南与避坑</h1><p>前言: <em>开发项目之前,定下良好的开发规范,能帮助我们规范流程,避免因为偷懒等因素,造成后期项目难以维护,commit log难以阅读等问题。本次借助项目机会,利用格式化工具<code>prettier</code>,对项目的格式化,<code>commitlint</code>对<code>git commit</code>进行约束。便于后期的维护,方便其他人阅读代码。中间踩很多坑,希望能给大家带来帮助</em></p><p>注:本次项目基于create-react-app创建的react项目</p><h2 id="配置prettier格式化工具"><a href="#配置prettier格式化工具" class="headerlink" title="配置prettier格式化工具"></a>配置<code>prettier</code>格式化工具</h2><ol><li>react不是为我们提供了<code>eslint</code>吗?</li></ol><p>答:<code>eslint</code>只能进行语法检查,只能帮我们检查语法错误。<code>prettier</code>时代码格式化工具,能保证我们代码的格式,即使其他人阅读你的代码,也看到的时和你一样风格的代码。</p><ol start="2"><li>为什么使用?<ul><li>按保存键时,代码就被格式化了</li><li>代码评审时无须争论代码样式</li><li>节省时间和精力</li></ul></li></ol><p><strong>如果你按照<code>prettier</code>进行安装使用,其中有几个坑需要注意,请往下阅读。</strong></p><h4 id="简单配置"><a href="#简单配置" class="headerlink" title="简单配置"></a>简单配置</h4><ol><li><p>安装</p><p><code>npm install --save-dev --save-exact prettier</code></p></li><li><p><code>echo {}> .prettierrc.json</code> </p><p><strong>如果你使用的是<code>windows</code>电脑并且使用<code>vscode</code>终端,按照官网在你的项目中使用这条命令创建<code>.prettierrc.json</code>文件,创建过程不会出现任何的错误,但是在你下面操作的过程中,如使用<code>npx prettier --write .</code>命令时会报错,并且你很难定位。原因是,创建的过程中,会创建出一个格式不是<code>utd8</code>格式的<code>json</code>文件,取决于你的系统和电脑。因为报错信息没有精准定位,导致很难排查。你可以手动创建该文件或者将该文件的格式修改为<code>utf8</code></strong></p></li><li><p>创建<code>.prettierignore</code>文件,用来忽略那些文件或者文件夹不需要进行格式化</p></li><li><p>进行格式化<code>npx prettier --write .</code></p><p>使用上面这条命令可以将你选择的文件或者文件夹内的文件进行格式化。如下图所示,格式化之前和格式化之后的对比。</p><ul><li>格式化之前的代码片段</li></ul><p> <img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/78fe79ab-d21f-44f4-9f3f-66cbe8164fb0" alt="prettier"></p><p>格式化之前代码没有换行,所以超出了vscode的显示区,或许很多同学都会配置vscode的最大显示长度。但是因为每个人配置不一样,为了能让一套代码,在所有人的编辑器里显示效果一样。所以使用prettier</p><ul><li>格式化之后的代码片段</li></ul><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/e97c4b71-12b0-4af5-afb4-e1f09e80a224" alt="prettier_after"></p></li><li><p>每次格式化时,都需要敲``npx prettier –write<code>这条命令,略显繁琐。我们想要更只能的方式。这里有两种方案,一种是使用</code>prettier<code>支持的编辑器插件,如</code>vscode<code>的</code>Prettier - Code formatter<code>,只需要在</code>vscode<code>中安装,然后就可以使用快捷键</code>Ctrl + Shift + P`来对文件进行格式化。但是我们觉得这种方式还是不够智能,所以重点介绍下面的方法,使用自动化工具帮助我们进行格式化。</p></li></ol><h4 id="配置自动化工具"><a href="#配置自动化工具" class="headerlink" title="配置自动化工具"></a>配置自动化工具</h4><h5 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h5><ol><li><p><code>husky</code> 是一个为 git 客户端增加 hook 的工具。安装后,它会自动在仓库中的 <code>.git/</code> 目录下增加相应的钩子;比如 <code>pre-commit</code> 钩子就会在你执行 <code>git commit</code> 的触发。那么我们可以在 <code>pre-commit</code> 中实现一些比如 lint 检查、单元测试、代码格式化等操作。</p></li><li><p><code>lint-staged</code>,一个仅仅过滤出 Git 代码暂存区文件(被 git add 的文件)的工具;我们如果对整个项目的代码做一个检查,可能耗时很长,如果是老项目,要对之前的代码做一个代码规范检查并修改的话,可能导致项目改动很大。</p><p><code>lint-staged</code>,对团队项目和开源项目来说,是一个很好的工具,它是对个人要提交的代码的一个规范和约束。</p></li></ol><h5 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h5><ol><li><p><code>npx mrm lint-staged</code></p><p><a href="https://github.com/sapegin/mrm">mrm</a> 是一个自动化工具,它将根据 <code>package.json</code> 依赖项中的代码质量工具来安装和配置 husky 和 lint-staged,因此请确保在此之前安装并配置所有代码质量工具,如 Prettier 和 ESlint</p><p><strong>注: 有可能会安装失败,因为<code>mrm</code>需要单独安装,请使用<code>npm install mrm -D</code></strong></p></li><li><p>配置<code>package.json</code>文件</p><p><code>create-react-app</code>创建的项目,默认为我们提供了<code>eslint</code>进行代码检查,为了避免和<code>prettier</code>冲突。我们用<code>prettier</code>覆盖一部分的<code>eslint</code>配置。如下</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><code class="hljs json"><span class="hljs-attr">"eslintConfig"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"extends"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-string">"react-app"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"react-app/jest"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"prettier"</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p> 在<code>package.json</code>添加下面的配置</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs json">"husky": {<br> "hooks": {<br> "pre-commit": "lint-staged"<br> }<br>},<br>"lint-staged": {<br> "*.{js,css,md,ts,tsx}": "prettier --write"<br>}<br></code></pre></td></tr></table></figure><p> 添加完毕,当我们使用<code>git commit</code>的时候,就会去调用<code>husky</code>中定义的<code>hooks</code>钩子,这个钩子就会去找<code>lint-staged</code>,<code>lint-staged</code>中使用了最开始手动格式化得到命令<code>prettier --write</code>。到这里,这一套自动化的工具,就会帮助我们自动格式化我们<code>commit</code>的代码。</p></li></ol><h2 id="commitlint"><a href="#commitlint" class="headerlink" title="commitlint"></a><code>commitlint</code></h2><p><em>这个工具在我们在每一次commit时,检查我们的commit messages是否符合规范。它为我们提供了一套规则,我们必须按照它提供的规范来进行commit messages的填写。这样做的好处时,我们以后的commit messages非常的适合阅读。通过阅读,能够清楚的知道我们本次commit进行了什么的修改或者操作。</em></p><h3 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h3><ol><li><p>install</p><p><code>npm install -g @commitlint/cli @commitlint/config-conventional</code></p></li><li><p>Configure</p><p><code>echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js</code></p></li><li><p>配置规则,当我们使用commit的时候,会帮我们去检验git commit 的messages时候符合规范。</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">"husky"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"hooks"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"pre-commit"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"lint-staged"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"commit-msg"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"commitlint -E HUSKY_GIT_PARAMS"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure></li></ol><h4 id="git-commit-格式"><a href="#git-commit-格式" class="headerlink" title="git commit 格式"></a>git commit 格式</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs Git">git commit -m <type>[optional scope]: <description><br></code></pre></td></tr></table></figure><h5 id="常用的type类别"><a href="#常用的type类别" class="headerlink" title="常用的type类别"></a>常用的type类别</h5><p>type :用于表明我们这次提交的改动类型</p><ul><li><p>build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交</p></li><li><p>ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交</p></li><li><p>docs:文档更新</p></li><li><p>feat:新增功能</p></li><li><p>fix:bug 修复</p></li><li><p>perf:性能优化</p></li><li><p>refactor:重构代码(既没有新增功能,也没有修复 bug)</p></li><li><p>style:不影响程序逻辑的代码修改(修改空白字符,补全缺失的分号等)</p></li><li><p>test:新增测试用例或是更新现有测试</p></li><li><p>revert:回滚某个更早之前的提交</p></li><li><p>chore:不属于以上类型的其他类型(日常事务)</p></li></ul><p>optional scope:一个可选的修改范围。用于标识此次提交主要涉及到代码中哪个模块。</p><p>description:描述此次提交的内容信息</p><p>例子</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git commit -m "test: test commitlint"<br></code></pre></td></tr></table></figure><p>以上就是配置<code>prettier</code>进行自动化代码格式化和自动化检测,规范git commit 的commitlint的配置使用。还有更多高级功能等待发现。</p>]]></content>
<categories>
<category>工程化</category>
</categories>
<tags>
<tag>工程化</tag>
</tags>
</entry>
<entry>
<title>时隔两年的回家</title>
<link href="/2021/03/27/%E6%97%B6%E9%9A%94%E4%B8%A4%E5%B9%B4%E7%9A%84%E5%9B%9E%E5%AE%B6/"/>
<url>/2021/03/27/%E6%97%B6%E9%9A%94%E4%B8%A4%E5%B9%B4%E7%9A%84%E5%9B%9E%E5%AE%B6/</url>
<content type="html"><![CDATA[<h2 id="时隔两年的回家"><a href="#时隔两年的回家" class="headerlink" title="时隔两年的回家"></a>时隔两年的回家</h2><p> 因为疫情和自己的原因,两年春节没有回家!<br> 三月28日,从小一起长大的表妹要结婚了。表妹老早就通知我,作为家里唯一的哥哥,送妹妹走入婚姻的殿堂,突然觉得是一个哥哥不可推卸的责任。所以,提前一个月订好了机票,参加妹妹婚礼顺便看看两年没见面的爸爸妈妈。<br> 回家的前一天晚上,突然有点抗拒回家。或许是大多数出门在外的孩子都有的一个心结,从农村走出来,到了首都,魔都。想要靠着自己的努力打拼一下,混的人模狗样,让父母再为了我们担心。<br> 现实总是骨感的,呆在大城市的我们,过上了三点一线的生活。或许因为自己的选择错误,能力不足进入了一个发展不是很好的公司,又或许是因为幼稚可笑,被社会狠狠的教育了一番,刚毕业就负债累累。没错就是我!!!<br> 2021的跨年那一天,莫名的具有了一种力量。想要改变这种现状,去争取更好的工作机会。在这个过程中,心态起起落落。总觉得自己很难,甚至会陷入自我感动。但是,谁活着是容易的呢?我有什么资格觉得辛苦。<br> 回到家,没有第一时间见到父母,最早见到妈妈,是在一个昏暗的小房子里面,一个人在忙碌着做饭,接近五十岁的妈妈,在一个新建的电厂附近开了一个小店,卖饭给电厂的员工吃。我不敢表现的太过伤心,我想妈妈就是为了多赚点钱。毕竟有我们两个儿子,心酸留给自己吧。我想我没有资格说辛苦。爸爸妈妈不比我辛苦?先写这么多吧。实在写不下去了。</p><p> 又抽空来写了,现在是4月1日,于昨晚抵达上海。总感觉有什么东西留在了家里。说不清道不明,或许是对爸爸妈妈的担心,真的很希望他们健健康康,真的不希望他们去挣多少多少的钱,他们身体健康,是我最开心的事情。</p><p> 不知道说些什么了。我只知道我需要非常努力,只有我努力,我成功,父母才会开心,安心。我也是!</p>]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>随笔</tag>
</tags>
</entry>
<entry>
<title>使用Ajax结合FormData对象实现文件上传</title>
<link href="/2021/03/16/%E5%88%A9%E7%94%A8FormData%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"/>
<url>/2021/03/16/%E5%88%A9%E7%94%A8FormData%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/</url>
<content type="html"><![CDATA[<h3 id="使用Ajax结合FormData实现文件上传"><a href="#使用Ajax结合FormData实现文件上传" class="headerlink" title="使用Ajax结合FormData实现文件上传"></a>使用<code>Ajax</code>结合<code>FormData</code>实现文件上传</h3><p><em>使用<code>FormData</code>之前,我们需要知道它的作用,或者说是本质吧。<code>FormData</code>本质上是<code>HTML</code>提供的一个对象,可以模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。用一个对象来代替正常的<code>FORM</code>表单提交,但是又比表单更强大。</em></p><h4 id="1-FormData使用方法"><a href="#1-FormData使用方法" class="headerlink" title="1 FormData使用方法"></a>1 <code>FormData</code>使用方法</h4><h5 id="1-1-创建一个表单对象"><a href="#1-1-创建一个表单对象" class="headerlink" title="1.1 创建一个表单对象"></a>1.1 创建一个表单对象</h5><p>因为<code>FormData</code>对象不能脱离<code>form</code>元素使用,所以我们必须先有一个表单。</p><p><strong>注意:创建表单,表单中的元素,如input标签等,必须有name属性,因为<code>FormData</code>对象要根据<code>name</code>属性来获得对应的值,或者是操作对应的值。下文会有详细介绍</strong></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><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"form"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"form"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"username"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span>></span><br><span class="hljs-tag"></<span class="hljs-name">form</span>></span><br></code></pre></td></tr></table></figure><h5 id="1-2-创建一个FormData实例,将表单转化为FormData对象"><a href="#1-2-创建一个FormData实例,将表单转化为FormData对象" class="headerlink" title="1.2 创建一个FormData实例,将表单转化为FormData对象"></a>1.2 创建一个<code>FormData</code>实例,将表单转化为<code>FormData</code>对象</h5><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs ts"><span class="hljs-comment">// 使用FormData构造函数创建一个实例,注意该构造函数接受一个参数,参数必须是一个表单对象。如下:</span><br><span class="hljs-comment">// 获得表单</span><br><span class="hljs-number">1.</span> <span class="hljs-keyword">let</span> form = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'form'</span>);<br><span class="hljs-comment">// 将表单转化为Form对象,此时,FormData其实就是包含了表单form内容的一个对象。具体的形式为{key, value}的形式,key就是form表单的name属性值,因此,form中的控件必须有name属性。</span><br><span class="hljs-number">2.</span> <span class="hljs-keyword">let</span> formData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>(form);<br></code></pre></td></tr></table></figure><p><strong>注意: 完成了上面步骤后,如果此时你在开发者工具尝试输出<code>FormData</code>对象,输出会是一个空对象,因为<code>FormData</code>是一种特殊格式,无法输出。</strong></p><h5 id="1-3-使用Ajax发送FormData"><a href="#1-3-使用Ajax发送FormData" class="headerlink" title="1.3 使用Ajax发送FormData"></a>1.3 使用<code>Ajax</code>发送<code>FormData</code></h5><p><strong>创建的<code>FormData</code>对象,使用<code>Ajax</code>中的<code>send()</code>进行发送,<code>FormData</code>对象只能使用<code>send()</code>进行发送,不能使用<code>get</code>请求的<code>url</code>拼接参数的方式。</strong><br>如下,客户端使用Ajax进行了一次post请求。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs ts"><span class="hljs-keyword">let</span> xhr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>();<br><br>xhr.<span class="hljs-title function_">open</span>(<span class="hljs-string">'POST'</span>, <span class="hljs-string">'http://localhost:3000/FormData'</span>);<br><br><span class="hljs-comment">// TODO 对于FormData对象,不能设置常规的Content-Type</span><br><span class="hljs-comment">// xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');</span><br><br>xhr.<span class="hljs-title function_">send</span>(formData);<br><br>xhr.<span class="hljs-property">onload</span> = <span class="hljs-function">() =></span> {<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(xhr.<span class="hljs-property">responseText</span>);<br>}<br></code></pre></td></tr></table></figure><p>Node服务端进行响应(使用express框架,formidable第三方包)</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs ts"><span class="hljs-comment">// 路由</span><br>app.<span class="hljs-title function_">post</span>(<span class="hljs-string">'/FormData'</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =></span> {<br><span class="hljs-comment">// form 使用formidable的一个实例,用来接受处理FormData的请求,fields参数是FormData的内容。</span><br> form.<span class="hljs-title function_">parse</span>(req, <span class="hljs-function">(<span class="hljs-params">err, fields, files</span>) =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(fields) <span class="hljs-comment">//{"username":"123","password":"123"}</span><br> <span class="hljs-comment">// 返回结果给客户端</span><br> res.<span class="hljs-title function_">send</span>(fields);<br> <span class="hljs-title function_">next</span>();<br> })<br>})<br></code></pre></td></tr></table></figure><p><em>如上,<code>FormData</code>对象的基础对象就结束了,<code>FormData</code>对象上有很多方法,请自行查阅文档学习,我们本次文件上传只用到了append()方法,语法为append(name, value)。另一些说明在代码中进行了详细注释</em></p><p><strong>完整的实现代码</strong></p><p><strong>html</strong></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></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-comment"><!-- 容器 --></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"overflow: hidden;"</span>></span><br> <span class="hljs-comment"><!-- 进度条 --></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"margin: 20px auto;"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"progress"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress-bar progress-bar-striped progress-bar-animated"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"progressbar"</span> <span class="hljs-attr">aria-valuenow</span>=<span class="hljs-string">"75"</span> <span class="hljs-attr">aria-valuemin</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">aria-valuemax</span>=<span class="hljs-string">"100"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"width: 0"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-comment"><!-- 空的form表单 --></span><br> <span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"form"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"form"</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">form</span>></span><br> <span class="hljs-comment"><!-- 文件上传控件 --></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"upload-file"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file"</span>></span><br> <span class="hljs-comment"><!-- 进行文件上传成功后的图片动态显示显示 --></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"avatar"</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">div</span>></span><br></code></pre></td></tr></table></figure><p><strong>JavaScript</strong></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></pre></td><td class="code"><pre><code class="hljs javascript"><script><br> <span class="hljs-comment">// 获得form表单</span><br> <span class="hljs-keyword">let</span> form = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'form'</span>);<br> <span class="hljs-keyword">let</span> file = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'upload-file'</span>);<br> <span class="hljs-comment">// 存放动态渲染图片的区域</span><br> <span class="hljs-keyword">let</span> avatar = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'.avatar'</span>);<br> <span class="hljs-comment">// 控制进度显示</span><br> <span class="hljs-keyword">let</span> progress = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'progress'</span>);<br> <span class="hljs-comment">// 创建FormData实例</span><br> <span class="hljs-keyword">let</span> formData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>();<br> <span class="hljs-comment">// TODO 事件处理函数内部需要使用this时,不要使用箭头函数 导致this丢失</span><br> <span class="hljs-keyword">function</span> <span class="hljs-title function_">handleUploadFile</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">let</span> xhr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>();<br> <span class="hljs-comment">// TODO 文件上传this.files是一个数组 存储了选择的文件</span><br> <span class="hljs-comment">// 传递的应该是对应的文件如第一个文件就应该是此处的this.files[0]</span><br> <span class="hljs-comment">// 将选择的文件添加到FormData对象中</span><br> formData.<span class="hljs-title function_">append</span>(<span class="hljs-string">'file'</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">files</span>[<span class="hljs-number">0</span>]);<br><span class="hljs-comment">// 发送Ajax请求</span><br> xhr.<span class="hljs-title function_">open</span>(<span class="hljs-string">'POST'</span>, <span class="hljs-string">'http://localhost:3000/upload'</span>);<br> <span class="hljs-comment">// 将XMLHttpRequest.upload.onprogress 放到send()方法后是不生效的</span><br> xhr.<span class="hljs-property">upload</span>.<span class="hljs-property">onprogress</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">ev</span>) {<br> <span class="hljs-comment">// loaded 已加载内容 total 总共的内容</span><br> <span class="hljs-keyword">let</span> per = (ev.<span class="hljs-property">loaded</span> / ev.<span class="hljs-property">total</span>) * <span class="hljs-number">100</span> + <span class="hljs-string">'%'</span>;<br> progress.<span class="hljs-property">style</span>.<span class="hljs-property">width</span> = per;<br> }<br> <span class="hljs-comment">// 发送FormData表单 </span><br> xhr.<span class="hljs-title function_">send</span>(formData);<br> <span class="hljs-comment">// 提前知道服务端返回的是图片的地址,所以此处声明一个变量,接受返回地址</span><br> <span class="hljs-keyword">let</span> imgPath = <span class="hljs-string">''</span>;<br><span class="hljs-comment">// 服务端返回</span><br> xhr.<span class="hljs-property">onload</span> = <span class="hljs-function">() =></span> {<br> <span class="hljs-comment">// 服务端返回的数据格式为json字符串 需要进行反序列化</span><br> <span class="hljs-keyword">let</span> responseData = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(xhr.<span class="hljs-property">responseText</span>);<br> <span class="hljs-comment">// 利用服务端返回的信息 利用\upload\进行字符串切割 取得文件路径</span><br> imgPath = responseData.<span class="hljs-property">path</span>.<span class="hljs-title function_">split</span>(<span class="hljs-string">'\\public\\'</span>)[<span class="hljs-number">1</span>];<br><span class="hljs-comment">// 请求返回成功</span><br> <span class="hljs-keyword">if</span> (xhr.<span class="hljs-property">status</span> === <span class="hljs-number">200</span>) {<br> <span class="hljs-comment">// 动态创建一个img标签</span><br> <span class="hljs-keyword">let</span> img = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'img'</span>);<br> <span class="hljs-comment">// 图片的地址给img添加</span><br> img.<span class="hljs-property">src</span> = imgPath;<br> <span class="hljs-comment">// 图片加载完成</span><br> img.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-comment">// 将img标签添加到DOM中</span><br> avatar.<span class="hljs-title function_">appendChild</span>(img);<br> }<br> }<br> }<br> }<br> file.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'change'</span>, handleUploadFile);<br> </script><br></code></pre></td></tr></table></figure><p><strong>服务端处理</strong></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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);<br><span class="hljs-keyword">const</span> port = <span class="hljs-number">3000</span>;<br><span class="hljs-keyword">const</span> public = express.<span class="hljs-title function_">static</span>(<span class="hljs-string">'public'</span>);<br><span class="hljs-keyword">const</span> formidable = <span class="hljs-built_in">require</span>(<span class="hljs-string">'formidable'</span>);<br><span class="hljs-keyword">const</span> form = <span class="hljs-keyword">new</span> formidable.<span class="hljs-title class_">IncomingForm</span>();<br><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);<br><span class="hljs-comment">// 设置文件上传的路径</span><br>form.<span class="hljs-property">uploadDir</span> = path.<span class="hljs-title function_">join</span>(__dirname + <span class="hljs-string">'/public'</span> + <span class="hljs-string">'/upload'</span>);<br><span class="hljs-comment">// 默认文件上传后会去除后缀名。开启保留文件后缀名</span><br>form.<span class="hljs-property">keepExtensions</span> = <span class="hljs-literal">true</span>;<br><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>();<br>app.<span class="hljs-title function_">use</span>(<span class="hljs-string">'/'</span>, public);<br><br><span class="hljs-comment">// 文件上传 主要看这里就可以了</span><br>app.<span class="hljs-title function_">post</span>(<span class="hljs-string">'/upload'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =></span> {<br> form.<span class="hljs-title function_">parse</span>(req, <span class="hljs-function">(<span class="hljs-params">err, fields, files</span>) =></span> {<br> <span class="hljs-comment">//files.file是一个对象存放了接收到的文件的相关信息</span><br> <span class="hljs-comment">// 文件在服务器端的地址 </span><br> <span class="hljs-keyword">let</span> path = files.<span class="hljs-property">file</span>.<span class="hljs-property">path</span>;<br> <span class="hljs-comment">// 默认对象会以json的格式返回 </span><br> res.<span class="hljs-title function_">send</span>({ <span class="hljs-attr">path</span>: path });<br> })<br>})<br><br>app.<span class="hljs-title function_">listen</span>(port, <span class="hljs-function">() =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'express start access port is 3000'</span>)<br>})<br></code></pre></td></tr></table></figure><p><strong>以上就是利用<code>Ajax</code>结合<code>FormData</code>对象实现的简易版的文件上传功能。实践出真知。虽然看着很简单,但是在完成的过程中,遇到了各种各样的小问题。慢慢的Google,慢慢的看文档,思考。收获的还是蛮多的。一起动手试试吧</strong></p>]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
</tags>
</entry>
<entry>
<title>keep-alive实现页面缓存</title>
<link href="/2021/02/09/keep-alive%E5%AE%9E%E7%8E%B0%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98/"/>
<url>/2021/02/09/keep-alive%E5%AE%9E%E7%8E%B0%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98/</url>
<content type="html"><![CDATA[<h4 id="1-业务场景"><a href="#1-业务场景" class="headerlink" title="1. 业务场景"></a>1. 业务场景</h4><p>现在有A,B,两个页面,在B页面做一些操作,比如说通过输入搜索条件搜出相关的数据,然后点击一条数据,跳转到详情页A,在A页面点击返回按钮,B页面还保持离开前的状态,但是从B页面到A页面,A页面一直是最新的页面。</p><h4 id="2-具体做法"><a href="#2-具体做法" class="headerlink" title="2. 具体做法"></a>2. 具体做法</h4><h5 id="keep-alive使用"><a href="#keep-alive使用" class="headerlink" title="keep-alive使用"></a>keep-alive使用</h5><p><strong>注:</strong>对于这个例子来说,我会将A页面和B页面都缓存。实际开发中,选择自己的要缓存的组件就可以,我只是为了演示这个例子。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs vue"><template><br> <div id="app"><br> <nav><br> <router-link to="/">to A page</router-link><br> <router-link to="/about">to B page</router-link><br> </nav><br> <!-- 需要缓存的视图 --><br> <keep-alive><br> <router-view /><br> </keep-alive><br> </div><br></template><br></code></pre></td></tr></table></figure><h5 id="keep-alive的生命周期"><a href="#keep-alive的生命周期" class="headerlink" title="keep-alive的生命周期"></a><code>keep-alive</code>的生命周期</h5><ul><li>初次进入时:<ol><li><code>created</code> > <code>mounted</code> > <code>activated</code></li><li>退出后触发 <code>deactivated</code></li></ol></li><li>再次进入:<ol><li>只会触发 <code>activated</code></li></ol></li><li>再次离开:<ol><li>退出后触发 <code>deactivated</code></li></ol></li></ul><p>了解了keep-alive的钩子函数后,结合业务,我们要在离开B页面时保存当前的位置。不用想,这个时候必须要监听<code>scroll</code>事件,什么时候监听呢?因为每次进入B页面,都可能会滚动页面,所以在<code>activated</code>钩子函数中做这件事无疑是最合适的。如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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></pre></td><td class="code"><pre><code class="hljs vue"><script><br>export default {<br> name: 'AboutView',<br> components: {},<br> data() {<br> return {<br> scrollTop: '',<br> }<br> },<br> // 进入当前组件时监听scroll事件,并且将页面位置记录到data对象scrollTop中。<br> // 根据保存的scrollTop值回到上一次离开页面的位置<br> activated() {<br> console.log('About activated ...')<br> document.documentElement.scrollTop = this.scrollTop<br> window.addEventListener('scroll', this.handlerScroll)<br> },<br> // 离开页面,移除scroll事件监听<br> deactivated() {<br> console.log('About deactivated ...')<br> window.removeEventListener('scroll', this.handlerScroll)<br> },<br> methods: {<br> handlerScroll() {<br> this.scrollTop = document.documentElement.scrollTop<br> },<br> },<br>}<br></script><br></code></pre></td></tr></table></figure><p>如上所示,当离开B页面时,记录了当前的位置,当再次进入时,根据之前保存的数据。回到离开前的位置。</p><h4 id="3-有哪些可以优化的点?"><a href="#3-有哪些可以优化的点?" class="headerlink" title="3. 有哪些可以优化的点?"></a>3. 有哪些可以优化的点?</h4><p>可以注意到上面我们注册了<code>scroll</code>滚动事件,这种事件触发的频率比较高,一般会使用防抖或者节流来进行限制。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs vue"><script><br>const _ = require('lodash')<br>export default {<br> name: 'AboutView',<br> components: {},<br> data() {<br> return {<br> scrollTop: '',<br> removeFlag: null,<br> }<br> },<br> // 进入当前组件时监听scroll事件,并且将页面位置记录到data对象scrollTop中。<br> // 根据保存的scrollTop值回到上一次离开页面的位置<br> activated() {<br> console.log('About activated ...')<br> document.documentElement.scrollTop = this.scrollTop<br> window.addEventListener('scroll', this.handlerDebounceScroll())<br> },<br> // 离开页面,移除scroll事件监听<br> deactivated() {<br> console.log('About deactivated ...')<br> window.removeEventListener('scroll', this.handlerDebounceScroll())<br> },<br> methods: {<br> handlerScroll() {<br> this.scrollTop = document.documentElement.scrollTop<br> },<br> handlerDebounceScroll(delay = 500) {<br> // 移除事件的标识<br> if (!this.removeFlag) {<br> this.removeFlag = _.debounce(this.handlerScroll, delay)<br> }<br> return this.removeFlag<br> },<br> },<br>}<br></script><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Vue</category>
</categories>
<tags>
<tag>Vue</tag>
</tags>
</entry>
<entry>
<title>学习Vue.js 响应式原理</title>
<link href="/2021/01/10/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86%E5%AE%9E%E7%8E%B0/"/>
<url>/2021/01/10/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<h2 id="学习Vue-js-响应式原理"><a href="#学习Vue-js-响应式原理" class="headerlink" title="学习Vue.js 响应式原理"></a>学习Vue.js 响应式原理</h2><p><em>最近在学习Vue的源码,对于核心原理比较感兴趣。本文就Vue.js的响应式原理进行模拟实现。代码实现不难,花二十分钟看一下,<code>download</code>到本地<code>run</code>一下。你也会有很大收获。加油!!!</em></p><h3 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h3><ul><li>数据驱动</li><li>响应式的核心原理</li><li>发布订阅模式和观察者模式</li></ul><h4 id="数据驱动"><a href="#数据驱动" class="headerlink" title="数据驱动"></a>数据驱动</h4><ul><li>数据响应式 <ul><li>数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁<br> 琐的 DOM 操作,提高开发效率</li></ul></li><li>双向绑定 <ul><li>数据改变,视图改变;视图改变,数据也随之改变 </li><li>我们可以使用 v-model 在表单元素上创建双向数据绑定</li></ul></li><li>数据驱动<ul><li>开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图</li></ul></li></ul><h4 id="数据响应式的原理"><a href="#数据响应式的原理" class="headerlink" title="数据响应式的原理"></a>数据响应式的原理</h4><p>因为Vue3相较于Vue2原理发生了变化,本文讨论Vue2。Vue3将在下一篇中分析</p><p><strong>Vue2.X</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 模拟Vue实例</span><br><span class="hljs-keyword">const</span> vm = {} <br><span class="hljs-comment">// 模拟Vue中的data选项</span><br><span class="hljs-keyword">const</span> data = {<br> <span class="hljs-attr">message</span>: <span class="hljs-string">'Hello World'</span>,<br> <span class="hljs-attr">count</span>: <span class="hljs-number">1</span><br>}<br><span class="hljs-keyword">const</span> <span class="hljs-title function_">ProxyData</span> = (<span class="hljs-params"></span>) => {<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">keys</span>(data).<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">key</span>) =></span> {<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperty</span>(vm, key, {<br> <span class="hljs-attr">enumerable</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">configurable</span>: <span class="hljs-literal">true</span>,<br> get () {<br> <span class="hljs-keyword">return</span> data[key]<br> },<br><br> set (newValue) {<br> <span class="hljs-keyword">if</span> (newValue === data[key]) {<br> <span class="hljs-keyword">return</span> newValue<br> }<br> data[key] = newValue<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'#app'</span>).<span class="hljs-property">textContent</span> = newValue<br> }<br> })<br> })<br>}<br></code></pre></td></tr></table></figure><h4 id="发布订阅模式和观察者模式"><a href="#发布订阅模式和观察者模式" class="headerlink" title="发布订阅模式和观察者模式"></a>发布订阅模式和观察者模式</h4><h5 id="发布-订阅模式"><a href="#发布-订阅模式" class="headerlink" title="发布/订阅模式"></a>发布/订阅模式</h5><ul><li>订阅者</li><li>发布者</li><li>信号中心</li></ul><blockquote><p>假设存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”(publish)一个信<br>号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执<br>行。这就叫做”发布/订阅模式”(publish-subscribe pattern) </p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 事件中心</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">EventEimt</span> {<br> <span class="hljs-title function_">constructor</span> () {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span> = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">create</span>(<span class="hljs-literal">null</span>)<br> }<br><br> <span class="hljs-comment">// 注册事件 发布事件</span><br> $on (eventType, handler) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>[eventType] = <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>[eventType] ? <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>[eventType] : []<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>[eventType].<span class="hljs-title function_">push</span>(handler)<br> }<br><br> <span class="hljs-comment">// 触发事件 订阅事件</span><br> $emit (eventType) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>[eventType]?.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">handler</span> =></span> <span class="hljs-title function_">handler</span>())<br> }<br>}<br><br><span class="hljs-comment">// 测试用例</span><br><span class="hljs-keyword">const</span> ev = <span class="hljs-keyword">new</span> <span class="hljs-title class_">EventEimt</span>()<br><br>ev.$on(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'click1'</span>)<br>})<br><br>ev.$on(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'click2'</span>)<br>})<br><br>ev.$emit(<span class="hljs-string">'c'</span>)<br></code></pre></td></tr></table></figure><h5 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h5><ul><li><p>观察者(订阅者) – Watcher</p><ul><li>update():当事件发生时,具体要做的事情</li></ul></li><li><p>目标(发布者) – Dep</p><ul><li>subs 数组:存储所有的观察者</li><li>addSub():添加观察者</li><li>notify():当事件发生,调用所有观察者的 update() 方法</li><li>没有事件中心</li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 发布者</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Dep</span> {<br> <span class="hljs-title function_">constructor</span> () {<br> <span class="hljs-comment">// 记录所有订阅者</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span> = []<br> }<br><br> <span class="hljs-comment">// 添加订阅者</span><br> addSub (sub) {<br> <span class="hljs-keyword">if</span> (sub && sub.<span class="hljs-property">update</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>.<span class="hljs-title function_">push</span>(sub)<br> }<br> }<br><br> <span class="hljs-comment">// 通知订阅者</span><br> notify () {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">sub</span> =></span> sub.<span class="hljs-title function_">update</span>())<br> }<br>}<br><br><span class="hljs-comment">// 订阅者</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Watch</span> {<br> update () {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'update'</span>)<br> }<br>}<br><br><span class="hljs-comment">// 测试用例</span><br><span class="hljs-keyword">const</span> pub = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Dep</span>()<br><span class="hljs-keyword">const</span> w1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Watch</span>()<br>pub.<span class="hljs-title function_">addSub</span>(w1)<br>pub.<span class="hljs-title function_">addSub</span>(w1)<br>pub.<span class="hljs-title function_">notify</span>()<br></code></pre></td></tr></table></figure><h3 id="Vue-响应式原理"><a href="#Vue-响应式原理" class="headerlink" title="Vue 响应式原理"></a>Vue 响应式原理</h3><h4 id="整体架构图"><a href="#整体架构图" class="headerlink" title="整体架构图"></a>整体架构图</h4><p><img src="https://github.com/askwuxue/FrontEnd/blob/master/note-images/vue-2.png" alt="Vue 响应式原理"></p><p>上图中各部分的功能以及实现如下:</p><h4 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h4><ul><li>负责接收初始化的参数(选项)</li><li>负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter</li><li>负责调用 observer 监听 data 中所有属性的变化</li><li>负责调用 compiler 解析指令/插值表达式</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vue</span> {<br> <span class="hljs-title function_">constructor</span> (options = {}) {<br> <span class="hljs-comment">// 1. 保存创建Vue实例时传递的options</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">$options</span> = options<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">$data</span> = options.<span class="hljs-property">data</span> || {}<br> <span class="hljs-comment">// options.el是选择器或者DOM对象</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">$el</span> = <span class="hljs-keyword">typeof</span> options.<span class="hljs-property">el</span> === <span class="hljs-string">'string'</span> ? <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(options.<span class="hljs-property">el</span>) : options.<span class="hljs-property">el</span><br> <span class="hljs-comment">// 2. 把Data中的成员转换成get和set</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">_ProxyData</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">$data</span>)<br> <span class="hljs-comment">// 3. observer对象,监听data数据的变化</span><br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Observer</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">$data</span>)<br> <span class="hljs-comment">// 4. 调用compile函数,解析指令和差值表达式</span><br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Compiler</span>(<span class="hljs-variable language_">this</span>)<br> }<br><br> <span class="hljs-comment">// 注册数据的getter和setter方法</span><br> _ProxyData (data) {<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">keys</span>(data).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">key</span> =></span> {<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperty</span>(<span class="hljs-variable language_">this</span>, key, {<br> <span class="hljs-attr">configurable</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">enumerable</span>: <span class="hljs-literal">true</span>,<br> get () {<br> <span class="hljs-keyword">return</span> data[key]<br> },<br> set (newValue) {<br> <span class="hljs-keyword">if</span> (newValue === data[key]) {<br> <span class="hljs-keyword">return</span><br> }<br> data[key] = newValue<br> }<br> })<br> })<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="Observer"><a href="#Observer" class="headerlink" title="Observer"></a>Observer</h4><ul><li>负责把 data 选项中的属性转换成响应式数据</li><li>如果data 中的某个属性是对象,把该属性转换成响应式数据</li><li>数据变化发送通知</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Observer</span> {<br> <span class="hljs-title function_">constructor</span> (data) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">walk</span>(data)<br> }<br><br> <span class="hljs-comment">// 遍历data属性</span><br> walk (data) {<br> <span class="hljs-comment">// data不存在或者data不是对象</span><br> <span class="hljs-keyword">if</span> (!data || <span class="hljs-keyword">typeof</span> data !== <span class="hljs-string">'object'</span>) {<br> <span class="hljs-keyword">return</span><br> }<br> <span class="hljs-comment">// 遍历data对象</span><br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">keys</span>(data).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">key</span> =></span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">defineReactive</span>(data, key, data[key]))<br> }<br><br> <span class="hljs-comment">// 为data对象上的所有属性注册get和set</span><br> defineReactive (obj, key, val) {<br> <span class="hljs-keyword">const</span> that = <span class="hljs-variable language_">this</span><br> <span class="hljs-comment">// 负责收集依赖并发布通知</span><br> <span class="hljs-keyword">let</span> dep = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Dep</span>()<br> <span class="hljs-comment">// TODO data中的属性值可能是对象类型,调用walk方法,如果是对象类型,可以递归的将其设置为响应式数据</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">walk</span>(val)<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperty</span>(obj, key, {<br> <span class="hljs-attr">configurable</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">enumerable</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-comment">// 监听对$data数据的访问</span><br> get () {<br> <span class="hljs-comment">// 收集依赖</span><br> <span class="hljs-title class_">Dep</span>.<span class="hljs-property">target</span> && dep.<span class="hljs-title function_">addSub</span>(<span class="hljs-title class_">Dep</span>.<span class="hljs-property">target</span>)<br> <span class="hljs-comment">// TODO 此处只能使用val值而不能使用obj[key] 因为只要访问data的数据就会调用observer对象的get方法。会导致堆栈溢出</span><br> <span class="hljs-keyword">return</span> val<br> },<br> <span class="hljs-comment">// 监听对$data对象的改变</span><br> set (newValue) {<br> <span class="hljs-keyword">if</span> (newValue === val) {<br> <span class="hljs-keyword">return</span><br> }<br> val = newValue<br> <span class="hljs-comment">// TODO 如果为data中的数据进行了重新赋值为对象,那么需要对该对象遍历,注册get和set</span><br> <span class="hljs-comment">// 此处的this指向了data对象,不是observer对象</span><br> that.<span class="hljs-title function_">walk</span>(newValue)<br> <span class="hljs-comment">// 发送通知</span><br> dep.<span class="hljs-title function_">notify</span>()<br> }<br> })<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="Compiler"><a href="#Compiler" class="headerlink" title="Compiler"></a>Compiler</h4><ul><li>负责编译模板,解析指令/插值表达式</li><li>负责页面的首次渲染</li><li>当数据变化后重新渲染视图</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><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><code class="hljs js"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Compiler</span> {<br> <span class="hljs-comment">// 接收vue实例</span><br> <span class="hljs-title function_">constructor</span> (vm) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">el</span> = vm.<span class="hljs-property">$el</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">vm</span> = vm<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">compile</span>(vm.<span class="hljs-property">$el</span>)<br> }<br><br> <span class="hljs-comment">// 编译模板 处理文本节点和元素节点</span><br> compile (el) {<br> <span class="hljs-comment">// 遍历el节点的子节点</span><br> <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(el.<span class="hljs-property">childNodes</span>).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">node</span> =></span> {<br> <span class="hljs-comment">// 子节点为元素节点</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-title function_">isElementNode</span>(node)) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">compileElement</span>(node)<br> }<br> <span class="hljs-comment">// 子节点为文本节点</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-title function_">isTextNode</span>(node)) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">compileText</span>(node)<br> }<br> <span class="hljs-comment">// 子节点还存在子节点</span><br> <span class="hljs-keyword">if</span> (node.<span class="hljs-property">childNodes</span> && node.<span class="hljs-property">childNodes</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">compile</span>(node)<br> }<br> })<br> }<br><br> <span class="hljs-comment">// 编译元素节点,处理指令</span><br> compileElement (node) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'node: '</span>, node.<span class="hljs-property">attributes</span>);<br> <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(node.<span class="hljs-property">attributes</span>).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">attr</span> =></span> {<br> <span class="hljs-comment">// 判断是否是指令</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-title function_">isDirective</span>(attr.<span class="hljs-property">name</span>)) {<br> <span class="hljs-comment">// 指令的名</span><br> <span class="hljs-keyword">const</span> attrName = attr.<span class="hljs-property">name</span>.<span class="hljs-title function_">substr</span>(<span class="hljs-number">2</span>)<br> <span class="hljs-comment">// 指令的值,即数data的key值</span><br> <span class="hljs-keyword">const</span> key = attr.<span class="hljs-property">value</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">update</span>(node, key, attrName)<br> }<br> })<br> }<br><br> <span class="hljs-comment">// 统一更新</span><br> update (node, key, attrName) {<br> <span class="hljs-keyword">const</span> updateFn = <span class="hljs-variable language_">this</span>[attrName + <span class="hljs-string">'Updater'</span>]<br> <span class="hljs-comment">// 调用指令处理函数并且传递data数据</span><br> updateFn && <span class="hljs-title function_">updateFn</span>(node, <span class="hljs-variable language_">this</span>.<span class="hljs-property">vm</span>[key])<br> }<br><br> <span class="hljs-comment">// 处理v-text指令</span><br> textUpdater (node, value) {<br> node.<span class="hljs-property">textContent</span> = value<br> }<br><br> <span class="hljs-comment">// 处理v-model指令</span><br> <span class="hljs-comment">// TODO 没有实现数据的双向绑定</span><br> modelUpdater (node, value) {<br> node.<span class="hljs-property">value</span> = value<br> }<br><br> <span class="hljs-comment">// 编译文本节点,处理插值表达式</span><br> compileText (node) {<br> <span class="hljs-keyword">const</span> reg = <span class="hljs-regexp">/\{\{(.*?)\}\}/g</span><br> reg.<span class="hljs-title function_">exec</span>(node.<span class="hljs-property">textContent</span>)<br> node.<span class="hljs-property">textContent</span> = node.<span class="hljs-property">textContent</span>.<span class="hljs-title function_">replace</span>(reg, <span class="hljs-function">(<span class="hljs-params">match, key</span>) =></span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">vm</span>[key.<span class="hljs-title function_">trim</span>()])<br> }<br><br> <span class="hljs-comment">// 判断元素属性是否为指令</span><br> isDirective (attrName) {<br> <span class="hljs-keyword">return</span> attrName.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'v-'</span>)<br> }<br><br> <span class="hljs-comment">// 判断节点是否是文本节点</span><br> isTextNode (node) {<br> <span class="hljs-keyword">return</span> node.<span class="hljs-property">nodeType</span> === <span class="hljs-number">3</span><br> }<br><br> <span class="hljs-comment">// 判断节点是否为元素节点</span><br> isElementNode (node) {<br> <span class="hljs-keyword">return</span> node.<span class="hljs-property">nodeType</span> === <span class="hljs-number">1</span><br> }<br>}<br></code></pre></td></tr></table></figure><ul><li>Dep<ul><li>收集依赖,添加观察者(watcher)</li><li>通知所有观察者</li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Dep</span> {<br> <span class="hljs-title function_">constructor</span> () {<br> <span class="hljs-comment">// 存储所有观察者</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span> = []<br> }<br><br> <span class="hljs-comment">// 添加观察者</span><br> addSub (sub) {<br> <span class="hljs-keyword">if</span> (sub && sub.<span class="hljs-property">update</span>)<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>.<span class="hljs-title function_">push</span>(sub)<br> }<br><br> <span class="hljs-comment">// 发送通知</span><br> notify () {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">subs</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">sub</span> =></span> {<br> sub.<span class="hljs-title function_">update</span>()<br> })<br> }<br>}<br></code></pre></td></tr></table></figure><ul><li>Watcher<ul><li>当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图</li><li>自身实例化的时候往 dep 对象中添加自己</li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Watch</span> {<br> <span class="hljs-title function_">constructor</span> (vm, key, cb) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">vm</span> = vm<br> <span class="hljs-comment">// data属性</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">key</span> = key<br> <span class="hljs-comment">// 更新视图的回调函数</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">cb</span> = cb<br> <span class="hljs-comment">// watch对象记录到Dep的静态方法target上</span><br> <span class="hljs-title class_">Dep</span>.<span class="hljs-property">target</span> = <span class="hljs-variable language_">this</span><br> <span class="hljs-comment">// 触发get方法,触发Dep的addSub方法</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">oldValue</span> = vm[key]<br> <span class="hljs-title class_">Dep</span>.<span class="hljs-property">target</span> = <span class="hljs-literal">null</span><br> }<br> update () {<br> <span class="hljs-keyword">let</span> newValue = <span class="hljs-variable language_">this</span>.<span class="hljs-property">vm</span>[<span class="hljs-variable language_">this</span>.<span class="hljs-property">key</span>]<br> <span class="hljs-keyword">if</span> (newValue === <span class="hljs-variable language_">this</span>.<span class="hljs-property">oldValue</span>) {<br> <span class="hljs-keyword">return</span><br> }<br> <span class="hljs-comment">// 更新视图</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">cb</span>(newValue)<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">html</span>></span><br><span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">head</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">title</span>></span>Document<span class="hljs-tag"></<span class="hljs-name">title</span>></span><br><span class="hljs-tag"></<span class="hljs-name">head</span>></span><br><span class="hljs-tag"><<span class="hljs-name">body</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span>></span>{{count}}<span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span>></span>{{message}}<span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>v-text: <span class="hljs-tag"></<span class="hljs-name">p</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">v-text</span>=<span class="hljs-string">"obj"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-comment"><!-- TODO 有一个bug 差值表达式有空格无法正常渲染,data发生变化时,全部覆盖了 --></span><br> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>v-model: {{input}}<span class="hljs-tag"></<span class="hljs-name">p</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"input"</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./dep.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./watch.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./vue.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./observer.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./compiler.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="language-javascript"></span><br><span class="language-javascript"> <span class="hljs-keyword">const</span> vm = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Vue</span>({</span><br><span class="language-javascript"> <span class="hljs-attr">el</span>: <span class="hljs-string">'#app'</span>,</span><br><span class="language-javascript"> <span class="hljs-attr">data</span>: {</span><br><span class="language-javascript"> <span class="hljs-attr">count</span>: <span class="hljs-number">1</span>,</span><br><span class="language-javascript"> <span class="hljs-attr">message</span>: <span class="hljs-string">'hello'</span>,</span><br><span class="language-javascript"> <span class="hljs-attr">obj</span>: <span class="hljs-string">'<p>hello</p>'</span>,</span><br><span class="language-javascript"> <span class="hljs-attr">input</span>: <span class="hljs-string">'222'</span></span><br><span class="language-javascript"> }</span><br><span class="language-javascript"> })</span><br><span class="language-javascript"> </span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br><span class="hljs-tag"></<span class="hljs-name">body</span>></span><br><span class="hljs-tag"></<span class="hljs-name">html</span>></span><br></code></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>因为Vue.js的功能丰富,一个一个功能去实现也没有必要。本文只是抱着学习的心态,实现了Vue.js的几个核心功能。大概是两百行左右的代码,整体来说核心功能的实现代码不是很难,但是思想值得学习。如果你也对Vue.js的实现原理感兴趣的话,也可以访问我的<a href="https://github.com/askwuxue/note_source/tree/master/vue/vue-core/mini-vue2">github</a>,clone到本次run一下,debugger一下。可以发现我的实现有bug的地方。也可以看一下我的实现方式,代码有超多注释,通俗易懂哈哈哈。</p>]]></content>
<categories>
<category>Vue</category>
</categories>
<tags>
<tag>Vue</tag>
</tags>
</entry>
<entry>
<title>Promise实现原理</title>
<link href="/2020/10/11/Promise%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/"/>
<url>/2020/10/11/Promise%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</url>
<content type="html"><![CDATA[<h1 id="Promise实现原理"><a href="#Promise实现原理" class="headerlink" title="Promise实现原理"></a>Promise实现原理</h1><p>本文旨在实现Promise,Promise用法请求移步至<a href="https://es6.ruanyifeng.com/#docs/promise">阮一峰老师ES6教程</a></p><h3 id="Promise用法"><a href="#Promise用法" class="headerlink" title="Promise用法"></a>Promise用法</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'create a promise'</span>);<br> <span class="hljs-title function_">resolve</span>(<span class="hljs-string">'成功了'</span>);<br>})<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"after new promise"</span>);<br><br><span class="hljs-keyword">const</span> p2 = p1.<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">data</span> =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(data)<br> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'失败了'</span>)<br>})<br><br><span class="hljs-keyword">const</span> p3 = p2.<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">data</span> =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'success'</span>, data)<br>}, <span class="hljs-function"><span class="hljs-params">err</span> =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'faild'</span>, err)<br>})<br></code></pre></td></tr></table></figure><p>输出</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-string">"create a promise"</span><br><span class="hljs-string">"after new promise"</span><br><span class="hljs-string">"成功了"</span><br><span class="hljs-string">"faild Error: 失败了"</span><br></code></pre></td></tr></table></figure><h3 id="Promise基本特征"><a href="#Promise基本特征" class="headerlink" title="Promise基本特征"></a>Promise基本特征</h3><ol><li><p>promise 有三个状态:<code>pending</code>,<code>fulfilled</code>, <code>rejected</code>;</p></li><li><p><code>new promise</code>时, 需要传递一个<code>executor()</code>执行器,执行器立即执行;</p></li><li><p><code>executor</code>接受两个参数,分别是<code>resolve</code>和<code>reject</code>;</p></li><li><p>promise 的默认状态是 <code>pending</code>;</p></li><li><p>promise 有一个<code>value</code>保存成功状态的值,可以是<code>undefined/thenable/promise</code>;</p></li><li><p>promise 有一个<code>reason</code>保存失败状态的原因;</p></li><li><p>promise 只能从<code>pending</code>到<code>rejected</code>, 或者从<code>pending</code>到<code>fulfilled</code>,状态一旦确认,就不会再改变;</p></li><li><p>promise 必须有一个<code>then</code>方法,then 接收两个参数,分别是 promise 成功的回调 successCallBack, 和 promise 失败的回调 failCallBack;</p></li><li><p>如果调用 then 时,promise 已经成功,则执行<code>successCallBack</code>,参数是<code>promise</code>的<code>value</code>;</p></li><li><p>如果调用 then 时,promise 已经失败,那么执行<code>failCallBack</code>, 参数是<code>promise</code>的<code>reason</code>;</p></li><li><p>如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调<code>failCallBack</code>;</p></li></ol><h3 id="Promise实现"><a href="#Promise实现" class="headerlink" title="Promise实现"></a>Promise实现</h3><p>因为Promise实现有很多的细节,直接贴上最终版的代码,可能会劝退很多人,也不是很好理解。我们从最基础的Promise对象功能以及方法的实现入手,由简单到复杂,展现最终效果。</p><h4 id="1-基础版Promise实现"><a href="#1-基础版Promise实现" class="headerlink" title="1. 基础版Promise实现"></a>1. 基础版Promise实现</h4><p>如果大家了解了Promise的基本特征,很容易实现这个简易版的Promise对象。代码如下,有详细的注释。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PENDING</span> = <span class="hljs-string">'pending'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FULFILLED</span> = <span class="hljs-string">'fulfilled'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">REJECTED</span> = <span class="hljs-string">'rejected'</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPromise</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">executor</span>) {<br> <span class="hljs-title function_">executor</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">resolve</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">reject</span>)<br> }<br> <span class="hljs-comment">// Promise状态</span><br> status = <span class="hljs-variable constant_">PENDING</span><br> <span class="hljs-comment">// 成功的的值</span><br> value = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的原因</span><br> reason = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 成功的回调函数</span><br> successCallBack = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的回调函数</span><br> failCallBack = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// pending -> fulfilled</span><br> resolve = <span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">FULFILLED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span> = value<br> }<br> <span class="hljs-comment">// pending -> rejected</span><br> reject = <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">REJECTED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span> = reason<br> }<br> <span class="hljs-comment">// then</span><br> <span class="hljs-title function_">then</span>(<span class="hljs-params">successCallBack, failCallBack</span>) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">FULFILLED</span>) {<br> <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">REJECTED</span>) {<br> <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> }<br> }<br>}<br><span class="hljs-comment">// 导出Promise</span><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">MyPromise</span><br></code></pre></td></tr></table></figure><h4 id="2-异步的Promise"><a href="#2-异步的Promise" class="headerlink" title="2. 异步的Promise"></a>2. 异步的Promise</h4><p>上面的简易版中,我们基本实现了Promise的特征。但是少了Promise的灵魂,我们都知道Promise的出现,是为了解决异步调用的问题,但是简易版中我们并没有支持异步调用。实现之前,我们先看一下,Promise是如何支持异步调用的。</p><p>下面是一个<code>Promise</code>对象的简单例子。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">timeout</span>(<span class="hljs-params">ms</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-built_in">setTimeout</span>(resolve, ms, <span class="hljs-string">'done'</span>);<br> });<br>}<br><br><span class="hljs-title function_">timeout</span>(<span class="hljs-number">100</span>).<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(value);<br>});<br></code></pre></td></tr></table></figure><p>上面代码中,<code>timeout</code>方法返回一个<code>Promise</code>实例,表示一段时间以后才会发生的结果。过了指定的时间(<code>ms</code>参数)以后,<code>Promise</code>实例的状态变为<code>resolved</code>,就会触发<code>then</code>方法绑定的回调函数。</p><p>Promise 新建后就会立即执行。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">let</span> promise = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params">resolve, reject</span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Promise'</span>);<br> <span class="hljs-title function_">resolve</span>();<br>});<br><br>promise.<span class="hljs-title function_">then</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'resolved.'</span>);<br>});<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Hi!'</span>);<br><br><span class="hljs-comment">// Promise</span><br><span class="hljs-comment">// Hi!</span><br><span class="hljs-comment">// resolved</span><br></code></pre></td></tr></table></figure><p>上面代码中,Promise 新建后立即执行,所以首先输出的是<code>Promise</code>。然后,<code>then</code>方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以<code>resolved</code>最后输出。</p><p>现在请大家认真观察简易版中then方法的实现,我们只匹配了fulfilled的状态和rejected的状态,如果想要支持异步的调用,那么then方法还应该存在pending状态,当resolve或者reject方法触发时,then方法的pending状态才会发生变化。</p><h5 id="核心"><a href="#核心" class="headerlink" title="核心"></a>核心</h5><ul><li>then方法pending状态时将成功回调和失败回调暂存</li><li>调用resolve方法时,判断是否存在successCallBack方法,存在则调用</li><li>调用reject方法时,判断是够存在failCallBack,存在则调用</li></ul><h5 id="实现代码"><a href="#实现代码" class="headerlink" title="实现代码"></a>实现代码</h5><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PENDING</span> = <span class="hljs-string">'pending'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FULFILLED</span> = <span class="hljs-string">'fulfilled'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">REJECTED</span> = <span class="hljs-string">'rejected'</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPromise</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">executor</span>) {<br> <span class="hljs-title function_">executor</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">resolve</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">reject</span>)<br> }<br> <span class="hljs-comment">// Promise状态</span><br> status = <span class="hljs-variable constant_">PENDING</span><br> <span class="hljs-comment">// 成功的的值</span><br> value = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的原因</span><br> reason = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 成功的回调函数</span><br> successCallBack = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的回调函数</span><br> failCallBack = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// pending -> fulfilled</span><br> resolve = <span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">FULFILLED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span> = value<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span> && <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">successCallBack</span>(value)<br> }<br> <span class="hljs-comment">// pending -> rejected</span><br> reject = <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">REJECTED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span> = reason<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span> && <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">failCallBack</span>(reason)<br> }<br> <span class="hljs-comment">// then</span><br> <span class="hljs-title function_">then</span>(<span class="hljs-params">successCallBack, failCallBack</span>) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">FULFILLED</span>) {<br> <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">REJECTED</span>) {<br> <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-comment">// pending状态时将成功回调和失败回调暂存</span><br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span> = successCallBack<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span> = failCallBack<br> }<br> }<br>}<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">MyPromise</span><br></code></pre></td></tr></table></figure><h4 id="3-多次调用then方法"><a href="#3-多次调用then方法" class="headerlink" title="3. 多次调用then方法"></a>3. 多次调用then方法</h4><p>因为then方法可以多次调用,我们要对多次then方法调用进行处理。如果是同步的情况下我们不需要进行特殊处理。因为每一个then会等待上一个then方法执行结束后执行,我们需要对异步情况下的then方法多次调用进行处理。处理逻辑其实很简单,我们只需要对then方法的pending逻辑进行修改就可以,一个then方法得到时候,我们只需要一个变量进行存储回调函数,多个then方法调用。我们只需要将回调函数存储到一个数组中。当执行resolve方法或者reject方法时,我们按照顺序将数组中的回调函数取出并执行就可以了。</p><h5 id="核心-1"><a href="#核心-1" class="headerlink" title="核心"></a>核心</h5><ul><li>then方法的pending状态时,将回调函数存储到数组中。</li><li>调用resolve方法时,判断是否存在successCallBack方法,存在则调用</li><li>调用reject方法时,判断是够存在failCallBack,存在则调用</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PENDING</span> = <span class="hljs-string">'pending'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FULFILLED</span> = <span class="hljs-string">'fulfilled'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">REJECTED</span> = <span class="hljs-string">'rejected'</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPromise</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">executor</span>) {<br> <span class="hljs-title function_">executor</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">resolve</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">reject</span>)<br> }<br> <span class="hljs-comment">// Promise状态</span><br> status = <span class="hljs-variable constant_">PENDING</span><br> <span class="hljs-comment">// 成功的的值</span><br> value = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的原因</span><br> reason = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 成功的回调函数</span><br> successCallBack = []<br> <span class="hljs-comment">// 失败的回调函数</span><br> failCallBack = []<br> <span class="hljs-comment">// pending -> fulfilled</span><br> resolve = <span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">FULFILLED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span> = value<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> }<br> }<br> <span class="hljs-comment">// pending -> rejected</span><br> reject = <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">REJECTED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span> = reason<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> }<br> }<br> <span class="hljs-comment">// then</span><br> <span class="hljs-title function_">then</span>(<span class="hljs-params">successCallBack, failCallBack</span>) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">FULFILLED</span>) {<br> <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">REJECTED</span>) {<br> <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-comment">// pending状态时将成功回调和失败回调暂存</span><br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">push</span>(successCallBack)<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">push</span>(failCallBack)<br> }<br> }<br>}<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">MyPromise</span><br></code></pre></td></tr></table></figure><h4 id="then方法的链式调用"><a href="#then方法的链式调用" class="headerlink" title="then方法的链式调用"></a>then方法的链式调用</h4><p>Promise 实例具有<code>then</code>方法,也就是说,<code>then</code>方法是定义在原型对象<code>Promise.prototype</code>上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,<code>then</code>方法的第一个参数是<code>resolved</code>状态的回调函数,第二个参数是<code>rejected</code>状态的回调函数,它们都是可选的。</p><p><code>then</code>方法返回的是一个新的<code>Promise</code>实例(注意,不是原来那个<code>Promise</code>实例)。因此可以采用链式写法,即<code>then</code>方法后面再调用另一个<code>then</code>方法</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><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><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PENDING</span> = <span class="hljs-string">'pending'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FULFILLED</span> = <span class="hljs-string">'fulfilled'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">REJECTED</span> = <span class="hljs-string">'rejected'</span><br><br><span class="hljs-comment">// 判断Promise对象then方法返回值并进行处理</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">resolvePromise</span> = (<span class="hljs-params">nextValue, resolve, reject</span>) => {<br> <span class="hljs-comment">// 如果在then方法中返回了当前Promise对象,则进行了循环引用,需要错误处理</span><br> <span class="hljs-keyword">if</span> (nextValue <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">MyPromise</span>) {<br> <span class="hljs-comment">// 返回值是Promise对象,调用该Promise对象的then方法</span><br> nextValue.<span class="hljs-title function_">then</span>(resolve, reject)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-title function_">resolve</span>(nextValue)<br> }<br>}<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPromise</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">executor</span>) {<br> <span class="hljs-title function_">executor</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">resolve</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">reject</span>)<br> }<br> <span class="hljs-comment">// Promise状态</span><br> status = <span class="hljs-variable constant_">PENDING</span><br> <span class="hljs-comment">// 成功的的值</span><br> value = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的原因</span><br> reason = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 成功的回调函数</span><br> successCallBack = []<br> <span class="hljs-comment">// 失败的回调函数</span><br> failCallBack = []<br> <span class="hljs-comment">// pending -> fulfilled</span><br> resolve = <span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">FULFILLED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span> = value<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> }<br> }<br> <span class="hljs-comment">// pending -> rejected</span><br> reject = <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">REJECTED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span> = reason<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> }<br> }<br> <span class="hljs-comment">// then</span><br> <span class="hljs-title function_">then</span>(<span class="hljs-params">successCallBack, failCallBack</span>) {<br> <span class="hljs-comment">// 如果then方法没有传递参数,则使用一个函数作为默认参数</span><br> successCallBack = successCallBack ? successCallBack : <span class="hljs-function"><span class="hljs-params">value</span> =></span> value<br> failCallBack = failCallBack ? failCallBack : <span class="hljs-function"><span class="hljs-params">reason</span> =></span> { <span class="hljs-keyword">throw</span> reason }<br> <span class="hljs-comment">// then方法返回一个Promise对象</span><br> <span class="hljs-keyword">let</span> promise = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-comment">// 当前状态是fulfilled,执行成功回调</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">FULFILLED</span>) {<br> <span class="hljs-comment">// 传递给下一个Promise对象的值是then方法的返回值</span><br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> <span class="hljs-title function_">resolvePromise</span>(nextValue, resolve, reject)<br> <span class="hljs-comment">// 当前状态是rejected,执行失败回调</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">REJECTED</span>) {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-title function_">resolvePromise</span>(nextValue, resolve, reject)<br> <span class="hljs-comment">// 当前状态是padding, 如异步函数调用。暂时将成功和失败的回调存储。</span><br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">push</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> <span class="hljs-title function_">resolvePromise</span>(nextValue, resolve, reject)<br> })<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">push</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-title function_">resolvePromise</span>(nextValue, resolve, reject)<br> })<br> }<br> })<br> <span class="hljs-keyword">return</span> promise<br> }<br>}<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">MyPromise</span><br></code></pre></td></tr></table></figure><h4 id="捕获错误"><a href="#捕获错误" class="headerlink" title="捕获错误"></a>捕获错误</h4><p>前面的版本我们没有进行错误处理,我们接下来在可能出现错误的地方,进行错误处理,让代码更严谨一点</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PENDING</span> = <span class="hljs-string">'pending'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FULFILLED</span> = <span class="hljs-string">'fulfilled'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">REJECTED</span> = <span class="hljs-string">'rejected'</span><br><br><span class="hljs-comment">// 判断Promise对象then方法返回值并进行处理</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">resolvePromise</span> = (<span class="hljs-params">promise, nextValue, resolve, reject</span>) => {<br> <span class="hljs-comment">// 如果在then方法中返回了当前Promise对象,则进行了循环引用,需要错误处理</span><br> <span class="hljs-keyword">if</span> (promise === nextValue) <span class="hljs-keyword">return</span> <span class="hljs-title function_">reject</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">TypeError</span>(<span class="hljs-string">`Chaining cycle detected for promise #<Promise>`</span>))<br> <span class="hljs-comment">// 如果在then方法中返回了当前Promise对象,则进行了循环引用,需要错误处理</span><br> <span class="hljs-keyword">if</span> (nextValue <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">MyPromise</span>) {<br> <span class="hljs-comment">// 返回值是Promise对象,调用该Promise对象的then方法</span><br> nextValue.<span class="hljs-title function_">then</span>(resolve, reject)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-title function_">resolve</span>(nextValue)<br> }<br>}<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPromise</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">executor</span>) {<br> <span class="hljs-title function_">executor</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">resolve</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">reject</span>)<br> }<br> <span class="hljs-comment">// Promise状态</span><br> status = <span class="hljs-variable constant_">PENDING</span><br> <span class="hljs-comment">// 成功的的值</span><br> value = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的原因</span><br> reason = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 成功的回调函数</span><br> successCallBack = []<br> <span class="hljs-comment">// 失败的回调函数</span><br> failCallBack = []<br> <span class="hljs-comment">// pending -> fulfilled</span><br> resolve = <span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">FULFILLED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span> = value<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> }<br> }<br> <span class="hljs-comment">// pending -> rejected</span><br> reject = <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">REJECTED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span> = reason<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> }<br> }<br> <span class="hljs-comment">// then</span><br> <span class="hljs-title function_">then</span>(<span class="hljs-params">successCallBack, failCallBack</span>) {<br> <span class="hljs-comment">// 如果then方法没有传递参数,则使用一个函数作为默认参数</span><br> successCallBack = successCallBack ? successCallBack : <span class="hljs-function"><span class="hljs-params">value</span> =></span> value<br> failCallBack = failCallBack ? failCallBack : <span class="hljs-function"><span class="hljs-params">reason</span> =></span> { <span class="hljs-keyword">throw</span> reason }<br> <span class="hljs-comment">// then方法返回一个Promise对象</span><br> <span class="hljs-keyword">let</span> promise = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-comment">// 当前状态是fulfilled,执行成功回调</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">FULFILLED</span>) {<br> <span class="hljs-comment">// 为了能拿到Mypromise实例对象,需要异步执行函数</span><br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// 传递给下一个Promise对象的值是then方法的返回值</span><br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>)<br> <span class="hljs-comment">// 当前状态是rejected,执行失败回调</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">REJECTED</span>) {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> <span class="hljs-comment">// 当前状态是padding, 如异步函数调用。暂时将成功和失败的回调存储。</span><br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">push</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>)<br> })<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">push</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>);<br> })<br> }<br> })<br> <span class="hljs-keyword">return</span> promise<br> }<br>}<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">MyPromise</span><br></code></pre></td></tr></table></figure><h4 id="Promise-resolve"><a href="#Promise-resolve" class="headerlink" title="Promise.resolve"></a>Promise.resolve</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// Promise.resolve 静态方法</span><br><span class="hljs-keyword">static</span> <span class="hljs-title function_">resolve</span>(<span class="hljs-params">value</span>) {<br> <span class="hljs-keyword">if</span> (value <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">MyPromise</span>) <span class="hljs-keyword">return</span> value;<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =></span> <span class="hljs-title function_">resolve</span>(value));<br>}<br></code></pre></td></tr></table></figure><h4 id="Promise-all"><a href="#Promise-all" class="headerlink" title="Promise.all"></a>Promise.all</h4><p><code>Promise.all()</code>方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> p = <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>([p1, p2, p3]);<br></code></pre></td></tr></table></figure><p>上面代码中,<code>Promise.all()</code>方法接受一个数组作为参数,<code>p1</code>、<code>p2</code>、<code>p3</code>都是 Promise 实例,如果不是,就会先调用下面讲到的<code>Promise.resolve</code>方法,将参数转为 Promise 实例,再进一步处理。另外,<code>Promise.all()</code>方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。</p><p><code>p</code>的状态由<code>p1</code>、<code>p2</code>、<code>p3</code>决定,分成两种情况。</p><p>(1)只有<code>p1</code>、<code>p2</code>、<code>p3</code>的状态都变成<code>fulfilled</code>,<code>p</code>的状态才会变成<code>fulfilled</code>,此时<code>p1</code>、<code>p2</code>、<code>p3</code>的返回值组成一个数组,传递给<code>p</code>的回调函数。</p><p>(2)只要<code>p1</code>、<code>p2</code>、<code>p3</code>之中有一个被<code>rejected</code>,<code>p</code>的状态就变成<code>rejected</code>,此时第一个被<code>reject</code>的实例的返回值,会传递给<code>p</code>的回调函数。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs ts"><span class="hljs-comment">// Promise.all 静态方法</span><br><span class="hljs-keyword">static</span> <span class="hljs-title function_">all</span>(<span class="hljs-params">array</span>) {<br> <span class="hljs-keyword">let</span> result = [];<br> <span class="hljs-keyword">let</span> index = <span class="hljs-number">0</span>;<br> <span class="hljs-comment">// Promise.all 返回一个Promise对象</span><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < array.<span class="hljs-property">length</span>; ++i) {<br> <span class="hljs-title class_">MyPromise</span>.<span class="hljs-title function_">resolve</span>(array[i]).<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> result[i] = value<br> ++index<br> <span class="hljs-comment">// 当all参数数组中所有项执行结束后执行resolve方法</span><br> <span class="hljs-keyword">if</span> (index === array.<span class="hljs-property">length</span>) {<br> <span class="hljs-title function_">resolve</span>(result);<br> }<br> }, <span class="hljs-function"><span class="hljs-params">err</span> =></span> <span class="hljs-title function_">reject</span>(err))<br> }<br> })<br>}<br></code></pre></td></tr></table></figure><h4 id="最终版Promise"><a href="#最终版Promise" class="headerlink" title="最终版Promise"></a>最终版Promise</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PENDING</span> = <span class="hljs-string">'pending'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FULFILLED</span> = <span class="hljs-string">'fulfilled'</span><br><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">REJECTED</span> = <span class="hljs-string">'rejected'</span><br><br><span class="hljs-comment">// 判断Promise对象then方法返回值并进行处理</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">resolvePromise</span> = (<span class="hljs-params">promise, nextValue, resolve, reject</span>) => {<br> <span class="hljs-comment">// 如果在then方法中返回了当前Promise对象,则进行了循环引用,需要错误处理</span><br> <span class="hljs-keyword">if</span> (promise === nextValue) <span class="hljs-keyword">return</span> <span class="hljs-title function_">reject</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">TypeError</span>(<span class="hljs-string">`Chaining cycle detected for promise #<Promise>`</span>))<br> <span class="hljs-comment">// 如果在then方法中返回了当前Promise对象,则进行了循环引用,需要错误处理</span><br> <span class="hljs-keyword">if</span> (nextValue <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">MyPromise</span>) {<br> <span class="hljs-comment">// 返回值是Promise对象,调用该Promise对象的then方法</span><br> nextValue.<span class="hljs-title function_">then</span>(resolve, reject)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-title function_">resolve</span>(nextValue)<br> }<br>}<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPromise</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">executor</span>) {<br> <span class="hljs-title function_">executor</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">resolve</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">reject</span>)<br> }<br> <span class="hljs-comment">// Promise状态</span><br> status = <span class="hljs-variable constant_">PENDING</span><br> <span class="hljs-comment">// 成功的的值</span><br> value = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 失败的原因</span><br> reason = <span class="hljs-literal">undefined</span><br> <span class="hljs-comment">// 成功的回调函数</span><br> successCallBack = []<br> <span class="hljs-comment">// 失败的回调函数</span><br> failCallBack = []<br> <span class="hljs-comment">// pending -> fulfilled</span><br> resolve = <span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">FULFILLED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span> = value<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> }<br> }<br> <span class="hljs-comment">// pending -> rejected</span><br> reject = <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> !== <span class="hljs-variable constant_">PENDING</span>) <span class="hljs-keyword">return</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> = <span class="hljs-variable constant_">REJECTED</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span> = reason<br> <span class="hljs-comment">// 调用then方法中的回调函数</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-property">length</span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">shift</span>()(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> }<br> }<br> <span class="hljs-comment">// then</span><br> <span class="hljs-title function_">then</span>(<span class="hljs-params">successCallBack, failCallBack</span>) {<br> <span class="hljs-comment">// 如果then方法没有传递参数,则使用一个函数作为默认参数</span><br> successCallBack = successCallBack ? successCallBack : <span class="hljs-function"><span class="hljs-params">value</span> =></span> value<br> failCallBack = failCallBack ? failCallBack : <span class="hljs-function"><span class="hljs-params">reason</span> =></span> { <span class="hljs-keyword">throw</span> reason }<br> <span class="hljs-comment">// then方法返回一个Promise对象</span><br> <span class="hljs-keyword">let</span> promise = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-comment">// 当前状态是fulfilled,执行成功回调</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">FULFILLED</span>) {<br> <span class="hljs-comment">// 为了能拿到Mypromise实例对象,需要异步执行函数</span><br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">// 传递给下一个Promise对象的值是then方法的返回值</span><br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>)<br> <span class="hljs-comment">// 当前状态是rejected,执行失败回调</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> === <span class="hljs-variable constant_">REJECTED</span>) {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> <span class="hljs-comment">// 当前状态是padding, 如异步函数调用。暂时将成功和失败的回调存储。</span><br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">successCallBack</span>.<span class="hljs-title function_">push</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">successCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">value</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>)<br> })<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">failCallBack</span>.<span class="hljs-title function_">push</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">let</span> nextValue = <span class="hljs-title function_">failCallBack</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">reason</span>)<br> <span class="hljs-title function_">resolvePromise</span>(promise, nextValue, resolve, reject)<br> } <span class="hljs-keyword">catch</span> (e) {<br> <span class="hljs-title function_">reject</span>(e)<br> }<br> }, <span class="hljs-number">0</span>);<br> })<br> }<br> })<br> <span class="hljs-keyword">return</span> promise<br> }<br><br> <span class="hljs-keyword">catch</span>(failCallback) {<br> <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">then</span>(<span class="hljs-literal">undefined</span>, failCallback)<br> }<br><br> <span class="hljs-comment">// Promise.resolve 静态方法</span><br> <span class="hljs-keyword">static</span> <span class="hljs-title function_">resolve</span>(<span class="hljs-params">value</span>) {<br> <span class="hljs-keyword">if</span> (value <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">MyPromise</span>) <span class="hljs-keyword">return</span> value;<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =></span> <span class="hljs-title function_">resolve</span>(value));<br> }<br><br> <span class="hljs-comment">// Promise.all 静态方法</span><br> <span class="hljs-keyword">static</span> <span class="hljs-title function_">all</span>(<span class="hljs-params">array</span>) {<br> <span class="hljs-keyword">let</span> result = [];<br> <span class="hljs-keyword">let</span> index = <span class="hljs-number">0</span>;<br> <span class="hljs-comment">// Promise.all 返回一个Promise对象</span><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPromise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < array.<span class="hljs-property">length</span>; ++i) {<br> <span class="hljs-title class_">MyPromise</span>.<span class="hljs-title function_">resolve</span>(array[i]).<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> result[i] = value<br> ++index<br> <span class="hljs-comment">// 当all参数数组中所有项执行结束后执行resolve方法</span><br> <span class="hljs-keyword">if</span> (index === array.<span class="hljs-property">length</span>) {<br> <span class="hljs-title function_">resolve</span>(result);<br> }<br> }, <span class="hljs-function"><span class="hljs-params">err</span> =></span> <span class="hljs-title function_">reject</span>(err))<br> }<br> })<br> }<br>}<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">MyPromise</span><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>React学习笔记</title>
<link href="/2020/04/14/React%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2020/04/14/React%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="React学习笔记"><a href="#React学习笔记" class="headerlink" title="React学习笔记"></a>React学习笔记</h1><h2 id="1-什么时候需要使用类组件的构造函数"><a href="#1-什么时候需要使用类组件的构造函数" class="headerlink" title="1. 什么时候需要使用类组件的构造函数"></a>1. 什么时候需要使用类组件的构造函数</h2><p>根据官方给出的实例,大概只有这么三种情况下,才必须要使用类组件的构造函数。</p><ol><li>需要在构造函数中初始化state。</li><li>必须在构造函数中对this.props进行接收(通常来说这种情况不是必须的)。</li><li>需要绑定类组件中自定义方法的this。(因为自定义方法中的this,如果不是使用箭头函数<br>定义的,则自定的函数中的this指向会丢失。所以需要在constructor中使用bind进行绑定。这种情况可以使用箭头函数来代替。)<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Clock</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">React.Component</span> {<br> <span class="hljs-title function_">constructor</span>(<span class="hljs-params">props</span>) {<br> <span class="hljs-variable language_">super</span>(props);<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span> = {<span class="hljs-attr">date</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()};<br> }<br>}<br></code></pre></td></tr></table></figure></li></ol><h2 id="2-如何实现组件的通信"><a href="#2-如何实现组件的通信" class="headerlink" title="2. 如何实现组件的通信"></a>2. 如何实现组件的通信</h2><h3 id="2-1-自上而下的数据流"><a href="#2-1-自上而下的数据流" class="headerlink" title="2.1 自上而下的数据流"></a>2.1 自上而下的数据流</h3><p> React哲学是从上至下的数据流,当从上至下进行通信时,通常的方式是,上层组件传递给下层组件的数据放在下层组件的属性上,最终下层组件会在props对象上拿到上层组件传递的数据并进行渲染。</p><h3 id="2-2-同级组件间的通信"><a href="#2-2-同级组件间的通信" class="headerlink" title="2.2 同级组件间的通信"></a>2.2 同级组件间的通信</h3><h4 id="2-2-1-React原生"><a href="#2-2-1-React原生" class="headerlink" title="2.2.1 React原生"></a>2.2.1 React原生</h4><p> React不支持同级组件间直接通信,提出了一种状态提升的概念。需要组件通信时,调用上层组件传递的方法,将需要传递的内容放在该函数的实参中。上层组件在该函数的定义体中拿到下层组件传递的数据,也就是状态提升到上层组件,上层组件将数据传递给其他子组件,从而实现了同级组件的通信。</p><h4 id="2-2-2-发布订阅"><a href="#2-2-2-发布订阅" class="headerlink" title="2.2.2 发布订阅"></a>2.2.2 发布订阅</h4><p> 当组件间的关系变得复杂时,react原生的组件通信方式,会产生很多”中间者”性质的<code>props</code>。代码会变得允许且不优雅。发布订阅模式是其中的一个解决方案。<br> 发布订阅,即,需要发起通信的组件,发布消息,需要接受消息的组件,订阅消息。当消息发布,接受消息的组件,知道消息发布,从而可以接收到消息。伪代码如下:</p><p><strong>订阅消息的组件</strong></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs jsx"><span class="hljs-keyword">import</span> <span class="hljs-title class_">React</span>, { <span class="hljs-title class_">Component</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span><br><span class="hljs-keyword">import</span> <span class="hljs-title class_">PubSub</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'pubsub-js'</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">List</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Component</span> {<br><span class="hljs-comment">// 组件挂载结束之后订阅</span><br> <span class="hljs-title function_">componentDidMount</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">token</span> = <span class="hljs-title class_">PubSub</span>.<span class="hljs-title function_">subscribe</span>(<span class="hljs-string">'setData'</span>, <span class="hljs-function">(<span class="hljs-params">msg, data</span>) =></span> {<br> <span class="hljs-comment">// 根据订阅结果更新state</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>(data);<br> })<br> }<br><br><span class="hljs-comment">// 组件卸载了之后清除订阅</span><br><span class="hljs-title function_">componentWillUnmount</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-title class_">PubSub</span>.<span class="hljs-title function_">unsubscribe</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">token</span>);<br>}<br>}<br></code></pre></td></tr></table></figure><p><strong>发布消息的组件</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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><code class="hljs js"><span class="hljs-keyword">import</span> <span class="hljs-title class_">React</span>, { <span class="hljs-title class_">Component</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span><br><span class="hljs-keyword">import</span> <span class="hljs-title class_">PubSub</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'pubsub-js'</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Search</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Component</span> {<br><span class="hljs-comment">// 调用getUserInfo函数时,发布消息</span><br>getUserInfo = <span class="hljs-function">() =></span> {<br><span class="hljs-title class_">PubSub</span>.<span class="hljs-title function_">publish</span>(<span class="hljs-string">'setData'</span>, {<br> <span class="hljs-attr">isFirst</span>: <span class="hljs-literal">false</span>,<br> <span class="hljs-attr">isLoading</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">isError</span>: <span class="hljs-literal">false</span>,<br> <span class="hljs-attr">listItems</span>: [] <br> });<br>}<br>}<br><br></code></pre></td></tr></table></figure><h4 id="2-2-3-Redux"><a href="#2-2-3-Redux" class="headerlink" title="2.2.3 Redux"></a>2.2.3 Redux</h4><p> 使用发布订阅可以解决一些问题,但是随着项目的规模升级,复杂度升级。使用发布订阅,也可以解决问题,组件内到处都是发布订阅的方法,项目会变得越来越难以掌控,维护性也会变差。随之,<code>redux(状态管理)</code>这种解决方案出现了。和发布订阅一样,<code>redux</code>也不是原生React的内容,是官方维护的第三方库,所以可以放心使用。</p><blockquote><p><strong>“只有遇到 React 实在解决不了的问题,你才需要 Redux 。”</strong></p></blockquote><p><strong>原理</strong></p><p><img src="/img/React/redux.jpg"></p><p>图中,有几个概念还是需要清楚的。</p><ol><li><p><strong>Store</strong></p><p>Store,状态管理仓库,所以的状态在Store中进行管理。</p></li><li><p><strong>Action Creators</strong></p><p>动作创造者,如 进行 + 1 的操作,就会创造出一个<code>action</code>, 形式是:{ type: ‘+’, data: 1 }</p></li><li><p><strong>Reducers</strong></p><p>最终进行处理的地方</p></li></ol><p><code>Store</code>作为一个中心调度者,将<code>action</code>分发给<code>reducers</code>, <code>reducers</code></p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
</tags>
</entry>
<entry>
<title>HTTP学习笔记</title>
<link href="/2020/04/01/HTTP%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2020/04/01/HTTP%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="HTTP学习笔记"><a href="#HTTP学习笔记" class="headerlink" title="HTTP学习笔记"></a>HTTP学习笔记</h1><p><em>HTTP协议,只是网络协议中很小的一部分。HTTP协议对我们来说,看不见,摸不着,但是真实存在。或许我们无法像使用编程语言一样,实现具体的功能。但是HTTP的重要性,不需要特意强调。跨域方案<code>CROS</code>响应头信息字段<code>Access-Control-Allow-Origin</code>的设置,使用<code>cookie</code>时,响应头<code>Set-Cookie</code>的设置等等,其实我们都在使用HTTP协议。但是一般情况下都是边用边查,没有形成体系。没有一个组织结构,导致都是一些零散的知识点,对于使用,学习,存在很大的困难。这篇博客(可能不是一篇),有体系的整理HTTP协议的内容。希望可以帮助自己和其他小伙伴</em></p><h3 id="什么是HTTP协议"><a href="#什么是HTTP协议" class="headerlink" title="什么是HTTP协议"></a>什么是HTTP协议</h3><p> HTTP(HyperText Transfer Protocol)超文本传输协议。</p><ol><li>超文本:包括文字,图片,音频,视频等。</li><li>传输:负责超文本的传输</li><li>协议:负责传输超文本过程中的规范</li></ol><h4 id="大白话总结"><a href="#大白话总结" class="headerlink" title="大白话总结"></a>大白话总结</h4><p> HTTP就是一个用在计算机世界里面的协议,使用计算机能理解的语言,确立了计算机之间交流通信的规范。</p><h3 id="发展历程"><a href="#发展历程" class="headerlink" title="发展历程"></a>发展历程</h3><p> <em>学习任何一项技术,都离不开发展历程。通过发展历程,才能知道这门技术在什么时候,遇到了什么样的问题,发生了什么变革。了解了这些,对我们的学习可以起到事半功倍的效果。</em><br>下面按照HTTP的时间顺序整理</p><h4 id="HTTP-0-9"><a href="#HTTP-0-9" class="headerlink" title="HTTP 0.9"></a>HTTP 0.9</h4><ol><li>只能处理纯文本格式</li><li>请求响应结束后立即关闭连接</li><li>只允许使用<code>GET</code>请求从服务器获取HTML文档</li></ol><h4 id="HTTP-1-1"><a href="#HTTP-1-1" class="headerlink" title="HTTP 1.1"></a>HTTP 1.1</h4><ol><li>增加了HEAD,POST等新方法</li><li>引入了HTTP Header的概念,处理请求和响应变得更加灵活</li><li>传输数据不仅限于文本</li><li>增加了响应状态码,标记可能的错误原因</li><li>增加了PUT,DELETE等方法</li><li>允许长连接</li><li>增加了缓存管理和控制</li><li>允许数据分块,利于传输大文件</li><li>强制要求Host头</li></ol><h4 id="HTTP-2-0"><a href="#HTTP-2-0" class="headerlink" title="HTTP 2.0"></a>HTTP 2.0</h4><ol><li>二进制协议,不仅限于纯文本。</li><li>使用专用算法压缩头部,减少了数据传输量。</li><li>废弃了管道,可以发起多个请求。</li><li>增强了安全性,要求加密通信。</li><li>允许服务端向客户端主动推送。</li></ol><h3 id="与HTTP相关的各种概念"><a href="#与HTTP相关的各种概念" class="headerlink" title="与HTTP相关的各种概念"></a>与HTTP相关的各种概念</h3><ol><li>CDN(Content Delivery Network)内容分发网络:应用于HTTP协议里的缓存和代理技术,代替源站响应客户端的请求。</li><li>代理:HTTP协议中请求方和响应方直接的一个环节。代理分为下面几种。</li></ol><ul><li>匿名代理: 完全隐匿了被代理的机器,外界看到的只有代理服务器。</li><li>透明代理: 在传输过程中是“透明开放”的,外界即知道代理也知道客户端。</li><li>正向代理: 靠近客户端,代表客户端向服务端发送请求。</li><li>反向代理: 靠近服务端,代表服务端向客户端响应请求。</li></ul><h3 id="TCP-IP协议族"><a href="#TCP-IP协议族" class="headerlink" title="TCP/IP协议族"></a>TCP/IP协议族</h3><p> TCP/IP 协议严格来说并不是一种协议,而是互联网相关各类协议族的总称。其中包括TCP,IP,HTTP等协议。TCP/IP协议族最重要的一点就是分层。按照层次分为以下四层:应用层,传输层,网络层,数据链路层。</p><h4 id="TCP-IP协议族各层的作用"><a href="#TCP-IP协议族各层的作用" class="headerlink" title="TCP/IP协议族各层的作用"></a>TCP/IP协议族各层的作用</h4><ul><li>应用层: 决定向用户提供服务时通信的活动。如:FTP(文件传输协议),DNS(域名系统),HTTP协议</li><li>传输层: 提供处于网络连接中的两台计算机之间的通信。如TCP(传输控制协议),UDP(用户数据报协议)</li><li>网络层: 用来处理在网络上流动的数据包。数据包是网络传输的最小单位。该层规定了通过怎样的路径到达对象计算机,并把数据包传送给对方。在多台设备间传输时,网络层的作用就是选择一条传输路径。如:IP协议</li><li>数据链路层: 用来处理连接网络的硬件部分。包括控制操作系统,硬件的设备驱动,网卡等一切传输介质。</li></ul><h3 id="与HTTP相关的各种协议"><a href="#与HTTP相关的各种协议" class="headerlink" title="与HTTP相关的各种协议"></a>与HTTP相关的各种协议</h3><ol><li><p>TCP 协议<br> 有状态的协议,需要先与对方建立连接然后才能发送数据,而且保证数据不丢失,不重复。TCP的数据是连续的字节流,有先后顺序。</p></li><li><p>UDP 协议<br> 无状态的协议,不用事先与对方建立连接就可以发送数据,但不保证数据一定会发送到对方。UDP的数据是分散的小数据包,顺序发,乱序收。</p></li><li><p>DNS协议<br> DNS协议就是域名到IP的一个映射,将域名转成IP地址(一个IP地址可以对应多个域名)。域名方便我们记忆。但是网络中寻址,只认IP地址。所以需要将域名映射为IP。<br>DNS 通过一个访问顺序来逐渐确定域名的IP地址。</p></li></ol><ul><li><p>根域名服务器 > 顶级域名服务器 > 权威域名服务器。<br>3.1 分析<br> 3.1.1 如果我们访问<a href="http://www.google.com/">www.google.com</a> 这个网址,假设没有缓存,是首次访问。</p><pre><code class="hljs">1. 访问`root server `根服务器 2. 根服务器返回了一个顶级域名的地址,即 .com 服务器地址 3. 递归访问` .com`顶级域名服务器。 4. 顶级域名服务器收到请求后,返回权威域名服务器地址,即`google.com`地址。</code></pre><p> 3.1.2 如果我们第二次访问<a href="http://www.google.com/">www.google.com</a> 这个网址。</p><pre><code class="hljs">5. 查看浏览器缓存,有没有DNS查询的结果 6. 查看操作系统缓存,有没有DNS查询结果 7. 查看主机映射文件`hosts`有没有`www.google.com`的IP映射 8. 使用DNS协议。</code></pre><p>3.2 域名的新玩法</p><ol><li>重定向</li></ol><pre><code class="hljs">- 将域名的IP在需要的时候进行更改,如切换主机。</code></pre><ol start="2"><li>实现负载均衡</li></ol><pre><code class="hljs">- 域名可以对应多个IP,即多台主机。客户端收到多个主机地址后,使用自己的轮询算法依次向服务器端发送请求,实现负载均衡。- 域名解析可以配置内部的策略,返回离客户端最近的主机,这样DNS端把请求分发到不同的服务器,实现负载均衡。</code></pre></li></ul><h3 id="HTTP请求过程"><a href="#HTTP请求过程" class="headerlink" title="HTTP请求过程"></a>HTTP请求过程</h3><ol><li>浏览器从地址栏的输入中获得服务器的 IP 地址和端口号;</li><li>浏览器用 TCP 的三次握手与服务器建立连接;</li><li>浏览器向服务器发送拼好的报文;</li><li>服务器收到报文后处理请求,同样拼好报文再发给浏览器;</li><li>浏览器解析报文,渲染输出页面。</li></ol><h3 id="HTTP请求总结"><a href="#HTTP请求总结" class="headerlink" title="HTTP请求总结"></a>HTTP请求总结</h3><ol><li>HTTP 协议基于底层的 TCP/IP 协议,所以必须要用 IP 地址建立连接;</li><li>如果不知道 IP 地址,就要用 DNS 协议去解析得到 IP 地址,否则就会连接失败;</li><li>建立 TCP 连接后会顺序收发数据,请求方和应答方都必须依据 HTTP 规构建和解析报文;</li><li>为了减少响应时间,整个过程中的每一个环节都会有缓存,能够实现“短路”操作;</li></ol>]]></content>
<categories>
<category>HTTP</category>
</categories>
<tags>
<tag>HTTP</tag>
</tags>
</entry>
<entry>
<title>简易版Vue-Router实现</title>
<link href="/2020/03/19/%E7%AE%80%E6%98%93%E7%89%88Vue-Router%E5%AE%9E%E7%8E%B0/"/>
<url>/2020/03/19/%E7%AE%80%E6%98%93%E7%89%88Vue-Router%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<h2 id="简易版Vue-Router实现"><a href="#简易版Vue-Router实现" class="headerlink" title="简易版Vue-Router实现"></a>简易版Vue-Router实现</h2><p>Vue-Router重要性不用说了。不想当一个只会使用的coder,尝试实现一个简易版的Vue-Router,来帮助自己加深理解掌握Vue-Router。Vue-Router有两种模式。默认模式hash,也就是会变更url中#后面的内容,不会刷新浏览器,另一种个是history模式,history模式一般配合服务端的返回来使用。本次实现history模式的Vue-Router(hash模式也是异曲同工),且适用于SPA,不依赖服务端的返回。</p><h3 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h3><p><strong>插件、slot 插槽、混入、render 函数。</strong></p><p>前置知识不清楚建议先阅读Vue官方文档,可以帮助更好理解。</p><h3 id="Vue-Router-的核心代码"><a href="#Vue-Router-的核心代码" class="headerlink" title="Vue Router 的核心代码"></a>Vue Router 的核心代码</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 注册插件</span><br><span class="hljs-comment">// Vue.use() 内部调用传入对象的 install 方法</span><br><span class="hljs-title class_">Vue</span>.<span class="hljs-title function_">use</span>(<span class="hljs-title class_">VueRouter</span>)<br><br><span class="hljs-comment">// 创建路由对象</span><br><span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> <span class="hljs-title class_">VueRouter</span>({<br><span class="hljs-attr">routes</span>: [<br>{ <span class="hljs-attr">name</span>: <span class="hljs-string">'home'</span>, <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>, <span class="hljs-attr">component</span>: homeComponent }<br>]<br>})<br><span class="hljs-comment">// 创建 Vue 实例,注册 router 对象</span><br><span class="hljs-keyword">new</span> <span class="hljs-title class_">Vue</span>({<br>router,<br><span class="hljs-attr">render</span>: <span class="hljs-function"><span class="hljs-params">h</span> =></span> <span class="hljs-title function_">h</span>(<span class="hljs-title class_">App</span>)<br>}).$mount(<span class="hljs-string">'#app'</span>)<br></code></pre></td></tr></table></figure><h3 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h3><ul><li>创建 VueRouter 插件,静态方法 install <ul><li>判断插件是否已经被加载 </li><li>当 Vue 加载的时候把传入的 router 对象挂载到 Vue 实例上(注意:只执行一次)</li></ul></li><li>创建 VueRouter 类 <ul><li>初始化,options、routeMap、data (创建 Vue 实例作为响应式数据记录当前路<br> 径) </li><li>initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中 </li><li>注册 popstate 事件,当路由地址发生变化,重新记录当前的路径 </li><li>创建 router-link 和 router-view 组件 </li><li>当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view</li></ul></li></ul><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">let</span> _Vue = <span class="hljs-literal">null</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">VueRouter</span> {<br> <span class="hljs-comment">// 接受两个参数,一个是Vue的构造函数</span><br> <span class="hljs-keyword">static</span> install (<span class="hljs-title class_">Vue</span>) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-title class_">VueRouter</span>.<span class="hljs-property">install</span>.<span class="hljs-property">installed</span>) {<br> <span class="hljs-keyword">return</span><br> }<br> <span class="hljs-comment">// 1. 判断当前插件是否安装</span><br> <span class="hljs-title class_">VueRouter</span>.<span class="hljs-property">install</span>.<span class="hljs-property">installed</span> = <span class="hljs-literal">true</span><br> <span class="hljs-comment">// 2. Vue构造函数记录到全局,后续使用</span><br> _Vue = <span class="hljs-title class_">Vue</span><br> <span class="hljs-comment">// 3. 把创建Vue实例时传入的router对象注入到Vue实例</span><br> <span class="hljs-comment">// TODO 混入 所有Vue实例以及组件上都会被混入</span><br> _Vue.<span class="hljs-title function_">mixin</span>({<br> beforeCreate () {<br> <span class="hljs-comment">// 只有当前Vue实例上具有$options,才在Vue原型上挂载$router对象</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">$options</span>.<span class="hljs-property">router</span>) {<br> _Vue.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">$router</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">$options</span>.<span class="hljs-property">router</span><br> <span class="hljs-comment">// 进行初始化</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">$options</span>.<span class="hljs-property">router</span>.<span class="hljs-title function_">init</span>()<br> }<br> }<br> })<br> }<br><br> <span class="hljs-comment">// 构造函数</span><br> <span class="hljs-title function_">constructor</span> (options) {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">options</span> = options<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">routeMap</span> = {}<br> <span class="hljs-comment">// observable 方法让一个对象可响应 Vue 内部会用它来处理 data 函数返回的对象</span><br> <span class="hljs-comment">// 返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">data</span> = _Vue.<span class="hljs-title function_">observable</span>({<br> <span class="hljs-attr">current</span>: <span class="hljs-string">'/'</span><br> })<br> }<br><br> <span class="hljs-comment">// 初始化操作</span><br> init () {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">createRouteMap</span>()<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">initComponent</span>(_Vue)<br> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">intiEvent</span>()<br> }<br><br> <span class="hljs-comment">// 将传递给Vue-Router对象的options对象转换成routeMap</span><br> createRouteMap () {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">options</span>.<span class="hljs-property">routes</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">route</span> =></span> {<br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">routeMap</span>[route.<span class="hljs-property">path</span>] = route.<span class="hljs-property">component</span><br> })<br> }<br><br> <span class="hljs-comment">// 创建router-link</span><br> initComponent (<span class="hljs-title class_">Vue</span>) {<br> <span class="hljs-title class_">Vue</span>.<span class="hljs-title function_">component</span>(<span class="hljs-string">'router-link'</span>, {<br> <span class="hljs-attr">props</span>: {<br> <span class="hljs-attr">to</span>: <span class="hljs-title class_">String</span><br> },<br> <span class="hljs-attr">methods</span>: {<br> clickHandler (e) {<br> <span class="hljs-comment">// pushState方法改变浏览器的地址栏,会被浏览器的历史记住 不刷新页面,不向服务器发送请求</span><br> <span class="hljs-variable language_">window</span>.<span class="hljs-property">history</span>.<span class="hljs-title function_">pushState</span>({}, <span class="hljs-string">''</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">to</span>)<br> <span class="hljs-comment">// router-link是Vue实例 都可以访问Vue.prototype 注册了$router对象</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">$router</span>.<span class="hljs-property">data</span>.<span class="hljs-property">current</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">to</span><br> <span class="hljs-comment">// 阻止默认事件行为</span><br> e.<span class="hljs-title function_">preventDefault</span>()<br> }<br> },<br> <span class="hljs-comment">// Vue默认创构建的是运行时版本,不支持template,可以通过vue-cli配置或者使用render函数</span><br> <span class="hljs-comment">// template: `<a :href="to"><slot></slot></a>`</span><br> render (h) {<br> <span class="hljs-comment">// h 函数创建虚拟DOM 第一个参数标签名,第二个参数是属性对象, 第三个参数是内容</span><br> <span class="hljs-keyword">return</span> <span class="hljs-title function_">h</span>(<span class="hljs-string">'a'</span>, {<br> <span class="hljs-attr">attrs</span>: {<br> <span class="hljs-string">'href'</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">to</span><br> },<br> <span class="hljs-attr">on</span>: {<br> <span class="hljs-attr">click</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">clickHandler</span><br> }<br> }, [<span class="hljs-variable language_">this</span>.<span class="hljs-property">$slots</span>.<span class="hljs-property">default</span>])<br> }<br> })<br><br> <span class="hljs-keyword">const</span> self = <span class="hljs-variable language_">this</span><br> <span class="hljs-comment">// 创建router-view</span><br> <span class="hljs-title class_">Vue</span>.<span class="hljs-title function_">component</span>(<span class="hljs-string">'router-view'</span>, {<br> render (h) {<br> <span class="hljs-comment">// 获取路由组件</span><br> <span class="hljs-keyword">const</span> component = self.<span class="hljs-property">routeMap</span>[self.<span class="hljs-property">data</span>.<span class="hljs-property">current</span>]<br> <span class="hljs-keyword">return</span> <span class="hljs-title function_">h</span>(component)<br> }<br> })<br> }<br><br> <span class="hljs-comment">// 初始化事件处理</span><br> intiEvent () {<br> <span class="hljs-comment">// popstate 事件函数 当浏览器地址栏发生变化时触发</span><br> <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'popstate'</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-comment">// 改变当前的路径 data 是一个响应对象发生变化后重新加载组件</span><br> <span class="hljs-variable language_">this</span>.<span class="hljs-property">data</span>.<span class="hljs-property">current</span> = <span class="hljs-variable language_">window</span>.<span class="hljs-property">location</span>.<span class="hljs-property">pathname</span><br> })<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="注意点"><a href="#注意点" class="headerlink" title="注意点"></a>注意点</h3><ol><li><p>创建router-link和router-view组件时,没有使用template模板,因为vue-cli创建的项目默认使用的运行时版本的Vue。不支持template。解决方法如下</p><ul><li>如果想切换成带编译器版本的 Vue.js 。项目根目录创建 vue.config.js 文件,添加 runtimeCompiler</li></ul> <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {<br><span class="hljs-attr">runtimeCompiler</span>: <span class="hljs-literal">true</span><br>}<br></code></pre></td></tr></table></figure><ul><li>可以实现代码一样,不使用template,也不需要配置vue-cli,使用运行时版本支持的render函数对模板进行编译</li></ul></li></ol>]]></content>
<categories>
<category>Vue</category>
</categories>
<tags>
<tag>Vue</tag>
</tags>
</entry>
<entry>
<title>跨域方案解决</title>
<link href="/2019/01/10/%E8%B7%A8%E5%9F%9F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
<url>/2019/01/10/%E8%B7%A8%E5%9F%9F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</url>
<content type="html"><![CDATA[<h3 id="跨域方案解决"><a href="#跨域方案解决" class="headerlink" title="跨域方案解决"></a>跨域方案解决</h3><p><em><code>ajax</code>的出现,推动了web的发展。随之,为了防止恶意文档,保护用户信息安全。浏览器制定了同源安全策略</em></p><h4 id="1-同源安全策略是什么?"><a href="#1-同源安全策略是什么?" class="headerlink" title="1. 同源安全策略是什么?"></a>1. 同源安全策略是什么?</h4><p><strong>很简单,接着看!</strong></p><h5 id="1-1-什么是同源"><a href="#1-1-什么是同源" class="headerlink" title="1.1 什么是同源"></a>1.1 什么是同源</h5><p><strong>同源就是比较两个<code>URL</code>地址,当他们满足一定的条件,他们就是同源的。</strong><br><strong>同时满足下面三个条件</strong></p><ul><li>协议相同</li><li>主机相同</li><li>端口相同</li></ul><p>看个例子</p><figure class="highlight http"><table><tr><td class="gutter"><pre><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><code class="hljs http">https://www.baidu.com/s?ie=UTF-8&wd=a<br>// 协议 https<br>// 主机 www.baidu.com<br>// 端口 默认端口为80所以没有显示。<br></code></pre></td></tr></table></figure><p><strong>练习</strong><br>下表给出了与 URL <a href="http://store.company.com/dir/page.html">http://store.company.com/dir/page.html</a> 的源进行对比的示例:</p><table><thead><tr><th><strong>URL</strong></th><th align="left"><strong>结果</strong></th><th><strong>原因</strong></th></tr></thead><tbody><tr><td><a href="http://store.company.com/dir2/other.html">http://store.company.com/dir2/other.html</a></td><td align="left">同源</td><td>只有路径不同</td></tr><tr><td><a href="http://store.company.com/dir/inner/another.html">http://store.company.com/dir/inner/another.html</a></td><td align="left">同源</td><td>只有路径不同</td></tr><tr><td><a href="https://store.company.com/secure.html">https://store.company.com/secure.html</a></td><td align="left">不同源</td><td>协议不同</td></tr><tr><td><a href="http://store.company.com:81/dir/etc.html">http://store.company.com:81/dir/etc.html</a></td><td align="left">不同源</td><td>端口不同 ( <code>http://</code> 默认端口是80)</td></tr><tr><td><a href="http://news.company.com/dir/other.html">http://news.company.com/dir/other.html</a></td><td align="left">不同源</td><td>主机不同</td></tr></tbody></table><h5 id="1-2-同源安全策略"><a href="#1-2-同源安全策略" class="headerlink" title="1.2 同源安全策略"></a>1.2 同源安全策略</h5><p> 同源安全策略,是浏览器方提供一种的安全策略。举个例子:我们登陆了一个网址为<code>http://www.A.com</code>的网站,那么默认我们只能请求和<code>http://www.A.com</code>同源的网站。在<code>http://www.A.com</code>网站,发送的所有请求地址只要是和<code>http://www.A.com</code>网站同源的地址,那么可以访问。但是如果此时在<code>http://www.A.com</code>向<code>http://www.B.com</code>发送一个请求。此时就会触发浏览器同源安全策略。</p><p><strong>演示</strong><br>在一个地址为<code>http://localhost:3000/</code>的网址地址中的页面,向地址为<code>http://localhost:3001/</code>的网址发送一个ajax请求。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs ts"><span class="hljs-variable language_">window</span>.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br><br> <span class="hljs-keyword">let</span> xhr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>();<br><br> xhr.<span class="hljs-title function_">open</span>(<span class="hljs-string">'get'</span>, <span class="hljs-string">'http://localhost:3001/get'</span>);<br><br> xhr.<span class="hljs-title function_">send</span>();<br><br> xhr.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(xhr.<span class="hljs-property">responseText</span>);<br> }<br></code></pre></td></tr></table></figure><p>ajax请求触发浏览器同源策略<br><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/13bac764-e1b3-4ab1-a28b-447221422579" alt="ajax_跨域"></p><p>如上,我们用<code>ajax</code>进行了一个跨域请求,触发了浏览器的同源安全策略。因此,不能使用<code>ajax</code>进行请求,聪明的人想出了各种办法。其中<code>jsonp</code>是最早的解决方案之一。</p><h4 id="2-跨域方案解决之JSONP(jsonp-json-with-padding-)"><a href="#2-跨域方案解决之JSONP(jsonp-json-with-padding-)" class="headerlink" title="2. 跨域方案解决之JSONP(jsonp json with padding )"></a>2. 跨域方案解决之<code>JSONP(jsonp json with padding )</code></h4><p><em>我们已经知道了,<code>ajax</code>不支持跨域请求。但是我们页面中是有支持跨域请求的存在的,如<code>script</code>标签的<code>src</code>属性,用来加载<code>JavaScript</code>资源,请求方式是<code>GET</code>。这种方式是支持跨域的,这也是我们在页面上可以引入在线的<code>jQuery</code>等资源的原因。所以,我们可以利用这个<code>script</code>标签的这个特性来进行跨域请求。</em></p><h5 id="2-1-script标签的src属性"><a href="#2-1-script标签的src属性" class="headerlink" title="2.1 script标签的src属性"></a>2.1 script标签的src属性</h5><p>我们必须清楚的认识到以下3点</p><ul><li><code>script</code>中包含的必须可执行的<code>JavaScript</code>代码</li><li>服务端必须返回<code>JavaScript</code>代码</li><li>必须要有动态创建的<code>script</code>标签。因为请求是动态的,我们需要根据请求动态生成<code>script</code>标签</li></ul><h5 id="2-1-JSONP实现思路"><a href="#2-1-JSONP实现思路" class="headerlink" title="2.1 JSONP实现思路"></a>2.1 JSONP实现思路</h5><ol><li>在客户端定义一个函数。</li><li>在服务端返回第一步定义函数的调用给客户端(服务端无法调用,只是将函数的调用作为字符串返回)。</li><li>客户端接收到该函数调用,然后执行。<br><strong>补充: 1. 在上述第第二步中,服务端将真正想返回的数据放在函数的实参中。在客户端调用时,就可以拿到对应的数据</strong><br> <strong>2. 我们定义的函数必须是全局的,这样服务端返回后,我们才能正常调用。因为服务端返回的函数调用是全局下的</strong></li></ol><h5 id="2-1-JSONP请求封装"><a href="#2-1-JSONP请求封装" class="headerlink" title="2.1 JSONP请求封装"></a>2.1 JSONP请求封装</h5><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 参数说明</span><br><span class="hljs-comment">// 调用JSONP请求传递的用户需要传递</span><br>options = {<br><span class="hljs-attr">url</span>: 请求地址,<br><span class="hljs-comment">// 用户自定义参数</span><br><span class="hljs-attr">params</span>: {<span class="hljs-attr">key</span>: value},<br><span class="hljs-comment">// 用户请求成功回调函数</span><br><span class="hljs-attr">succss</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {}<br>}<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">jsonp</span>(<span class="hljs-params">options</span>) {<br> <span class="hljs-comment">// 获得页面body标签</span><br> <span class="hljs-keyword">let</span> body = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementsByTagName</span>(<span class="hljs-string">'body'</span>)[<span class="hljs-number">0</span>];<br> <span class="hljs-comment">// 动态创建script标签</span><br> <span class="hljs-keyword">let</span> script = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'script'</span>);<br> <span class="hljs-comment">// 用户请求的地址</span><br> script.<span class="hljs-property">src</span> = options.<span class="hljs-property">url</span>;<br> <span class="hljs-comment">// 生成一个随机数,我们想将函数的名称发送给服务端,并要求服务端返回同名函数。因为我们在客户端上将定义该函数。因此,要保证函数名不一致,所以使用随机数</span><br> <span class="hljs-keyword">let</span> random = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>().<span class="hljs-title function_">toString</span>().<span class="hljs-title function_">replace</span>(<span class="hljs-string">'.'</span>, <span class="hljs-string">''</span>);<br> <span class="hljs-comment">// 随机函数名</span><br> <span class="hljs-keyword">let</span> fn = (<span class="hljs-string">'jsonp'</span> + random);<br> <span class="hljs-comment">// 必须将fn挂在在全局下,否则服务端返回函数无法执行。</span><br> <span class="hljs-variable language_">window</span>[fn] = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {};<br> <span class="hljs-comment">// 将用户调用的成功的函数体赋值给全局下的函数</span><br> <span class="hljs-variable language_">window</span>[fn] = options.<span class="hljs-property">success</span>;<br> <span class="hljs-comment">// 真正的请求地址</span><br> script.<span class="hljs-property">src</span> += <span class="hljs-string">`?callback=<span class="hljs-subst">${fn}</span>`</span>;<br> <span class="hljs-comment">// 用户传递了自定义参数</span><br> <span class="hljs-keyword">if</span> (options.<span class="hljs-property">params</span>) {<br> <span class="hljs-keyword">let</span> paramStr = <span class="hljs-string">''</span>;<br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">keys</span>(options.<span class="hljs-property">params</span>).<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item</span>) =></span> {<br> paramStr += (<span class="hljs-string">'&'</span> + item + <span class="hljs-string">'='</span> + options.<span class="hljs-property">params</span>[item]);<br> })<br> script.<span class="hljs-property">src</span> += paramStr;<br> }<br> <span class="hljs-comment">// 动态生成script</span><br> body.<span class="hljs-title function_">appendChild</span>(script);<br> <span class="hljs-comment">// script 成功请求后将script从DOM中删除</span><br> script.<span class="hljs-property">onload</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> body.<span class="hljs-title function_">removeChild</span>(script);<br> }<br>}<br></code></pre></td></tr></table></figure><h5 id="1-3-JSONP客户端实现代码"><a href="#1-3-JSONP客户端实现代码" class="headerlink" title="1.3 JSONP客户端实现代码"></a>1.3 JSONP客户端实现代码</h5><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 请求url中的参数</span><br> <span class="hljs-keyword">let</span> urlParams = req.<span class="hljs-property">url</span>.<span class="hljs-title function_">substring</span>(req.<span class="hljs-property">url</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-string">'?'</span>) + <span class="hljs-number">1</span>);<br> <span class="hljs-comment">// 对请求参数切割</span><br> <span class="hljs-keyword">let</span> paramsArr = urlParams.<span class="hljs-title function_">split</span>(<span class="hljs-string">'&'</span>);<br> <span class="hljs-comment">// 请求参数对象</span><br> <span class="hljs-keyword">let</span> paramsObj = {};<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = paramsArr.<span class="hljs-property">length</span>; i--; i < <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">let</span> temp = paramsArr[i].<span class="hljs-title function_">split</span>(<span class="hljs-string">'='</span>);<br> paramsObj[temp[<span class="hljs-number">0</span>]] = temp[<span class="hljs-number">1</span>];<br> }<br> <span class="hljs-comment">// 返回的数据</span><br> <span class="hljs-keyword">let</span> data = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ <span class="hljs-attr">name</span>: <span class="hljs-string">"wuxue"</span> });<br> <span class="hljs-comment">// TODO 浏览器只能接受字符串</span><br><span class="hljs-comment">// 返回给客户端的内容</span><br> <span class="hljs-keyword">let</span> resFn = <span class="hljs-string">`<span class="hljs-subst">${paramsObj[<span class="hljs-string">'callback'</span>]}</span>(<span class="hljs-subst">${data}</span>)`</span>;<br> res.<span class="hljs-title function_">send</span>(resFn);<br></code></pre></td></tr></table></figure><p><em>如上,简单的实现了JSONP请求,解决了跨域的问题。跨域方案的解决,有很多种方式。JSONP只是其中最古老,并且受限比较多的一种。JSONP主要的问题如下</em></p><ul><li><p>只支持GET请求方式</p></li><li><p>不够优雅,有些繁琐</p></li><li><p>非种正统的方式</p></li></ul><p>参考资料</p><blockquote><p>阮一峰-<a href="http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html">浏览器同源政策及其规避方法</a><br>阮一峰-<a href="http://www.ruanyifeng.com/blog/2016/04/cors.html">跨域资源共享CORS</a></p></blockquote>]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>Git命令</title>
<link href="/2018/12/04/Git%E5%85%A5%E9%97%A8%E4%B8%8E%E5%91%BD%E4%BB%A4/"/>
<url>/2018/12/04/Git%E5%85%A5%E9%97%A8%E4%B8%8E%E5%91%BD%E4%BB%A4/</url>
<content type="html"><![CDATA[<p><em>Git是一个版本控制系统,已经成为程序员必备的,必会的的工作软件。还没有入门的小伙伴,推荐大家学习廖雪峰老师的<a href="https://www.liaoxuefeng.com/wiki/896043488029600">Git教程</a>,该教程非常适合新入门的同学学习,通俗易懂。因为基础的概念已经清楚了,但是对于常用的命令还没有很熟练,做一个常用命令的整理</em></p><h3 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h3><ol><li>添加远程仓库(本质就是为远程仓库添加一个别名,方便以后操作)<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git remote add origin(仓库别名) ([email protected]:askwuxue/Users.git)远程仓库地址<br></code></pre></td></tr></table></figure></li><li>查看远程仓库<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git remote -v <br></code></pre></td></tr></table></figure></li><li>删除远程仓库<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git remote rm origin<br></code></pre></td></tr></table></figure></li><li>新建并切换分支<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git checkout -b dev(新分支名)<br></code></pre></td></tr></table></figure></li><li>切换分支<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git branch dev(要切换的分支名)<br></code></pre></td></tr></table></figure></li><li>删除分支<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git branch -d dev(要删除的分支名)<br></code></pre></td></tr></table></figure></li><li>查看所有标签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git tag<br></code></pre></td></tr></table></figure></li><li>查看某标签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git show <tagName><br></code></pre></td></tr></table></figure></li><li>新建标签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git tag <tagName> <commitId><br></code></pre></td></tr></table></figure></li><li>新建标签并添加描述<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gut">git tag -a <tagName> -m "tag message" <commitId><br></code></pre></td></tr></table></figure></li><li>推送至远程库 <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs git">// 将分支内容推送至远程库,远程库就是github仓库地址的别名。-u是记住本次的对应关系,后续可直接使用 git push<br>git push -u origin(远程仓库) master(本地分支)<br></code></pre></td></tr></table></figure></li><li>撤销暂存区修改 <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs git">git checkout --filename <br></code></pre></td></tr></table></figure></li><li>版本回退 <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs git"> // 第一步查看要回退到哪一个版本<br> git log // 查看提交历史<br> git log --pretty=oneline // 查看提交历史的主要信息,比git log信息简洁<br> <br>// 第二步<br>git reset --hard commitID // commitId 在查看版本历史的时候,会清楚的显示<br></code></pre></td></tr></table></figure></li></ol><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><ol><li>解决冲突<br> <em>冲突产生:一个文件,没有在同一个版本的基础上顺序修改就产生了冲突。如:A,B 拿了同一个版本的文件进行编辑,A编辑好了,并且push了。B随后编辑好了,也要push。但是这个时候A已经进行过编辑,B又编辑,就产生了冲突。本质是没有拿同一个版本的文件进行编辑。所以在正常情况下,最好拿最新版本进行编辑,可以有效避免冲突。如果冲突已经产生,课按照下面的方式进行处理</em><br> 1.1 手动解决冲突,在merge的时候,Git会告诉我们有冲突,如下<br> <img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/df50bfba-476f-46f2-a1e8-cbd4e4bfb0c0" alt="conflict"></li></ol><p> Git 告诉我们有冲突,需要我们手动进行合并。合并后如下</p><p> <img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/1beb3c51-181e-42e4-bcd2-3cc2c8b3175a" alt="resolve_conflict"></p><p>手动合并结束,接下来,只需要像处理普通文件<code>git add</code>,<code>git commit</code>进行处理即可,至此,冲突解决完毕。</p>]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>JavaScript中各种for循环的使用场景以及性能</title>
<link href="/2018/11/02/JavaScript%E4%B8%AD%E5%90%84%E7%A7%8Dfor%E5%BE%AA%E7%8E%AF%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E4%BB%A5%E5%8F%8A%E6%80%A7%E8%83%BD/"/>
<url>/2018/11/02/JavaScript%E4%B8%AD%E5%90%84%E7%A7%8Dfor%E5%BE%AA%E7%8E%AF%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E4%BB%A5%E5%8F%8A%E6%80%A7%E8%83%BD/</url>
<content type="html"><![CDATA[<h3 id="JavaScript中各种for循环的使用场景以及性能"><a href="#JavaScript中各种for循环的使用场景以及性能" class="headerlink" title="JavaScript中各种for循环的使用场景以及性能"></a>JavaScript中各种for循环的使用场景以及性能</h3><p><em>JavaScript中的<code>for</code>循环,<code>forEach</code>循环,<code>for...in</code>循环,<code>for...of</code>循环。你能清楚的区分在哪些场景下应该那种循环方式吗?让我们来一起看一下。今天在掘金看到一篇文章,自己也算是有了一丢丢的收获,应该可以写一写。</em></p><h4 id="1-运行速度"><a href="#1-运行速度" class="headerlink" title="1. 运行速度"></a>1. 运行速度</h4><p>请看下面的测试代码,比较了在同样条件下,<code>forEach</code>循环,<code>for...in</code>循环,<code>for...of</code>循环的耗时情况。<br><strong>1.1 创造一个有1000000个元素的数组</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><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><code class="hljs ts"><span class="hljs-keyword">let</span> arr = <span class="hljs-title class_">Array</span>(<span class="hljs-number">1000000</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = arr.<span class="hljs-property">length</span>; i--; i) {<br> arr[i] = i;<br>}<br></code></pre></td></tr></table></figure><p><strong>1.2 <code>forEach</code>循环,<code>for...in</code>循环,<code>for...of</code>循环的耗时</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">'for'</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = arr.<span class="hljs-property">length</span>; i >= <span class="hljs-number">0</span>; i--) {}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">'for'</span>);<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">'forEach'</span>);<br>arr.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">v</span>) =></span> v)<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">'forEach'</span>);<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">'for-of'</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">of</span> arr) {}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">'for-of'</span>);<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">'for-in'</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> arr) {}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">'for-in'</span>);<br><br><span class="hljs-comment">// 运行时间</span><br><span class="hljs-comment">// for: 4.077ms</span><br><span class="hljs-comment">// forEach: 26.605ms</span><br><span class="hljs-comment">// for-of: 53.678ms</span><br><span class="hljs-comment">// for-in: 533.47ms</span><br></code></pre></td></tr></table></figure><p> _上述的结果,在不同的环境下。可能会存在不同,但是整体趋势不变,速度最快的是for循环,然后依次是<code>ForEach</code>,<code>for of</code>,<code>for in</code>_。<br><strong>注意:请注意上述1.1,使用Array()生成数组后又将数组的内容进行了重新赋值。因为Array(1000000)生成的是具有1000000空位的数组。对于数组内的空位,循环的处理不同。<code>ForEach</code>会跳过空位,<code>for of</code>依旧会遍历空位。因此,如果数组内全部都是空位,会发现<code>forEach</code>速度最快。具体可以参考阮一峰老师的<a href="https://es6.ruanyifeng.com/#docs/array">ES6教程</a></strong></p><p><strong>1.3 倒序的for循环可能会更快一些</strong></p><p>来看两种for循环的不同写法。</p><p><strong>正序</strong></p><figure class="highlight ts"><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><code class="hljs ts"><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">'for-asc'</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < arr.<span class="hljs-property">length</span>; i++) {}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">'for-asc'</span>);<br></code></pre></td></tr></table></figure><p><strong>倒序</strong></p><figure class="highlight ts"><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><code class="hljs ts"><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">'for-desc'</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = arr.<span class="hljs-property">length</span>; i >= <span class="hljs-number">0</span>; i--) {}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">'for-desc'</span>);<br></code></pre></td></tr></table></figure><p> <em>这两种方式的差别,是for循环的中<code>**condition**</code>的差别,正序的方式,每一次都要获取数组的长度,然后和<code>i</code>进行大小比较。倒序的方式只需要获取一次数组的长度。因此,数据量巨大时。倒序的方式可能更快一点。当然,将数组的长度赋值给一个变量。每一次循环只需要获取一次这个变量的值是最优雅的写法。也就不存在正序块还是倒序快的说法了。通过这个小知识点,其实想表达的是,写代码真的有很多可以推敲的地方。优化代码的地方,这些我们容易形成思维定式的地方。藏着太多的知识点和</em></p><p>这个地方是倒序,比for循环的正序要快,因为倒序只需要取一次arr.length</p><h4 id="2-for循环,for-in循环,for-of循环,forEach循环的差异与使用场景"><a href="#2-for循环,for-in循环,for-of循环,forEach循环的差异与使用场景" class="headerlink" title="2. for循环,for in循环,for of循环,forEach循环的差异与使用场景"></a>2. <code>for循环</code>,<code>for in循环</code>,<code>for of循环</code>,<code>forEach循环</code>的差异与使用场景</h4><p> for循环的可读性较差,应该是指循环数组和对象的时候,会有一定的不优雅,产生了其他的几种for循环的变形。这些变形,除了上述,我们测试的效率不同之外,场景和方法也略有不同。</p><p><strong>2.1 <code>ForEach</code></strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 接受一个回调函数,可以对数组的每一项进行处理。</span><br>arr.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">item, index, arr</span>) =></span> {})<br></code></pre></td></tr></table></figure><p><strong><code>forEach</code>不能像for循环一样使用brake循环跳出当前循环。目前的唯一方法是抛出异常跳出循环,这应该不是我们想要的。</strong></p><p><strong>2.2 <code>for...in</code></strong></p><figure class="highlight ts"><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><code class="hljs ts"><span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> target) {<br> <span class="hljs-comment">// 做些什么</span><br>}<br></code></pre></td></tr></table></figure><p><code>for in</code> 以任意顺序遍历一个对象的除了Symbol以外的可枚举属性,会遍历原型上的属性。因为,最好不要在数组中使用,一般遍历数组,我们都希望按照索引输出。<code>for in </code>会在意料之外</p><p><strong>2.3 ``for of</strong></p><figure class="highlight ts"><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><code class="hljs ts"><span class="hljs-keyword">for</span> (variable <span class="hljs-keyword">of</span> iterable) {<br> <span class="hljs-comment">//statements</span><br>}<br></code></pre></td></tr></table></figure><p>for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句</p><p><strong><code>for of </code>只在可迭代的对象上才可以执行,普通对象不是一个可迭代对象,所以不可以使用<code>for of</code></strong></p><p><strong>根据<code>for循环</code>,<code>for in循环</code>,<code>for of循环</code>,<code>forEach循环</code>的不同的特性,在不同的场景,选择最合适的方式才是最优解。</strong></p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>改变函数this指向的apply()、call()、bind() 方法</title>
<link href="/2018/10/19/%E6%94%B9%E5%8F%98%E5%87%BD%E6%95%B0this%E6%8C%87%E5%90%91%E7%9A%84apply()%E3%80%81call()%E3%80%81bind()-%E6%96%B9%E6%B3%95/"/>
<url>/2018/10/19/%E6%94%B9%E5%8F%98%E5%87%BD%E6%95%B0this%E6%8C%87%E5%90%91%E7%9A%84apply()%E3%80%81call()%E3%80%81bind()-%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<p><em>不同函数运行时会存在不同的this指向,总是指向调用函数的对象。有些时候,我们希望通过改变函数的this指向来实现某些功能。常见的如继承等等</em></p><h3 id="函数的this指向"><a href="#函数的this指向" class="headerlink" title="函数的this指向"></a>函数的this指向</h3><p> 根据函数的调用方式可以分为以下几种,它们分别有各自的this指向。<br><img src="/img/%E6%94%B9%E5%8F%98%E5%87%BD%E6%95%B0this/%E5%87%BD%E6%95%B0%E7%9A%84this%E6%8C%87%E5%90%91.png"></p><h4 id="1-使用call-改变函数的this指向"><a href="#1-使用call-改变函数的this指向" class="headerlink" title="1. 使用call()改变函数的this指向"></a>1. 使用call()改变函数的this指向</h4><p>1.1 基本语法<br>使用<code>fn.call(object, parm1, parm2....)</code>会立即调用<code>fn</code>, 第一个参数是要给<code>fn</code>绑定的对象,也就是要将<code>fn</code>的this指向更改为对应的对象,从第二个参数起,是传给<code>fn</code>的参数,参数的长度不限,参数之间使用空格分开。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>;<br><span class="hljs-keyword">const</span> obj = {<br><span class="hljs-attr">a</span>: <span class="hljs-number">3</span>,<br><span class="hljs-attr">b</span>: <span class="hljs-number">4</span><br>}<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">fn</span>(<span class="hljs-params">a, b</span>) {<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> + <span class="hljs-variable language_">this</span>.<span class="hljs-property">b</span>);<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a + b);<br>}<br><span class="hljs-title function_">fn</span>(<span class="hljs-number">5</span>, <span class="hljs-number">6</span>); <span class="hljs-comment">// 此时的this指向的是Window 所以输出的结果是 3、11</span><br>fn.<span class="hljs-title function_">call</span>(obj, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>); <span class="hljs-comment">//此时的this指向了obj, 所以输出的结果是 7、15</span><br></code></pre></td></tr></table></figure><h4 id="2-使用apply-改变函数的this指向"><a href="#2-使用apply-改变函数的this指向" class="headerlink" title="2. 使用apply()改变函数的this指向"></a>2. 使用apply()改变函数的this指向</h4><p>2.1 基本语法<br><code>fn.apply(object, arr)</code>, 和<code>call</code>非常类似,也会立即调用<code>fn</code>, 也会更改<code>fn</code>的this指向为接受到的第一个参数。不同之处在于<code>apply()</code>只接受两个参数,第二个参数是一个数组,将一个数组作为参数传递给<code>fn</code>。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>;<br><span class="hljs-keyword">const</span> obj = {<br><span class="hljs-attr">a</span>: <span class="hljs-number">3</span>,<br><span class="hljs-attr">b</span>: <span class="hljs-number">4</span><br>}<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">fn</span>(<span class="hljs-params">a, b</span>) {<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> + <span class="hljs-variable language_">this</span>.<span class="hljs-property">b</span>);<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a + b);<br>}<br><span class="hljs-title function_">fn</span>(<span class="hljs-number">5</span>, <span class="hljs-number">6</span>); <span class="hljs-comment">// 此时的this指向的是Window 所以输出的结果是 3、11</span><br>fn.<span class="hljs-title function_">call</span>(obj, [<span class="hljs-number">7</span>, <span class="hljs-number">8</span>]); <span class="hljs-comment">//此时的this指向了obj, 所以输出的结果是 7、15, 需要将需要的参数按照数组的方式进行传递</span><br></code></pre></td></tr></table></figure><h4 id="3-使用bind-改变函数的this指向"><a href="#3-使用bind-改变函数的this指向" class="headerlink" title="3. 使用bind() 改变函数的this指向"></a>3. 使用bind() 改变函数的this指向</h4><p>3.1 基本语法</p><p><code>fn.bind(object, para1, para2...)</code>,在形式上和<code>fn.call()</code>非常相似,传递的参数是一模一样的,唯一的区别是<code>fn.bind()方法使用时</code>,不会立即调用<code>fn</code>.上面的另外两个方法<code>fn.apply()</code>和<code>fn.call()</code>会在使用时立即调用<code>fn</code>函数。使用<code>bind()</code>返回的是原函数改变this之后产生的新函数。</p><p>使用场景</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs javscript">// 下面是一个使用场景 <br>function LateBloomer() {<br> this.petalCount = Math.ceil(Math.random() * 12) + 1;<br>}<br><br>// 在 1 秒钟后声明 bloom 回调函数的this指向的是window<br>// 如果这个地方换成apply或者bind,也能改变this的指向,但是函数会立即执行,<br>// setTimeout设置的时间间隔会失效<br>LateBloomer.prototype.bloom = function() {<br> window.setTimeout(this.declare.bind(this), 1000);<br>};<br><br>LateBloomer.prototype.declare = function() {<br> console.log('I am a beautiful flower with ' + this.petalCount +'petals!');<br>};<br><br>var flower = new LateBloomer();<br>flower.bloom(); // 一秒钟后, 调用 'declare' 方法<br></code></pre></td></tr></table></figure><p><strong>总结</strong>:存在可能合理,不同的使用场景下,选择不同的方法实现效果。达到目的即可,关于更深层的理解,以后有了深入理解会补充。</p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>闭包到底是什么</title>
<link href="/2018/08/20/%E9%97%AD%E5%8C%85%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88/"/>
<url>/2018/08/20/%E9%97%AD%E5%8C%85%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88/</url>
<content type="html"><![CDATA[<h3 id="闭包到底什么"><a href="#闭包到底什么" class="headerlink" title="闭包到底什么"></a>闭包到底什么</h3><h4 id="1-定义"><a href="#1-定义" class="headerlink" title="1. 定义"></a>1. 定义</h4><blockquote><p>JavaScript高级程序语言设计: <em>闭包是指有权访问另一个函数作用域中变量的函数</em><br>根据这个定义,我们去分析<br><strong>闭包是什么?</strong> <code>一个函数</code><br><strong>闭包是什么函数?</strong><code>一个可以访问其他函数作用域中变量的函数</code><br>我们仔细分析其中的关键字,<code>函数</code>、<code>作用域</code>、<code>变量</code></p></blockquote><h5 id="1-1-深入分析函数"><a href="#1-1-深入分析函数" class="headerlink" title="1.1 深入分析函数"></a>1.1 深入分析函数</h5><p> <code>JavaScript</code>在<code>ES6</code>前是没有块级作用域的。但是有函数作用域,类似块级作用域,在函数内部使用<code>var</code>关键字声明一个变量。<strong>直接</strong>在函数外部访问函数内部的变量是访问不到的。<br><strong>注意: 函数内部的变量必须使用<code>var</code>关键字声明,如果不使用var关键字声明。在函数外部是可以直接使用这个变量的,因为这时,函数内部的这个变量是全局变量。如下,在函数内部有一个局部变量和一个全局变量</strong></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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">fun</span>(<span class="hljs-params"></span>) {<br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<span class="hljs-comment">// 局部变量</span><br>b = <span class="hljs-number">2</span>;<span class="hljs-comment">// 全局变量</span><br>}<br></code></pre></td></tr></table></figure><p>上面说到,在函数外部不能直接访问函数内的局部变量。反过来不然,在函数内部,是可以访问到全局变量的。这个时候需要涉及到作用域的相关知识。</p><h6 id="1-1-1-作用域"><a href="#1-1-1-作用域" class="headerlink" title="1.1.1 作用域"></a>1.1.1 作用域</h6><blockquote><p><em>作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性</em><br> 简单来说,作用域就是你到底能不能访问到某些变量,函数,对象等。定义在全局上的,都是可以访问的。为什么呢?举个列子,全局可以理解称为一个所有人都可以访问的仓库,你需要一个东西,不需要和任何人打招呼你就可以进行拿,没人管你。<br> 如果你有自己的家,你需要什么,根据就近原则,先在自己的家里找,找不到了。你才会向东西更多,更全的地方去找。类似于很多个同心圆,总是从最内层一直向外扩散的感觉。<br> 函数作用域,相当于和你没有交集的人家,是不会允许你平白无故去人家的家里拿东西。即便人家的家里有这件东西。请看</p></blockquote><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><code class="hljs javascript"><span class="hljs-keyword">var</span> b = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">father</span>(<span class="hljs-params"></span>) {<br><span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>;<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">son</span>(<span class="hljs-params"></span>) {<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(b); <span class="hljs-comment">// 2</span><br>}<br>}<br></code></pre></td></tr></table></figure><p>**** 作用域链**<br> 上面这段伪代码,<code>son()</code>函数要访问<code>b</code>变量,但是<code>son</code>函数内部没有b变量。就会向上面找,为什么向上面找,因为<code>son</code>函数是在<code>father</code>函数内部的,它们存在包含关系。<code>son</code>函数可以向上寻找。找到了<code>b</code>变量,取到了值。就不会继续找了。假如,<code>father</code>函数内部没有<code>b</code>变量呢?那么就会在<code>father</code>函数的上一层找,<code>father</code>的上一层是全局变量,全局变量中定义了<code>b</code>变量,所以也可以找到,只不过这个时候的<code>b</code>的值为1。这样一层层向上寻找的过程就形成了一条作用域链。</p><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">f1</span>(<span class="hljs-params"></span>) {<br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br>}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a) <span class="hljs-comment">// 想要访问f1内部的变量a。</span><br></code></pre></td></tr></table></figure><p> 上面的代码,想要在函数外部使用函数内部的变量。但是由于函数内部作用域的限制,无法实现。人类总是有办法解决。请向下看。</p><h6 id="1-1-2-函数返回值功不可灭"><a href="#1-1-2-函数返回值功不可灭" class="headerlink" title="1.1.2 函数返回值功不可灭"></a>1.1.2 函数返回值功不可灭</h6><p> <code>JavaScript</code>的函数返回值可以返回任何类型的值。所以,返回值是一个函数也没有什么奇怪的。因此,<code>result</code>接收到的是一个没有调用的函数。<code>result()</code>调用后,我们在全局环境中通过了<code>f2函数</code>间接的拿到了<code>f1函数</code>内部的值。<code>f2函数</code>就是一个闭包。回过头看闭包的定义就变得一目了然。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">f1</span>(<span class="hljs-params"></span>) {<br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">function</span> <span class="hljs-title function_">f2</span>(<span class="hljs-params"></span>) {<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a);<br>}<br><span class="hljs-keyword">return</span> f2;<br>}<br><span class="hljs-keyword">var</span> result = <span class="hljs-title function_">f1</span>();<span class="hljs-comment">// f2() { console.log(a); }</span><br><span class="hljs-title function_">result</span>();<span class="hljs-comment">// 1</span><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(a) <span class="hljs-comment">// 想要访问f1内部的变量a。</span><br></code></pre></td></tr></table></figure><blockquote><p>阮一峰: 在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。 </p></blockquote><h4 id="闭包的作用"><a href="#闭包的作用" class="headerlink" title="闭包的作用"></a>闭包的作用</h4><ol><li>可以访问函数内部的变量。</li><li>可以让变量一直存在内存中。(可以参考阮一峰老师的例子)如下所示:<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><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">f1</span>(<span class="hljs-params"></span>){<br> <span class="hljs-keyword">var</span> n = <span class="hljs-number">999</span>;<br> nAdd = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){ <br> n += <span class="hljs-number">1</span><br> }<br> <span class="hljs-keyword">function</span> <span class="hljs-title function_">f2</span>(<span class="hljs-params"></span>){<br> <span class="hljs-title function_">alert</span>(n);<br> }<br> <span class="hljs-keyword">return</span> f2;<br> }<br> <br> <span class="hljs-keyword">var</span> result=<span class="hljs-title function_">f1</span>();<br> <span class="hljs-title function_">result</span>(); <span class="hljs-comment">// 999</span><br> <span class="hljs-title function_">nAdd</span>();<br> <span class="hljs-title function_">result</span>(); <span class="hljs-comment">// 1000</span><br></code></pre></td></tr></table></figure>正常情况下,在函数内部定义的变量,当该函数被调用后,根据JavaScript的垃圾回收机制,函数内部的变量都会被垃圾回收器清除,释放内存。但是根据上述代码所示。<code>n</code>并没有被回收,也就是一直被保存在内存中。所以会造成内存消耗过大,甚至在某些浏览器会引起内存泄露的问题。所以需要谨慎使用闭包。</li></ol><p>总结: 个人觉得闭包在设计上就是一个缺陷,是因为语言特性所决定的。在ES6中有了<code>let</code>等新特性,出现了块级作用域。全局变量和内部变量界限分明或许是最好的解决方案。</p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>ES6新语法学习_第一回</title>
<link href="/2018/04/20/ES6%E6%96%B0%E8%AF%AD%E6%B3%95%E5%AD%A6%E4%B9%A0_%E7%AC%AC%E4%B8%80%E5%9B%9E/"/>
<url>/2018/04/20/ES6%E6%96%B0%E8%AF%AD%E6%B3%95%E5%AD%A6%E4%B9%A0_%E7%AC%AC%E4%B8%80%E5%9B%9E/</url>
<content type="html"><![CDATA[<p><em>随着学习的深入,真的发现<code>ECMAScript5</code>留下了蛮多坑的,还好,标准不断的更新,语言也会越来越好,简单看一下ES6新的语法,慢慢的更新吧。</em></p><h5 id="作用域的变化"><a href="#作用域的变化" class="headerlink" title="作用域的变化"></a>作用域的变化</h5><p><strong>let</strong><br><em>1. 引入块级作用域,let产生产生暂时性死区,取消变量提升。</em><br>阮一峰老师的教程中举了一个<code>for</code>循环的中用<code>var</code>定义的循环变量导致泄漏为全局变量的问题。</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><code class="hljs javascript"><span class="hljs-keyword">var</span> arr = [];<br><span class="hljs-title function_">var</span>(<span class="hljs-params"><span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++</span>) {<br>arr[i] = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(i);<br>}<br>}<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(arr[<span class="hljs-number">5</span>]); <span class="hljs-comment">// 10</span><br></code></pre></td></tr></table></figure><p> 我记得以前我大致遇到过同样的问题,但是并不知道是为什么,后来看了阮一峰老师的ES6,瞬间明白。<code>var</code>声明的循环变量,其实是一个全局变量。在循环体内部,输出<code>i</code>的值是没有问题的。但是除了循环体,这时候全局变量<code>i</code>的值已经是10了。其实是一个变量泄露为全局变量的问题。<br> 使用<code>let</code>声明的循环变量,是属于块级作用域的。不会泄露为全局变量,所以使用<code>let</code>声明,最后输出的结果是6。</p><p><em>2. 没有预解析,必须先声明使用,同一个作用域不能重复定义。</em></p><p><strong><code>const</code>常量</strong><br> <code>const</code>声明一个常量,实际不是一个变量的值不变,而是指向那个变量的内存地址不变。其实主要是约束引用类型的。<br> 对于对象来讲,<code>const</code>约束了对象的内存地址不可变,也就是一个变量被声明,不能被重新赋值,这里的不能被重新赋值指的是,你不能对该变量的内存地址进行修改。如下</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> obj = {};<br>obj = {};<br></code></pre></td></tr></table></figure><p> 当一个变量被使用<code>const</code>声明,该变量的内存地址不可变,接下来,用另一个对象进行赋值(本质是把另一个对象的地址给<code>obj</code>变量)是不允许的。<br>对象是存在堆中的,如果直接对象本身修改是可以的。如下</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript">obj.<span class="hljs-property">a</span> = <span class="hljs-string">'xxx'</span>;<br></code></pre></td></tr></table></figure><p> 如果想要一个对象真的不可修改,可以使用<code>Objec.freeze()</code>这个方法,对象将会被冻结,关于该对象的一切都不可修改。包括该对象的原型。语法如下,<code>return</code>: 被冻结的对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-title class_">Object</span>.<span class="hljs-title function_">freeze</span>(对象)<br></code></pre></td></tr></table></figure><h5 id="解构赋值"><a href="#解构赋值" class="headerlink" title="解构赋值"></a>解构赋值</h5><p><em>结构赋值的本质是模式匹配,只要等号两边的模式相同,左边的变量就会被赋予对应的值。没有被赋值到的值默认是<code>undefined</code></em></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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 1. 基本语法</span><br><span class="hljs-keyword">let</span> [a, b, c] = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>];<br><span class="hljs-comment">// 2. 对象结构赋值</span><br><span class="hljs-keyword">let</span> json = {<span class="hljs-string">"name"</span>: <span class="hljs-string">"wuxue"</span>, <span class="hljs-string">"age"</span>: <span class="hljs-number">20</span>}<br><span class="hljs-keyword">let</span> {<span class="hljs-attr">name</span>:n, <span class="hljs-attr">age</span>:a} = json;<br><span class="hljs-comment">// 应用:交换两数,接受后台数据</span><br></code></pre></td></tr></table></figure><h5 id="字符串的查找"><a href="#字符串的查找" class="headerlink" title="字符串的查找"></a>字符串的查找</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><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><code class="hljs javascript"><span class="hljs-comment">// str是否包含某字符串 返回的是boolean</span><br><span class="hljs-number">1.</span> str.<span class="hljs-title function_">includes</span>(substr)<br><span class="hljs-comment">// 返回的是索引的位置</span><br><span class="hljs-number">2.</span> str.<span class="hljs-title function_">indexOf</span>(substr)<br><span class="hljs-comment">// 判断是否是某字符串开头/结束。返回值为Boolean</span><br><span class="hljs-number">3.</span> str.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'substr'</span>)/str.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">'substr'</span>)<br><span class="hljs-comment">// 重复谋和某个字符串,n 重复的次数,返回重复后的string</span><br><span class="hljs-number">4.</span> str.<span class="hljs-title function_">repeat</span>(n);<br><span class="hljs-comment">// 在字符串的头部填充字符串</span><br><span class="hljs-comment">// 如果规定的字符串的长度超出了,填充的字符串会被自动截取</span><br><span class="hljs-number">5.</span> str.<span class="hljs-title function_">padStart</span>(targetLength, padString)<br><span class="hljs-number">6.</span> str.<span class="hljs-title function_">padEnd</span>(padString, padString);<br></code></pre></td></tr></table></figure><h5 id="ES6新增的数组方法"><a href="#ES6新增的数组方法" class="headerlink" title="ES6新增的数组方法"></a>ES6新增的数组方法</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><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 第一个参数是回调函数 第二个参数是this指向</span><br><span class="hljs-comment">// 回调函数参数分别为 value(当前值)、index(当前索引)、arr(指向当前调用的arr)</span><br><span class="hljs-comment">// 箭头函数的话,无法改变this指向</span><br><br><span class="hljs-comment">// 加强的for循环</span><br><span class="hljs-number">1.</span> arr.<span class="hljs-title function_">forEach</span>()<br><span class="hljs-title function_">callback</span>(value,index,arr);<br><span class="hljs-comment">// 正常情况需要配合return 如果没有return 功能相当于foreach 返回值是一个数组,将元素组项变换</span><br><span class="hljs-comment">// 注意:只要是用map 一定要有return 语句</span><br><br><span class="hljs-number">2.</span> arr.<span class="hljs-title function_">map</span>()<br> <span class="hljs-title function_">callback</span>(item,index,arr);<br><br><span class="hljs-comment">// 如果filter 的回调函数 是true的项留下</span><br><span class="hljs-number">3.</span> arr.<span class="hljs-title function_">filter</span>()<br> <span class="hljs-title function_">callback</span>(<span class="hljs-params">item,index,arr</span>) {<br> <span class="hljs-keyword">return</span> condition;<br> };<br><br><span class="hljs-comment">// 某些项满足条件</span><br><span class="hljs-number">4.</span> arr.<span class="hljs-title function_">some</span>()<br><br><span class="hljs-comment">// 每一项满足条件</span><br><span class="hljs-number">5.</span> arr.<span class="hljs-title function_">every</span>()<br> <span class="hljs-title function_">callback</span>(<span class="hljs-params">item,index,arr</span>) {<br> <span class="hljs-keyword">return</span> condition;<br> }<br><span class="hljs-comment">// 默认prev指arr[0] cur为arr[1]</span><br><span class="hljs-number">6.</span> arr.<span class="hljs-title function_">reduce</span>()<br> <span class="hljs-title function_">callback</span>(<span class="hljs-params">prev,cur,index,arr</span>){<br> <span class="hljs-keyword">return</span> prev + cur;<br> }<br><br><span class="hljs-comment">// 从arr的右边向左边</span><br><span class="hljs-number">7.</span> arr.<span class="hljs-title function_">reduceRight</span>();<br></code></pre></td></tr></table></figure><h5 id="新增的对象方法"><a href="#新增的对象方法" class="headerlink" title="新增的对象方法"></a>新增的对象方法</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><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><code class="hljs javascript">简写写法<br><span class="hljs-number">1.</span> 属性值和属性一样时,可以只写其中一个<br><span class="hljs-comment">// 比较两个值是否相等 可以用来判断两个对象的是否有同一个引用</span><br><span class="hljs-number">2.</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">is</span>(a, b);<br><span class="hljs-comment">// Object.is(NaN,NaN)true</span><br><span class="hljs-comment">// Object.is(+0,-0)false</span><br><span class="hljs-comment">// 用来合并对象/复制复制数组/合并 后面的对象中的一致的会覆盖前面的</span><br><span class="hljs-number">5.</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">assign</span>(target,souse1,souse2...)<br></code></pre></td></tr></table></figure><h5 id="函数的新变化"><a href="#函数的新变化" class="headerlink" title="函数的新变化"></a>函数的新变化</h5><ol><li><p>可以有默认参数,参数可以解构</p></li><li><p>函数的参数默认是已经定义的,不能在函数中重复定义</p></li><li><p>拓展运算符,reset运算符 <code>...</code>展开数组 在函数参数中用拓展运算符接受多余参数 </p></li><li><p>箭头函数<code>let funName = (params)=> {}</code>; 如果只有一个<code>return</code>语句,大括号省略,<code> return</code>省略</p></li><li><p>箭头函数中的<code>this</code>是定义函数的时候所在的对象,不再是运行时所在对象。</p></li><li><p>箭头函数中没有<code>arguments</code></p></li><li><p>箭头函数不能当成构造函数</p></li></ol><h5 id="新增的数据结构"><a href="#新增的数据结构" class="headerlink" title="新增的数据结构"></a>新增的数据结构</h5><p><strong>1. Set数据结构</strong></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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//会将重复的值过滤掉,具有唯一性</span><br><span class="hljs-keyword">let</span> sets = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>([<span class="hljs-string">'a'</span>,<span class="hljs-string">'b'</span>,<span class="hljs-string">'c'</span>]);<br>sets.<span class="hljs-title function_">add</span>(<span class="hljs-string">'d'</span>);<span class="hljs-comment">// 添加一个元素</span><br>sets.<span class="hljs-title function_">delete</span>(<span class="hljs-string">'d'</span>);<span class="hljs-comment">// 删除一个元素</span><br>sets.<span class="hljs-title function_">has</span>(<span class="hljs-string">'d'</span>);<span class="hljs-comment">// false</span><br>sets.<span class="hljs-property">size</span><span class="hljs-comment">// 3 查看个数</span><br>sets.<span class="hljs-title function_">clear</span>()<span class="hljs-comment">// 清除所有</span><br><br><span class="hljs-comment">// 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值</span><br>set.<span class="hljs-title function_">value</span>();<br><span class="hljs-comment">// 与values()方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> item <span class="hljs-keyword">of</span> sets.<span class="hljs-title function_">keys</span>()) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item);<br>}<br><span class="hljs-comment">// 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。</span><br><span class="hljs-comment">// 为了使这个方法和Map对象保持相似, 每个值的键和值相等。</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> [k,v] <span class="hljs-keyword">of</span> sets.<span class="hljs-title function_">entries</span>())<br><br>sets.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">value,index</span>) =></span> {})<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>ES6</category>
</categories>
<tags>
<tag>ES6</tag>
</tags>
</entry>
<entry>
<title>块级格式化上下文</title>
<link href="/2018/04/17/%E5%9D%97%E7%BA%A7%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%B8%8A%E4%B8%8B%E6%96%87/"/>
<url>/2018/04/17/%E5%9D%97%E7%BA%A7%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%B8%8A%E4%B8%8B%E6%96%87/</url>
<content type="html"><![CDATA[<h4 id="格式化上下文"><a href="#格式化上下文" class="headerlink" title="格式化上下文"></a>格式化上下文</h4><p><em>在解决外边距叠加的时候,MDN给出了这个概念,触发 了BFC不会发生外边距叠加,今天来看一下,理解不是特别深刻,今天通过查阅资料,解决这个问题。</em></p><p><strong>MDN的定义</strong></p><p>块级格式化上下文(Block Formatting Context,BFC) 是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。</p><p>看了几个文档都没有太看明白,借鉴:前端精选文摘-BFC背后的原理一文,基本上为我们解释清楚了,我决定,结合自己的理解与大佬的文章巩固一下。</p><h5 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h5><p><strong>开始之前,需要了解下面三点。</strong></p><ol><li>box:(盒)布局的基本单位。<br> 元素的类型和display属性,决定了这个box的类型。不同类型的box,会参与不同的 Formatting Content (决定如何渲染文档的容器)。因此。box内的元素会以不同的方式渲染。</li><li>不同类型的盒子都参与哪些 Formatting Content<blockquote><p>**block-level box(块级盒) **<br><code>display</code>属性为:<code> block</code>、<code>list-item</code>、<code>table</code>的元素会生成<code>block-level box</code>。参与<code>block formatting content</code> (块级格式化上下文)</p></blockquote></li></ol><blockquote><p><strong>inline-level box (行内盒)</strong><br><code>display</code>属性为<code>inline</code>、<code>inline-block</code>、<code>inline-table</code>的元素生成<code>inline-level box</code>。参与<code>inline formatting content</code> (IFC)</p></blockquote><ol start="3"><li><strong>Formatting Content</strong><br> <code>Formatting content</code>是 W3C CSS2.1 规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。最常见的<code>Formatting context</code>有<code>Block fomatting context</code>(简称BFC)和<code>Inline formatting context</code>(简称IFC)。</li></ol><h5 id="什么是BFC"><a href="#什么是BFC" class="headerlink" title="什么是BFC"></a>什么是BFC</h5><p><em>BFC:块级格式化上下文(概念)。一个独立的渲染区域,只有块级以及盒子参与,并且规定了内部的块级盒子如何布局,并且和独立区域外部毫不相干。</em></p><p>BFC规定了内部块级的盒子如何布局,有一套BFC的布局规则。 如下</p><ol><li>内部的Box会在垂直方向,一个接一个地放置。</li><li>Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠</li><li>每个元素的margin box的左边,与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。</li><li>BFC的区域不会与float box重叠。</li><li>BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。</li><li>计算BFC的高度时,浮动元素也参与计算</li></ol><h5 id="如何形成BFC"><a href="#如何形成BFC" class="headerlink" title="如何形成BFC"></a>如何形成BFC</h5><ol><li>根元素或包含根元素的元素</li><li>浮动元素(元素的 float 不是 none)</li><li>绝对定位元素(元素的 position 为 absolute 或 fixed)</li><li>display为inline-block, table-cell, table-caption, flex, inline-flex和inline-grid元素的直接子元素</li><li>overflow 值不为 visible 的块元素</li></ol><h5 id="BFC的简单用法"><a href="#BFC的简单用法" class="headerlink" title="BFC的简单用法"></a>BFC的简单用法</h5><p>直接拿大佬的过来,觉得总结的太精辟了</p><h6 id="1-自适应两栏布局"><a href="#1-自适应两栏布局" class="headerlink" title="1. 自适应两栏布局"></a>1. 自适应两栏布局</h6><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs css"><style><br> <span class="hljs-selector-tag">body</span> {<br> <span class="hljs-attribute">width</span>: <span class="hljs-number">300px</span>;<br> <span class="hljs-attribute">position</span>: relative;<br> }<br> <br> <span class="hljs-selector-class">.aside</span> {<br> <span class="hljs-attribute">width</span>: <span class="hljs-number">100px</span>;<br> <span class="hljs-attribute">height</span>: <span class="hljs-number">150px</span>;<br> <span class="hljs-attribute">float</span>: left;<br> <span class="hljs-attribute">background</span>: <span class="hljs-number">#f66</span>;<br> }<br> <br> <span class="hljs-selector-class">.main</span> {<br> <span class="hljs-attribute">height</span>: <span class="hljs-number">200px</span>;<br> <span class="hljs-attribute">background</span>: <span class="hljs-number">#fcc</span>;<br> }<br></style><br><<span class="hljs-selector-tag">body</span>><br> <<span class="hljs-selector-tag">div</span> class="<span class="hljs-selector-tag">aside</span>"></<span class="hljs-selector-tag">div</span>><br> <<span class="hljs-selector-tag">div</span> class="<span class="hljs-selector-tag">main</span>"></<span class="hljs-selector-tag">div</span>><br></<span class="hljs-selector-tag">body</span>><br></code></pre></td></tr></table></figure><p>页面</p><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/5202f8b0-aa95-4234-9971-f0b30870c156" alt="BFC1"></p><p><strong>应用BFC的规则</strong></p><ol><li>每个元素的<code>margin box</code>的左边,与包含块<code>border box</code>的左边相接触(对于从左往右的格式,反之相反)。即使存在浮动也是。<br>此时,<code>body</code>元素的内部构成了一个<code>BFC</code><br>所以,即使<code>aslide</code>存在,<code>main</code>的左边依旧与包含块的左边相接触。</li><li><code>BFC</code>的区域不会与<code>float box</code>重叠<br>这时候,我们通过<code>main</code>生成<code>BFC</code><figure class="highlight css"><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><code class="hljs css"><span class="hljs-selector-class">.main</span> {<br> <span class="hljs-attribute">overflow</span>: hidden;<br>}<br></code></pre></td></tr></table></figure>这之后,新的BFC不会与浮动的aside 重叠。因此,根据包含块的大小,和aside的宽度类调整自身的宽度。此时main不可设置宽度。</li></ol><p>如下:</p><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/b858bd0b-509e-4599-9b04-8fea14aa1c0f" alt="BFC2"></p><h6 id="2-清除内部的浮动"><a href="#2-清除内部的浮动" class="headerlink" title="2. 清除内部的浮动"></a>2. 清除内部的浮动</h6><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs css"><style><br> <span class="hljs-selector-class">.par</span> {<br> <span class="hljs-attribute">border</span>: <span class="hljs-number">5px</span> solid <span class="hljs-number">#fcc</span>;<br> <span class="hljs-attribute">width</span>: <span class="hljs-number">300px</span>;<br> }<br> <br> <span class="hljs-selector-class">.child</span> {<br> <span class="hljs-attribute">border</span>: <span class="hljs-number">5px</span> solid <span class="hljs-number">#f66</span>;<br> <span class="hljs-attribute">width</span>:<span class="hljs-number">100px</span>;<br> <span class="hljs-attribute">height</span>: <span class="hljs-number">100px</span>;<br> <span class="hljs-attribute">float</span>: left;<br> }<br></style><br><<span class="hljs-selector-tag">body</span>><br> <<span class="hljs-selector-tag">div</span> class="par"><br> <<span class="hljs-selector-tag">div</span> class="child"></<span class="hljs-selector-tag">div</span>><br> <<span class="hljs-selector-tag">div</span> class="child"></<span class="hljs-selector-tag">div</span>><br> </<span class="hljs-selector-tag">div</span>><br></<span class="hljs-selector-tag">body</span>><br><br>在这里,应用 计算BFC的高度时,浮动元素也参与计算设置 <br><span class="hljs-selector-class">.par</span> {<br><span class="hljs-attribute">overflow</span>: hidden;<br>}<br>BFC 会被浮动的元素撑开。清除浮动<br></code></pre></td></tr></table></figure><p>效果图如下:</p><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/acccebd3-9d09-4451-858d-69cf316af9c4" alt="BFC3"></p><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/c41be58b-ff56-4c22-bacb-8f8659c14107" alt="BFC4"></p><h6 id="3-防止margin-重叠"><a href="#3-防止margin-重叠" class="headerlink" title="3. 防止margin 重叠"></a>3. 防止margin 重叠</h6><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs css"><style><br> <span class="hljs-selector-tag">p</span> {<br> <span class="hljs-attribute">color</span>: <span class="hljs-number">#f55</span>;<br> <span class="hljs-attribute">background</span>: <span class="hljs-number">#fcc</span>;<br> <span class="hljs-attribute">width</span>: <span class="hljs-number">200px</span>;<br> <span class="hljs-attribute">line-height</span>: <span class="hljs-number">100px</span>;<br> <span class="hljs-attribute">text-align</span>:center;<br> <span class="hljs-attribute">margin</span>: <span class="hljs-number">100px</span>;<br> }<br></style><br><<span class="hljs-selector-tag">body</span>><br> <<span class="hljs-selector-tag">p</span>>Haha</<span class="hljs-selector-tag">p</span>><br> <<span class="hljs-selector-tag">p</span>>Hehe</<span class="hljs-selector-tag">p</span>><br></<span class="hljs-selector-tag">body</span>><br></code></pre></td></tr></table></figure><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/f4acb658-6aae-44dd-a136-8825706f74a1" alt="BFC5"></p><p><strong>Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠</strong></p><p>两个P都在body构成的一个BFC中,可以将任意的一个p元素包裹在div标签中,通过div去触发另一个BFC。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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><code class="hljs css"><style><br> <span class="hljs-selector-class">.wrap</span> {<br> <span class="hljs-attribute">overflow</span>: hidden;<br> }<br></style><br><<span class="hljs-selector-tag">div</span> class="wrap"><br> <<span class="hljs-selector-tag">p</span>>Haha</<span class="hljs-selector-tag">p</span>><br></<span class="hljs-selector-tag">div</span>><br><<span class="hljs-selector-tag">p</span>>Hehe</<span class="hljs-selector-tag">p</span>>><br></code></pre></td></tr></table></figure><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/06126c11-324c-400c-9d61-c60f239581c9" alt="BFC6"></p><p><strong>总结:</strong>BFC是CSS的精髓所在,只有深刻的理解BFC,然后加以实践,在布局方面在能更加的熟练。菜鸡一枚,继续努力呀。</p>]]></content>
<categories>
<category>CSS</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title>ajax的原理简单分析</title>
<link href="/2018/04/15/ajax%E5%8E%9F%E7%90%86%E7%AE%80%E5%8D%95%E5%88%86%E6%9E%90/"/>
<url>/2018/04/15/ajax%E5%8E%9F%E7%90%86%E7%AE%80%E5%8D%95%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h4 id="关于ajax"><a href="#关于ajax" class="headerlink" title="关于ajax"></a>关于ajax</h4><p>Ajax的出现是为了解决局部刷新的问题的。在web的远古时代,使用的方式是服务器渲染的技术。也就是说所有内容在服务端进行校验。对当时的web服务来说,这样是完全可行的。因为当时的web主要功能是图文浏览。随着时代的发展,需求的增加。我们需要在网站上完成一些工作。比如说:登录,注册等功能。这个时候,如果使用服务端渲染。用户在注册页面,必须填写完整所有的信息后,将页面发送至服务端进行校验。如果失败的话,用户之前填写的所有内容全部丢失,对用户来说非常不友好。此时,需要一种技术来解决这种困境。Ajax诞生了,可以不用将整个页面发送到服务端进行校验,可以将页面的局部内容发送至服务端进行处理。Ajax的诞生,才有了web的蓬勃发展与各种各样的新奇玩法。用户体验与web的性能有了质的提升。Ajax的主要作用总结如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">1. 实现了局部刷新,提升了用户体验。<br></code></pre></td></tr></table></figure><h5 id="ajax的实现原理"><a href="#ajax的实现原理" class="headerlink" title="ajax的实现原理"></a>ajax的实现原理</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><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><code class="hljs javascript"><span class="hljs-comment">// ajax 实现是依靠XMLHttpRequest对象实现的,IE浏览器下是ActiveXobject对象</span><br><span class="hljs-keyword">let</span> xhr = <span class="hljs-variable language_">window</span>.<span class="hljs-property">XMLHttpRequest</span> ? <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>() : <span class="hljs-keyword">new</span> <span class="hljs-title class_">ActiveXObject</span>(<span class="hljs-string">'Microsoft.XMLHTTP'</span>);<br><br><span class="hljs-comment">// 参数分别是请求的方式,请求地址,同步(默认true)</span><br>xhr.<span class="hljs-title function_">open</span>(method, url, <span class="hljs-literal">true</span>)<br><br><span class="hljs-comment">// 等待浏览器响应</span><br>xhr.<span class="hljs-title function_">send</span>();<br><br>xhr.<span class="hljs-property">onreadystatechange</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">readyState</span> != <span class="hljs-number">4</span>) <span class="hljs-keyword">return</span><br>}<br></code></pre></td></tr></table></figure><h6 id="实现详解"><a href="#实现详解" class="headerlink" title="实现详解"></a>实现详解</h6><p>第一点 <code>xhr</code>这个对象的状态<code>readyState</code><br>0 当xhr对象创建的时候<br>1 <code>open()</code>方法调用的时候<br>2 已经接受到了响应的响应头<br>3 正在下载响应体<br>4 整个响应报文下载完毕</p><p> 所以,大家看到定义的<code>onreadystatechange</code>方法,用来监听对象的状态变化,当<code>readyState</code>值为4,我们可以认为响应已经结束了。我们可以进行下来的处理了。虽然报文下载结束了,但是服务器一般有多种的情况,比如,我们常见的404,这是什么情况呢?其实,<code>xhr</code>对象上还有一个属性,<code>xhr.status</code>。这个属性值有很多,如下:</p><figure class="highlight http"><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><code class="hljs http">200——交易成功<br><br>404——没有发现文件、查询或URl<br></code></pre></td></tr></table></figure><p>还有很多状态码。文章末尾总结一下</p><p> 当浏览器返回http状态码的时候,我们可以利用<code>xhr.status</code>对响应的状态码进行获取然后处理。比如,ajax的时候,我们一般关心的是浏览器给我返回的是我想要的数据,那么这时候,对应的<code>xhr.status</code>是200,所以上述代码的最后一句的判断条件,在只关心服务器返回正常的情况下,是这样</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">readyState</span> == <span class="hljs-number">4</span> && <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> == <span class="hljs-number">200</span>) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">response</span>)<span class="hljs-comment">// 打印响应内容</span><br>}<br></code></pre></td></tr></table></figure><p> 第二点要解释的其实是<code>send()</code>方法,这个方法可以理解是向服务器请求数据的过程,我们说过了,服务器是一个响应的过程,想到了什么,没错,<code>send()</code>是一个异步的方法,加入不是异步的,我们<code>send()</code>下面的代码就一点意义也没有。个人认为,<code>ajax</code>的异步方式也是体现在这里。我们不用过怎么请求的,我们继续往下进行,到请求结束了,我们获取数据就行了。不会对下面的步骤产生影响。<br> 基本原理已经清楚了,但是还有小小的不妥。主要是post请求导致的,post,一般我们理解是发送。在网络中,除了发送数据本身(请求体)以外,还需要请求体。所以<br>send()方法通过post请求的时候,会带着请求体,<br>假如:现在要登录,我要发送给服务器的就是我的账户username和密码password,<code>send()</code>的格式就是</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-title function_">send</span>(<span class="hljs-string">`username=<span class="hljs-subst">${username}</span>&password=<span class="hljs-subst">${password}</span>`</span>)<br><span class="hljs-comment">// 这种键值对的方式是 urlencoded</span><br></code></pre></td></tr></table></figure><p> 在这种情况下,我们必须为请求设置请求头的<code>Content-Type</code>,告诉服务器,请求体的格式如下</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript">xhr.<span class="hljs-title function_">setRequestHeader</span>(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'application/x-www-form-urlencoded'</span>);<br></code></pre></td></tr></table></figure><p> 到了这里,<code>ajax</code>的原理基本了解了,开始使用的时候,觉得有点麻烦,每次ajax那么多代码,当然是封装了。其他的框架或者库都是封装的,如jQuery的ajax,功能强大。</p><h5 id="旧版ajax的封装(不推荐)"><a href="#旧版ajax的封装(不推荐)" class="headerlink" title="旧版ajax的封装(不推荐)"></a>旧版ajax的封装(不推荐)</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><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><code class="hljs javascript"><span class="hljs-comment">/*</span><br><span class="hljs-comment">obj 可选参数 </span><br><span class="hljs-comment">method 请求方式 (支持大小写)</span><br><span class="hljs-comment">url 请求地址</span><br><span class="hljs-comment">data 请求体 {}</span><br><span class="hljs-comment">callback 回调函数</span><br><span class="hljs-comment"> */</span><br><br><span class="hljs-keyword">let</span> <span class="hljs-title function_">ajax</span> = (<span class="hljs-params">obj</span>) => {<br> <span class="hljs-keyword">const</span> method = obj.<span class="hljs-property">method</span>.<span class="hljs-title function_">toUpperCase</span>();<br> <span class="hljs-keyword">let</span> url = obj.<span class="hljs-property">url</span>;<br> <span class="hljs-keyword">let</span> data = obj.<span class="hljs-property">data</span>;<br> <span class="hljs-keyword">const</span> callback = obj.<span class="hljs-property">callback</span>;<br> <span class="hljs-comment">// 为了兼容IE浏览器</span><br> <span class="hljs-keyword">const</span> xhr = <span class="hljs-variable language_">window</span>.<span class="hljs-property">XMLHttpRequest</span> ? <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>() : <span class="hljs-keyword">new</span> <span class="hljs-title class_">ActiveXObject</span>(<span class="hljs-string">'Microsoft.XMLHTTP'</span>);<br> <span class="hljs-keyword">const</span> tempArr = [];<br> <span class="hljs-comment">// 将用户传进来的对象转成urlencoded的格式</span><br> <span class="hljs-keyword">for</span> (key <span class="hljs-keyword">in</span> data) {<br> <span class="hljs-keyword">let</span> value = data[key];<br> tempArr.<span class="hljs-title function_">push</span>(<span class="hljs-string">`<span class="hljs-subst">${key}</span>=<span class="hljs-subst">${value}</span>`</span>);<br> }<br> <span class="hljs-keyword">let</span> params = tempArr.<span class="hljs-title function_">join</span>(<span class="hljs-string">'&'</span>);<br> <span class="hljs-comment">// get请求需要将数据拼接在URL后</span><br> <span class="hljs-keyword">if</span> (method == <span class="hljs-string">'GET'</span>) {<br> url = <span class="hljs-string">`<span class="hljs-subst">${url}</span>?<span class="hljs-subst">${params}</span>`</span>;<br> }<br> xhr.<span class="hljs-title function_">open</span>(method, url, <span class="hljs-literal">true</span>);<br> data = <span class="hljs-literal">null</span>;<br> <span class="hljs-comment">// POST 设置requestHeader</span><br> <span class="hljs-keyword">if</span> (method == <span class="hljs-string">'POST'</span>) {<br> xhr.<span class="hljs-title function_">setRequestHeader</span>(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'application/x-www-form-urlencoded'</span>);<br> data = params;<br> }<br> xhr.<span class="hljs-title function_">send</span>(data);<br> xhr.<span class="hljs-property">onreadystatechange</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">readyState</span> == <span class="hljs-number">4</span> && <span class="hljs-variable language_">this</span>.<span class="hljs-property">status</span> == <span class="hljs-number">200</span>) {<br> <span class="hljs-title function_">callback</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">responseText</span>);<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h5 id="发送ajax请求"><a href="#发送ajax请求" class="headerlink" title="发送ajax请求"></a>发送ajax请求</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><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><code class="hljs javascript">btn.<span class="hljs-property">onclick</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {<br> <span class="hljs-title function_">ajax</span>({<br> <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,<span class="hljs-comment">//GET</span><br> <span class="hljs-attr">url</span>: <span class="hljs-string">'http://localhost:3000/'</span>,<br> <span class="hljs-attr">data</span>: {<span class="hljs-attr">username</span>: username.<span class="hljs-property">value</span>, <span class="hljs-attr">password</span>: password.<span class="hljs-property">value</span>},<br> <span class="hljs-attr">callback</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">res</span>) {<br> <span class="hljs-title function_">alert</span>(res);<br> }<br> })<br> }<br></code></pre></td></tr></table></figure><h5 id="服务端响应ajax"><a href="#服务端响应ajax" class="headerlink" title="服务端响应ajax"></a>服务端响应ajax</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><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><code class="hljs javascript"><span class="hljs-keyword">const</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);<br><span class="hljs-keyword">const</span> url = <span class="hljs-built_in">require</span>(<span class="hljs-string">'url'</span>);<br><span class="hljs-keyword">const</span> querystring = <span class="hljs-built_in">require</span>(<span class="hljs-string">'querystring'</span>);<br><br><span class="hljs-keyword">const</span> server = http.<span class="hljs-title function_">createServer</span>(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =></span> {<br> res.<span class="hljs-title function_">writeHead</span>(<span class="hljs-number">200</span>, {<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Access-Control-Allow-Origin"</span>:<span class="hljs-string">"*"</span>});<br> <span class="hljs-comment">// 注释的代码是对于get请求的响应</span><br> <span class="hljs-comment">// let body = url.parse(req.url, true);</span><br> <span class="hljs-comment">// let parse = querystring.parse(req.url)</span><br> <span class="hljs-comment">// console.log(body.query);</span><br> <span class="hljs-comment">// res.end(JSON.stringify(body.query));</span><br> <br> <span class="hljs-comment">// 下面是对于post请求的响应</span><br> <span class="hljs-keyword">let</span> str = <span class="hljs-string">''</span>;<br> req.<span class="hljs-title function_">on</span>(<span class="hljs-string">'data'</span>, <span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) {<br> str += data;<br> });<br> req.<span class="hljs-title function_">on</span>(<span class="hljs-string">'end'</span>, <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">const</span> post_body = querystring.<span class="hljs-title function_">parse</span>(str);<br> res.<span class="hljs-title function_">end</span>(<span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(post_body));<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(post_body);<br> });<br> <br>})<br>server.<span class="hljs-title function_">listen</span>(<span class="hljs-number">3000</span>);<br></code></pre></td></tr></table></figure><h5 id="新版-ajax封装"><a href="#新版-ajax封装" class="headerlink" title="新版-ajax封装"></a>新版-ajax封装</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><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">main</span> = (<span class="hljs-params"></span>) => {<br><br> <span class="hljs-keyword">const</span> <span class="hljs-title function_">ajax</span> = (<span class="hljs-params">options</span>) => {<br><br> <span class="hljs-comment">// 默认参数对象</span><br> <span class="hljs-keyword">const</span> defaultObj = {<br> <span class="hljs-attr">type</span>: <span class="hljs-string">''</span>,<br> <span class="hljs-attr">url</span>: <span class="hljs-string">''</span>,<br> <span class="hljs-attr">header</span>: {<br> <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/application/x-www-form-urlencoded'</span><br> },<br> <span class="hljs-attr">params</span>: {},<br> <span class="hljs-attr">access</span>: <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(data);<br> },<br> <span class="hljs-attr">error</span>: <span class="hljs-function">(<span class="hljs-params">err</span>) =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(err);<br> }<br> }<br><br> <span class="hljs-comment">// 用传递参数覆盖默认参数</span><br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">assign</span>(defaultObj, options)<br><br> <span class="hljs-comment">// 创建实例</span><br> <span class="hljs-keyword">let</span> xhr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">XMLHttpRequest</span>();<br><br> <span class="hljs-comment">// 拼接参数字符串</span><br> <span class="hljs-keyword">let</span> paramStr = <span class="hljs-string">''</span>;<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> defaultObj.<span class="hljs-property">params</span>) {<br> paramStr += <span class="hljs-string">`<span class="hljs-subst">${key}</span>=<span class="hljs-subst">${defaultObj.params[key]}</span>&`</span><br> }<br><br> <span class="hljs-comment">// 去除最后一个&</span><br> paramStr = paramStr.<span class="hljs-title function_">substring</span>(<span class="hljs-number">0</span>, paramStr.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>);<br><br> <span class="hljs-comment">// get请求</span><br> <span class="hljs-keyword">if</span> (defaultObj.<span class="hljs-property">type</span> === <span class="hljs-string">'GET'</span>) {<br><br> <span class="hljs-comment">// 设置请求方式及地址</span><br> xhr.<span class="hljs-title function_">open</span>(defaultObj.<span class="hljs-property">type</span>, <span class="hljs-string">`<span class="hljs-subst">${defaultObj.url}</span>?<span class="hljs-subst">${paramStr}</span>`</span>);<br><br> <span class="hljs-comment">// 发送请求 get请求不传参数 post请求向send()传递参数</span><br> xhr.<span class="hljs-title function_">send</span>();<br> } <span class="hljs-keyword">else</span> {<br><br> <span class="hljs-comment">// 设置请求方式及地址</span><br> xhr.<span class="hljs-title function_">open</span>(defaultObj.<span class="hljs-property">type</span>, <span class="hljs-string">`<span class="hljs-subst">${defaultObj.url}</span>`</span>);<br><br> <span class="hljs-comment">// 请求凡是是post必须设置请求头</span><br> xhr.<span class="hljs-title function_">setRequestHeader</span>(<span class="hljs-string">'Content-Type'</span>, defaultObj.<span class="hljs-property">header</span>[<span class="hljs-string">'Content-Type'</span>])<br><br> <span class="hljs-comment">// 根据请求头 发送不同格式数据</span><br> <span class="hljs-keyword">if</span> (defaultObj.<span class="hljs-property">header</span>[<span class="hljs-string">'Content-Type'</span>] === <span class="hljs-string">'application/x-www-form-urlencoded'</span>) {<br><br> <span class="hljs-comment">// TODO 请求头设置为application/x-www-form-urlencoded 格式send()中传递必须使用该格式</span><br> <span class="hljs-comment">// post请求通过send()传递参数</span><br> xhr.<span class="hljs-title function_">send</span>(paramStr);<br> } <span class="hljs-keyword">else</span> {<br> xhr.<span class="hljs-title function_">send</span>(<span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(defaultObj.<span class="hljs-property">params</span>));<br> }<br><br> }<br> <span class="hljs-comment">// 请求成功</span><br> xhr.<span class="hljs-property">onload</span> = <span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">let</span> <span class="hljs-title class_">ContentType</span> = xhr.<span class="hljs-title function_">getResponseHeader</span>(<span class="hljs-string">'Content-Type'</span>);<br> <span class="hljs-keyword">let</span> responseText = xhr.<span class="hljs-property">responseText</span>;<br><br> <span class="hljs-comment">// TODO 服务器返回的永远是字符串</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title class_">ContentType</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">'application/json'</span>)) {<br> responseText = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(responseText);<br> }<br><br> <span class="hljs-comment">// 请求成功</span><br> <span class="hljs-keyword">if</span> (xhr.<span class="hljs-property">status</span> == <span class="hljs-number">200</span>) {<br> defaultObj.<span class="hljs-title function_">access</span>(responseText, xhr);<br> } <span class="hljs-keyword">else</span> {<br> defaultObj.<span class="hljs-title function_">error</span>(responseText, xhr);<br> }<br> }<br> }<br> <span class="hljs-title function_">ajax</span>({<br> <span class="hljs-attr">type</span>: <span class="hljs-string">'POST'</span>,<br> <span class="hljs-attr">url</span>: <span class="hljs-string">'http://localhost:3000/post'</span>,<br> <span class="hljs-attr">header</span>: {<br> <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span><br> },<br> <span class="hljs-attr">params</span>: {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">"wuxue"</span>,<br> <span class="hljs-attr">age</span>: <span class="hljs-number">25</span><br> },<br> });<br><br>}<br></code></pre></td></tr></table></figure><p><code>ajax</code>的基本原理和基本的封装基本上明白了,我自己也验证了<code>ajax</code>请求的可行性,但是大家有没有发现,这里其实是几个不爽的,或者是遗留的问题</p><ol><li>假如,我在ajax中得到了服务器响应值之后,进一步的处理是根据响应的数据进行再次的ajax请求呢?多次呢?想一想都觉得可怕!恐怕这就是回调黑洞了。</li><li>其实这里还有一个问题,请看服务端的代码。假如去除第五行中<code>Access-Control-Allow-Origin":"*"</code>,这一句代码,其实ajax是无法使用的。</li></ol><p><strong>好了,可以开始解决上述的问题了,实践是检验的唯一标准</strong></p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>外边距叠加的相关问题</title>
<link href="/2018/03/11/%E5%A4%96%E8%BE%B9%E8%B7%9D%E5%8F%A0%E5%8A%A0%E7%9A%84%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/"/>
<url>/2018/03/11/%E5%A4%96%E8%BE%B9%E8%B7%9D%E5%8F%A0%E5%8A%A0%E7%9A%84%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<h4 id="外边距叠加的问题"><a href="#外边距叠加的问题" class="headerlink" title="外边距叠加的问题"></a>外边距叠加的问题</h4><p>外边距叠加是一个简单的概念:在CSS中,两个或多个(特殊情况:一个)毗邻的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外边距会发生叠加,这种形成的外边距称之为外边距叠加。</p><p><strong>关键词</strong>: 两个或者多个,盒子(块级元素),毗邻,普通流,垂直方向</p><p><strong>注意<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/float">浮动元素</a>和<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/position#absolute">绝对定位元素</a>的外边距不会折叠(因为这里触发了 <a href="https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Block_formatting_context">块格式化上下文 Block Formatting Context, BFC</a>)。</strong></p><ol><li>兄弟元素之间,在垂直方向上发生了外边距叠加</li></ol><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/28236633-e035-4028-8340-be7740f7cf76" alt="外边距叠加"></p><p>2.当父子元素之间的margin-top或者margin-bottom没有被border,padding隔开的时候,他们的内外边距会发生叠加。</p><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/8229e47c-20a5-4366-bf8e-f5bf08900672" alt="外边距叠加2"></p><ol start="3"><li>单个的块级元素中不包含任何内容,并且在其 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin-top"><code>margin-top</code></a> 与 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin-bottom"><code>margin-bottom</code></a> 之间没有边框、内边距、行内内容、<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/height"><code>height</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/min-height"><code>min-height</code></a> 将两者分开,则该元素的上下外边距会折叠。</li></ol><p><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/7e0355ba-25ef-4750-aaf3-eb4153bba2aa" alt="外边距叠加3"></p><p><strong>注意</strong></p><ul><li>即使某一外边距为0,这些规则仍然适用。因此就算父元素的外边距是0,第一个或最后一个子元素的外边距仍然会“溢出”到父元素的外面。</li><li>如果参与折叠的外边距中包含负值,折叠后的外边距的值为最大的正边距与最小的负边距(即绝对值最大的负边距)的和。</li><li>如果所有参与折叠的外边距都为负,折叠后的外边距的值为最小的负边距的值。这一规则适用于相邻元素和嵌套元素。</li></ul><h5 id="外边距叠加的影响"><a href="#外边距叠加的影响" class="headerlink" title="外边距叠加的影响"></a>外边距叠加的影响</h5><p>外边距叠加的影响不一一列举,常见的就是想要实现子元素的margin-top等效果,结果发现父盒子也被加上了margin-top,从而影响了父盒子的位置。遇到类似问题,能够想到是因为外边距合并导致的就可以了。当然,外边距叠加并不一定都是负面的影响,如下图,也有正面的影响。在文字排版的时候,利用外边距合并,实现了段落之间相同宽度的效果。在没有想到更好的解决方案之前,这可以作为一个备选方案。<br><img src="https://github.com/askwuxue/askwuxue.github.io/assets/32808762/9dff8f35-fcf2-4bdc-ac5a-0b4ecbd1dbdf" alt="外边距叠加4"></p><h5 id="如何消除外边距叠加"><a href="#如何消除外边距叠加" class="headerlink" title="如何消除外边距叠加"></a>如何消除外边距叠加</h5><p>从外边距叠加入手,就可以解决这个问题。</p><ol><li>如果两个元素上下毗邻,分别设置外边距的时候考虑一下,如果必须设置,考虑利用浮动或者绝对定位是否能解决。</li><li>当嵌套块级元素的时候,看看时候能为他们设置padding或者border来破坏外边距叠加的条件</li><li>不要为一个空元素设置上下边距,应该没有人会这么干。</li><li>inline-block 元素和其他任何元素之间不发生外边距叠加,也包括它的子元素</li><li>创建了 BFC 的元素不会和它的子元素发生外边距叠加,不同的BFC中,不会发生外边距叠加</li></ol><p>如有不正之处,欢迎指正。随着学习的深入,可能会进一步的对BFC的原理等进行输出。</p>]]></content>
<categories>
<category>CSS</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
</search>