-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
459 lines (262 loc) · 272 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Kaviilee's blog</title>
<link href="/blog/atom.xml" rel="self"/>
<link href="https://kaviilee.github.io/blog/"/>
<updated>2023-01-02T18:31:29.271Z</updated>
<id>https://kaviilee.github.io/blog/</id>
<author>
<name>kaviilee</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>一些js小技巧</title>
<link href="https://kaviilee.github.io/blog/2022/04/23/%E4%B8%80%E4%BA%9Bjs%E5%B0%8F%E6%8A%80%E5%B7%A7/"/>
<id>https://kaviilee.github.io/blog/2022/04/23/%E4%B8%80%E4%BA%9Bjs%E5%B0%8F%E6%8A%80%E5%B7%A7/</id>
<published>2022-04-23T11:42:07.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>记录一些js的小技巧。</p><a id="more"></a><h2 id="一些js小技巧"><a href="#一些js小技巧" class="headerlink" title="一些js小技巧"></a>一些js小技巧</h2><h3 id="操作URL查询参数"><a href="#操作URL查询参数" class="headerlink" title="操作URL查询参数"></a>操作URL查询参数</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> params = <span class="keyword">new</span> URLSearchParams(location.search); <span class="comment">// location.search = '?key=dd&value=kk'</span></span><br><span class="line">params.has(<span class="string">'key'</span>); <span class="comment">// true</span></span><br><span class="line">params.get(<span class="string">'value'</span>); <span class="comment">// kk</span></span><br></pre></td></tr></table></figure><h3 id="取整"><a href="#取整" class="headerlink" title="取整"></a>取整</h3><blockquote><p>代替正数的 Math.floor(),代替负数的 Math.ceil()</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> num1 = ~~<span class="number">2.3</span>;</span><br><span class="line"><span class="keyword">const</span> num2 = <span class="number">2.3</span> | <span class="number">0</span>;</span><br><span class="line"><span class="keyword">const</span> num3 = <span class="number">2.3</span> >> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><h3 id="短路运算"><a href="#短路运算" class="headerlink" title="短路运算"></a>短路运算</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = d && <span class="number">1</span>; <span class="comment">// 满足条件赋值:取假运算,从左到右依次判断,遇到假值返回假值,后面不再执行,否则返回最后一个真值</span></span><br><span class="line"><span class="keyword">const</span> b = d || <span class="number">1</span>; <span class="comment">// 默认赋值:取真运算,从左到右依次判断,遇到真值返回真值,后面不再执行,否则返回最后一个假值</span></span><br><span class="line"><span class="keyword">const</span> c = !d; <span class="comment">// 取假赋值:单个表达式转换为true则返回false,否则返回true</span></span><br></pre></td></tr></table></figure><h3 id="判断空对象"><a href="#判断空对象" class="headerlink" title="判断空对象"></a>判断空对象</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = {};</span><br><span class="line"><span class="keyword">const</span> flag = !<span class="built_in">Object</span>.keys(obj).length; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h4 id="满足条件时执行"><a href="#满足条件时执行" class="headerlink" title="满足条件时执行"></a>满足条件时执行</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> flagA = <span class="literal">true</span>; <span class="comment">// 条件A</span></span><br><span class="line"><span class="keyword">const</span> flagB = <span class="literal">false</span>; <span class="comment">// 条件B</span></span><br><span class="line">(flagA || flagB) && Func(); <span class="comment">// 满足A或B时执行</span></span><br><span class="line">(flagA || !flagB) && Func(); <span class="comment">// 满足A或不满足B时执行</span></span><br><span class="line">flagA && flagB && Func(); <span class="comment">// 同时满足A和B时执行</span></span><br><span class="line">flagA && !flagB && Func(); <span class="comment">// 满足A且不满足B时执行</span></span><br></pre></td></tr></table></figure><h3 id="克隆数组"><a href="#克隆数组" class="headerlink" title="克隆数组"></a>克隆数组</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> _arr = [...arr]; <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="合并数组"><a href="#合并数组" class="headerlink" title="合并数组"></a>合并数组</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> arr2 = [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"><span class="keyword">const</span> arr = [...arr1, ...arr2]; <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br></pre></td></tr></table></figure><h3 id="数组去重"><a href="#数组去重" class="headerlink" title="数组去重"></a>数组去重</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [...new <span class="built_in">Set</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">6</span>, <span class="literal">null</span>, <span class="literal">null</span>])]; <span class="comment">// [1, 2 ,4, 6, null]</span></span><br></pre></td></tr></table></figure><h3 id="过滤空值"><a href="#过滤空值" class="headerlink" title="过滤空值"></a>过滤空值</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="literal">undefined</span>, <span class="literal">null</span>, <span class="string">""</span>, <span class="number">0</span>, <span class="literal">false</span>, <span class="literal">NaN</span>, <span class="number">1</span>, <span class="number">2</span>].filter(<span class="built_in">Boolean</span>); <span class="comment">// [1, 2]</span></span><br></pre></td></tr></table></figure><h3 id="统计成员个数"><a href="#统计成员个数" class="headerlink" title="统计成员个数"></a>统计成员个数</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">const</span> count = arr.reduce(<span class="function">(<span class="params">t, v</span>) =></span> {</span><br><span class="line"> t[v] = t[v] ? ++t[v] : <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> t;</span><br><span class="line">}, {});</span><br><span class="line"><span class="comment">// count => { 0: 1, 1: 2, 2: 3 }</span></span><br></pre></td></tr></table></figure><h3 id="按属性对Object分类"><a href="#按属性对Object分类" class="headerlink" title="按属性对Object分类"></a>按属性对Object分类</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> people = [</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'Alice'</span>, <span class="attr">age</span>: <span class="number">20</span> },</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'Max'</span>, <span class="attr">age</span>: <span class="number">21</span> },</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'Tom'</span>, <span class="attr">age</span>: <span class="number">20</span> }</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> groupBy = <span class="function">(<span class="params">objectArr, property</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> objectArr.reduce(<span class="function">(<span class="params">acc, obj</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> key = obj[property];</span><br><span class="line"> <span class="keyword">if</span> (!acc[key]) {</span><br><span class="line"> acc[key] = []</span><br><span class="line"> }</span><br><span class="line"> acc[key].push(obj);</span><br><span class="line"> <span class="keyword">return</span> acc;</span><br><span class="line"> }, {})</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> groupedPeople = groupBy(people, <span class="string">'age'</span>);</span><br><span class="line"><span class="comment">/* {</span></span><br><span class="line"><span class="comment"> 20: [</span></span><br><span class="line"><span class="comment"> { name: 'Max', age: 20 },</span></span><br><span class="line"><span class="comment"> { name: 'Jane', age: 20 }</span></span><br><span class="line"><span class="comment"> ],</span></span><br><span class="line"><span class="comment"> 21: [{ name: 'Alice', age: 21 }]</span></span><br><span class="line"><span class="comment">} */</span></span><br></pre></td></tr></table></figure><h3 id="删除对象无用属性"><a href="#删除对象无用属性" class="headerlink" title="删除对象无用属性"></a>删除对象无用属性</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = { <span class="attr">a</span>: <span class="number">0</span>, <span class="attr">b</span>: <span class="number">1</span>, <span class="attr">c</span>: <span class="number">2</span> }; <span class="comment">// 只想拿b和c</span></span><br><span class="line"><span class="keyword">const</span> { a, ...rest } = obj; <span class="comment">// { b: 1, c: 2 }</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>记录一些js的小技巧。</p>
</summary>
<category term="javascript" scheme="https://kaviilee.github.io/blog/tags/javascript/"/>
</entry>
<entry>
<title>刷刷算法</title>
<link href="https://kaviilee.github.io/blog/2021/03/30/%E5%88%B7%E5%88%B7%E7%AE%97%E6%B3%95/"/>
<id>https://kaviilee.github.io/blog/2021/03/30/%E5%88%B7%E5%88%B7%E7%AE%97%E6%B3%95/</id>
<published>2021-03-30T01:52:01.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>是算法耶!</p><a id="more"></a><ol><li>实现 Trie (前缀树)<br>Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。</li></ol><p>请你实现 Trie 类:</p><ul><li>Trie() 初始化前缀树对象。</li><li>void insert(String word) 向前缀树中插入字符串 word 。</li><li>boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。</li><li>boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。</li></ul><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> Trie {</span><br><span class="line"> root: { [key: <span class="built_in">string</span>]: <span class="built_in">any</span> }</span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">this</span>.root = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> insert(word: <span class="built_in">string</span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">let</span> node = <span class="keyword">this</span>.root;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> w of word) {</span><br><span class="line"> <span class="keyword">if</span> (!node[w]) {</span><br><span class="line"> node[w] = <span class="built_in">Object</span>.create(<span class="literal">null</span>);</span><br><span class="line"> }</span><br><span class="line"> node = node[w]</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> node.isEnd = <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> searchPrefix(word: <span class="built_in">string</span>) {</span><br><span class="line"> <span class="keyword">let</span> node = <span class="keyword">this</span>.root;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> w of word) {</span><br><span class="line"> node = node[w];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!node) <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> node</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> search(word: <span class="built_in">string</span>): <span class="built_in">boolean</span> {</span><br><span class="line"> <span class="keyword">let</span> node = <span class="keyword">this</span>.searchPrefix(word)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> !!node && !!node.isEnd;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> startWith(prefix: <span class="built_in">string</span>): <span class="built_in">boolean</span> {</span><br><span class="line"> <span class="keyword">return</span> !!<span class="keyword">this</span>.searchPrefix(prefix)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="2"><li>实现 LRU 算法</li></ol><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> LRUCache {</span><br><span class="line"> cache: Map<<span class="built_in">number</span>, <span class="built_in">number</span>>;</span><br><span class="line"> capacity: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params">capacity: <span class="built_in">number</span></span>) {</span><br><span class="line"> <span class="keyword">this</span>.cache = <span class="keyword">new</span> Map<<span class="built_in">number</span>, <span class="built_in">number</span>>()</span><br><span class="line"> <span class="keyword">this</span>.capacity = capcity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> put(key: <span class="built_in">number</span>, value: <span class="built_in">number</span>): <span class="built_in">void</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.cache.has(key)) {</span><br><span class="line"> <span class="keyword">this</span>.cache.delete(key);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>.cache.size >= <span class="keyword">this</span>.capacity) {</span><br><span class="line"> <span class="keyword">this</span>.cache.delete(<span class="keyword">this</span>.cache.keys().next().value)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.cache.set(key, value)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">get</span>(key: <span class="built_in">number</span>): <span class="built_in">number</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.cache.has(key)) {</span><br><span class="line"> <span class="keyword">const</span> val = <span class="keyword">this</span>.cache.get(key);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.cache.delete(key);</span><br><span class="line"> <span class="keyword">this</span>.cache.set(key, val);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> val</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>是算法耶!</p>
</summary>
<category term="算法" scheme="https://kaviilee.github.io/blog/tags/%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>凉经</title>
<link href="https://kaviilee.github.io/blog/2021/03/24/%E5%87%89%E7%BB%8F/"/>
<id>https://kaviilee.github.io/blog/2021/03/24/%E5%87%89%E7%BB%8F/</id>
<published>2021-03-24T15:31:09.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>记录一下自己 “被打” 的过程😂</p><a id="more"></a><h2 id="面试记录"><a href="#面试记录" class="headerlink" title="面试记录"></a>面试记录</h2><p>2021.2.28 从前公司离职,开始了复习知识和面试的日子。记录一下自己面试的经历以及题目,常看常新,补全知识。</p><h2 id="富途一面"><a href="#富途一面" class="headerlink" title="富途一面"></a>富途一面</h2><ol><li><p>Vue 组件间通信</p><ul><li><p>如果是父子组件,那么可以直接通过 <code>props</code> 进行通信,也可以使用 <code>provide/inject</code></p></li><li><p>Event Bus 通过新建一个 <code>Vue</code> 实例使两个组件能够进行通信</p></li><li><p>Vuex</p><blockquote><p>拓展:Vuex 和 Vue Router 在 install 的时候发生了什么?(其实是 Vue 插件机制)<br>其实 Vue 通过 use() 安装插件,就是通过 mixin 混入了方法。Vuex 和 Router 都是通过在 beforeCreate 这个生命周期里把 <code>$router</code> 和 <code>store</code> 挂载到了 Vue 的实例上。</p></blockquote></li></ul></li><li><p>Vue2 和 Vue3 的生命周期和区别</p><table><thead><tr><th align="center">Vue 2.x</th><th align="center">Vue 3.x</th><th align="left">Details</th></tr></thead><tbody><tr><td align="center">beforeCreate</td><td align="center">beforeCreate</td><td align="left">在实例初始化之后,数据观测和 event/watcher 事件配置之前被调用</td></tr><tr><td align="center">created</td><td align="center">created</td><td align="left">在实例创建完成之后立即被调用。<code>$el</code> property 不可用</td></tr><tr><td align="center">beforeMount</td><td align="center">beforeMount</td><td align="left">在挂载开始之前被调用:相关的 <code>render</code> 函数首次被调用</td></tr><tr><td align="center">mounted</td><td align="center">mounted</td><td align="left">实例被挂载后调用,此时可以使用 <code>nextTick</code> 和进行 DOM 操作</td></tr><tr><td align="center">beforeUpdate</td><td align="center">beforeUpdate</td><td align="left">数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新前访问现有 DOM,手动移除事件监听器</td></tr><tr><td align="center">updated</td><td align="center">updated</td><td align="left">由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。</td></tr><tr><td align="center">activated</td><td align="center">activated</td><td align="left">被 <code>keep-alive</code> 缓存的组件激活时调用</td></tr><tr><td align="center">deactivated</td><td align="center">deactivated</td><td align="left">被 <code>keep-alive</code> 缓存的组件停用时停用</td></tr><tr><td align="center">beforeDestroy</td><td align="center"><strong>beforeUmount</strong></td><td align="left">在销毁(卸载)组件实例之前调用。此时实例还是完全正常的</td></tr><tr><td align="center">destroyed</td><td align="center"><strong>unmounted</strong></td><td align="left">实例销毁(卸载)后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。</td></tr><tr><td align="center">errorCaptured</td><td align="center">errorCaptured</td><td align="left">当捕获一个来自子孙组件的错误时被调用。</td></tr><tr><td align="center">-</td><td align="center">renderTracked</td><td align="left">跟踪虚拟 DOM 重新渲染时调用。钩子接收 <code>debugger event</code> 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。</td></tr><tr><td align="center">-</td><td align="center">renderTriggered</td><td align="left">当虚拟 DOM 重新渲染为 triggered.Similarly 为 <code>renderTracked</code>,接收 <code>debugger event</code> 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。</td></tr></tbody></table><blockquote><p>拓展1: Vue 父子组件的渲染顺序</p><p>父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted</p><p>拓展2: 子组件更新过程</p><p>父beforeUpdate => 子beforeUpdate => 子updated => 父updated</p><p>拓展3: 销毁过程</p><p>父beforeDestroy => 子beforeDestroy => 子destroyed => 父destroyed</p></blockquote></li><li><p>浏览器渲染</p><p> 浏览器渲染进程主要有 GUI 渲染线程,JS 引擎线程,事件触发线程,定时触发器线程,异步 HTTP 请求线程。</p><p> 其中 GUI 渲染线程和 JS 引擎线程是互斥的。GUI 渲染线程负责渲染浏览器界面,解析 HTML 和 CSS,在负责处理 JavaScript 脚本程序的 JS 引擎线程执行时,GUI 渲染线程会暂时挂起,直到 JS 引擎空闲。</p><blockquote><p>延伸1:</p><ul><li>浏览器工作流程: 构建 DOM => 构建 CSSOM => 构建渲染树 => 布局 => 绘制。</li><li>CSSOM 会阻塞渲染,只有当 CSSOM 构建完毕之后才会进入下一阶段的构建树。</li><li>通常情况下 DOM 和 CSSOM 是并行构建的,但是当浏览器遇到 script 标签时,DOM 构建将暂停,直到脚本执行完成。但由于 JavaScript 可以修改 CSSOM,所以需要等 CSSOM 构建完毕后再执行 JS。</li><li>所以如果想要首屏渲染时间缩短,就需要把 script 标签放在 body 标签底部,或者直接不要在首屏加载 JS 文件。</li></ul></blockquote></li><li><p>浏览器缓存</p><p> 基本的网络请求就是三个步骤:请求,处理,响应。后端主要就是”处理”这个步骤,所以其实前端缓存就在“请求”和“响应”中进行。</p><p> 当浏览器要请求资源时:</p><ol><li>调用 Service Worker 的 fetch 事件</li><li>查看 memory cache</li><li>查看 disk cache:<ul><li>如果有强制缓存且未失效,则使用强制缓存,不请求服务器。状态码全部是200</li><li>如果有强制缓存但已失效,使用对比缓存,比较后确定是304还是200</li></ul></li><li>发送网络请求,等待响应</li><li>把响应内容存入 disk cache(如果 HTTP 头信息配置可以缓存)</li><li>把响应内容的<code>引用</code>存入 memory cache(无视 HTTP 头信息的配置)</li><li>把响应内容存入 Service Worker 的 Cache Storage</li></ol></li></ol><ol start="5"><li><p>this 指向问题</p><p> 可以从 Reference 类型来判断 this 的指向。</p><p> 首先判断出 MemberExpression 赋值给 ref,然后判断 ref 是不是一个 Reference 类型。</p> <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Reference = {</span><br><span class="line">// 属性所在的对象或 EnvironmentRecord,它的值只可能是 undefined, Object, Boolean, String, Number, environment record</span><br><span class="line">base,</span><br><span class="line">// 属性名</span><br><span class="line">name,</span><br><span class="line">// 是否为 strict 模式</span><br><span class="line">strict</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 举个栗子</span><br><span class="line">var value = 1;</span><br><span class="line">var foo = {</span><br><span class="line"> value: 2,</span><br><span class="line"> bar: function () {</span><br><span class="line"> return this.value;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">console.log(foo.bar()); // 2</span><br><span class="line">/*</span><br><span class="line"> MemberExpression 为 foo.bar</span><br><span class="line"></span><br><span class="line"> Renference = {</span><br><span class="line"> base: foo,</span><br><span class="line"> name: 'bar',</span><br><span class="line"> strict: false</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> 所以 this = foo,该表达式输出 2.</span><br><span class="line">*/</span><br><span class="line">// 有操作符,逻辑运算符或逗号运算符,ref 的就不是 Reference类型,this 就是 undefined,最终的 this 会隐式转换为全局对象,所以该表达式输出 1.</span><br><span class="line">console.log((foo.bar = foo.bar)()); // 1</span><br><span class="line">console.log((foo.bar || foo.bar)()); // 1</span><br><span class="line">console.log((foo.bar, foo.bar)()); // 1</span><br></pre></td></tr></table></figure><blockquote><p>拓展1: 将 foo.bar 改成 箭头函数会怎样?</p><p>拓展2: 将 var 换成 let 或 const 又会怎样?</p></blockquote></li><li><p>箭头函数和普通函数的区别</p></li></ol><blockquote><p>箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。</p></blockquote><ul><li>箭头函数没有自己的 this,它会捕获定义时自己所处的外层执行环境的 this,并继承这个 this。</li><li>箭头函数继承来的 this 指向永远不变</li><li>call,apply,bind 无法改变箭头函数中 this 的指向</li><li>箭头函数不能作为构造函数使用</li><li>箭头函数没有自己的 arguments 对象</li><li>箭头函数没有原型 prototype</li><li>箭头函数不能用作 Generator 函数,不能使用 yeild 关键字</li></ul><ol start="7"><li>斐波那契函数,以及缓存优化</li></ol><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 动态规划</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fib1</span>(<span class="params">n: <span class="built_in">number</span></span>): <span class="title">number</span> </span>{</span><br><span class="line"> <span class="keyword">let</span> a = <span class="number">0</span>, b = <span class="number">1</span>, sum;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> sum = (a + b) % <span class="number">1000000007</span>;</span><br><span class="line"> a = b;</span><br><span class="line"> b = sum;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 花里胡哨</span></span><br><span class="line"> <span class="keyword">let</span> a = <span class="number">0</span>, b= <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(n--) {</span><br><span class="line"> [a, b] = [b, (a + b) % <span class="number">1000000007</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> a</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 缓存 闭包</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fib2</span>(<span class="params">n: <span class="built_in">number</span></span>): <span class="title">number</span> </span>{</span><br><span class="line"> <span class="keyword">const</span> map = <span class="keyword">new</span> Map<<span class="built_in">number</span>, <span class="built_in">number</span>>();</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">sub</span>(<span class="params">n: <span class="built_in">number</span></span>): <span class="title">number</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n < <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">return</span> n</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (map.get(n)) {</span><br><span class="line"> <span class="keyword">return</span> map.get(n)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> sum = sub(n - <span class="number">1</span>) + sub(n - <span class="number">2</span>);</span><br><span class="line"> map.set(n, sum);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> sum % <span class="number">1000000007</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> sub(n)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="8"><li><p>Vue 数据量大的时候,如何优化</p><ul><li>按需加载局部数据,虚拟列表,无限下拉刷新(可视区域)</li><li>大量纯展示的数据,可以使用 <code>Object.freeze</code> 冻结</li><li>还可以进行分页</li></ul></li><li><p>图片网站 优化</p><p>首先我们分析一下图片加载存在的问题和原因:</p><ol><li><p>启动页面时加载过多图片</p><p>首屏图片优先加载,等首屏图片加载完成再去加载非首屏图片</p><p>可以进行域名切分,来提升并发的请求数量,或者使用 HTTP/2 协议</p></li><li><p>部分图片体积过大</p><p>单位像素优化 - 改变图片格式,使用 <code>webp</code> 格式</p><p>图片像素总数优化 - 裁剪大图片,减少图片体积,减少网络开销,加快下载速率</p></li></ol></li><li><p>防抖节流</p></li></ol><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 防抖 触发高频事件 n 秒内只会执行一次函数,n 秒内事件再次触发,则重新计算时间 如果你在期间一直触发,则不会触发</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">debounce</span>(<span class="params">fn, ms</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> timeout = <span class="literal">null</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> clearTimeout(timeout);</span><br><span class="line"> timeout = setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> fn.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>)</span><br><span class="line"> }, ms)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 节流 触发高频事件,但在 n 秒内只会执行一次,所以节流会稀释函数的执行效率 一定会触发</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">throttle</span>(<span class="params">fn, ms</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> timeout;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!timeout) {</span><br><span class="line"> timeout = setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> timeout = <span class="literal">null</span></span><br><span class="line"> fn.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>)</span><br><span class="line"> }, ms)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="11"><li><p>[事件循环机制](<a href="https://zhuanlan.zhihu.com/p/33058983" target="_blank" rel="noopener">详解JavaScript中的Event Loop(事件循环)机制 - 知乎 (zhihu.com)</a></p></li><li><p>var,let,const 的区别</p></li></ol><p>let/const 是使用块级作用域;var 使用的是函数作用域,且存在变量提升。</p><p>在函数外部使用 var 进行声明都会自动成为 window 对象上的一个属性。</p><ol start="13"><li>暂时性死区(TDZ)</li></ol><p>只要块级作用域内存在 <code>let</code> 命令,它所声明的变量就”绑定”这个区域,不再受外部的影响。</p><p>ES6 中规定,如果区块中存在 <code>let</code> 和 <code>const</code> 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明前使用这些变量,就会报错。</p><ol start="14"><li>闭包</li></ol><p>能够访问自由变量的函数被称为闭包。</p><p>自由变量:在函数中使用的既不是函数参数也不是局部变量的变量。</p><ol start="15"><li>HTTPS 和 HTTP 的区别</li></ol><ul><li>HTTPS 是 HTTP 协议的安全版本,HTTP 协议的数据传输是明文的,是不安全的,HTTPS 使用了 SSL/TLS 协议进行加密处理。</li><li>HTTP 和 HTTPS 使用的链接方式不同,默认端口也不一样,前者是80,后者是443.</li></ul><blockquote><p>引申: 对称加密和非对称加密 使用不同密钥进行加密和解密就是非对称加密,相对的加密解密使用同一密钥的就是对称加密。</p></blockquote><ol start="16"><li>HTTP2.0 和 HTTP1.1 的区别?多个请求在这两个协议之间的区别?如果请求的是不同主域,那么区别在哪?</li></ol><p>HTTP2.0</p><ul><li>多路复用</li><li>header 压缩</li><li>服务端推送</li></ul><p>HTTP1.1</p><ul><li><code>keepalive</code> 可以让 HTTP 重用 TCP 链接,就是所谓的长链接。</li></ul><p>HTTP2.0 会采用多路复用,一次 TCP 连接即可,采用 HTTP 的话,如果没有设置 <code>keepalive</code> 那么一次请求就要建立一个 TCP 连接。</p><h2 id="探迹一面"><a href="#探迹一面" class="headerlink" title="探迹一面"></a>探迹一面</h2><ol><li>事件机制</li><li>事件循环</li><li>Vue 响应式原理</li><li>react 优化</li><li>setState</li><li>数组去重</li><li>深浅拷贝</li><li>get post 区别</li><li>前端性能优化</li><li>call的实现</li><li>node require</li><li>node 怎么读文件</li></ol>]]></content>
<summary type="html">
<p>记录一下自己 “被打” 的过程😂</p>
</summary>
<category term="面经" scheme="https://kaviilee.github.io/blog/tags/%E9%9D%A2%E7%BB%8F/"/>
</entry>
<entry>
<title>修改 console.log 的颜色</title>
<link href="https://kaviilee.github.io/blog/2021/03/22/%E4%BF%AE%E6%94%B9console%E7%9A%84%E9%A2%9C%E8%89%B2/"/>
<id>https://kaviilee.github.io/blog/2021/03/22/%E4%BF%AE%E6%94%B9console%E7%9A%84%E9%A2%9C%E8%89%B2/</id>
<published>2021-03-22T16:49:05.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>今日用 node 写一个小工具的时候,想要 <code>console</code> 输出不一样的颜色,又不想而外引入工具库,遂查找资料,记录一下。</p><a id="more"></a><h2 id="不使用库"><a href="#不使用库" class="headerlink" title="不使用库"></a>不使用库</h2><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">console.log(fg, bg, content, "\x1b[0m");</span><br></pre></td></tr></table></figure><p>这里 <code>fg</code> 其实就是字体颜色,<code>bg</code> 对应背景颜色,<code>content</code> 就是要输出的内容</p><p>以下列出其他的颜色</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Reset = "\x1b[0m"</span><br><span class="line">Bright = "\x1b[1m"</span><br><span class="line">Dim = "\x1b[2m"</span><br><span class="line">Underscore = "\x1b[4m"</span><br><span class="line">Blink = "\x1b[5m"</span><br><span class="line">Reverse = "\x1b[7m"</span><br><span class="line">Hidden = "\x1b[8m"</span><br><span class="line"></span><br><span class="line">FgBlack = "\x1b[30m"</span><br><span class="line">FgRed = "\x1b[31m"</span><br><span class="line">FgGreen = "\x1b[32m"</span><br><span class="line">FgYellow = "\x1b[33m"</span><br><span class="line">FgBlue = "\x1b[34m"</span><br><span class="line">FgMagenta = "\x1b[35m"</span><br><span class="line">FgCyan = "\x1b[36m"</span><br><span class="line">FgWhite = "\x1b[37m"</span><br><span class="line"></span><br><span class="line">BgBlack = "\x1b[40m"</span><br><span class="line">BgRed = "\x1b[41m"</span><br><span class="line">BgGreen = "\x1b[42m"</span><br><span class="line">BgYellow = "\x1b[43m"</span><br><span class="line">BgBlue = "\x1b[44m"</span><br><span class="line">BgMagenta = "\x1b[45m"</span><br><span class="line">BgCyan = "\x1b[46m"</span><br><span class="line">BgWhite = "\x1b[47m"</span><br></pre></td></tr></table></figure><p><code>Fgxx</code> 对应的是字体颜色,<code>Bgxx</code> 对应的是背景颜色。</p><p>当然我们可以封装一下,使调用更加便捷一点</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> colors = {</span><br><span class="line"> reset: <span class="string">"\x1b[0m"</span>,</span><br><span class="line"> bright: <span class="string">"\x1b[1m"</span>,</span><br><span class="line"> dim: <span class="string">"\x1b[2m"</span>,</span><br><span class="line"> underscore: <span class="string">"\x1b[4m"</span>,</span><br><span class="line"> blink: <span class="string">"\x1b[5m"</span>,</span><br><span class="line"> reverse: <span class="string">"\x1b[7m"</span>,</span><br><span class="line"> hidden: <span class="string">"\x1b[8m"</span>,</span><br><span class="line"> </span><br><span class="line"> fg: {</span><br><span class="line"> black: <span class="string">"\x1b[30m"</span>,</span><br><span class="line"> red: <span class="string">"\x1b[31m"</span>,</span><br><span class="line"> green: <span class="string">"\x1b[32m"</span>,</span><br><span class="line"> yellow: <span class="string">"\x1b[33m"</span>,</span><br><span class="line"> blue: <span class="string">"\x1b[34m"</span>,</span><br><span class="line"> magenta: <span class="string">"\x1b[35m"</span>,</span><br><span class="line"> cyan: <span class="string">"\x1b[36m"</span>,</span><br><span class="line"> white: <span class="string">"\x1b[37m"</span>,</span><br><span class="line"> },</span><br><span class="line"> bg: {</span><br><span class="line"> black: <span class="string">"\x1b[40m"</span>,</span><br><span class="line"> red: <span class="string">"\x1b[41m"</span>,</span><br><span class="line"> green: <span class="string">"\x1b[42m"</span>,</span><br><span class="line"> yellow: <span class="string">"\x1b[43m"</span>,</span><br><span class="line"> blue: <span class="string">"\x1b[44m"</span>,</span><br><span class="line"> magenta: <span class="string">"\x1b[45m"</span>,</span><br><span class="line"> cyan: <span class="string">"\x1b[46m"</span>,</span><br><span class="line"> white: <span class="string">"\x1b[47m"</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="使用库"><a href="#使用库" class="headerlink" title="使用库"></a>使用库</h2><p>修改 console 颜色的库有</p><ul><li><a href="https://github.com/chalk/chalk" target="_blank" rel="noopener">chalk</a></li><li><a href="https://github.com/Marak/colors.js" target="_blank" rel="noopener">colors</a></li><li><a href="https://github.com/medikoo/cli-color" target="_blank" rel="noopener">cli-color</a></li></ul><h2 id="碎碎念"><a href="#碎碎念" class="headerlink" title="碎碎念"></a>碎碎念</h2><p>好看的 <code>console.log</code> 可以让写代码的我快乐(๑•̀ㅂ•́)و✧</p>]]></content>
<summary type="html">
<p>今日用 node 写一个小工具的时候,想要 <code>console</code> 输出不一样的颜色,又不想而外引入工具库,遂查找资料,记录一下。</p>
</summary>
<category term="其他" scheme="https://kaviilee.github.io/blog/tags/%E5%85%B6%E4%BB%96/"/>
</entry>
<entry>
<title>webpack 常用 loaders 和 plugins</title>
<link href="https://kaviilee.github.io/blog/2021/03/20/webpack%E5%B8%B8%E7%94%A8loaders%E5%92%8Cplugins/"/>
<id>https://kaviilee.github.io/blog/2021/03/20/webpack%E5%B8%B8%E7%94%A8loaders%E5%92%8Cplugins/</id>
<published>2021-03-20T21:27:36.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>记录一下 <code>webpack</code> 常用 <code>loaders</code> 和 <code>plugins</code></p><a id="more"></a><h2 id="常用-Loaders"><a href="#常用-Loaders" class="headerlink" title="常用 Loaders"></a>常用 Loaders</h2><h3 id="加载文件"><a href="#加载文件" class="headerlink" title="加载文件"></a>加载文件</h3><ul><li><strong><a href="https://github.com/webpack-contrib/raw-loader" target="_blank" rel="noopener">raw-loader</a></strong>:把文本文件的内容加载到代码中去。</li><li><strong><a href="https://github.com/webpack-contrib/file-loader" target="_blank" rel="noopener">file-loader</a></strong>:把文本输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件。</li><li><strong><a href="https://github.com/webpack-contrib/url-loader" target="_blank" rel="noopener">url-loader</a></strong>:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去。</li><li><strong><a href="https://github.com/webpack-contrib/source-map-loader" target="_blank" rel="noopener">source-map-loader</a></strong>:加载额外的 Source Map 文件,以方便断点调试。</li><li><strong><a href="https://github.com/webpack-contrib/svg-inline-loader" target="_blank" rel="noopener">svg-inline-loader</a></strong>:把压缩后的 SVG 内容注入到代码中。</li><li><strong><a href="https://github.com/webpack-contrib/node-loader" target="_blank" rel="noopener">node-loader</a></strong>:加载 Node.js 原生模块 .node 文件。</li><li><strong><a href="https://github.com/tcoopman/image-webpack-loader" target="_blank" rel="noopener">image-loader</a></strong>:加载并且压缩图片文件。</li><li><strong><a href="https://github.com/webpack-contrib/json-loader" target="_blank" rel="noopener">json-loader</a></strong>:加载 JSON 文件。</li><li><strong><a href="https://github.com/eemeli/yaml-loader" target="_blank" rel="noopener">yaml-loader</a></strong>:加载 YAML 文件。</li></ul><h3 id="编译模板"><a href="#编译模板" class="headerlink" title="编译模板"></a>编译模板</h3><ul><li><strong><a href="https://github.com/pugjs/pug-loader" target="_blank" rel="noopener">pug-loader</a></strong>:把 Pug 模版转换成 JavaScript 函数返回。</li><li><strong><a href="https://github.com/difelice/ejs-loader" target="_blank" rel="noopener">ejs-loader</a></strong>:把 EJS 模版编译成函数返回。</li><li><strong><a href="https://github.com/peerigon/markdown-loader" target="_blank" rel="noopener">markdown-loader</a></strong>:把 Markdown 文件转换成 HTML。</li></ul><h3 id="转换脚本语言"><a href="#转换脚本语言" class="headerlink" title="转换脚本语言"></a>转换脚本语言</h3><ul><li><strong><a href="https://github.com/babel/babel-loader" target="_blank" rel="noopener">babel-loader</a></strong>:把 ES6 转换成 ES5。</li><li><strong><a href="https://github.com/s-panferov/awesome-typescript-loader" target="_blank" rel="noopener">awesome-typescript-loader</a></strong>:把 TypeScript 转换成 JavaScript,性能要比 ts-loader 好。</li><li><strong><a href="https://github.com/webpack-contrib/coffee-loader" target="_blank" rel="noopener">coffee-loader</a></strong>:把 CoffeeScript 转换成 JavaScript。</li></ul><h3 id="转换样式文件"><a href="#转换样式文件" class="headerlink" title="转换样式文件"></a>转换样式文件</h3><ul><li><strong><a href="https://github.com/webpack-contrib/css-loader" target="_blank" rel="noopener">css-loader</a></strong>:加载 CSS,支持模块化、压缩、文件导入等特性。</li><li><strong><a href="https://github.com/webpack-contrib/style-loader" target="_blank" rel="noopener">style-loader</a></strong>:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。</li><li><strong><a href="https://github.com/webpack-contrib/sass-loader" target="_blank" rel="noopener">sass-loader</a></strong>:把 SCSS/SASS 代码转换成 CSS。</li><li><strong><a href="https://github.com/webpack-contrib/postcss-loader" target="_blank" rel="noopener">postcss-loader</a></strong>:扩展 CSS 语法,使用下一代 CSS。</li><li><strong><a href="https://github.com/webpack-contrib/less-loader" target="_blank" rel="noopener">less-loader</a></strong>:把 Less 代码转换成 CSS 代码。</li><li><strong><a href="https://github.com/webpack-contrib/stylus-loader" target="_blank" rel="noopener">stylus-loader</a></strong>:把 Stylus 代码转换成 CSS 代码。</li></ul><h3 id="检查代码"><a href="#检查代码" class="headerlink" title="检查代码"></a>检查代码</h3><ul><li><strong><a href="https://github.com/webpack-contrib/eslint-loader" target="_blank" rel="noopener">eslint-loader</a></strong>:通过 ESLint 检查 JavaScript 代码。</li><li><strong><a href="https://github.com/wbuchwalter/tslint-loader" target="_blank" rel="noopener">tslint-loader</a></strong>:通过 TSLint 检查 TypeScript 代码。</li><li><strong><a href="https://github.com/webpack-contrib/mocha-loader" target="_blank" rel="noopener">mocha-loader</a></strong>:加载 Mocha 测试用例代码。</li></ul><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul><li><strong><a href="https://github.com/vuejs/vue-loader" target="_blank" rel="noopener">vue-loader</a></strong>:加载 Vue.js 单文件组件。</li></ul><h2 id="常用-Plugins"><a href="#常用-Plugins" class="headerlink" title="常用 Plugins"></a>常用 Plugins</h2><ul><li><strong><a href="https://webpack.js.org/plugins/commons-chunk-plugin" target="_blank" rel="noopener">commons-chunk-plugin</a></strong>:提取公共代码,在4-11提取公共代码中有介绍。</li><li><strong><a href="https://github.com/webpack-contrib/extract-text-webpack-plugin" target="_blank" rel="noopener">extract-text-webpack-plugin</a></strong>:提取 JavaScript 中的 CSS 代码到单独的文件中。</li><li><strong><a href="https://github.com/gajus/prepack-webpack-plugin" target="_blank" rel="noopener">prepack-webpack-plugin</a></strong>:通过 Facebook 的 Prepack 优化输出的 JavaScript 代码性能。</li><li><strong><a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">uglifyjs-webpack-plugin</a></strong>:通过 UglifyES 压缩 ES6 代码。</li><li><strong><a href="https://github.com/gdborton/webpack-parallel-uglify-plugin" target="_blank" rel="noopener">webpack-parallel-uglify-plugin</a></strong>:多进程执行 UglifyJS 代码压缩,提升构建速度。</li><li><strong><a href="https://github.com/amireh/happypack" target="_blank" rel="noopener">happypack</a></strong>:多进程处理任务。</li><li><strong><a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener">ModuleConcatenationPlugin</a></strong>:开启 Webpack Scope Hoisting 功能。</li></ul>]]></content>
<summary type="html">
<p>记录一下 <code>webpack</code> 常用 <code>loaders</code> 和 <code>plugins</code></p>
</summary>
<category term="前端工程化" scheme="https://kaviilee.github.io/blog/categories/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
<category term="webpack" scheme="https://kaviilee.github.io/blog/tags/webpack/"/>
</entry>
<entry>
<title>webpack.config 整体结构</title>
<link href="https://kaviilee.github.io/blog/2021/03/20/webpack-config%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84/"/>
<id>https://kaviilee.github.io/blog/2021/03/20/webpack-config%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84/</id>
<published>2021-03-20T17:26:19.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>记录一下 <code>webpack</code> 配置对应的意义,便于自己查阅~</p><a id="more"></a><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>)</span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="comment">// entry 表示入口,webpack 执行构建的第一步从 entry 开始</span></span><br><span class="line"> <span class="comment">// 类型可以是 String | Object | Array</span></span><br><span class="line"> entry: <span class="string">'index.js'</span>, <span class="comment">// 只有一个入口, 入口只有一个文件</span></span><br><span class="line"> entry: [<span class="string">'index1.js'</span>, <span class="string">'index2.js'</span>], <span class="comment">// 只有一个入口,入口有2个文件</span></span><br><span class="line"> entry: { <span class="comment">// 有两个入口</span></span><br><span class="line"> a: <span class="string">'indexA.js'</span>,</span><br><span class="line"> b: [<span class="string">'indexB1.js'</span>, <span class="string">'indexB2.js'</span>]</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如何输出结果:在 webpack 的一系列处理之后,如何输出最终的代码</span></span><br><span class="line"> output: {</span><br><span class="line"> <span class="comment">// 输出文件的存放目录,必须是 String 类型的绝对路径</span></span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 输出文件的名称</span></span><br><span class="line"> filename: <span class="string">'bundle.js'</span>, <span class="comment">// 完整的名称</span></span><br><span class="line"> <span class="comment">// 当配置了多个 entry 时,通过名称模板为不同的 entry 生成不同的文件名称</span></span><br><span class="line"> filename: <span class="string">'[name].js'</span>,</span><br><span class="line"> <span class="comment">// 根据文件内容 hash 值生成文件名称,用于浏览器长时间缓存文件</span></span><br><span class="line"> filename: <span class="string">'[chunkhash].js'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发布到线上的所有资源的 URL 前缀,String 类型</span></span><br><span class="line"> publicPath: <span class="string">'/public'</span>, <span class="comment">// 放到指定目录下</span></span><br><span class="line"> publicPath: <span class="string">''</span>, <span class="comment">// 放到当前目录下</span></span><br><span class="line"> publicPath: <span class="string">'https://cdn.jsdelivr.net/'</span>, <span class="comment">// 放到 CDN 上</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 导出库的名称 String 类型</span></span><br><span class="line"> <span class="comment">// 缺省时,默认输出格式是匿名的立即执行函数</span></span><br><span class="line"> library: <span class="string">'MyLibrary'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 导出库的类型,枚举类型 默认是 var</span></span><br><span class="line"> <span class="comment">// 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign</span></span><br><span class="line"> <span class="comment">// | global | jsonp</span></span><br><span class="line"> libraryTarget: <span class="string">'umd'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否包含有用的文件路径信息到生成的代码中 Boolean 类型</span></span><br><span class="line"> pathinfo: <span class="literal">true</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 附加 Chunk 的文件名称</span></span><br><span class="line"> chunkFilename: <span class="string">'[id].js'</span>,</span><br><span class="line"> chunkFilename: <span class="string">'[chunkhash].js'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// JSONP 异步加载资源时的回调函数名称,需要和服务端配合使用</span></span><br><span class="line"> jsonpFunction: <span class="string">'webpackJsonp'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 生成的 Source Map 文件名称</span></span><br><span class="line"> sourceMapFilename: <span class="string">'[file].map'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 浏览器开发者工具里显示的源码模块名称</span></span><br><span class="line"> devtoolModuleFilenameTemplate: <span class="string">'webpack:///[resource-path]'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 异步加载跨域的资源时使用的方式</span></span><br><span class="line"> crossOriginLoading: <span class="string">'use-credentials'</span>,</span><br><span class="line"> crossOriginLoading: <span class="string">'anonymous'</span>,</span><br><span class="line"> crossOriginLoading: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 配置模块相关</span></span><br><span class="line"> <span class="built_in">module</span>: {</span><br><span class="line"> rules: [ <span class="comment">// loader 配置</span></span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.jsx?$/</span>, <span class="comment">// 正则匹配命中要使用 loader 的文件</span></span><br><span class="line"> include: [ <span class="comment">// 只会名字这里的文件</span></span><br><span class="line"> path.resolve(__dirname, <span class="string">'src'</span>),</span><br><span class="line"> ],</span><br><span class="line"> exclude: [ <span class="comment">// 忽略这里的文件</span></span><br><span class="line"> /node_modules/</span><br><span class="line"> ],</span><br><span class="line"> use: [ <span class="comment">// 使用哪些 loader,从右到左,从后到前执行</span></span><br><span class="line"> <span class="string">'style-loader'</span>,</span><br><span class="line"> {</span><br><span class="line"> loader: <span class="string">'css-loader'</span>,</span><br><span class="line"> options: {}</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> noParse: [ <span class="comment">// 不用解析和处理的模块</span></span><br><span class="line"> /special-library\.js$/ <span class="comment">// 正则匹配</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 配置插件</span></span><br><span class="line"> plugins: {},</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 配置寻找模块的规则</span></span><br><span class="line"> resolve: {</span><br><span class="line"> modules: [ <span class="comment">// 寻找模块的根目录,Array 类型,默认根目录为 node_modules</span></span><br><span class="line"> <span class="string">'node_modules'</span>,</span><br><span class="line"> path.resolve(__dirname, <span class="string">'src'</span>)</span><br><span class="line"> ],</span><br><span class="line"> extensions: [<span class="string">'.js'</span>, <span class="string">'json'</span>, <span class="string">'jsx'</span>, <span class="string">'.css'</span>], <span class="comment">// 模块的后缀名</span></span><br><span class="line"> alias: { <span class="comment">// 模块别名配置,用于映射模块</span></span><br><span class="line"> <span class="comment">// 把 '@' 映射为 'src','@/index'会被映射为 'src/index'</span></span><br><span class="line"> <span class="string">'@'</span>: path.resolve(__dirname, <span class="string">'src'</span>),</span><br><span class="line"> <span class="comment">// 使用结尾符号 $ 后,把 'only-module' 映射为 'new-module'</span></span><br><span class="line"> <span class="comment">// 但不会把 'only-module/index' 映射为 'new-module/index'</span></span><br><span class="line"> <span class="string">'only-module$'</span>: <span class="string">'new-module'</span></span><br><span class="line"> },</span><br><span class="line"> alias: [ <span class="comment">// 支持数组来配置</span></span><br><span class="line"> {</span><br><span class="line"> name: <span class="string">'module'</span>, <span class="comment">// 老的模块</span></span><br><span class="line"> alias: <span class="string">'new-module'</span>, <span class="comment">// 新的模块</span></span><br><span class="line"> <span class="comment">// 是否只映射模块,对应上面是否有 $</span></span><br><span class="line"> onlyModule: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> symlinks: <span class="literal">true</span>, <span class="comment">// 是否跟随文件软链接去搜寻模块的路径</span></span><br><span class="line"> descriptionFiles: [<span class="string">'package.json'</span>], <span class="comment">// 模块的描述文件</span></span><br><span class="line"> mainFields: [<span class="string">'main'</span>], <span class="comment">// 模块的描述文件里的描述入口的文件的字段</span></span><br><span class="line"> enforceExtension: <span class="literal">false</span>, <span class="comment">// 是否强制导入语句必须要写明文件后缀</span></span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 输出文件性能检查配置</span></span><br><span class="line"> performance: {</span><br><span class="line"> hints: <span class="string">'warning'</span>, <span class="comment">// 有性能问题时输出 'warning' 警告 'error' 错误 false 关闭</span></span><br><span class="line"> maxAssetSize: <span class="number">200000</span>, <span class="comment">// 最大文件大小 bytes</span></span><br><span class="line"> maxEntrypointSize: <span class="number">400000</span>, <span class="comment">// 最大入口文件大小 bytes</span></span><br><span class="line"> assetFilter: <span class="function"><span class="keyword">function</span> (<span class="params">assetFilename</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> assetFilename.endWith(<span class="string">'.css'</span>) || assetFilename.endWith(<span class="string">'.js'</span>)</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> devtool: <span class="string">'source-map'</span>, <span class="comment">// 配置 source-map 类型</span></span><br><span class="line"></span><br><span class="line"> context: __dirname, <span class="comment">// webpack 使用的根目录,String 类型 必须是绝对路径</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 配置输出代码的运行环境</span></span><br><span class="line"></span><br><span class="line"> target: <span class="string">'web'</span>, <span class="comment">// 浏览器,默认</span></span><br><span class="line"> target: <span class="string">'webworker'</span>, <span class="comment">// WebWorker</span></span><br><span class="line"> target: <span class="string">'node'</span>, <span class="comment">// Node.js,使用 `require` 语句加载 Chunk 代码</span></span><br><span class="line"> target: <span class="string">'async-node'</span>, <span class="comment">// Node.js,异步加载 Chunk 代码</span></span><br><span class="line"> target: <span class="string">'node-webkit'</span>, <span class="comment">// nw.js</span></span><br><span class="line"> target: <span class="string">'electron-main'</span>, <span class="comment">// electron, 主线程</span></span><br><span class="line"> target: <span class="string">'electron-renderer'</span>, <span class="comment">// electron, 渲染线程</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用来自 JavaScript 运行环境提供的全局编写</span></span><br><span class="line"> externals: {</span><br><span class="line"> jquery: <span class="string">'jQuery'</span></span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 控制台输出日志控制</span></span><br><span class="line"> stats: {</span><br><span class="line"> assets: <span class="literal">true</span>,</span><br><span class="line"> colors: <span class="literal">true</span>,</span><br><span class="line"> errors: <span class="literal">true</span>,</span><br><span class="line"> errorDetails: <span class="literal">true</span>,</span><br><span class="line"> hash: <span class="literal">true</span>,</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// DevServer 相关配置</span></span><br><span class="line"> devServer: {</span><br><span class="line"> proxy: { <span class="comment">// 代理到后端服务接口</span></span><br><span class="line"> <span class="string">'/api'</span>: <span class="string">'http://localhost:3000'</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 配置 DevServer HTTP 服务器的文件目录</span></span><br><span class="line"> contentBase: path.join(__dirname, <span class="string">'public'</span>),</span><br><span class="line"> compress: <span class="literal">true</span>, <span class="comment">// 是否开启 gzip 压缩</span></span><br><span class="line"> historyApiFallback: <span class="literal">true</span>, <span class="comment">// 是否开发 HTML5 History API 网页</span></span><br><span class="line"> hot: <span class="literal">true</span>, <span class="comment">// 是否开启模块热替换功能</span></span><br><span class="line"> https: <span class="literal">false</span>, <span class="comment">// 是否开启 HTTPS 模式</span></span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> profile: <span class="literal">true</span>, <span class="comment">// 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳</span></span><br><span class="line"> cache: <span class="literal">false</span>, <span class="comment">// 是否开启缓存提升构建速度</span></span><br><span class="line"></span><br><span class="line"> watch: <span class="literal">true</span>, <span class="comment">// 是否开启监听模式</span></span><br><span class="line"> watchOptions: { <span class="comment">// 监听模式选项</span></span><br><span class="line"> <span class="comment">// 不监听的文件或文件夹,支持正则,默认为空</span></span><br><span class="line"> ignored: <span class="regexp">/node_modules/</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高</span></span><br><span class="line"> <span class="comment">// 默认为300ms </span></span><br><span class="line"> aggregateTimeout: <span class="number">300</span>,</span><br><span class="line"> <span class="comment">// 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,默认每隔1000毫秒询问一次</span></span><br><span class="line"> poll: <span class="number">1000</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>记录一下 <code>webpack</code> 配置对应的意义,便于自己查阅~</p>
</summary>
<category term="前端工程化" scheme="https://kaviilee.github.io/blog/categories/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
<category term="webpack" scheme="https://kaviilee.github.io/blog/tags/webpack/"/>
</entry>
<entry>
<title>一些TS中的运算符</title>
<link href="https://kaviilee.github.io/blog/2021/01/28/%E4%B8%80%E4%BA%9Bts%E4%B8%AD%E7%9A%84%E8%BF%90%E7%AE%97%E7%AC%A6/"/>
<id>https://kaviilee.github.io/blog/2021/01/28/%E4%B8%80%E4%BA%9Bts%E4%B8%AD%E7%9A%84%E8%BF%90%E7%AE%97%E7%AC%A6/</id>
<published>2021-01-28T17:59:30.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>近来用到了不少的 TypeScript 的运算符,但是也有很多是没用过的,本着学到就是赚到的想法,记录一下。</p><a id="more"></a><h3 id="非空断言操作符"><a href="#非空断言操作符" class="headerlink" title="非空断言操作符 !"></a>非空断言操作符 !</h3><p><code>!</code> 是一个后缀表达式操作符,可以用于断言操作对象是非 null 和非 undefined 类型。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> func = <span class="function">(<span class="params">str: <span class="built_in">string</span> | <span class="literal">null</span> | <span class="literal">undefined</span></span>) =></span> {</span><br><span class="line"> <span class="comment">// Type 'string | null | undefined' is not assignable to type 'string'.</span></span><br><span class="line"> <span class="comment">// Type 'undefined' is not assignable to type 'string'.(2322)</span></span><br><span class="line"> <span class="keyword">let</span> onlyStr: <span class="built_in">string</span> = str; <span class="comment">// error</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> ignoredNullOrUndefined: <span class="built_in">string</span> = str!; <span class="comment">// ok</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="运算符"><a href="#运算符" class="headerlink" title="?. 运算符"></a>?. 运算符</h3><p><code>?.</code> 可选链,在我们遇到可能是 null 或 undefined 时就可以停止表达式的运行。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = b?.c</span><br></pre></td></tr></table></figure><p>上述 TypeScript 代码在编译之后</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = b === <span class="literal">null</span> || b === <span class="keyword">void</span> <span class="number">0</span> ? <span class="keyword">void</span> <span class="number">0</span> : b.c;</span><br></pre></td></tr></table></figure><p>所以使用 <code>?.</code> 运算符就可以自动检查对象 b 是否为 null 或 undefined,如果是就返回 undefined。<br>使用 <code>?.</code> 可以代替 <code>&&</code> 执行空检查。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = b && b.c</span><br><span class="line"></span><br><span class="line"><span class="comment">// equal to</span></span><br><span class="line"><span class="keyword">const</span> a = b?.c</span><br></pre></td></tr></table></figure><p>值得注意的是,<code>?.</code> 只检查 null 和 undefined,对于空字串和 0,它是不会检查的。</p><blockquote><p>可选链还可以和函数调用一起使用</p></blockquote><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> res = obj.func?.()</span><br></pre></td></tr></table></figure><p>编译之后</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> res = (_a = obj.func) === <span class="literal">null</span> || _a === <span class="keyword">void</span> <span class="number">0</span> ? <span class="keyword">void</span> <span class="number">0</span> : _a.call(obj);</span><br></pre></td></tr></table></figure><p>如果 obj 不存在 func 这个方法,就回返回 undefined.</p><p>注意事项:</p><ul><li>如果 func 不存在于 obj,上面代码会产生一个 <code>TypeError</code> 异常</li><li>可选链的运算行为被局限在属性的访问,调用和元素的访问,不会延伸到后续的表达式,也就是说可选调用不会阻止 a?.b / someMethod() 表达式中的除法运算或 someMethod 的方法调用</li></ul><h3 id="空值合并运算符"><a href="#空值合并运算符" class="headerlink" title="?? 空值合并运算符"></a>?? 空值合并运算符</h3><blockquote><p>当左侧操作数为 null 或 undefined时,返回右侧的操作数,否则返回左边的操作符</p></blockquote><p>与 || 运算符不同,<code>??</code> 也是只判断 null 和 undefined,其他的 falsy 它是不会返回右侧的。</p><h3 id="可选属性"><a href="#可选属性" class="headerlink" title="?: 可选属性"></a>?: 可选属性</h3><p>这个运算符一般用在定义类型的时候</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> Person {</span><br><span class="line"> age?: <span class="built_in">number</span>;</span><br><span class="line"> <span class="comment">// equal to</span></span><br><span class="line"> <span class="comment">// age: number | undefined</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="Partial"><a href="#Partial" class="headerlink" title="Partial"></a>Partial<T></h4><p>把某个接口的属性变成可选属性</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> Person {</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* type CopyPerson = {</span></span><br><span class="line"><span class="comment"> name?: string;</span></span><br><span class="line"><span class="comment"> age?: number;</span></span><br><span class="line"><span class="comment">} */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CopyPerson = Partial<Person></span><br></pre></td></tr></table></figure><h4 id="Required"><a href="#Required" class="headerlink" title="Required"></a>Required<T></h4><p>使用 <code>Required</code> 可以把可选属性转为必选属性。</p><h3 id="amp-运算符"><a href="#amp-运算符" class="headerlink" title="& 运算符"></a>& 运算符</h3><p>在 TypeScript 中使用 <code>&</code> 运算符可以将多个类型叠加成为一种类型。</p><blockquote><ul><li>同名基础类型合并,如果存在相同的成员,那么该成员的类型会变成 never</li><li>同名非基础类型合并,是可以合并的</li></ul></blockquote><h3 id="分隔符"><a href="#分隔符" class="headerlink" title="| 分隔符"></a>| 分隔符</h3><blockquote><p>在 TypeScript 中联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型.</p></blockquote><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> test: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br></pre></td></tr></table></figure><p>表示 <code>test</code> 可以是 string 或 undefined.</p><h3 id="Record"><a href="#Record" class="headerlink" title="Record"></a>Record</h3><p><code>Record<K extends keyof any, T></code> 将 <code>K</code> 中所有的属性的值转化为 <code>T</code> 类型。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Record<K <span class="keyword">extends</span> keyof <span class="built_in">any</span>, T> = {</span><br><span class="line"> [P <span class="keyword">in</span> K]: T</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// example</span></span><br><span class="line"><span class="keyword">type</span> Route = <span class="string">'home'</span> | <span class="string">'demo'</span> | <span class="string">'help'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> route: Record<Route, <span class="built_in">string</span>> = {</span><br><span class="line"> home: <span class="built_in">string</span>;</span><br><span class="line"> demo: <span class="built_in">string</span>;</span><br><span class="line"> gelp: <span class="built_in">string</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Pick"><a href="#Pick" class="headerlink" title="Pick"></a>Pick</h3><p><code>Pick<T, K extends keyof T></code> 将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Pick<T, K <span class="keyword">extends</span> keyof T> = {</span><br><span class="line"> [P <span class="keyword">in</span> K]: T[P]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// example</span></span><br><span class="line"><span class="keyword">interface</span> Todo {</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"> description: <span class="built_in">string</span>;</span><br><span class="line"> status: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> TodoPreview = Pick<Todo, <span class="string">'title'</span> | <span class="string">'status'</span>>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> todo: TodoPreview = {</span><br><span class="line"> title: <span class="string">'todo1'</span>,</span><br><span class="line"> status: <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="EXclude"><a href="#EXclude" class="headerlink" title="EXclude"></a>EXclude</h3><p><code>Exclude<T, U></code> 将某个类型中的另一个类型移除。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果 T 能够赋值给 U,那么就返回 never 类型,否则返回 T 类型,其实就是将 T 中属于 U 的类型移除。</span></span><br><span class="line"><span class="keyword">type</span> Exclude<T, U> = T <span class="keyword">extends</span> U ? : never : T</span><br><span class="line"></span><br><span class="line"><span class="comment">// example</span></span><br><span class="line"><span class="keyword">type</span> Demo = Exclude<<span class="string">'a'</span> | <span class="string">'b'</span> | <span class="string">'c'</span>, <span class="string">'c'</span>>; <span class="comment">// 'a' | 'b'</span></span><br></pre></td></tr></table></figure><h3 id="ReturnType"><a href="#ReturnType" class="headerlink" title="ReturnType"></a>ReturnType</h3><p><code>ReturnType<T></code> 用于获取函数 <code>T</code> 的返回类型。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> ReturnType<T <span class="keyword">extends</span> (...args: <span class="built_in">any</span>) => <span class="built_in">any</span>> = T <span class="keyword">extends</span> (...args: <span class="built_in">any</span>) => infer R ? R : <span class="built_in">any</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// example</span></span><br><span class="line"><span class="keyword">type</span> Demo = ReturnType<<span class="function"><span class="params">()</span> =></span> <span class="built_in">string</span>>; <span class="comment">// string</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>近来用到了不少的 TypeScript 的运算符,但是也有很多是没用过的,本着学到就是赚到的想法,记录一下。</p>
</summary>
<category term="TypeScript" scheme="https://kaviilee.github.io/blog/tags/typescript/"/>
</entry>
<entry>
<title>浏览器后退事件踩坑</title>
<link href="https://kaviilee.github.io/blog/2021/01/20/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%90%8E%E9%80%80%E4%BA%8B%E4%BB%B6%E8%B8%A9%E5%9D%91/"/>
<id>https://kaviilee.github.io/blog/2021/01/20/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%90%8E%E9%80%80%E4%BA%8B%E4%BB%B6%E8%B8%A9%E5%9D%91/</id>
<published>2021-01-20T10:43:29.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>这是一次关于浏览器后退事件的踩坑记录。</p><a id="more"></a><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>页面需要在打开一个 Drawer 的情况下,用户点击浏览器的后退按钮之后,将 Drawer 关闭,而不是页面发生了后退。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>并不是要阻止浏览器触发后退事件,而是在浏览器触发后退事件前,将预定的 history 信息加入到浏览器的路由信息中,后退时,是后退到了指定的页面,然后在后退事件中加入自己的处理。</p><h3 id="一点点代码"><a href="#一点点代码" class="headerlink" title="一点点代码"></a>一点点代码</h3><p>利用 <code>history.pushState()</code> 方法在浏览器的历史堆栈中添加一个 state。<br>然后再监听 <code>popstate</code> 事件,加入自己的处理。</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> handleOnpopState = <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// your code</span></span><br><span class="line"> Drawer.close()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">window</span>.history.pushState(<span class="literal">null</span>, <span class="string">''</span>, <span class="built_in">document</span>.URL);</span><br><span class="line"></span><br><span class="line"><span class="comment">// mounted</span></span><br><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">'popstate'</span>, handleOnpopState, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// unmount</span></span><br><span class="line"><span class="built_in">window</span>.removeEventListener(<span class="string">'popstate'</span>, handleOnpopState, <span class="literal">false</span>)</span><br></pre></td></tr></table></figure><h3 id="注意点"><a href="#注意点" class="headerlink" title="注意点"></a>注意点</h3><p>当网页加载时,各浏览器对popstate事件是否触发有不同的表现,Chrome 和 Safari会触发popstate事件, 而Firefox不会.</p>]]></content>
<summary type="html">
<p>这是一次关于浏览器后退事件的踩坑记录。</p>
</summary>
</entry>
<entry>
<title>关于Antd框架下Cascader动态加载选项的问题</title>
<link href="https://kaviilee.github.io/blog/2021/01/13/%E5%85%B3%E4%BA%8Eantd%E6%A1%86%E6%9E%B6%E4%B8%8Bcascader%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E9%80%89%E9%A1%B9%E7%9A%84%E9%97%AE%E9%A2%98/"/>
<id>https://kaviilee.github.io/blog/2021/01/13/%E5%85%B3%E4%BA%8Eantd%E6%A1%86%E6%9E%B6%E4%B8%8Bcascader%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E9%80%89%E9%A1%B9%E7%9A%84%E9%97%AE%E9%A2%98/</id>
<published>2021-01-13T18:07:43.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>近日在使用 Ant Design 的 Cascader 初始化传值遇到的问题,记录一下。</p><a id="more"></a><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>在使用 Cascader 组件的动态加载数据时,由于需要传入初始值来初始化当前的选择,但是此时组件内并不存在对应的数据。<br>假设我们使用一个 <code>Number</code> 类型的字段来作为 Cascader 的 value 值,那么如果贸然传入数字类型的数组,就会出现初始化不正常的问题。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>既然不能简单的传入数字类型的数组来初始化组件,那么我们可以传入对象数组或者是字符串数组,利用 Cascader 的 displayRender 属性来渲染。</p><p>并利用 <code>fetched</code> 来控制是否已经进行异步加载,当进行异步加载之后,渲染就变为选择的值。</p><p>具体可以查看 <a href="https://codesandbox.io/s/dongtaijiazaixuanxiang-antd4102-forked-whwnc" target="_blank" rel="noopener">动态加载选项初始化组件</a></p>]]></content>
<summary type="html">
<p>近日在使用 Ant Design 的 Cascader 初始化传值遇到的问题,记录一下。</p>
</summary>
<category term="Antd v4" scheme="https://kaviilee.github.io/blog/tags/antd-v4/"/>
</entry>
<entry>
<title>浅尝 deno</title>
<link href="https://kaviilee.github.io/blog/2021/01/05/%E6%B5%85%E5%B0%9Ddeno/"/>
<id>https://kaviilee.github.io/blog/2021/01/05/%E6%B5%85%E5%B0%9Ddeno/</id>
<published>2021-01-05T00:01:36.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>新的一年新的开始!新的东西,学起!(扶朕起来,朕还可以再学</p><a id="more"></a><blockquote><p>它是 Node.js 的替代品。有了它,将来可能就不需要 Node.js 了。 ———— 阮一峰《Deno 运行时入门教程:Node.js 的替代品》</p></blockquote><h2 id="Deno-简介"><a href="#Deno-简介" class="headerlink" title="Deno 简介"></a>Deno 简介</h2><p>Deno 是一个跨平台的运行时,即基于 Google V8 引擎的运行时环境,该运行时环境是使用 Rust 语言开发的,并使用 Tokio 库来构建事件循环系统。Deno 有以下优点:</p><ul><li>默认安全。除非显式启用,否则不能访问文件,网络或环境</li><li>开箱即用的 TypeScript 支持</li><li>只分发一个可执行文件</li><li>具有内置的工具箱,比如依赖项检查工具(deno info)和 代码格式化工具(deno fmt)</li><li>拥有一组保证能够与 Deno 一起使用的经过审查的标准模块:<code>deno.land/std</code></li><li>脚本可以打包到一个 JavaScript 文件中</li></ul><h3 id="一些命令"><a href="#一些命令" class="headerlink" title="一些命令"></a>一些命令</h3><p>执行 <code>deno -h</code> 或 <code>deno --help</code> 就能够显示 Deno 支持的子命令,以下列举一些主要使用的命令</p><blockquote><ul><li>deno bundle:deno bundle [options] <source_file> [out_file] 将脚本和依赖打包</li><li>deno fmt:代码格式化</li><li>deno info:显示本地的依赖缓存</li><li>deno install:将脚本安装为可执行文件</li><li>deno repl:进入 REPL 环境</li><li>deno run:运行脚本</li></ul></blockquote><h2 id="与-Node-js-的区别"><a href="#与-Node-js-的区别" class="headerlink" title="与 Node.js 的区别"></a>与 Node.js 的区别</h2><p>Deno 既然是 Node.js 的 ”替代品“,究竟解决了 Node.js 的哪些痛点呢?</p><h3 id="模块引入的方式"><a href="#模块引入的方式" class="headerlink" title="模块引入的方式"></a>模块引入的方式</h3><h4 id="Node-模块引入"><a href="#Node-模块引入" class="headerlink" title="Node 模块引入"></a>Node 模块引入</h4><p>Node 内置 API 通过模块引入的方式引入。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line">fs.readFileSync(<span class="string">'demo.txt'</span>)</span><br></pre></td></tr></table></figure><h4 id="Deno-全局对象"><a href="#Deno-全局对象" class="headerlink" title="Deno 全局对象"></a>Deno 全局对象</h4><p>在 Deno 中则是一个全局对象 <code>Deno</code> 的属性和方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">Deno.readFileSync(<span class="string">'demo.txt'</span>)</span><br></pre></td></tr></table></figure><h3 id="模块系统"><a href="#模块系统" class="headerlink" title="模块系统"></a>模块系统</h3><p>在模块系统方面,这是 Deno 和 Node.js 差别最大的地方。</p><h4 id="Node-js-CommonJS-规范"><a href="#Node-js-CommonJS-规范" class="headerlink" title="Node.js CommonJS 规范"></a>Node.js CommonJS 规范</h4><p>Node.js 采用的是 <code>CommonJS</code> 模块规范。具体规范见 <a href="https://javascript.ruanyifeng.com/nodejs/module.html#" target="_blank" rel="noopener">CommonJS 规范</a></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'demo'</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="Deno-的模块规范"><a href="#Deno-的模块规范" class="headerlink" title="Deno 的模块规范"></a>Deno 的模块规范</h4><p>不同于 Node.js 的规范,Deno 所采用的模块规范是 <a href="https://es6.ruanyifeng.com/#docs/module" target="_blank" rel="noopener">ES Module 规范</a></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="keyword">as</span> fs <span class="keyword">from</span> <span class="string">"https//deno.land/std/fs/mod.ts"</span></span><br><span class="line"><span class="keyword">import</span> { Application } <span class="keyword">from</span> <span class="string">"./initApp.js"</span></span><br><span class="line"><span class="keyword">import</span> creatApp <span class="keyword">from</span> <span class="string">"./creatApp.ts"</span></span><br></pre></td></tr></table></figure><p>在 Deno 中,可以直接 import url 来引用线上的资源,并且资源的扩展名和文件名不可以省略。</p><h3 id="安全"><a href="#安全" class="headerlink" title="安全"></a>安全</h3><p>这个安全主要体现在 Deno 的操作很多都需要提供权限,比如:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// demo.ts</span></span><br><span class="line"><span class="keyword">const</span> file = <span class="keyword">await</span> Deno.creat(<span class="string">"./bar.txt"</span>)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`create file: bar.txt`</span>)</span><br></pre></td></tr></table></figure><p>我们执行这段代码</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ deno run .\demo.ts</span><br><span class="line">Check file:///C:/Users/lee/Desktop/demo.ts</span><br><span class="line">error: Uncaught (<span class="keyword">in</span> promise) PermissionDenied: <span class="built_in">read</span> access to <span class="string">"bar.txt"</span>, run again with the --allow-read flag</span><br><span class="line"> at processResponse (deno:core/core.js:223:11)</span><br><span class="line"> at Object.jsonOpAsync (deno:core/core.js:240:12)</span><br><span class="line"> at async open (deno:runtime/js/30_files.js:44:17)</span><br><span class="line"> at async file:///C:/Users/lee/Desktop/demo.ts:1:14</span><br></pre></td></tr></table></figure><p>这会告诉我们没有给予它 <code>--allow-read</code> 的权限,当我们给予它权限后,就能成功创建文件</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ deno run --allow-read --allow-write .\demo.ts</span><br><span class="line">create file: bar.txt</span><br></pre></td></tr></table></figure><h4 id="Deno-的权限列表"><a href="#Deno-的权限列表" class="headerlink" title="Deno 的权限列表"></a>Deno 的权限列表</h4><ul><li><code>-A, --allow-all</code> 允许所有权限,这将禁用所有安全限制。</li><li><code>--allow-env</code> 允许环境访问,例如读取和设置环境变量。</li><li><code>--allow-hrtime</code> 允许高精度时间测量,高精度时间能够在计时攻击和特征识别中使用。</li><li><code>--allow-net=<allow-net></code> 允许网络访问。您可以指定一系列用逗号分隔的域名,来提供域名白名单。</li><li><code>--allow-plugin</code> 允许加载插件。请注意:这是一个不稳定功能。</li><li><code>--allow-read=<allow-read></code> 允许读取文件系统。您可以指定一系列用逗号分隔的目录或文件,来提供文件系统白名单。</li><li><code>--allow-run</code> 允许运行子进程。请注意,子进程不在沙箱中运行,因此没有与 deno 进程相同的安全限制,请谨慎使用。</li><li><code>--allow-write=<allow-write></code> 允许写入文件系统。您可以指定一系列用逗号分隔的目录或文件,来提供文件系统白名单。</li></ul><p>这里插一句不属于权限但是属于 <code>run</code> 的时候可以传入的选项 <code>--unstable</code>,传递这个选项将允许在运行时使用不稳定的 API。</p><h4 id="白名单"><a href="#白名单" class="headerlink" title="白名单"></a>白名单</h4><p>Deno 还可以设置白名单来控制权限的粒度。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">deno run --allow-read=/etc https://deno.land/std@<span class="variable">$STD_VERSION</span>/examples/main.ts /etc/passwd</span><br></pre></td></tr></table></figure><p>可以使得文件系统能够访问 <code>/etc</code> 目录。</p><h3 id="支持-TypeScript"><a href="#支持-TypeScript" class="headerlink" title="支持 TypeScript"></a>支持 TypeScript</h3><p>Deno 原生支持 TS,这点对于一个 TS 使用者来说实在是太赞了。Node.js 中要使用 TypeScript 的话还需要 <code>ts-node</code> 之类的工具的支持。</p><h3 id="无-node-modules"><a href="#无-node-modules" class="headerlink" title="无 node_modules"></a>无 node_modules</h3><p>不同于 Node.js,Deno 并没有 node_modules 来进行包管理。但是 Deno 在每次初执行时,会进行依赖的下载和编译,我们可以通过 <code>deno info</code> 来查看</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ deno info [file_name]</span><br><span class="line">DENO_DIR location: <span class="string">"C:\\Users\\lee\\AppData\\Local\\deno"</span></span><br><span class="line">Remote modules cache: <span class="string">"C:\\Users\\lee\\AppData\\Local\\deno\\deps"</span></span><br><span class="line">TypeScript compiler cache: <span class="string">"C:\\Users\\lee\\AppData\\Local\\deno\\gen"</span></span><br></pre></td></tr></table></figure><p>其实这就相当于是进行了包管理,编译后的文件,远程模块缓存都会放在相应的位置。</p><h3 id="异步操作"><a href="#异步操作" class="headerlink" title="异步操作"></a>异步操作</h3><p>Node 用回调的方式处理异步操作,而 Deno 则使用的是 Promise</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// node</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line">fs.readFile(<span class="string">'./demo.txt'</span>, (err, data) => {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span>(err)</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(data)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// deno</span></span><br><span class="line"><span class="keyword">const</span> data = <span class="keyword">await</span> Deno.readFile(<span class="string">'./demo.txt'</span>)</span><br><span class="line"><span class="built_in">console</span>.log(data)</span><br></pre></td></tr></table></figure><h2 id="这是实战"><a href="#这是实战" class="headerlink" title="这是实战"></a>这是实战</h2><blockquote><p>项目采用的框架是 <code>oak</code>,应该是对应了 Node.js 的 <code>Koa</code>,如果之前使用过 <code>Koa</code> 的话,使用 <code>oak</code> 这个框架应该会很熟悉。</p></blockquote><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>因为笔者是用的 VSCode 在开发,所以在开始写 Deno 项目前我们先做好相关设置。</p><p>在 <code>.vscode/settings.json</code> 中写入设置</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"deno.enable"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"deno.import_intellisense_origins"</span>: {</span><br><span class="line"> <span class="attr">"https://deno.land"</span>: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"deno.lint"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"[typescript]"</span>: {</span><br><span class="line"> <span class="attr">"editor.defaultFormatter"</span>: <span class="string">"denoland.vscode-deno"</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"[typescriptreact]"</span>: {</span><br><span class="line"> <span class="attr">"editor.defaultFormatter"</span>: <span class="string">"denoland.vscode-deno"</span>,</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使得编辑器在写 deno 时能够进行代码提示。</p><p>虽然 Deno 说自己没有像 <a href="https://npmjs.com" target="_blank" rel="noopener">npmjs</a> 那样的包管理中心,但其实他还是有属于自己的 <a href="https://deno.land/[email protected]" target="_blank" rel="noopener">Deno标准库</a>,目前版本是 <code>0.83.0</code>。<br>当我们要使用一些标准模块,比如 <code>path</code>,我们就要去引入标准库。</p><h3 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">└── deno-demo</span><br><span class="line"> ├── configs.ts <span class="comment"># 配置信息文件</span></span><br><span class="line"> ├── db <span class="comment"># 数据库目录</span></span><br><span class="line"> ├── controllers <span class="comment"># 控制器目录</span></span><br><span class="line"> ├── app.ts <span class="comment"># 入口文件</span></span><br><span class="line"> ├── middlewares <span class="comment"># 中间件目录</span></span><br><span class="line"> ├── models <span class="comment"># 模型定义目录</span></span><br><span class="line"> ├── routes.ts <span class="comment"># 路由文件</span></span><br><span class="line"> └── services <span class="comment"># 服务层程序目录</span></span><br></pre></td></tr></table></figure><h3 id="入口文件"><a href="#入口文件" class="headerlink" title="入口文件"></a>入口文件</h3><p><strong>app.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { Application } <span class="keyword">from</span> <span class="string">"https://deno.land/x/oak/mod.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> { HOST, PORT } <span class="keyword">from</span> <span class="string">"./configs.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">"./routes.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> notFound <span class="keyword">from</span> <span class="string">"./controllers/notFound.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> errorMiddleware <span class="keyword">from</span> <span class="string">"./middlewares/error.ts"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Application();</span><br><span class="line"></span><br><span class="line">app.use(errorMiddleware);</span><br><span class="line">app.use(router.routes());</span><br><span class="line">app.use(router.allowedMethods());</span><br><span class="line">app.use(notFound);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`Listening on <span class="subst">${PORT}</span>...`</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> app.listen(<span class="string">`<span class="subst">${HOST}</span>:<span class="subst">${PORT}</span>`</span>);</span><br><span class="line">} <span class="keyword">catch</span>(e) {</span><br><span class="line"> <span class="built_in">console</span>.log(e)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从以上的代码来看,整个流程跟 Node.js 使用 Koa 开发几乎一样。</p><h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><p><strong>configs.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> env = Deno.env.toObject();</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> HOST = env.HOST || <span class="string">"127.0.0.1"</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> PORT = env.PORT || <span class="number">3000</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> DB = env.DB || <span class="string">"./db/todos.json"</span>;</span><br></pre></td></tr></table></figure><p>在配置文件中配置 <code>HOST</code>、<code>PORT</code>、<code>DB</code> 等参数。</p><h3 id="路由配置"><a href="#路由配置" class="headerlink" title="路由配置"></a>路由配置</h3><p><strong>routes.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { Router } <span class="keyword">from</span> <span class="string">"https://deno.land/x/oak/mod.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> getTodos <span class="keyword">from</span> <span class="string">"./controllers/getTodos.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> createTodo <span class="keyword">from</span> <span class="string">"./controllers/createTodo.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> updateTodo <span class="keyword">from</span> <span class="string">"./controllers/updateTodo.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> deleteTodo <span class="keyword">from</span> <span class="string">"./controllers/deleteTodo.ts"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = <span class="keyword">new</span> Router();</span><br><span class="line"></span><br><span class="line">router</span><br><span class="line"> .get(<span class="string">"/todos"</span>, getTodos)</span><br><span class="line"> .post(<span class="string">"/todos"</span>, createTodo)</span><br><span class="line"> .put(<span class="string">"/todos/:id"</span>, updateTodo)</span><br><span class="line"> .delete(<span class="string">"/todos/:id"</span>, deleteTodo);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router;</span><br></pre></td></tr></table></figure><p>路由文件也和使用 <code>Koa-router</code> 时的代码差不多。</p><h3 id="定义模型(model)"><a href="#定义模型(model)" class="headerlink" title="定义模型(model)"></a>定义模型(model)</h3><p>定义模型在本项目中体现为定义了一个接口</p><p><strong>model/todo.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Todo {</span><br><span class="line"> id: <span class="built_in">string</span>;</span><br><span class="line"> userId: <span class="built_in">number</span>;</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"> status: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="数据操作"><a href="#数据操作" class="headerlink" title="数据操作"></a>数据操作</h3><p><strong>service/db.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { DB } <span class="keyword">from</span> <span class="string">"../configs.ts"</span>;</span><br><span class="line"><span class="keyword">import</span> { Todo } <span class="keyword">from</span> <span class="string">"../models/todo.ts"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 Todo 数据</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> fetchTodo = <span class="keyword">async</span> (): <span class="built_in">Promise</span><Todo[]> => {</span><br><span class="line"> <span class="keyword">const</span> data = <span class="keyword">await</span> Deno.readFile(DB);</span><br><span class="line"> <span class="keyword">const</span> decoder = <span class="keyword">new</span> TextDecoder();</span><br><span class="line"> <span class="keyword">const</span> decodedData = decoder.decode(data);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">JSON</span>.parse(decodedData);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入 Todo 数据</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> persistTodo = <span class="keyword">async</span> (data: Todo[]): <span class="built_in">Promise</span><<span class="built_in">void</span>> => {</span><br><span class="line"> <span class="keyword">const</span> encoder = <span class="keyword">new</span> TextEncoder();</span><br><span class="line"> <span class="keyword">await</span> Deno.writeFile(DB, encoder.encode(<span class="built_in">JSON</span>.stringify(data)));</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="数据的增删改查"><a href="#数据的增删改查" class="headerlink" title="数据的增删改查"></a>数据的增删改查</h3><p>详情见项目的 <code>controllers</code> 下的文件</p><h3 id="Not-Found"><a href="#Not-Found" class="headerlink" title="Not Found"></a>Not Found</h3><p>当 api 不能匹配到对应的路由时,返回 <code>Not Found</code> 信息</p><p><strong>controllers/notFound.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { Response } <span class="keyword">from</span> <span class="string">"https://deno.land/x/oak/mod.ts"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> ({ response }: { response: Response }) => {</span><br><span class="line"> response.status = <span class="number">404</span>;</span><br><span class="line"> response.body = { message: <span class="string">"Not Found"</span> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="中间件-Middlewares"><a href="#中间件-Middlewares" class="headerlink" title="中间件 Middlewares"></a>中间件 <code>Middlewares</code></h3><p>我们定义了一个中间件 <code>error.ts</code> 来处理读不到数据的错误</p><p><strong>moddlewares/error.ts</strong></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { Response } <span class="keyword">from</span> <span class="string">"https://deno.land/x/oak/mod.ts"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">async</span> (</span><br><span class="line"> { response }: { response: Response },</span><br><span class="line"> next: <span class="function"><span class="params">()</span> =></span> <span class="built_in">Promise</span><<span class="built_in">void</span>>,</span><br><span class="line">) => {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> next();</span><br><span class="line"> } <span class="keyword">catch</span> (err) {</span><br><span class="line"> response.status = <span class="number">500</span>;</span><br><span class="line"> response.body = { message: err.message };</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>在进行以上代码的写入后,我们就可以进行 <code>run</code> 整个项目了</p><p>注意 <code>run</code> 的时候要带上 <code>-A</code> 或 <code>--allow-all</code>,意思是给予全部权限给当前项目。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">deno run -A ./app.ts</span><br><span class="line"></span><br><span class="line">Check file:///D:/DenoDemo/app.ts</span><br><span class="line">Listening on 3000...</span><br></pre></td></tr></table></figure><p>ok!整个项目就跑起来啦。这次 deno 的初体验也就完成了。完整代码的地址是 <a href="https://github.com/Kaviilee/deno-demo" target="_blank" rel="noopener">deno-demo</a>。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li>不能开代理。否则无法运行</li></ol>]]></content>
<summary type="html">
<p>新的一年新的开始!新的东西,学起!(扶朕起来,朕还可以再学</p>
</summary>
</entry>
<entry>
<title>记一次文字拷贝</title>
<link href="https://kaviilee.github.io/blog/2020/12/29/%E8%AE%B0%E4%B8%80%E6%AC%A1%E6%96%87%E5%AD%97%E6%8B%B7%E8%B4%9D/"/>
<id>https://kaviilee.github.io/blog/2020/12/29/%E8%AE%B0%E4%B8%80%E6%AC%A1%E6%96%87%E5%AD%97%E6%8B%B7%E8%B4%9D/</id>
<published>2020-12-29T14:54:50.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p><code>Clipboard API</code>,“船新”的浏览器与系统剪贴板交互的异步 API。</p><a id="more"></a><h2 id="小小的碎碎念"><a href="#小小的碎碎念" class="headerlink" title="小小的碎碎念"></a>小小的碎碎念</h2><p>恰巧前段时间要做 <code>复制到剪贴板</code> 这个功能,就去查了一下实现方式,大致都是 <code>document.execCommand</code> 来实现。使用的时候没多注意这个 API 已经废弃,之后发现了,觉得既然废弃就应该有替代的。于是再继续查了查,原来确实是有替代,就是今天的主角 <code>Clipboard API</code>。</p><p>接下来就是通过比对 <code>document.execCommand</code> 与 <code>Clipboard API</code> 的使用方法来介绍这两种方式的区别,来认识 <code>Clipboard API</code>。</p><h2 id="写入系统剪贴板"><a href="#写入系统剪贴板" class="headerlink" title="写入系统剪贴板"></a>写入系统剪贴板</h2><p>有两种方式向系统剪贴板写入数据。<code>document.execCommand</code> 触发 <code>cut</code> 和 <code>copy</code> 来写入;另一种方式就是使用 <code>Clipboard API</code> 的 <code>writeText()</code> 或 <code>write()</code> 写入数据。 </p><h3 id="使用-document-execCommand"><a href="#使用-document-execCommand" class="headerlink" title="使用 document.execCommand"></a>使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand" target="_blank" rel="noopener">document.execCommand</a></h3><blockquote><p>bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)</p><p>返回值为布尔值,如果是 <code>false</code> 则表示操作不被支持或未被启用。</p><p>document.execCommand 是一个非常强大的 API,但我们这里只用到了它的 <code>cut</code>,<code>copy</code>,<code>paste</code></p></blockquote><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">const copy = () => {</span><br><span class="line"> const copyEle = document.querySelector('#copy') as HTMLInputElement</span><br><span class="line"> copyEle.select()</span><br><span class="line"> const success = document.execCommand('copy')</span><br><span class="line"> if (success) {</span><br><span class="line"> alert('已将文本复制到剪贴板')</span><br><span class="line"> } else {</span><br><span class="line"> alert('复制失败')</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><input value="这是一个copy测试" id="copy" /></span><br><span class="line"><button onClick={copy}>点击复制</button></span><br></pre></td></tr></table></figure><h3 id="使用-Clipboard-API"><a href="#使用-Clipboard-API" class="headerlink" title="使用 Clipboard API"></a>使用 <a href="https://www.w3.org/TR/clipboard-apis" target="_blank" rel="noopener">Clipboard API</a></h3><blockquote><p>相较 <strong>document.execCommand</strong>,<strong>Clipboard API</strong> 更加灵活,它不仅能够将当前选择复制到剪贴板,还能够直接指定要放入剪贴板的信息。</p><p>不同于 document.execCommand,Clipboard API 需要申请 <code>clipboardRead</code> 与 <code>clipboardWrite</code> 权限。</p></blockquote><p>我们可以通过 <code>navigator.permissions.query()</code> 来查询权限:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">navigator.permissions.query({ <span class="attr">name</span>: <span class="string">'clipboard-write'</span> }).then(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (res.state === <span class="string">'granted'</span> || res.state === <span class="string">'prompt'</span>) {</span><br><span class="line"> <span class="comment">// write your code</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>更新剪贴板使用方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> updateClipboard = <span class="function">(<span class="params">newText</span>) =></span> {</span><br><span class="line"> navigator.clipboard.writeText(newText).then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// successfully set</span></span><br><span class="line"> }, () => {</span><br><span class="line"> <span class="comment">// failed</span></span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="不同浏览器的差异"><a href="#不同浏览器的差异" class="headerlink" title="不同浏览器的差异"></a>不同浏览器的差异</h4><ul><li><strong>Chrome</strong><ul><li>可以在所有执行上下中写入系统剪贴板</li><li>不需要权限</li></ul></li><li><strong>Firefox</strong><ul><li>version 51 以上才开始支持 “clipboardWrite” 权限</li></ul></li></ul><h4 id="具体的兼容性"><a href="#具体的兼容性" class="headerlink" title="具体的兼容性"></a>具体的兼容性</h4><ul><li><p>write</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/28/QhbqOFXy3TCeWcI.png" alt="caniuse-write"></p></li><li><p>writeText</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/28/x8OaLTZ3A6BuhN9.png" alt="caniuse-writeText"></p></li></ul><h2 id="读取系统剪贴板"><a href="#读取系统剪贴板" class="headerlink" title="读取系统剪贴板"></a>读取系统剪贴板</h2><p><code>document.execCommand</code> 提供了 <code>paste</code> 指令,让用户能够粘贴内容。也可以使用 <code>Clipboard API</code> 的 <code>read()</code> 和 <code>readText()</code></p><h3 id="使用-document-execCommand-1"><a href="#使用-document-execCommand-1" class="headerlink" title="使用 document.execCommand"></a>使用 <code>document.execCommand</code></h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">const paste = () => {</span><br><span class="line"> const pasteText = document.querySelector('#output') as HTMLInputElement</span><br><span class="line"> pasteText.focus()</span><br><span class="line"> const success = document.execCommand('paste')</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><input id="output" type="text"/></span><br><span class="line"><button onClick={paste}>粘贴</button></span><br></pre></td></tr></table></figure><h3 id="使用-Clipboard-API-1"><a href="#使用-Clipboard-API-1" class="headerlink" title="使用 Clipboard API"></a>使用 <code>Clipboard API</code></h3><p>在获得了 “clipboard-read” 权限之后,剪贴板的数据读取</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">navigator.clipboard.readText().then(<span class="function"><span class="params">text</span> =></span> {</span><br><span class="line"> <span class="built_in">document</span>.querySelector(<span class="string">'#output'</span>).innerText = text</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>这个代码片段从剪贴板提取文本并且用该文本替换 ID 为 <code>"outbox"</code> 的元素的当前内容。</p><h3 id="具体的兼容性-1"><a href="#具体的兼容性-1" class="headerlink" title="具体的兼容性"></a>具体的兼容性</h3><ul><li><p>read</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/28/IGuecb8V15aZyzo.png" alt="caniuse-read"></p></li><li><p>readText</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/28/5ftaJYc3DjElWIm.png" alt="caniuse-readText"></p></li></ul><h2 id="Clipboard-Event"><a href="#Clipboard-Event" class="headerlink" title="Clipboard Event"></a>Clipboard Event</h2><p>Clipboard API 还可以让我们响应 <code>复制</code>,<code>剪切</code>,<code>粘贴</code> 等事件。只需要给元素增加对 <code>copy</code>,<code>cut</code> 和 <code>paste</code> 事件的监听,就能够拦截到事件之后进行自定义的处理。</p><p>Clipboard API 中定义了一个 <code>ClipboardEvent</code>,而这个 <code>ClipboardEvent</code> 有一个属性 <code>ClipboardData</code> (目前还是一个实验性的属性),顾名思义这是剪贴板数据。</p><p><code>ClipboardData</code> 属性是一个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer" target="_blank" rel="noopener">DataTransfer</a> 对象,有两个方法:</p><ul><li><code>setData(format, data)</code>:当用户进行复制和剪贴时,可以通过这个方法来写入。参数 <code>format</code>: DOMString 表示数据的类型,参数 <code>data</code>: DOMString 表示数据。</li><li><code>getData(format)</code>:可以在用户进行粘贴时处理数据。</li></ul><h3 id="拦截-copy,cut-事件"><a href="#拦截-copy,cut-事件" class="headerlink" title="拦截 copy,cut 事件"></a>拦截 <code>copy</code>,<code>cut</code> 事件</h3><p>通过拦截用户的 <code>copy</code> 和 <code>cut</code> 事件,可以做到更改用户的剪贴板数据</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> handleCopy = <span class="function"><span class="params">e</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> selection = <span class="built_in">document</span>.getSelection()</span><br><span class="line"> e.clipboardData?.setData(<span class="string">'text/plain'</span>, <span class="string">`你的剪贴板数据被我更改了哦~~<span class="subst">${selection}</span>`</span>)</span><br><span class="line"> e.preventDefault()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.querySelector(<span class="string">'#copy'</span>).addEventListener(<span class="string">'copy'</span>, proxyClipboardCopy)</span><br></pre></td></tr></table></figure><p>利用这个方法,我们可以做到在用户复制特定元素的内容时,在复制内容中加入你想加入的信息,比如: <strong>版权信息</strong>。</p><h3 id="拦截-paste-事件"><a href="#拦截-paste-事件" class="headerlink" title="拦截 paste 事件"></a>拦截 <code>paste</code> 事件</h3><p>利用拦截用户的 <code>paste</code> 事件,我们可以做到更多的事情,比如富文本编辑中的粘贴图片之后自动上传或者进行 base64 转换之类的操作。</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><textarea id="input" placeholder="请开始你的表演" /></span><br><span class="line"></span><br><span class="line">const handlePaste = (e) => {</span><br><span class="line"> const input = document.querySelector('#input')</span><br><span class="line"> input.addEventListener('paste', () => {</span><br><span class="line"> const text = e.clipboardData.getData('text/plain')</span><br><span class="line"> console.log(text)</span><br><span class="line"> e.preventDefault()</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>说一个小知识 <code>document.execCommand</code> 这个 API 本身也不是标准 API,这是 IE 的私有 API,只是被其他浏览器做了兼容支持。而这个 API 被废弃的原因主要是安全问题,它不需要任何权限就能执行一些敏感操作,还有就是这是一个同步方法,而且是操作 DOM 对象的,会阻塞页面渲染和脚本执行。</p><p>可能就是要解决以上的两个问题,<code>Clipboard API</code> 被设计成了需要相对应的权限才能使用的异步方法。</p><p>目前各个浏览器对 <code>Clipboard API</code> 的支持不相同,尤其是在能够复制更复杂内容的 <code>write</code> 和 <code>read</code> 上,Chrome 实现了而 Firefox 却还没有实现。而且具体的规范和标准也还处在工作草案阶段。相较于已经被废弃的 <code>execCommand</code> ,<code>Clipboard API</code> 之后会发展成啥样也是蛮值得期待的。</p>]]></content>
<summary type="html">
<p><code>Clipboard API</code>,“船新”的浏览器与系统剪贴板交互的异步 API。</p>
</summary>
<category term="JavaScript" scheme="https://kaviilee.github.io/blog/categories/javascript/"/>
</entry>
<entry>
<title>linear-gradient笔记</title>
<link href="https://kaviilee.github.io/blog/2020/12/21/linear-gradient%E7%AC%94%E8%AE%B0/"/>
<id>https://kaviilee.github.io/blog/2020/12/21/linear-gradient%E7%AC%94%E8%AE%B0/</id>
<published>2020-12-21T15:15:25.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>之前用 <code>background-image</code> 的 <code>linear-gradient</code> 画了下划线,觉得很神奇,就去认真学习了一下。做了些小笔记,备忘。</p><a id="more"></a><h2 id="渐变线的构成"><a href="#渐变线的构成" class="headerlink" title="渐变线的构成"></a>渐变线的构成</h2><blockquote><p>CSS linear-gradient() 函数用于创建一个表示两种或多种颜色线性渐变的图片。其结果属于 <code><gradient></code> 数据类型,是一种特别的 <code><image></code> 数据类型。</p></blockquote><p>以上是 mdn 关于 <code>linear-gradient</code> 的定义。语法如下</p><figure class="highlight"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">linear-gradient</span>(</span><br><span class="line"> <span class="selector-attr">[ <angle> | to <side-or-corner> ,]</span>? <<span class="selector-tag">color-stop-list</span>> )</span><br><span class="line"> \---------------------------------/ \----------------------------/</span><br><span class="line"> <span class="selector-tag">Definition</span> <span class="selector-tag">of</span> <span class="selector-tag">the</span> <span class="selector-tag">gradient</span> <span class="selector-tag">line</span> <span class="selector-tag">List</span> <span class="selector-tag">of</span> <span class="selector-tag">color</span> <span class="selector-tag">stops</span></span><br><span class="line"></span><br><span class="line">where <side-or-corner> = [ left | right ] || [ top | bottom ]</span><br><span class="line"> and <color-stop-list> = [ <linear-color-stop> [, <color-hint>? ]? ]#, <linear-color-stop></span><br><span class="line"> and <linear-color-stop> = <color> [ <color-stop-length> ]?</span><br><span class="line"> and <color-stop-length> = [ <percentage> | <length> ]{1,2}</span><br><span class="line"> and <color-hint> = [ <percentage> | <length> ] // 颜色中转点</span><br></pre></td></tr></table></figure><p>这个函数表示接受<strong>第一个参数</strong>为是渐变的角度,它可以是单位为 <code>deg</code>,<code>rad</code>,<code>grad</code> 或 <code>turn</code> 的具体的角度值(该角度是顺时针增加的);还可以是 <code>to</code> 和表示方向的关键词(<code>top</code>,<code>right</code>,<code>bottom</code>,<code>left</code> 这些值会被转换成 0°,90°,180°,270°,其余值会被转换成一个以顶部中央方向为起点顺时针旋转的角度),关键词先后顺序无影响,都是可选的。<strong>第二个参数</strong>是一系列的颜色节点,由一个 <code><color></code> 值组成,并且伴随一个可选的终点位置,这个终点位置可以是一个百分比值或者是沿着渐变轴的 <code><length></code>。</p><h3 id="渐变容器"><a href="#渐变容器" class="headerlink" title="渐变容器"></a>渐变容器</h3><p>渐变图像是没有内在尺寸的,是无限大的。那么 <code>linear-gradient</code> 函数的具体尺寸由渐变容器的大小决定。</p><p>一般来说,如果给一个元素的 <code>background-image</code> 使用 <code>linear-gradient</code>,那么渐变的区域就是该元素的区域。而如果该元素设置了 <code>background-size</code> 渐变容器就会变成设置的 <code>background-size</code> 大小。</p><h3 id="渐变线"><a href="#渐变线" class="headerlink" title="渐变线"></a>渐变线</h3><blockquote><p>渐变线(Gradient line)由包含渐变图形的容器的中心点和一个角度来定义的。</p></blockquote><p>这里借用一下 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/linear-gradient()" target="_blank" rel="noopener">mdn</a> 的图片</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://developer.mozilla.org/files/3537/linear-gradient.png" alt="linear-gradient"></p><h3 id="渐变角度"><a href="#渐变角度" class="headerlink" title="渐变角度"></a>渐变角度</h3><p><code>linear-gradient</code> 是通过渐变角度来控制渐变的方向的。</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/21/7GzHpLmk1Q6w5s9.png" alt="渐变角度"></p><p>如上图所示,红线就是渐变角度。上文中有说到,定义这个角度有两种方法:</p><ul><li>使用方向关键词:<code>to <top | right | bottom | left | top right | top left | bottom right | bottom left></code></li><li>使用具体的角度值:比如 <code>45deg</code></li></ul><p>如果缺省角度值,默认值就是 <code>to bottom (180deg)</code> </p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/21/ug8wz2qYCJiPmbS.png" alt="default"></p><p>这里方向关键词有其对应的具体角度值:</p><p><code>top(0deg)</code>,<code>right(90deg)</code>,<code>bottom(180deg)</code>,<code>left(270deg)</code>。</p><p>而如果是是使用 <code>to top left</code> 这种复合的顶角关键词,就没有对应的固定角度。因为它依赖的是渐变容器的尺寸,如果容器刚好是一个正方形,那么 <code>to top right</code> 和 <code>45deg</code> 的效果是一样。</p><h3 id="渐变色节点-linear-color-stop"><a href="#渐变色节点-linear-color-stop" class="headerlink" title="渐变色节点 (linear color stop)"></a>渐变色节点 (linear color stop)</h3><p>渐变色节点可以这样定义:</p><figure class="highlight"><table><tr><td class="code"><pre><span class="line"><linear-color-stop> = <color> [ <percentage> | <length> ]?</span><br></pre></td></tr></table></figure><p>这意味着颜色在渐变线上的位置并不需要强制提供。</p><p>如果没有提供颜色在渐变线上的位置,颜色的位置就会沿着渐变线平均分布。如果只有 2 个颜色,那么颜色 1 将被放在 <code>0%</code> 的位置,颜色 2 就被放在 <code>100%</code> 的位置;如果有 3 个颜色,那么颜色 1 在 <code>0%</code>,颜色 2 在 <code>50%</code>,颜色 3 在 <code>100%</code>,以此类推。</p><p>运用好 <code>linear-gradient</code> 可以让你的页面更好看(是真的</p>]]></content>
<summary type="html">
<p>之前用 <code>background-image</code> 的 <code>linear-gradient</code> 画了下划线,觉得很神奇,就去认真学习了一下。做了些小笔记,备忘。</p>
</summary>
<category term="CSS" scheme="https://kaviilee.github.io/blog/categories/css/"/>
<category term="CSS" scheme="https://kaviilee.github.io/blog/tags/css/"/>
</entry>
<entry>
<title>使用background-image画虚线</title>
<link href="https://kaviilee.github.io/blog/2020/12/14/%E4%BD%BF%E7%94%A8background-image%E7%94%BB%E8%99%9A%E7%BA%BF/"/>
<id>https://kaviilee.github.io/blog/2020/12/14/%E4%BD%BF%E7%94%A8background-image%E7%94%BB%E8%99%9A%E7%BA%BF/</id>
<published>2020-12-14T14:54:50.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>在知道可以用 <code>background-image</code> 的渐变来定义虚线之前我都是用 <code>border-bottom: 1px dashed #ccc</code> 的。是新的知识,耶!</p><a id="more"></a><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span> </span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"item"</span>></span>background-image<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h2 id="传统的-border"><a href="#传统的-border" class="headerlink" title="传统的 border"></a>传统的 border</h2><figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> </span><br><span class="line"> <span class="selector-class">.item</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">16px</span>;</span><br><span class="line"> <span class="attribute">border-bottom</span>: <span class="number">1px</span> solid <span class="number">#ccc</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用 border 的效果如下</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/14/dHDhBA2OSG7cy9u.png" alt="border-bottom.png"></p><h2 id="使用-background-image"><a href="#使用-background-image" class="headerlink" title="使用 background-image"></a>使用 background-image</h2><figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> </span><br><span class="line"> <span class="selector-class">.item</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">16px</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="selector-tag">&</span><span class="selector-pseudo">::after</span> {</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">''</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// important</span></span><br><span class="line"> <span class="attribute">background-image</span>: linear-gradient(to right, <span class="number">#ccc</span> <span class="number">0%</span>, <span class="number">#ccc</span> <span class="number">50%</span>, transparent <span class="number">50%</span>);</span><br><span class="line"> <span class="comment">// 设置图片宽度与高度</span></span><br><span class="line"> <span class="attribute">background-size</span>: <span class="number">20px</span> <span class="number">1px</span>;</span><br><span class="line"> <span class="attribute">background-repeat</span>: repeat-x;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/12/14/mqUpH1GbIjc54Ru.png" alt="background-image.png"></p><p>使用 <code>background-image</code> 就可以通过改变 <code>background-size</code> 来改变虚线的间距。</p><p>如果要改变虚线的方向,需要修改 width,height,background-image, background-repeat 和 background-size。</p><figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">&</span><span class="selector-pseudo">::after</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">1px</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">background-image</span>: linear-gradient(to bottom, <span class="number">#ccc</span> <span class="number">0%</span>, <span class="number">#ccc</span> <span class="number">50%</span>, transparent <span class="number">50%</span>);</span><br><span class="line"> <span class="attribute">background-repeat</span>: repeat-y;</span><br><span class="line"> <span class="attribute">background-size</span>: <span class="number">1px</span> <span class="number">10px</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>在知道可以用 <code>background-image</code> 的渐变来定义虚线之前我都是用 <code>border-bottom: 1px dashed #ccc</code> 的。是新的知识,耶!</p>
</summary>
<category term="CSS" scheme="https://kaviilee.github.io/blog/categories/css/"/>
</entry>
<entry>
<title>antd form 自定义校验相关错误问题</title>
<link href="https://kaviilee.github.io/blog/2020/12/09/antd-form-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C%E7%9B%B8%E5%85%B3%E9%94%99%E8%AF%AF%E9%97%AE%E9%A2%98/"/>
<id>https://kaviilee.github.io/blog/2020/12/09/antd-form-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C%E7%9B%B8%E5%85%B3%E9%94%99%E8%AF%AF%E9%97%AE%E9%A2%98/</id>
<published>2020-12-09T14:17:35.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>在使用 ant design form 组件自定义校验,进行远程校验数据唯一性的时候,遇到的一些问题以及解决方法。</p><a id="more"></a><h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>使用 <a href="https://ant.design/components/form-cn/" target="_blank" rel="noopener">ant design form</a> 设计表单时,某些字段需要进行远程校验,比如:用户名(username),手机号(mobile),就需要自定义校验规则。</p><h2 id="错误示例"><a href="#错误示例" class="headerlink" title="错误示例"></a>错误示例</h2><p>在需要进行校验的地方,之前错误的写法</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">/** 验证字段唯一性</span><br><span class="line">* @param key 字段key</span><br><span class="line">* @param value 字段value</span><br><span class="line">*/</span><br><span class="line">const validateUnique = (key: string, value: string): any => {</span><br><span class="line"> if (value) {</span><br><span class="line"> remoteValidate({ key, value }).then((res: any) => {</span><br><span class="line"> if (res.success) {</span><br><span class="line"> return res.data.isUnique;</span><br><span class="line"> }</span><br><span class="line"> }).catch(e => {</span><br><span class="line"> message.error(e)</span><br><span class="line"> return false</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><Form.Item label="用户名" name="username" required rules={[{ required: true, message: '请输入用户名' }, () => ({</span><br><span class="line"> validator(_, value) {</span><br><span class="line"> if (!value || validateUnique('username', value)) {</span><br><span class="line"> return Promise.resolve()</span><br><span class="line"> }</span><br><span class="line"> return Promise.reject('已存在相同的用户名')</span><br><span class="line"> }</span><br><span class="line">})]}></span><br><span class="line"> <Input /></span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure><p>以上的写法能够校验字段值为空时的错误,但是在进行远程校验字段唯一性的时候,就无法验证。<br><code>validateUnique</code> 返回的值一直为 <code>undefined</code>,使得错误一直是 <code>已存在相同的用户名</code>。</p><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>为了解决校验一直返回 <code>undefined</code>,我们修改写法为</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><Form.Item label="用户名" name="username" required rules={[{ required: true, message: '请输入用户名' }, () => ({</span><br><span class="line"> validator(_, value) {</span><br><span class="line"> return new Promise((resolve, reject) => {</span><br><span class="line"> if (value) {</span><br><span class="line"> remoteValidate({ key: 'username', value }).then(async (res) => {</span><br><span class="line"> if (res.data.isUnique) {</span><br><span class="line"> return resolve()</span><br><span class="line"> }</span><br><span class="line"> return reject('已存在相同的手机号')</span><br><span class="line"> })</span><br><span class="line"> } else {</span><br><span class="line"> return reject()</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">})]}></span><br><span class="line"> <Input /></span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>在使用 ant design form 组件自定义校验,进行远程校验数据唯一性的时候,遇到的一些问题以及解决方法。</p>
</summary>
<category term="框架" scheme="https://kaviilee.github.io/blog/categories/%E6%A1%86%E6%9E%B6/"/>
<category term="Antd v4" scheme="https://kaviilee.github.io/blog/tags/antd-v4/"/>
<category term="react" scheme="https://kaviilee.github.io/blog/tags/react/"/>
</entry>
<entry>
<title>RESTful API 简介与实践</title>
<link href="https://kaviilee.github.io/blog/2020/10/21/restful/"/>
<id>https://kaviilee.github.io/blog/2020/10/21/restful/</id>
<published>2020-10-21T17:02:37.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>RESTful API 相关</p><a id="more"></a><blockquote><p><strong>REST</strong> 全称 <strong>Representational State Transfer</strong>,即「表现层状态转化」。符合 REST 原则的架构,就被称为 RESTful 架构。</p></blockquote><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><h3 id="一、资源-Resources"><a href="#一、资源-Resources" class="headerlink" title="一、资源 (Resources)"></a>一、资源 (Resources)</h3><p>REST 的名称「表现层状态转化」的主语其实是指的是「资源」(Resources),即 “资源表现层状态转化”。</p><p><strong>资源</strong>,就是网络上的一个实体,或者说是网络上的一个具体信息。可以是文本,图片,歌曲,服务等等。我们可以用一个 <strong>URI</strong> 指向它,每种资源对应一个特定的 <strong>URI</strong>。要获得这个资源,访问这个 URI就可以,因此 <strong>URI</strong> 也就成了每个资源的地址或者独一无二的标识符。</p><h3 id="二、表现层-Representation"><a href="#二、表现层-Representation" class="headerlink" title="二、表现层 (Representation)"></a>二、表现层 (Representation)</h3><p>「资源」是一种信息实体,它可以有多种外在表现形式。<strong>我们把「资源」具体呈现出来的形式,叫做它的「表现层」(Representation)</strong>。</p><p>比如,文本可以用 txt 格式表现,也可以用 HTML,XML,JSON等格式表现,图片可以用 JPG 格式表现,也可以用 PNG 格式表现。</p><p><strong>URI</strong> 只代表资源的实体,不代表表现形式。具体的表现格式,就属于「表现层」的范畴,而URI应该只代表「资源」的位置。它的具体表现形式,应该在HTTP请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对「表现层」的描述。</p><h3 id="三、状态转化-State-Transfer"><a href="#三、状态转化-State-Transfer" class="headerlink" title="三、状态转化 (State Transfer)"></a>三、状态转化 (State Transfer)</h3><p>访问一个状态,就代表了客户端和服务端的一个互动过程。在这个过程中,必然涉及到数据和状态的变化。</p><p>互联网通讯协议 HTTP 协议,是一个无状态协议。也就是说,所有的状态都保存在服务器端。因此,<strong>客户端想要操作服务器,就必须通过某种手段,使服务器发生「状态转化」(State Transfer)。而这种转化是建立在表现层上的,也就是「表现层状态转化」。</strong></p><p>在 HTTP 协议中,四个表示操作方式的动词:<strong>GET,POST,PUT,DELETE</strong>。他们分别对应四种基本操作:<strong>GET 获取资源,POST 新建资源(也可以更新资源),PUT 更新资源,DELETE 删除资源</strong>。</p><h3 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h3><p>总结一下 RESTful 架构:</p><ol><li>每一个 <strong>URI</strong> 代表一种资源;</li><li>客户端和服务器之间,传递这种资源的表现层;</li><li>客户端通过四个 HTTP 动词,对服务端资源进行操作,实现「表现层状态转化」。</li></ol><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><h3 id="一、URL设计"><a href="#一、URL设计" class="headerlink" title="一、URL设计"></a>一、URL设计</h3><h4 id="1-1-动词-宾语"><a href="#1-1-动词-宾语" class="headerlink" title="1.1 动词 + 宾语"></a>1.1 动词 + 宾语</h4><p>RESTful 的核心思想就是,客户端发出的数据操作指令都是「动词 + 宾语」的结构。比如,<code>GET /todos</code> 这个命令,<code>GET</code> 是动词,<code>/todos</code> 是宾语。</p><p>动词通常就是五种 HTTP 方法,对应 CRUD 操作。</p><blockquote><ul><li>GET:读取(Read)</li><li>POST:新建(Create)</li><li>PUT:更新(Update)</li><li>PATCH:更新(Update),通常是部分更新</li><li>DELETE:删除(Delete)</li></ul></blockquote><h4 id="1-2-动词的覆盖"><a href="#1-2-动词的覆盖" class="headerlink" title="1.2 动词的覆盖"></a>1.2 动词的覆盖</h4><p>有些客户端只能使用 <code>GET</code> 和 <code>POST</code> 方法。服务器必须接受 <code>POST</code> 模拟其他三个方法(<code>PUT</code>,<code>PATCH</code>,<code>DELETE</code>) 。</p><p>此时,客户端发出的 HTTP 请求,要加上 <code>X-HTTP-Method-Override</code> 属性,告诉服务器应该使用哪个动词来覆盖 <code>POST</code> 方法。</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">POST /api/todos/4 HTTP/1.1</span><br><span class="line">X-HTTP-Method-Override: PUT</span><br></pre></td></tr></table></figure><h4 id="1-3-宾语必须是名词"><a href="#1-3-宾语必须是名词" class="headerlink" title="1.3 宾语必须是名词"></a>1.3 宾语必须是名词</h4><p>宾语就是 <strong>API</strong> 的 <strong>URL</strong>,是 <strong>HTTP</strong> 动词作用的对象。它应该是名词,不能是动词。</p><h4 id="1-4-复数-URL"><a href="#1-4-复数-URL" class="headerlink" title="1.4 复数 URL"></a>1.4 复数 URL</h4><p>既然 URL 是名词,那么应该使用复数,还是单数?</p><p>这没有统一的规定,但是常见的操作是读取一个集合,比如 <code>GET /articles</code>(读取所有文章),这里明显应该是复数。</p><p>为了统一起见,建议都使用复数 URL,比如 <code>GET /articles/2</code> 要好于 <code>GET /article/2</code>。</p><h4 id="1-5-避免多级-URL"><a href="#1-5-避免多级-URL" class="headerlink" title="1.5 避免多级 URL"></a>1.5 避免多级 URL</h4><p>常见的情况是,资源需要多级分类,因此很容易写出多级的 URL,比如获取某个作者的某一类文章。</p><figure class="highlight"><table><tr><td class="code"><pre><span class="line">GET /authors/12/categories/2</span><br></pre></td></tr></table></figure><p>改成查询字符串更好</p><figure class="highlight"><table><tr><td class="code"><pre><span class="line">GET /authors/12?categories=2</span><br></pre></td></tr></table></figure><h3 id="二、状态码"><a href="#二、状态码" class="headerlink" title="二、状态码"></a>二、状态码</h3><h4 id="2-1-状态码必须精确"><a href="#2-1-状态码必须精确" class="headerlink" title="2.1 状态码必须精确"></a>2.1 状态码必须精确</h4><p>客户端的每一次请求,服务器都必须给出回应。回应包含 HTTP 状态码和数据两部分。</p><p>HTTP 状态码就是一个三位数,分成五个类别。</p><blockquote><ul><li><code>1xx</code>:相关信息</li><li><code>2xx</code>:操作成功</li><li><code>3xx</code>:重定向</li><li><code>4xx</code>:客户端错误</li><li><code>5xx</code>:服务器错误</li></ul></blockquote><p>API 不需要 <code>1xx</code> 状态码</p><h4 id="2-2-状态码具体意义"><a href="#2-2-状态码具体意义" class="headerlink" title="2.2 状态码具体意义"></a>2.2 状态码具体意义</h4><p>服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。</p><blockquote><ul><li>200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。</li><li>201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。</li><li>202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)</li><li>204 NO CONTENT - [DELETE]:用户删除数据成功。</li><li>400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。</li><li>401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。</li><li>403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。</li><li>404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。</li><li>406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。</li><li>410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。</li><li>422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。</li><li>500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。</li><li>503 Service Unavailable: 服务器无法处理请求,一般用于网站维护状态</li></ul></blockquote><blockquote><p>本文摘自阮一峰老师的 <a href="http://www.ruanyifeng.com/blog/2011/09/restful.html" target="_blank" rel="noopener">理解RESTful架构</a> 和 <a href="http://www.ruanyifeng.com/blog/2014/05/restful_api.html" target="_blank" rel="noopener">RESTful API 最佳实践</a></p></blockquote>]]></content>
<summary type="html">
<p>RESTful API 相关</p>
</summary>
<category term="技术" scheme="https://kaviilee.github.io/blog/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="规范" scheme="https://kaviilee.github.io/blog/tags/%E8%A7%84%E8%8C%83/"/>
</entry>
<entry>
<title>约定式提交 (Conventional Commits)</title>
<link href="https://kaviilee.github.io/blog/2020/10/13/conventional-commits/"/>
<id>https://kaviilee.github.io/blog/2020/10/13/conventional-commits/</id>
<published>2020-10-13T00:19:25.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>关于约定式提交的规范内容。</p><a id="more"></a><h1 id="约定式提交"><a href="#约定式提交" class="headerlink" title="约定式提交"></a>约定式提交</h1><blockquote><p>一种用于给提交信息增加人机可读含义的规范。</p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>约定式提交规范是一种基于提交消息的轻量级约定。 它提供了一组用于创建清晰的提交历史的简单规则; 这使得编写基于规范的自动化工具变得更容易。在提交信息中描述新特性、bug 修复和破坏性变更。</p><p>提交说明的结构如下:</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><类型>[可选的作用域]: <描述></span><br><span class="line">[可选的正文]</span><br><span class="line">[可选的脚注]</span><br></pre></td></tr></table></figure><p>提交说明包含了下面的结构化元素,以向类库使用者表明其意图:</p><ol><li><strong>fix</strong>: 类型为 <code>fix</code> 的提交表示在代码库中修复了一个 bug。</li><li><strong>feat</strong>: 类型为 <code>feat</code> 的提交表示在代码库中新增了一个功能。</li><li><strong>BREAKING CHANGE</strong>: 在可选的正文或脚注的起始位置带有 <code>BREAKING CHANGE:</code> 的提交,表示引入了破坏性 API 变更。破坏性变更可以是任意 <em>类型</em> 提交的一部分。</li><li><strong>其它情况:</strong> 除 <code>fix:</code> 和 <code>feat:</code> 之外的提交 <em>类型</em> 也是被允许的,例如 <a href="https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional" target="_blank" rel="noopener">@commitlint/config-conventional</a>(基于 <a href="https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines" target="_blank" rel="noopener">Angular 约定</a>)中推荐的 <code>chore:</code>、<code>docs:</code>、<code>style:</code>、<code>refactor:</code>、<code>perf:</code>、<code>test:</code> 及其他标签。 我们也推荐使用<code>improvement</code>,用于对当前实现进行改进而没有添加新功能或修复错误的提交。 请注意,这些标签在约定式提交规范中并不是强制性的。并且在语义化版本中没有隐式的影响(除非他们包含 BREAKING CHANGE)。 可以为提交类型添加一个围在圆括号内的作用域,以为其提供额外的上下文信息。例如 <code>feat(parser): adds ability to parse arrays.</code>。</li></ol><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><h3 id="包含了描述以及正文内有破坏性变更的提交说明"><a href="#包含了描述以及正文内有破坏性变更的提交说明" class="headerlink" title="包含了描述以及正文内有破坏性变更的提交说明"></a>包含了描述以及正文内有破坏性变更的提交说明</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">feat: allow provided config object to extend other configs</span><br><span class="line"></span><br><span class="line">BREAKING CHANGE: `extends` key in config file is now used for extending other config files</span><br></pre></td></tr></table></figure><h3 id="不包含正文的提交说明"><a href="#不包含正文的提交说明" class="headerlink" title="不包含正文的提交说明"></a>不包含正文的提交说明</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">docs: correct spelling of CHANGELOG</span><br></pre></td></tr></table></figure><h3 id="包含作用域的提交说明"><a href="#包含作用域的提交说明" class="headerlink" title="包含作用域的提交说明"></a>包含作用域的提交说明</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">feat(lang): add polish language</span><br></pre></td></tr></table></figure><h3 id="为-fix-编写的提交说明,包含(可选的)-issue-编号"><a href="#为-fix-编写的提交说明,包含(可选的)-issue-编号" class="headerlink" title="为 fix 编写的提交说明,包含(可选的) issue 编号"></a>为 fix 编写的提交说明,包含(可选的) issue 编号</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">fix: correct minor typos in code</span><br><span class="line"></span><br><span class="line">see the issue for details on the typos fixed</span><br><span class="line"></span><br><span class="line">closes issue #12</span><br></pre></td></tr></table></figure><h2 id="约定式提交规范"><a href="#约定式提交规范" class="headerlink" title="约定式提交规范"></a>约定式提交规范</h2><ol><li>每个提交都<strong>必须</strong>使用类型字段前缀,它由一个名词组成,诸如 <code>feat</code> 或 <code>fix</code> ,其后接一个<strong>可选的</strong>作用域字段,以及一个<strong>必要的</strong>冒号(英文半角)和空格。</li><li>当一个提交为应用或类库实现了新特性时,<strong>必须</strong>使用 <code>feat</code> 类型。</li><li>当一个提交为应用修复了 bug 时,<strong>必须</strong>使用 <code>fix</code> 类型。</li><li>作用域字段<strong>可以</strong>跟随在类型字段后面。作用域<strong>必须</strong>是一个描述某部分代码的名词,并用圆括号包围,例如: <code>fix(parser):</code></li><li>描述字段<strong>必须</strong>紧接在类型/作用域前缀的空格之后。描述指的是对代码变更的简短总结,例如: <em>fix: array parsing issue when multiple spaces were contained in string.</em></li><li>在简短描述之后,<strong>可以</strong>编写更长的提交正文,为代码变更提供额外的上下文信息。正文<strong>必须</strong>起始于描述字段结束的一个空行后。</li><li>在正文结束的一个空行之后,<strong>可以</strong>编写一行或多行脚注。脚注<strong>必须</strong>包含关于提交的元信息,例如:关联的合并请求、Reviewer、破坏性变更,每条元信息一行。</li><li>破坏性变更<strong>必须</strong>标示在正文区域最开始处,或脚注区域中某一行的开始。一个破坏性变更<strong>必须</strong>包含大写的文本 <code>BREAKING CHANGE</code>,后面紧跟冒号和空格。</li><li>在 <code>BREAKING CHANGE:</code>之后<strong>必须</strong>提供描述,以描述对 API 的变更。例如: <em>BREAKING CHANGE: environment variables now take precedence over config files.</em></li><li>在提交说明中,<strong>可以</strong>使用 <code>feat</code> 和 <code>fix</code> 之外的类型。</li><li>工具的实现<strong>必须不</strong>区分大小写地解析构成约定式提交的信息单元,只有 <code>BREAKING CHANGE</code> <strong>必须</strong>是大写的。</li><li><strong>可以</strong>在类型/作用域前缀之后,<code>:</code> 之前,附加 <code>!</code> 字符,以进一步提醒注意破坏性变更。当有 <code>!</code> 前缀时,正文或脚注内<strong>必须</strong>包含 <code>BREAKING CHANGE: description</code></li></ol><br /><p>具体类型含义如下:</p><table><thead><tr><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>feat:</code></td><td>新增功能</td></tr><tr><td><code>fix:</code></td><td>修复 bug</td></tr><tr><td><code>docs:</code></td><td>仅修改文档</td></tr><tr><td><code>style:</code></td><td>样式不会影响代码含义的更改,如 <strong>空白符</strong>、<strong>格式</strong>、<strong>分号补全</strong>、<strong>错别字修改等</strong></td></tr><tr><td><code>refactor:</code></td><td>既不修复错误也不增加功能的代码修改</td></tr><tr><td><code>perf:</code></td><td>本次代码的更改可提高性能</td></tr><tr><td><code>test:</code></td><td>添加或修改测试内容</td></tr><tr><td><code>build:</code></td><td>影响构建系统或外部依赖的更改 (<strong>Example scopes:</strong> <code>webpack</code>, <code>npm</code>)</td></tr><tr><td><code>ci:</code></td><td>对 CI 配置文件和脚本的更改 (<strong>Example scopes:</strong> <code>travis</code>)</td></tr><tr><td><code>chore</code></td><td>其他不会修改 src 或测试文件的更改,如 <code>.gitignore</code>,<code>package.json</code>、<code>yarn.json</code> 等</td></tr><tr><td><code>revert</code></td><td>回退旧版本</td></tr></tbody></table><p>更多内容参见:<a href="https://www.conventionalcommits.org/" target="_blank" rel="noopener">约定式提交</a></p>]]></content>
<summary type="html">
<p>关于约定式提交的规范内容。</p>
</summary>
<category term="技术" scheme="https://kaviilee.github.io/blog/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="git" scheme="https://kaviilee.github.io/blog/tags/git/"/>
<category term="规范" scheme="https://kaviilee.github.io/blog/tags/%E8%A7%84%E8%8C%83/"/>
</entry>
<entry>
<title>JavaScript类型转换</title>
<link href="https://kaviilee.github.io/blog/2020/09/09/javascript%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/"/>
<id>https://kaviilee.github.io/blog/2020/09/09/javascript%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/</id>
<published>2020-09-09T23:23:10.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>关于 JavaScript 类型转换的一些笔记。</p><a id="more"></a><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>将值从一种类型转换到另一种类型通常称为类型转换。</p><h2 id="原始值转布尔值"><a href="#原始值转布尔值" class="headerlink" title="原始值转布尔值"></a>原始值转布尔值</h2><p>JavaScript中,只有 6 种值可以被转换为false,其余都会被转换成 true。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>()) <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(<span class="literal">false</span>)) <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(<span class="literal">undefined</span>)) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(<span class="literal">null</span>)) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(+<span class="number">0</span>)) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(<span class="number">-0</span>)) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(<span class="literal">NaN</span>)) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Boolean</span>(<span class="string">""</span>)) <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h2 id="原始值转数字"><a href="#原始值转数字" class="headerlink" title="原始值转数字"></a>原始值转数字</h2><p>使用 <code>Number</code> 函数将值的类型转换成数字类型。</p><p>根据规范,如果有参数,则调用 <code>ToNumber(value)</code> 计算出值返回,否则返回 +0。</p><p>此处的 <code>ToNumber</code> 表示的是一个底层规范上实现的方法,并没有暴露出来。</p><table><thead><tr><th align="center">值类型</th><th align="center">结果</th></tr></thead><tbody><tr><td align="center">Undefined</td><td align="center">NaN</td></tr><tr><td align="center">Null</td><td align="center">+0</td></tr><tr><td align="center">Boolean</td><td align="center">如果是 true,返回1;如果是 false,返回0</td></tr><tr><td align="center">Number</td><td align="center">返回与之相等的值</td></tr><tr><td align="center">String</td><td align="center">如下</td></tr></tbody></table><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>()) <span class="comment">// +0</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="literal">undefined</span>)) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="literal">null</span>)) <span class="comment">// +0</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="literal">false</span>)) <span class="comment">// +0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="literal">true</span>)) <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"123"</span>)) <span class="comment">// 123</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"-123"</span>)) <span class="comment">// -123</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"1.2"</span>)) <span class="comment">// 1.2</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"000123"</span>)) <span class="comment">// 123</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"-000123"</span>)) <span class="comment">// -123</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"0x11"</span>)) <span class="comment">// 17</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">""</span>)) <span class="comment">// 0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">" "</span>)) <span class="comment">// 0</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"123 123"</span>)) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"foo"</span>)) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="string">"100a"</span>)) <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><h2 id="原始值转字符"><a href="#原始值转字符" class="headerlink" title="原始值转字符"></a>原始值转字符</h2><p>使用 <code>String</code> 函数将值的类型转换成字符类型。</p><p>根据规范,如果函数有参数,则调用 <code>ToString(value)</code> 计算出值返回,否则返回空字符串。</p><table><thead><tr><th align="center">值类型</th><th align="center">结果</th></tr></thead><tbody><tr><td align="center">Undefined</td><td align="center">‘undefined’</td></tr><tr><td align="center">Null</td><td align="center">‘null’</td></tr><tr><td align="center">Boolean</td><td align="center">如果是 true,返回 “true”;如果是 false,返回 “false”</td></tr><tr><td align="center">String</td><td align="center">返回与之相等的值</td></tr><tr><td align="center">Number</td><td align="center">如下</td></tr></tbody></table><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>()) <span class="comment">// 空字符串</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="literal">undefined</span>)) <span class="comment">// undefined</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="literal">null</span>)) <span class="comment">// null</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="literal">false</span>)) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="literal">true</span>)) <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="number">0</span>)) <span class="comment">// 0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="number">-0</span>)) <span class="comment">// 0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="literal">NaN</span>)) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="literal">Infinity</span>)) <span class="comment">// Infinity</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(-<span class="literal">Infinity</span>)) <span class="comment">// -Infinity</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">String</span>(<span class="number">1</span>)) <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h2 id="原始值转对象"><a href="#原始值转对象" class="headerlink" title="原始值转对象"></a>原始值转对象</h2><p>原始值通过调用 String()、Number() 或者 Boolean() 构造函数就能转化为各自的包装对象。</p><p>null 和 undefined,当将它们用在期望是一个对象的地方都会造成一个类型错误 (TypeError) 异常,而不会执行正常的转换。</p><h2 id="对象转布尔值"><a href="#对象转布尔值" class="headerlink" title="对象转布尔值"></a>对象转布尔值</h2><p>所有对象转为布尔值都为 true。</p><h2 id="对象转字符串和数字"><a href="#对象转字符串和数字" class="headerlink" title="对象转字符串和数字"></a>对象转字符串和数字</h2><p>对象转换到字符串或者数字都是调用待转换对象的一个方法来实现的。主要有两个方法 <code>toString()</code> 和 <code>valueOf()</code>。</p><p>所有对象除了 null 和 undefined 之外的任何值都有 <code>toString</code> 方法,通常,它和 String 方法的返回值是一致的。</p><p>JavaScript 根据不同的类各自的特点,定义了更多版本的 <code>toString</code> 方法。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(({}).toString()) <span class="comment">// [object Object]</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log([].toString()) <span class="comment">// ""</span></span><br><span class="line"><span class="built_in">console</span>.log([<span class="number">0</span>].toString()) <span class="comment">// 0</span></span><br><span class="line"><span class="built_in">console</span>.log([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].toString()) <span class="comment">// 1,2,3</span></span><br><span class="line"><span class="built_in">console</span>.log((<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="keyword">var</span> a = <span class="number">1</span>;}).toString()) <span class="comment">// function (){var a = 1;}</span></span><br><span class="line"><span class="built_in">console</span>.log((<span class="regexp">/\d+/g</span>).toString()) <span class="comment">// /\d+/g</span></span><br><span class="line"><span class="built_in">console</span>.log((<span class="keyword">new</span> <span class="built_in">Date</span>(<span class="number">2010</span>, <span class="number">0</span>, <span class="number">1</span>)).toString()) <span class="comment">// Fri Jan 01 2010 00:00:00 GMT+0800 (CST)</span></span><br></pre></td></tr></table></figure><p>另一个转换对象的方法是 <code>valueOf</code>,表示对象的原始值。默认的 <code>valueOf</code> 方法返回这个对象本身。日期是个例外,它会返回它的一个内容表示:1970 年 1 月 1日以来的毫秒数。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> date = <span class="keyword">new</span> <span class="built_in">Date</span>(<span class="number">2020</span>, <span class="number">9</span>, <span class="number">6</span>);</span><br><span class="line"><span class="built_in">console</span>.log(date.valueOf()) <span class="comment">// 1601913600000</span></span><br></pre></td></tr></table></figure><h3 id="对象转字符串"><a href="#对象转字符串" class="headerlink" title="对象转字符串"></a>对象转字符串</h3><table><thead><tr><th align="center">值类型</th><th align="center">结果</th></tr></thead><tbody><tr><td align="center">Object</td><td align="center">1. primValue = ToPrimitive(input, Number)<br/>2. 返回 ToString(primValue)。</td></tr></tbody></table><h3 id="对象转数字"><a href="#对象转数字" class="headerlink" title="对象转数字"></a>对象转数字</h3><table><thead><tr><th align="center">参数类型</th><th align="center">结果</th></tr></thead><tbody><tr><td align="center">Object</td><td align="center">1. primValue = ToPrimitive(input, Number)<br/>2. 返回 ToNumber(primValue)。</td></tr></tbody></table><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>({})) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>({<span class="attr">a</span> : <span class="number">1</span>})) <span class="comment">// NaN</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>([])) <span class="comment">// 0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>([<span class="number">0</span>])) <span class="comment">// 0</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="keyword">var</span> a = <span class="number">1</span>;})) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="regexp">/\d+/g</span>)) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="keyword">new</span> <span class="built_in">Date</span>(<span class="number">2010</span>, <span class="number">0</span>, <span class="number">1</span>))) <span class="comment">// 1262275200000</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Number</span>(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'a'</span>))) <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><p><code>Number([])</code> 时,先调用了 <code>[]</code> 的 <code>valueOf</code> 方法返回了 <code>[]</code> ,因为不是原始值,继续调用 <code>toString</code> 方法,返回空字符串,继续调用 <code>ToNumber</code>,空字符串返回 0。</p><p><code>Number([1,2,3])</code> 时,先调用了 <code>[1, 2, 3]</code> 的 <code>valueOf</code> 方法返回了 <code>[1, 2, 3]</code>,再调用 <code>toString</code> 方法,返回 <code>1,2,3</code>,调用 <code>ToNumber</code>,返回NaN。</p><h3 id="ToPrimitive"><a href="#ToPrimitive" class="headerlink" title="ToPrimitive"></a>ToPrimitive</h3><p>语法如下:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">ToPrimitive(input[, PreferredType])</span><br></pre></td></tr></table></figure><p>第一个参数是 input,表示要处理的输入值。</p><p>第二个参数是 PreferredType,非必填,表示希望转换成的类型,有两个值可以选,Number 或者 String。</p><p>当不传入 PreferredType 时,如果 input 是日期类型,相当于传入 String,否则,都相当于传入 Number。</p><p>如果传入的 input 是 Undefined、Null、Boolean、Number、String 类型,直接返回该值。</p><p>如果是 ToPrimitive(obj, Number),处理步骤如下:</p><ol><li>如果 obj 为 基本类型,直接返回</li><li>否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。</li><li>否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。</li><li>否则,JavaScript 抛出一个类型错误异常。</li></ol><p>如果是 ToPrimitive(obj, String),处理步骤如下:</p><ol><li>如果 obj为 基本类型,直接返回</li><li>否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。</li><li>否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。</li><li>否则,JavaScript 抛出一个类型错误异常。</li></ol><blockquote><p>隐式转换场景</p></blockquote><h2 id="一元操作符"><a href="#一元操作符" class="headerlink" title="一元操作符 +"></a>一元操作符 +</h2><p>当 + 运算符作为一元操作符时,会调用 <code>ToNumber</code> 处理该值,相当于 <code>Number(value)</code>。</p><p>如果 value 是对象,会先调用 <code>ToPrimitive(obj, Number)</code> 方法。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(+[<span class="string">'1'</span>]); <span class="comment">// 1</span></span><br><span class="line"><span class="built_in">console</span>.log(+[<span class="string">'1'</span>, <span class="string">'2'</span>, <span class="string">'3'</span>]); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">console</span>.log(+{}); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><h2 id="二元操作符"><a href="#二元操作符" class="headerlink" title="二元操作符 +"></a>二元操作符 +</h2><p>当计算 value1 + value2 时:</p><ol><li>lprim = ToPrimitive(value1)</li><li>rprim = ToPrimitive(value2)</li><li>如果 lprim 是字符串或者 rprim 是字符串,那么返回 ToString(lprim) 和 ToString(rprim) 的拼接结果</li><li>返回 ToNumber(lprim) 和 ToNumber(rprim) 的运算结果</li></ol><h2 id="相等"><a href="#相等" class="headerlink" title="== 相等"></a>== 相等</h2><table><thead><tr><th align="center">类型 (x)</th><th align="center">类型 (y)</th><th align="center">结果</th></tr></thead><tbody><tr><td align="center">null</td><td align="center">undefined</td><td align="center">true</td></tr><tr><td align="center">undefined</td><td align="center">null</td><td align="center">true</td></tr><tr><td align="center">数值</td><td align="center">字符串</td><td align="center">x == ToNumber(y)</td></tr><tr><td align="center">字符串</td><td align="center">数值</td><td align="center">ToNumber(x) == y</td></tr><tr><td align="center">布尔值</td><td align="center">任何类型</td><td align="center">ToNumber(x) == y</td></tr><tr><td align="center">任何类型</td><td align="center">布尔值</td><td align="center">x = ToNumber(y)</td></tr><tr><td align="center">字符串或数</td><td align="center">对象</td><td align="center">x = ToPrimitive(y)</td></tr><tr><td align="center">对象</td><td align="center">字符串或数</td><td align="center">ToPrimitive(x) == y</td></tr></tbody></table>]]></content>
<summary type="html">
<p>关于 JavaScript 类型转换的一些笔记。</p>
</summary>
<category term="JavaScript" scheme="https://kaviilee.github.io/blog/categories/javascript/"/>
</entry>
<entry>
<title>Git Flow</title>
<link href="https://kaviilee.github.io/blog/2020/09/03/git-flow/"/>
<id>https://kaviilee.github.io/blog/2020/09/03/git-flow/</id>
<published>2020-09-03T09:56:17.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>之前偶尔看到的一篇文章,是讲 Git Flow 的,谈了分支相关的一些知识。</p><a id="more"></a><h2 id="Git-Flow"><a href="#Git-Flow" class="headerlink" title="Git Flow"></a>Git Flow</h2><h3 id="分支应用场景"><a href="#分支应用场景" class="headerlink" title="分支应用场景"></a>分支应用场景</h3><p>根据 Git Flow 的建议,主要的分支有 master、develop、hotfix、release 以及 feature 这五种分支,各种分支负责不同的功能。其中 Master 以及 Develop 这两个分支又被称作长期分支,因为他们会一直存活在整个 Git Flow 里,而其它的分支大多会因任务结束而被刪除。</p><h3 id="Master-分支"><a href="#Master-分支" class="headerlink" title="Master 分支"></a>Master 分支</h3><p>主要是用来放稳定、随时可上线的版本。这个分支的来源只能从别的分支合并过来,开发者不会直接 Commit 到这个分支。因为是稳定版本,所以通常也会在这个分支上的 Commit 上打上版本号 tag。</p><p><strong>每个版本发布完,develop 会合并到 master,并打tag</strong>。</p><h3 id="Develop-分支"><a href="#Develop-分支" class="headerlink" title="Develop 分支"></a>Develop 分支</h3><p>这个分支主要是所有开发的基础分支,当要新增功能的时候,所有的 Feature 分支都是从这个分支切出去的。而 Feature 分支的功能完成后,也都会合并回来这个分支。</p><p><strong>开发过程中的稳定分支,develop分支应该保证每次最新的commit都是可以被run的</strong>。</p><h3 id="Hotfix-分支"><a href="#Hotfix-分支" class="headerlink" title="Hotfix 分支"></a>Hotfix 分支</h3><p>当线上产品发生紧急问题的时候,会从 Master 分支开一个 Hotfix 分支出来进行修复,Hotfix 分支修复完成之后,会合并回 Master 分支,也同时会合并一份到 Develop 分支。</p><h3 id="Release-分支"><a href="#Release-分支" class="headerlink" title="Release 分支"></a>Release 分支</h3><p>当认为 Develop 分支够成熟了,就可以把 Develop 分支合并到 Release 分支,在这边进行算是上线前的最后测试。测试完成后,Release 分支将会同时合并到 Master 以及 Develop 这两个分支上。 Master 分支是上线版本,而合并回 Develop 分支的目的,是因为可能在 Release 分支上还会测到并修正一些问题,所以需要跟 Develop 分支同步,免得之后的版本又再度出现同样的问题。</p><p><strong>其实正常的做法应该是提测,或测试到某个阶段以后使用。一般用在多版本并行开发的时候,充当特定版本的develop 分支使用</strong></p><h3 id="Feature-分支"><a href="#Feature-分支" class="headerlink" title="Feature 分支"></a>Feature 分支</h3><p>当要开始新增功能的时候,就是使用 Feature 分支的时候了。 Feature 分支都是从 Develop 分支来的,完成之后会再并回 Develop 分支。</p><p><strong>目前feature分支起名规则以 dev_ 开头,后面跟当前开发功能</strong></p><h2 id="分支提交与合并"><a href="#分支提交与合并" class="headerlink" title="分支提交与合并"></a>分支提交与合并</h2><p><strong>如果 feature 需要 develop 的功能,不要将 develop 分支 merge 到 feature 分支,应该使用 feature rebase develop</strong></p><p>分支提交使用 rebase 或 merge 方式都可以,一般来说commit 少的场景我会比较喜欢 rebase,但如果commit攒的多了,rebase 解决冲突会很累。</p><h3 id="rebase-操作"><a href="#rebase-操作" class="headerlink" title="rebase 操作"></a>rebase 操作</h3><p>例如,开始开发时,分支是这样的</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">* -- * -- A (develop)</span><br><span class="line"> \</span><br><span class="line"> * (dev_xxx)</span><br></pre></td></tr></table></figure><p>开发完成,准备提交时:</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">* -- * -- A -- B -- C -- D -- E (develop)</span><br><span class="line"> \</span><br><span class="line"> * -- X -- Y -- Z (dev_xxx)</span><br></pre></td></tr></table></figure><p>命令行步骤如下(GUI参考命令行执行对应操作)</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git add .</span><br><span class="line"></span><br><span class="line">git commit -m "xxx"</span><br><span class="line"></span><br><span class="line">git pull //用于更新远端分支(git fetch 也可以)</span><br><span class="line"></span><br><span class="line">git rebase origin/develop //将当前分支base变为develop 的最新 commit</span><br></pre></td></tr></table></figure><p>这一步的实际原理是,git会从develop新开一个分支,将你当前的dev_xxx 分支的 commit 按照提交顺序挨个 cherry-pick 到新开分支上</p><p>这一步执行过程中如果 develop 的 B C D E等commit 与 X Y Z 冲突,则需要依次解决冲突</p><p>解决冲突以后,执行 <code>git rebase --continue</code> 或者 <code>git rebase --skip</code> 继续执行接下来的 rebase</p><p>rebase 结束后,分支结构将变为:</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">* -- * -- A -- B -- C -- D -- E (develop)</span><br><span class="line"> \</span><br><span class="line"> -- * -- X -- Y -- Z (dev_xxx)</span><br></pre></td></tr></table></figure><p>然后需要执行</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git push -f // 必须加 -f 强制推送,否则由于本地分支的base与远端不一致,会报需要 git pull 无法提交</span><br></pre></td></tr></table></figure><p>提交完成后去 gitlab 创建 merge request,走正常 review 流程,合并代码。</p><br /><p>本文转载自 <a href="https://www.kymjs.com/manager/2020/05/29/01/" target="_blank" rel="noopener">再聊 Git Flow</a></p>]]></content>
<summary type="html">
<p>之前偶尔看到的一篇文章,是讲 Git Flow 的,谈了分支相关的一些知识。</p>
</summary>
<category term="技术" scheme="https://kaviilee.github.io/blog/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="git" scheme="https://kaviilee.github.io/blog/tags/git/"/>
<category term="规范" scheme="https://kaviilee.github.io/blog/tags/%E8%A7%84%E8%8C%83/"/>
</entry>
<entry>
<title>中文文案排版指北</title>
<link href="https://kaviilee.github.io/blog/2020/08/18/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%88%E6%8E%92%E7%89%88%E6%8C%87%E5%8C%97/"/>
<id>https://kaviilee.github.io/blog/2020/08/18/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%88%E6%8E%92%E7%89%88%E6%8C%87%E5%8C%97/</id>
<published>2020-08-18T11:31:47.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>今天在看文档的时候,偶然看到这篇关于中文文案排版建议的文章,觉得可以分享出来~</p><a id="more"></a><h2 id="空格"><a href="#空格" class="headerlink" title="空格"></a>空格</h2><p>「有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。</p><p>与大家共勉之。」——<a href="https://github.com/vinta/pangu.js" target="_blank" rel="noopener">vinta/paranoid-auto-spacing</a></p><h3 id="中英文之间需要增加空格"><a href="#中英文之间需要增加空格" class="headerlink" title="中英文之间需要增加空格"></a>中英文之间需要增加空格</h3><p>正确:</p><blockquote><p>在 LeanCloud 上,数据存储是围绕 <code>AVObject</code> 进行的。</p></blockquote><p>错误:</p><blockquote><p>在LeanCloud上,数据存储是围绕<code>AVObject</code>进行的。</p></blockquote><blockquote><p>在 LeanCloud上,数据存储是围绕<code>AVObject</code> 进行的。</p></blockquote><p>完整的正确用法:</p><blockquote><p>在 LeanCloud 上,数据存储是围绕 <code>AVObject</code> 进行的。每个 <code>AVObject</code> 都包含了与 JSON 兼容的 key-value 对应的数据。数据是 schema-free 的,你不需要在每个 <code>AVObject</code> 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。</p></blockquote><p>例外:「豆瓣FM」等产品名词,按照官方所定义的格式书写。</p><h3 id="中文与数字之间需要增加空格"><a href="#中文与数字之间需要增加空格" class="headerlink" title="中文与数字之间需要增加空格"></a>中文与数字之间需要增加空格</h3><p>正确:</p><blockquote><p>今天出去买菜花了 5000 元。</p></blockquote><p>错误:</p><blockquote><p>今天出去买菜花了 5000元。</p></blockquote><blockquote><p>今天出去买菜花了5000元。</p></blockquote><h3 id="数字与单位之间无需增加空格"><a href="#数字与单位之间无需增加空格" class="headerlink" title="数字与单位之间无需增加空格"></a>数字与单位之间无需增加空格</h3><p>正确:</p><blockquote><p>我家的光纤入户宽带有 10Gbps,SSD 一共有 10TB。</p></blockquote><p>错误:</p><blockquote><p>我家的光纤入户宽带有 10 Gbps,SSD 一共有 20 TB。</p></blockquote><p>另外,度/百分比与数字之间不需要增加空格:</p><p>正确:</p><blockquote><p>今天是 233° 的高温。</p></blockquote><blockquote><p>新 MacBook Pro 有 15% 的 CPU 性能提升。</p></blockquote><p>错误:</p><blockquote><p>今天是 233 ° 的高温。</p></blockquote><blockquote><p>新 MacBook Pro 有 15 % 的 CPU 性能提升。</p></blockquote><h3 id="全角标点与其他字符之间不加空格"><a href="#全角标点与其他字符之间不加空格" class="headerlink" title="全角标点与其他字符之间不加空格"></a>全角标点与其他字符之间不加空格</h3><p>正确:</p><blockquote><p>刚刚买了一部 iPhone,好开心!</p></blockquote><p>错误:</p><blockquote><p>刚刚买了一部 iPhone ,好开心!</p></blockquote><h3 id="ms-text-autospace-to-the-rescue"><a href="#ms-text-autospace-to-the-rescue" class="headerlink" title="-ms-text-autospace to the rescue?"></a><code>-ms-text-autospace</code> to the rescue?</h3><p>Microsoft 有个 <a href="http://msdn.microsoft.com/en-us/library/ie/ms531164(v=vs.85).aspx" target="_blank" rel="noopener"><code>-ms-text-autospace</code></a> 的 CSS 属性可以实现自动为中英文之间增加空白。不过目前并未普及,另外在其他应用场景,例如 OS X、iOS 的用户界面目前并不存在这个特性,所以请继续保持随手加空格的习惯。</p><h2 id="标点符号"><a href="#标点符号" class="headerlink" title="标点符号"></a>标点符号</h2><h3 id="不重复使用标点符号"><a href="#不重复使用标点符号" class="headerlink" title="不重复使用标点符号"></a>不重复使用标点符号</h3><p>正确:</p><blockquote><p>德国队竟然战胜了巴西队!</p></blockquote><blockquote><p>她竟然对你说「喵」?!</p></blockquote><p>错误:</p><blockquote><p>德国队竟然战胜了巴西队!!</p></blockquote><blockquote><p>德国队竟然战胜了巴西队!!!!!!!!</p></blockquote><blockquote><p>她竟然对你说「喵」??!!</p></blockquote><blockquote><p>她竟然对你说「喵」?!?!??!!</p></blockquote><h2 id="全角和半角"><a href="#全角和半角" class="headerlink" title="全角和半角"></a>全角和半角</h2><p>不明白什么是全角(全形)与半角(半形)符号?请查看维基百科词条『<a href="http://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2" target="_blank" rel="noopener">全角和半角</a>』。</p><h3 id="使用全角中文标点"><a href="#使用全角中文标点" class="headerlink" title="使用全角中文标点"></a>使用全角中文标点</h3><p>正确:</p><blockquote><p>嗨!你知道嘛?今天前台的小妹跟我说「喵」了哎!</p></blockquote><blockquote><p>核磁共振成像(NMRI)是什么原理都不知道?JFGI!</p></blockquote><p>错误:</p><blockquote><p>嗨! 你知道嘛? 今天前台的小妹跟我说 “喵” 了哎!</p></blockquote><blockquote><p>嗨!你知道嘛?今天前台的小妹跟我说”喵”了哎!</p></blockquote><blockquote><p>核磁共振成像 (NMRI) 是什么原理都不知道? JFGI!</p></blockquote><blockquote><p>核磁共振成像(NMRI)是什么原理都不知道?JFGI!</p></blockquote><h3 id="数字使用半角字符"><a href="#数字使用半角字符" class="headerlink" title="数字使用半角字符"></a>数字使用半角字符</h3><p>正确:</p><blockquote><p>这件蛋糕只卖 1000 元。</p></blockquote><p>错误:</p><blockquote><p>这件蛋糕只卖 1000 元。</p></blockquote><p>例外:在设计稿、宣传海报中如出现极少量数字的情形时,为方便文字对齐,是可以使用全角数字的。</p><h3 id="遇到完整的英文整句、特殊名词,其內容使用半角标点"><a href="#遇到完整的英文整句、特殊名词,其內容使用半角标点" class="headerlink" title="遇到完整的英文整句、特殊名词,其內容使用半角标点"></a>遇到完整的英文整句、特殊名词,其內容使用半角标点</h3><p>正确:</p><blockquote><p>乔布斯那句话是怎么说的?「Stay hungry, stay foolish.」</p></blockquote><blockquote><p>推荐你阅读《Hackers & Painters: Big Ideas from the Computer Age》,非常的有趣。</p></blockquote><p>错误:</p><blockquote><p>乔布斯那句话是怎么说的?「Stay hungry,stay foolish。」</p></blockquote><blockquote><p>推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。</p></blockquote><h2 id="名词"><a href="#名词" class="headerlink" title="名词"></a>名词</h2><h3 id="专有名词使用正确的大小写"><a href="#专有名词使用正确的大小写" class="headerlink" title="专有名词使用正确的大小写"></a>专有名词使用正确的大小写</h3><p>大小写相关用法原属于英文书写范畴,不属于本 wiki 讨论內容,在这里只对部分易错用法进行简述。</p><p>正确:</p><blockquote><p>使用 GitHub 登录</p></blockquote><blockquote><p>我们的客户有 GitHub、Foursquare、Microsoft Corporation、Google、Facebook, Inc.。</p></blockquote><p>错误:</p><blockquote><p>使用 github 登录</p></blockquote><blockquote><p>使用 GITHUB 登录</p></blockquote><blockquote><p>使用 Github 登录</p></blockquote><blockquote><p>使用 gitHub 登录</p></blockquote><blockquote><p>使用 gイんĤЦ8 登录</p></blockquote><blockquote><p>我们的客户有 github、foursquare、microsoft corporation、google、facebook, inc.。</p></blockquote><blockquote><p>我们的客户有 GITHUB、FOURSQUARE、MICROSOFT CORPORATION、GOOGLE、FACEBOOK, INC.。</p></blockquote><blockquote><p>我们的客户有 Github、FourSquare、MicroSoft Corporation、Google、FaceBook, Inc.。</p></blockquote><blockquote><p>我们的客户有 gitHub、fourSquare、microSoft Corporation、google、faceBook, Inc.。</p></blockquote><blockquote><p>我们的客户有 gイんĤЦ8、キouЯƧquムгє、๓เςг๏ร๏Ŧt ς๏гק๏гคtเ๏ภn、900913、ƒ4ᄃëв๏๏к, IПᄃ.。</p></blockquote><p>注意:当网页中需要配合整体视觉风格而出现全部大写/小写的情形,HTML 中请使用标准的大小写规范进行书写;并通过 <code>text-transform: uppercase;</code>/<code>text-transform: lowercase;</code> 对表现形式进行定义。</p><h3 id="不要使用不地道的缩写"><a href="#不要使用不地道的缩写" class="headerlink" title="不要使用不地道的缩写"></a>不要使用不地道的缩写</h3><p>正确:</p><blockquote><p>我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架(如 Backbone.js、AngularJS、React 等)的前端开发者。</p></blockquote><p>错误:</p><blockquote><p>我们需要一位熟悉 Js、h5,至少理解一种框架(如 backbone、angular、RJS 等)的 FED。</p></blockquote><h2 id="争议"><a href="#争议" class="headerlink" title="争议"></a>争议</h2><p>以下用法略带有个人色彩,即:无论是否遵循下述规则,从语法的角度来讲都是<strong>正确</strong>的。</p><h3 id="链接之间增加空格"><a href="#链接之间增加空格" class="headerlink" title="链接之间增加空格"></a>链接之间增加空格</h3><p>用法:</p><blockquote><p>请 <a href="#">提交一个 issue</a> 并分配给相关同事。</p></blockquote><blockquote><p>访问我们网站的最新动态,请 <a href="#">点击这里</a> 进行订阅!</p></blockquote><p>对比用法:</p><blockquote><p>请<a href="#">提交一个 issue</a> 并分配给相关同事。</p></blockquote><blockquote><p>访问我们网站的最新动态,请<a href="#">点击这里</a>进行订阅!</p></blockquote><h3 id="简体中文使用直角引号"><a href="#简体中文使用直角引号" class="headerlink" title="简体中文使用直角引号"></a>简体中文使用直角引号</h3><p>用法:</p><blockquote><p>「老师,『有条不紊』的『紊』是什么意思?」</p></blockquote><p>对比用法:</p><blockquote><p>“老师,‘有条不紊’的‘紊’是什么意思?”</p></blockquote><br /><p>本文属于摘录,更多内容在 <a href="https://github.com/mzlogin/chinese-copywriting-guidelines" target="_blank" rel="noopener">chinese-copywriting-guidelines</a> ~</p>]]></content>
<summary type="html">
<p>今天在看文档的时候,偶然看到这篇关于中文文案排版建议的文章,觉得可以分享出来~</p>
</summary>
<category term="技术" scheme="https://kaviilee.github.io/blog/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="规范" scheme="https://kaviilee.github.io/blog/tags/%E8%A7%84%E8%8C%83/"/>
</entry>
<entry>
<title>Docker 学习笔记</title>
<link href="https://kaviilee.github.io/blog/2020/07/21/docker/"/>
<id>https://kaviilee.github.io/blog/2020/07/21/docker/</id>
<published>2020-07-21T00:03:15.000Z</published>
<updated>2023-01-02T18:31:29.271Z</updated>
<content type="html"><![CDATA[<p>这是在看完狂神的 Docker 视频之后做的笔记~存档用</p><a id="more"></a><h2 id="Docker-概述"><a href="#Docker-概述" class="headerlink" title="Docker 概述"></a>Docker 概述</h2><h3 id="Docekr-为什么会出现"><a href="#Docekr-为什么会出现" class="headerlink" title="Docekr 为什么会出现"></a>Docekr 为什么会出现</h3><p>一款产品有开发和生产两套环境,也就对应两套应用配置,一般就会存在两个问题。</p><p>开发和运维问题:这个项目在我的电脑上是可以运行的!但是伴随版本更新迭代,不同版本环境的兼容,导致服务不可用。</p><p>环境配置问题:环境配置是非常麻烦的,每个机器都要部署环境,费时费力。</p><p>思考:项目是否可以带上环境一起安装打包?把原始环境一模一样地复制过来?</p><p>在服务器上部署十分麻烦,不能跨平台。</p><p>传统开发:开发人员做项目。运维人员做部署</p><p>现在:开发打包部署上线,一起做</p><h3 id="Docker-能干什么?"><a href="#Docker-能干什么?" class="headerlink" title="Docker 能干什么?"></a>Docker 能干什么?</h3><blockquote><p>之前的虚拟机技术,所有的项目都在同一个环境下运行。</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/XKaBgUARTQxqc2b.png" alt=""></p><p>虚拟机的缺点</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">1.占用资源非常多</span><br><span class="line">2.冗余技术多</span><br><span class="line">3.启动很慢</span><br></pre></td></tr></table></figure><blockquote><p>容器化技术</p></blockquote><p><code>容器化技术不是一个完整的操作系统</code></p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/uYhFIpJeADPOQ5r.png" alt="image-2020070202"></p><p>比较 Docker 和虚拟机技术的不同</p><ul><li>传统的虚拟机,虚拟出一套硬件后,在其上运行一个完整的操作系统,在该操作系统上安装和运行软件;</li><li>容器内的应用直接运行在宿主机的内核,容器是没有自己的内核的,也没有进行硬件虚拟,更加轻便;</li><li>每个容器间都是相互隔离的,每个容器都有自己的文件系统,互不影响,能区分计算资源。</li></ul><blockquote><p>DevOps (开发,运维)</p></blockquote><p><strong>更快速的交付和部署</strong></p><p>传统:一堆帮助文档,安装程序</p><p>Docker:一键运行打包镜像发布测试</p><p><strong>更便捷的升级和扩缩容</strong></p><p>使用了 Docker 之后,我们部署应用就像搭积木一样!</p><p>项目打包为一个镜像,扩展,可以在多个服务器上部署。</p><p><strong>更简单的系统运维</strong></p><p>在容器化之后,我们开发,测试环境是高度一致的。</p><p><strong>更高效的计算机资源利用</strong></p><p>Docker 是内核级别的虚拟化,可以在一个物理机上运行很多的容器实例,服务器性能可以被压榨到极致。</p><h2 id="Docker-安装"><a href="#Docker-安装" class="headerlink" title="Docker 安装"></a>Docker 安装</h2><h3 id="Docker-的基本组成"><a href="#Docker-的基本组成" class="headerlink" title="Docker 的基本组成"></a>Docker 的基本组成</h3><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://oscimg.oschina.net/oscnet/6980be9f402ffabf9c044b8170630006086.png" alt="Docker架构图"></p><p><strong>镜像 (image)</strong></p><p>Docker 镜像好比一个模板,可以通过这个模板来创建容器服务 nginx 镜像 => run => nginx01 (提供服务器)。</p><p>通过这个镜像可以创建多个容器(最终服务运行或者项目运行就是在容器中的)。</p><p><strong>容器 (container)</strong></p><p>Docker 利用容器技术,独立运行一个或一组应用,通过镜像来创建</p><p>容器和镜像关系类似于面向对象编程中的对象和类。</p><p><strong>仓库(repository)</strong></p><p>仓库就是存放镜像的地方,仓库分为共有仓库和私有仓库。</p><p>Docker hub(默认国外镜像),阿里云,网易有国内镜像加速服务。</p><h3 id="安装-Docker"><a href="#安装-Docker" class="headerlink" title="安装 Docker"></a>安装 Docker</h3><h4 id="CentOS-Docker-安装"><a href="#CentOS-Docker-安装" class="headerlink" title="CentOS Docker 安装"></a>CentOS Docker 安装</h4><blockquote><p>环境准备</p></blockquote><ol><li>会一点 Linux 基础</li><li>使用的操作系统 CentOS7</li><li>使用 XShell 连接远程服务器</li></ol><blockquote><p>环境查看</p></blockquote><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查看系统内核</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> uname -r</span></span><br><span class="line">3.10.0-1062.18.1.el7.x86_64</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">系统版本</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> cat /etc/os-release</span></span><br><span class="line">NAME="CentOS Linux"</span><br><span class="line">VERSION="7 (Core)"</span><br><span class="line">ID="centos"</span><br><span class="line">ID_LIKE="rhel fedora"</span><br><span class="line">VERSION_ID="7"</span><br><span class="line">PRETTY_NAME="CentOS Linux 7 (Core)"</span><br><span class="line">ANSI_COLOR="0;31"</span><br><span class="line">CPE_NAME="cpe:/o:centos:centos:7"</span><br><span class="line">HOME_URL="https://www.centos.org/"</span><br><span class="line">BUG_REPORT_URL="https://bugs.centos.org/"</span><br><span class="line"></span><br><span class="line">CENTOS_MANTISBT_PROJECT="CentOS-7"</span><br><span class="line">CENTOS_MANTISBT_PROJECT_VERSION="7"</span><br><span class="line">REDHAT_SUPPORT_PRODUCT="centos"</span><br><span class="line">REDHAT_SUPPORT_PRODUCT_VERSION="7"</span><br></pre></td></tr></table></figure><blockquote><p>安装 Docker</p></blockquote><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 一, 卸载旧的版本</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> sudo yum remove docker \</span></span><br><span class="line"> docker-client \</span><br><span class="line"> docker-client-latest \</span><br><span class="line"> docker-common \</span><br><span class="line"> docker-latest \</span><br><span class="line"> docker-latest-logrotate \</span><br><span class="line"> docker-logrotate \</span><br><span class="line"> docker-engine</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> 2, 需要安装的包</span></span><br><span class="line">yum install -y yum-utils \</span><br><span class="line"> device-mapper-persistent-data \</span><br><span class="line"> lvm2</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">3. 设置镜像仓库</span></span><br><span class="line">yum-config-manager \</span><br><span class="line"> --add-repo \</span><br><span class="line"> https://download.docker.com/linux/centos/docker-ce.repo #默认是国外的</span><br><span class="line"></span><br><span class="line">yum-config-manager \</span><br><span class="line"> --add-repo \</span><br><span class="line"> https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #推荐使用</span><br><span class="line"><span class="meta">#</span><span class="bash">更新yum软件包索引</span></span><br><span class="line">yum makecache fast</span><br><span class="line"><span class="meta">#</span><span class="bash">安装 docker </span></span><br><span class="line">sudo yum install docker-ce docker-ce-cli containerd.io </span><br><span class="line"><span class="meta">#</span><span class="bash">启动 docker</span></span><br><span class="line">systemctl start docker</span><br><span class="line"><span class="meta">#</span><span class="bash">查看 docker 版本</span></span><br><span class="line">docker version</span><br></pre></td></tr></table></figure><blockquote><p>下载镜像</p><p>docker pull [要下载的镜像]</p></blockquote><blockquote><p>查看下载的镜像</p><p>docker images (docker image ls)</p></blockquote><p>卸载 docker</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">yum remove docker-ce docker-ce-cli containerd.io</span><br><span class="line"></span><br><span class="line">rm -rf /var/lib/docker #docker 默认工作路径</span><br></pre></td></tr></table></figure><h4 id="Windows-Docker-安装(win10)"><a href="#Windows-Docker-安装(win10)" class="headerlink" title="Windows Docker 安装(win10)"></a>Windows Docker 安装(win10)</h4><p>下载官方 Docker-desktop 安装程序</p><blockquote><p><a href="https://www.docker.com/products/docker-desktop" target="_blank" rel="noopener">https://www.docker.com/products/docker-desktop</a></p></blockquote><p>开启 Hyper-V</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 开启搜索</span></span><br><span class="line">win + s</span><br><span class="line"><span class="meta">#</span><span class="bash"> 输入</span></span><br><span class="line">启用或关闭windows功能</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/kEfpcwAuH7BGl45.png" alt=""></p><p>选中 Hyper-V</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/8pywI4dJSsPqVXh.png" alt=""></p><h5 id="配置镜像加速"><a href="#配置镜像加速" class="headerlink" title="配置镜像加速"></a>配置镜像加速</h5><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/1QNDC4Ug2wVuIW8.png" alt="image-20200720114221052"></p><h3 id="底层原理"><a href="#底层原理" class="headerlink" title="底层原理"></a>底层原理</h3><p><strong>Docker 是怎么工作的</strong></p><p>Docker 是一个 C/S 结构的系统,Docker 的守护进程运行在主机上,通过 Socket 从客户端访问</p><p>DockerServer 接收到 Docker-Client 的指令,就会执行这个命令</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/qTZ1yHfGixmYtU2.png" alt="Docker底层"></p><p>Docker 为什么比 VM 快?</p><ol><li>Docker 有着比虚拟机更少的抽象层</li><li>Dcoker 利用的是宿主机的内核,VM 需要的是 Guest OS</li></ol><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="http://hongyitong.github.io/img/docker.png" alt="Docker与虚拟机"></p><p>新建一个容器的时候,Docker 不需要像虚拟机一样重新安装一个操作系统内核,虚拟机是加载 Guest OS,分钟级别的,而 Docker 是利用宿主机的操作系统,省略了这个复杂的过程</p><hr><h2 id="Docker-命令"><a href="#Docker-命令" class="headerlink" title="Docker 命令"></a>Docker 命令</h2><h3 id="帮助命令"><a href="#帮助命令" class="headerlink" title="帮助命令"></a>帮助命令</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">docker version #docker版本</span><br><span class="line">docker info #显示docker的系统信息,包括镜像和容器数量</span><br><span class="line">docker [命令] --help #查看某个具体的命令</span><br></pre></td></tr></table></figure><h3 id="镜像命令"><a href="#镜像命令" class="headerlink" title="镜像命令"></a>镜像命令</h3><p><strong>docker images</strong> 查看下载的所有镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> docker images</span></span><br><span class="line">REPOSITORY TAG IMAGE ID CREATED SIZE</span><br><span class="line">mysql 5.6 8de95e6026c3 20 hours ago 302MB</span><br><span class="line">redis latest 36304d3b4540 12 days ago 104MB</span><br><span class="line">mysql latest 30f937e841c8 2 weeks ago 541MB</span><br><span class="line">centos/mysql-57-centos7 latest f83a2938370c 8 months ago 452MB</span><br><span class="line"><span class="meta">#</span><span class="bash"> 解释</span></span><br><span class="line">REPOSITORY 镜像的仓库名</span><br><span class="line">TAG 镜像的标签</span><br><span class="line">IMAGE ID 镜像ID</span><br><span class="line">CREATED 镜像创建时间</span><br><span class="line">SIZE 镜像的大小</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">可选项</span></span><br><span class="line">Options:</span><br><span class="line"> -a, --all #列出所有镜像</span><br><span class="line"> -q, --quiet #只显示镜像ID</span><br></pre></td></tr></table></figure><p><strong>docker search</strong> 搜索镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker search mysql</span><br><span class="line">NAMEDESCRIPTIONSTARSOFFICIALAUTOMATED</span><br><span class="line">mysqlMySQL is a widely used, open-source relation… 9604 [OK] </span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">可选项,通过收藏来过滤</span></span><br><span class="line">--filter=stars=3000 #搜索出来的镜像收藏就是大于3000的</span><br></pre></td></tr></table></figure><p><strong>docker pull</strong> 拉取镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker pull nginx [:tag]</span><br><span class="line">Using default tag: latest #如果不写tag 默认使用最新版本</span><br><span class="line">latest: Pulling from library/nginx</span><br><span class="line">8559a31e96f4: Pull complete #分层下载,docker image核心 联合文件系统</span><br><span class="line">8d69e59170f7: Pull complete </span><br><span class="line">3f9f1ec1d262: Pull complete </span><br><span class="line">d1f5ff4f210d: Pull complete </span><br><span class="line">1e22bfa8652e: Pull complete </span><br><span class="line">Digest: sha256:21f32f6c08406306d822a0e6e8b7dc81f53f336570e852e25fbe1e3e3d0d0133 #签名</span><br><span class="line">Status: Downloaded newer image for nginx:latest</span><br><span class="line">docker.io/library/nginx:latest #真实地址</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> docker pull nginx 等价于 dicker pull docker.io/library/nginx:latest</span></span><br></pre></td></tr></table></figure><p><strong>docker rmi</strong> 删除镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 删除指定的镜像</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rmi -f 8de95e6026c3 </span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 删除全部的镜像</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rmi -f $(docker images -ap)</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 清空临时镜像</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rmi $(docker images -q -f dangling=<span class="literal">true</span>)</span></span><br></pre></td></tr></table></figure><p><strong>docker build</strong> 使用dockerfile创建镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 使用当前目录的 dockerfile 创建镜像 当 dockerfile 的命名为 dockerfile 就不需要制定文件名 -f</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker build -t node:10.15-alpine .</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker build -f /path/to/a/dockerfile . <span class="comment"># /path/to/a</span></span></span><br></pre></td></tr></table></figure><h3 id="容器命令"><a href="#容器命令" class="headerlink" title="容器命令"></a>容器命令</h3><p><strong>新建容器并启动</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker run [options] image</span><br><span class="line"><span class="meta">#</span><span class="bash"> options</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 若image本地没有则会去 docker镜像库拉取</span></span><br><span class="line">--name="" 容器名字 用于区分容器</span><br><span class="line">-d 后台方式运行</span><br><span class="line">-it 使用交互方式运行,进入容器查看内容</span><br><span class="line">-p 指定容器的端口 -p 80:8080 主机端口:容器端口</span><br><span class="line">-P(大写) 随机指定端口</span><br></pre></td></tr></table></figure><p><strong>列出所有运行的容器</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> docker ps 命令 列出当前正在运行的容器</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> options</span></span><br><span class="line">-a # 列出当前正在运行的容器+历史运行过的容器</span><br><span class="line">-n=? # 显示最近创建的容器</span><br><span class="line">-q # 只显示容器的编号</span><br><span class="line"><span class="meta">$</span><span class="bash"> docker ps </span></span><br><span class="line">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span><br><span class="line"><span class="meta">$</span><span class="bash"> docker ps -a </span></span><br><span class="line">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span><br><span class="line">919e58ff5521 redis "Docker-entrypoint.s…" 20 hours ago Exited (0) 16 hours ago redis</span><br></pre></td></tr></table></figure><p><strong>退出容器</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">exit #直接容器停止并退出</span><br></pre></td></tr></table></figure><p><strong>删除容器</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker rm 容器id</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rm -f $(docker ps -aq) <span class="comment">#删除所有的容器</span></span></span><br></pre></td></tr></table></figure><p><strong>启动和停止容器</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker start 容器id or 容器name <span class="comment"># 启动一个或多个已经被停止的容器</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker restart 容器id or 容器name <span class="comment"># 重启容器</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker stop 容器id or 容器name <span class="comment"># 停止运行中的容器</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">kill</span> 容器id or 容器name <span class="comment"># 杀掉运行中的容器</span></span></span><br></pre></td></tr></table></figure><h3 id="其他常用命令"><a href="#其他常用命令" class="headerlink" title="其他常用命令"></a>其他常用命令</h3><p><strong>后台启动容器</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker run -d 镜像名</span></span><br></pre></td></tr></table></figure><p><strong>查看容器中进程信息</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker top 容器id</span></span><br></pre></td></tr></table></figure><p><strong>查看镜像元数据</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker inspect 容器id or 容器name</span></span><br></pre></td></tr></table></figure><p><strong>进入当前正在运行的容器</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">我们通常容器都是使用后台方式运行的,需要进入容器,修改一些配置</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">命令</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 进入容器后开启一个新的终端,可以在里面操作(常用) 退出 shell 不会导致容器停止运行</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it 容器id or name bashshell 默认命令行</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 进入容器正在执行的终端,不会启动新的进程 如果退出 shell,容器会停止运行</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker attach 容器id or 容器name</span></span><br></pre></td></tr></table></figure><p><strong>从容器内拷贝文件到主机上</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker cp 容器id: 容器内路径 目的主机路径</span></span><br></pre></td></tr></table></figure><p><strong>docker system命令</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查看docker磁盘占用情况</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker system df</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 命令可以用于清理磁盘,删除关闭的容器、无用的数据卷和网络</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker system prune</span></span><br><span class="line">-a # 没有容器使用的 docker 容器都删除</span><br></pre></td></tr></table></figure><p><strong>手动清除 docker 镜像/容器/数据卷</strong></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 删除所有 dangling 镜像(即无 tag 的镜像)</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rmi $(docker images | grep <span class="string">"^<none>"</span> | awk <span class="string">"{print <span class="variable">$3</span>}"</span>)</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 删除所有 dangling 数据卷(即无用的 volume)</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker volume rm $(docker volume ls -qf dangling=<span class="literal">true</span>)</span></span><br></pre></td></tr></table></figure><h2 id="Docker-镜像"><a href="#Docker-镜像" class="headerlink" title="Docker 镜像"></a>Docker 镜像</h2><h3 id="镜像是什么"><a href="#镜像是什么" class="headerlink" title="镜像是什么"></a>镜像是什么</h3><p>镜像就是一个轻量级的,可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码,运行时,库,环境变量和配置文件。</p><p><strong>如何得到镜像</strong></p><ul><li>从远处仓库下载</li><li>拷贝</li><li>自己制作一个镜像 dockerfile</li></ul><h3 id="docker-镜像加载原理"><a href="#docker-镜像加载原理" class="headerlink" title="docker 镜像加载原理"></a>docker 镜像加载原理</h3><blockquote><p>UnionFs (联合文件系统查询)</p></blockquote><p>我们下载的时候看到的一层一层就是这个</p><p>UnionFs (联合文件系统): Union 文件系统(UnionFS)是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下, Union 文件系统是 Docker 镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像</p><p>特性: 一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录结构</p><blockquote><p>Docker 镜像加载原理</p></blockquote><p>Docker 的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS</p><p>bootfs(boot file system)主要包含 bootlloader 和 kernel, bootfs 主要是引导加载 kernel, Linux 刚启动时会加载 bootfs 文件系统,在 Docker 镜像的最底层是 bootfs, 这一层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加载器和内核,当 boot 加载完成之后整个内核就在内存中了,此时内存的使用权已由 bootfa 转交给内核,此时系统也会卸载 bootfs</p><p>rootfs(root file system),在 bootfs 之上,包含的就是典型 Linux 系统中的 /dev, /proc,/bin, /etc 等标准目录和文件, rootfs 就是各种不同的操作系统发行版,比如 Ubuntu, CentOS 等等</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9XbElrc3Y1RVVKa2hrdUU3MEZSeGhZRllsSkVpYkl5bmFzOWUxU1BFMDBwNEtsR0RjTUdiN1hwOGliS2hKVXVmdFl1OXNwMmljWmgwSjBsbm11ZVhTWTZWQS82NDA?x-oss-process=image/format,png" alt="镜像分层"></p><h3 id="分层理解"><a href="#分层理解" class="headerlink" title="分层理解"></a>分层理解</h3><p>镜像下载的时候是一层一层的在下载</p><p>思考: 为什么 Docker 镜像要采用这种分层的结构呢?</p><p>最大好处,我觉得莫过于资源共享了!比如有多个镜像都从相同的 Base 镜像构建而来,那么宿主机</p><p>只需在磁盘上保留一份 Base 镜像,同时内存中也只需要加载一份 Base 镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享</p><p>查看镜像分层的方式可以通过 <strong>Docker image inspec</strong>t 命令!</p><h3 id="commit-镜像"><a href="#commit-镜像" class="headerlink" title="commit 镜像"></a>commit 镜像</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker commit 提交容器成为一个新的镜像</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">命令和git原理类似</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker commit -m=<span class="string">"提交的描述信息"</span> -a=<span class="string">"作者"</span> 容器ID 目标镜像名:[tag]</span></span><br></pre></td></tr></table></figure><h2 id="容器数据卷"><a href="#容器数据卷" class="headerlink" title="容器数据卷"></a>容器数据卷</h2><h3 id="什么是容器数据卷"><a href="#什么是容器数据卷" class="headerlink" title="什么是容器数据卷"></a>什么是容器数据卷</h3><p><strong>Docker 的理念回顾</strong></p><p>将应用和环境都打包成一个镜像!</p><p>如果数据都在容器中,那么我们容器删除,数据就会丢失!<strong><mark> 需求: 数据可以持久化 </mark></strong></p><p>MySQL,容器删了,数据丢失. <strong><mark> 需求:MySQL 数据可以存储到本地 </mark></strong></p><p>容器之间可以有一个数据共享的技术!Docker 容器中产生的数据,同步到本地</p><p>目录的挂载,将容器内的目录挂载到 Linux 上面</p><p><strong>总结一句话: 容器的持久化和同步操作! 容器间也可以数据共享的!</strong></p><h3 id="使用数据卷"><a href="#使用数据卷" class="headerlink" title="使用数据卷"></a>使用数据卷</h3><blockquote><p>方法一:直接使用命令来挂载 -v</p></blockquote><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker run -it -v 主机目录:容器内目录 -p 主机端口:容器端口</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 启动起来我们可以使用 docker inspect 容器id</span></span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://static01.imgkr.com/temp/d5a266188ba1427596296c38e950f1fc.png" alt="容器信息"></p><h3 id="实战-安装-MySQL"><a href="#实战-安装-MySQL" class="headerlink" title="实战:安装 MySQL"></a>实战:安装 MySQL</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 获取镜像</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker pull mysql:5.7</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 运行容器,需要做数据挂载! <span class="comment"># 安装启动mysql,需要配置密码,这是官方的</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 官方测试: docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=密码 -d mysql:tag</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">启mysql</span></span><br><span class="line">-d 后台运行</span><br><span class="line">-p 端口映射</span><br><span class="line">-v 端口映射</span><br><span class="line">-e 环境配置</span><br><span class="line">--name 容器名</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 启动成功之后,我们在本地使用navicat来测试连接</span></span><br><span class="line"><span class="meta">#</span><span class="bash">navicat-连接到服务器的3310 --- 3310和容器内的3306映射,这个时候我们就可以连接上了</span></span><br></pre></td></tr></table></figure><p>假设我们将容器删除,挂载到本地 的数据卷依旧没有丢失,这就实现了容器数据持久化功能</p><h3 id="具名和匿名挂载"><a href="#具名和匿名挂载" class="headerlink" title="具名和匿名挂载"></a>具名和匿名挂载</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 匿名挂载</span></span><br><span class="line">-v 容器内路径</span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -p 8080:80 --name nginx01 -v /etc/nginx nginx:alpine</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看所有的volume情况</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker volume ls</span></span><br><span class="line">DRIVER VOLUME NAME</span><br><span class="line">local b448950f96ca2daed2a90cd21e687431653dc9a2f40ccf51e0ce38432f6564a4</span><br><span class="line"><span class="meta">#</span><span class="bash"> 这个就是匿名挂载,-v时只写了容器内路径,没有写</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 通过 -v 卷名:容器内路径</span></span><br></pre></td></tr></table></figure><p>所有的 Docker 容器内的卷,没有指定目录的情况下都是在 /var/lib/Docker/volumes/ 卷名 /_data</p><p>我们通过具名挂载可以方便的找到一个卷,大多数情况在使用的<code>具名挂载</code></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 如何确定是具名挂载还是匿名挂载还是指定路径挂载</span></span><br><span class="line">-v 容器内路径 # 匿名挂载</span><br><span class="line">-v 卷名:容器内路径 # 具名挂载</span><br></pre></td></tr></table></figure><p>拓展:</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">通过 -v 容器内路径: ro rw 改变读写权限</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> ro <span class="built_in">read</span> only</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="built_in">read</span> and write</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">一旦设置了容器权限,容器对挂载出来的内容就有限定了!</span></span><br><span class="line">docker -run -P -name nginx01 -v /etc/nginx:ro nginx</span><br><span class="line">docker -run -P -name nginx01 -v /etc/nginx:rw nginx</span><br><span class="line">ro : 只要看到ro就说明这个路径只能通过宿主机来改变,容器内部无法操作</span><br></pre></td></tr></table></figure><h2 id="dockerfile"><a href="#dockerfile" class="headerlink" title="dockerfile"></a>dockerfile</h2><p>dockerfile 是用来构建 docker 镜像的文件,命令参数脚本。</p><p>构建步骤:</p><ol><li>编写一个 dockerfile 文件</li><li>docker build 构建成为一个镜像</li><li>docker run 镜像</li><li>docker push 发布镜像(dockerHub,阿里云镜像仓库)</li></ol><p>很多官方镜像都是基础包,很多功能都是没有的,我们通常自己创建自己的镜像。</p><h3 id="Dockerfile的构建过程"><a href="#Dockerfile的构建过程" class="headerlink" title="Dockerfile的构建过程"></a>Dockerfile的构建过程</h3><p><strong>基础知识</strong>:</p><ol><li>每个保留关键字(指令)都必须是大写字母</li><li>执行从上到下顺序执行</li><li>#表示注释</li><li>每一条命令都会创建提交一个镜像层,并提交</li></ol><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/B6Kh2SznJNAdG7P.png" alt="enter description here"></p><p>Dockerfile 是面向开发的,我们以后要发布项目,做镜像,就需要编写 Dockerfile 文件,这个文件十分简单。</p><p>Docker 镜像逐渐成为企业交付的标准,必须要掌握</p><p>步骤: 开发,部署,运维</p><p>Dockerfile:构建文件,定义了一切步骤,源代码</p><p>DockerImages:通过 Dockerfile 构建生成的镜像,最终发布和运行的产品</p><p>Docker容器:容器就是镜像运行起来提供服务器</p><h3 id="Dockerfile的指令"><a href="#Dockerfile的指令" class="headerlink" title="Dockerfile的指令"></a>Dockerfile的指令</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">FROM # 基础镜像, 一切从这里开始构建</span><br><span class="line">MANTAINER # 镜像是谁写的, 姓名+邮箱</span><br><span class="line">RUN # 镜像构建的时候需要运行的命令</span><br><span class="line">ADD # 步骤, tomcat镜像,压缩包! 添加内容</span><br><span class="line">WORKDIR # 镜像的工作目录</span><br><span class="line">VOLUME # 挂载的目录</span><br><span class="line">EXPOSE # 暴露端口配置</span><br><span class="line">CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代</span><br><span class="line">ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令</span><br><span class="line">ONBUILD # 当构建一个被继承 DockerFile 这个时候就会运行ONBUILD的指令,触发指令</span><br><span class="line">COPY #类似ADD,将我们文件拷贝到镜像中</span><br><span class="line">ENV # 构建的时候设置环境变量</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://wx1.sbimg.cn/2020/07/06/CXzSa.png" alt="Dockerfile命令"></p><h3 id="实战测试"><a href="#实战测试" class="headerlink" title="实战测试"></a>实战测试</h3><p>docker Hub 中99%镜像都是从 CentOS 基础镜像过来的,然后配置需要的软件</p><blockquote><p>创建一个自己的 CentOS</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 编写 dockerfile 文件</span></span><br><span class="line"><span class="keyword">FROM</span> centos</span><br><span class="line"><span class="keyword">MAINTAINER</span> jiawei<jiaweilee95@<span class="number">126</span>.com></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> MYPATH /usr/local</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> <span class="variable">${MYPATH}</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> yum -y install vim && yum -y install net-tools</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> <span class="built_in">echo</span> <span class="variable">${MYPATH}</span> && <span class="built_in">echo</span> <span class="string">"--end--"</span> && /bin/sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 通过这个文件构建镜像</span></span><br><span class="line">docker build -f <dockerfile文件目录> -t <镜像名:[tag]> .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 测试</span></span><br></pre></td></tr></table></figure><blockquote><p>CMD 和 ENTRYPOINT 的区别</p></blockquote><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代</span><br><span class="line">ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令</span><br></pre></td></tr></table></figure><p>测试 CMD</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 编写 dockerfile 文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vim dockerfile-cmd-test</span></span><br><span class="line"></span><br><span class="line">FROM centos</span><br><span class="line">CMD ["ls", "-a"]</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 构建镜像</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker build -f dockerfile-cmd-test -t centos .</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> run 运行,发现ls -a生效</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run 963149b1ac5d</span></span><br><span class="line">.</span><br><span class="line">..</span><br><span class="line">.dockerenv</span><br><span class="line">bin</span><br><span class="line">dev</span><br><span class="line">etc</span><br><span class="line">home</span><br><span class="line">lib</span><br><span class="line">lib64</span><br><span class="line">lost+found</span><br><span class="line">media</span><br><span class="line">mnt</span><br><span class="line">opt</span><br><span class="line">proc</span><br><span class="line">root</span><br><span class="line">run</span><br><span class="line">sbin</span><br><span class="line">srv</span><br><span class="line">sys</span><br><span class="line">tmp</span><br><span class="line">usr</span><br><span class="line">var</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 想要追加一个命令 -l ls -al</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run 963149b1ac5d -l</span></span><br><span class="line">docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.</span><br><span class="line">ERRO[0000] error waiting for container: context canceled </span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> cmd的情况下 替换了CMD[<span class="string">"ls"</span>,<span class="string">"-a"</span>]命令,-不是命令追加</span></span><br></pre></td></tr></table></figure><p>ENTRYPOINT 是往命令之后追加</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 编写dockerfile文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vim dockerfile-cmd-test</span></span><br><span class="line"></span><br><span class="line">FROM centos</span><br><span class="line">ENTRYPOINT ["ls", "-a"]</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 构建镜像</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker build -f dockerfile-cmd-test -t centos .</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> run 运行,发现ls -a生效</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run 963149b1ac5d</span></span><br><span class="line">.</span><br><span class="line">..</span><br><span class="line">.dockerenv</span><br><span class="line">bin</span><br><span class="line">dev</span><br><span class="line">etc</span><br><span class="line">home</span><br><span class="line">lib</span><br><span class="line">lib64</span><br><span class="line">lost+found</span><br><span class="line">media</span><br><span class="line">mnt</span><br><span class="line">opt</span><br><span class="line">proc</span><br><span class="line">root</span><br><span class="line">run</span><br><span class="line">sbin</span><br><span class="line">srv</span><br><span class="line">sys</span><br><span class="line">tmp</span><br><span class="line">usr</span><br><span class="line">var</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 想要追加一个命令 -l ls -al</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run 963149b1ac5d -l</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 这里是生效的</span></span><br></pre></td></tr></table></figure><h3 id="实战:Tomcat镜像"><a href="#实战:Tomcat镜像" class="headerlink" title="实战:Tomcat镜像"></a>实战:Tomcat镜像</h3><ol><li>准备镜像文件 tomcat 压缩包,jdk 压缩包</li><li><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://img-blog.csdnimg.cn/20200619184436678.png#pic_center" alt=""></li><li>编写 Dockerfile 文件,官方命名 <mark>Dockerfile</mark>,build 会自动寻找这个文件,就不需要 -f 指定文件了</li></ol><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">FROM centos</span><br><span class="line">MAINTAINER czp<[email protected]></span><br><span class="line"></span><br><span class="line">COPY readme.txt /usr/local/readme.txt</span><br><span class="line"></span><br><span class="line">ADD apache-tomcat-9.0.33.tar.gz /usr/local/</span><br><span class="line"></span><br><span class="line">ADD jdk-8u221-linux-x64.rpm /usr/local/</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">RUN yum -y install vim</span><br><span class="line"> </span><br><span class="line">ENV MYPATH /usr/local</span><br><span class="line"> </span><br><span class="line">WORKDIR $MYPATH</span><br><span class="line"> </span><br><span class="line">ENV JAVA_HOME /usr/local/jdk1.8.0_11</span><br><span class="line">ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar</span><br><span class="line"></span><br><span class="line">ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.33</span><br><span class="line"></span><br><span class="line">ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.33</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 配置环境变量</span></span><br><span class="line">ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:/CATALINA_HOME/bin</span><br><span class="line"></span><br><span class="line">EXPOSE 8080</span><br><span class="line"></span><br><span class="line">CMD /usr/local/apache-tomcat-9.0.33/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.33/bin/logs/catalina.out</span><br></pre></td></tr></table></figure><ol start="4"><li><p>构建镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> docker build -t diytomcat .</span></span><br></pre></td></tr></table></figure></li><li><p>本地测试</p><blockquote><p>curl localhost:9090</p></blockquote></li></ol><h3 id="发布镜像"><a href="#发布镜像" class="headerlink" title="发布镜像"></a>发布镜像</h3><blockquote><p>Dockerhub</p></blockquote><ol><li><p>地址 hub.docker.com 注册自己的账号!</p></li><li><p>确定这个账号可以登录</p></li><li><p>在服务器上提交自己的镜像</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker login --<span class="built_in">help</span></span></span><br><span class="line"></span><br><span class="line">Usage: docker login [OPTIONS] [SERVER]</span><br><span class="line"></span><br><span class="line">Log in to a docker registry.</span><br><span class="line">If no server is specified, the default is defined by the daemon.</span><br><span class="line"></span><br><span class="line">Options:</span><br><span class="line"> -p, --password string Password</span><br><span class="line"> --password-stdin Take the password from stdin</span><br><span class="line"> -u, --username string Username</span><br></pre></td></tr></table></figure></li><li><p>登录完毕就可以提交镜像了,就是一步 docker push</p></li></ol><blockquote><p>提交到阿里云镜像仓库</p></blockquote><ol><li><p>登录阿里云</p></li><li><p>找到容器镜像服务</p></li><li><p>创建命名空间</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/jFcRduLDS4wpnsY.png" alt="创建命名空间"></p></li><li><p>创建容器镜像</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/rGJLpDIsCcE49b7.png" alt="创建容器镜像"></p></li><li><p>浏览阿里云</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/08/14/axQFySgmeY3OTCB.png" alt="浏览阿里云"></p></li></ol><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://static.packt-cdn.com/products/9781787120532/graphics/B06157_10_04-1.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://blog.fntsr.tw/wp-content/uploads/2014/12/Docker-Command-Diagram.png" alt=""></p><h2 id="Docker-网络原理"><a href="#Docker-网络原理" class="headerlink" title="Docker 网络原理"></a>Docker 网络原理</h2><h3 id="理解-Docker0"><a href="#理解-Docker0" class="headerlink" title="理解 Docker0"></a>理解 Docker0</h3><p>清空所有环境</p><blockquote><p>测试</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/PHnrqkQRxTziwvt.png" alt="enter description here"></p><p>三个网络</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> docker 是如何处理容器网络访问的?</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -P --name tomcat01 tomcat</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查看容器内部网络地址 ip addr 发现容器启动的时候会得到一个eth0@if8 ip地址,docker分配的</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat01 ip addr</span></span><br><span class="line">1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000</span><br><span class="line"> link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00</span><br><span class="line"> inet 127.0.0.1/8 scope host lo</span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line">2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000</span><br><span class="line"> link/sit 0.0.0.0 brd 0.0.0.0</span><br><span class="line">7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default</span><br><span class="line"> link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0</span><br><span class="line"> inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0</span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line"><span class="meta">#</span><span class="bash"> linux能ping通容器内部</span></span><br></pre></td></tr></table></figure><blockquote><p>原理</p></blockquote><ol><li><p>我们每启动一个 docker 容器,docker 就会给 docker 容器分配一个 ip,我们只要安装了 docker,就会有一个网卡 Docker0</p><p>桥接模式,使用的是 veth-pair 技术</p><p>再次测试 ip addr</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/2esohKzy4MgYbXu.png" alt="enter description here"></p></li><li><p>再启动一个容器,发现又多了一对网卡</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/rFbTA9SH3LfIhDz.png" alt="enter description here"></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 我们发现这个容器带来网卡, 都是一对对的</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> veth-pair 就是一对虚拟机设备接口,他们都是成对出现的,一端连着协议,一端彼此相连</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 正因为有这个特性,veth-pair 充当桥梁,连接各种虚拟网络设备的</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> openStac,Docker容器之间的连接,OVS的连接,都是使用 veth-pair 技术</span></span><br></pre></td></tr></table></figure></li><li><p>测试 tomcat01 和 tomcat02 是否能 ping 通</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 结论:容器和容器之间是可以互相ping通的</span></span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/RTQcMNI1js4gp8q.png" alt="enter description here"></p></li></ol><p>结论: <strong>tomcat01 和 tomcat02 是共用的一个路由器, Docker0</strong></p><p>所有的容器不指定网络的情况下,都是 Docker0 路由的, Docker 会给我们的容器分配一个默认的可用IP</p><blockquote><p>小结</p></blockquote><p>Docker 使用的是 Linux 的桥接,宿主机是一个 Docker 容器的网桥 Docker0</p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/FjIyzRZqDNavBeb.png" alt="enter description here"></p><p>Docker 中所有的网络接口都是虚拟的,虚拟的转发效率高(内网传递文件)</p><p><strong>只要容器删除,对应网桥的一对就没了</strong></p><h3 id="–link"><a href="#–link" class="headerlink" title="–link"></a>–link</h3><blockquote><p>思考一个场景,我们编写了一个微服务,database url = ip;项目不重启,数据库 ip 改变了,我们希望可以处理这个问题,可以通过名字来访问容器吗?</p></blockquote><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat02 ping tomcat01</span></span><br><span class="line">ping: tomcat01: Name or service not known</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 通过--link可以解决网络连接问题</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -P --name tomcat03 --link tomcat02 tomcat</span></span><br><span class="line">6aedb0ba2e798b184f42f98e4a38ce2a54cb97d47b985d17065b064a7f73d404</span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat03 ping tomcat02</span></span><br><span class="line">PING tomcat02 (172.17.0.3) 56(84) bytes of data.</span><br><span class="line">64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.061 ms</span><br><span class="line">64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.040 ms</span><br><span class="line">64 bytes from tomcat02 (172.17.0.3): icmp_seq=3 ttl=64 time=0.040 ms</span><br><span class="line">64 bytes from tomcat02 (172.17.0.3): icmp_seq=4 ttl=64 time=0.082 ms</span><br><span class="line">64 bytes from tomcat02 (172.17.0.3): icmp_seq=5 ttl=64 time=0.039 ms</span><br><span class="line">^C</span><br><span class="line">--- tomcat02 ping statistics ---</span><br><span class="line">5 packets transmitted, 5 received, 0% packet loss, time 134ms</span><br><span class="line">rtt min/avg/max/mdev = 0.039/0.052/0.082/0.018 ms</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 反向是否可以ping通吗</span></span><br><span class="line">[root@CZP ~]# docker exec -it tomcat02 ping tomcat03</span><br></pre></td></tr></table></figure><p>-link <strong>本质就是在 hosts 中添加映射</strong></p><p>我们现在玩 docker 已经不建议使用 –link 了!</p><p>自定义网络,不使用 Docker0!</p><p>Docker0 问题: 它不支持容器名连接访问!</p><h3 id="自定义网络"><a href="#自定义网络" class="headerlink" title="自定义网络"></a>自定义网络</h3><blockquote><p>查看所有的 Docker 网络</p></blockquote><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker network ls</span></span><br><span class="line">NETWORK ID NAME DRIVER SCOPE</span><br><span class="line">86c70406cec4 bridge bridge local</span><br><span class="line">e2cd35c81ffb host host local</span><br><span class="line">c6fe6b78ab62 none null local</span><br></pre></td></tr></table></figure><p><strong>网络模式</strong></p><p>bridge: 桥接模式 docker 搭桥(默认)</p><p>none: 不配置网络</p><p>host:和宿主机共享网络</p><p>container: 容器内网络连通(用得少,局限很大)</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 直接启动的命令 --net brodge,默认docker0</span></span><br><span class="line">docker run -d -P --name tomcat01 --net bridge tomcat</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> docker0的特点: 默认的,域名是不能访问的, --link可以打通连接</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 自定义网络</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> --driver bridge</span></span><br><span class="line">docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet</span><br><span class="line">87d0f163b3a0c857d281bf4e97675d03555486c530969d1cb04950f203133b55</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker network ls</span></span><br><span class="line">NETWORK ID NAME DRIVER SCOPE</span><br><span class="line">86c70406cec4 bridge bridge local</span><br><span class="line">e2cd35c81ffb host host local</span><br><span class="line">87d0f163b3a0 mynet bridge local</span><br><span class="line">c6fe6b78ab62 none null local</span><br></pre></td></tr></table></figure><p>自己的网络创建好了</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> Docker network inspect mynet</span></span><br><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> "Name": "mynet",</span><br><span class="line"> "Id": "87d0f163b3a0c857d281bf4e97675d03555486c530969d1cb04950f203133b55",</span><br><span class="line"> "Created": "2020-07-08T01:56:39.0611734Z",</span><br><span class="line"> "Scope": "local",</span><br><span class="line"> "Driver": "bridge",</span><br><span class="line"> "EnableIPv6": false,</span><br><span class="line"> "IPAM": {</span><br><span class="line"> "Driver": "default",</span><br><span class="line"> "Options": {},</span><br><span class="line"> "Config": [</span><br><span class="line"> {</span><br><span class="line"> "Subnet": "192.168.0.0/16",</span><br><span class="line"> "Gateway": "192.168.0.1"</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> "Internal": false,</span><br><span class="line"> "Attachable": false,</span><br><span class="line"> "Ingress": false,</span><br><span class="line"> "ConfigFrom": {</span><br><span class="line"> "Network": ""</span><br><span class="line"> },</span><br><span class="line"> "ConfigOnly": false,</span><br><span class="line"> "Containers": {},</span><br><span class="line"> "Options": {},</span><br><span class="line"> "Labels": {}</span><br><span class="line"> }</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -P --name tomcat-net-01 --net mynet tomcat</span></span><br><span class="line">f8acd6bd8a21c27ca293d4c2d150448299192bd1f58b41d273d61d24cfe7d9a8</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -P --name tomcat-net-02 --net mynet tomcat</span></span><br><span class="line">84b8b3a4a45c579eb479dfa036bc6e88f2c4ea5a0e8edd0c8f225bddebb2747c</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker network inspect mynet</span></span><br><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> "Name": "mynet",</span><br><span class="line"> "Id": "87d0f163b3a0c857d281bf4e97675d03555486c530969d1cb04950f203133b55",</span><br><span class="line"> "Created": "2020-07-08T01:56:39.0611734Z",</span><br><span class="line"> "Scope": "local",</span><br><span class="line"> "Driver": "bridge",</span><br><span class="line"> "EnableIPv6": false,</span><br><span class="line"> "IPAM": {</span><br><span class="line"> "Driver": "default",</span><br><span class="line"> "Options": {},</span><br><span class="line"> "Config": [</span><br><span class="line"> {</span><br><span class="line"> "Subnet": "192.168.0.0/16",</span><br><span class="line"> "Gateway": "192.168.0.1"</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> "Internal": false,</span><br><span class="line"> "Attachable": false,</span><br><span class="line"> "Ingress": false,</span><br><span class="line"> "ConfigFrom": {</span><br><span class="line"> "Network": ""</span><br><span class="line"> },</span><br><span class="line"> "ConfigOnly": false,</span><br><span class="line"> "Containers": {</span><br><span class="line"> "84b8b3a4a45c579eb479dfa036bc6e88f2c4ea5a0e8edd0c8f225bddebb2747c": {</span><br><span class="line"> "Name": "tomcat-net-02",</span><br><span class="line"> "EndpointID": "889a15d10cf311193a18033af3a75eefa6a074291e84aab65e9d88f4b9889bf2",</span><br><span class="line"> "MacAddress": "02:42:c0:a8:00:03",</span><br><span class="line"> "IPv4Address": "192.168.0.3/16",</span><br><span class="line"> "IPv6Address": ""</span><br><span class="line"> },</span><br><span class="line"> "f8acd6bd8a21c27ca293d4c2d150448299192bd1f58b41d273d61d24cfe7d9a8": {</span><br><span class="line"> "Name": "tomcat-net-01",</span><br><span class="line"> "EndpointID": "810c98a4ee532167410f1bc28acbc1d3aac11390e7c5a0c0864c20832bf06fb6",</span><br><span class="line"> "MacAddress": "02:42:c0:a8:00:02",</span><br><span class="line"> "IPv4Address": "192.168.0.2/16",</span><br><span class="line"> "IPv6Address": ""</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> "Options": {},</span><br><span class="line"> "Labels": {}</span><br><span class="line"> }</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 再次测试ping连接</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat-net-01 ping 192.168.0.3</span></span><br><span class="line">PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.</span><br><span class="line">64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.056 ms</span><br><span class="line">64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.156 ms</span><br><span class="line">64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.086 ms</span><br><span class="line">64 bytes from 192.168.0.3: icmp_seq=4 ttl=64 time=0.037 ms</span><br><span class="line">^C</span><br><span class="line">--- 192.168.0.3 ping statistics ---</span><br><span class="line">4 packets transmitted, 4 received, 0% packet loss, time 162ms</span><br><span class="line">rtt min/avg/max/mdev = 0.037/0.083/0.156/0.046 ms</span><br><span class="line"><span class="meta">#</span><span class="bash"> 现在不使用--link也可以ping容器名字</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat-net-02 ping 192.168.0.2</span></span><br><span class="line">PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.</span><br><span class="line">64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.039 ms</span><br><span class="line">64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.066 ms</span><br><span class="line">^C</span><br><span class="line">--- 192.168.0.2 ping statistics ---</span><br><span class="line">2 packets transmitted, 2 received, 0% packet loss, time 47ms</span><br><span class="line">rtt min/avg/max/mdev = 0.039/0.052/0.066/0.015 ms</span><br></pre></td></tr></table></figure><p>自定义网络 docker 都帮我们维护好了对应关系,推荐平时这样使用网络!</p><p>好处:</p><p>不同的集群使用不同的集群,保证集群之间是安全和健康的</p><h3 id="网络连通"><a href="#网络连通" class="headerlink" title="网络连通"></a>网络连通</h3><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/DKgzSZ2xc4LXoqY.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/Kaviilee/[email protected]/blog/images/loader/imageloading.svg" data-original="https://i.loli.net/2020/07/20/lrGh3epJAs1ntXx.png" alt=""></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">测试打通 tomcat01到tomcat-net-01</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker network connect mynet tomcat01</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 连通之后就是将 tomcat01 放到了mynet网络下</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 一个容器两个ip 阿里云: 公网ip 私网ip</span></span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 01 连通ok</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat01 ping tomcat-net-01</span></span><br><span class="line">PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.</span><br><span class="line">64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.087 ms</span><br><span class="line">64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.065 ms</span><br><span class="line">^C</span><br><span class="line">--- tomcat-net-01 ping statistics ---</span><br><span class="line">2 packets transmitted, 2 received, 0% packet loss, time 71ms</span><br><span class="line">rtt min/avg/max/mdev = 0.065/0.076/0.087/0.011 ms</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 02 依旧是连不通的</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it tomcat02 ping tomcat-net-01</span></span><br><span class="line">ping: tomcat-net-01: Name or service not known</span><br></pre></td></tr></table></figure><p>结论:要跨网络操作别人,就需要使用 <strong>docker network connect</strong> 连通</p>]]></content>
<summary type="html">
<p>这是在看完狂神的 Docker 视频之后做的笔记~存档用</p>
</summary>
<category term="技术" scheme="https://kaviilee.github.io/blog/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Docker" scheme="https://kaviilee.github.io/blog/tags/docker/"/>
</entry>
</feed>