-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
524 lines (250 loc) · 291 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Web安全</title>
<link href="/2021/06/27/Web%E5%AE%89%E5%85%A8/"/>
<url>/2021/06/27/Web%E5%AE%89%E5%85%A8/</url>
<content type="html"><![CDATA[<h2 id="XSS"><a href="#XSS" class="headerlink" title="XSS"></a>XSS</h2><p>Cross-Site Scripting(跨站脚本攻击),是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在浏览器运行。利用这些恶意脚本,攻击者可以获取用户的敏感信息,如Cookie、SessionID等,进而危害数据安全。</p><p>任何可以输入的地方,都可能造成XSS攻击,包括URL。</p><p>常见的XSS目的:盗取cookie、投放广告、监听用户行为、修改DOM结构获取用户信息。</p><h4 id="XSS的分类"><a href="#XSS的分类" class="headerlink" title="XSS的分类"></a>XSS的分类</h4><p>根据攻击来源,可以分为3类:存储型、反射型、DOM型。</p><h5 id="存储型XSS(持久性)"><a href="#存储型XSS(持久性)" class="headerlink" title="存储型XSS(持久性)"></a>存储型XSS(持久性)</h5><ol><li>攻击者将恶意代码提交到<strong>目标网站的数据库</strong>中。</li><li>当用户打开网站,网站服务端将恶意代码调出,拼接在HTML中返回给浏览器。</li><li>浏览器解析执行,混在其中的恶意脚本也被执行。</li><li>该恶意脚本窃取用户数据发送到攻击者的网站,或冒充用户的行为,调用目标网站接口执行攻击者指定的操作。</li></ol><p>常见于带有用户数据保存的网站功能。比如攻击者在网站上提交一篇有恶意脚本的文章,被存到了数据库,当用户访问这个文章,浏览器解析到这段恶意脚本,就会执行。</p><h5 id="反射型XSS(非持久性)"><a href="#反射型XSS(非持久性)" class="headerlink" title="反射型XSS(非持久性)"></a>反射型XSS(非持久性)</h5><ol><li>攻击者构造初特殊的URL,其中包含恶意代码。</li><li><strong>诱导用户打开带有恶意代码的URL</strong>,网站服务器端不对这个url参数过滤处理,简单的“反射”给浏览器。</li><li>浏览器解析执行,混在其中的恶意脚本也被执行。</li><li>恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。</li></ol><p>它和存储型XSS的区别是:存储型XSS的恶意代码保存在数据库中,而反射型XSS保存在URL中。</p><p>常见于通过URL传参的功能,如网站搜索、跳转、QQ邮件中的一些恶意链接等。</p><h5 id="DOM型XSS"><a href="#DOM型XSS" class="headerlink" title="DOM型XSS"></a>DOM型XSS</h5><ol><li>基于浏览器解析DOM导致的漏洞,并不依赖于服务器</li><li>用户点击一个带有恶意脚本的URL</li><li>浏览器在DOM解析的时候直接使用了恶意数据</li><li>导致用户受到攻击</li></ol><p>DOM型XSS和前两种的区别:DOM型XSS取出和执行恶意代码由浏览器完成,属于前端JS自身安全漏洞,而前两种是服务器的安全漏洞。防范DOM型XSS完全是前端的责任。</p><p>比如说:前端把某个元素的.innerHTML设置为一个输入框的相关数据,当恶意攻击者在输入框输入恶意脚本,这个脚本会被执行。</p><h4 id="XSS-Payload"><a href="#XSS-Payload" class="headerlink" title="XSS Payload"></a>XSS Payload</h4><p>有效的XSS攻击,包括:</p><ul><li>窃取用户的Cookie (document.cookie)</li><li>识别用户的浏览器 (navigator.userAgent)</li><li>伪造请求</li><li>钓鱼网站 (在页面中注入钓鱼网站链接,诱导用户点击)</li></ul><h4 id="常见XSS防范方法"><a href="#常见XSS防范方法" class="headerlink" title="常见XSS防范方法"></a>常见XSS防范方法</h4><ul><li>设置httpOnly:这样js脚本就无法获取cookie的信息了。</li><li>设置黑名单,对不安全的输入进行过滤和转码,把<script>标签、javascript等字符过滤掉</li><li>设置白名单,只有符合相应格式的输入才能通过</li><li>利用CSP,明确表示哪些外部资源是可以加载和执行的</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"Content-Security-Policy"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"script-src 'self'"</span>></span><br></code></pre></td></tr></table></figure><p>或者设置http头部字段:Content-Security-Policy</p><ul><li><p>X-XSS-Protection</p><p>在http的响应头中设置该字段:</p><p>0:禁用XSS过滤</p><p>1:启用XSS过滤(默认),当检测到XSS攻击,会删除页面中不安全的部分</p><p>1;mode=block:当检测到XSS攻击,会阻止页面的加载</p><p>1; report=<report-url>:当检测到攻击,会清除页面并且使用CSP的功能发送违规报告。</p></li></ul><h4 id="预防DOM型XSS攻击"><a href="#预防DOM型XSS攻击" class="headerlink" title="预防DOM型XSS攻击"></a>预防DOM型XSS攻击</h4><ul><li>小心使用<code>.innerHTML</code> <code>.outerHTML</code> <code>document.write()</code>,不要把不可信数据作为HTML插到页面上,尽量使用<code>.textContent</code> <code>.setAttribute()</code>。</li><li>在Vue和React中,不要使用v-html、dangerouslySetInnerHTML,这样就在前端render阶段避免了XSS隐患。</li><li>DOM 中的内联事件监听器,如 <code>location</code>、<code>onclick</code>、<code>onerror</code>、<code>onload</code>、<code>onmouseover</code> 等,<code><a></code> 标签的 <code>href</code> 属性,JavaScript 的 <code>eval()</code>、<code>setTimeout()</code>、<code>setInterval()</code> 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。</li></ul><h2 id="CSRF(Cross-Site-request-forgery)"><a href="#CSRF(Cross-Site-request-forgery)" class="headerlink" title="CSRF(Cross-Site request forgery)"></a>CSRF(Cross-Site request forgery)</h2><p>跨站点请求伪造,是一种挟制用户在当前已登录的Web应用程序上执行非本意操作的攻击。如:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。</p><h4 id="常见CSRF类型"><a href="#常见CSRF类型" class="headerlink" title="常见CSRF类型"></a>常见CSRF类型</h4><h5 id="GET类型的CSRF"><a href="#GET类型的CSRF" class="headerlink" title="GET类型的CSRF"></a>GET类型的CSRF</h5><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"http://bank.example/withdraw?amount=10000&for=hacker"</span> ></span> <br></code></pre></td></tr></table></figure><p>当用户打开有这张图片的页面,浏览器就会自动向bank这个网站发起HTTP请求。</p><h5 id="POST类型的CSRF"><a href="#POST类型的CSRF" class="headerlink" title="POST类型的CSRF"></a>POST类型的CSRF</h5><p>使用一个自动提交的表单。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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 xml"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"http://bank.example/withdraw"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">POST</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"account"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"xiaoming"</span> /></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"amount"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"10000"</span> /></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"for"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"hacker"</span> /></span><br><span class="hljs-tag"></<span class="hljs-name">form</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="javascript"> <span class="hljs-built_in">document</span>.forms[<span class="hljs-number">0</span>].submit(); </span><span class="hljs-tag"></<span class="hljs-name">script</span>></span> <br></code></pre></td></tr></table></figure><p>访问该页面后,表单自动提交,相当于模拟用户进行了一次POST操作。</p><h5 id="链接类型的CSRF"><a href="#链接类型的CSRF" class="headerlink" title="链接类型的CSRF"></a>链接类型的CSRF</h5><figure class="highlight routeros"><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 routeros"><a <span class="hljs-attribute">href</span>=<span class="hljs-string">"http://test.com/csrf/withdraw.php?amount=1000&for=hacker"</span> <span class="hljs-attribute">taget</span>=<span class="hljs-string">"_blank"</span>><br> 重磅消息!!<br> <a/><br></code></pre></td></tr></table></figure><h4 id="CSRF的特点"><a href="#CSRF的特点" class="headerlink" title="CSRF的特点"></a>CSRF的特点</h4><ul><li>攻击一般发起在第三方网站,被攻击的网站无法阻止发生</li><li>利用了受害者在被攻击网站的登陆凭证,冒充受害者提交操作</li><li>跨站请求可以用各种方式:图片URL、超链接、Form提交等。</li></ul><h4 id="CSRF和XSS的区别"><a href="#CSRF和XSS的区别" class="headerlink" title="CSRF和XSS的区别"></a>CSRF和XSS的区别</h4><ul><li>有时候,CSRF是由XSS实现的(蠕虫) 。</li><li>本质上说,XSS是代码注入的问题,CSRF是HTTP的问题。XSS是因为内容上没有过滤导致浏览器将攻击者的代码执行。CSRF则是因为浏览器发送HTTP的时候自动带上Cookie,而一般网站的session都存在cookie里。</li></ul><h4 id="防御"><a href="#防御" class="headerlink" title="防御"></a>防御</h4><ul><li><p>防止用户不知情的情况下盗用了用户的身份</p><p>验证码:强制用户必须与应用进行交互,才达成最终请求。</p><p>用户体验不好</p></li><li><p>防止跨站请求</p><p>Refer check:通过request header的referer字段,查看请求来自哪个源</p><p>不可靠,依然可能被篡改</p></li><li><p>防止参数伪造</p><p>token(常用):前后端协商一个token加密的算法,当前端发起请求的时候,带上token,后端收到请求后,用同样的方式算出用户对应的token,如果一致,才通过验证。</p></li><li><p>sameSite:在服务器通过set-cookie返回响应的时候,可以设置samesite属性的值为strict</p></li></ul><h3 id="samesite"><a href="#samesite" class="headerlink" title="samesite"></a>samesite</h3><p>strict:完全禁止发送第三方Cookie,只有当前URL和请求目标一致才带上Cookie</p><p>lax:允许发送Get请求的第三方Cookie</p><p>none:允许发送所有Cookie,但cookie要用secure属性,否则无效</p><p>第一方Cookie:用户访问的域创建的Cookie。</p><p>第三方Cookie:建立在别的域名下的Cookie,比如广告网络商是最常见的第三方Cookie来源。</p><h2 id="DDOS攻击"><a href="#DDOS攻击" class="headerlink" title="DDOS攻击"></a>DDOS攻击</h2><p>通过大规模互联网流量淹没目标服务器或其周边基础设施,以破坏目标服务器、服务或网络正常流量的恶意行为。</p><p>就好比一家餐厅原本能同时容纳30个人,但有一天,一个流氓带着300个人进了餐厅,占着位置但是却不点餐,导致正常的顾客页无法光顾,餐厅瘫痪了。</p><h4 id="DDOS防范"><a href="#DDOS防范" class="headerlink" title="DDOS防范"></a>DDOS防范</h4><ul><li>验证码:降低用户体验</li><li>限制请求频率:通过一些标识(IP或Cookie)定位客户端,限制一个客户端请求的频率</li><li>扩容加带宽:适合双十一活动或12306抢车票等场景</li></ul><h2 id="网页劫持"><a href="#网页劫持" class="headerlink" title="网页劫持"></a>网页劫持</h2><h4 id="DNS劫持"><a href="#DNS劫持" class="headerlink" title="DNS劫持"></a>DNS劫持</h4><p>解析域名时,服务提供商可能会对IP地址劫持,返回一个错误的IP地址。</p><h4 id="HTTP劫持"><a href="#HTTP劫持" class="headerlink" title="HTTP劫持"></a>HTTP劫持</h4><p>DNS劫持一般会替换整个网页,而http劫持,利用了http协议明文传输的特点,篡改了返回的html内容。</p><p>常见的场景是在页面加一个小窗,可能投放广告。</p><p>这是由于信息没有加密而造成的。可以使用HTTPS解决。</p><h4 id="路由劫持"><a href="#路由劫持" class="headerlink" title="路由劫持"></a>路由劫持</h4><p>比如:小米路由器,把404之类的页面换成自己的页面。</p><h4 id="软件劫持"><a href="#软件劫持" class="headerlink" title="软件劫持"></a>软件劫持</h4><p>使用一些软件来清除广告,但这会管控全局流量,电脑的所有网络流量都会经过这个软件,所以进行劫持也是很简单的事情。</p>]]></content>
</entry>
<entry>
<title>前端模块化</title>
<link href="/2021/06/13/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/"/>
<url>/2021/06/13/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/</url>
<content type="html"><![CDATA[<h2 id="为什么前端开发需要模块化?"><a href="#为什么前端开发需要模块化?" class="headerlink" title="为什么前端开发需要模块化?"></a>为什么前端开发需要模块化?</h2><p>在讨论模块化之前,我们先了解一下以前的代码是怎么组织的:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">body</span>></span><br> <span class="hljs-comment"><!-- 许多html代码 --></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">scr</span>=<span class="hljs-string">'./index.js'</span>></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></code></pre></td></tr></table></figure><p>一开始,我们可能会将所有的JS代码写在一个index.js文件里,然后通过script标签引入。后来,随着业务逐渐复杂,index.js文件变得庞大,也变得难以维护。所以我们考虑将代码按照功能模块,拆分到不同的文件里,然后引入多个js文件:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">body</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">'./a.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">'./b.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">'./c.js'</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br> ...<br><span class="hljs-tag"></<span class="hljs-name">body</span>></span><br></code></pre></td></tr></table></figure><p>通过这样的拆分,我们各个文件的代码量变少了,看起来确实会更清晰,但是也会带来几个问题:</p><ul><li><p>命名冲突和变量污染</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// a.js</span><br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><br><span class="hljs-comment">// b.js</span><br><span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>;<br><br><span class="hljs-comment">// index.html</span><br><script src=<span class="hljs-string">"./a.js"</span>></script><br><script src=<span class="hljs-string">"./b.js"</span>></script><br><script><br> <span class="hljs-built_in">console</span>.log(a, b); <br></script><br></code></pre></td></tr></table></figure><p>假设有两个功能模块a和b,分别拆分到a.js和b.js中,上面的代码打印出来的是<code>1 2</code>,其实没有什么问题,但如果有一天,一个新接手的程序员,在b.js中又定义了一个变量a。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// a.js</span><br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><br><span class="hljs-comment">// b.js</span><br><span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>;<br><span class="hljs-keyword">var</span> a = <span class="hljs-number">3</span>;<br></code></pre></td></tr></table></figure><p>这时候,控制台打印的代码变成:<code>3 2</code>。由于a和b都是定义到了全局作用域,所用两个模块的变量命名有了冲突。我们可以通过修改变量名来规避,但这是治标不治本的,因为他们没有自己的作用域,所以我们依然可以在b模块中,修改a模块的变量,可能会造成一些bug。</p></li><li><p>资源或模块之间的依赖</p><p>如上面的代码片段,在html中添加了很多script标签来一个个引入资源,首先可能会造成很多请求,导致页面卡顿。</p><p>更重要的是,如果资源之间有依赖关系,还要按照依赖关系从上到下排序。如果引入了defer或async属性,逻辑将会变得更加复杂,难以维护。</p></li></ul><h2 id="实现模块化"><a href="#实现模块化" class="headerlink" title="实现模块化"></a>实现模块化</h2><p>那么我们要如何解决这两个问题呢?</p><h3 id="解决命名冲突和变量污染"><a href="#解决命名冲突和变量污染" class="headerlink" title="解决命名冲突和变量污染"></a>解决命名冲突和变量污染</h3><p>为了避免命名冲突和变量污染,我们想到为每一个模块创建一个私有的作用域,避免命名冲突,模块外只能访问被暴露的一些变量,避免变量污染。</p><p>由于在函数内部可以形成一个局部作用域,所以我们可以将模块的代码包裹到一个函数中,而且考虑到我们往往只需要调用一次这个函数,所以可以使用立即执行函数表达式(IIFE):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// a.js</span><br><span class="hljs-keyword">var</span> moduleA = (<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-built_in">console</span>.log(a);<br> }<br> <span class="hljs-keyword">return</span> {<br> foo,<br> };<br>})();<br><br><span class="hljs-comment">// b.js</span><br><span class="hljs-keyword">var</span> moduleB = (<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">var</span> b = <span class="hljs-number">2</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-built_in">console</span>.log(a, b);<br> }<br> <span class="hljs-keyword">var</span> a = <span class="hljs-number">3</span>;<br> <span class="hljs-keyword">return</span> {<br> foo,<br> }<br>})();<br></code></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-comment"><!-- index.html --></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./a.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">"./b.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><br><span class="javascript"> moduleA.foo(); <span class="hljs-comment">// 1</span></span><br><span class="javascript"> moduleB.foo(); <span class="hljs-comment">// 3 2</span></span><br><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br></code></pre></td></tr></table></figure><p>这个时候,尽管两个module都有变量a和方法foo,但是他们都在各自的作用域里,不会造成命名冲突,而且moduleB也无法修改moduleA的变量,不会造成变量污染。</p><h3 id="解决模块间的依赖"><a href="#解决模块间的依赖" class="headerlink" title="解决模块间的依赖"></a>解决模块间的依赖</h3><p>我们的一个模块,可以会依赖另外一个模块,所以在html中,我们要注意按顺序引入,比如有这样一个依赖图:</p><p><img src="/2021/06/13/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/%E4%BE%9D%E8%B5%961.png" alt="依赖1"></p><p>通过拓扑排序,我们的引入顺序可能是:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./d.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">"./c.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">"./b.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">"./a.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br></code></pre></td></tr></table></figure><p>这时候,如果新添加一个moduleE,那么我们要重新分析依赖,把moduleE的引入放在合适的位置。随着业务的发展,每次添加一个module,都要重新分析的话,不仅笨重,而且给维护也带来了一定的困难。</p><p>所以,手动管理依赖终究不是一个好办法。</p><h2 id="模块化规范"><a href="#模块化规范" class="headerlink" title="模块化规范"></a>模块化规范</h2><p>虽然上面我们讨论了模块化的实现,但还是存在两个问题:</p><ul><li>模块化实现方式不统一</li><li>手动维护模块依赖困难</li></ul><p>为了解决这两个问题,开发人员们提出了模块化规范,也就是统一定义模块的方法,以及解放手动维护依赖。</p><p>目前主流的三种模块化规范分别是:</p><ul><li>CommonJS</li><li>AMD</li><li>CMD</li></ul><h4 id="CommonJS"><a href="#CommonJS" class="headerlink" title="CommonJS"></a>CommonJS</h4><p>这个规范在Node.js中被广泛使用,每个文件就是一个模块。有四个关键的环境变量:</p><ul><li><code>module</code>:每个模块内部都有一个这样的变量,表示当前模块</li><li><code>exports</code>:module的一个属性,表示对外暴露的接口</li><li><code>global</code>:表示全局环境(Node)</li><li><code>require</code>:同步加载某个模块的exports属性</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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// a.js</span><br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">var</span> addA = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">value</span>) </span>{<br> <span class="hljs-keyword">return</span> a + value;<br>}<br><span class="hljs-built_in">module</span>.exports = {<br> a,<br> addA<br>}<br><br><span class="hljs-comment">// b.js</span><br><span class="hljs-keyword">const</span> { a, addA } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./a.js'</span>);<br><span class="hljs-built_in">console</span>.log(a); <span class="hljs-comment">// 1</span><br><span class="hljs-built_in">console</span>.log(addA(<span class="hljs-number">2</span>)); <span class="hljs-comment">// 3</span><br></code></pre></td></tr></table></figure><p>这个规范中,以同步的方式加载模块。因为在服务端,模块文件都存在本地磁盘,读取速度快,这这样做不会有问题,但如果在浏览器端,由于网络原因,应该使用异步加载,提前编译打包好。</p><p>CommonJS模块的加载机制是,加载被输出的值的拷贝,也就是说,一旦这个值输出了,模块内部对这个值的修改,不会影响被输出的值。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// a.js</span><br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">var</span> addA = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> a++;<br>}<br><span class="hljs-keyword">var</span> getA = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">return</span> a;<br>}<br><span class="hljs-built_in">module</span>.exports = {<br> a,<br> addA,<br> getA<br>}<br><br><span class="hljs-comment">// b.js</span><br><span class="hljs-keyword">var</span> { a, addA, getA } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./a'</span>);<br><span class="hljs-built_in">console</span>.log(a); <span class="hljs-comment">// 1</span><br>addA();<br><span class="hljs-built_in">console</span>.log(a); <span class="hljs-comment">// 1</span><br><span class="hljs-built_in">console</span>.log(getA()); <span class="hljs-comment">// 2</span><br></code></pre></td></tr></table></figure><p>从上面一段代码可以看到,调用了addA方法,虽然a.js内部的a值已经变成了2,但是这不会影响到b.js模块引入的a值,因因为a是原始类型的值,载入时被缓存起来了。</p><h4 id="AMD"><a href="#AMD" class="headerlink" title="AMD"></a>AMD</h4><p>一般在浏览器环境下采用,异步加载模块,所有依赖这个模块的语句,都会被定义在一个回调函数中。</p><p>主要命令:<code>define(id?, dependency?, factory)</code>、<code>require(modules, callback)</code></p><p>使用方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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 js"><span class="hljs-comment">// 定义没有依赖的模块A</span><br>define(<span class="hljs-string">'moduleA'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-built_in">require</span>, <span class="hljs-built_in">exports</span>, <span class="hljs-built_in">module</span></span>) </span>{<br> <span class="hljs-comment">//...</span><br>});<br><span class="hljs-comment">// 定义依赖模块A、B、C的模块D</span><br>define(<span class="hljs-string">'moduleD'</span>, [<span class="hljs-string">'moduleA'</span>, <span class="hljs-string">'moduleB'</span>, <span class="hljs-string">'moduleC'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a, b, c</span>) </span>{<br> <span class="hljs-comment">// 在最前面声明并初始化了依赖的所有模块</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-literal">false</span>){<br> <span class="hljs-comment">// 即便没用到b,但b还是初始化且执行了</span><br> b.dosomething();<br> }<br> <span class="hljs-comment">// 通过return方法暴露接口</span><br> <span class="hljs-keyword">return</span> {<br> ...<br> }<br>});<br><br><span class="hljs-comment">// 加载模块</span><br><span class="hljs-built_in">require</span>([<span class="hljs-string">'moduleA'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a</span>) </span>{<br><span class="hljs-comment">// ... </span><br>});<br></code></pre></td></tr></table></figure><p>实现这个规范,需要模块加载器require.js,所以引入模块之前,我们要先引入require.js文件,新建main.js,作为入口文件,也可以使用require.config()定义模块依赖的配置。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./require.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">"./main.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></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><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-comment">// main.js</span><br><span class="hljs-built_in">require</span>([<span class="hljs-string">'a'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a</span>) </span>{<br> a.say(); <span class="hljs-comment">// b a</span><br>});<br><br><span class="hljs-comment">// a.js</span><br>define(<span class="hljs-string">'a'</span>, [<span class="hljs-string">'b'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">b</span>) </span>{<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">say</span>(<span class="hljs-params"></span>) </span>{<br> b.say();<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a'</span>);<br> }<br> <span class="hljs-keyword">return</span> {<br> say<br> }<br>});<br><br><span class="hljs-comment">// b.js</span><br>define(<span class="hljs-string">'b'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-built_in">require</span>, <span class="hljs-built_in">exports</span>, <span class="hljs-built_in">module</span></span>)</span>{<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">say</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'b'</span>);<br> }<br> <span class="hljs-built_in">exports</span>.say = say;<br>})<br></code></pre></td></tr></table></figure><h2 id="CMD"><a href="#CMD" class="headerlink" title="CMD"></a>CMD</h2><p>另一种js模块化方案,与AMD类似。AMD推崇依赖前置,提前执行。而CMD推崇依赖就近、延迟执行。在使用某个模块时,需要显式声明。</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">define([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a, b, c</span>) </span>{<br> <span class="hljs-keyword">var</span> a = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./a'</span>); <span class="hljs-comment">// 使用时需声明</span><br> a.doSomething();<br>})<br></code></pre></td></tr></table></figure><p>要使用这个规范,需要引入模块加载器SeaJS,用法类似requireJS,只是需要在回调函数里显式的使用require引入模块,在这里暂时不赘述。</p><p>随着时代的发展,Web相关的标准不断更新,ES6也引入了新的模块规范,requireJS和SeaJS虽然能用,但是也过时了,人们对它们不再像从前一样依赖。</p><h2 id="ES6-module"><a href="#ES6-module" class="headerlink" title="ES6 module"></a>ES6 module</h2><p>无论是CommonJS还是AMD,都是在运行的时候,才能确定模块的依赖关系和输入输出,而ES6的模块设计,则是追求尽量静态化。在模块中你使用 import 和 export 关键字来导入或导出模块中的东西。</p><p>ES6 module有几个特点:</p><ul><li>自动开启严格模式</li><li>一个 JS 文件就代表一个 JS 模块</li><li>每个模块就是一个单例,只会加载一次</li></ul><p>和CommonJS相比:</p><ul><li><p>commonJS输出的是值的拷贝,ES6输出的是值的引用</p></li><li><p>commonJS是运行时加载,ES6是编译时输出接口。commonJS模块就是对象,在输入时加载整个模块,再从这个对象上读方法。而ES6允许import指定加载某个输出值。</p><p>因为CommonJS 加载的是一个对象(即<code>module.exports</code>属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。</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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// a.js</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br>a = <span class="hljs-number">2</span>;<br><br><span class="hljs-comment">// main.js</span><br><span class="hljs-keyword">import</span> { a } <span class="hljs-keyword">from</span> <span class="hljs-string">'./a'</span>;<br><span class="hljs-built_in">console</span>.log(a); <span class="hljs-comment">// 2</span><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><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-comment">// 第一种写法</span><br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> a;<br><br><span class="hljs-comment">// 第二种写法</span><br><span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<br><span class="hljs-keyword">export</span> { a <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span> };<br><br></code></pre></td></tr></table></figure><ul><li>第一种写法中,export default绑定的是a这个表达式,而不是标识符。因此,当在export default后更新a值,并不会反映到导出的值中。</li><li>而第二种写法,导出绑定了a这个标识符,所以a的修改会影响到导出侧的值。</li></ul><p>实际采用哪一种写法,可以根据这个导出后续是否需要更新来选择。无论如何,我们最好通过代码注释,来解释我们的意图。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>为了解决命名冲突、变量污染以及模块依赖管理的问题,前端引入了模块化的规范。</p><p>其中,CommonJS规范是同步加载,适合服务器端使用。</p><p>在浏览器端,我们要使用异步的模块化规范,早期提出了AMD和CMD规范.</p><p>随着时代的发展,ES6 module参考CommonJS和AMD,标准化了模块的加载和解析方式,实现起来也更加简单,提供了更加简洁的语法,成为了浏览器和服务器端通用的一种规范。</p><p><img src="/2021/06/13/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/%E6%A8%A1%E5%9D%97%E5%8C%96.png" alt="总结"></p><p>参考资料:<br><a href="https://juejin.cn/post/6844903744518389768#heading-49">前端模块化详解</a></p><p><a href="https://ke.qq.com/classroom/index.html#course_id=327150&term_id=100388238&ch_id=498625&vch_id=274§ion_id=1077&task_id=2639536576593390">NEXT学位课程</a></p><p>《你不知道的JavaScript》(下卷)</p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>正则表达式</title>
<link href="/2021/06/08/%E3%80%90%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%E3%80%91%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
<url>/2021/06/08/%E3%80%90%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%E3%80%91%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</url>
<content type="html"><![CDATA[<blockquote><p>正则表达式是匹配模式,要么匹配字符,要么匹配位置。</p></blockquote><h2 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h2><table><thead><tr><th>字符</th><th>含义</th></tr></thead><tbody><tr><td>\</td><td>转义</td></tr><tr><td>匹配位置:</td><td></td></tr><tr><td>^</td><td>匹配输入的开始</td></tr><tr><td>$</td><td>匹配输入的结束</td></tr><tr><td>\b</td><td>匹配单词的开始或结束(所谓单词的开始和结束,就是\b前后不同时是\w)</td></tr><tr><td>x(?=y)</td><td>正向前瞻,仅匹配后面紧跟着y的x</td></tr><tr><td>x(?!y)</td><td>负向前瞻,仅匹配后面不跟着y的x</td></tr><tr><td>匹配字符</td><td></td></tr><tr><td>\w</td><td>字母、数字、下划线</td></tr><tr><td>\d</td><td>数字</td></tr><tr><td>\s</td><td>空白字符,包括空格、制表符、换页符和换行符</td></tr><tr><td>[xyz]</td><td>匹配[]中的任意字符</td></tr><tr><td>x|y</td><td>匹配x或y</td></tr><tr><td>.</td><td>默认匹配除换行符之外的任何单个字符</td></tr><tr><td>量词</td><td></td></tr><tr><td>*</td><td>匹配前一个表达式0次或多次</td></tr><tr><td>?</td><td>匹配前一个表达式0次或1次<br />正则默认是贪婪匹配,也就是匹配尽量多的字符<br />在量词后的?表示当前匹配是惰性匹配</td></tr><tr><td>+</td><td>匹配前一个表达式1次或多次</td></tr><tr><td>{n}</td><td>匹配前一个表达式n次</td></tr><tr><td>{n,}</td><td>前一个表达式至少出现n次</td></tr><tr><td>flags</td><td></td></tr><tr><td>g</td><td>全局匹配(global)</td></tr><tr><td>i</td><td>不区分大小写(ignoreCase)</td></tr><tr><td>m</td><td>多行搜索</td></tr></tbody></table><h2 id="基本方法"><a href="#基本方法" class="headerlink" title="基本方法"></a>基本方法</h2><h4 id="创建一个正则表达式"><a href="#创建一个正则表达式" class="headerlink" title="创建一个正则表达式"></a>创建一个正则表达式</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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> reg = <span class="hljs-regexp">/\w+/</span>; <span class="hljs-comment">// 字面量</span><br><span class="hljs-comment">// 等价于</span><br><span class="hljs-keyword">var</span> reg = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">'\\w+'</span>); <span class="hljs-comment">// 构造函数的字符串参数 </span><br></code></pre></td></tr></table></figure><p>注意:当使用构造函数创造正则对象时,需要常规的字符转义规则(也就是在转义字符前也要加反斜杠 <code>\</code>)。</p><h4 id="正则表达式(RegExp)的方法"><a href="#正则表达式(RegExp)的方法" class="headerlink" title="正则表达式(RegExp)的方法"></a>正则表达式(RegExp)的方法</h4><ul><li><p>test</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs js">/a/.test(<span class="hljs-string">'a'</span>); <span class="hljs-comment">// true</span><br>/a/.test(<span class="hljs-string">'b'</span>); <span class="hljs-comment">// false</span><br></code></pre></td></tr></table></figure></li><li><p>exec</p><p>regExp是一个有状态的对象,多次对同一个或者相等的字符串执行同样的方法,可以遍历所有匹配的结果。</p><p>相比test会返回更多内容,如果匹配成功会返回一个数组,并且更新lastIndex属性。</p><p>返回的数组其实是类数组,第一项是匹配的字符串,接着是捕获的分组,还有groups、index、input属性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> reg = <span class="hljs-regexp">/(a|d)/g</span>;<br><span class="hljs-keyword">var</span> target = <span class="hljs-string">'abcd'</span>;<br><span class="hljs-keyword">var</span> arr;<br><span class="hljs-keyword">while</span>(arr = reg.exec(target)) {<br> <span class="hljs-built_in">console</span>.log(arr);<br>}<br><span class="hljs-built_in">console</span>.log(arr);<br><br><span class="hljs-comment">// 长度为2的数组</span><br><span class="hljs-comment">// ["a", "a", index: 0, input: "abcd", groups: undefined]</span><br><span class="hljs-comment">// ["d", "d", index: 3, input: "abcd", groups: undefined]</span><br><span class="hljs-comment">// null</span><br></code></pre></td></tr></table></figure><h4 id="String对象的方法"><a href="#String对象的方法" class="headerlink" title="String对象的方法"></a>String对象的方法</h4></li><li><p>split</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> values = <span class="hljs-string">'1,2,3,4,5'</span>; <span class="hljs-comment">//以中文或英文逗号分隔的值</span><br><span class="hljs-keyword">var</span> arr = values.split(<span class="hljs-regexp">/,|,/</span>); <span class="hljs-comment">// ['1', '2', '3', '4', '5']</span><br></code></pre></td></tr></table></figure></li><li><p>match</p><ul><li><p>如果正则表达式有flag g,那么match方法会返回所有匹配的结果,但不会返回捕获组。</p></li><li><p>如果没有,就和exec方法很像,会返回第一个匹配和相关的捕获组,以及index、input、groups属性。如果不匹配,则返回null。</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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> str = <span class="hljs-string">'Hello World'</span>;<br><span class="hljs-built_in">console</span>.log(str.match(<span class="hljs-regexp">/[A-Z]/</span>)); <span class="hljs-comment">// ["H", index: 0, input: "Hello World", groups: undefined]</span><br><span class="hljs-built_in">console</span>.log(str.match(<span class="hljs-regexp">/[A-Z]/g</span>)); <span class="hljs-comment">// ["H", "W"]</span><br></code></pre></td></tr></table></figure></li><li><p>replace</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-built_in">console</span>.log(<span class="hljs-string">'abcd'</span>.replace(<span class="hljs-regexp">/ab/</span>, <span class="hljs-string">'AB'</span>)); <span class="hljs-comment">// ABcd</span><br></code></pre></td></tr></table></figure></li><li><p>search</p><p>返回匹配到位置的索引,匹配失败时返回-1。</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-built_in">console</span>.log(<span class="hljs-string">'hello World'</span>.search(<span class="hljs-regexp">/[A-Z]/</span>)); <span class="hljs-comment">// 6</span><br></code></pre></td></tr></table></figure></li></ul><h2 id="分组与捕获"><a href="#分组与捕获" class="headerlink" title="分组与捕获"></a>分组与捕获</h2><h4 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h4><p>正则表达式中,括号的作用除了使结构更加清晰,还有一个更重要的功能:分组和捕获,也就是可以提取匹配到的数据或进行替换,而这个功能需要配合相关的api使用,比如:exec、match。</p><p>假设我们要匹配一个年月日,可以用这样的正则表达式匹配:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> reg = <span class="hljs-regexp">/\d{4}-\d{2}-\d{2}/</span>;<br>reg.exec(<span class="hljs-string">'2021-06-01'</span>); <span class="hljs-comment">// ["2021-06-01", index: 0, input: "2021-06-01", groups: undefined]</span><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> reg = <span class="hljs-regexp">/(\d{4})-(\d{2})-(\d{2})/</span>;<br>reg.exec(<span class="hljs-string">'2021-06-01'</span>); <span class="hljs-comment">// ["2021-06-01", "2021", "06", "01", index: 0, input: "2021-06-01", groups: undefined]</span><br></code></pre></td></tr></table></figure><p>可以看到,返回的数组多了几项,从第二项开始,分别对应的是三个括号内匹配到的内容,这就是分组,在exec方法中可以捕获到,通过RegExp.$1-$9,也可以获取到对应的内容。</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-built_in">console</span>.log(<span class="hljs-built_in">RegExp</span>.$1); <span class="hljs-comment">// 2021console.log(RegExp.$2); // 06console.log(RegExp.$3); // 01</span><br></code></pre></td></tr></table></figure><h4 id="反向引用"><a href="#反向引用" class="headerlink" title="反向引用"></a>反向引用</h4><p>除了上面这种引用,还有一种反向引用,也就是正则本身引用之前出现的分组。</p><p>考虑这样的场景:我们要匹配三种格式的日期:yyyy-mm-dd、yyyy/mm/dd、yyyy.mm.dd。或许我们可以这样写正则:</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">var</span> reg = <span class="hljs-regexp">/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/</span>;<br></code></pre></td></tr></table></figure><p>虽然确实可以匹配上面三种格式的日期,但是这个正则表达式同时也匹配了像‘2021-06/02’这样的日期,要怎么样保持连接符号的一致呢?这就需要反向引用了。</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">var</span> reg = <span class="hljs-regexp">/\d{4}(-|\/|\.)\d{2}\1\d{2}/</span>;<br></code></pre></td></tr></table></figure><p>这里的<code>\1</code>,引用的就是前面出现的第一个分组,通过<code>\1</code>、<code>\2</code>、<code>\3</code>这样的转义字符,可以捕获到前面的分组,无论前面匹配到什么,反向引用匹配的都是同样的具体某个字符。</p><p>如果遇到嵌套的括号,则以左括号为准,标识分组顺序。</p><h4 id="非捕获分组"><a href="#非捕获分组" class="headerlink" title="非捕获分组"></a>非捕获分组</h4><p>还是上面的例子,我们用括号包裹连接符,创建了分组,但捕获的时候,我们其实并不关心连接符号,而只关心匹配到的年月日,那么我们可以使用非捕获分组<code>(?:)</code>:</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">var</span> reg = <span class="hljs-regexp">/(\d{4})(?:-|\.|\/)(\d{2})(?:-|\.|\/)(\d{2})/</span>;<span class="hljs-built_in">console</span>.log(reg.exec(<span class="hljs-string">'2021-06-01'</span>)); <span class="hljs-comment">// ["2021-06-01", "2021", "06", "01", index: 0, input: "2021-06-01", groups: undefined]</span><br></code></pre></td></tr></table></figure><p>如此,我们捕获到的分组,就只是年、月、日了。</p><h2 id="正则的构建"><a href="#正则的构建" class="headerlink" title="正则的构建"></a>正则的构建</h2><p>构建正则之前,我们要考虑几个问题:</p><ul><li><p>是否能使用正则</p></li><li><p>是否有必要使用正则,能用字符串API解决的简单问题,就没有必要使用正则。</p></li><li><p>是否有必要构建一个复杂的正则</p><p>对于密码匹配,假设规则有很多,那么构建出来的可能是一个很庞大的正则表达式,但其实也可以使用很多个小正则来做</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-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkPassword</span>(<span class="hljs-params">string</span>) </span>{ <span class="hljs-keyword">if</span> (!regex1.test(string)) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-keyword">if</span> (!regex2.test(string)) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-keyword">if</span> (!regex3.test(string)) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; ...}<br></code></pre></td></tr></table></figure><p>正则采用的是回溯的方法来匹配,对于太复杂的正则表达式,可能会影响到性能。</p></li></ul><p>构建正则表达式时,还要注意:</p><ul><li>匹配预期的字符串</li><li>不匹配非预期的字符串</li><li>效率优化:<ul><li>使用具体型字符组代替通配符</li><li>使用非捕获型分组</li><li>独立出确定字符</li><li>提取分支的公共部分</li><li>减少分支的数量</li></ul></li></ul><h2 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h2><h4 id="表单验证-提取"><a href="#表单验证-提取" class="headerlink" title="表单验证/提取"></a>表单验证/提取</h4><p>验证输入的某个值是否符合一个模式,使用match方法,如果匹配失败,返回null,匹配成功,则返回带有捕获分组信息的类数组。</p><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> arr = value.split(<span class="hljs-regexp">/,|,/</span>);<br></code></pre></td></tr></table></figure><blockquote><p><a href="https://regex101.com/">一个用于测试正则表达式的网站:</a><a href="https://regex101.com/">https://regex101.com/</a></p></blockquote><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://github.com/qdlaoyao/js-regex-mini-book">JS正则迷你书</a></p><p><a href="https://ke.qq.com/classroom/index.html#course_id=198477&term_id=100235260&ch_id=327937&vch_id=36&task_id=1511154178525005">NEXT学位课程</a></p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>Promise学习笔记(三)</title>
<link href="/2021/05/22/Promise%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89/"/>
<url>/2021/05/22/Promise%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89/</url>
<content type="html"><![CDATA[<h3 id="Promise的模式"><a href="#Promise的模式" class="headerlink" title="Promise的模式"></a>Promise的模式</h3><h4 id="Promise-all-…"><a href="#Promise-all-…" class="headerlink" title="Promise.all([…])"></a>Promise.all([…])</h4><p>接收一个可迭代对象(数组/Map/Set),返回一个promise实例pAll。元素通常是promise实例,但也可以是一个thenable对象,也可以是一个立即值。</p><blockquote><p>本质而言,列表中的每个值都会通过Promise.resolve(..)来过滤,以确保要等待的是一个真正的Promise实例</p></blockquote><p>当且仅当这个可迭代对象中的<strong>所有</strong>promise实例都成功时,pAll才成功。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> resolve(<span class="hljs-number">1</span>);<br>})<br><span class="hljs-keyword">const</span> p2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> resolve(<span class="hljs-number">2</span>);<br>})<br><br><span class="hljs-keyword">const</span> pAll = <span class="hljs-built_in">Promise</span>.all([p1,p2])<br><br>pAll.then(<br> (values) => {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'all resolved'</span>,values)<br>}, <span class="hljs-function">(<span class="hljs-params">reason</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'reject'</span>,reason)<br>})<br></code></pre></td></tr></table></figure><p>如果pAll是成功的,就执行onResolved函数,其中<strong>values是一个数组</strong>,按照原来的先后顺序,存放promise的结果。</p><p>如果pAll是失败的,就执行onRejected函数,其中reason是第一个抛出的错误信息。</p><h3 id="Promise-race"><a href="#Promise-race" class="headerlink" title="Promise.race()"></a>Promise.race()</h3><p>这个模式叫竞态Promise。同样接收一个可迭代对象(数组/Map/Set),元素的类型可以是promise实例、thenable对象或立即值,并且返回一个promise实例pRace。</p><p>可迭代对象中<strong>第一个完成状态转换</strong>的promise,决定了pRace是成功还是失败:</p><p>(立即值在race中显然没什么意义,因为他一定是第一个resolve的)</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> resolve(<span class="hljs-number">1</span>);<br>})<br><span class="hljs-keyword">const</span> p2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> resolve(<span class="hljs-number">2</span>);<br>})<br><br><span class="hljs-keyword">const</span> pRace = <span class="hljs-built_in">Promise</span>.all([p1,p2]);<br><br>pRace.then(<br> (res) => {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'race resolved'</span>,res)<br>}, <span class="hljs-function">(<span class="hljs-params">reason</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'race reject'</span>,reason)<br>}); <br></code></pre></td></tr></table></figure><p>如果第一个完成状态转换的promise实例是成功的,就执行onResolved函数,输出的res是第一个成功的promise的结果。</p><p>如果第一个完成状态转换的promise实例是失败的,就执行onRejected函数,输出的reason是第一个失败的promise的结果。</p><h3 id="Promise-finally"><a href="#Promise-finally" class="headerlink" title="Promise.finally()"></a>Promise.finally()</h3><p>返回一个promise,在上一个promise结束时,无论fulfilled还是rejected,都会执行指定的回调函数,可以避免同样的语句,在resolve和reject中各写一次的情况。</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-keyword">var</span> p = <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-number">42</span>);<br><br>p.then(something).finally(cleanup);<br></code></pre></td></tr></table></figure><h3 id="Promise的局限性"><a href="#Promise的局限性" class="headerlink" title="Promise的局限性"></a>Promise的局限性</h3><h4 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h4><p>前面也有提到Promise链中的错误很容易被无意中忽略掉。由于一个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">var</span> p = foo(<span class="hljs-number">42</span>).then(..).then(..);<br></code></pre></td></tr></table></figure><p>如上面一段代码,p指向的是最后一个then返回的promise,没有指定错误处理函数,如果出错了,在外部是没有办法获取到错误通知的。</p><h4 id="单一值"><a href="#单一值" class="headerlink" title="单一值"></a>单一值</h4><p>Promise只能有一个完成值或一个拒绝理由。当我们想要传递多个参数的时候,我们将不得不使用一个对象或者数组传参。</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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-built_in">Promise</span>.resolve({<br> foo: <span class="hljs-string">'hello'</span>,<br> bar: <span class="hljs-string">'world'</span>,<br>}).then(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br> <span class="hljs-keyword">const</span> { foo, bar } = value;<br> ...<br>});<br></code></pre></td></tr></table></figure><h4 id="单决议"><a href="#单决议" class="headerlink" title="单决议"></a>单决议</h4><p>很多异步情况下,我们只会获取一个值一次,所以Promise只能被决议一次,不算是个缺点。</p><p>但对于异步的另一种模式,比如:按钮点击</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">resolve, reject</span>) </span>{<br> click(<span class="hljs-string">'#btn'</span>, resolve); <span class="hljs-comment">// 绑定btn的点击事件</span><br>}).then(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">return</span> request(...);<br>}).then(<span class="hljs-function">(<span class="hljs-params">vals</span>) =></span> {<br> ...<br>})<br></code></pre></td></tr></table></figure><p>当第一次点击按钮,可以正常工作,但当第二次点击时,由于promise已经决议,后续的resolve将会被忽略。</p><p>虽然我们可以每次点击都新建一个promise,来解决这个问题,但是每次click都启动一个新的Promise序列,也不够理想。</p><h4 id="无法取消"><a href="#无法取消" class="headerlink" title="无法取消"></a>无法取消</h4><p>一旦创建了一个Promise,并为其注册了完成和拒绝处理函数,那么它就会开始执行,我们没有办法从外部停止它的进程。</p>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>Promise学习笔记(二)</title>
<link href="/2021/05/22/Promise%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<url>/2021/05/22/Promise%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89/</url>
<content type="html"><![CDATA[<h3 id="链式调用"><a href="#链式调用" class="headerlink" title="链式调用"></a>链式调用</h3><p>Promise的.then方法会返回一个新的Promise(catch同理),所以可以将then链接起来。</p><p><strong>它的值和状态都取决于上一个then的执行情况</strong></p><h4 id="1、上一个then没有返回值"><a href="#1、上一个then没有返回值" class="headerlink" title="1、上一个then没有返回值"></a>1、上一个then没有返回值</h4><p>新promise实例状态为fulfilled,值为undefined</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br> resolve(<span class="hljs-number">1</span>);<br>})<br><br><span class="hljs-keyword">let</span> p2 = p1.then(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'xxx'</span>);<br>})<br><span class="hljs-comment">// p1完成后,p2:{<fulfilled>: undefined}</span><br></code></pre></td></tr></table></figure><h4 id="2、上一个then返回非promise的任意值"><a href="#2、上一个then返回非promise的任意值" class="headerlink" title="2、上一个then返回非promise的任意值"></a>2、上一个then返回非promise的任意值</h4><p>新promise实例状态为fulfilled,值为返回值</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br> resolve(<span class="hljs-number">2</span>);<br>})<br><br><span class="hljs-keyword">let</span> p2 = p1.then(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-number">6</span>;<br>})<br><span class="hljs-comment">// p1完成后,p2:{<fulfilled>: 6}</span><br></code></pre></td></tr></table></figure><h4 id="3、上一个then显式地返回一个promise"><a href="#3、上一个then显式地返回一个promise" class="headerlink" title="3、上一个then显式地返回一个promise"></a>3、上一个then<strong>显式</strong>地返回一个promise</h4><p>新promise实例的结果等于返回的promise的结果</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br> resolve(<span class="hljs-number">3</span>);<br>})<br><br><span class="hljs-keyword">let</span> p2 = p1.then(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-number">9</span>);<br>})<br><span class="hljs-comment">// p1完成后,p2:{<fulfilled>: 9}</span><br></code></pre></td></tr></table></figure><h4 id="4、上一个then没有提供onResolved或者onRejected方法"><a href="#4、上一个then没有提供onResolved或者onRejected方法" class="headerlink" title="4、上一个then没有提供onResolved或者onRejected方法"></a>4、上一个then没有提供onResolved或者onRejected方法</h4><p>如果上一个promise实例是成功的,而then没有提供onResolved方法,就基于上一次resolve的结果</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-keyword">let</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br> resolve(<span class="hljs-number">2</span>);<br>})<br><br><span class="hljs-keyword">let</span> p2 = p1.then();<br><span class="hljs-comment">// p1完成后,p2:{<fulfilled>: 2}</span><br></code></pre></td></tr></table></figure><h4 id="5、上一个then中抛出错误"><a href="#5、上一个then中抛出错误" class="headerlink" title="5、上一个then中抛出错误"></a>5、上一个then中抛出错误</h4><p>新promise实例状态为rejected,值为抛出的异常</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br> resolve(<span class="hljs-number">2</span>)<br>})<br><br><span class="hljs-keyword">let</span> p2 = p1.then(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'fail'</span>);<br>})<br><span class="hljs-comment">// p1完成后,p2:{<rejected>: 'fail'}</span><br></code></pre></td></tr></table></figure><h3 id="中断promise链"><a href="#中断promise链" class="headerlink" title="中断promise链"></a>中断promise链</h3><p>如果在某一步后,不想再执行后面的then,从此中断promise链,可以返回一个pending状态的promise:</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">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> resolve(<span class="hljs-number">100</span>)<br>}).then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <br>}).then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <span class="hljs-comment">//到此终止</span><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">() =></span> {})<br>}).then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <br>}).catch(<span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-built_in">console</span>.log(reason)<br>})<br></code></pre></td></tr></table></figure><p>这样写的话,最后一个then和最后一个catch都不会执行(因为无法接收到 fulfilled 或者 rejected 的promise实例)</p><h3 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h3><p>前面已经说过了,Promise中的错误处理,拒绝处理函数会被传递到then函数的第二个参数,或者用catch来捕捉。如果then中的回调函数执行出错,那么这个then的返回一个rejected状态的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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> p = <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-number">42</span>);<br><br>p.then(<span class="hljs-function">(<span class="hljs-params">msg</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(msg.toLowerCase());<br>}, <span class="hljs-function">(<span class="hljs-params">err</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(err); <span class="hljs-comment">// 永远不会执行</span><br>})<br></code></pre></td></tr></table></figure><p>p的状态是fulfilled,返回值42,但是在起成功处理函数中,number类型没有toLowerCase方法,所以会抛出一个错误。</p><p>但为什么这个错误不会被我们的错误处理函数捕获呢?</p><p>因为p这个promise已经用值42填充了,变成了fulfiiled状态,它不会再被改变。所以p.then(…)里面的错误,会被通知到p.then(…).then(…)中,但是我们没有在这里捕捉。</p><p>为了避免丢失被忽略或抛弃的Promise错误,一些开发者表示,最佳实践是在最后总以一个catch(..)结束,这种处理叫<strong>异常穿透</strong>。</p><p>如果第一个开头的promise失败了,但是后面的then都没有写onRejected函数:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> reject(<span class="hljs-number">1</span>);<br>}).then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <br>}).then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <br>}).then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <br>}).catch(<span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br> <span class="hljs-built_in">console</span>.log(reason);<br>})<br></code></pre></td></tr></table></figure><p>那么这个reject(1)就会一层一层透传到最后一个catch(注意:它不是一下子找到最后一个catch的)</p><blockquote><p>无论是then还是catch,它的执行情况都取决于上一个then()</p></blockquote><p>因为then不手动写onRejected函数,默认被这样处理:</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">.then(<span class="hljs-function"><span class="hljs-params">value</span> =></span> {<br> <br>}, <span class="hljs-function"><span class="hljs-params">reason</span> =></span> {<br><span class="hljs-keyword">throw</span> reason;<br>})<br></code></pre></td></tr></table></figure><p>但是不管怎样,在Promise链的最后一步,总存在着未捕获的错误的可能性,尽管这种可能性越来越低。</p><p>有没有办法解决这个问题呢?</p><p>有一些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-built_in">window</span>.addEventListener(<span class="hljs-string">"unhandledrejection"</span>, handler);<br></code></pre></td></tr></table></figure><p>有一种看法是:Promise应该添加一个done函数,从本质上标识标识Promise链的结束。done的回调中抛出的错误,会被当作一个全局未处理的错误,可以在try…catch块中捕获到。</p><p>然而,它并不是ES6标准的一部分,我们可以选择自己实现它:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-built_in">Promise</span>.prototype.done = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">onFulfilled, onRejected</span>) </span>{<br> <span class="hljs-built_in">this</span><br> .then(onFulfilled, onRejected)<br> .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">reason</span>) </span>{<br> <span class="hljs-comment">// 抛出一个全局错误</span><br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">throw</span> reason<br> }, <span class="hljs-number">0</span>);<br> })<br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>Promise学习笔记(一)</title>
<link href="/2021/05/22/Promise%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/2021/05/22/Promise%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p>抽象上看,Promise是JavaScript中进行异步编程的解决方案。</p><p>具体来看,Promise是一个对象,它通常用于描述 现在开始执行,一段时间后才能获得结果的异步行为,内部保存了该异步行为的结果。</p><p>Promise对象有且仅有以下3种状态:</p><ul><li>pending:待定(进行中)</li><li>fulfilled:成功</li><li>rejected:失败</li></ul><p>一个Promise的状态转换仅有以下2种,<strong>Promise一旦决议,就会一直保持其决议结果(fulfilled或rejected)不变</strong>:</p><ul><li>pending 到 fulfilled</li><li>pending 到 rejected</li></ul><h3 id="基础用法"><a href="#基础用法" class="headerlink" title="基础用法"></a>基础用法</h3><p>Promise对象构造器接收一个executor执行器</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">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br><span class="hljs-comment">//这个代码块是executor</span><br>})<br></code></pre></td></tr></table></figure><p>执行器通常承担2个任务:</p><ul><li>初始化一个异步行为</li><li>控制状态的最终转换</li></ul><p>执行器接收两个函数作为参数,其中:</p><ul><li>resolve:用于将状态 pending 转换成 fulfilled</li><li>reject:用于将状态 pending 转换成 rejected</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve,reject</span>) =></span> {<br><span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br>resolve()<br>},<span class="hljs-number">1000</span>)<br>})<br></code></pre></td></tr></table></figure><p>在setTimeout真正被执行的1000ms后,对象p的状态从pending转换成fulfilled,并将resolve对应的回调函数放入异步队列(等待执行)。</p><h3 id="实例方法-then"><a href="#实例方法-then" class="headerlink" title="实例方法.then( )"></a>实例方法.then( )</h3><p>then方法可接收2个函数作为参数</p><ul><li>第一个为onResolved:当executor中执行resolve( )的时候,就会进入onResolved这个函数,传递成功的value</li><li>第二个为onRejected:当executor中执行reject( )的时候,就会进入onRejected这个函数,传递失败的reason</li></ul><p>我们可以把这2个函数单独写在外部</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise执行成功'</span>)<br> resolve(<span class="hljs-number">3</span>)<br>})<br><br>p.then(onResolved,onRejected)<br><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onResolved</span>(<span class="hljs-params">value</span>)</span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'resolved:'</span>+ value)<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onRejected</span>(<span class="hljs-params">reason</span>)</span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'rejected:'</span>+ reason)<br>}<br><span class="hljs-comment">//输出如下:</span><br><span class="hljs-comment">//promise执行成功</span><br><span class="hljs-comment">//resolved:3</span><br></code></pre></td></tr></table></figure><p>也可以将函数直接写在then括号内(省略函数名的定义)</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise执行成功'</span>)<br> resolve(<span class="hljs-number">3</span>)<br>})<br><br>p.then(<span class="hljs-function">(<span class="hljs-params">value</span>)=></span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'rejected:'</span>+ value)<br>}, <span class="hljs-function">(<span class="hljs-params">reason</span>)=></span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'rejected:'</span>+ reason)<br>})<br><span class="hljs-comment">//与上面的写法完全等价</span><br></code></pre></td></tr></table></figure><p>then方法的参数是可选的</p><p>当参数只有onResolved的时候,可以这样写:</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">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> resolve(<span class="hljs-number">3</span>)<br>})<br><br>p.then(<span class="hljs-function">(<span class="hljs-params">value</span>)=></span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'resolved:'</span>+ value)<br>})<br></code></pre></td></tr></table></figure><p>当参数只有onRejected的时候,需要把第一个参数设置为null</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">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> reject(<span class="hljs-string">'error'</span>)<br>})<br><br>p.then(<span class="hljs-literal">null</span>,<span class="hljs-function">(<span class="hljs-params">reason</span>)=></span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'rejected:'</span>+reason)<br>})<br></code></pre></td></tr></table></figure><h3 id="实例方法-catch"><a href="#实例方法-catch" class="headerlink" title="实例方法 .catch( )"></a>实例方法 .catch( )</h3><p>catch专门用于处理失败的promise对象,它只接收一个 onRejected函数作为参数</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">let</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> reject(<span class="hljs-string">'error'</span>)<br>})<br><br>p.catch(<span class="hljs-function">(<span class="hljs-params">reason</span>) =></span> {<br> <span class="hljs-built_in">console</span>.log(reason)<br>})<br></code></pre></td></tr></table></figure><h3 id="如何判断Promise或者类似于Promise的值"><a href="#如何判断Promise或者类似于Promise的值" class="headerlink" title="如何判断Promise或者类似于Promise的值"></a>如何判断Promise或者类似于Promise的值</h3><p>虽然Promise是通过new Promise(…)语法创建,但如果用<code>p instanceof Promise</code>来检查某个值是否为promise,是不全面的,因为:</p><ul><li>Promise的值可能是从其他浏览器窗口(iframe)中接收到的,这个窗口的Promise可能和当前窗口的不一样,所以无法识别Promise实例</li><li>某一些库或框架会实现自己的Promise,而不是使用原生的ES6 Promise</li></ul><p>所以,识别Promise就是定义一个thenable的东西,任何具有then(…)方法的对象和函数,都被称为Promise一致的thenable。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> thenable = {<br><span class="hljs-function"><span class="hljs-title">then</span>(<span class="hljs-params">res</span>)</span> {<br><span class="hljs-built_in">setTimeout</span>(res, <span class="hljs-number">3000</span>)<br>}<br>}<br><span class="hljs-comment">// 1</span><br><span class="hljs-built_in">Promise</span>.resolve()<br>.then(<span class="hljs-function">()=></span>thenable)<br>.then(<span class="hljs-function">()=></span><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'3秒过去'</span>));<br><br><span class="hljs-comment">// 2</span><br>!<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br><span class="hljs-keyword">const</span> sleep = <span class="hljs-function">() =></span> thenable<br><br><span class="hljs-keyword">await</span> sleep();<br><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'3秒过去'</span>);<br>}();<br></code></pre></td></tr></table></figure><p>如上面一段代码,无论是哪一种写法,都会经过3秒然后打印。证明判断一个对象是不是Promise或行为方式类似于Promise,仅仅判断它是否有 <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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">if</span> (<br> p !== <span class="hljs-literal">null</span> && <br> (<br><span class="hljs-keyword">typeof</span> p === <span class="hljs-string">"object"</span> ||<br> <span class="hljs-keyword">typeof</span> p === <span class="hljs-string">"function"</span><br> ) &&<br> <span class="hljs-keyword">typeof</span> p.then === <span class="hljs-string">"function"</span><br> ) {<br> <span class="hljs-comment">// 这是一个thenable对象</span><br>} <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 这不是一个thenable对象</span><br>}<br></code></pre></td></tr></table></figure><p>如果我们无意给某个对象加上then函数,却不希望它被当作Promise或者thenable,那恐怕会事与愿违,他会被自动识别为thenable,按照特定的规则处理。所以这可能是有害的,可能导致难以追踪的bug。</p><blockquote><p>这种类型检测叫鸭子类型(duck typing): When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.</p></blockquote><h3 id="为什么要使用Promise"><a href="#为什么要使用Promise" class="headerlink" title="为什么要使用Promise"></a>为什么要使用Promise</h3><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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// A</span><br>ajax(<span class="hljs-string">'..'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">..</span>) </span>{<br> <span class="hljs-comment">// C</span><br>});<br><span class="hljs-comment">// B</span><br></code></pre></td></tr></table></figure><p>A和B发生于现在,C可能会延迟到将来发生,并且是在第三方的控制下。这种控制反转会出现五个问题:</p><ul><li>回调调用次数太少或太多(第三方可能会不如我们所期待地多次调用回调函数)</li><li>调用回调过早(在追踪之前)</li><li>调用回调过晚(甚至没有调用)</li><li>没有把所需要的环境/参数传给回调函数</li><li>吞掉可能出现的错误或异常</li></ul><p>Promise的特性就是用来解决这些问题的:</p><ul><li><p>解决回调调用过早或过晚:即使是立即完成的promise,其回调then函数里的内容也总是会被放到微任务队列里,异步执行,即使有多个回调函数,它们的执行是独立的,不会受到影响。比如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs js">p.then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> p.then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'C'</span>);<br> });<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'A'</span>);<br>})<br>p.then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'B'</span>);<br>})<br><span class="hljs-comment">// A B C</span><br></code></pre></td></tr></table></figure></li><li><p>解决回调次数过少(未调用):我们可以设定一个超时函数,并且用promise.race来解决超时未调用的问题</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-function"><span class="hljs-keyword">function</span> <span class="hljs-title">timeout</span>(<span class="hljs-params">delay</span>) </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Timeout!'</span>);<br> });<br> });<br>}<br><span class="hljs-built_in">Promise</span>.race([p, timeout(<span class="hljs-number">3000</span>)]).then(resCb, rejCb);<br><span class="hljs-comment">// 当超时或者p抛出错误,都会调用rejCb</span><br></code></pre></td></tr></table></figure></li><li><p>解决回调次数过多:若代码试图多次调用resolve或reject,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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {<br> <span class="hljs-comment">// ...</span><br> resolve(<span class="hljs-string">'1'</span>);<br> <span class="hljs-comment">// 之后的决议全部忽略</span><br> resolve(<span class="hljs-string">'2'</span>);<br> reject(<span class="hljs-string">'3'</span>);<br>});<br>p.then(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> { <span class="hljs-built_in">console</span>.log(value); }, <span class="hljs-function">(<span class="hljs-params">reason</span>) =></span> { <span class="hljs-built_in">console</span>.error(reason); }); <span class="hljs-comment">// 1</span><br></code></pre></td></tr></table></figure></li></ul><ul><li><p>解决未能传递参数/环境值:Promise的resolve和reject都只能传一个参数,第二个参数及之后的都会被忽略,如果未显式定义,则这个值为undefined,详情见后文的链式调用流。</p></li><li><p>解决吞掉异常或错误的问题:每个then函数都会返回另一个promise,所以任何地方抛出错误,都会导致相应的promise被拒绝,可以在catch或者then的第二个参数中定义异常处理,详情见后文的错误处理。</p></li></ul><p>参考文章:</p><blockquote><p><a href="https://mp.weixin.qq.com/s/s4YE7upruEcqdWwtuK1e6Q">再一次深入理解Promise</a></p><p>《你不知道的JavaScript》(中卷)</p></blockquote>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>读《你不知道的JavaScript》——对象不变性</title>
<link href="/2021/04/06/%E8%AF%BB%E3%80%8A%E4%BD%A0%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84JavaScript%E3%80%8B%E2%80%94%E2%80%94%E5%AF%B9%E8%B1%A1%E4%B8%8D%E5%8F%98%E6%80%A7/"/>
<url>/2021/04/06/%E8%AF%BB%E3%80%8A%E4%BD%A0%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84JavaScript%E3%80%8B%E2%80%94%E2%80%94%E5%AF%B9%E8%B1%A1%E4%B8%8D%E5%8F%98%E6%80%A7/</url>
<content type="html"><![CDATA[<p>我们知道,ES6引入的const是用来定义常量的,对于基本数据类型来说,变量存储的是数据的值,所以用起来没问题,但当我们使用const定义一个数组或者对象时,由于存储的是对象的地址,所以即使我们修改了对象的某一键值,或者为数组增加、删除一项,它的存储地址,所以依旧是常量,这有时候不是我们想要的。</p><p>那么要如何定义一个“真正”的对象类型的常量呢?</p><h4 id="使用对象的属性描述符"><a href="#使用对象的属性描述符" class="headerlink" title="使用对象的属性描述符"></a>使用对象的属性描述符</h4><p>使用<code>configurable: false</code>和<code>writable: false</code>创建一个常量属性(不可修改、重定义或删除)</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> myObj = {};<br><span class="hljs-built_in">Object</span>.defineProperty(myObj, <span class="hljs-string">'a'</span>, {<br> value: <span class="hljs-number">1</span>,<br> configurable: <span class="hljs-literal">false</span>,<br> writable: <span class="hljs-literal">false</span><br>})<br></code></pre></td></tr></table></figure><h4 id="禁止扩展"><a href="#禁止扩展" class="headerlink" title="禁止扩展"></a>禁止扩展</h4><p>禁止一个对象添加新的属性。原有的属性可以修改可以使用delete删除。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> myObj = {<span class="hljs-attr">a</span>: <span class="hljs-number">2</span>};<br><span class="hljs-built_in">Object</span>.preventExtensions(myObj);<br>myObj.b = <span class="hljs-number">3</span>;<br>myObj.b; <span class="hljs-comment">// undefined</span><br>myObj.a = <span class="hljs-number">3</span>;<br>myObj.a; <span class="hljs-comment">// 3</span><br><span class="hljs-keyword">delete</span> myObj.a <span class="hljs-comment">// true</span><br></code></pre></td></tr></table></figure><h4 id="密封"><a href="#密封" class="headerlink" title="密封"></a>密封</h4><p><code>Object.seal</code>:创建一个“密封”的对象,实际上是在现有的对象上调用<code>Object.preventExtensions(..)</code>并把所有的现有属性标记为<code>configurable: false</code></p><p>密封之后,不能添加新属性,也不能重新配置或删除任何现有属性,但是可以修改现有属性的值。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span> };<br><span class="hljs-built_in">Object</span>.seal(obj);<br>obj.b = <span class="hljs-number">1</span>;<br>obj.b <span class="hljs-comment">// undefined</span><br><span class="hljs-built_in">Object</span>.defineProperty(obj, <span class="hljs-string">'a'</span>, {<br> configurable: <span class="hljs-literal">true</span><br>}) <span class="hljs-comment">// Uncaught TypeError: Cannot redefine property: a</span><br>obj.a = <span class="hljs-number">3</span><br>obj.a <span class="hljs-comment">// 3</span><br></code></pre></td></tr></table></figure><h4 id="冻结"><a href="#冻结" class="headerlink" title="冻结"></a>冻结</h4><p><code>Object.freeze(..)</code>创建一个冻结对象,实际上是在现有的对象上调用<code>Object.seal(..)</code>并把所有的“数据访问”属性标记为<code>writable: false</code>,这样就无法修改属性的值。这个方法是级别最高的不可变性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-built_in">Object</span>.freeze(obj)<br>obj.a = <span class="hljs-number">4</span><br>obj.a <span class="hljs-comment">// 3</span><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>页面加载的优化</title>
<link href="/2021/03/23/%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E7%9A%84%E4%BC%98%E5%8C%96/"/>
<url>/2021/03/23/%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E7%9A%84%E4%BC%98%E5%8C%96/</url>
<content type="html"><![CDATA[<h2 id="HTML的解析"><a href="#HTML的解析" class="headerlink" title="HTML的解析"></a>HTML的解析</h2><p>在浏览器渲染引擎中,有一个HTML解析器,负责将HTML字节流转换为DOM结构。</p><p>网络进程中加载了多少数据,HTML解析器就会解析多少数据(不会等整个HTML文档加载完,再进行解析)</p><p>一般流程:</p><ol><li>通过分词器,将字节流转换为token</li><li>将token解析为DOM节点</li><li>将DOM节点添加到DOM树中</li></ol><h2 id="解析HTML时遇到JS和CSS会怎么样"><a href="#解析HTML时遇到JS和CSS会怎么样" class="headerlink" title="解析HTML时遇到JS和CSS会怎么样"></a>解析HTML时遇到JS和CSS会怎么样</h2><p>结论是:JS是全阻塞的,CSS是半阻塞的。</p><p>如果遇到script标签或者外部的JS文件,会阻塞DOM的解析和其他资源的加载,因为JS脚本可能会修改DOM结构。</p><p>如果遇到css文件,不会阻塞DOM的解析,但是会阻塞JS的加载,因为JS脚本中可能依赖最新样式。也会阻塞页面的渲染,在cssom树构建好之前,浏览器不会有任何显示,因为没有css的页面通常是凌乱的,无法使用的。</p><h2 id="浏览器预加载"><a href="#浏览器预加载" class="headerlink" title="浏览器预加载"></a>浏览器预加载</h2><p>虽然在脚本执行的时候,构建DOM是不安全的。但我们仍然可以继续解析HTML,查看它所需要的资源。这就是现在很多浏览器支持的预解析。</p><p>当脚本正在执行时,仍然继续解析HTML,找到以来的文件,在后台并行的下载。当脚本执行完毕之后,这些文件可能已经下载完成,可以直接使用了。虽然脚本可能会改变HTML结构,导致一些“预测”的浪费,但是这不常见,预解析还是会带来性能上的提升。</p><h2 id="prefetch"><a href="#prefetch" class="headerlink" title="prefetch"></a>prefetch</h2><p>允许浏览器获取资源并存储在缓存中,他告诉浏览器未来可能会使用某个资源,浏览器会在空闲时间加载。</p><p>prefetch的分类:</p><ul><li>DNS prefetch</li><li>Link prefetch</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"prefetch"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"script"</span>></span><br></code></pre></td></tr></table></figure><blockquote><p>prerender:和prefetch非常类似,优化了导航到下一页面的资源加载。区别是:prerender在后台渲染了整个页面。</p><p><code><link rel="prerender" href="..."></code></p></blockquote><h2 id="preload"><a href="#preload" class="headerlink" title="preload"></a>preload</h2><p>向浏览器声明一个需要提前加载的资源,当资源真正需要使用时,立即执行。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"style"</span>></span><br>或者<br><span class="hljs-tag"><<span class="hljs-name">script</span>></span><br><span class="javascript"><span class="hljs-keyword">const</span> link = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'link'</span>);</span><br><span class="javascript"> link.rel = <span class="hljs-string">"preload"</span>;</span><br><span class="javascript"> link.href = <span class="hljs-string">'...'</span>;</span><br><span class="javascript"> link.as = <span class="hljs-string">'style'</span>;</span><br><span class="javascript"> <span class="hljs-built_in">document</span>.head.appendChild(link);</span><br><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br></code></pre></td></tr></table></figure><h2 id="三者不同之处"><a href="#三者不同之处" class="headerlink" title="三者不同之处"></a>三者不同之处</h2><ul><li><p>浏览器预解析和preload的区别</p><p>浏览器的预解析,只能预先加载HTML声明的资源。但是preload指令事实上克服了这个限制,它允许预加载在CSS和JavaScript中定义的资源,并允许决定何时应用这个资源。</p></li><li><p>preload和prefetch的区别</p><p>preload专注于当前页面,以更高的优先级加载资源。prefetch专注于下一个页面将要加载的资源,并以低优先级加载。</p></li></ul><h2 id="不同资源的优先级"><a href="#不同资源的优先级" class="headerlink" title="不同资源的优先级"></a>不同资源的优先级</h2><p>一个资源有五个优先级:<code>Highest High Medium Low Lowest</code></p><p>一般来说,HTML/CSS的优先级最高,其次是font字体资源,然后是图片资源(出现在视口>没有出现在视口)</p><ul><li>对于prefetch的资源,优先级默认为最低,Lowest</li><li>对于preload的资源,可以通过as或者type指定优先级(比如通过<code>as="style"</code>指定,即使资源不是样式文件)</li><li>没有as的会被当作异步请求</li></ul><h2 id="使用案例"><a href="#使用案例" class="headerlink" title="使用案例"></a>使用案例</h2><ul><li>使用preload提前加载字体文件</li><li>在onload中预加载第二屏的内容,这也用户滚动能更快看到次屏内容</li><li>分析页面上的链接,使用prefetch加载下一跳页面的资源</li><li>预测用户行为,比如在商品列表页,如果用户鼠标停留在某个商品超过一段时间,就去分析商品详情页所需要的资源进行preload</li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://zhuanlan.zhihu.com/p/48521680">使用 Preload/Prefetch 优化你的应用</a></p><p><a href="https://juejin.cn/post/6844903646996480007">什么是 Preload,Prefetch 和 Preconnect?</a></p>]]></content>
<categories>
<category>浏览器</category>
</categories>
</entry>
<entry>
<title>解读Redux中间件源码</title>
<link href="/2021/03/20/redux%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<url>/2021/03/20/redux%E4%B8%AD%E9%97%B4%E4%BB%B6/</url>
<content type="html"><![CDATA[<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></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">applyMiddleware</span>(<span class="hljs-params">...middlewares</span>) </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">createStore</span>) =></span> <span class="hljs-function">(<span class="hljs-params">reducer, preloadedState, enhancer</span>) =></span> {<br> <span class="hljs-keyword">const</span> store = createStore(reducer, preloadedState, enhancer)<br> <span class="hljs-keyword">let</span> dispatch = store.dispatch<br> <span class="hljs-keyword">let</span> chain = []<br> <br> <span class="hljs-keyword">const</span> middlewareAPI = {<br> getState: store.getState,<br> dispatch: <span class="hljs-function">(<span class="hljs-params">action</span>) =></span> dispatch(action)<br> }<br> <span class="hljs-comment">// 注入了store</span><br> chain = middlewares.map(<span class="hljs-function"><span class="hljs-params">middleware</span> =></span> middleware(middlewareAPI))<br> <span class="hljs-comment">// 注入了每个中间件的next</span><br> dispatch = compose(...chain)(store.dispatch)<br> <span class="hljs-comment">// 现在的dispatch就是接收一个action,会依次沿着中间件链依次调用的函数。</span><br> <span class="hljs-keyword">return</span> {<br> ...store,<br> dispatch<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>主要步骤:</p><h2 id="1-中间件串联:"><a href="#1-中间件串联:" class="headerlink" title="1.中间件串联:"></a>1.中间件串联:</h2><p>目的:将所有的middleware串联在一起,并保证最后一个执行的是dispatch(action)。</p><p><strong>compose方法</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></pre></td><td class="code"><pre><code class="hljs js">compose(funcC, funcB, funcA)() === funcC(funcB(funcA()))<br><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compose</span>(<span class="hljs-params">...funcs</span>) </span>{<br> <span class="hljs-keyword">if</span>(funcs.length === <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-params">args</span> =></span> args<br> }<br> <span class="hljs-keyword">if</span>(funcs.length === <span class="hljs-number">1</span>) {<br> <span class="hljs-keyword">return</span> funcs[<span class="hljs-number">0</span>];<br> }<br> <span class="hljs-keyword">return</span> funcs.reduce(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> <span class="hljs-function">(<span class="hljs-params">...args</span>) =></span> a(b(...args)));<br>}<br></code></pre></td></tr></table></figure><p>思路:用compose方法组合函数,封装最后一个函数作为dispatch(action)方法。</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">const</span> middleware1 = <span class="hljs-function"><span class="hljs-params">action</span> =></span> action;<br><span class="hljs-keyword">const</span> middleware2 = <span class="hljs-function"><span class="hljs-params">action</span> =></span> action;<br><span class="hljs-keyword">const</span> final = <span class="hljs-function"><span class="hljs-params">action</span> =></span> store.dispatch(action);<br><br>compose(final, middlewares2, middleware1)(action);<br></code></pre></td></tr></table></figure><h2 id="2-中间件可访问store的state"><a href="#2-中间件可访问store的state" class="headerlink" title="2.中间件可访问store的state"></a>2.中间件可访问store的state</h2><p>给每个middleware都传递store,保证中间件访问到的store是一致的。</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-keyword">const</span> middleware1 = <span class="hljs-function">(<span class="hljs-params">store, action</span>) =></span> action;<br><span class="hljs-keyword">const</span> middleware2 = <span class="hljs-function">(<span class="hljs-params">store, action</span>) =></span> action;<br><span class="hljs-keyword">const</span> final = <span class="hljs-function">(<span class="hljs-params">store, action</span>) =></span> store.dispatch(action);<br></code></pre></td></tr></table></figure><p>但是现在就没有办法使用compose函数进行组合了,因为参数类型要求是(store, action),而上一个中间件返回的是action,所以要使用函数柯里化配合compose:</p><p>通过循环将store传递给所有的中间件,这里是延迟计算的思想。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> middleware1 = <span class="hljs-function">(<span class="hljs-params">store</span>) =></span> <span class="hljs-function"><span class="hljs-params">action</span> =></span> action;<br><span class="hljs-keyword">const</span> middleware2 = <span class="hljs-function">(<span class="hljs-params">store</span>) =></span> <span class="hljs-function"><span class="hljs-params">action</span> =></span> action;<br><span class="hljs-keyword">const</span> final = <span class="hljs-function">(<span class="hljs-params">store</span>) =></span> <span class="hljs-function"><span class="hljs-params">action</span> =></span> dispatch(action);<br><br><span class="hljs-keyword">const</span> chain = [final, middleware2, middleware1].map(<span class="hljs-function"><span class="hljs-params">midItem</span> =></span> midItem(store));<br>compose(...chain)(action);<br></code></pre></td></tr></table></figure><h2 id="3-中间件调用的dispatch方法"><a href="#3-中间件调用的dispatch方法" class="headerlink" title="3.中间件调用的dispatch方法"></a>3.中间件调用的dispatch方法</h2><p>在源码中可以看到,store的dispatch是被改装过的dispatch(含有中间件调用链),所以如果我们在中间件在再使用这个dispatch,会造成死循环。所以还得给每个中间件传入原生的dispatch。</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">const</span> middlewareAPI = {<br> getState: store.getState,<br> dispatch: <span class="hljs-function">(<span class="hljs-params">action</span>) =></span> dispatch(action)<br>}<br><br>chain = middlewares.map(<span class="hljs-function"><span class="hljs-params">middleware</span> =></span> middleware(middlewareAPI))<br><span class="hljs-comment">// 现在dispatch被改变了,但是中间件用的还是原生dispatch方法</span><br>dispatch = compose(...chain)(store.dispatch);<br></code></pre></td></tr></table></figure><h2 id="4-保证中间件不断裂"><a href="#4-保证中间件不断裂" class="headerlink" title="4.保证中间件不断裂"></a>4.保证中间件不断裂</h2><p>我们之前定义了中间件的格式是 <code>mid = store => action => action</code>,但要怎么保证中间件不会因为没有返回action而断裂呢?答案是:要保证上一个中间件有下一个中间件的注册,就不会断裂。所以next就是执行下一个中间件的方法,最后一个next是dispatch(action).</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">const</span> middleware1 = <span class="hljs-function"><span class="hljs-params">store</span> =></span> <span class="hljs-function"><span class="hljs-params">next</span> =></span> <span class="hljs-function"><span class="hljs-params">action</span> =></span> {<span class="hljs-built_in">console</span>.log(<span class="hljs-number">1</span>); next(action);}<br><span class="hljs-keyword">const</span> middleware2 = <span class="hljs-function"><span class="hljs-params">store</span> =></span> <span class="hljs-function"><span class="hljs-params">next</span> =></span> <span class="hljs-function"><span class="hljs-params">action</span> =></span> {<span class="hljs-built_in">console</span>.log(<span class="hljs-number">2</span>); next(action);}<br><br><span class="hljs-keyword">const</span> chain = [middleware1, middleware2].map(<span class="hljs-function"><span class="hljs-params">midItem</span> =></span> midItem({<br> dispatch: <span class="hljs-function">(<span class="hljs-params">action</span>) =></span> store.dispatch(action);<br>}))<br><br>dispatch = compose(...chain)(store.dispatch);<br></code></pre></td></tr></table></figure><h2 id="总结:"><a href="#总结:" class="headerlink" title="总结:"></a>总结:</h2><p><code>(store) => (next) => (action) => {...next(action);...}</code></p><ol><li>中间件机制的核心是使用compose组合函数,将所有的中间件串联起来。</li><li>配合compose对单参数的使用,对每个中间件使用柯里化的设计,使每个中间件共享store的state和dispatch。</li><li>为了保证中间件不会因为没有返action而断裂,用next来保证可以本个中间件中注册下一个中间件。next最后执行的是store.dispatch。</li></ol><p>参考资料:<a href="https://juejin.cn/post/6844903569032740877#heading-3">Redux Middleware中间件源码 分析</a></p><h2 id="附:redux-thunk的实现"><a href="#附:redux-thunk的实现" class="headerlink" title="附:redux-thunk的实现"></a>附:redux-thunk的实现</h2><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-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createThunkMiddleware</span>(<span class="hljs-params">extraArguments</span>) </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">{ dispatch, getState}</span>) =></span> <span class="hljs-function">(<span class="hljs-params">next</span>) =></span> <span class="hljs-function">(<span class="hljs-params">action</span>) =></span> {<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> action === <span class="hljs-string">'function'</span>) {<br> <span class="hljs-keyword">return</span> action(dispatch, getState, extraArguments);<br> }<br> <br> <span class="hljs-keyword">return</span> next(action);<br> };<br>}<br><br><span class="hljs-keyword">const</span> thunk = createThunkMiddleware();<br>thunk.withExtraArgument = createThunkMiddlewawre;<br><br>exprt <span class="hljs-keyword">default</span> thunk;<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
</tags>
</entry>
<entry>
<title>React Hook的闭包陷阱</title>
<link href="/2021/03/08/React-Hook%E7%9A%84%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1/"/>
<url>/2021/03/08/React-Hook%E7%9A%84%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1/</url>
<content type="html"><![CDATA[<h2 id="什么是闭包陷阱"><a href="#什么是闭包陷阱" class="headerlink" title="什么是闭包陷阱"></a>什么是闭包陷阱</h2><p>所谓的闭包陷阱,就是在函数式组件中,我们拿不到通过useState定义的变量的最新值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">const App = ()=>{<br> const [count,setCount] = useState(0)<br> useEffect(()=>{<br> const timeId = setInterval(()=>{<br> console.log(count);<br> },1000)<br> return ()=>{clearInterval(timeId)}<br> },[])<br> return (<br> <div><br> <span>{count}</span><br> <button onClick={()=>{setCount(count+1)}}>ADD</button><br> </div><br> )<br>}<br></code></pre></td></tr></table></figure><p>比如说,这段代码我们点击button,页面上渲染的count值是会增加,但控制台打印的值,始终会是0。</p><p>事实上,esLint也会报一个warning:</p><p><img src="/2021/03/08/React-Hook%E7%9A%84%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1/%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1.png" alt="闭包陷阱"></p><p>始终输出0的原因是,函数式组件每次渲染都会有自己的Effect函数和count值,我们依赖数组设为[],后面useEffect也并没有更新了,所以setInterval里读取的都是第一次渲染的count,也就是0。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><ol><li>在依赖数组中加入count</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs react">useEffect(()=>{<br> const timeId = setInterval(()=>{<br> console.log(count);<br> },1000)<br> return ()=>{clearInterval(timeId)}<br> },[count])<br></code></pre></td></tr></table></figure><p>这样每次渲染,都会更新useEffect函数,拿到最新的count值。</p><ol start="2"><li>useRef存储变量</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs react">const [count, setCount] = useState(0);<br>const latestCount = useRef();<br>latestCount.current = count;<br>useEffect(()=>{<br>const timeId = setInterval(()=>{<br>console.log(latestCount.current)<br>}, 1000);<br>return () => clearInterval(timeId);<br>}, []);<br></code></pre></td></tr></table></figure><h2 id="导致闭包陷阱的原因"><a href="#导致闭包陷阱的原因" class="headerlink" title="导致闭包陷阱的原因"></a>导致闭包陷阱的原因</h2><ol><li>异步函数</li><li><code>window.addEventListener</code>绑定事件</li></ol>]]></content>
<categories>
<category>React</category>
</categories>
</entry>
<entry>
<title>函数式组件的渲染</title>
<link href="/2021/03/08/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E7%9A%84%E6%B8%B2%E6%9F%93/"/>
<url>/2021/03/08/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E7%9A%84%E6%B8%B2%E6%9F%93/</url>
<content type="html"><![CDATA[<p>“<strong>组件每一次渲染都会有自己的props和state。每一次渲染都会有自己的事件处理函数</strong>。”接下来,我们来深刻理解这句话。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs react">function Counter(){<br> const [count, setCount] = useState(0);<br> return (<br> <p>you clicked {count} times</p><br> <button onClick={() => setCount(count+1)}>+++</button><br> )<br>}<br></code></pre></td></tr></table></figure><p>这段代码里,我们如果点击button,显示的count的值确实会增加。它的原理是count“监听”状态的变化然后自动更新吗?其实并不是。count不是双向绑定或者“watcher”或者其他任何东西,它只是一个普通的变量。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs react">const count = 0;<br>...<br><p>you clicked {count} times</p><br></code></pre></td></tr></table></figure><p>初始状态下,count值是0。当调用setCount(1)的时候,React重新渲染了组件,这次count的值是1。也就是说每次渲染拿到的count值是独立的,不是同一份。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">// During first render<br>function Counter() {<br> const count = 0; // Returned by useState()<br> // ...<br> <p>You clicked {count} times</p><br> // ...<br>}<br><br>// After a click, our function is called again<br>function Counter() {<br> const count = 1; // Returned by useState()<br> // ...<br> <p>You clicked {count} times</p><br> // ...<br>}<br><br>// After another click, our function is called again<br>function Counter() {<br> const count = 2; // Returned by useState()<br> // ...<br> <p>You clicked {count} times</p><br> // ...<br>}<br></code></pre></td></tr></table></figure><p>关键在于,任意一次渲染中,const常量是不会变化的,我们看到的输出变化,是因为组件被重新调用了,并且传入了不同的参数,这个参数独立于其他任何一次渲染。</p><p>对于基本变量来说是这样的,那事件处理函数的情况呢?</p><p>答案其实一样,函数式组件的渲染,每次都会从上到下执行一遍代码。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">function Counter(){<br> const [count, setCount] = useState(0);<br> function handleClick(){<br> setTimeout(() => {<br> console.log(count);<br> },5000);<br> }<br> return (<br> <><br> <p>you clicked {count} times</p><br> <button onClick={() => setCount(count+1)}>+++</button><br> <button onClick={handleClick}>console</button><br> </><br> )<br>}<br></code></pre></td></tr></table></figure><p>如果我们在点击完console button后的5s时间内点击了多次+++按钮,请问会输出什么?答案是:0。</p><p>它的原理就类似于普通函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sayHi</span>(<span class="hljs-params">person</span>)</span>{<br><span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">()=></span>{<span class="hljs-built_in">console</span>.log(person.name)}, <span class="hljs-number">1000</span>);<br>}<br><span class="hljs-keyword">let</span> someone = {<span class="hljs-attr">name</span>: <span class="hljs-string">"Dan"</span>};<br>sayHi(someone); <span class="hljs-comment">// "Dan"</span><br><br>someone = {<span class="hljs-attr">name</span>: <span class="hljs-string">"Mike"</span>};<br>sayHi(someone); <span class="hljs-comment">// "Mike"</span><br><br>someone = {<span class="hljs-attr">name</span>: <span class="hljs-string">"John"</span>};<br>sayHi(someone); <span class="hljs-comment">// "John"</span><br></code></pre></td></tr></table></figure><p>即使在console.log执行之前,someone已经被赋予了新值,但在sayHi函数内,person会和某次调用的someone关联,也就是说每次调用的person都是独立的,虽然名字一样,但保存在不同的空间里。</p><p>同样的,组件每次渲染都会有新的handleClick函数,它会记住这次渲染的count值。虽然名字和之前渲染的handleClick一样,但他们是独立的。也就是说,事件处理函数“属于”某一次特定的渲染。</p><p>再来看看useEffect这个Hook,它和上面分析的结果一样,每次渲染都会有自己的Effects。直接上代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">function Counter(){<br> const [count, setCount] = useState(0);<br> useEffect(() => {<br> setTimeout(()=>{<br> console.log(count);<br> }, 3000);<br> })<br> return (<br> <div><br> <p>You clicked {count} times</p><br> <button onClick={() => setCount(count + 1)}><br> Click me<br> </button><br> </div><br> )<br>}<br></code></pre></td></tr></table></figure><p>这段代码,如果在3s内连续点击五次,会依次输出<code>0,1,2,3,4,5</code>。因为每次调用effect,它看到的都是属于本次渲染的唯一的count。</p><p>总的来说,每一次渲染的组件都会拥有它自己的所有东西(props、state、事件处理函数、effects、定时器、API调用等)。</p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
<tag>函数式组件</tag>
</tags>
</entry>
<entry>
<title>受控组件与非受控组件(三)</title>
<link href="/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%89%EF%BC%89/"/>
<url>/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%89%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="非受控组件"><a href="#非受控组件" class="headerlink" title="非受控组件"></a>非受控组件</h2><p>非受控组件指的是表单的数据不再由React组件进行管理,而是交给DOM节点处理,可以使用refs属性来从DOM节点中获取到数据。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">class NameForm extends React.Component {<br> constructor(props){<br> super(props);<br> this.input = React.createRef();<br> }<br> handleSubmit(e){<br> console.log(this.input.current.value);<br> e.preventDefault();<br> }<br> render(){<br> return (<br> <form onSubmit={this.handleSubmit.bind(this)}><br> <input type="text" ref={this.input} /><br> <input type="submit" value="submit" /><br> </form><br> )<br> }<br>}<br></code></pre></td></tr></table></figure><p>非受控组件将代码存储在真实的DOM节点中,可以节省很多代码。</p><p>在React组件中,表单元素的value值会覆盖这个DOM节点,如果我们想要给组件添加默认值,又不影响后续的输入,可以使用<code>defaultValue</code>属性:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs react"><input defaultValue="hi" ref={this.input} /><br></code></pre></td></tr></table></figure><h2 id="什么时候用受控组件或非受控组件"><a href="#什么时候用受控组件或非受控组件" class="headerlink" title="什么时候用受控组件或非受控组件"></a>什么时候用受控组件或非受控组件</h2><p>受控组件的特点是它和state是类似双向绑定的,也就是通过state可以实时拿到表单中的数据。</p><blockquote><p>The state gives the value to the input, and the input asks the <code>Form</code> to change the current value.</p></blockquote><p>所以受控组件能快速响应表单内容的变化,它适用于以下场景:</p><ul><li>输入实时验证</li><li>依照某个条件禁用或不禁用一个button</li><li>强制输入规范</li><li>多个输入映射到同一份data</li></ul><p>非受控组件的使用场景主要有:</p><ul><li>对DOM 元素焦点的控制、内容选择或者媒体播放;</li><li>通过对DOM元素控制,触发动画特效;</li><li>通第三方DOM库的集成。</li></ul><p>参考资料:</p><p><a href="https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/">关于受控和非受控组件</a></p><p><a href="https://zh-hans.reactjs.org/docs/uncontrolled-components.html">React官方文档</a></p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
</tags>
</entry>
<entry>
<title>受控组件与非受控组件(二)</title>
<link href="/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<url>/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%BA%8C%EF%BC%89/</url>
<content type="html"><![CDATA[<p>讲非受控组件之前,我们先理解Refs and the DOM的概念。</p><h2 id="Refs-and-DOM"><a href="#Refs-and-DOM" class="headerlink" title="Refs and DOM"></a>Refs and DOM</h2><p>在React的典型数据流中,props是父子组件的唯一交互方式。要修改子组件,就要通过修改props来重新渲染子组件。而refs属性,则提供了在典型数据流以外,强制修改子组件的方式。被修改的子组件可能是一个DOM元素,也有可能是一个组件React实例。</p><h4 id="使用refs属性"><a href="#使用refs属性" class="headerlink" title="使用refs属性"></a>使用refs属性</h4><p>通过<code>React.createRef()</code>函数创建一个ref,并通过ref属性附加到React元素上:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs react">class MyComponent extends React.Component {<br>constructor(){<br>super();<br>this.myRef = React.createRef();<br> this.handleClick = this.handleClick.bind(this);<br>}<br> handleClick(){<br> console.log(this.myRef.current)<br> }<br>render(){<br>return <button ref={this.myRef} onClick={this.handleClick}>点击</button><br>}<br>}<br></code></pre></td></tr></table></figure><p>当 ref 被传递给 <code>render</code> 中的元素时,对该节点的引用可以在 ref 的 <code>current</code> 属性中被访问。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs react">const node = this.myRef.current<br></code></pre></td></tr></table></figure><p>如果<code>ref</code>属性属于一个HTML元素,则接收底层 DOM 元素作为<code>current</code>属性。</p><p>如果<code>ref</code>属性属于一个class组件,<code>ref</code> 对象接收组件的挂载实例作为其 <code>current</code> 属性。</p><p>React组件会在挂载的时候给current属性传入DOM元素,并在卸载的时候传入null值。<code>ref</code> 会在 <code>componentDidMount</code> 或 <code>componentDidUpdate</code> 生命周期钩子触发前更新。</p><h4 id="refs转发"><a href="#refs转发" class="headerlink" title="refs转发"></a>refs转发</h4><p>不能在函数组件上使用ref,因为函数组件没有实例。但可以在函数式组件内部使用(使用useRef hook),只要它指向一个DOM或者class组件实例。要想在函数式组件中使用ref属性,可以使用forward ref:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs react">const FancyButton = React.forwardRef((prop, refProp) => {<br> return <button ref={refProp}></button><br>})<br>const refForward = React.createRef();<br><FancyButton ref={refForward}></FancyButton><br></code></pre></td></tr></table></figure><p>这样通过ref可以访问到button元素,这个FancyButton接收了ref,并向下传递给子组件,就叫ref转发。</p><h4 id="refs回调"><a href="#refs回调" class="headerlink" title="refs回调"></a>refs回调</h4><p>通过ref回调可以在父组件在访问到子组件。通过给ref属性传入一个函数:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">function CustomTextInput(props) {<br> return (<br> <div><br> <input ref={props.inputRef} /></div><br> );<br>}<br><br>class Parent extends React.Component {<br> render() {<br> return (<br> <CustomTextInput<br> inputRef={el => this.inputElement = el} /><br> );<br> }<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,我们在父组件中把回调函数以属性的方式传给子组件,子组件把ref属性设置为这个回调函数,这样,父组件的<code>inputElement</code>就指向子组件的input元素了。</p><p>参考资料:</p><p><a href="https://zh-hans.reactjs.org/docs/refs-and-the-dom.html">React官方文档</a></p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
</tags>
</entry>
<entry>
<title>受控组件与非受控组件(一)</title>
<link href="/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="受控组件"><a href="#受控组件" class="headerlink" title="受控组件"></a>受控组件</h2><p>React里面,HTML的表单元素工作方式和其他DOM不太一样。表单元素通常会维护内部的状态,这些状态会根据用户的输入而更新。而React中,可变状态通常保存在state中,并且只能通过setState更新。</p><p>所以React就把这两种方式结合起来,让state成为React的“唯一数据源”。并且React还控制着用户的输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。</p><p>所谓的受控组件和非受控组件,是针对表单元素而言的。</p><p>受控组件有以下几个特点:</p><ul><li>表单元素依赖于状态,它的值始终由React的state来驱动。</li><li>表单元素的修改,会实时映射到state,跟双向绑定类似。</li><li>必须继承React.Component</li><li>受控组件必须在表单上使用onChange来绑定事件</li></ul><h4 id="常见的受控组件:"><a href="#常见的受控组件:" class="headerlink" title="常见的受控组件:"></a>常见的受控组件:</h4><ol><li>input</li></ol><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InputForm</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{<br><span class="hljs-function"><span class="hljs-title">constructor</span>(<span class="hljs-params">props</span>)</span>{<br><span class="hljs-built_in">this</span>.state = {<br>value: <span class="hljs-string">''</span><br>}<br>}<br><span class="hljs-built_in">this</span>.handleChange = handleChange.bind(<span class="hljs-built_in">this</span>);<br><span class="hljs-function"><span class="hljs-title">handleChange</span>(<span class="hljs-params">e</span>)</span>{<br><span class="hljs-built_in">this</span>.setState({<span class="hljs-attr">value</span>: e.target.value})<br>}<br><span class="hljs-function"><span class="hljs-title">render</span>(<span class="hljs-params"></span>)</span>{<br><span class="hljs-keyword">return</span> (<br><form><br><input<br>value={<span class="hljs-built_in">this</span>.state.value}<br>onChange={<span class="hljs-built_in">this</span>.handleChange}<br>/><br></form><br>)<br>}<br>}<br></code></pre></td></tr></table></figure><p>但如果input标签的type取值为“file”,它的value是只读的,所以它是非受控组件。</p><ol start="2"><li>textarea</li></ol><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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 scala"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TextAreaForm</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{<br>constructor(){<br><span class="hljs-keyword">this</span>.state = {<br>value: <span class="hljs-string">"默认文本"</span><br>}<br>}<br>render(){<br><span class="hljs-keyword">return</span> (<br><textarea <br> value={<span class="hljs-keyword">this</span>.state.value} <br> onChange={(e) => {<span class="hljs-keyword">this</span>.setState({value: e.target.value})}}<br>/><br>)<br>}<br>}<br></code></pre></td></tr></table></figure><ol start="3"><li><p>select </p><p>React不会使用selected属性,而是在selected标签里用value表示选中项。</p><p>对于多选框。可以在value里传入一个数组。</p></li></ol><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectForm</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{<br><span class="hljs-function"><span class="hljs-title">cosntructor</span>(<span class="hljs-params"></span>)</span>{<br><span class="hljs-built_in">this</span>.state = {<br>value: <span class="hljs-string">"item1"</span><br><span class="hljs-comment">// value: ["item1", "item2"]</span><br>}<br>}<br><span class="hljs-function"><span class="hljs-title">render</span>(<span class="hljs-params"></span>)</span>{<br><span class="hljs-keyword">return</span> (<br><select<br> {<span class="hljs-comment">/* mutiply={true} */</span>}<br>value={<span class="hljs-built_in">this</span>.state.value}<br>onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {<span class="hljs-built_in">this</span>.setState({e.target.value})}}<br>><br> <option value=<span class="hljs-string">"item1"</span>>item1</option><br> <option value=<span class="hljs-string">"item2"</span>>item2</option><br> <option value=<span class="hljs-string">"item3"</span>>item3</option><br></select><br>)<br>}<br>}<br></code></pre></td></tr></table></figure><p>注意:</p><ul><li><p>我们必须给受控组件添加onChange事件监听函数,否则浏览器会报错:<img src="/2021/03/07/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E9%9D%9E%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%80%EF%BC%89/%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B61.png" alt="受控组件1"></p><p>解决办法:设置readOnly属性或者添加onChange函数</p></li><li><p>当我们给input的value属性指定一个和state相关的值,或者没有在onChange函数中写setState逻辑,会阻止用户的输入,但指定null或undefined除外:</p></li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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 xml">// 用户无法输入<br><span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"hi"</span>/></span><br><span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{this.state.value}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{()</span>=></span>{}}<br>// 用户可以输入<br><span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{null}</span> /></span><br><span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{undefined}</span> /></span><br></code></pre></td></tr></table></figure><p>有时候,使用受控组件是件很麻烦的事情,需要为数据变化的每一种方式都编写处理函数,并通过一个React组件传递所有的state。如果将非React代码转成React代码或者做React代码和非React代码集成时,会非常繁琐。这些情况下,可以引用非受控组件——表单的另一种实现方式。</p><p>参考资料:</p><p><a href="https://zh-hans.reactjs.org/docs/forms.html#controlled-components">React官方文档</a></p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
</tags>
</entry>
<entry>
<title>i18n(Internationalization)</title>
<link href="/2021/02/25/i18n%E5%AE%9E%E7%8E%B0%E8%AF%AD%E8%A8%80%E5%88%87%E6%8D%A2/"/>
<url>/2021/02/25/i18n%E5%AE%9E%E7%8E%B0%E8%AF%AD%E8%A8%80%E5%88%87%E6%8D%A2/</url>
<content type="html"><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><ul><li><p>语言包作为作为静态资源单独保存</p></li><li><p>每种语言对应一个文件</p></li><li><p>切换语言设置时,语言文件随之切换 </p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2></li></ul><p>i18next:目前最主流的框架 react-i18next:提供了更多面向react的api(HOC、hooks)</p><h2 id="配置i18n"><a href="#配置i18n" class="headerlink" title="配置i18n"></a>配置i18n</h2><p>首先创建i18n文件夹,创建configs.ts</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs react">import i18n from "i18next";<br>import { initReactI18next } from "react-i18next";<br></code></pre></td></tr></table></figure><p>进行下一步之前,我们要先引入语言文件包,这里我使用中文、英文两种语言,文件是json格式的,所以引入了中文版zh.json和英文版en.json。其实就是普通的json对象,两个文件的结构一样,只有最后的值不同,一个是中文一个是英文。</p><p>接着,我们要在配置文件中引入这两个文件,然后定义一个代表语言资源的本地变量:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs react">import translation_en from './en.json'<br>import translation_zh from './zh.json'<br><br>const resources = {<br> en: {<br> translation: translation_en<br> },<br> zh: {<br> translation: translation_zh<br> }<br>}<br></code></pre></td></tr></table></figure><p>接着按照官方文档进行初始化:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">i18n<br> .use(initReactI18next) // 通过react-i18next进行初始化<br> .init({<br> resources, // 传入本地资源变量<br> lng: "zh", // 默认语言:zh<br>// keySeparator为true,代表我们可以通过链式结构访问字符串,如:"header.slogan"<br> // keySeparator: false, // we do not use keys in form messages.welcome<br><br> interpolation: {<br> // 不会强行把html字符串转换为普通字符串<br> escapeValue: false // react already safes from xss<br> }<br> });<br></code></pre></td></tr></table></figure><p>最后导出配置:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs react">export default i18n<br></code></pre></td></tr></table></figure><h2 id="使用i18n"><a href="#使用i18n" class="headerlink" title="使用i18n"></a>使用i18n</h2><p>i18next的基本原理是Context,就是在全局注入provider,然后子组件中使用相应API获取数据。但实际上,我们只需在index.js文件中引入配置文件,就大功告成了,因为react-i18next这个框架在初始化对象的时候,就帮我们完成了context API的注入。所以我们现在已经可以在各组件中使用这个context API了。</p><h3 id="类组件"><a href="#类组件" class="headerlink" title="类组件"></a>类组件</h3><p>类组件中,我们使用高阶函数完成语言的注入,导入react-i18next的高阶函数withTranslation,并修改组件的结构:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs react">import { withTranslation } from "react-i18next"<br>class HomeComponent extends React.Component {<br> ...<br>}<br>export default withTranslation()(HomeComponent)<br></code></pre></td></tr></table></figure><p>这个高阶函数需要写两个小括号,第一个代表命名空间,第二个才是我们的组件。这样,我们就可以在props中访问到函数t了,利用这个函数,我们可以以字符串的形式访问到语言文件的json对象。</p><p>因为要在props中使用t,我们要先传入i18n的typescript定义。导入WithTranslation,这个就是typescript的类型定义:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs react">import { withTranslation, WithTranslation } from "react-i18next";<br>type PropsType = WithTranslation<br>class HomeComponent extends React.Component<PropsType> {<br> ...<br>}<br></code></pre></td></tr></table></figure><p>然后我们就用t函数来替换硬编码的字符串。比如说把首页的“爆款推荐”换成<code>{t("home_page.hot_recommend")}</code>。</p><h3 id="函数式组件"><a href="#函数式组件" class="headerlink" title="函数式组件"></a>函数式组件</h3><p>在函数式组件中调取全局数据,用的是钩子函数,首先,我们要引入useTranslation这个hook</p><p><code>import { useTranslation } from 'react-i18next';</code></p><p>然后直接使用得到t函数即可,之后的用法和类组件一模一样。</p><p><code>const { t } = useTranslation();</code></p><h2 id="语言切换"><a href="#语言切换" class="headerlink" title="语言切换"></a>语言切换</h2><p>现在,我们可以正常显示语言了,还差最后一步,就是实现中英切换。由于language保存在store中,所以我们点击语言切换时,用reducer来改变store中的状态,在处理全局language数据的同时,我们调用i18next的API来切换语言即可。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs react">// languageReducer.ts<br>import i18n from 'i18next';<br>switch (action.type) {<br> case CHANGE_LANGUAGE:<br> // 传入的是语言的key<br> i18n.changeLanguage(action.payload);<br> return { ...state, language: action.payload };<br> ...<br> }<br></code></pre></td></tr></table></figure><p>现在我们功能已经实现了,但是这么实现是有问题的。根据redux的定义所以的reducer都是纯函数,也就是没有副作用的函数,但是我们调用i18n.changeLanguage()这个函数时,这个reducer就不再是纯函数了,所以我们要使用中间件来改进。</p><p>在middlewares文件夹中创建changeLanguage.ts,编写中间件:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs react">import { Middleware } from 'redux';<br>import { CHANGE_LANGUAGE } from "../language/languageActions";<br>import i18n from "i18next";<br><br>export const changeLanguage : Middleware = (store) => (next) => (action) => {<br> if(action.type === CHANGE_LANGUAGE){<br> i18n.changeLanguage(action.payload);<br> }<br> next(action);<br>}<br></code></pre></td></tr></table></figure><p>然后在store文件中引入即可:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs react">const store = configureStore({<br> reducer: persistedReducer,<br> middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), changeLanguage, actionLog],<br> devTools: true,<br>})<br></code></pre></td></tr></table></figure><p>现在我们就完全实现了语言的切换功能。</p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
<tag>i18n</tag>
</tags>
</entry>
<entry>
<title>React Redux</title>
<link href="/2021/02/17/Redux/"/>
<url>/2021/02/17/Redux/</url>
<content type="html"><![CDATA[<h2 id="基本工作流程"><a href="#基本工作流程" class="headerlink" title="基本工作流程"></a>基本工作流程</h2><ol><li>用户通过action creator创建action并派发dispatch action。</li><li>store收到之后自动调用相应的reducer,传入当前的state和收到的action,返回新的state。</li><li>state一旦发生变化,会调用监听函数,通知订阅了store的组件(store.subscribe(listener))。</li><li>Reacr component中可以通过store.getState()获取到store的状态。</li></ol><img src="http://www.ruanyifeng.com/blogimg/asset/2016/bg2016091802.jpg" alt="flow" /> <h2 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a>中间件</h2><p>上述流程中,我们的reducer只能处理一些同步的、无副作用的action,那一步操作怎么办?像数据获取这些有副作用的操作怎么办?这时我们可以使用redux中的新工具——中间件(middleware)。</p><p>所谓中间件,就是对原来的store.dispatch进行封装,在发出action和执行reducer之间添加一些操作。</p><h4 id="中间件的用法"><a href="#中间件的用法" class="headerlink" title="中间件的用法"></a>中间件的用法</h4><p>我们既可以使用现有的中间件(redux-thunk、redux-logger等),也可以自定义中间件:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs react">const middleWare = (store) => (next) => (action) => {<br>//派发action之前进行一些操作<br> ...<br> //dispatch这个action<br>next(action)<br> //action执行后<br>...<br>}<br></code></pre></td></tr></table></figure><p>要使用中间件,只需在createStore的时候将applyMiddlewares(thunk, logger)参数传入即可。</p><h2 id="异步操作的基本思路(使用redux-thunk)"><a href="#异步操作的基本思路(使用redux-thunk)" class="headerlink" title="异步操作的基本思路(使用redux-thunk)"></a>异步操作的基本思路(使用redux-thunk)</h2><p>假设说我们要向服务器请求数据,而且这个数据在多个模块中要用到,那么我们可以考虑在store中获取数据,并使用redux-thunk中间件,派发三种action,分别是</p><ul><li>fetchDataStart</li><li>fetchDataSuccess</li><li>fetchDataFail</li></ul><p>维护state对象,包含loading、data、error三个属性,分别表示是否加载数据中,获取到的data,出错信息。</p><p>我们的目标是将获取数据的操作放到store中,也就是在component中dispatch一个类似giveMeData这样的action,然后store收到这个action之后自动执行API请求、请求成功或失败后的处理。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs react">//在react component中<br>componentDidMount(){<br> dispatch(giveMeDataActionCreator())<br>}<br></code></pre></td></tr></table></figure><p>一般的action creator返回的都是一个对象,这没有办法满足我们的需求,这时我们就要引入redux-thunk这个中间件,它封装了dispatch这个方法,让dispatch多支持一种参数类型——函数类型。</p><p>giveMeDataCreator返回一个函数,带有dispatch和getState两个redux方法。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs react">export const giveMeDataCreator = () : ThunkAction => (dispatch, getState) => {<br> dispatch(fetchDataStartCreator());<br> try{<br> const { data } = await axios.get(...);<br> dispatch(fetchDataSuccessCreator(data));<br> } catch (e) {<br> dispatch(fetchDataFailCreator(e.message));<br> }<br>}<br></code></pre></td></tr></table></figure><p>在这个action creator中,我们连续发送两个action,让reducer完成相关操作。而我们的中间件会持续执行,直到异步逻辑全部结束。</p><p><strong>普通的dispatch只支持对象类型的参数,redux-thunk这个中间件,添加了函数类型参数的支持,类似的,redux-promise添加了对promise类型参数的支持。</strong></p><h2 id="UI组件和容器组件"><a href="#UI组件和容器组件" class="headerlink" title="UI组件和容器组件"></a>UI组件和容器组件</h2><p>React-Redux将所有组件分为UI组件和容器组件两类。</p><p>UI组件特征:</p><ul><li>只负责UI的呈现,不带有任何业务逻辑。</li><li>没有状态(即不使用this.state这个变量)</li><li>所有参数由this.props提供</li><li>不使用任何Redux的API</li></ul><p>容器组件特征:</p><ul><li>负责管理数据和业务逻辑,不负责UI的呈现</li><li>带有内部状态</li><li>使用Redux的API</li></ul><p>当一个组件既涉及UI呈现,又包含业务逻辑处理,我们可以把它拆分成这样的结构:外面一个容器组件,里面包含一个UI组件,前者负责从外部获取数据传给内部,后者负责根据传来的数据渲染出视图。</p><h4 id="connect()"><a href="#connect()" class="headerlink" title="connect()"></a>connect()</h4><p>react-redux方法提供connect方法用于从UI组件中生成容器组件。使用方法如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs react">export const Home = <br> connect(<br> mapStateToProps,<br> mapDispatchToProps)<br>(UIComponent)<br></code></pre></td></tr></table></figure><p>这个方法后面跟两个括号,第一个括号接收两个参数mapStateToProps和mapDispatchToProps。第二个括号传入需要包裹的UI组件。</p><h5 id="mapStateToProps"><a href="#mapStateToProps" class="headerlink" title="mapStateToProps"></a>mapStateToProps</h5><p>负责输入逻辑,将外部的state映射为内部的props。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs react">const mapStateToProps = (state: RootState, ownProps) => {<br> return {<br> loading: state.recommendProducts.loading,<br> error: state.recommendProducts.error,<br> productList: state.recommendProducts.productList<br> }<br>};<br></code></pre></td></tr></table></figure><p>是一个函数,接收外部的state为参数,返回一个对象,里面的每个键值对就是一个映射,比如说,通过this.props.loading,可以获取到store中state的loading属性。mapStateToProps会订阅store,当state更新时,会自动执行,重新计算UI组件的参数,触发重新渲染。</p><p>如果connect方法省略这个参数,UI组件就不会订阅store,也就是state的更新不会触发UI组件更新。</p><h5 id="mapDispatchToProps"><a href="#mapDispatchToProps" class="headerlink" title="mapDispatchToProps"></a>mapDispatchToProps</h5><p>用来建立UI组件参数到store.dispatch方法的映射。它可以是一个函数,也可以是一个对象。</p><p>如果是函数,他会接收dispatch和ownProps作为参数,返回一个对象。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs react">const mapDispatchToProps = (dispatch) => {<br> return {<br> giveMeData: () => {<br> dispatch(giveMeDataActionCreator())<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>如果mapDispatchToProps是对象,它的每一个键值是一个函数,被当作action creator。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs react">const mapDispatchToProps = {<br> giveMeData: () => {<br> type:...,<br> payload: ...<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="Provider"><a href="#Provider" class="headerlink" title="Provider"></a>Provider</h4><p>connect方法能生成容器组件,但要让容器获取到store中的state,才能生成UI组件的参数。</p><p>一种方便的方法是,使用react-redux提供的Provider组件。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs react">import { Provider } from 'react-redux'<br><Provider store={store}><br><App /><br></Provider><br></code></pre></td></tr></table></figure><p>这样的包裹,使得App内所有子组件都可以通过context获取到state。</p><p>参考资料:</p><p><a href="http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html">阮一峰的网络日志——Redux入门教程一/二/三</a></p>]]></content>
<categories>
<category>React</category>
</categories>
</entry>
<entry>
<title>React-Router</title>
<link href="/2021/02/16/%E5%85%B3%E4%BA%8Ereact-router/"/>
<url>/2021/02/16/%E5%85%B3%E4%BA%8Ereact-router/</url>
<content type="html"><![CDATA[<h2 id="路由配置"><a href="#路由配置" class="headerlink" title="路由配置"></a>路由配置</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs react">React.render((<br> <Router><br> <Route path="/" component={App}><br> <Route path="about" component={About} /><br> <Route path="inbox" component={Inbox}><br> <Route path="messages/:id" component={Message} /><br> </Route><br> </Route><br> </Router><br>), document.body)<br></code></pre></td></tr></table></figure><p>通过path属性和component属性,可以配置url对应的路由。</p><p>此时,当url为’/‘时,我们会渲染App组件,但这时this.props.children是undefined,这种情况下,我们可以使用IndexRoute设置默认页面。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">React.render((<br> <Router><br> <Route path="/" component={App}><br> <IndexRoute component={Dashboard}/><br> <Route path="about" component={About} /><br> <Route path="inbox" component={Inbox}><br> <Route path="messages/:id" component={Message} /><br> {/* 使用 /messages/:id 替换 messages/:id */}<br> {/*<Route path="/messages/:id" component={Message} />*/}<br> {/*<Redirect from="messages/:id" to="/messages/:id" />*/}<br> </Route><br> </Route><br> </Router><br>), document.body)<br></code></pre></td></tr></table></figure><p>如果我们想url为/message/:id也能正常访问,那么只需使用 /messages/:id 替换 messages/:id,但这样做会导致url被改变,当我们访问/inbox/message/:id时,会得到错误的页面。要解决这个问题,我们还要加一个Redirect标签。</p><h2 id="Router中的history属性"><a href="#Router中的history属性" class="headerlink" title="Router中的history属性"></a>Router中的history属性</h2><p>history知道如何监听浏览器地址的变化,并解析这个url转化为location对象,然后router使用它匹配到路由,最后正确渲染组件。</p><ul><li><p><strong>browserHistory</strong></p><p>是使用react router的应用推荐的history,它使用浏览器中的 History API 用于处理 URL,创建一个像<code>example.com/some/path</code>这样真实的 URL。当URL发生变化的时候,会向服务器发送request请求。对多页面模式应用(MPA),浏览器会通过自身的history处理好页面间的操作,但对于单页面应用(SPA),只有一个真实的HTML页面,是无法体现页面跳转效果的,这时,需要服务器配合,模拟出多个HTML页面,从而实现浏览器真实的页面跳转效果。</p></li><li><p><strong>hashHistory</strong></p><p>Hash history 使用 URL 中的 hash(<code>#</code>)部分去创建形如 <code>example.com/#/some/path</code> 的路由。不需要服务器的任何配置就可以运行。</p></li><li><p><strong>createMemoryHistory</strong></p><p>Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。</p><p>和另外两种history的一点不同是你必须创建它,这种方式便于测试。</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-keyword">const</span> history = createMemoryHistory(location)<br></code></pre></td></tr></table></figure><h2 id="react-router-dom"><a href="#react-router-dom" class="headerlink" title="react-router-dom"></a>react-router-dom</h2></li></ul><p>react-router实现了路由的核心功能,而react-router-dom则基于react-router,加入了浏览器环境下的一些功能,比如说Link组件、BrowserRouter和HashRouter组件。但类似Switch、Route这样的组件,react-router-dom都是从react-router中引入,然后重新导出而已。因此,我们在npm安装时,不用再显示安装react-router了。</p><h2 id="Switch组件"><a href="#Switch组件" class="headerlink" title="Switch组件"></a>Switch组件</h2><p>会从上往下匹配它包裹的Route中的path,渲染第一个匹配的URL。</p><h2 id="Route组件"><a href="#Route组件" class="headerlink" title="Route组件"></a>Route组件</h2><p>他的path属性总是匹配url的前缀,因此path=’/‘会匹配任何url,因此我们要把这一条route放在switch的最后或者使用exact关键字修饰。</p><h2 id="react-router-dom的hooks"><a href="#react-router-dom的hooks" class="headerlink" title="react-router-dom的hooks"></a>react-router-dom的hooks</h2><h3 id="useHistory"><a href="#useHistory" class="headerlink" title="useHistory"></a>useHistory</h3><p>获取history实例,通过history.push()方法跳转路由。</p><h3 id="useLocation"><a href="#useLocation" class="headerlink" title="useLocation"></a>useLocation</h3><p>返回一个location对象,包含当前url的信息。</p><h3 id="useParams"><a href="#useParams" class="headerlink" title="useParams"></a>useParams</h3><p>返回键值对对象,包含匹配到的路由中的参数。</p><h3 id="useRouteMatch"><a href="#useRouteMatch" class="headerlink" title="useRouteMatch"></a>useRouteMatch</h3><p>和<Route>一样用来匹配当前URL,但是不会渲染对应的组件,只是返回match对象。</p><h2 id="withRouter"><a href="#withRouter" class="headerlink" title="withRouter"></a>withRouter</h2><p>使用这个高阶组件,就可以从props中获取到match,location和history的信息。在外部可以通过WrappedComponent这个静态属性获取到原来的组件。</p>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React Router</tag>
</tags>
</entry>
<entry>
<title>React Hook</title>
<link href="/2021/02/11/React-Hook%E5%B0%8F%E7%BB%93/"/>
<url>/2021/02/11/React-Hook%E5%B0%8F%E7%BB%93/</url>
<content type="html"><![CDATA[<blockquote><p>Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。</p></blockquote><h2 id="使用Hook的动机"><a href="#使用Hook的动机" class="headerlink" title="使用Hook的动机"></a>使用Hook的动机</h2><ol><li><p><strong>Hook 使你在无需修改组件结构的情况下复用状态逻辑</strong>。在Hook之前,我们可以使用render props和高阶组件来添加可复用的状态逻辑。</p><p><strong>render props</strong>就是在react组件之间使用一个值为函数的prop共享代码技术,组件接收一个返回React元素的函数,并在组件内部调用这个函数完成渲染逻辑。(这个prop的名字叫render或其他名字),而且也不一定要放到JSX元素的attribute列表中,也可以放在元素标签内部。</p><p><strong>高阶组件</strong>(Higher Order Component)是参数为组件,返回值为新组件的函数。HOC将组件包装在容器组件中来组成新的组件,来完成一些可复用的逻辑。</p><p>但这两个方案需要重新组织组件的结构,可能使代码难以理解。使用Hook可以从组件中提取状态逻辑,使这些逻辑可以单独测试且复用,并无需修改组件结构。</p></li><li><p>我们维护组件时,随着时间推移,组建的生命周期函数往往变得臃肿,会有很多不相关的状态逻辑。<strong>Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)</strong>,而并非强制按照生命周期划分。</p></li><li><p><strong>Hook 使你在非 class 的情况下可以使用更多的 React 特性</strong>。无需理解class</p></li></ol><h2 id="Hook的特性"><a href="#Hook的特性" class="headerlink" title="Hook的特性"></a>Hook的特性</h2><p>Hook是钩子的意思,就是我们使用函数式组件时,尽量写成纯函数,如果需要React state和生命周期等特性的函数,就使用Hook完成这些原本在class中才能实现的需求。Hook是</p><ul><li>完全可选的</li><li>100%向后兼容</li><li>现在可用</li><li>没有计划从React中移除class</li></ul><h2 id="Hook使用规则"><a href="#Hook使用规则" class="headerlink" title="Hook使用规则"></a>Hook使用规则</h2><ul><li>只能在函数最外层调用Hook,不要再循环、条件判断或子函数中调用。</li></ul><p>React怎么知道哪个state对应哪个useState,答案是靠Hook执行的顺序,如果我们在条件语句中使用hook,那么很有可能导致前后两次hooks执行顺序发生改变,导致bug产生。如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部。</p><ul><li>只能在React的函数式组件中调用,不要在其他JavaScript函数中调用。</li></ul><h2 id="React中几种常见的Hook"><a href="#React中几种常见的Hook" class="headerlink" title="React中几种常见的Hook"></a>React中几种常见的Hook</h2><h3 id="useState"><a href="#useState" class="headerlink" title="useState"></a><strong>useState</strong></h3><p>相当于类组件中的state。</p><p>当我们调用useState时,我们传入一个参数,作为这个state的初始值,它返回一个有两个元素的数组,分别代表当前state的值以及更新这个state的函数,比如<code>[count, setCount] = useState(0)</code>,通过数组解构,我们得到两个变量,count是这个state的变量名,我们可以调用setCount更新count这个state。</p><p>使用useState时,应该使用单个state变量还是使用一个对象打包所有state?</p><p>React官方推荐把 state 切分成多个 state 变量,因为每次更新一个state,会用新值整个替换旧值,不像class组件的setState那样合并。因此如果非要用一个对象包含所有state,state的更新要这样实现:</p><figure class="highlight pf"><table><tr><td class="gutter"><pre><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 pf"><span class="hljs-keyword">const</span> [<span class="hljs-keyword">state</span>, <span class="hljs-built_in">set</span>State] = useState({ left: <span class="hljs-number">0</span>, top: <span class="hljs-number">0</span>, width: <span class="hljs-number">100</span>, height: <span class="hljs-number">100</span> });<br>...<br>/ 展开 「...<span class="hljs-keyword">state</span>」 以确保我们没有 「丢失」 width 和 height<br> <span class="hljs-built_in">set</span>State(<span class="hljs-keyword">state</span> => ({ ...<span class="hljs-keyword">state</span>, left: e.pageX, top: e.pageY }));<br></code></pre></td></tr></table></figure><h3 id="useEffect"><a href="#useEffect" class="headerlink" title="useEffect"></a><strong>useEffect</strong></h3><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">useEffect(<span class="hljs-function">() =></span> {...}, []);<br></code></pre></td></tr></table></figure><p>数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。我们可以使用useEffect完成在函数式组件中这些功能。</p><p>清除操作:只需在返回值中返回一个函数,那么React会在组件卸载的时候执行清除操作并调用它,比如取消订阅。</p><p>默认情况下,useEffect会在每次渲染后执行,如果要通知React跳过对effect的调用,即不要每次渲染都调用,我们可以传递数组作为第二个参数,比如我们传入[count]作为第二个参数,那么只有当count发生改变时,这个副作用函数才会被执行。</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">//相当于componentDidMount和componentWillUnmount</span><br>useEffect(<span class="hljs-function">() =></span> {...}, [])<br><span class="hljs-comment">//会紧紧盯着count,只要count值改变,就会执行</span><br>useEffect(<span class="hljs-function">() =></span> {...}, [count])<br><span class="hljs-comment">//默认情况,相当于componentDidMount和componentDidUpdate</span><br>useEffect(<span class="hljs-function">() =></span> {...})<br></code></pre></td></tr></table></figure><h3 id="useContext"><a href="#useContext" class="headerlink" title="useContext"></a><strong>useContext</strong></h3><p>在组件之间共享状态的钩子。在React中,如果我们要传递变量,可以使用props属性向下传递给子组件。这种方法很简单,但当我们想把变量传递给子组件的子组件时,就需要使用子组件的props再往下传递,这样就形成了props的深度注入。随着props注入越深,组件更新的频率也越来越高,UI效率也就越来越慢。</p><p>而useContext就是来解决非父子组件的数据共享问题的。</p><p>假设说我们需要全局共享一个变量,username。</p><p>第一步,我们要利用React Context API,在组件外部创建一个context,并传入默认初始值。</p><figure class="highlight typescript"><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 typescript"><span class="hljs-keyword">const</span> defaultContextValue = { <span class="hljs-attr">username</span>: <span class="hljs-string">'sxx'</span> };<br><span class="hljs-keyword">const</span> appContext = React.createContext(defaultContextValue)<br></code></pre></td></tr></table></figure><p>然后,为了使App组件和其子组件能共享这个username,我们要用appContext.Provider把整个render函数包裹起来。并且要把defaultContextValue注入到value属性中。</p><figure class="highlight typescript"><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 typescript"><appContext.Provider value={defaultContextValue}><br> <App /><br></appContext.Provider><br></code></pre></td></tr></table></figure><p>接下来,我们就可以在他的子孙组件中访问到username这个变量了。</p><p>有两种方法:</p><ol><li>利用appContext.Consumer组件,在组件内部使用花括号,使用箭头函数在其内部共享数据:</li></ol><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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 typescript"><span class="hljs-keyword">import</span> { appContext } <span class="hljs-keyword">from</span> ...<br>...<br> <appContext.Consumer><br> {<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br> {<span class="hljs-comment">/*在这里可以访问到全局的username啦*/</span>}<br> <h1>{value.username}</h1><br> }}<br></appContext.Consumer><br></code></pre></td></tr></table></figure><ol start="2"><li>使用React Hook。利用useContext钩子函数,我们不用改变代码的结构,能很方便的在组件中获取数据:</li></ol><figure class="highlight typescript"><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 typescript"><span class="hljs-keyword">import</span> { useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;<br><span class="hljs-keyword">const</span> value = useContext(appContext);<br><span class="hljs-comment">//接着就可以在return中直接使用value了</span><br></code></pre></td></tr></table></figure><p>useContext这个hook极大的减少了模板代码,降低了代码层级,也消灭了多个consumer嵌套的可能性。</p><h3 id="useReducer"><a href="#useReducer" class="headerlink" title="useReducer"></a><strong>useReducer</strong></h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">const</span> [state, dispatch] = useReducer(reducer, initialState);<br></code></pre></td></tr></table></figure><p>这个钩子接收一个reducer和initialState为参数,返回当前状态和dispatch action的函数,可以在不使用redux的情况下,管理数据状态。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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 typescript"><span class="hljs-keyword">const</span> myReducer = <span class="hljs-function">(<span class="hljs-params">state, action</span>) =></span> {<br> <span class="hljs-keyword">switch</span>(action.type) {<br> <span class="hljs-keyword">case</span>(<span class="hljs-string">'countUp'</span>):<br> <span class="hljs-keyword">return</span> {<br> ...state,<br> count: state.count + <span class="hljs-number">1</span><br> }<br> <span class="hljs-keyword">default</span>:<br> <span class="hljs-keyword">return</span> state;<br> }<br>}<br><br><span class="hljs-comment">//组件代码</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">const</span> [state, dispatch] = useReducer(myReducer, { <span class="hljs-attr">count</span>: <span class="hljs-number">0</span> });<br> <span class="hljs-keyword">return</span> (<br> <div className=<span class="hljs-string">"App"</span>><br> <button onClick={<span class="hljs-function">() =></span> dispatch({ <span class="hljs-attr">type</span>: <span class="hljs-string">'countUp'</span> })}><br> +<span class="hljs-number">1</span><br> </button><br> <p>Count: {state.count}</p><br> </div><br> );<br>}<br></code></pre></td></tr></table></figure><h3 id="useRef"><a href="#useRef" class="headerlink" title="useRef"></a><strong>useRef</strong></h3><p>在默认情况下,React中的函数会捕获props和state。但是如果我们想要读取最新的props和state呢?也就是在本次渲染中读取未来的渲染的state。在类组件中,通过<code>this.props</code>和<code>this.state</code>实现。而在函数式组件中,就是使用这个Hook。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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 react">function Counter(){<br> const [count, setCount] = useState(0);<br> const latestCount = useRef(count);<br> useEffect(() => {<br> // Set the mutable latest value<br> latestCount.current = count;<br> setTimeout(() => {<br> // Read the mutable latest value<br> console.log(`You clicked ${latestCount.current} times`);<br> }, 3000);<br> });<br> return (<br> <div><br> <p>You clicked {count} times</p><br> <button onClick={() => setCount(count + 1)}><br> Click me<br> </button><br> </div><br> )<br>}<br></code></pre></td></tr></table></figure><p>useRef可以很方便的保存任意可变值,相当于class组件里的this.state,它创建的是一个普通js对象,但和自己创建<code>{current: ...}</code>不同的是,每次渲染返回的都是同一个ref对象。</p><p>所以如果在三秒内点击五次button,这段代码的输出是5,5,5,5,5.</p><h3 id="useMemo"><a href="#useMemo" class="headerlink" title="useMemo"></a><strong>useMemo</strong></h3><p>返回一个memorized的值,只有依赖项变化才会重新计算。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs react">const memorizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);<br></code></pre></td></tr></table></figure><p>可以作为性能优化的一个手段。</p><h3 id="自定义Hook"><a href="#自定义Hook" class="headerlink" title="自定义Hook"></a><strong>自定义Hook</strong></h3><p>自定义 Hook 的命名以use开头,不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。</p><h2 id="参考资料:"><a href="#参考资料:" class="headerlink" title="参考资料:"></a>参考资料:</h2><ol><li><a href="https://zh-hans.reactjs.org/docs/hooks-intro.html">React Hook官方文档</a></li><li><a href="http://www.ruanyifeng.com/blog/2019/09/react-hooks.html">阮一峰的网络日志——React Hooks 入门教程</a></li></ol>]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
<tag>Hook</tag>
</tags>
</entry>
<entry>
<title>Leetcode之买卖股票题目总结</title>
<link href="/2021/02/08/Leetcode%E4%B9%8B%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E9%A2%98%E7%9B%AE%E6%80%BB%E7%BB%93/"/>
<url>/2021/02/08/Leetcode%E4%B9%8B%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E9%A2%98%E7%9B%AE%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>关于股票买卖的题目,都可以用动态规划来解决,我们要根据具体题目,分析不同的初始条件以及转移方程。在接下来的每一道题目中,我都会提供使用动态规划方法的解答,以及一些针对某道特定的题目,更为简单的解答思路。</p><h2 id="买卖股票的最佳时机Ⅰ"><a href="#买卖股票的最佳时机Ⅰ" class="headerlink" title="买卖股票的最佳时机Ⅰ"></a>买卖股票的最佳时机Ⅰ</h2><p><img src="/2021/02/08/Leetcode%E4%B9%8B%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E9%A2%98%E7%9B%AE%E6%80%BB%E7%BB%93/1.png" alt="题目描述"></p><h3 id="动态规划"><a href="#动态规划" class="headerlink" title="动态规划"></a>动态规划</h3><p>假设以dp[i]来表示第i天的最大利润,我们以dp[i][0]和dp[i][1]来分别代表今天结束时手上持有股票的状态和不持有状态的股票。<br>对于今天结束时持有股票的状态,有可能是昨天结束时已经持有了股票,今天没有进行任何操作,也有可能是今天以prices[i]的价格买入了股票。因此转移方程为<code>dp[i][0] = Math.max{dp[i-1][0], -prices[i]}</code>。<br>而如果今天结束时不持有股票,那么可能是昨天结束时已经不持有股票了,今天没有进行任何操作,也可能是昨天结束时持有股票,今天以prices[i]的价格卖出。那么转移方程为<code>dp[i][1] = Math.max{dp[i-1][1], dp[i-1][0] + prices[i]}</code>。<br>初始条件:第一天结束若是持有股票,则利润一定是-prices[i],即<code>dp[0][0] = -prices[i]</code>。第一天结束若不持有股票,一定是没有买也没有卖,即<code>dp[i][1] = 0</code>。<br>我们可以观察到,第i天的状态只与第i-1天的状态有关,因此我们想到了空间优化的措施,用两个变量存储前一天两种状态下的最大利润,避免创建二维数组:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 javascript"><span class="hljs-keyword">var</span> maxProfit = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">prices</span>) </span>{<br> <span class="hljs-keyword">let</span> n = prices.length;<br> <span class="hljs-keyword">let</span> buy = -prices[<span class="hljs-number">0</span>];<br> <span class="hljs-keyword">let</span> sell = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">1</span>;i<n;i++){<br> <span class="hljs-keyword">let</span> s0 = buy, s1 = sell;<br> buy = <span class="hljs-built_in">Math</span>.max(s0, -prices[i])<br> sell = <span class="hljs-built_in">Math</span>.max(s1, s0 + prices[i])<br> }<br> <span class="hljs-keyword">return</span> sell;<br>};<br></code></pre></td></tr></table></figure><h3 id="计算最大的差值"><a href="#计算最大的差值" class="headerlink" title="计算最大的差值"></a>计算最大的差值</h3><p>对这道题来说,动态规划其实有点小题大做了。由于只能进行一次交易,我们只需要知道prices[j] - prices[i] (j>i) 的最大值。用一个minPrice来维护最小买入价格,以profit来记录当前的最大利润,遍历一遍数组,比较得出当前的最大利润以及更新minPrice。</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> maxProfit = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">prices</span>) </span>{<br> <span class="hljs-keyword">let</span> minPrice = prices[<span class="hljs-number">0</span>];<br> <span class="hljs-keyword">let</span> profit = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">1</span>; i<prices.length;i++){<br> <span class="hljs-keyword">if</span>(prices[i] < minPrice){<br> minPrice = prices[i]<br> }<span class="hljs-keyword">else</span>{<br> profit = <span class="hljs-built_in">Math</span>.max(profit, prices[i] - minPrice)<br> }<br> }<br> <span class="hljs-keyword">return</span> profit;<br>};<br></code></pre></td></tr></table></figure><h2 id="买卖股票的最佳时机Ⅱ"><a href="#买卖股票的最佳时机Ⅱ" class="headerlink" title="买卖股票的最佳时机Ⅱ"></a>买卖股票的最佳时机Ⅱ</h2><p><img src="/2021/02/08/Leetcode%E4%B9%8B%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E9%A2%98%E7%9B%AE%E6%80%BB%E7%BB%93/2.png" alt="题目描述"></p><h3 id="动态规划-1"><a href="#动态规划-1" class="headerlink" title="动态规划"></a>动态规划</h3><p>这道题和第一题的唯一区别是可以进行无限次的交易。如果今天结束的状态是持有股票,那么前一天有可能是完成了一次交易,卖掉了股票。因此dp[i][0]的转移方程就变成了<code>dp[i][0] = Math.max{dp[i-1][0], dp[i-1][1] - prices[i]}</code>(因为初始条件dp[i][1] = 0,所以如果是进行第一次交易,也不会影响结果)。<br>和上一题一样,我们也可以使用两个变量进行空间的优化。</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> maxProfit = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">prices</span>) </span>{<br> <span class="hljs-keyword">let</span> n = prices.length;<br> <span class="hljs-keyword">if</span>(n == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">let</span> buy = -prices[<span class="hljs-number">0</span>];<br> <span class="hljs-keyword">let</span> sell = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i < n; i++){<br> <span class="hljs-keyword">let</span> a = buy, b = sell;<br> buy = <span class="hljs-built_in">Math</span>.max(a, b - prices[i]);<br> sell = <span class="hljs-built_in">Math</span>.max(b, a + prices[i]);<br> }<br> <span class="hljs-keyword">return</span> sell;<br>};<br></code></pre></td></tr></table></figure><h3 id="贪心"><a href="#贪心" class="headerlink" title="贪心"></a>贪心</h3><p>由于交易次数不限,我们可以这样想:加入今天的价格比明天低,那么我们就在今天买入,明天卖出,这也必能增加利润。如果价格连续几天都攀升,我们使用这种方法就相当于在价格最低点买入,最高点卖出。这样我们遍历一次数组就能得到最大利润。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> maxProfit = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">prices</span>) </span>{<br> <span class="hljs-keyword">let</span> profit = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">if</span>(prices.length == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span>(i < prices.length){<br> <span class="hljs-keyword">if</span>(prices[i] > prices[i-<span class="hljs-number">1</span>]){<br> profit += prices[i] - prices[i-<span class="hljs-number">1</span>]<br> }<br> i++;<br> }<br> <span class="hljs-keyword">return</span> profit;<br>};<br></code></pre></td></tr></table></figure><h2 id="买卖股票的最佳时机Ⅲ"><a href="#买卖股票的最佳时机Ⅲ" class="headerlink" title="买卖股票的最佳时机Ⅲ"></a>买卖股票的最佳时机Ⅲ</h2><p><img src="/2021/02/08/Leetcode%E4%B9%8B%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E9%A2%98%E7%9B%AE%E6%80%BB%E7%BB%93/3.png" alt="题目描述"></p><h3 id="动态规划1-0"><a href="#动态规划1-0" class="headerlink" title="动态规划1.0"></a>动态规划1.0</h3><p>在前面题目的基础上,这道题目加了一个限制,最多完成两笔交易,那么一天结束之后,我们可能有五种状态:</p><ol><li>没有过进行任何交易</li><li>持有第一支股票(第一笔交易中)</li><li>完成了第一次交易(买和卖),现在不持有股票</li><li>持有第二支股票(第二笔交易中)</li><li>完成第二次交易(买和卖),现在不持有股票<br>第一个状态,也是初始状态,利润一定为0,因此不必维护与更新。<br>我们用dp[i][0]、dp[i][1]、dp[i][2]、dp[i][3]分别表示第i天2-5状态下的最大利润。用-Infinity来表示不可能的状态(如果第一笔交易没完成或收益不为正,就不进行第二次交易)。那么<figure class="highlight apache"><table><tr><td class="gutter"><pre><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 apache"><span class="hljs-attribute">dp</span>[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] = -prices[<span class="hljs-number">0</span>]<br><span class="hljs-attribute">dp</span>[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">0</span><br><span class="hljs-attribute">dp</span>[<span class="hljs-number">0</span>][<span class="hljs-number">2</span>] = -Infinity<br><span class="hljs-attribute">dp</span>[<span class="hljs-number">0</span>][<span class="hljs-number">3</span>] = -Infinity<br></code></pre></td></tr></table></figure>接下来考虑每一天的转移方程:<figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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 inform7">// 第一笔交易的转移方程不必多说<br>dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[0]</span> = Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[0]</span>, -prices<span class="hljs-comment">[i]</span>)<br>dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[1]</span> = Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[1]</span>, dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[0]</span> + prices<span class="hljs-comment">[i]</span>);<br>// 如果前一天第一笔交易结束时收益不为正,说明第一笔交易没有意义也无效,不应该有第二次交易,<br>// 因此把最大利润设为-Infinity<br>// 如果前一天第一笔交易结束时收益大于0,今天结束的状态是持有第二支股票<br>// 那么有可能是延续了前一天的状态或是昨天卖出第一支股票,今天买入第二支股票<br>dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[2]</span> = dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[1]</span> > 0 ? Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[1]</span> - prices<span class="hljs-comment">[i]</span>, dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span>) : -Infinity<br>// 如果前一天不可能买入第二天股票,那么今天也不可能售出<br>// 如果dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span>有效,那么有可能延续昨天的状态或是在昨天买入的基础上卖出股票<br>dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[3]</span> = dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span> != -Infinity ? Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[3]</span>, dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span> + prices<span class="hljs-comment">[i]</span>) : -Infinity<br></code></pre></td></tr></table></figure>最后返回最大利润,有可能没有进行交易、进行一次交易、或进行了两次交易。因此返回这三个的最大值就可以了,也就是<code>Math.max(0, dp[prices.length-1][1], dp[prices.length-1][3])</code><figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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 inform7">var maxProfit = function(prices) {<br> let n = prices.length;<br> if(n == 0) return 0;<br> let dp = <span class="hljs-comment">[]</span>;<br> dp<span class="hljs-comment">[0]</span> = <span class="hljs-comment">[]</span>;<br> dp<span class="hljs-comment">[0]</span><span class="hljs-comment">[0]</span> = -prices<span class="hljs-comment">[0]</span>; <br> dp<span class="hljs-comment">[0]</span><span class="hljs-comment">[1]</span> = 0;<br> dp<span class="hljs-comment">[0]</span><span class="hljs-comment">[2]</span> = -Infinity; dp<span class="hljs-comment">[0]</span><span class="hljs-comment">[3]</span> = -Infinity;<br> for(let i = 1; i < n; i++){<br> dp<span class="hljs-comment">[i]</span> = <span class="hljs-comment">[]</span>;<br> dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[0]</span> = Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[0]</span>, -prices<span class="hljs-comment">[i]</span>)<br> dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[1]</span> = Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[1]</span>, dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[0]</span> + prices<span class="hljs-comment">[i]</span>);<br> dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[2]</span> = dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[1]</span> > 0 ? Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[1]</span> - prices<span class="hljs-comment">[i]</span>, dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span>) : -Infinity<br> dp<span class="hljs-comment">[i]</span><span class="hljs-comment">[3]</span> = dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span> != -Infinity ? Math.max(dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[3]</span>, dp<span class="hljs-comment">[i-1]</span><span class="hljs-comment">[2]</span> + prices<span class="hljs-comment">[i]</span>) : -Infinity<br> }<br> return Math.max(0, dp<span class="hljs-comment">[n-1]</span><span class="hljs-comment">[3]</span>, dp<span class="hljs-comment">[n-1]</span><span class="hljs-comment">[1]</span>)<br>};<br></code></pre></td></tr></table></figure><h3 id="动态规划2-0"><a href="#动态规划2-0" class="headerlink" title="动态规划2.0"></a>动态规划2.0</h3>无论题目是否允许同一天买入并卖出,最终答案都不会受到影响,因为这一操作的收益为0。<br>在这一思想基础上,我们考虑对动态规划作优化。<br>我们用<strong>buy1、sell1、buy2、sell2</strong>分别代表2-4状态的最大利润。<br>边界条件(第一天结束时):<figure class="highlight ini"><table><tr><td class="gutter"><pre><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 ini"><span class="hljs-attr">buy1</span> = -prices[<span class="hljs-number">0</span>] <br><span class="hljs-attr">sell1</span> = <span class="hljs-number">0</span> <br><span class="hljs-attr">buy2</span> = -prices[<span class="hljs-number">0</span>] <br><span class="hljs-attr">sell2</span> = <span class="hljs-number">0</span><br></code></pre></td></tr></table></figure>接下来考虑转移方程,<figure class="highlight ini"><table><tr><td class="gutter"><pre><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 ini"><span class="hljs-attr">buy1</span> = Math.max{buy1, -prices[i]}<br><span class="hljs-attr">sell1</span> = Math.max{sell1, buy1 + prices[i]}<br><span class="hljs-attr">buy2</span> = Math.max{buy2, sell1 - prices[i]}<br><span class="hljs-attr">sell2</span> = Math.max{sell2, buy2 + prices[i]}<br></code></pre></td></tr></table></figure>有了前面题目的铺垫,得到这个转移方程并不难,只是这次我们没有用临时变量存储前一天的最大利润,而是直接计算。这样做会对结果有影响吗?答案是不会。比如在计算第i天的sell1的时候,我们用到的变量是第i天的buy1,它多考虑了第i天买入股票的情况,而这对计算sell1(第i天卖出股票)不会有任何影响,因为第i天买入又在第i天卖出,利润为0,对答案不会有影响。同理,计算buy2和sell2时,也可以直接用当天的值来算。<br>最后返回最大利润,必然是sell1、sell2和0之中的最大值,由于sell1和sell2的初始状态就是0,所以不用额外和0作比较。如果最后最好的情况是只进行一次交易,由于我们允许了同一天买和卖,sell1和sell2其实是相等的,因此最终返回的最大利润其实就是sell2。<br>这样,我们就得到了解答:<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> maxProfit = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">prices</span>) </span>{<br> <span class="hljs-keyword">let</span> n = prices.length;<br> <span class="hljs-keyword">if</span>(n == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">let</span> buy1 = -prices[<span class="hljs-number">0</span>], sell1 = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">let</span> buy2 = -prices[<span class="hljs-number">0</span>], sell2 = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i < n; i++){<br> buy1 = <span class="hljs-built_in">Math</span>.max(buy1, -prices[i]);<br> sell1 = <span class="hljs-built_in">Math</span>.max(sell1, buy1 + prices[i]);<br> buy2 = <span class="hljs-built_in">Math</span>.max(buy2, sell1 - prices[i]);<br> sell2 = <span class="hljs-built_in">Math</span>.max(sell2, buy2 + prices[i]);<br> }<br> <span class="hljs-keyword">return</span> sell2;<br>};<br></code></pre></td></tr></table></figure><h2 id="买卖股票的最佳时机Ⅳ"><a href="#买卖股票的最佳时机Ⅳ" class="headerlink" title="买卖股票的最佳时机Ⅳ"></a>买卖股票的最佳时机Ⅳ</h2><img src="/2021/02/08/Leetcode%E4%B9%8B%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E9%A2%98%E7%9B%AE%E6%80%BB%E7%BB%93/4.png" alt="题目描述"><br>上一题最多允许2次交易,我们用4的变量来记录不同状态的最大利润。这道题目最大能进行k次交易,那么就一共会有2k个状态,因此,我们可以考虑用两个数量为k的数组表示第k次交易买和卖情况下的最大利润。初始状态和转移方程和上一题差不多。直接上代码:<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-keyword">var</span> maxProfit = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">k, prices</span>) </span>{<br> <span class="hljs-keyword">let</span> n = prices.length;<br> <span class="hljs-keyword">if</span>(n == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-comment">//分别用一个数组记录第k次买入或卖出状态的最大利润</span><br> <span class="hljs-comment">//初始状态:buy[1] = ... = buy[k] = -prices[0] sell[1] = ... = sell[k] = 0</span><br> <span class="hljs-comment">//假设允许当天即买即卖,那样子利润为0,对结果不会有影响</span><br> <span class="hljs-keyword">let</span> buy = [], sell = [];<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i <= k; i++){<br> buy[i] = -prices[<span class="hljs-number">0</span>];<br> sell[i] = <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> d = <span class="hljs-number">1</span>; d < n; d++){<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i <= k; i++){<br> buy[i] = <span class="hljs-built_in">Math</span>.max(buy[i], sell[i-<span class="hljs-number">1</span>] - prices[d])<br> sell[i] = <span class="hljs-built_in">Math</span>.max(sell[i], buy[i] + prices[d])<br> }<br> }<br> <span class="hljs-keyword">return</span> sell[k];<br>};<br></code></pre></td></tr></table></figure>到这,我们就由易而难的解决了买卖股票的最佳时机四道题。他们都可以用动态规划解决,只是我们要根据具体的题目条件确定边界值和转移方程。</li></ol>]]></content>
<categories>
<category>Algorithm</category>
</categories>
<tags>
<tag>Leetcode</tag>
<tag>动态规划</tag>
<tag>买卖股票</tag>
</tags>
</entry>
<entry>
<title>better-scroll的踩坑总结</title>
<link href="/2021/02/08/better-scroll%E7%9A%84%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/"/>
<url>/2021/02/08/better-scroll%E7%9A%84%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h3 id="better-scroll是什么"><a href="#better-scroll是什么" class="headerlink" title="better-scroll是什么"></a>better-scroll是什么</h3><p><a href="https://github.com/ustbhuangyi/better-scroll">BetterScroll</a> 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 iscroll 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。</p><h3 id="vue中better-scroll的简单使用"><a href="#vue中better-scroll的简单使用" class="headerlink" title="vue中better-scroll的简单使用"></a>vue中better-scroll的简单使用</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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 xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"wrapper"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content"</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 class="hljs-attr">ref</span>=<span class="hljs-string">'child'</span>></span>...<span class="hljs-tag"></<span class="hljs-name">li</span>></span><br> ...<br> <span class="hljs-tag"></<span class="hljs-name">ul</span>></span><br> <span class="hljs-comment"><!-- 这里可以放一些其它的 DOM,但不会影响滚动 --></span><br><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br></code></pre></td></tr></table></figure><p>使用better-scroll,首先一定要有三层的html元素,最外层是wrapper,要有固定的高度,而且要设置<code> overflow: hidden</code>样式。<br><br/><strong>为什么需要中间的一层content,而不直接用wrapper包含所有<li>标签呢?</strong><br/><br>因为better-scroll默认处理第一个子元素的滚动,而忽略其他子元素,因此我们需要content这一层来包裹真正滚动的元素。<br>使用之前,我们要先通过npm install一下better-scroll,然后再import进来:</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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 pgsql"><span class="hljs-keyword">import</span> Bscroll <span class="hljs-keyword">from</span> <span class="hljs-string">'better-scroll'</span><br>export <span class="hljs-keyword">default</span> {<br>mounted () {<br> this.scroll = <span class="hljs-built_in">new</span> Bscroll(this.$refs.<span class="hljs-keyword">wrapper</span>, {<br> click: <span class="hljs-keyword">true</span><br> })<br> }<br>}<br></code></pre></td></tr></table></figure><p>通过this.$ref取到wrapper这个dom进行初始化,构造函数的第二个是参数是配置项。由于我们使用better-scroll滚动元素默认是不可点击的,因此可以通过设置click为true派发点击事件。<br><br/><br>另外,better-scroll也提供一个方法scrollToElement,通过这个方法,可以将页面滚动到指定的子元素:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">this</span>.scrollToElement(<span class="hljs-keyword">this</span>.$ref.child)<br></code></pre></td></tr></table></figure><h3 id="踩坑总结"><a href="#踩坑总结" class="headerlink" title="踩坑总结"></a>踩坑总结</h3><h4 id="初始化Bscroll之后页面无法滚动"><a href="#初始化Bscroll之后页面无法滚动" class="headerlink" title="初始化Bscroll之后页面无法滚动"></a>初始化Bscroll之后页面无法滚动</h4><p>首先,我们要知道better-scroll的滚动原理,如下图,只有当content的高度大于wrapper的高度,页面才能够滚动。<br><img src="/2021/02/08/better-scroll%E7%9A%84%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/%E5%8E%9F%E7%90%86%E5%9B%BE.png"><br/><br>但在项目开发过程中,我发现,我的content的高度确实大于wrapper的高度时,页面依然无法滚动,这是为什么?其实原因很有可能是new scroll的时机不对,导致初始化时,wrapper的高度大于content。而我们看到的content高度大于wrapper,其实是在bscroll初始化结束后的因为获取到数据或其他方式改变的。因此,我们初始化Bscroll的时机很重要。<br><br/><br>在很多实际应用中,我们列表的数据往往是动态获取的。而由数据改变到触发页面重新渲染又是一个异步的过程,我们要在获取到数据,并且页面已经重新渲染过后,再进行Bscroll的初始化。</p><blockquote><p>官方文档描述:<br/><br>Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。<br>因此,我们可以在数据变化后立即使用nextTick函数,在下一个事件循环中,即dom更新后,再new Bsroll。</p></blockquote><figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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 coffeescript">created () {<br>requestData().<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">(res)</span> =></span> {<br> <span class="hljs-built_in">this</span>.data = res.data<br> <span class="hljs-built_in">this</span>.$nextTick(<span class="hljs-function"><span class="hljs-params">()</span> =></span> {<br> <span class="hljs-built_in">this</span>.scroll = <span class="hljs-keyword">new</span> Bscroll(<span class="hljs-built_in">this</span>.$refs.wrapper)<br> })<br> })<br>}<br></code></pre></td></tr></table></figure><h4 id="scroll-refresh"><a href="#scroll-refresh" class="headerlink" title="scroll.refresh()"></a>scroll.refresh()</h4><p>由于数据是可能不断变化的,我们不可能每次数据变更都初始化一个Bscroll,这时,我们可以使用better-scroll提供的另一个方法refresh(),触发重新计算wrapper和content的高度,比如在updated钩子函数或监听某个数据的函数中使用。<br><br/></p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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 kotlin"><span class="hljs-comment">//父组件部分代码</span><br><template><br> <div><br> ...<br> <city-list :cities=<span class="hljs-string">"cities"</span>></city-list><br> ...<br> </div><br></template><br>export default {<br> name: <span class="hljs-string">'City'</span>,<br> <span class="hljs-keyword">data</span> () {<br> <span class="hljs-keyword">return</span> {<br> cities: {}<br> }<br> }<br> },<br> methods: {<br> getCityInfo () {<br> axios.<span class="hljs-keyword">get</span>(<span class="hljs-string">'/api/city.json'</span>).then(<span class="hljs-keyword">this</span>.getCityInfoSucc)<br> },<br> getCityInfoSucc (res) {<br> res = res.<span class="hljs-keyword">data</span><br> <span class="hljs-keyword">if</span> (res.ret && res.<span class="hljs-keyword">data</span>) {<br> <span class="hljs-keyword">const</span> <span class="hljs-keyword">data</span> = res.<span class="hljs-keyword">data</span><br> <span class="hljs-keyword">this</span>.cities = <span class="hljs-keyword">data</span>.cities<br> <span class="hljs-keyword">this</span>.hotCities = <span class="hljs-keyword">data</span>.hotCities<br> }<br> },<br> },<br> created () {<br> <span class="hljs-keyword">this</span>.getCityInfo()<br> }<br>}<br></code></pre></td></tr></table></figure><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><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 gradle"><span class="hljs-comment">//子组件部分代码</span><br><<span class="hljs-keyword">div</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"list"</span> ref=<span class="hljs-string">"wrapper"</span>><br> <<span class="hljs-keyword">div</span>><br> ...<br> <<span class="hljs-keyword">div</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"area"</span> v-<span class="hljs-keyword">for</span>=<span class="hljs-string">"(item, key) of cities"</span> :key=<span class="hljs-string">"key"</span> :ref=<span class="hljs-string">"key"</span>><br> <<span class="hljs-keyword">div</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"title border-topbottom"</span>>{{key}}</<span class="hljs-keyword">div</span>><br> <<span class="hljs-keyword">div</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"item-list"</span>><br> <<span class="hljs-keyword">div</span><br> <span class="hljs-keyword">class</span>=<span class="hljs-string">"item border-bottom"</span><br> v-<span class="hljs-keyword">for</span>=<span class="hljs-string">"innerItem of item"</span><br> :key=<span class="hljs-string">"innerItem.id"</span><br> @click=<span class="hljs-string">"handleCityClick(innerItem.name)"</span><br> ><br> {{innerItem.name}}<br> </<span class="hljs-keyword">div</span>><br> </<span class="hljs-keyword">div</span>><br> </<span class="hljs-keyword">div</span>><br> </<span class="hljs-keyword">div</span>><br></<span class="hljs-keyword">div</span>><br>export <span class="hljs-keyword">default</span> {<br> name: <span class="hljs-string">'CityList'</span>,<br> props: {<br> cities: Object<br> },<br> methods: {<br> mounted () {<br> <span class="hljs-keyword">this</span>.scroll = <span class="hljs-keyword">new</span> Bscroll(<span class="hljs-keyword">this</span>.$refs.wrapper, {<br> click: <span class="hljs-keyword">true</span><br> })<br> },<br> updated () {<br> <span class="hljs-keyword">this</span>.scroll.refresh()<br> }<br>}<br></code></pre></td></tr></table></figure><p>上面是我开发项目中的部分代码,其中,父组件在created后通过axios请求数据,以props的形式传值给子组件,子组件要根据这个值渲染列表,也就是说,子组件里用到了Bscroll,但数据和dom结构依赖于父组件传过来的值而动态更新。<br/><br>在这里,我没法在数据获取成功的回调函数中使用nextTick,因此我想到一个办法,在updated钩子函数中,使用refresh函数,当父组件获取数据成功后,props中的值也会相应update,此时让scroll重新计算dom元素的高度,列表便能正常滚动了。<br/><br>当然,如果担心数据频繁变化,导致refresh刷新频繁,影响性能的话,可以利用timer进行防抖操作。</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">//data里定义一个timer</span><br>updated () {<br> <span class="hljs-keyword">const</span> refresh=<span class="hljs-built_in">this</span>.debounce(<span class="hljs-number">500</span>)<br> refresh()<br>},<br>methods: {<br> <span class="hljs-function"><span class="hljs-title">debounce</span>(<span class="hljs-params">delay</span>)</span>{<br> <span class="hljs-keyword">let</span> timer1 = <span class="hljs-literal">null</span><br> <span class="hljs-keyword">return</span> <span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">if</span> (timer1) <span class="hljs-built_in">clearTimeout</span>(timer1)<br> timer1 = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {<br> <span class="hljs-built_in">this</span>.scroll.refresh()<br> }, delay)<br> }<br>}<br><br></code></pre></td></tr></table></figure><h4 id="better-scroll和scrollBehavior"><a href="#better-scroll和scrollBehavior" class="headerlink" title="better-scroll和scrollBehavior"></a>better-scroll和scrollBehavior</h4><p>在项目中,我想让每次进入这个页面时,列表都回到顶端,因此我利用vue-router的scrollBehavior:</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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 applescript">scrollBehavior (<span class="hljs-keyword">to</span>, <span class="hljs-keyword">from</span>, savedPosition) {<br> <span class="hljs-keyword">if</span> (savedPosition) {<br><span class="hljs-built_in"> return</span> savedPosition<br> } <span class="hljs-keyword">else</span> {<br><span class="hljs-built_in"> return</span> { x: <span class="hljs-number">0</span>, y: <span class="hljs-number">0</span> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>然而这在better-scroll列表页中并没有没有生效,但是重新进入其他页面都能回到顶端,这又是为什么呢?<br><br/><br>我们要搞清楚这两个scroll的原理。当使用vue-router时,scrollBehavior记录的是整个页面的滚动,也就是说,页面是由于内容太多,自动撑高的,这个高度不固定。而使用better-scroll(再次引用上面那张图),外层wrapper的高度是固定下来的,滚动的是wrapper内的元素,因此,整个页面其实是没有滚动的,这样就造成了列表里面的元素还停留在原来的位置,但页面其实已经回到顶端的现象。<br><img src="/2021/02/08/better-scroll%E7%9A%84%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/%E5%8E%9F%E7%90%86%E5%9B%BE.png"><br/><br>那要怎么让better-scroll内滚动的元素回到最顶端呢?<br>可以利用scrollToElement(this.$refs.wrapper)方法,或者scrollTo(0, 0)方法。</p><p>以上是我自己在使用better-scroll时的一些体验与心得,如有不当,欢迎指出。</p><p>参考:<br/><br><a href="https://zhuanlan.zhihu.com/p/27407024">当 better-scroll 遇见 Vue</a></p>]]></content>
<categories>
<category>Vue</category>
</categories>
</entry>
<entry>
<title>Vue的父子组件的生命周期顺序</title>
<link href="/2021/02/08/Vue%E7%9A%84%E7%88%B6%E5%AD%90%E7%BB%84%E4%BB%B6%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%A1%BA%E5%BA%8F/"/>
<url>/2021/02/08/Vue%E7%9A%84%E7%88%B6%E5%AD%90%E7%BB%84%E4%BB%B6%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%A1%BA%E5%BA%8F/</url>
<content type="html"><![CDATA[<p>我们都知道,vue实例的生命周期依次是:</p><pre><code>beforeCreate -> created -> beforeMount -> mounted -> ( beforeUpdate -> updated ) -> beforeDestroy -> destroyed</code></pre><p>但当我们使用组件时,父组件和子组件的生命周期钩子的执行顺序又是怎样的呢?我们将通过代码验证:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></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">id</span>=<span class="hljs-string">"root"</span>></span><br> {{msg}}<br> <span class="hljs-tag"><<span class="hljs-name">child</span> <span class="hljs-attr">:message</span>=<span class="hljs-string">"fromParent"</span>></span><span class="hljs-tag"></<span class="hljs-name">child</span>></span><br><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//子组件</span><br>Vue.component(<span class="hljs-string">'child'</span>, {<br> template:<span class="hljs-string">`</span><br><span class="hljs-string"> <div>{{msg}}, I got {{message}}</div></span><br><span class="hljs-string"> `</span>,<br> props: [<span class="hljs-string">'message'</span>],<br> <span class="hljs-function"><span class="hljs-title">data</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">return</span>{<br> msg: <span class="hljs-string">'我是子组件'</span><br> }<br> },<br> beforeCreate () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child before create'</span>)<br> },<br> created () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child created'</span>)<br> },<br> beforeMount () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child before mount'</span>)<br> },<br> mounted () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child mounted'</span>)<br> },<br> beforeDestroy () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child before destroy'</span>)<br> },<br> destroyed () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child destroyed'</span>)<br> },<br> beforeUpdate () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child before update'</span>)<br> },<br> updated () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'child updated'</span>)<br> }<br>})<br><span class="hljs-comment">//根实例(父组件)</span><br><span class="hljs-keyword">var</span> vm = <span class="hljs-keyword">new</span> Vue({<br> el: <span class="hljs-string">'#root'</span>,<br> data: {<br> msg: <span class="hljs-string">'我是父组件'</span><br> },<br> beforeCreate () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent before created'</span>)<br> },<br> created () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent created'</span>)<br> },<br> beforeMount () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent before mount'</span>)<br> },<br> mounted () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent mounted'</span>)<br> },<br> beforeDestroy () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent before destroy'</span>)<br> },<br> destroyed () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent destroyed'</span>)<br> },<br> beforeUpdate () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent before update'</span>)<br> },<br> updated () {<br> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'parent updated'</span>)<br> }<br>})<br></code></pre></td></tr></table></figure><p> <strong>运行结果如下图:</strong> </p> <div align='center'> <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a3c8e02a1fd4a4abf1129950a7b99f8~tplv-k3u1fbpfcp-watermark.image"> </div> <div align=center>加载渲染</div> <br/> <div align='center'> <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/93685ff41f6041f4998692def0e8aead~tplv-k3u1fbpfcp-watermark.image"> </div> <div align=center>组件销毁</div> <br/> <div align='center'> <img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce7aff5f07c14a159f961da598029d8f~tplv-k3u1fbpfcp-watermark.image"> </div> <div align=center>父组件数据更新</div> <br/> <div align='center'> <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a19860f81ec43d7be454f6c3c35e1cd~tplv-k3u1fbpfcp-watermark.image"> </div> <div align=center>子组件数据更新(单向数据流)</div> 由此我们得知,父子组件的生命周期函数是嵌套的关系,**从外到内,再从内到外**]]></content>
<categories>
<category>Vue</category>
</categories>
<tags>
<tag>Vue</tag>
</tags>
</entry>
<entry>
<title>矩阵中的最长递归路径</title>
<link href="/2021/02/08/%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%BD%92%E8%B7%AF%E5%BE%84/"/>
<url>/2021/02/08/%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%BD%92%E8%B7%AF%E5%BE%84/</url>
<content type="html"><![CDATA[<h2 id="题目描述(leetcode329题)"><a href="#题目描述(leetcode329题)" class="headerlink" title="题目描述(leetcode329题)"></a>题目描述(leetcode329题)</h2><p><img src="/2021/02/08/%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%BD%92%E8%B7%AF%E5%BE%84/%E9%A2%98%E7%9B%AE%E6%8F%8F%E8%BF%B0.png" alt="题目描述"></p><h2 id="基于深度优先搜索"><a href="#基于深度优先搜索" class="headerlink" title="基于深度优先搜索"></a>基于深度优先搜索</h2><p>我们可以把矩阵每个单元格看作一个点,而若一个点的值小于相邻点的值,那么就看作这两个点之间有一条有向边,由小的点指向大的点。这样,求最长递增路径的问题,就变成了求有向无环图最长路径的问题。我们可以用深度优先搜索来解决。</p><p>那么问题又来了,如果直接使用深度优先搜索,那么将会大量重复计算一些节点的路径,因此我们采用记忆化的方法,用一个矩阵记录下已经算好的节点,若这个矩阵的节点值为0,则是没有计算过的,需要计算,否则,直接用它的值就可以了。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param <span class="hljs-type">{number[][]}</span> <span class="hljs-variable">matrix</span></span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return <span class="hljs-type">{number}</span></span></span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> longestIncreasingPath = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">matrix</span>) </span>{<br> <span class="hljs-keyword">var</span> m = matrix.length; <span class="hljs-keyword">var</span> n = matrix[<span class="hljs-number">0</span>].length;<br> <span class="hljs-keyword">var</span> path = [];<br> <span class="hljs-keyword">var</span> res = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">var</span> dfs = <span class="hljs-function">(<span class="hljs-params">i, j</span>) =></span> {<br> <span class="hljs-keyword">if</span>(path[i][j] != <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> path[i][j];<br> <span class="hljs-keyword">let</span> distance = <span class="hljs-number">0</span>;<br> <span class="hljs-comment">//可以往上走</span><br> <span class="hljs-keyword">if</span>(i><span class="hljs-number">0</span> && matrix[i][j] < matrix[i-<span class="hljs-number">1</span>][j]){<br> distance = <span class="hljs-built_in">Math</span>.max(dfs(i-<span class="hljs-number">1</span>,j),distance);<br> }<br> <span class="hljs-comment">//可以往下走</span><br> <span class="hljs-keyword">if</span>(i<m-<span class="hljs-number">1</span> && matrix[i][j] < matrix[i+<span class="hljs-number">1</span>][j]){<br> distance = <span class="hljs-built_in">Math</span>.max(dfs(i+<span class="hljs-number">1</span>,j),distance);<br> }<br> <span class="hljs-comment">//可以往左走</span><br> <span class="hljs-keyword">if</span>(j><span class="hljs-number">0</span> && matrix[i][j] < matrix[i][j-<span class="hljs-number">1</span>]){<br> distance = <span class="hljs-built_in">Math</span>.max(dfs(i,j-<span class="hljs-number">1</span>),distance);<br> }<br> <span class="hljs-comment">//可以往右走</span><br> <span class="hljs-keyword">if</span>(j<n-<span class="hljs-number">1</span> && matrix[i][j] < matrix[i][j+<span class="hljs-number">1</span>]){<br> distance = <span class="hljs-built_in">Math</span>.max(dfs(i,j+<span class="hljs-number">1</span>),distance);<br> }<br> path[i][j] = <span class="hljs-number">1</span> + distance;<br> <span class="hljs-keyword">return</span> path[i][j];<br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">0</span>;i<m;i++){<br> path[i] = [];<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> j=<span class="hljs-number">0</span>;j<n;j++){<br> path[i][j] = <span class="hljs-number">0</span>;<br> }<br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">0</span>;i<m;i++){<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> j=<span class="hljs-number">0</span>;j<n;j++){<br> res = <span class="hljs-built_in">Math</span>.max(dfs(i,j), res);<br> }<br> }<br> <span class="hljs-keyword">return</span> res;<br>};<br></code></pre></td></tr></table></figure><p>时间复杂度:O(mn) (深度优先搜索的时间复杂度为O(V+E),这里V=mn,E=4mn)</p><p>空间复杂度:O(mn)</p><h2 id="基于拓扑排序(动态规划)"><a href="#基于拓扑排序(动态规划)" class="headerlink" title="基于拓扑排序(动态规划)"></a>基于拓扑排序(动态规划)</h2><p>其实我一开始也有用动态规划解决问题的想法,用一个矩阵记录以该节点为起点最多能沿着递增路径走多远。</p><p>递推公式: <code>dp[i][j] = 1 + max(dp[x][y]) (x,y为i,j相邻的比i,j大的点)</code></p><p>然后通过不断更新dp矩阵,直到矩阵不变,矩阵中最大的值便是最长路径的长度。但是最坏情况下,可能要计算m+n-1次。从时间复杂度上说,是绝对不可取的。而官方题解,则给出了一个更加优雅的解法。</p><p>首先,计算所有节点的出度(从该节点能走到相邻的节点,则出度+1)。出度为0的,则一定是路径的终点,我们把它加进队列里面去。利用基于广度优先搜索的思路,不断更新出度矩阵:本轮中,对于队列里的每一个顶点v,如果其相邻的点u有一条到v的边,那么u的出度-1。这时,如果u的出度变为0,也就意味着u除了能通往v,不能再通向其他节点了。把u也加进队列里,等待下一轮的计算。在这里,我们计算了多少轮,其实也就代表着最长路径的长度。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> longestIncreasingPath = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">matrix</span>) </span>{<br> <span class="hljs-keyword">var</span> direction = [[<span class="hljs-number">0</span>,-<span class="hljs-number">1</span>],[<span class="hljs-number">0</span>,<span class="hljs-number">1</span>],[-<span class="hljs-number">1</span>,<span class="hljs-number">0</span>],[<span class="hljs-number">1</span>,<span class="hljs-number">0</span>]];<br> <span class="hljs-keyword">var</span> m = matrix.length; <span class="hljs-keyword">var</span> n = matrix[<span class="hljs-number">0</span>].length;<br> <span class="hljs-keyword">var</span> outDegree = [];<br> <span class="hljs-keyword">var</span> queue = [];<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>;i<m;i++){<br> outDegree[i] = [];<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> j=<span class="hljs-number">0</span>;j<n;j++){<br> outDegree[i][j] = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> dir <span class="hljs-keyword">of</span> direction){<br> <span class="hljs-keyword">let</span> row = i + dir[<span class="hljs-number">0</span>];<br> <span class="hljs-keyword">let</span> col = j + dir[<span class="hljs-number">1</span>];<br> <span class="hljs-keyword">if</span>(row >= <span class="hljs-number">0</span> && row < m && col >=<span class="hljs-number">0</span> && col < n && matrix[i][j] < matrix[row][col]){<br> outDegree[i][j]++<br> }<br> <br> }<br> <span class="hljs-keyword">if</span>(outDegree[i][j] == <span class="hljs-number">0</span>){<br> queue.push([i,j]);<br> }<br> }<br> }<br> <span class="hljs-keyword">var</span> res = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">while</span>(queue.length != <span class="hljs-number">0</span>){<br> res++;<br> <span class="hljs-keyword">let</span> len = queue.length;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> l=<span class="hljs-number">0</span>;l<len;l++){<br> <span class="hljs-keyword">let</span> cur = queue.shift();<br> <span class="hljs-keyword">let</span> i = cur[<span class="hljs-number">0</span>]; <span class="hljs-keyword">let</span> j = cur[<span class="hljs-number">1</span>];<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> dir <span class="hljs-keyword">of</span> direction){<br> <span class="hljs-keyword">let</span> row = i + dir[<span class="hljs-number">0</span>];<br> <span class="hljs-keyword">let</span> col = j + dir[<span class="hljs-number">1</span>];<br> <span class="hljs-keyword">if</span>(row >= <span class="hljs-number">0</span> && row < m && col >=<span class="hljs-number">0</span> && col < n && matrix[row][col] < matrix[i][j]){<br> outDegree[row][col]--;<br> <span class="hljs-keyword">if</span>(outDegree[row][col] == <span class="hljs-number">0</span>){<br> queue.push([row,col]);<br> }<br> }<br> <br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> res;<br>};<br></code></pre></td></tr></table></figure><p>在这个解法中,用direction数组来计算相邻点坐标,再验证其有效性,相比第一种解法的四个if判断,更加的高效。</p><p>时间复杂度:O(mn)</p><p>空间复杂度:O(mn)</p>]]></content>
<categories>
<category>Algorithm</category>
</categories>
<tags>
<tag>Leetcode</tag>
</tags>
</entry>
<entry>
<title>数组中的第k个最大元素</title>
<link href="/2021/02/08/%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACk%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0/"/>
<url>/2021/02/08/%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACk%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0/</url>
<content type="html"><![CDATA[<p><img src="/2021/02/08/%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACk%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0/Leetcode251.png" alt="题目描述"></p><h3 id="构造堆"><a href="#构造堆" class="headerlink" title="构造堆"></a>构造堆</h3><p>看到这个题目,找第k个大的元素,第一想法是堆排序!把数组按照堆排序的方式排列,然后进行k次移除最大的元素操作,就能得到结果。</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param <span class="hljs-type">{number[]}</span> <span class="hljs-variable">nums</span></span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">k</span></span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return <span class="hljs-type">{number}</span></span></span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> arr;<br><span class="hljs-keyword">var</span> size;<br><span class="hljs-keyword">var</span> findKthLargest = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">nums, k</span>) </span>{<br> <span class="hljs-comment">//buildHeap</span><br> <span class="hljs-keyword">var</span> result;<br> arr = nums;<br> size = arr.length;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-built_in">Math</span>.floor(size/<span class="hljs-number">2</span>)- <span class="hljs-number">1</span>;i>=<span class="hljs-number">0</span>;i--){<br> siftdown(i);<br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">0</span>;i<k;i++){<br> result = removeMax();<br> }<br> <span class="hljs-keyword">return</span> result;<br>};<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">siftdown</span>(<span class="hljs-params">i</span>)</span>{<br> <span class="hljs-keyword">if</span>(i > <span class="hljs-built_in">Math</span>.floor(size/<span class="hljs-number">2</span>)- <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span>;<br> <span class="hljs-keyword">let</span> child = <span class="hljs-number">2</span> * i + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span>(child < size-<span class="hljs-number">1</span>){<br> <span class="hljs-keyword">if</span>(arr[child] < arr[child+<span class="hljs-number">1</span>])<br> child++;<br> }<br> <span class="hljs-keyword">if</span>(arr[child] > arr[i]){<br> swap(child,i);<br> siftdown(child);<br> }<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>(<span class="hljs-params">i,j</span>)</span>{<br> <span class="hljs-keyword">let</span> temp = arr[i];<br> arr[i] = arr[j];<br> arr[j] = temp;<br>}v<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">removeMax</span>(<span class="hljs-params"></span>)</span>{<br> swap(<span class="hljs-number">0</span>,size-<span class="hljs-number">1</span>);<br> size--;<br> siftdown(<span class="hljs-number">0</span>);<br> <span class="hljs-keyword">return</span> arr[size];<br>}<br></code></pre></td></tr></table></figure><p>确实,这个思路能通过。 时间复杂度应该是O(n + klogn)=O(nlogn),空间复杂度是O(logn)。堆排序也确实是个不错的方法,但用堆排序有没有更优雅的方式呢?<br>当然是有的!<br>我们可以建一个大小为k的最小堆,来记录数组最大的k个元素。通过扫描数组一遍,不断更新调整这个最小堆,最后堆顶的元素,就是我们要求的结果:</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> findKthLargest = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">nums, k</span>) </span>{<br> <span class="hljs-keyword">var</span> heap = nums.slice(<span class="hljs-number">0</span>,k);<br> <span class="hljs-keyword">var</span> siftdown = <span class="hljs-function">(<span class="hljs-params">i</span>) =></span> {<br> <span class="hljs-keyword">if</span>(i > <span class="hljs-built_in">Math</span>.floor(k/<span class="hljs-number">2</span>)) <span class="hljs-keyword">return</span>;<br> <span class="hljs-keyword">let</span> child = <span class="hljs-number">2</span> * i + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span>(child < heap.length-<span class="hljs-number">1</span> && heap[child+<span class="hljs-number">1</span>] < heap[child]){<br> child++;<br> }<br> <span class="hljs-keyword">if</span>(heap[child] < heap[i]){<br> swap(heap,i,child);<br> siftdown(child);<br> }<br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-built_in">Math</span>.floor(k/<span class="hljs-number">2</span>);i>=<span class="hljs-number">0</span>;i--){<br> siftdown(i);<br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=k;i<nums.length;i++){<br> <span class="hljs-keyword">if</span>(nums[i] > heap[<span class="hljs-number">0</span>]){<br> heap[<span class="hljs-number">0</span>] = nums[i];<br> siftdown(<span class="hljs-number">0</span>);<br> }<br> }<br> <span class="hljs-keyword">return</span> heap[<span class="hljs-number">0</span>];<br>};<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>(<span class="hljs-params">nums, i, j</span>)</span>{<br> <span class="hljs-keyword">let</span> temp = nums[j];<br> nums[j] = nums[i];<br> nums[i] = temp;<br>}<br></code></pre></td></tr></table></figure><p>这个解法对比起对整个数组进行堆排序,用的堆空间更小了,空间复杂度是O(k),时间复杂度是O(nlogk)。</p><h3 id="基于快速排序的思路"><a href="#基于快速排序的思路" class="headerlink" title="基于快速排序的思路"></a>基于快速排序的思路</h3><p>除了利用堆的思路,我们还可以基于快速排序来减治。<br>随机选取一个轴值,通过一轮快速排序的交换,获取到这个轴值的位置,如果刚好是nums.length - k,我们就找到了第k大的元素,这是最好的情况。当然,如果是一般情况,我们也减小了问题的规模。<br>我们令目标值target = nums.length - k,若轴值的位置p<target,我们进一步在p的右边寻找第k大的元素;否则,我们在p的左边寻找。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> findKthLargest = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">nums, k</span>) </span>{<br> <span class="hljs-keyword">let</span> target = nums.length - k;<br> <span class="hljs-keyword">let</span> left = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">let</span> right = nums.length - <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>){<br> <span class="hljs-keyword">let</span> p = partition(nums,left,right);<br> <span class="hljs-keyword">if</span>(p === target) <span class="hljs-keyword">return</span> nums[p];<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(p < target) left = p + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">else</span> right = p - <span class="hljs-number">1</span>;<br> }<br>};<br><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">partition</span>(<span class="hljs-params">nums, left, right</span>)</span>{<br> <span class="hljs-keyword">let</span> p = nums[left];<br> <span class="hljs-keyword">let</span> j = left;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = left+<span class="hljs-number">1</span>;i<=right;i++){<br> <span class="hljs-keyword">if</span>(nums[i] < p){<br> j++;<br> swap(nums,j,i);<br> }<br> }<br> swap(nums,left,j);<br> <span class="hljs-keyword">return</span> j;<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>(<span class="hljs-params">nums,i,j</span>)</span>{<br> <span class="hljs-keyword">let</span> temp = nums[i];<br> nums[i] = nums[j];<br> nums[j] = temp;<br>}<br></code></pre></td></tr></table></figure><p>时间复杂度:O(n),空间复杂度:O(1)</p>]]></content>
<categories>
<category>Algorithm</category>
</categories>
<tags>
<tag>Leetcode</tag>
<tag>堆</tag>
<tag>快速排序</tag>
</tags>
</entry>
</search>