-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
220 lines (126 loc) · 696 KB
/
atom.xml
File metadata and controls
220 lines (126 loc) · 696 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Litgod's Blog</title>
<link href="/atom.xml" rel="self"/>
<link href="https://litgod.net/"/>
<updated>2020-09-08T09:08:49.624Z</updated>
<id>https://litgod.net/</id>
<author>
<name>齐小神</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>「面试必会」中高级前端必会的手写面试题(二)</title>
<link href="https://litgod.net/2020/08/16/api-4/"/>
<id>https://litgod.net/2020/08/16/api-4/</id>
<published>2020-08-16T12:42:13.000Z</published>
<updated>2020-09-08T09:08:49.624Z</updated>
<content type="html"><![CDATA[<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/375312727.jpg" width="800" height="100%"><a id="more"></a><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在面试中,常常会问到一些“手写XXX”的面试题,如果我们只是停留在熟练使用这些 API,问到这种问题想必总是束手无策的。其实想要手写 API 的实现也并不难,更多的是需要我们训练自己通过使用方式来推倒实现的能力,千万不要死记硬背。最近我也在强化自己手写 API 的能力,并汇总了面试中高频的手写 API 面试题,希望对大家有所帮助~</p><blockquote><p>前文回顾: <a href="https://juejin.im/post/6859026583533912072" rel="external nofollow noopener noreferrer" target="_blank">「面试必会」中高级前端必会的手写面试题(一)</a></p></blockquote><h2 id="一、使用ES5实现类的继承"><a href="#一、使用ES5实现类的继承" class="headerlink" title="一、使用ES5实现类的继承"></a>一、使用ES5实现类的继承</h2><h3 id="1-构造函数继承"><a href="#1-构造函数继承" class="headerlink" title="1. 构造函数继承"></a>1. 构造函数继承</h3><ul><li>思路:</li></ul><p>在子类的构造函数中执行父类的构造函数。并为其绑定子类的<code>this</code>,让父类的构造函数把成员的属性和方法都挂在<code>子类的this</code>这样能避免实例之间共享一个原型实例,又能向父类构造函数传参。</p><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><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">Parent</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">this</span>.name = <span class="string">'Cherry'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 父类的原型方法</span></span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 子类</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>)</span>{</span><br><span class="line"> Parent.call(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">this</span>.type = <span class="string">'child'</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">const</span> child = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="built_in">console</span>.log(child); <span class="comment">// Child { name: 'Cherry', type: 'child' }</span></span><br><span class="line"><span class="built_in">console</span>.log(child.getName()); <span class="comment">// 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/api_1.png" alt></p><p>这么看使用构造函数继承的缺点已经很明显了:<strong>继承不到父类原型上的属性和方法</strong>,那么引出下面的方法。</p><h3 id="2-原型链继承"><a href="#2-原型链继承" class="headerlink" title="2. 原型链继承"></a>2. 原型链继承</h3><ul><li>思路:</li></ul><p>让子类的原型指向父类的实例,当子类实例找不到对用的属性和方法时,就会沿着原型链向上找,也就是去父类的实例上找,从而实现对父类属性和方法的继承。</p><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = <span class="string">'Cherry'</span>;</span><br><span class="line"> <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.type = <span class="string">'child'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 子类的原型对象指向父类实例</span></span><br><span class="line">Child.prototype = <span class="keyword">new</span> Parent();</span><br><span class="line"><span class="comment">// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要</span></span><br><span class="line">Child.prototype.constructor = Child;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> child = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="built_in">console</span>.log(child); <span class="comment">// Parent { type: 'child' }</span></span><br><span class="line"><span class="built_in">console</span>.log(child.getName()); <span class="comment">// Cherry</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/api_2.png" alt></p><p>看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的问题:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child();</span><br><span class="line">child1.play.push(<span class="number">4</span>);</span><br><span class="line"><span class="built_in">console</span>.log(child1.play, child2.play); <span class="comment">// [ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/api_3.png" alt></p><p>在上面这个例子中,虽然我只改变了<code>child1</code>的<code>play</code>属性,但是<code>child2</code>的<code>play</code>属性也跟着变了。原因是因为<strong>两个实例引用的是同一个原型对象。</strong></p><p>由此我们可以发现,使用原型链继承有以下两个缺点:</p><ol><li>由于所有Child实例原型都指向同一个Parent实例, 因此对某个Child实例的父类<strong>引用类型</strong>变量修改会影响所有的Child实例</li><li>在创建子类实例时无法向父类构造传参, 即没有实现<code>super()</code>的功能</li></ol><h3 id="3-组合式继承"><a href="#3-组合式继承" class="headerlink" title="3. 组合式继承"></a>3. 组合式继承</h3><p>既然原型链继承和构造函数继承各有互补的优缺点,那么我们为什么不组合起来使用呢,所以就有了综合二者的<strong>组合式继承</strong>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = <span class="string">'Cherry'</span>;</span><br><span class="line"> <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 构造函数继承</span></span><br><span class="line"> Parent.call(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">this</span>.type = <span class="string">'child'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//原型链继承</span></span><br><span class="line">Child.prototype = <span class="keyword">new</span> Parent();</span><br><span class="line"><span class="comment">// 如果不指定 Child.prototype.constructor 为 Child,根据原型链规则会默认向上查找,最后会指向 Parent</span></span><br><span class="line">Child.prototype.constructor = Child;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="built_in">console</span>.log(child1); <span class="comment">// Child { name: 'Cherry', play: [ 1, 2, 3 ], type: 'child' }</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.getName()); <span class="comment">// Cherry</span></span><br><span class="line">child1.play.push(<span class="number">4</span>);</span><br><span class="line"><span class="built_in">console</span>.log(child1.play, child2.play); <span class="comment">// [ 1, 2, 3, 4 ] [ 1, 2, 3 ]</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/api_4.png" alt></p><p>我们通过控制台的输出结果可以看到,之前的问题都得到了解决。但是这里又增加了一个新问题,那就是Parent的构造函数会多执行了一次<code>Child.prototype = new Parent();</code>虽然这并不影响父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅。那么如何解决这个问题?</p><h3 id="4-寄生式组合继承"><a href="#4-寄生式组合继承" class="headerlink" title="4. 寄生式组合继承"></a>4. 寄生式组合继承</h3><p>为了解决构造函数被执行两次的问题, 我们将<code>指向父类实例</code>改为<code>指向父类原型</code>, 减去一次构造函数的执行。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = <span class="string">'Cherry'</span>;</span><br><span class="line"> <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> Parent.call(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">this</span>.type = <span class="string">'child'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 将`指向父类实例`改为`指向父类原型`</span></span><br><span class="line">Child.prototype = Parent.prototype;</span><br><span class="line">Child.prototype.constructor = Child;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="built_in">console</span>.log(child1); <span class="comment">// Child { name: 'Cherry', play: [ 1, 2, 3 ], type: 'child' }</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.getName()); <span class="comment">// Cherry</span></span><br><span class="line">child1.play.push(<span class="number">4</span>);</span><br><span class="line"><span class="built_in">console</span>.log(child1.play, child2.play); <span class="comment">// [ 1, 2, 3, 4 ] [ 1, 2, 3 ]</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/api_5.png" alt></p><p>但这种方式存在一个问题,由于子类原型和父类原型指向同一个对象,我们对子类原型的操作会影响到父类原型,例如给<code>Child.prototype</code>增加一个<code>getName()</code>方法,那么会使<code>Parent.prototype</code>上也增加或被覆盖一个<code>getName()</code>方法,为了解决这个问题,我们会给<code>Parent.prototype</code>做一个浅拷贝。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = <span class="string">'Cherry'</span>;</span><br><span class="line"> <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> Parent.call(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">this</span>.type = <span class="string">'child'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 给Parent.prototype做一个浅拷贝</span></span><br><span class="line">Child.prototype = <span class="built_in">Object</span>.create(Parent.prototype);</span><br><span class="line">Child.prototype.constructor = Child;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child();</span><br><span class="line"><span class="built_in">console</span>.log(child1); <span class="comment">// Child { name: 'Cherry', play: [ 1, 2, 3 ], type: 'child' }</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.getName()); <span class="comment">// Cherry</span></span><br><span class="line">child1.play.push(<span class="number">4</span>);</span><br><span class="line"><span class="built_in">console</span>.log(child1.play, child2.play); <span class="comment">// [ 1, 2, 3, 4 ] [ 1, 2, 3 ]</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/api_6.png" alt></p><p>到这里我们就完成了ES5环境下的继承的实现,这种继承方式称为<code>寄生组合式继承</code>,是目前最成熟的继承方式,babel对ES6继承的转化也是使用了寄生组合式继承。</p><h2 id="二、实现数组扁平化"><a href="#二、实现数组扁平化" class="headerlink" title="二、实现数组扁平化"></a>二、实现数组扁平化</h2><ul><li>概念:</li></ul><p>将一个多维数组变为一维数组:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">1</span>, [<span class="number">2</span>, <span class="number">3</span>, [<span class="number">4</span>, <span class="number">5</span>]]] ------> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br></pre></td></tr></table></figure><h3 id="1-ES6的flat"><a href="#1-ES6的flat" class="headerlink" title="1. ES6的flat()"></a>1. ES6的flat()</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="number">1</span>, [<span class="number">2</span>, <span class="number">3</span>, [<span class="number">4</span>, <span class="number">5</span>]]];</span><br><span class="line">arr.flat(<span class="literal">Infinity</span>);</span><br></pre></td></tr></table></figure><h3 id="2-序列化后正则"><a href="#2-序列化后正则" class="headerlink" title="2. 序列化后正则"></a>2. 序列化后正则</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="number">1</span>, [<span class="number">2</span>, <span class="number">3</span>, [<span class="number">4</span>, <span class="number">5</span>]]];</span><br><span class="line"><span class="keyword">let</span> str = <span class="built_in">JSON</span>.stringify(arr).replace(<span class="regexp">/(\[|\])/g</span>, <span class="string">''</span>);</span><br><span class="line">str = <span class="string">'['</span> + str + <span class="string">']'</span>;</span><br><span class="line"><span class="built_in">JSON</span>.parse(str); <span class="comment">// [1, 2, 3, 4, 5]</span></span><br></pre></td></tr></table></figure><h3 id="3-递归处理"><a href="#3-递归处理" class="headerlink" title="3. 递归处理"></a>3. 递归处理</h3><p>对于树状结构的数据,最直接的处理方式就是递归</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="number">1</span>, [<span class="number">2</span>, <span class="number">3</span>, [<span class="number">4</span>, <span class="number">5</span>]]];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flat</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> result = [];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> arr) {</span><br><span class="line"> item <span class="keyword">instanceof</span> <span class="built_in">Array</span> ? result = result.concat(flat(item)) : result.push(item)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">flat(arr); <span class="comment">// [1, 2, 3, 4, 5]</span></span><br></pre></td></tr></table></figure><h3 id="4-reduce"><a href="#4-reduce" class="headerlink" title="4. reduce"></a>4. reduce</h3><p>遍历数组每一项,若值为数组则递归遍历,否则直接累加。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="number">1</span>, [<span class="number">2</span>, <span class="number">3</span>, [<span class="number">4</span>, <span class="number">5</span>]]];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flat</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> arr.reduce(<span class="function">(<span class="params">prev, current</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> prev.concat(current <span class="keyword">instanceof</span> <span class="built_in">Array</span> ? flat(current) : current)</span><br><span class="line"> }, [])</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">flat(arr); <span class="comment">// [1, 2, 3, 4, 5]</span></span><br></pre></td></tr></table></figure><h3 id="5-迭代-扩展运算符"><a href="#5-迭代-扩展运算符" class="headerlink" title="5. 迭代+扩展运算符"></a>5. 迭代+扩展运算符</h3><p>es6的扩展运算符能将二维数组变为一维</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 每次while都会合并一层的元素,然后arr.some判定数组中是否存在数组,如果存在,继续进入第二次循环进行合并</span></span><br><span class="line"><span class="keyword">let</span> arr = [<span class="number">1</span>, [<span class="number">1</span>,<span class="number">2</span>], [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,[<span class="number">4</span>,<span class="number">4</span>,<span class="number">4</span>]]]</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (arr.some(<span class="built_in">Array</span>.isArray)) {</span><br><span class="line"> arr = [].concat(...arr);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(arr); <span class="comment">// [ 1, 1, 2, 1, 2, 3, 4, 4, 4 ]</span></span><br></pre></td></tr></table></figure><h2 id="三、实现数组去重"><a href="#三、实现数组去重" class="headerlink" title="三、实现数组去重"></a>三、实现数组去重</h2><h3 id="1-使用-filter-方法"><a href="#1-使用-filter-方法" class="headerlink" title="1. 使用 filter 方法"></a>1. 使用 filter 方法</h3><p>filter 方法可以过滤掉不符合条件的元素,并返回一个新数组,任何不符合条件的数组都将不在过滤后的数组中。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> data.filter(<span class="function">(<span class="params">value, index</span>) =></span> data.indexOf(value) === index);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><p>我们还可以通过简单的调整,使用filter方法从数据中检索出重复值</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> data.filter(<span class="function">(<span class="params">value, index</span>) =></span> data.indexOf(value) !== index);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'apple', 'lemon' ]</span></span><br></pre></td></tr></table></figure><h3 id="2-使用-ES6-的-Set"><a href="#2-使用-ES6-的-Set" class="headerlink" title="2.使用 ES6 的 Set"></a>2.使用 ES6 的 Set</h3><p>Set 是 ES6 中的新对象类型,用于创建唯一key的集合。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> [...new <span class="built_in">Set</span>(data)];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><h3 id="3-使用-forEach-方法"><a href="#3-使用-forEach-方法" class="headerlink" title="3. 使用 forEach 方法"></a>3. 使用 forEach 方法</h3><p>forEach 方法可以遍历数组中的元素,如果该元素不在数组中,就将该元素push到数组中。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> unique = [];</span><br><span class="line"> data.forEach(<span class="function"><span class="params">element</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!unique.includes(element)) {</span><br><span class="line"> unique.push(element);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> unique;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><h3 id="4-使用-reduce-方法"><a href="#4-使用-reduce-方法" class="headerlink" title="4.使用 reduce 方法"></a>4.使用 reduce 方法</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> unique = data.reduce(<span class="function"><span class="keyword">function</span> (<span class="params">a, b</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (a.indexOf(b) < <span class="number">0</span>) a.push(b);</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line"> }, []);</span><br><span class="line"> <span class="keyword">return</span> unique;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><p>或者:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> data.reduce(<span class="function">(<span class="params">acc, curr</span>) =></span> acc.includes(curr) ? acc : [...acc, curr], []);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><h3 id="5-在数组原型上添加去重方法"><a href="#5-在数组原型上添加去重方法" class="headerlink" title="5.在数组原型上添加去重方法"></a>5.在数组原型上添加去重方法</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.prototype.unique = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> unique = [];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="keyword">const</span> current = <span class="keyword">this</span>[i];</span><br><span class="line"> <span class="keyword">if</span> (unique.indexOf(current) < <span class="number">0</span>) unique.push(current);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> unique;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(arr.unique()); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><h3 id="6-Array-from-ES6-Set"><a href="#6-Array-from-ES6-Set" class="headerlink" title="6. Array.from + ES6 Set"></a>6. Array.from + ES6 Set</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"banana"</span>, <span class="string">"apple"</span>, <span class="string">"orange"</span>, <span class="string">"lemon"</span>, <span class="string">"apple"</span>, <span class="string">"lemon"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeDuplicates</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Array</span>.from(<span class="keyword">new</span> <span class="built_in">Set</span>(arr))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(removeDuplicates(arr)); <span class="comment">// [ 'banana', 'apple', 'orange', 'lemon' ]</span></span><br></pre></td></tr></table></figure><h3 id="7-从对象数组中删除重复的对象"><a href="#7-从对象数组中删除重复的对象" class="headerlink" title="7.从对象数组中删除重复的对象"></a>7.从对象数组中删除重复的对象</h3><p>有时,我们需要通过属性的名称从对象数据中删除重复的对象,我们可以使用 Map 来实现:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> users = [</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">'susan'</span>, <span class="attr">age</span>: <span class="number">25</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">'cherry'</span>, <span class="attr">age</span>: <span class="number">28</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">'cindy'</span>, <span class="attr">age</span>: <span class="number">27</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">'cherry'</span>, <span class="attr">age</span>: <span class="number">28</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">'susan'</span>, <span class="attr">age</span>: <span class="number">25</span> },</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">uniqueByKey</span>(<span class="params">data, key</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> [</span><br><span class="line"> ...new <span class="built_in">Map</span>(</span><br><span class="line"> data.map(<span class="function"><span class="params">x</span> =></span> [key(x), x])</span><br><span class="line"> ).values()</span><br><span class="line"> ]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(uniqueByKey(users, item => item.id));</span><br><span class="line"></span><br><span class="line"><span class="comment">// [ { id: 1, name: 'susan', age: 25 },</span></span><br><span class="line"><span class="comment">// { id: 2, name: 'cherry', age: 28 },</span></span><br><span class="line"><span class="comment">// { id: 3, name: 'cindy', age: 27 } ]</span></span><br></pre></td></tr></table></figure><p>或者用reduce实现:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> users = [</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">'susan'</span>, <span class="attr">age</span>: <span class="number">25</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">'cherry'</span>, <span class="attr">age</span>: <span class="number">28</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">'cindy'</span>, <span class="attr">age</span>: <span class="number">27</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">'cherry'</span>, <span class="attr">age</span>: <span class="number">28</span> },</span><br><span class="line"> { <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">'susan'</span>, <span class="attr">age</span>: <span class="number">25</span> },</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">uniqueByKey</span>(<span class="params">data, key</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> object = {};</span><br><span class="line"> data = data.reduce(<span class="function">(<span class="params">prev, next</span>) =></span> {</span><br><span class="line"> <span class="comment">// eslint-disable-next-line no-unused-expressions</span></span><br><span class="line"> object[next[key]]</span><br><span class="line"> ? <span class="string">''</span></span><br><span class="line"> : (object[next[key]] = <span class="literal">true</span> && prev.push(next));</span><br><span class="line"> <span class="keyword">return</span> prev;</span><br><span class="line"> }, []);</span><br><span class="line"> <span class="keyword">return</span> data;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(uniqueByKey(users, <span class="string">"id"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// [ { id: 1, name: 'susan', age: 25 },</span></span><br><span class="line"><span class="comment">// { id: 2, name: 'cherry', age: 28 },</span></span><br><span class="line"><span class="comment">// { id: 3, name: 'cindy', age: 27 } ]</span></span><br></pre></td></tr></table></figure><h2 id="四、实现数组的取交集,并集,差集"><a href="#四、实现数组的取交集,并集,差集" class="headerlink" title="四、实现数组的取交集,并集,差集"></a>四、实现数组的取交集,并集,差集</h2><h3 id="1-取交集"><a href="#1-取交集" class="headerlink" title="1. 取交集"></a>1. 取交集</h3><p><strong>Array.prototype.includes</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> intersection = a.filter(<span class="function"><span class="params">v</span> =></span> b.includes(v));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(intersection); <span class="comment">// [ 2 ]</span></span><br></pre></td></tr></table></figure><p><strong>Array.from</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">let</span> aSet = <span class="keyword">new</span> <span class="built_in">Set</span>(a);</span><br><span class="line"><span class="keyword">let</span> bSet = <span class="keyword">new</span> <span class="built_in">Set</span>(b);</span><br><span class="line"><span class="keyword">let</span> intersection = <span class="built_in">Array</span>.from(<span class="keyword">new</span> <span class="built_in">Set</span>(a.filter(<span class="function"><span class="params">v</span> =></span> bSet.has(v))));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(intersection); <span class="comment">// [ 2 ]</span></span><br></pre></td></tr></table></figure><p><strong>Array.prototype.indexOf</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">let</span> intersection = a.filter(<span class="function">(<span class="params">v</span>) =></span> b.indexOf(v) > <span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(intersection); <span class="comment">// [ 2 ]</span></span><br></pre></td></tr></table></figure><h3 id="2-取并集"><a href="#2-取并集" class="headerlink" title="2. 取并集"></a>2. 取并集</h3><p><strong>Array.prototype.includes</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> union = a.concat(b.filter(<span class="function"><span class="params">v</span> =></span> !a.includes(v)));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(union); <span class="comment">// [ 1, 2, 3, 4, 5 ]</span></span><br></pre></td></tr></table></figure><p><strong>Array.from</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">let</span> aSet = <span class="keyword">new</span> <span class="built_in">Set</span>(a);</span><br><span class="line"><span class="keyword">let</span> bSet = <span class="keyword">new</span> <span class="built_in">Set</span>(b);</span><br><span class="line"><span class="keyword">let</span> union = <span class="built_in">Array</span>.from(<span class="keyword">new</span> <span class="built_in">Set</span>(a.concat(b)));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(union); <span class="comment">// [ 1, 2, 3, 4, 5 ]</span></span><br></pre></td></tr></table></figure><p><strong>Array.prototype.indexOf</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">let</span> union = a.concat(b.filter(<span class="function">(<span class="params">v</span>) =></span> a.indexOf(v) === <span class="number">-1</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(union); <span class="comment">// [ 1, 2, 3, 4, 5 ]</span></span><br></pre></td></tr></table></figure><h3 id="3-取差集"><a href="#3-取差集" class="headerlink" title="3. 取差集"></a>3. 取差集</h3><p><strong>Array.prototype.includes</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> difference = a.concat(b).filter(<span class="function"><span class="params">v</span> =></span> !a.includes(v) || !b.includes(v));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(difference); <span class="comment">// [ 1, 3, 4, 5 ]</span></span><br></pre></td></tr></table></figure><p><strong>Array.from</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">let</span> aSet = <span class="keyword">new</span> <span class="built_in">Set</span>(a);</span><br><span class="line"><span class="keyword">let</span> bSet = <span class="keyword">new</span> <span class="built_in">Set</span>(b);</span><br><span class="line"><span class="keyword">let</span> difference = <span class="built_in">Array</span>.from(<span class="keyword">new</span> <span class="built_in">Set</span>(a.concat(b).filter(<span class="function"><span class="params">v</span> =></span> !aSet.has(v) || !bSet.has(v))));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(difference); <span class="comment">// [ 1, 3, 4, 5 ]</span></span><br></pre></td></tr></table></figure><p><strong>Array.prototype.indexOf</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">let</span> b = [<span class="number">2</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">let</span> difference = a.filter(<span class="function">(<span class="params">v</span>) =></span> b.indexOf(v) === <span class="number">-1</span>).concat(b.filter(<span class="function">(<span class="params">v</span>) =></span> a.indexOf(v) === <span class="number">-1</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(difference); <span class="comment">// [ 1, 3, 4, 5 ]</span></span><br></pre></td></tr></table></figure><h2 id="五、实现发布订阅模式"><a href="#五、实现发布订阅模式" class="headerlink" title="五、实现发布订阅模式"></a>五、实现发布订阅模式</h2><blockquote><p><strong>发布订阅模式</strong> 一共分为两个部分:<code>on</code>、<code>emit</code>。<strong>发布和订阅之间没有依赖关系,发布者告诉第三方(事件频道)发生了改变,第三方再通知订阅者发生了改变。</strong></p></blockquote><ul><li><p>on:就是把一些函数维护到数组中</p></li><li><p>emit:让数组中的方法依次执行</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</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="keyword">let</span> event = {</span><br><span class="line"> arr: [],</span><br><span class="line"> on(fn) {</span><br><span class="line"> <span class="keyword">this</span>.arr.push(fn);</span><br><span class="line"> },</span><br><span class="line"> emit() {</span><br><span class="line"> <span class="keyword">this</span>.arr.forEach(<span class="function"><span class="params">fn</span> =></span> fn());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">event.on(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"读取了一个"</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">event.on(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">Object</span>.keys(school).length === <span class="number">2</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"读取完毕"</span>);</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> school = {};</span><br><span class="line">fs.readFile(<span class="string">'./name.txt'</span>, <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">err, data</span>) </span>{</span><br><span class="line"> school.name = data;</span><br><span class="line"> event.emit();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">fs.readFile(<span class="string">'./age.txt'</span>, <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">err, data</span>) </span>{</span><br><span class="line"> school.age = data;</span><br><span class="line"> event.emit();</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="六、实现观察者模式"><a href="#六、实现观察者模式" class="headerlink" title="六、实现观察者模式"></a>六、实现观察者模式</h2><blockquote><p><strong>观察者模式</strong> 是基于发布订阅模式的,分为<code>观察者</code>和<code>被观察者</code>两部分,需要被观察者先收集观察者,当被观察者的状态改变时通知观察者。<strong>观察者和被观察者之间存在关系,被观察者数据发生变化时直接通知观察者改变。</strong></p></blockquote><p>比如:现在有一家之口,爸爸、妈妈和小宝宝,爸爸妈妈告诉小宝宝你有任何状态变化都要通知我们,当小宝宝饿了的时候,就会通知爸爸妈妈过来处理。这里的小宝宝就是被观察者<code>Subject</code>,爸爸妈妈就是观察者<code>Observer</code>,小宝宝维护了一个观察者队列,当自己有任何状态改变的时候都直接通知队列中的观察者。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Subject</span> </span>{ <span class="comment">// 被观察者:小宝宝</span></span><br><span class="line"> <span class="keyword">constructor</span>(name) {</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.state = <span class="string">"开心的"</span>;</span><br><span class="line"> <span class="keyword">this</span>.observer = [];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> attach(o) {</span><br><span class="line"> <span class="keyword">this</span>.observer.push(o);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> setState(newState) {</span><br><span class="line"> <span class="keyword">this</span>.state = newState;</span><br><span class="line"> <span class="keyword">this</span>.observer.forEach(<span class="function"><span class="params">o</span> =></span> o.update(<span class="keyword">this</span>));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Observer</span> </span>{ <span class="comment">// 观察者:爸爸 妈妈</span></span><br><span class="line"> <span class="keyword">constructor</span>(name) {</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> update(baby) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"当前"</span>+<span class="keyword">this</span>.name+<span class="string">"被通知了,当前小宝宝的状态是:"</span>+baby.state);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 爸爸妈妈需要观察小宝宝的心理变化</span></span><br><span class="line"><span class="keyword">let</span> baby = <span class="keyword">new</span> Subject(<span class="string">"小宝宝"</span>);</span><br><span class="line"><span class="keyword">let</span> father = <span class="keyword">new</span> Observer(<span class="string">"爸爸"</span>);</span><br><span class="line"><span class="keyword">let</span> mother = <span class="keyword">new</span> Observer(<span class="string">"妈妈"</span>);</span><br><span class="line"></span><br><span class="line">baby.attach(father);</span><br><span class="line">baby.attach(mother);</span><br><span class="line">baby.setState(<span class="string">"我饿了"</span>);</span><br></pre></td></tr></table></figure><p>所以,我用下图表示这两个模式最重要的区别:</p><p><img src="/uploads/t_1.png" alt="image"></p><h2 id="七、实现单例模式"><a href="#七、实现单例模式" class="headerlink" title="七、实现单例模式"></a>七、实现单例模式</h2><blockquote><p>单例模式是创建型设计模式的一种。确保全局中有且仅有一个对象实例,并提供一个访问它的全局访问点,如线程池、全局缓存、window 对象等。</p></blockquote><h3 id="1-常规实现:"><a href="#1-常规实现:" class="headerlink" title="1.常规实现:"></a>1.常规实现:</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 单例构造函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">CreateSingleton</span> (<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.getName();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取实例的名字</span></span><br><span class="line">CreateSingleton.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.name)</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 单例对象</span></span><br><span class="line"><span class="keyword">var</span> Singleton = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> instance;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(!instance) {</span><br><span class="line"> instance = <span class="keyword">new</span> CreateSingleton(name);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建实例对象1</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> Singleton(<span class="string">'a'</span>);</span><br><span class="line"><span class="comment">// 创建实例对象2</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> Singleton(<span class="string">'b'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(a === b);</span><br></pre></td></tr></table></figure><h3 id="2-用闭包和Proxy属性拦截实现"><a href="#2-用闭包和Proxy属性拦截实现" class="headerlink" title="2. 用闭包和Proxy属性拦截实现"></a>2. 用闭包和Proxy属性拦截实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> singletonify = <span class="function">(<span class="params">className</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Proxy</span>(className.prototype.constructor, {</span><br><span class="line"> instance: <span class="literal">null</span>,</span><br><span class="line"> construct: <span class="function">(<span class="params">target, argumentsList</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.instance)</span><br><span class="line"> <span class="keyword">this</span>.instance = <span class="keyword">new</span> target(...argumentsList);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.instance;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(msg) {</span><br><span class="line"> <span class="keyword">this</span>.msg = msg;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> printMsg() {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.msg);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">MySingletonClass = singletonify(MyClass);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myObj = <span class="keyword">new</span> MySingletonClass(<span class="string">'first'</span>);</span><br><span class="line">myObj.printMsg(); <span class="comment">// 'first'</span></span><br><span class="line"><span class="keyword">const</span> myObj2 = <span class="keyword">new</span> MySingletonClass(<span class="string">'second'</span>);</span><br><span class="line">myObj2.printMsg(); <span class="comment">// 'first'</span></span><br></pre></td></tr></table></figure><h2 id="八、实现Promise"><a href="#八、实现Promise" class="headerlink" title="八、实现Promise"></a>八、实现Promise</h2><p>重点难点,面试高频考点,具体请参考我的另一篇文章:</p><p><a href="https://juejin.im/post/6850037281206566919" rel="external nofollow noopener noreferrer" target="_blank">面试官:“你能手写一个 Promise 吗”</a></p><h2 id="九、实现深拷贝"><a href="#九、实现深拷贝" class="headerlink" title="九、实现深拷贝"></a>九、实现深拷贝</h2><p>也是面试高频考点,具体请参考我的另一篇文章:</p><p><a href="https://juejin.im/post/6844904195758391310" rel="external nofollow noopener noreferrer" target="_blank">Javascript经典面试之深拷贝VS浅拷贝</a></p><h2 id="十、手写字符串转二进制"><a href="#十、手写字符串转二进制" class="headerlink" title="十、手写字符串转二进制"></a>十、手写字符串转二进制</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">charToBinary</span>(<span class="params">text</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> code = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">of</span> text) {</span><br><span class="line"> <span class="comment">// 字符编码</span></span><br><span class="line"> <span class="keyword">let</span> number = i.charCodeAt().toString(<span class="number">2</span>);</span><br><span class="line"> <span class="comment">// 1 bytes = 8bit,将 number 不足8位的0补上</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> a = <span class="number">0</span>; a <= <span class="number">8</span> - number.length; a++) {</span><br><span class="line"> number = <span class="number">0</span> + number;</span><br><span class="line"> }</span><br><span class="line"> code += number;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> code;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="十一、手写二进制转Base64"><a href="#十一、手写二进制转Base64" class="headerlink" title="十一、手写二进制转Base64"></a>十一、手写二进制转Base64</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将二进制数据每 6bit 位替换成一个 base64 字符</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">binaryTobase64</span>(<span class="params">code</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> base64Code = <span class="string">"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"</span>;</span><br><span class="line"> <span class="keyword">let</span> res = <span class="string">''</span>;</span><br><span class="line"> <span class="comment">// 1 bytes = 8bit,6bit 位替换成一个 base64 字符</span></span><br><span class="line"> <span class="comment">// 所以每 3 bytes 的数据,能成功替换成 4 个 base64 字符</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 对不足 24 bit (也就是 3 bytes) 的情况进行特殊处理</span></span><br><span class="line"> <span class="keyword">if</span> (code.length % <span class="number">24</span> === <span class="number">8</span>) {</span><br><span class="line"> code += <span class="string">'0000'</span>;</span><br><span class="line"> res += <span class="string">'=='</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (code.length % <span class="number">24</span> === <span class="number">16</span>) {</span><br><span class="line"> code += <span class="string">'00'</span>;</span><br><span class="line"> res += <span class="string">'='</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> encode = <span class="string">''</span>;</span><br><span class="line"> <span class="comment">// code 按 6bit 一组,转换为</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < code.length; i += <span class="number">6</span>) {</span><br><span class="line"> <span class="keyword">let</span> item = code.slice(i, i + <span class="number">6</span>);</span><br><span class="line"> encode += base64Code[<span class="built_in">parseInt</span>(item, <span class="number">2</span>)];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> encode + res;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="十二、手写字符转Base64"><a href="#十二、手写字符转Base64" class="headerlink" title="十二、手写字符转Base64"></a>十二、手写字符转Base64</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">base64encode</span>(<span class="params">text</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> base64Code = <span class="string">"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="</span>;</span><br><span class="line"> <span class="keyword">let</span> res = <span class="string">''</span>;</span><br><span class="line"> <span class="keyword">let</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (i < text.length) {</span><br><span class="line"> <span class="keyword">let</span> char1, char2, char3, enc1, enc2, enc3, enc4;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 三个字符一组,转二进制</span></span><br><span class="line"> char1 = text.charCodeAt(i++); </span><br><span class="line"> char2 = text.charCodeAt(i++);</span><br><span class="line"> char3 = text.charCodeAt(i++);</span><br><span class="line"></span><br><span class="line"> enc1 = char1 >> <span class="number">2</span>; <span class="comment">// 取第 1 字节的前 6 位</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 三个一组处理</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">isNaN</span>(char2)) {</span><br><span class="line"> <span class="comment">// 只有 1 字节的时候</span></span><br><span class="line"> enc2 = ((char1 & <span class="number">3</span>) << <span class="number">4</span>) | (<span class="number">0</span> >> <span class="number">4</span>);</span><br><span class="line"> <span class="comment">// 第65个字符用来代替补位的 = 号</span></span><br><span class="line"> enc3 = enc4 = <span class="number">64</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">isNaN</span>(char3)) {</span><br><span class="line"> <span class="comment">// 只有 2 字节的时候</span></span><br><span class="line"> enc2 = ((char1 & <span class="number">3</span>) << <span class="number">4</span>) | (char2 >> <span class="number">4</span>);</span><br><span class="line"> enc3 = ((char2 & <span class="number">15</span>) << <span class="number">2</span>) | (<span class="number">0</span> >> <span class="number">6</span>);</span><br><span class="line"> enc4 = <span class="number">64</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> enc2 = ((char1 & <span class="number">3</span>) << <span class="number">4</span>) | (char2 >> <span class="number">4</span>); <span class="comment">// 取第 1 个字节的后 2 位(3 = 11 << 4 = 110000) + 第 2 个字节的前 4 位</span></span><br><span class="line"> enc3 = ((char2 & <span class="number">15</span>) << <span class="number">2</span>) | (char3 >> <span class="number">6</span>); <span class="comment">// 取第 2 个字节的后 4 位 (15 = 1111 << 2 = 111100) + 第 3 个字节的前 2 位</span></span><br><span class="line"> enc4 = char3 & <span class="number">63</span>; <span class="comment">// 取最后一个字节的最后 6 位 (63 = 111111)</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 转base64</span></span><br><span class="line"> res += base64Code.charAt(enc1) + base64Code.charAt(enc2) + base64Code.charAt(enc3) + base64Code.charAt(enc4)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最优解:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> encodedData = <span class="built_in">window</span>.btoa(<span class="string">"this is a example"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(encodedData); <span class="comment">// dGhpcyBpcyBhIGV4YW1wbGU=</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> decodeData = <span class="built_in">window</span>.atob(encodedData);</span><br><span class="line"><span class="built_in">console</span>.log(decodeData); <span class="comment">// this is a example</span></span><br></pre></td></tr></table></figure><h2 id="十三、实现一个可以拖拽的DIV"><a href="#十三、实现一个可以拖拽的DIV" class="headerlink" title="十三、实现一个可以拖拽的DIV"></a>十三、实现一个可以拖拽的DIV</h2><p>思路:</p><ul><li><p>有一个DIV层,设定position属性为absolute或fixed,通过更改其left,top来更改层的相对位置。</p></li><li><p>在DIV层上绑定mousedown事件,设置一个拖动开始的标志为true,拖动结束的标志为false,本例为isMouseDown。</p></li><li><p>拖动时的细节优化,如:</p><ul><li><p>鼠标于DIV的相对位置</p></li><li><p>拖动时防止文字被选中</p></li><li><p>限定DIV的移动范围,拖动到边界处的处理</p></li><li><p>当鼠标移出窗口时失去焦点的处理</p></li><li><p>当鼠标移动到iframe上的处理</p></li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> injectedHTML = <span class="built_in">document</span>.createElement(<span class="string">"DIV"</span>);</span><br><span class="line"> injectedHTML.innerHTML = <span class="string">'<dragBox id="dragBox" class="drag-box">\</span></span><br><span class="line"><span class="string"> <dragBoxBar id="dragBoxBar" class="no-select"></dragBoxBar>\</span></span><br><span class="line"><span class="string"> <injectedBox id="injectedBox">CONTENT</injectedBox>\</span></span><br><span class="line"><span class="string"> </dragBox>'</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(injectedHTML);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> isMouseDown,</span><br><span class="line"> initX,</span><br><span class="line"> initY,</span><br><span class="line"> height = injectedBox.offsetHeight,</span><br><span class="line"> width = injectedBox.offsetWidth,</span><br><span class="line"> dragBoxBar = <span class="built_in">document</span>.getElementById(<span class="string">'dragBoxBar'</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">dragBoxBar.addEventListener(<span class="string">'mousedown'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> isMouseDown = <span class="literal">true</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.classList.add(<span class="string">'no-select'</span>);</span><br><span class="line"> injectedBox.classList.add(<span class="string">'pointer-events'</span>);</span><br><span class="line"> initX = e.offsetX;</span><br><span class="line"> initY = e.offsetY;</span><br><span class="line"> dragBox.style.opacity = <span class="number">0.5</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">dragBoxBar.addEventListener(<span class="string">'mouseup'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> mouseupHandler();</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'mousemove'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (isMouseDown) {</span><br><span class="line"> <span class="keyword">let</span> cx = e.clientX - initX,</span><br><span class="line"> cy = e.clientY - initY;</span><br><span class="line"> <span class="keyword">if</span> (cx < <span class="number">0</span>) {</span><br><span class="line"> cx = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (cy < <span class="number">0</span>) {</span><br><span class="line"> cy = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">window</span>.innerWidth - e.clientX + initX < width + <span class="number">16</span>) {</span><br><span class="line"> cx = <span class="built_in">window</span>.innerWidth - width;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (e.clientY > <span class="built_in">window</span>.innerHeight - height - dragBoxBar.offsetHeight + initY) {</span><br><span class="line"> cy = <span class="built_in">window</span>.innerHeight - dragBoxBar.offsetHeight - height;</span><br><span class="line"> }</span><br><span class="line"> dragBox.style.left = cx + <span class="string">'px'</span>;</span><br><span class="line"> dragBox.style.top = cy + <span class="string">'px'</span>;</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'mouseup'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (e.clientY > <span class="built_in">window</span>.innerWidth || e.clientY < <span class="number">0</span> || e.clientX < <span class="number">0</span> || e.clientX > <span class="built_in">window</span>.innerHeight) {</span><br><span class="line"> mouseupHandler();</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mouseupHandler</span>(<span class="params"></span>) </span>{</span><br><span class="line"> isMouseDown = <span class="literal">false</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.classList.remove(<span class="string">'no-select'</span>);</span><br><span class="line"> injectedBox.classList.remove(<span class="string">'pointer-events'</span>);</span><br><span class="line"> dragBox.style.opacity = <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">* {</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">border</span>: none</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">body</span>,</span><br><span class="line"><span class="selector-tag">html</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.drag-box</span> {</span><br><span class="line"> <span class="attribute">user-select</span>: none;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#f0f0f0</span>;</span><br><span class="line"> <span class="attribute">z-index</span>: <span class="number">2147483647</span>;</span><br><span class="line"> <span class="attribute">position</span>: fixed;</span><br><span class="line"> <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#dragBoxBar</span> {</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: space-between;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#ccc</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">40px</span>;</span><br><span class="line"> <span class="attribute">cursor</span>: move;</span><br><span class="line"> <span class="attribute">user-select</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.no-select</span> {</span><br><span class="line"> <span class="attribute">user-select</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.pointer-events</span> {</span><br><span class="line"> <span class="attribute">pointer-events</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="selector-class">.no-border</span> {</span><br><span class="line"> <span class="attribute">border</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#injectedBox</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">160px</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">2rem</span>;</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#eee</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="十四、实现一个批量请求函数-multiRequest-urls-maxNum"><a href="#十四、实现一个批量请求函数-multiRequest-urls-maxNum" class="headerlink" title="十四、实现一个批量请求函数 multiRequest(urls, maxNum)"></a>十四、实现一个批量请求函数 multiRequest(urls, maxNum)</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loadImg</span>(<span class="params">url</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> img = <span class="keyword">new</span> Image();</span><br><span class="line"> img.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(url, <span class="string">"加载完成"</span>);</span><br><span class="line"> resolve(img);</span><br><span class="line"> };</span><br><span class="line"> img.onerror = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Error at:'</span> + url));</span><br><span class="line"> };</span><br><span class="line"> img.src = url;</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">multiRequest</span>(<span class="params">urls, maxNum</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> firstMaxNum = urls.splice(<span class="number">0</span>, maxNum);</span><br><span class="line"> <span class="keyword">let</span> promises = firstMaxNum.map(<span class="function">(<span class="params">url, index</span>)=></span>{</span><br><span class="line"> <span class="keyword">return</span> loadImg(url).then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="keyword">return</span> index</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line"> <span class="keyword">return</span> urls.reduce(<span class="function">(<span class="params">res, cur</span>)=></span>{</span><br><span class="line"> <span class="keyword">return</span> res.then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Promise</span>.race(promises)</span><br><span class="line"> }).then(<span class="function">(<span class="params">idx</span>)=></span>{</span><br><span class="line"> promises[idx] = loadImg(cur).then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="keyword">return</span> idx</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line"> }, <span class="built_in">Promise</span>.resolve()).then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Promise</span>.all(promises)</span><br><span class="line"> }) </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">multiRequest(urls, <span class="number">4</span>).then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'finish'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="十五、实现一个-sleep-函数"><a href="#十五、实现一个-sleep-函数" class="headerlink" title="十五、实现一个 sleep 函数"></a>十五、实现一个 sleep 函数</h2><p>思路:比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Promise</span></span><br><span class="line"><span class="keyword">const</span> sleep = <span class="function"><span class="params">time</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =></span> setTimeout(resolve,time))</span><br><span class="line">}</span><br><span class="line">sleep(<span class="number">1000</span>).then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">1</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">//Generator</span></span><br><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">sleepGenerator</span>(<span class="params">time</span>) </span>{</span><br><span class="line"> <span class="keyword">yield</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve,reject</span>)</span>{</span><br><span class="line"> setTimeout(resolve,time);</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line">sleepGenerator(<span class="number">1000</span>).next().value.then(<span class="function"><span class="params">()</span>=></span>{<span class="built_in">console</span>.log(<span class="number">1</span>)})</span><br><span class="line"></span><br><span class="line"><span class="comment">//async</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sleep</span>(<span class="params">time</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =></span> setTimeout(resolve,time))</span><br><span class="line">}</span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">output</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> out = <span class="keyword">await</span> sleep(<span class="number">1000</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span> out;</span><br><span class="line">}</span><br><span class="line">output();</span><br><span class="line"></span><br><span class="line"><span class="comment">//ES5</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sleep</span>(<span class="params">callback,time</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">typeof</span> callback === <span class="string">'function'</span>)</span><br><span class="line"> setTimeout(callback,time)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">output</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line">sleep(output,<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="十六、模拟实现一个-localStorage"><a href="#十六、模拟实现一个-localStorage" class="headerlink" title="十六、模拟实现一个 localStorage"></a>十六、模拟实现一个 localStorage</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">'use strict'</span></span><br><span class="line"><span class="keyword">const</span> valuesMap = <span class="keyword">new</span> <span class="built_in">Map</span>()</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LocalStorage</span> </span>{</span><br><span class="line"> getItem (key) {</span><br><span class="line"> <span class="keyword">const</span> stringKey = <span class="built_in">String</span>(key)</span><br><span class="line"> <span class="keyword">if</span> (valuesMap.has(key)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">String</span>(valuesMap.get(stringKey))</span><br><span class="line"> }</span><br><span class="line"> <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"> setItem (key, val) {</span><br><span class="line"> valuesMap.set(<span class="built_in">String</span>(key), <span class="built_in">String</span>(val))</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> removeItem (key) {</span><br><span class="line"> valuesMap.delete(key)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> clear () {</span><br><span class="line"> valuesMap.clear()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> key (i) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">arguments</span>.length === <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."</span>) <span class="comment">// this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">let</span> arr = <span class="built_in">Array</span>.from(valuesMap.keys())</span><br><span class="line"> <span class="keyword">return</span> arr[i]</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">get</span> length () {</span><br><span class="line"> <span class="keyword">return</span> valuesMap.size</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> instance = <span class="keyword">new</span> LocalStorage()</span><br><span class="line"></span><br><span class="line">global.localStorage = <span class="keyword">new</span> <span class="built_in">Proxy</span>(instance, {</span><br><span class="line"> <span class="keyword">set</span>: function (obj, prop, value) {</span><br><span class="line"> <span class="keyword">if</span> (LocalStorage.prototype.hasOwnProperty(prop)) {</span><br><span class="line"> instance[prop] = value</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> instance.setItem(prop, value)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="keyword">get</span>: function (target, name) {</span><br><span class="line"> <span class="keyword">if</span> (LocalStorage.prototype.hasOwnProperty(name)) {</span><br><span class="line"> <span class="keyword">return</span> instance[name]</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (valuesMap.has(name)) {</span><br><span class="line"> <span class="keyword">return</span> instance.getItem(name)</span><br><span class="line"> }</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><a href="https://juejin.im/post/6844904116552990727" rel="external nofollow noopener noreferrer" target="_blank">2万字 | 前端基础拾遗90问</a><br><a href="https://blog.csdn.net/twoByte/article/details/73269653" rel="external nofollow noopener noreferrer" target="_blank">可拖动DIV层的实现方法</a><br><a href="https://github.com/Advanced-Frontend/Daily-Interview-Question" rel="external nofollow noopener noreferrer" target="_blank">高级前端面试题汇总</a><br><a href="https://levelup.gitconnected.com/7-ways-to-remove-duplicates-from-array-in-javascript-cea4144caf31" rel="external nofollow noopener noreferrer" target="_blank">7 ways to remove duplicates from an array in JavaScript</a><br><a href="https://www.30secondsofcode.org/blog/s/javascript-singleton-proxy" rel="external nofollow noopener noreferrer" target="_blank">How can I implement a singleton in JavaScript</a></p>]]></content>
<summary type="html">
<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/375312727.jpg" width="800" height="100%">
</summary>
</entry>
<entry>
<title>「面试必会」中高级前端必会的手写面试题(一)</title>
<link href="https://litgod.net/2020/08/04/api-2/"/>
<id>https://litgod.net/2020/08/04/api-2/</id>
<published>2020-08-04T05:40:57.000Z</published>
<updated>2020-08-25T08:19:20.917Z</updated>
<content type="html"><![CDATA[<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/375538043.jpg" width="800" height="100%"><a id="more"></a><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在面试中,常常会问到一些“手写XXX”的面试题,如果我们只是停留在熟练使用这些 API,问到这种问题想必总是束手无策的。其实想要手写 API 的实现也并不难,更多的是需要我们训练自己<strong>通过使用方式来推倒实现</strong>的能力,千万不要死记硬背。最近我也在强化自己手写 API 的能力,并汇总了面试中高频的手写 API 面试题,希望对大家有一丢丢帮助~</p><h3 id="一、实现call-apply"><a href="#一、实现call-apply" class="headerlink" title="一、实现call/apply"></a>一、实现call/apply</h3><ul><li>特点:</li></ul><ol><li>可以改变当前函数 this 的指向</li><li>让当前函数执行</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f1</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f2</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 让 f1 的 this 指向 f2,并且让 f1 执行</span></span><br><span class="line">f1.call(f2); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果多个 call,会让 call 方法执行,并把 call 中的 this 指向改变成 fn2</span></span><br><span class="line">f1.call.call.call(f2);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.call = <span class="function"><span class="keyword">function</span> (<span class="params">context</span>) </span>{</span><br><span class="line"> <span class="comment">// 如果 context 存在,使用 context,如果 context 不存在,使用 window;如果 context 是普通类型,转成对象。</span></span><br><span class="line"> context = context ? <span class="built_in">Object</span>(context) : <span class="built_in">window</span>;</span><br><span class="line"> context.fn = <span class="keyword">this</span>; <span class="comment">// this指向调用call的对象,即我们要改变this指向的函数</span></span><br><span class="line"> <span class="keyword">let</span> args = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">1</span>; i < <span class="built_in">arguments</span>.length; i++) {</span><br><span class="line"> args.push(<span class="string">'arguments['</span>+i+<span class="string">']'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> result = <span class="built_in">eval</span>(<span class="string">'context.fn('</span>+args+<span class="string">')'</span>); <span class="comment">// 字符串拼接参数让 fn 执行 </span></span><br><span class="line"> <span class="keyword">delete</span> context.fn; <span class="comment">// 删除我们声明的fn属性</span></span><br><span class="line"> <span class="keyword">return</span> result; <span class="comment">// 返回函数执行结果</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">Function</span>.prototype.apply = <span class="function"><span class="keyword">function</span> (<span class="params">context, args</span>) </span>{</span><br><span class="line"> <span class="comment">// 如果 context 存在,使用 context,如果 context 不存在,使用 window;如果 context 是普通类型,转成对象。</span></span><br><span class="line"> context = context ? <span class="built_in">Object</span>(context) : <span class="built_in">window</span>;</span><br><span class="line"> context.fn = <span class="keyword">this</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(!args){</span><br><span class="line"> <span class="keyword">return</span> context.fn();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> result = <span class="built_in">eval</span>(<span class="string">'context.fn('</span>+args+<span class="string">')'</span>);</span><br><span class="line"> <span class="keyword">delete</span> context.fn;</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="二、实现bind方法"><a href="#二、实现bind方法" class="headerlink" title="二、实现bind方法"></a>二、实现bind方法</h3><ul><li>特点:</li></ul><ol><li>bind 方法可以绑定 this 指向</li><li>bind 方法返回一个绑定后的函数</li><li>如果绑定的函数被 new,当前函数的 this 就是当前的实例</li><li>new 出来的实例要保证原函数的原型对象上的属性不能丢失</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用法一:</span></span><br><span class="line"><span class="keyword">let</span> person = {</span><br><span class="line"> name: <span class="string">"Cherry"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">name, age</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.name+ <span class="string">'养了一只'</span>+ name + <span class="string">'今年'</span> + age + <span class="string">'了'</span>); <span class="comment">// Cherry养了一只猫今年2了</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> bindFn = fn.bind(person, <span class="string">'猫'</span>);</span><br><span class="line"></span><br><span class="line">bindFn(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用法二:</span></span><br><span class="line"><span class="keyword">let</span> person = {</span><br><span class="line"> name: <span class="string">"Cherry"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">name, age</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.say = <span class="string">'说话'</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>); <span class="comment">// fn {say: "说话"}</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> bindFn = fn.bind(person, <span class="string">'猫'</span>);</span><br><span class="line"><span class="keyword">let</span> instance = <span class="keyword">new</span> bindFn(<span class="number">9</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用法三:</span></span><br><span class="line"><span class="keyword">let</span> person = {</span><br><span class="line"> name: <span class="string">"Cherry"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">name, age</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.say = <span class="string">'说话'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn.prototype.flag = <span class="string">'哺乳类'</span>;</span><br><span class="line"><span class="keyword">let</span> bindFn = fn.bind(person, <span class="string">'猫'</span>);</span><br><span class="line"><span class="keyword">let</span> instance = <span class="keyword">new</span> bindFn(<span class="number">9</span>);</span><br><span class="line"><span class="built_in">console</span>.log(instance.flag);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Funcition.protoType.bind = <span class="function"><span class="keyword">function</span> (<span class="params">context</span>) </span>{</span><br><span class="line"> <span class="comment">// this表示调用bind的函数</span></span><br><span class="line"> <span class="keyword">let</span> that = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">let</span> bindArgs = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">arguments</span>, <span class="number">1</span>); <span class="comment">//["猫"]</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">Fn</span>(<span class="params"></span>) </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">fBound</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> args = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">arguments</span>); <span class="comment">//[9] </span></span><br><span class="line"> <span class="comment">//this instanceof fBound为true表示构造函数的情况。如new bindFn(9);</span></span><br><span class="line"> <span class="keyword">return</span> that.apply(<span class="keyword">this</span> <span class="keyword">instanceof</span> fBound ? <span class="keyword">this</span> : context, bindArgs.concat(args));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fn.prototype = <span class="keyword">this</span>.prototype;</span><br><span class="line"> fBound.prototype = <span class="keyword">new</span> Fn();</span><br><span class="line"> <span class="keyword">return</span> fBound;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="三、实现new关键字"><a href="#三、实现new关键字" class="headerlink" title="三、实现new关键字"></a>三、实现new关键字</h3><ul><li>特点: </li></ul><ol><li>创建一个全新的对象,这个对象的<strong>proto</strong>要指向构造函数的原型对象</li><li>执行构造函数</li><li>返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Animal</span>(<span class="params">type</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.type = type; <span class="comment">// 实例上的属性</span></span><br><span class="line"> <span class="comment">// 如果当前构造函数返回的是一个引用类型,需要直接返回这个对象</span></span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">name</span>: <span class="string">'dog'</span>}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Animal.prototype.say = <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">'say'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> animal = <span class="keyword">new</span> Animal(<span class="string">'哺乳类'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(animal.type); <span class="comment">// 哺乳类</span></span><br><span class="line">animal.say(); <span class="comment">// say</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mockNew</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// Constructor => animal,剩余的 arguments 就是其他的参数</span></span><br><span class="line"> <span class="keyword">let</span> Constructor = [].shift.call(<span class="built_in">arguments</span>);</span><br><span class="line"> <span class="keyword">let</span> obj = {}; <span class="comment">//返回的结果</span></span><br><span class="line"> obj.__proto__ = Constructor.prototype;</span><br><span class="line"> Constructor.apply(obj, <span class="built_in">arguments</span>);</span><br><span class="line"> <span class="keyword">return</span> r <span class="keyword">instanceof</span> <span class="built_in">Object</span> ? r : obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="四、用ES5实现数组的map方法"><a href="#四、用ES5实现数组的map方法" class="headerlink" title="四、用ES5实现数组的map方法"></a>四、用ES5实现数组的map方法</h3><ul><li>特点:</li></ul><ol><li>循环遍历数组,并返回一个新数组</li><li>回调函数一共接收3个参数,分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」</li></ol><ul><li><p>用法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].map(<span class="function">(<span class="params">item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> item * <span class="number">2</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(array); <span class="comment">// [2, 4, 6]</span></span><br></pre></td></tr></table></figure></li><li><p>实现:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.map = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> arr = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> arr.push(fn(<span class="keyword">this</span>[i], i, <span class="keyword">this</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="五、用ES5实现数组的filter方法"><a href="#五、用ES5实现数组的filter方法" class="headerlink" title="五、用ES5实现数组的filter方法"></a>五、用ES5实现数组的filter方法</h3><ul><li>特点:</li></ul><ol><li>该方法返回一个由通过测试的元素组成的新数组,如果没有通过测试的元素,则返回一个空数组</li><li>回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].filter(<span class="function">(<span class="params">item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> item > <span class="number">2</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(array); <span class="comment">// [3]</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.filter = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> arr = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> fn(<span class="keyword">this</span>[i]) && arr.push(fn(<span class="keyword">this</span>[i], i, <span class="keyword">this</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="六、用ES5实现数组的some方法"><a href="#六、用ES5实现数组的some方法" class="headerlink" title="六、用ES5实现数组的some方法"></a>六、用ES5实现数组的some方法</h3><ul><li>特点:</li></ul><ol><li>在数组中查找元素,如果找到一个符合条件的元素就返回true,如果所有元素都不符合条件就返回 false;</li><li>回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」。</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> flag = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].some(<span class="function">(<span class="params">item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> item > <span class="number">1</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(flag); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.some = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (fn(<span class="keyword">this</span>[i])) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="七、用ES5实现数组的every方法"><a href="#七、用ES5实现数组的every方法" class="headerlink" title="七、用ES5实现数组的every方法"></a>七、用ES5实现数组的every方法</h3><ul><li>特点:</li></ul><ol><li>检测一个数组中的元素是否都能符合条件,都符合条件返回true,有一个不符合则返回 false</li><li>如果收到一个空数组,此方法在任何情况下都会返回 true</li><li>回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> flag = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].every(<span class="function">(<span class="params">item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> item > <span class="number">1</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(flag); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.every = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="keyword">if</span>(!fn(<span class="keyword">this</span>[i])) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="八、用ES5实现数组的find方法"><a href="#八、用ES5实现数组的find方法" class="headerlink" title="八、用ES5实现数组的find方法"></a>八、用ES5实现数组的find方法</h3><ul><li>特点:</li></ul><ol><li>在数组中查找元素,如果找到符合条件的元素就返回这个元素,如果没有符合条件的元素就返回 undefined,且找到后不会继续查找</li><li>回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> item = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].find(<span class="function">(<span class="params">item</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> item > <span class="number">1</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(item); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.find = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (fn(<span class="keyword">this</span>[i])) <span class="keyword">return</span> <span class="keyword">this</span>[i];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="九、用ES5实现数组的forEach方法"><a href="#九、用ES5实现数组的forEach方法" class="headerlink" title="九、用ES5实现数组的forEach方法"></a>九、用ES5实现数组的forEach方法</h3><ul><li>特点:</li></ul><ol><li>循环遍历数组,该方法没有返回值</li><li>回调函数一共接收3个参数,同 map 方法一样。分别是:「正在处理的当前元素的值、正在处理的当前元素的索引、正在遍历的集合对象」</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].forEach(<span class="function">(<span class="params">item, index, array</span>) =></span> {</span><br><span class="line"> <span class="comment">// 1 0 [1, 2, 3]</span></span><br><span class="line"> <span class="comment">// 2 1 [1, 2, 3]</span></span><br><span class="line"> <span class="comment">// 3 2 [1, 2, 3]</span></span><br><span class="line"> <span class="built_in">console</span>.log(item, index, array) </span><br><span class="line">})</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.forEach = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> fn(<span class="keyword">this</span>[i], i, <span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="十、用ES5实现数组的reduce方法"><a href="#十、用ES5实现数组的reduce方法" class="headerlink" title="十、用ES5实现数组的reduce方法"></a>十、用ES5实现数组的reduce方法</h3><ul><li>特点:</li></ul><ol><li>初始值不传时的特殊处理:会默认用数组中的第一个元素</li><li>函数的返回结果会作为下一次循环的 prev</li><li>回调函数一共接收4个参数,分别是「上一次调用回调时返回的值、正在处理的元素、正在处理的元素的索引,正在遍历的集合对象」</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> total = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].reduce(<span class="function">(<span class="params">prev, next, currentIndex, array</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> prev + next;</span><br><span class="line">}, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(total); <span class="comment">// 6</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.reduce = <span class="function"><span class="keyword">function</span>(<span class="params">fn, prev</span>) </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="comment">// 初始值不传时的处理</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> prev === <span class="string">'undefined'</span>) {</span><br><span class="line"> <span class="comment">// 明确回调函数的参数都有哪些</span></span><br><span class="line"> prev = fn(<span class="keyword">this</span>[i], <span class="keyword">this</span>[i+<span class="number">1</span>], i+<span class="number">1</span>, <span class="keyword">this</span>);</span><br><span class="line"> ++i;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> prev = fn(prev, <span class="keyword">this</span>[i], i, <span class="keyword">this</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 函数的返回结果会作为下一次循环的 prev</span></span><br><span class="line"> <span class="keyword">return</span> prev;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="十一、实现instanceof方法"><a href="#十一、实现instanceof方法" class="headerlink" title="十一、实现instanceof方法"></a>十一、实现instanceof方法</h3><ul><li>特点:</li></ul><p>沿着原型链的向上查找,直到找到原型的最顶端,也就是<code>Object.prototype</code>。查找构造函数的 prototype 属性是否出现在某个实例对象的原型链上,如果找到了返回 true,没找到返回 false。</p><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log([] <span class="keyword">instanceof</span> <span class="built_in">Array</span>); <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log([] <span class="keyword">instanceof</span> <span class="built_in">Object</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 相当于:</span></span><br><span class="line"><span class="built_in">console</span>.log([].__proto__ === <span class="built_in">Array</span>.prototype); <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log([].__proto__.__proto__ === <span class="built_in">Object</span>.prototype); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">myInstanceof</span>(<span class="params">left, right</span>) </span>{</span><br><span class="line"> left = left.__proto__;</span><br><span class="line"> <span class="keyword">while</span>(<span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">if</span> (left === <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (left === right.prototype) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> left = left.__proto__;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span></span>{};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> a = <span class="keyword">new</span> A();</span><br><span class="line"><span class="built_in">console</span>.log(myInstanceof(a, A)); <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log(myInstanceof(a, <span class="built_in">Object</span>)); <span class="comment">// true </span></span><br><span class="line"><span class="built_in">console</span>.log(myInstanceof(a, <span class="built_in">Array</span>)); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h3 id="十二、实现Object-create方法-经常考"><a href="#十二、实现Object-create方法-经常考" class="headerlink" title="十二、实现Object.create方法(经常考)"></a>十二、实现Object.create方法(经常考)</h3><ul><li>特点:</li></ul><p>创建一个新对象,使用现有的对象来提供新创建的对象的<strong>proto</strong></p><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> demo = {</span><br><span class="line"> c : <span class="string">'123'</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">let</span> cc = <span class="built_in">Object</span>.create(demo)</span><br><span class="line"><span class="built_in">console</span>.log(cc);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">create</span>(<span class="params">proto</span>) </span>{</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">Fn</span>(<span class="params"></span>) </span>{};</span><br><span class="line"> <span class="comment">// 将Fn的原型指向传入的 proto</span></span><br><span class="line"> Fn.prototype = proto;</span><br><span class="line"> Fn.prototype.constructor = Fn;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Fn();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="十三、实现一个通用的柯里化函数"><a href="#十三、实现一个通用的柯里化函数" class="headerlink" title="十三、实现一个通用的柯里化函数"></a>十三、实现一个通用的柯里化函数</h3><ul><li>特点:<br>柯里化就是将一个函数的功能细化,把接受「多个参数」的函数变换成接受一个「单一参数」的函数,并且返回接受「余下参数」返回结果的一种应用。</li></ul><ol><li>判断传递的参数是否达到执行函数的fn个数</li><li>没有达到的话,继续返回新的函数,将fn函数继续返回并将剩余参数累加</li><li>达到fn参数个数时,将累加后的参数传给fn执行</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">a, b, c, d, e</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> a+b+c+d+e;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = curring(sum)(<span class="number">1</span>,<span class="number">2</span>)(<span class="number">3</span>,<span class="number">4</span>)(<span class="number">5</span>); <span class="comment">// 15</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> curring = <span class="function">(<span class="params">fn, arr = []</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> len = fn.length;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">...args</span>) </span>{</span><br><span class="line"> arr = [...arr, ...args];</span><br><span class="line"> <span class="keyword">if</span> (arr.length < len) {</span><br><span class="line"> <span class="keyword">return</span> curring(fn, arr);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> fn(...arr);</span><br><span class="line"> }</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><ul><li><p>特点:<br>使用<code>call</code>、<code>apply</code>可以让非数组借用一些其他类型的函数,比如,<code>Array.prototype.push.call</code>, <code>Array.prototype.slice.call</code>, <code>uncrrying</code>把这些方法泛化出来,不在只单单的用于数组,更好的语义化。</p></li><li><p>用法:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 利用反柯里化创建检测数据类型的函数</span></span><br><span class="line"><span class="keyword">let</span> checkType = uncurring(<span class="built_in">Object</span>.prototype.toString);</span><br><span class="line"></span><br><span class="line">checkType(<span class="number">1</span>); <span class="comment">// [object Number]</span></span><br><span class="line">checkType(<span class="string">"hello"</span>); <span class="comment">// [object String]</span></span><br><span class="line">checkType(<span class="literal">true</span>); <span class="comment">// [object Boolean]</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.uncurring = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Function</span>.prototype.call.apply(self, <span class="built_in">arguments</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="十五、实现一个简单的节流函数-throttle"><a href="#十五、实现一个简单的节流函数-throttle" class="headerlink" title="十五、实现一个简单的节流函数(throttle)"></a>十五、实现一个简单的节流函数(throttle)</h3><ul><li>特点:</li></ul><p>规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。</p><p>节流重在加锁<code>flag = false</code></p><ul><li><p>应用场景:</p><ul><li>scroll滚动事件,每隔特定描述执行回调函数</li><li>input输入框,每个特定时间发送请求或是展开下拉列表,(防抖也可以)</li></ul></li><li><p>用法:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> throttleFn = throttle(fn, <span class="number">300</span>);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> throttle = <span class="function">(<span class="params">fn, delay = <span class="number">500</span></span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> flag = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function">(<span class="params">...args</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!flag) <span class="keyword">return</span>;</span><br><span class="line"> flag = <span class="literal">false</span>;</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> fn.apply(<span class="keyword">this</span>, args);</span><br><span class="line"> flag = <span class="literal">true</span>;</span><br><span class="line"> }, delay);</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="十六、实现一个简单的防抖函数-debounce"><a href="#十六、实现一个简单的防抖函数-debounce" class="headerlink" title="十六、实现一个简单的防抖函数(debounce)"></a>十六、实现一个简单的防抖函数(debounce)</h3><ul><li>特点:</li></ul><p>在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时</p><p>防抖重在清零<code>clearTimeout(timer)</code></p><ul><li><p>应用场景:</p><ul><li>浏览器窗口大小resize避免次数过于频繁</li><li>登录,发短信等按钮避免发送多次请求</li><li>文本编辑器实时保存</li></ul></li><li><p>用法:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> debounceFn = debounce(fn, <span class="number">300</span>);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> debounce = <span class="function">(<span class="params">fn, delay</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> timer = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function">(<span class="params">...args</span>) =></span> {</span><br><span class="line"> clearTimeout(timer);</span><br><span class="line"> timer = setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> fn.apply(<span class="keyword">this</span>, args);</span><br><span class="line"> }, delay);</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><code>lodash</code>、<code>underscore</code>等库中的节流防抖功能还提供了更多的配置参数,这里我们只是实现了最基本的节流防抖,感兴趣的同学可以看看<code>lodash</code>、<code>underscore</code>的源码。</p><h3 id="十七、实现一个-Compose-组合"><a href="#十七、实现一个-Compose-组合" class="headerlink" title="十七、实现一个 Compose (组合)"></a>十七、实现一个 Compose (组合)</h3><ul><li>特点:</li></ul><p>将需要嵌套执行的函数平铺,嵌套执行就是一个函数的返回值将作为另一个函数的参数。该函数调用的方向是从右至左的(先执行 sum,再执行 toUpper,再执行 add)</p><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">a, b</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> a+b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toUpper</span>(<span class="params">str</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> str.toUpperCase();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">str</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'==='</span>+str+<span class="string">'==='</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 compose 之前:</span></span><br><span class="line"><span class="built_in">console</span>.log(add(toUpper(sum(<span class="string">'cherry'</span>, <span class="string">'27'</span>)))); <span class="comment">// ===CHERRY27===</span></span><br><span class="line"><span class="comment">// 使用 compose 之后:</span></span><br><span class="line"><span class="built_in">console</span>.log(compose(add, toUpper, sum)(<span class="string">'cherry'</span>, <span class="string">'27'</span>)); <span class="comment">// ===CHERRY27===</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 ES5- reduceRight 实现</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">compose</span>(<span class="params">...fns</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">...args</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> lastFn = fns.pop();</span><br><span class="line"> <span class="keyword">return</span> fns.reduceRight(<span class="function">(<span class="params">a, b</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> b(a);</span><br><span class="line"> }, lastFn(...args));</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 ES6 - reduceRight 实现</span></span><br><span class="line"><span class="keyword">const</span> compose = <span class="function">(<span class="params">...fns</span>) =></span> <span class="function">(<span class="params">...args</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> lastFn = fns.pop();</span><br><span class="line"> <span class="keyword">return</span> fns.reduceRight(<span class="function">(<span class="params">a, b</span>) =></span> b(a), lastFn(...args));</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 ES6 - reduce 一行代码实现:</span></span><br><span class="line"><span class="keyword">const</span> compose = <span class="function">(<span class="params">...fns</span>) =></span> fns.reduce(<span class="function">(<span class="params">a, b</span>) =></span> <span class="function">(<span class="params">...args</span>) =></span> a(b(...args)));</span><br></pre></td></tr></table></figure><h3 id="十八、实现一个-Pipe-(管道)"><a href="#十八、实现一个-Pipe-(管道)" class="headerlink" title="十八、实现一个 Pipe (管道)"></a>十八、实现一个 Pipe (管道)</h3><ul><li>特点:</li></ul><p>pipe函数跟compose函数的作用是一样的,也是将参数平铺,只不过他的顺序是从左往右。(先执行 splitString,再执行 count)</p><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">splitString</span>(<span class="params">str</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> str.split(<span class="string">' '</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">count</span>(<span class="params">array</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> array.length;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 pipe 之前:</span></span><br><span class="line"><span class="built_in">console</span>.log(count(splitString(<span class="string">'hello cherry'</span>))); <span class="comment">// 2</span></span><br><span class="line"><span class="comment">// 使用 pipe 之后:</span></span><br><span class="line"><span class="built_in">console</span>.log(pipe(splitString, count)(<span class="string">'hello cherry'</span>)); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pipe = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">const</span> args = [].slice.apply(<span class="built_in">arguments</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> args.reduce(<span class="function">(<span class="params">res, cb</span>) =></span> cb(res), x);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 ES5- reduceRight 实现</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pipe</span>(<span class="params">...fns</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">...args</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> lastFn = fns.shift();</span><br><span class="line"> <span class="keyword">return</span> fns.reduceRight(<span class="function">(<span class="params">a, b</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> b(a);</span><br><span class="line"> }, lastFn(...args));</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 ES6 - reduceRight 实现</span></span><br><span class="line"><span class="keyword">const</span> pipe = <span class="function">(<span class="params">...fns</span>) =></span> <span class="function">(<span class="params">...args</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> lastFn = fns.shift();</span><br><span class="line"> <span class="keyword">return</span> fns.reduceRight(<span class="function">(<span class="params">a, b</span>) =></span> b(a), lastFn(...args));</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 ES6 - reduce 一行代码实现:(redux源码)</span></span><br><span class="line"><span class="keyword">const</span> pipe = <span class="function">(<span class="params">...fns</span>) =></span> <span class="function">(<span class="params">...args</span>) =></span> fns.reduce(<span class="function">(<span class="params">a, b</span>) =></span> b(a), ...args);</span><br></pre></td></tr></table></figure><h3 id="十九、实现一个模版引擎"><a href="#十九、实现一个模版引擎" class="headerlink" title="十九、实现一个模版引擎"></a>十九、实现一个模版引擎</h3><ul><li>特点:with语法 + 字符串拼接 + new Function来实现</li></ul><ol><li>先将字符串中的 <code><%=%></code>替换掉,拼出一个结果的字符串;</li><li>再采用<code>new Function</code>的方式执行该字符串,并且使用<code>with</code>解决作用域的问题。</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ejs = <span class="built_in">require</span>(<span class="string">'ejs'</span>);</span><br><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><br><span class="line">ejs.renderFile(path.resolve(__dirname, <span class="string">'template.html'</span>),{<span class="attr">name</span>: <span class="string">'Cherry'</span>, <span class="attr">age</span>: <span class="number">27</span>, <span class="attr">arr</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]}, <span class="function"><span class="keyword">function</span>(<span class="params">err, data</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">// ===== template.html =====</span><br><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Document<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">%=name%</span>></span> <span class="tag"><<span class="name">%=age%</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">%arr.forEach(item</span> =></span>{%></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span><span class="tag"><<span class="name">%=item%</span>></span><span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">%})%</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><p>我们用<code>{ {} }</code>替换<code><%=%></code>标签来模拟实现一个模版引擎,实现原理是一样的,重点看实现原理哈。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">// ===== my-template.html =====</span><br><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Document<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> {{name}} {{age}}</span><br><span class="line"> {%arr.forEach(item => {%}</span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span>{{item}}<span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> {%})%}</span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> renderFile = <span class="function">(<span class="params">filePath, obj, cb</span>) =></span> {</span><br><span class="line"> fs.readFile(filePath, <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, html</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(err) {</span><br><span class="line"> <span class="keyword">return</span> cb(err, html);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> html = html.replace(<span class="regexp">/\{\{([^}]+)\}\}/g</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">arguments</span>[<span class="number">1</span>], <span class="built_in">arguments</span>[<span class="number">2</span>]);</span><br><span class="line"> <span class="keyword">let</span> key = <span class="built_in">arguments</span>[<span class="number">1</span>].trim();</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'${'</span> + key + <span class="string">'}'</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> head = <span class="string">`let str = '';\r\n with(obj){\r\n`</span>;</span><br><span class="line"> head += <span class="string">'str+=`'</span>;</span><br><span class="line"> html = html.replace(<span class="regexp">/\{\%([^%]+)\%\}/g</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'`\r\n'</span> + <span class="built_in">arguments</span>[<span class="number">1</span>] + <span class="string">'\r\nstr+=`\r\n'</span>;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">let</span> tail = <span class="string">'`}\r\n return str;'</span>;</span><br><span class="line"> <span class="keyword">let</span> fn = <span class="keyword">new</span> <span class="built_in">Function</span>(<span class="string">'obj'</span>, head + html + tail);</span><br><span class="line"> cb(err, fn(obj));</span><br><span class="line"> });</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">renderFile(path.resolve(__dirname, <span class="string">'my-template.html'</span>),{<span class="attr">name</span>: <span class="string">'Cherry'</span>, <span class="attr">age</span>: <span class="number">27</span>, <span class="attr">arr</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]}, <span class="function"><span class="keyword">function</span>(<span class="params">err, data</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>计划输出:<br>中高级前端工程师必会的手写API(二)</p>]]></content>
<summary type="html">
<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/375538043.jpg" width="800" height="100%">
</summary>
<category term="JavaScript" scheme="https://litgod.net/categories/JavaScript/"/>
<category term="JavaScript" scheme="https://litgod.net/tags/JavaScript/"/>
</entry>
<entry>
<title>2020再度重温JS经典面试题,为你金九银十保驾护航(上)</title>
<link href="https://litgod.net/2020/07/20/questions/"/>
<id>https://litgod.net/2020/07/20/questions/</id>
<published>2020-07-20T09:12:03.000Z</published>
<updated>2020-11-02T07:29:46.895Z</updated>
<content type="html"><![CDATA[<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/353632561.jpg" width="800" height="100%"><a id="more"></a><p><img src="/uploads/fe.png" alt="image"></p><h1 id="CSS"><a href="#CSS" class="headerlink" title="CSS"></a>CSS</h1><h2 id="1-说说CSS选择器以及这些选择器的优先级"><a href="#1-说说CSS选择器以及这些选择器的优先级" class="headerlink" title="1.说说CSS选择器以及这些选择器的优先级"></a>1.说说CSS选择器以及这些选择器的优先级</h2><blockquote><p><code>!important</code> > 内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器 > 通配符选择器 > 继承 > 浏览器默认属性</p></blockquote><p>选择器的优先级:</p><ul><li><code>!important</code></li><li>内联样式(<code>style='xxx'</code>)(1000)</li><li>ID选择器(<code>#example</code>)(0100)</li><li>类选择器(<code>.example</code>)/属性选择器(<code>[type="radio"]</code>)/伪类选择器(<code>:hover</code>)(0010)</li><li>标签选择器(<code>h1</code>)/伪元素选择器(<code>::before</code>)(0001)</li><li>通配符选择器(<code>*</code>)/关系选择器(<code>+</code>,<code>></code>,<code>~</code>,<code>' '</code>,<code>||</code>)/否定伪类(<code>:not()</code>)(0000)</li></ul><p><strong>注</strong>:通配选择器(<code>*</code>)、组合选择器(<code>+</code>,<code>></code>,<code>~</code>,<code>' '</code>,<code>||</code>)和否定伪类(<code>:not()</code>)不会影响优先级(但是,<code>:not()</code>内部声明的选择器会影响优先级)。</p><p>优先级是由<code>A</code>、<code>B</code>、<code>C</code>、<code>D</code>的值来决定的,其中它们的值计算规则如下:</p><ol><li>如果存在内联样式,那么<code>A = 1,</code>否则<code>A = 0</code>;</li><li><code>B</code> 的值等于 <code>ID选择器</code> 出现的次数;</li><li><code>C</code> 的值等于 <code>类选择器</code> 和 <code>属性选择器</code> 和 <code>伪类</code> 出现的总次数;</li><li><code>D</code> 的值等于 <code>标签选择器</code> 和 <code>伪元素</code> 出现的总次数 。</li></ol><p>这么很抽象,那么来个例子你就懂了:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#nav-global > ul > li > a.nav-link</span><br></pre></td></tr></table></figure><p>套用上面的算法,依次求出 <code>A</code> <code>B</code> <code>C</code> <code>D</code> 的值:</p><ol><li>因为没有内联样式 ,所以 <code>A = 0;</code></li><li>ID选择器总共出现了1次, <code>B = 1</code>;<br>类选择器出现了1次, 属性选择器出现了0次,伪类选择器出现0次,所以 <code>C = (1 + 0 + 0) = 1</code>;<br>标签选择器出现了3次, 伪元素出现了0次,所以 <code>D = (3 + 0) = 3</code>;</li></ol><p>上面算出的<code>A</code>、 <code>B</code>、<code>C</code>、<code>D</code> 可以简记作:<code>(0, 1, 1, 3)</code>。</p><p>访问<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity" rel="external nofollow noopener noreferrer" target="_blank">MDN</a>来了解更多关于优先级的详细信息。</p><h2 id="2-伪类和伪元素的区别"><a href="#2-伪类和伪元素的区别" class="headerlink" title="2. 伪类和伪元素的区别"></a>2. 伪类和伪元素的区别</h2><p><strong>伪元素</strong>:主要是用来创建一些不存在原有dom结构树种的元素,例如:用::before和::after在一些存在的元素前后添加文字样式等,这些被添加的内容会以具体的UI显示出来,被用户所看到的,这些内容不会改变文档的内容,不会出现在DOM中,不可复制,仅仅是在CSS渲染层加入。CSS3中建议使用::表示伪元素,如:div::before。</p><p><strong>::before和::after这两个伪类下有特有的属性content,必须有这个属性。</strong></p><p><strong>伪类</strong>:表示已存在的某个元素处于某种状态,但是通过dom树又无法表示这种状态,就可以通过伪类来为其添加样式。例如a元素的:hover, :active等。CSS3中建议使用:表示伪元素,如:a:hover。</p><h2 id="3-你知道什么是BFC么?"><a href="#3-你知道什么是BFC么?" class="headerlink" title="3. 你知道什么是BFC么?"></a>3. 你知道什么是BFC么?</h2><p>3-1 什么是BFC<br><strong>块级格式化上下文</strong>,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。</p><blockquote><p>IE下为 Layout,可通过 zoom:1 触发<br>3-2 触发BFC的条件</p></blockquote><ul><li>根元素</li><li><code>float</code>元素</li><li><code>position: absolute/fixed</code></li><li><code>display: inline-block / table</code></li><li>ovevflow !== visible</li><li>display: flow-root</li><li>column-span: all</li></ul><p>3-3 BFC的约束规则</p><ul><li>属于同一个 BFC 的两个相邻 Box 垂直排列(可以看作BFC中有一个的常规流)</li><li>属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠</li><li>BFC 中子元素的 margin box 的左边,与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)</li><li>BFC 的区域不会与 float 的元素区域重叠</li><li>计算 BFC 的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算</li><li>文字层不会被浮动层覆盖,环绕于周围</li></ul><p>3-4 BFC可以解决的问题</p><ul><li>阻止<code>margin</code>重叠</li><li>可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)</li><li>自适用两列布局(float + overflow)</li><li>可以阻止元素被浮动元素覆盖</li></ul><h2 id="4-如何实现居中"><a href="#4-如何实现居中" class="headerlink" title="4.如何实现居中"></a>4.如何实现居中</h2><ol><li><p>绝对定位 + margin<br>优缺点:需要父级有具体宽高,且要知道宽高的具体值</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">.parent {</span><br><span class="line"> position: relative;</span><br><span class="line"> height: <span class="number">400</span>px;</span><br><span class="line"> width: <span class="number">100</span>%;</span><br><span class="line"> border: 1px solid #000;</span><br><span class="line">}</span><br><span class="line">.child {</span><br><span class="line"> position: absolute;</span><br><span class="line"> top: <span class="number">50</span>%;</span><br><span class="line"> left: <span class="number">50</span>%;</span><br><span class="line"> width: <span class="number">100</span>px;</span><br><span class="line"> height: <span class="number">100</span>px;</span><br><span class="line"> margin-left: <span class="number">-50</span>px;</span><br><span class="line"> margin-top: <span class="number">-50</span>px;</span><br><span class="line"> background-color: aquamarine;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>transform<br>优缺点:不需要父级有具体宽高,但是兼容性不是特别好</p></li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">.parent {</span><br><span class="line"> position: relative;</span><br><span class="line"> height: <span class="number">400</span>px;</span><br><span class="line"> width: <span class="number">100</span>%;</span><br><span class="line"> border: 1px solid #000;</span><br><span class="line">}</span><br><span class="line">.child {</span><br><span class="line"> position: absolute;</span><br><span class="line"> top: <span class="number">50</span>%;</span><br><span class="line"> left: <span class="number">50</span>%;</span><br><span class="line"> width: <span class="number">100</span>px;</span><br><span class="line"> height: <span class="number">100</span>px;</span><br><span class="line"> transform: translate(<span class="number">-50</span>%, <span class="number">-50</span>%);</span><br><span class="line"> background-color: aquamarine;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>绝对定位<br>优缺点:需要父级有具体宽高,但是不需要知道宽高的具体值</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">.parent {</span><br><span class="line"> position: relative;</span><br><span class="line"> height: <span class="number">400</span>px;</span><br><span class="line"> width: <span class="number">100</span>%;</span><br><span class="line"> border: 1px solid #000;</span><br><span class="line">}</span><br><span class="line">.child {</span><br><span class="line"> position: absolute;</span><br><span class="line"> top: <span class="number">0</span>;</span><br><span class="line"> left: <span class="number">0</span>;</span><br><span class="line"> bottom: <span class="number">0</span>;</span><br><span class="line"> right: <span class="number">0</span>;</span><br><span class="line"> margin: auto;</span><br><span class="line"> width: <span class="number">100</span>px;</span><br><span class="line"> height: <span class="number">100</span>px;</span><br><span class="line"> background-color: aquamarine;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>4.flex<br>优缺点:更简单了,但是也是兼容性不是特别好</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">.parent {</span><br><span class="line"> display: flex;</span><br><span class="line"> justify-content: center;</span><br><span class="line"> align-items: center;</span><br><span class="line"> height: <span class="number">400</span>px;</span><br><span class="line"> width: <span class="number">100</span>%;</span><br><span class="line"> border: 1px solid #000;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">.child {</span><br><span class="line"> width: <span class="number">100</span>px;</span><br><span class="line"> height: <span class="number">100</span>px;</span><br><span class="line"> background-color: aquamarine;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>5.table-cell<br>优缺点:要求父级有固定宽高,且不能使用百分比</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">.parent {</span><br><span class="line"> display: table-cell;</span><br><span class="line"> vertical-align: middle;</span><br><span class="line"> text-align: center;</span><br><span class="line"> height: <span class="number">400</span>px;</span><br><span class="line"> width: <span class="number">400</span>px;</span><br><span class="line"> border: 1px solid #000;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">.child {</span><br><span class="line"> display: inline-block;</span><br><span class="line"> width: <span class="number">100</span>px;</span><br><span class="line"> height: <span class="number">100</span>px;</span><br><span class="line"> background-color: aquamarine;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>6.javascript</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><script></span><br><span class="line"> <span class="keyword">let</span> html = <span class="built_in">document</span>.documentElement</span><br><span class="line"> winW = html.clientWidth</span><br><span class="line"> winH = html.clientHeight</span><br><span class="line"> boxW = box.offsetWidth</span><br><span class="line"> boxH = box.offsetHeight</span><br><span class="line"> box.style.position = <span class="string">'absolute'</span></span><br><span class="line"> box.style.left = (winW - boxW) / <span class="number">2</span> + <span class="string">'px'</span></span><br><span class="line"> box.style.top = (winH - boxH) / <span class="number">2</span> + <span class="string">'px'</span></span><br><span class="line"><<span class="regexp">/script></span></span><br></pre></td></tr></table></figure><h2 id="5-盒模型"><a href="#5-盒模型" class="headerlink" title="5.盒模型"></a>5.盒模型</h2><p>页面渲染时,dom 元素所采用的 布局模型。可通过<code>box-sizing</code>进行设置。根据计算宽高的区域可分为:</p><ul><li><code>content-box</code> (W3C 标准盒模型)</li><li><code>border-box</code> (IE 盒模型)</li><li><code>padding-box</code> (FireFox 曾经支持)</li><li><code>margin-box</code> (浏览器未实现)</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/450.png" alt="image"></p><p>注:理论上是有上面 4 种盒子,但现在 w3c 与 mdn 规范中均只支持 content-box 与 border-box;</p><h2 id="6-清除浮动的方法"><a href="#6-清除浮动的方法" class="headerlink" title="6. 清除浮动的方法"></a>6. 清除浮动的方法</h2><p>为什么要清除浮动:清除浮动是为了解决子元素浮动而导致父元素高度塌陷的问题</p><p><img src="/uploads/q_16.png" alt="image"></p><ol><li>添加新元素</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"parent"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"child"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="comment"><!-- 添加一个空元素,利用css提供的clear:both清除浮动 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"clear: both"</span>></span><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><ol start="2"><li>使用伪元素</li></ol><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 对父元素添加伪元素 */</span></span><br><span class="line"><span class="selector-class">.parent</span><span class="selector-pseudo">::after</span>{</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">clear</span>:both;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>触发父元素BFC</li></ol><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 触发父元素BFC */</span></span><br><span class="line"><span class="selector-class">.parent</span> {</span><br><span class="line"> <span class="attribute">overflow</span>: hidden;</span><br><span class="line"> <span class="comment">/* float: left; */</span></span><br><span class="line"> <span class="comment">/* position: absolute; */</span></span><br><span class="line"> <span class="comment">/* display: inline-block */</span></span><br><span class="line"> <span class="comment">/* 以上属性均可触发BFC */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-绝对定位、固定定位和-z-index"><a href="#7-绝对定位、固定定位和-z-index" class="headerlink" title="7.绝对定位、固定定位和 z-index"></a>7.绝对定位、固定定位和 z-index</h2><h2 id="8-如何实现左侧宽度固定,右侧宽度自适应的布局"><a href="#8-如何实现左侧宽度固定,右侧宽度自适应的布局" class="headerlink" title="8.如何实现左侧宽度固定,右侧宽度自适应的布局"></a>8.如何实现左侧宽度固定,右侧宽度自适应的布局</h2><h2 id="9-层叠上下文"><a href="#9-层叠上下文" class="headerlink" title="9.层叠上下文"></a>9.层叠上下文</h2><h2 id="10-如何实现圣杯布局和双飞翼布局"><a href="#10-如何实现圣杯布局和双飞翼布局" class="headerlink" title="10.如何实现圣杯布局和双飞翼布局"></a>10.如何实现圣杯布局和双飞翼布局</h2><h1 id="HTML"><a href="#HTML" class="headerlink" title="HTML"></a>HTML</h1><h2 id="1-说说HTML5的新特性"><a href="#1-说说HTML5的新特性" class="headerlink" title="1. 说说HTML5的新特性"></a>1. 说说HTML5的新特性</h2><ul><li><p>新的语义化标签:<code>aside/figure/section/header/footer/nav</code>等,多媒体标签:<code>video/audio</code>,使得样式和结构更加分离;</p></li><li><p>增强表单属性:增强了<code>input</code>的type属性;<code>meta</code>增加了charset以设置字符集,<code>script</code>增加了async以异步加载脚本;</p></li><li><p>新的本地存储:增加<code>localStorage</code>、<code>sessionStorage</code>和<code>indexedDB</code>,引入了<code>application cache</code>对web和应用进行缓存</p></li><li><p>新的API:增加<code>拖放API</code>、<code>地理定位</code>、<code>SVG</code>、<code>Canvas</code>、<code>Web Worker</code>、<code>WebSocket</code>、<code>WebGL</code>、<code>CSS</code>的3D功能</p></li></ul><h2 id="2-doctype的作用"><a href="#2-doctype的作用" class="headerlink" title="2. doctype的作用"></a>2. doctype的作用</h2><p>这个声明的目的是防止浏览器在渲染文档时,切换到我们称为“怪异模式(兼容模式)”的渲染模式。</p><ul><li><p>怪异模式:浏览器使用自己的模式解析文档,不加doctype时默认为怪异模式</p></li><li><p>标准模式:浏览器以W3C的标准解析文档</p></li></ul><h2 id="3-简要说一下几种前端储存之间的区别"><a href="#3-简要说一下几种前端储存之间的区别" class="headerlink" title="3. 简要说一下几种前端储存之间的区别"></a>3. 简要说一下几种前端储存之间的区别</h2><ul><li><p><strong>cookie</strong>: HTML5之前本地储存的主要方式,大小只有4k,HTTP请求头会自动带上cookie,兼容性好</p></li><li><p><strong>localStorage</strong>:HTML5新特性,持久性存储,即使页面关闭也不会被清除,以键值对的方式存储,大小为5M</p></li><li><p><strong>sessionStorage</strong>:HTML5新特性,操作及大小同localStorage,和localStorage的区别在于sessionStorage在选项卡(页面)被关闭时即清除,且不同选项卡之间的sessionStorage不互通</p></li><li><p><strong>IndexedDB</strong>:NoSQL型数据库,类比MongoDB,使用键值对进行储存,异步操作数据库,支持事务,储存空间可以在250MB以上,但是IndexedDB受同源策略限制</p></li><li><p><strong>Web SQL</strong>:是在浏览器上模拟的关系型数据库,开发者可以通过SQL语句来操作Web SQL,是HTML5以外一套独立的规范,兼容性差</p></li></ul><h2 id="4-href和src的区别"><a href="#4-href和src的区别" class="headerlink" title="4. href和src的区别"></a>4. href和src的区别</h2><p><code>href(hyperReference)</code>即超文本引用:当浏览器遇到href时,会并行的地下载资源,不会阻塞页面解析,例如我们使用<code><link></code>引入CSS,浏览器会并行地下载CSS而不阻塞页面解析. 因此我们在引入CSS时建议使用<code><link></code>而不是<code>@import</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><link href=<span class="string">"style.css"</span> rel=<span class="string">"stylesheet"</span> /></span><br></pre></td></tr></table></figure><p><code>src(resource)</code>即资源,当浏览器遇到src时,会暂停页面解析,直到该资源下载或执行完毕,这也是script标签之所以放底部的原因</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><script src=<span class="string">"script.js"</span>><span class="xml"><span class="tag"></<span class="name">script</span>></span></span></span><br></pre></td></tr></table></figure><h2 id="5-meta有哪些属性,作用是什么"><a href="#5-meta有哪些属性,作用是什么" class="headerlink" title="5. meta有哪些属性,作用是什么"></a>5. meta有哪些属性,作用是什么</h2><p>meta标签用于描述网页的<code>元信息</code>,如网站作者、描述、关键词,meta通过<code>name=xxx</code>和<code>content=xxx</code>的形式来定义信息,常用设置如下:</p><h3 id="charset"><a href="#charset" class="headerlink" title="charset"></a>charset</h3><p>定义HTML文档的字符集</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><meta charset=<span class="string">"UTF-8"</span> ></span><br></pre></td></tr></table></figure><h3 id="http-equiv"><a href="#http-equiv" class="headerlink" title="http-equiv"></a>http-equiv</h3><p>可用于模拟http请求头,可设置过期时间、缓存、刷新</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><meta http-equiv=<span class="string">"expires"</span> content=<span class="string">"Wed, 20 Jun 2019 22:33:00 GMT"</span>></span><br></pre></td></tr></table></figure><blockquote><p>参数及其作用:</p></blockquote><ul><li><p><strong>expires</strong>,指定过期时间</p></li><li><p><strong>progma</strong>,设置no-cache可以禁止缓存</p></li><li><p><strong>refresh</strong>,定时刷新</p></li><li><p><strong>set-cookie</strong>,可以设置cookie</p></li><li><p><strong>X-UA-Compatible</strong>,使用浏览器版本</p></li><li><p><strong>apple-mobile-web-app-status-bar-style</strong>,针对WebApp全屏模式,隐藏状态栏/设置状态栏颜色</p></li></ul><h3 id="viewport"><a href="#viewport" class="headerlink" title="viewport"></a>viewport</h3><p>视口,用于控制页面宽高及缩放比例</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><meta </span><br><span class="line"> name=<span class="string">"viewport"</span> </span><br><span class="line"> content=<span class="string">"width=device-width, initial-scale=1, maximum-scale=1"</span></span><br><span class="line">></span><br></pre></td></tr></table></figure><blockquote><p>参数及其作用:</p></blockquote><ul><li><p><strong>width/height</strong>,宽高,默认宽度980px</p></li><li><p><strong>initial-scale</strong>,初始缩放比例,1~10</p></li><li><p><strong>maximum-scale/minimum-scale</strong>,允许用户缩放的最大/小比例</p></li><li><p><strong>user-scalable</strong>,用户是否可以缩放 (yes/no)</p></li></ul><h1 id="Javascript基础"><a href="#Javascript基础" class="headerlink" title="Javascript基础"></a>Javascript基础</h1><h2 id="1-JS-的数据类型及存储方式的区别"><a href="#1-JS-的数据类型及存储方式的区别" class="headerlink" title="1. JS 的数据类型及存储方式的区别"></a>1. JS 的数据类型及存储方式的区别</h2><p>在 ECMAScript 规范中,共定义了 8 种数据类型,分为<strong>基本数据类型</strong>和<strong>引用数据类型</strong>(引用数据类型又称复杂数据类型)两大类:</p><blockquote><p>基本数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6 新增)、BigInt(ES10 新增)<br>引用数据类型:Object(Function、Array、Date、RegExp、Math…都是Object类型的实例对象)</p></blockquote><p>储存方式的区别:</p><blockquote><p>基本数据类型:变量名和值都储存在栈内存中;<br>引用数据类型:变量名储存在<strong>栈内存</strong>中,值储存在<strong>堆内存</strong>中,堆内存中会提供一个引用地址指向堆内存中的值,而这个引用地址是储存在栈内存中的。</p></blockquote><h2 id="2-常用的判断-JS-数据类型的四种方法"><a href="#2-常用的判断-JS-数据类型的四种方法" class="headerlink" title="2.常用的判断 JS 数据类型的四种方法"></a>2.常用的判断 JS 数据类型的四种方法</h2><p>2-1. typeof</p><p>缺点:不能判断 Object 类型,也不能判断 Null 类型。</p><blockquote><p><code>type null</code> 返回结果为 <code>object</code> 是因为 JavaScript 是用 32 位比特来存储值的,且是通过值的低1位或3位来识别类型的,object 的前三位表示是 000,null 为机器码空指针,32位表示全是0,它们俩的前三位一样,所以 <code>typeof null</code> 也会打印出 <code>object</code>。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> <span class="string">""</span>) <span class="comment">// string</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> <span class="number">1</span>) <span class="comment">// number</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> <span class="literal">true</span>) <span class="comment">// boolean</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> <span class="built_in">Symbol</span>()) <span class="comment">// symbol</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</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="keyword">typeof</span> <span class="literal">null</span>) <span class="comment">// object</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> []) <span class="comment">// object</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">typeof</span> {}) <span class="comment">// object</span></span><br></pre></td></tr></table></figure><p>2-2. constructor</p><p><code>constructor</code>可以找到这个变量是通过谁构造出来的。</p><p>缺点:<br>[1]. 不能判断 null 和 undefined,因为 null 和 undefined 是无效的对象,因此是不会有 constructor 存在,这两种类型的数据需要通过其他方式来判断。<br>[2]. 函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log((<span class="string">""</span>).constructor == <span class="built_in">String</span>) <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log((<span class="number">1</span>).constructor == <span class="built_in">Number</span>) <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log((<span class="literal">true</span>).constructor == <span class="built_in">Boolean</span>); <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log([].constructor == <span class="built_in">Array</span>) <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log(({}).constructor == <span class="built_in">Object</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>2-3. instanceof</p><p><code>instanceof</code>用来判断A是否是B的实例,在这里需要特别注意的是:<strong>instanceof 检测的是原型</strong>。</p><p>缺点:instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log([] <span class="keyword">instanceof</span> <span class="built_in">Array</span>) <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log({} <span class="keyword">instanceof</span> <span class="built_in">Object</span>) <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log([] <span class="keyword">instanceof</span> <span class="built_in">Object</span>) <span class="comment">// true</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="keyword">instanceof</span> <span class="built_in">Date</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>2-4. Object.prototype.toString.call()</p><p>通过 <strong>.call</strong> 将传进来的对象作为 Object 原型上的 this,然后通过 toString 方法可以看到具体的类型。</p><p>优缺点:不能细分谁是谁的实例,但是判断这个变量的类型还是非常方便和靠谱的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call()) <span class="comment">// [object Undefined]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="string">""</span>)) <span class="comment">// [object String]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="number">1</span>)) <span class="comment">// [object Number]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="literal">true</span>)) <span class="comment">// [object Boolean]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="built_in">Symbol</span>())) <span class="comment">// [object Symbol]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="literal">null</span>)) <span class="comment">// [object Null]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="keyword">new</span> <span class="built_in">Function</span>())) <span class="comment">// [object Function]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="keyword">new</span> <span class="built_in">Date</span>())) <span class="comment">// [object Date]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call([])) <span class="comment">// [object Array]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="keyword">new</span> <span class="built_in">RegExp</span>())) <span class="comment">// [object RegExp]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="keyword">new</span> <span class="built_in">Error</span>())) <span class="comment">// [object Error]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="built_in">document</span>)) <span class="comment">// [object HTMLDocument]</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.toString.call(<span class="built_in">window</span>)) <span class="comment">// [object Window]</span></span><br></pre></td></tr></table></figure><h2 id="3-作用域、作用域链、执行上下文(EC)、执行上下文栈(ECS)"><a href="#3-作用域、作用域链、执行上下文(EC)、执行上下文栈(ECS)" class="headerlink" title="3. 作用域、作用域链、执行上下文(EC)、执行上下文栈(ECS)"></a>3. 作用域、作用域链、执行上下文(EC)、执行上下文栈(ECS)</h2><h3 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h3><p>函数在定义时会产生两种<code>作用域</code>:分为 全局作用域 和 局部作用域,所以 JS 的作用域是<code>静态作用域</code>。</p><h3 id="执行上下文(EC)"><a href="#执行上下文(EC)" class="headerlink" title="执行上下文(EC)"></a>执行上下文(EC)</h3><p>当 JS 在执行一段代码的时候,会产生<code>执行上下文(EC)</code>;</p><p>「执行上下文」一共有三种类型:全局执行上下文、函数执行上下文、<code>eval</code>执行上下文。</p><p>「执行上下文」包含三个部分:变量对象(VO)、作用域链(词法作用域)、<code>this</code>指向。</p><blockquote><p>活动对象 (AO): 当变量对象所处的上下文为 active EC 时,称为活动对象。</p></blockquote><h3 id="执行上下文栈(ECS)"><a href="#执行上下文栈(ECS)" class="headerlink" title="执行上下文栈(ECS)"></a>执行上下文栈(ECS)</h3><p>在「执行上下文」的形成过程中,会使用「执行上下文栈」来管理执行上下文。</p><p>代码执行的过程:</p><ol><li>创建 全局上下文 (global EC)</li><li>全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层</li><li>函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起</li><li>函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行</li></ol><p>举个例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>{</span><br><span class="line"> b();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>{</span><br><span class="line"> c();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"welcome"</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="comment"></span></span><br><span class="line"><span class="comment">ECS = [</span></span><br><span class="line"><span class="comment"> globalContext</span></span><br><span class="line"><span class="comment">]</span></span><br><span class="line"><span class="comment">ECS.push(functionAContext);</span></span><br><span class="line"><span class="comment">ECS.push(functionBContext);</span></span><br><span class="line"><span class="comment">ECS.push(functionCContext);</span></span><br><span class="line"><span class="comment">ECS.pop();</span></span><br><span class="line"><span class="comment">ECS.pop();</span></span><br><span class="line"><span class="comment">ECS.pop();</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><p>函数内部会保留一个<code>[[scope]]</code>属性,它会保存所有的父级的变量对象。而且在函数执行的时候,会把自身的<code>AO</code>对象加进去,所以执行的时候会先找自己的<code>AO</code>属性,找不到的话会向上查找,这就是作用域链。</p><p>「作用域链」有两部分组成:</p><p>[1]. <code>[[scope]]</code>属性: 指向父级变量对象和作用域链,也就是包含了父级的<code>[[scope]]</code>和<code>AO</code>。<br>[2]. <code>AO</code>: 自身活动对象</p><p>如此 <code>[[scope]]</code>包含<code>[[scope]]</code>,便自上而下形成一条「作用域链」。</p><p>举个例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>{</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">函数声明时:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">a.[[scope]] = [</span></span><br><span class="line"><span class="comment"> globalContext.VO</span></span><br><span class="line"><span class="comment">]</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">b.[[scope]] = [</span></span><br><span class="line"><span class="comment"> aContext.AO,</span></span><br><span class="line"><span class="comment"> globalContext.VO</span></span><br><span class="line"><span class="comment">]</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">c.[[scope]] = [</span></span><br><span class="line"><span class="comment"> bContext.AO,</span></span><br><span class="line"><span class="comment"> aContext.AO,</span></span><br><span class="line"><span class="comment"> globalContext.VO</span></span><br><span class="line"><span class="comment">]</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> b = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">return</span> a+b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">sum();</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">sum.[[scope]] = {</span></span><br><span class="line"><span class="comment"> globalContext.VO</span></span><br><span class="line"><span class="comment">}</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">// 编译阶段: </span></span><br><span class="line"><span class="comment">sumContext = {</span></span><br><span class="line"><span class="comment"> A0:{</span></span><br><span class="line"><span class="comment"> arguments: {</span></span><br><span class="line"><span class="comment"> length: 0</span></span><br><span class="line"><span class="comment"> },</span></span><br><span class="line"><span class="comment"> b: undefined</span></span><br><span class="line"><span class="comment"> },</span></span><br><span class="line"><span class="comment"> Scope: [A0, sum.[[scope]]]</span></span><br><span class="line"><span class="comment">}</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">// 执行阶段:</span></span><br><span class="line"><span class="comment">ESC = [</span></span><br><span class="line"><span class="comment"> globalContext,</span></span><br><span class="line"><span class="comment"> sumContext</span></span><br><span class="line"><span class="comment">]</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">A0:{</span></span><br><span class="line"><span class="comment"> arguments: {</span></span><br><span class="line"><span class="comment"> length: 0</span></span><br><span class="line"><span class="comment"> },</span></span><br><span class="line"><span class="comment"> b: 2</span></span><br><span class="line"><span class="comment"> },</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">// 执行完:</span></span><br><span class="line"><span class="comment">ECS.pop()</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><h2 id="4-原型与原型链"><a href="#4-原型与原型链" class="headerlink" title="4. 原型与原型链"></a>4. 原型与原型链</h2><ul><li><p>原型:<code>prototype</code>,每一个函数都有一个 <code>prototype</code> 属性;</p></li><li><p>原型链:<code>__proto__</code>,每一个对象都有一个<strong>proto</strong>属性;</p></li><li><p>构造函数:可以通过<code>new</code>来<strong>新建一个对象</strong>的函数。</p></li><li><p>实例:通过构造函数和<code>new</code>创建出来的对象,便是实例。<strong>实例通过<code>__proto__</code>指向原型,通过<code>constructor</code>指向构造函数。</strong></p></li></ul><p>举个例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Animal</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.type = <span class="string">"哺乳类"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Animal.prototype.type = <span class="string">"哺乳"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实例</span></span><br><span class="line"><span class="keyword">let</span> animal = <span class="keyword">new</span> Animal();</span><br><span class="line"><span class="built_in">console</span>.log(animal.__proto__.__proto__ === <span class="built_in">Object</span>.prototype); <span class="comment">//true</span></span><br><span class="line"><span class="built_in">console</span>.log(Animal.prototype.constructor == Animal); <span class="comment">// true</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.prototype.__proto__); <span class="comment">// null</span></span><br></pre></td></tr></table></figure><p>这么说可能比较抽象,我画了一张图帮大家彻底理解他们之间的关系:</p><p><img src="/uploads/i5_1.png" alt="image"></p><p>特殊的情况:<strong>Function 可以充当对象,也可以充当函数</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Function</span>.__proto__ === <span class="built_in">Function</span>.prototype);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.__proto__ === <span class="built_in">Function</span>.prototype);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="built_in">Object</span>.__proto__ === <span class="built_in">Function</span>.__proto__ );</span><br></pre></td></tr></table></figure><h2 id="5-谈谈你对闭包的理解"><a href="#5-谈谈你对闭包的理解" class="headerlink" title="5. 谈谈你对闭包的理解"></a>5. 谈谈你对闭包的理解</h2><p>闭包属于一种特殊的作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的<code>[[scope]]</code>中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。一句话解释就是:<strong>函数定义的作用域和函数执行的作用域,不在同一个作用域下。</strong></p><ul><li><p>闭包会产生一个很经典的问题:<br>多个子函数的<code>[[scope]]</code>都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。</p></li><li><p>解决:</p></li></ul><ol><li>变量可以通过 <code>函数参数的形式</code> 传入,避免使用默认的<code>[[scope]]</code>向上查找</li><li>使用<code>setTimeout</code>包裹,通过第三个参数传入</li><li>使用 <code>块级作用域</code>,让变量成为自己上下文的属性,避免共享</li></ol><ul><li>手写一个简单的闭包</li></ul><p>下面例子中的 closure 就是一个闭包:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">func</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> a = <span class="number">1</span>,b = <span class="number">2</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">closure</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> a+b;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> closure;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="6-手写-call-apply"><a href="#6-手写-call-apply" class="headerlink" title="6. 手写 call/apply()"></a>6. 手写 call/apply()</h2><ul><li>特点:</li></ul><ol><li>可以改变当前函数 this 的指向</li><li>让当前函数执行</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f1</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f2</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 让 f1 的 this 指向 f2,并且让 f1 执行</span></span><br><span class="line">f1.call(f2); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果多个 call,会让 call 方法执行,并把 call 中的 this 指向改变成 fn2</span></span><br><span class="line">f1.call.call.call(f2);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.call = <span class="function"><span class="keyword">function</span> (<span class="params">context</span>) </span>{</span><br><span class="line"> <span class="comment">// 如果 context 存在,使用 context,如果 context 不存在,使用 window;如果 context 是普通类型,转成对象。</span></span><br><span class="line"> context = context ? <span class="built_in">Object</span>(context) : <span class="built_in">window</span>;</span><br><span class="line"> context.fn = <span class="keyword">this</span>; <span class="comment">// this指向调用call的对象,即我们要改变this指向的函数</span></span><br><span class="line"> <span class="keyword">let</span> args = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">1</span>; i < <span class="built_in">arguments</span>.length; i++) {</span><br><span class="line"> args.push(<span class="string">'arguments['</span>+i+<span class="string">']'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> result = <span class="built_in">eval</span>(<span class="string">'context.fn('</span>+args+<span class="string">')'</span>); <span class="comment">// 字符串拼接参数让 fn 执行 </span></span><br><span class="line"> <span class="keyword">delete</span> context.fn; <span class="comment">// 删除我们声明的fn属性</span></span><br><span class="line"> <span class="keyword">return</span> result; <span class="comment">// 返回函数执行结果</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">Function</span>.prototype.apply = <span class="function"><span class="keyword">function</span> (<span class="params">context, args</span>) </span>{</span><br><span class="line"> <span class="comment">// 如果 context 存在,使用 context,如果 context 不存在,使用 window;如果 context 是普通类型,转成对象。</span></span><br><span class="line"> context = context ? <span class="built_in">Object</span>(context) : <span class="built_in">window</span>;</span><br><span class="line"> context.fn = <span class="keyword">this</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(!args){</span><br><span class="line"> <span class="keyword">return</span> context.fn();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> r = <span class="built_in">eval</span>(<span class="string">'context.fn('</span>+args+<span class="string">')'</span>);</span><br><span class="line"> <span class="keyword">delete</span> context.fn;</span><br><span class="line"> <span class="keyword">return</span> r;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-手写-bind"><a href="#7-手写-bind" class="headerlink" title="7. 手写 bind()"></a>7. 手写 bind()</h2><ul><li>特点:</li></ul><ol><li>bind 方法可以绑定 this 指向</li><li>bind 方法返回一个绑定后的函数</li><li>如果绑定的函数被 new,当前函数的 this 就是当前的实例</li><li>new 出来的实例要保证原函数的原型对象上的属性不能丢失</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用法一:</span></span><br><span class="line"><span class="keyword">let</span> person = {</span><br><span class="line"> name: <span class="string">"Cherry"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">name, age</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>.name+ <span class="string">'养了一只'</span>+ name + <span class="string">'今年'</span> + age + <span class="string">'了'</span>); <span class="comment">// Cherry养了一只猫今年2了</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> bindFn = fn.bind(person, <span class="string">'猫'</span>);</span><br><span class="line"></span><br><span class="line">bindFn(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用法二:</span></span><br><span class="line"><span class="keyword">let</span> person = {</span><br><span class="line"> name: <span class="string">"Cherry"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">name, age</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.say = <span class="string">'说话'</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">this</span>); <span class="comment">// fn {say: "说话"}</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> bindFn = fn.bind(person, <span class="string">'猫'</span>);</span><br><span class="line"><span class="keyword">let</span> instance = <span class="keyword">new</span> bindFn(<span class="number">9</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用法三:</span></span><br><span class="line"><span class="keyword">let</span> person = {</span><br><span class="line"> name: <span class="string">"Cherry"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">name, age</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.say = <span class="string">'说话'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn.prototype.flag = <span class="string">'哺乳类'</span>;</span><br><span class="line"><span class="keyword">let</span> bindFn = fn.bind(person, <span class="string">'猫'</span>);</span><br><span class="line"><span class="keyword">let</span> instance = <span class="keyword">new</span> bindFn(<span class="number">9</span>);</span><br><span class="line"><span class="built_in">console</span>.log(instance.flag);</span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Funcition.protoType.bind = <span class="function"><span class="keyword">function</span> (<span class="params">context</span>) </span>{</span><br><span class="line"> <span class="comment">// this表示调用bind的函数</span></span><br><span class="line"> <span class="keyword">let</span> that = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">let</span> bindArgs = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">arguments</span>, <span class="number">1</span>); <span class="comment">//["猫"]</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">Fn</span>(<span class="params"></span>) </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">fBound</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> args = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">arguments</span>); <span class="comment">//[9] </span></span><br><span class="line"> <span class="comment">//this instanceof fBound为true表示构造函数的情况。如new bindFn(9);</span></span><br><span class="line"> <span class="keyword">return</span> that.apply(<span class="keyword">this</span> <span class="keyword">instanceof</span> fBound ? <span class="keyword">this</span> : context, bindArgs.concat(args));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 继承原型上的属性和方法</span></span><br><span class="line"> fn.prototype = <span class="keyword">this</span>.prototype;</span><br><span class="line"> fBound.prototype = <span class="keyword">new</span> Fn();</span><br><span class="line"> <span class="keyword">return</span> fBound;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="9-模拟实现-new"><a href="#9-模拟实现-new" class="headerlink" title="9. 模拟实现 new"></a>9. 模拟实现 new</h2><ul><li>特点: </li></ul><ol><li>创建一个全新的对象,这个对象的<strong>proto</strong>要指向构造函数的原型对象</li><li>执行构造函数</li><li>返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象</li></ol><ul><li>用法:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Animal</span>(<span class="params">type</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.type = type; <span class="comment">// 实例上的属性</span></span><br><span class="line"> <span class="comment">// 如果当前构造函数返回的是一个引用类型,需要直接返回这个对象</span></span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">name</span>: <span class="string">'dog'</span>}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Animal.prototype.say = <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">'say'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> animal = <span class="keyword">new</span> Animal(<span class="string">'哺乳类'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(animal.type); <span class="comment">// 哺乳类</span></span><br><span class="line">animal.say(); <span class="comment">// say</span></span><br></pre></td></tr></table></figure><ul><li>实现:</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// new是关键字,这里我们用函数mockNew来模拟</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mockNew</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// Constructor => animal,剩余的 arguments 就是其他的参数</span></span><br><span class="line"> <span class="keyword">let</span> Constructor = [].shift.call(<span class="built_in">arguments</span>);</span><br><span class="line"> <span class="keyword">let</span> obj = {}; <span class="comment">//返回的结果</span></span><br><span class="line"> obj.__proto__ = Constructor.prototype;</span><br><span class="line"> Constructor.apply(obj, <span class="built_in">arguments</span>);</span><br><span class="line"> <span class="keyword">return</span> r <span class="keyword">instanceof</span> <span class="built_in">Object</span> ? r : obj;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="9-数组扁平化"><a href="#9-数组扁平化" class="headerlink" title="9. 数组扁平化"></a>9. 数组扁平化</h2><p>对于<code>[1, [1,2], [1,2,3]]</code>这样多层嵌套的数组,我们如何将其扁平化为<code>[1, 1, 2, 1, 2, 3]</code>这样的一维数组呢:</p><ol><li>ES6的flat()</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, [<span class="number">1</span>,<span class="number">2</span>], [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]]</span><br><span class="line">arr.flat(<span class="literal">Infinity</span>) <span class="comment">// [1, 1, 2, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><ol start="2"><li>序列化后正则</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, [<span class="number">1</span>,<span class="number">2</span>], [<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> str = <span class="string">`[<span class="subst">${<span class="built_in">JSON</span>.stringify(arr).replace(<span class="regexp">/(\[|\])/g</span>, <span class="string">''</span>)}</span>]`</span></span><br><span class="line"><span class="built_in">JSON</span>.parse(str) <span class="comment">// [1, 1, 2, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><ol start="3"><li>递归</li></ol><p>对于树状结构的数据,最直接的处理方式就是递归</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, [<span class="number">1</span>,<span class="number">2</span>], [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]]</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flat</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> result = []</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> arr) {</span><br><span class="line"> item <span class="keyword">instanceof</span> <span class="built_in">Array</span> ? result = result.concat(flat(item)) : result.push(item)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">flat(arr) <span class="comment">// [1, 1, 2, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><ol start="4"><li>reduce()递归</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, [<span class="number">1</span>,<span class="number">2</span>], [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]]</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flat</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> arr.reduce(<span class="function">(<span class="params">prev, cur</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> prev.concat(cur <span class="keyword">instanceof</span> <span class="built_in">Array</span> ? flat(cur) : cur)</span><br><span class="line"> }, [])</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">flat(arr) <span class="comment">// [1, 1, 2, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><ol start="5"><li>迭代+展开运算符</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 每次while都会合并一层的元素,这里第一次合并结果为[1, 1, 2, 1, 2, 3, [4,4,4]]</span></span><br><span class="line"><span class="comment">// 然后arr.some判定数组中是否存在数组,因为存在[4,4,4],继续进入第二次循环进行合并</span></span><br><span class="line"><span class="keyword">let</span> arr = [<span class="number">1</span>, [<span class="number">1</span>,<span class="number">2</span>], [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,[<span class="number">4</span>,<span class="number">4</span>,<span class="number">4</span>]]]</span><br><span class="line"><span class="keyword">while</span> (arr.some(<span class="built_in">Array</span>.isArray)) {</span><br><span class="line"> arr = [].concat(...arr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="10-ES5如何实现继承"><a href="#10-ES5如何实现继承" class="headerlink" title="10. ES5如何实现继承"></a>10. ES5如何实现继承</h2><p>说到继承,最容易想到的是ES6的<code>extends</code>,当然如果只回答这个肯定不合格,我们要从函数和原型链的角度上实现继承,下面我们一步步地、递进地实现一个合格的继承</p><p>一、原型链继承</p><p>原型链继承的原理很简单,直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><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">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = <span class="string">'Cherry'</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 父类的原型方法</span></span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 子类</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 让子类的原型对象指向父类实例, 这样一来在Child实例中找不到的属性和方法就会到原型对象(父类实例)上寻找</span></span><br><span class="line">Child.prototype = <span class="keyword">new</span> Parent()</span><br><span class="line">Child.prototype.constructor = Child <span class="comment">// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 然后Child实例就能访问到父类及其原型上的name属性和getName()方法</span></span><br><span class="line"><span class="keyword">const</span> child = <span class="keyword">new</span> Child()</span><br><span class="line">child.name <span class="comment">// 'Cherry'</span></span><br><span class="line">child.getName() <span class="comment">// 'Cherry'</span></span><br></pre></td></tr></table></figure><p>原型继承的缺点:</p><ol><li>由于所有Child实例原型都指向同一个Parent实例, 因此对某个Child实例的父类引用类型变量修改会影响所有的Child实例</li><li>在创建子类实例时无法向父类构造传参, 即没有实现super()的功能</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 示例:</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = [<span class="string">'Cherry'</span>] </span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{}</span><br><span class="line"></span><br><span class="line">Child.prototype = <span class="keyword">new</span> Parent()</span><br><span class="line">Child.prototype.constructor = Child </span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试</span></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child()</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child()</span><br><span class="line">child1.name[<span class="number">0</span>] = <span class="string">'foo'</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.name) <span class="comment">// ['foo']</span></span><br><span class="line"><span class="built_in">console</span>.log(child2.name) <span class="comment">// ['foo'] (预期是['Cherry'], 对child1.name的修改引起了所有child实例的变化)</span></span><br></pre></td></tr></table></figure><p>二、构造函数继承</p><p>构造函数继承,即在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = [name]</span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> Parent.call(<span class="keyword">this</span>, <span class="string">'Cherry'</span>) <span class="comment">// 执行父类构造方法并绑定子类的this, 使得父类中的属性能够赋到子类的this上</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//测试</span></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child()</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child()</span><br><span class="line">child1.name[<span class="number">0</span>] = <span class="string">'foo'</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.name) <span class="comment">// ['foo']</span></span><br><span class="line"><span class="built_in">console</span>.log(child2.name) <span class="comment">// ['Cherry']</span></span><br><span class="line">child2.getName() <span class="comment">// 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法</span></span><br></pre></td></tr></table></figure><p>构造函数继承的缺点:</p><ol><li>继承不到父类原型上的属性和方法</li></ol><p>三、组合式继承</p><p>既然原型链继承和构造函数继承各有互补的优缺点, 那么我们为什么不组合起来使用呢, 所以就有了综合二者的组合式继承</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = [name]</span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 构造函数继承</span></span><br><span class="line"> Parent.call(<span class="keyword">this</span>, <span class="string">'Cherry'</span>) </span><br><span class="line">}</span><br><span class="line"><span class="comment">//原型链继承</span></span><br><span class="line">Child.prototype = <span class="keyword">new</span> Parent()</span><br><span class="line">Child.prototype.constructor = Child</span><br><span class="line"></span><br><span class="line"><span class="comment">//测试</span></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child()</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child()</span><br><span class="line">child1.name[<span class="number">0</span>] = <span class="string">'foo'</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.name) <span class="comment">// ['foo']</span></span><br><span class="line"><span class="built_in">console</span>.log(child2.name) <span class="comment">// ['Cherry']</span></span><br><span class="line">child2.getName() <span class="comment">// ['Cherry']</span></span><br></pre></td></tr></table></figure><p>组合式继承的缺点:</p><ol><li>每次创建子类实例都执行了两次构造函数(Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅</li></ol><p>四、寄生式组合继承:</p><p>为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = [name]</span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 构造函数继承</span></span><br><span class="line"> Parent.call(<span class="keyword">this</span>, <span class="string">'Cherry'</span>) </span><br><span class="line">}</span><br><span class="line"><span class="comment">//原型链继承</span></span><br><span class="line"><span class="comment">// Child.prototype = new Parent()</span></span><br><span class="line">Child.prototype = Parent.prototype <span class="comment">//将`指向父类实例`改为`指向父类原型`</span></span><br><span class="line">Child.prototype.constructor = Child</span><br><span class="line"></span><br><span class="line"><span class="comment">//测试</span></span><br><span class="line"><span class="keyword">const</span> child1 = <span class="keyword">new</span> Child()</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> Child()</span><br><span class="line">child1.name[<span class="number">0</span>] = <span class="string">'foo'</span></span><br><span class="line"><span class="built_in">console</span>.log(child1.name) <span class="comment">// ['foo']</span></span><br><span class="line"><span class="built_in">console</span>.log(child2.name) <span class="comment">// ['Cherry']</span></span><br><span class="line">child2.getName() <span class="comment">// ['Cherry']</span></span><br></pre></td></tr></table></figure><p>但这种方式存在一个问题,由于子类原型和父类原型指向同一个对象,我们对子类原型的操作会影响到父类原型,例如给Child.prototype增加一个getName()方法,那么会导致Parent.prototype也增加或被覆盖一个getName()方法,为了解决这个问题,我们给Parent.prototype做一个浅拷贝</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = [name]</span><br><span class="line">}</span><br><span class="line">Parent.prototype.getName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.name</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 构造函数继承</span></span><br><span class="line"> Parent.call(<span class="keyword">this</span>, <span class="string">'Cherry'</span>) </span><br><span class="line">}</span><br><span class="line"><span class="comment">//原型链继承</span></span><br><span class="line"><span class="comment">// Child.prototype = new Parent()</span></span><br><span class="line">Child.prototype = <span class="built_in">Object</span>.create(Parent.prototype) <span class="comment">//将`指向父类实例`改为`指向父类原型`</span></span><br><span class="line">Child.prototype.constructor = Child</span><br><span class="line"></span><br><span class="line"><span class="comment">//测试</span></span><br><span class="line"><span class="keyword">const</span> child = <span class="keyword">new</span> Child()</span><br><span class="line"><span class="keyword">const</span> parent = <span class="keyword">new</span> Parent()</span><br><span class="line">child.getName() <span class="comment">// ['Cherry']</span></span><br><span class="line">parent.getName() <span class="comment">// 报错, 找不到getName()</span></span><br></pre></td></tr></table></figure><p>到这里我们就完成了ES5环境下的继承的实现,这种继承方式称为<code>寄生组合式继承</code>,是目前最成熟的继承方式,babel对ES6继承的转化也是使用了寄生组合式继承<br>我们回顾一下实现过程:<br>一开始最容易想到的是<code>原型链继承</code>,通过把子类实例的原型指向父类实例来继承父类的属性和方法,但原型链继承的缺陷在于<code>对子类实例继承的引用类型的修改会影响到所有的实例对象以及无法向父类的构造方法传参</code>。<br>因此我们引入了<code>构造函数继承</code>, 通过在子类构造函数中调用父类构造函数并传入子类this来获取父类的属性和方法,但构造函数继承也存在缺陷,构造函数继承<code>不能继承到父类原型链上的属性和方法</code>。<br>所以我们综合了两种继承的优点,提出了<code>组合式继承</code>,但组合式继承也引入了新的问题,它<code>每次创建子类实例都执行了两次父类构造方法</code>,我们通过将子类原型指向父类实例改为子类原型指向父类原型的浅拷贝来解决这一问题,也就是最终实现 —— <code>寄生组合式继承</code></p><h2 id="11-for-in-、for-of-以及-forEach-的区别"><a href="#11-for-in-、for-of-以及-forEach-的区别" class="headerlink" title="11. for in 、for of 以及 forEach 的区别"></a>11. for in 、for of 以及 forEach 的区别</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Object</span>.prototype.objCustom = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{}; </span><br><span class="line"><span class="built_in">Array</span>.prototype.arrCustom = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>];</span><br><span class="line">arr.foo = <span class="string">'hello'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> arr) {</span><br><span class="line"> <span class="built_in">console</span>.log(i);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 结果是:</span></span><br><span class="line"><span class="comment">// 0</span></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// foo</span></span><br><span class="line"><span class="comment">// arrCustom</span></span><br><span class="line"><span class="comment">// objCustom</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Object</span>.prototype.objCustom = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{}; </span><br><span class="line"><span class="built_in">Array</span>.prototype.arrCustom = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>];</span><br><span class="line">arr.foo = <span class="string">'hello'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> arr) {</span><br><span class="line"> <span class="keyword">if</span> (arr.hasOwnProperty(i)) {</span><br><span class="line"> <span class="built_in">console</span>.log(i);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 结果是:</span></span><br><span class="line"><span class="comment">// 0</span></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// foo</span></span><br></pre></td></tr></table></figure><ol><li><p>for in 应用于数组循环返回的是数组的下标和数组的属性和原型上的方法和属性,而for in应用于对象循环返回的是对象的属性名和原型中的方法和属性。</p></li><li><p>for in 遍历的是数组的索引(即键名),而for of遍历的是数组元素值。 所以for in更适合遍历对象,不要使用for in遍历数组。</p></li><li><p>for in取到的是index,所以console.log时应该用<code>console.log(myArray[index])</code></p></li><li><p>forEach 不能中断</p></li></ol><h2 id="12-9种常用跨域手段"><a href="#12-9种常用跨域手段" class="headerlink" title="12. 9种常用跨域手段"></a>12. 9种常用跨域手段</h2><ol><li>JSONP</li></ol><p>原理:利用 <code><script></code> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。</p><p>优缺点:优点是简单兼容性好,缺点是<strong>仅支持get方法具有局限性,不安全可能会遭受XSS攻击</strong>。</p><p>实现:封装一个 JSONP</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.html</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">jsonp</span>(<span class="params">{url, params, callback}</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> script = <span class="built_in">document</span>.creatElement(<span class="string">'script'</span>);</span><br><span class="line"> <span class="built_in">window</span>[callback] = <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>{</span><br><span class="line"> resolve(data);</span><br><span class="line"> <span class="built_in">document</span>.body.removeChild(script);</span><br><span class="line"> }</span><br><span class="line"> params = {...pramas, callback};</span><br><span class="line"> <span class="keyword">let</span> arrs = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> key <span class="keyword">in</span> params) {</span><br><span class="line"> arrs.push(<span class="string">`<span class="subst">${key}</span>=<span class="subst">${params[key]}</span>`</span>)</span><br><span class="line"> }</span><br><span class="line"> script.src=<span class="string">`<span class="subst">${url}</span>?<span class="subst">${arrs.join(<span class="string">'&'</span>)}</span>`</span>;</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild(script);</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">jsonp({</span><br><span class="line"> url: <span class="string">'http://localhost:3000/say'</span>,</span><br><span class="line"> parmas: { <span class="attr">wd</span>: <span class="string">'Iloveyou'</span>},</span><br><span class="line"> callback: <span class="string">'show'</span></span><br><span class="line">}).then(<span class="function">(<span class="params">data</span>) =></span>{</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">// server.js</span></span><br><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">let</span> app = express()</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/say'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> { wd, callback } = req.query</span><br><span class="line"> <span class="built_in">console</span>.log(wd) <span class="comment">// Iloveyou</span></span><br><span class="line"> <span class="built_in">console</span>.log(callback) <span class="comment">// show</span></span><br><span class="line"> res.end(<span class="string">`<span class="subst">${callback}</span>('我不爱你')`</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">3000</span>)</span><br></pre></td></tr></table></figure><ol start="2"><li>CORS</li></ol><p>服务端设置 <strong>Access-Control-Allow-Origin</strong> 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。</p><p><strong>CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。</strong></p><p>通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为<strong>简单请求</strong>和<strong>复杂请求</strong>。</p><ul><li>简单请求:</li></ul><p>同时满足以下两个条件即为简单请求,</p><p>条件一:<code>GET</code>,<code>HEAD</code>,<code>POST</code>方法三者之一。</p><p>条件二:<code>Content-Type</code>的值仅限于<code>text/plain</code>,<code>multipart/form-data</code>,<code>application/x-www-form-urlencoded</code>三者之一。</p><ul><li>复杂请求:</li></ul><p>不符合简单请求条件的即为复杂请求。复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求,该请求是 <strong>option</strong> 方法的,通过该请求来知道服务端是否允许跨域请求。</p><p>实现:下面我们由<code>http://localhost:3000/index.html</code>向<code>http://localhost:4000/</code>发送跨域请求。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// http://localhost:3000/index.html</span></span><br><span class="line"><span class="keyword">let</span> xhr = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line"><span class="built_in">document</span>.cookie = <span class="string">'name=cherry'</span> <span class="comment">// cookie不能跨域</span></span><br><span class="line">xhr.withCredentials = <span class="literal">true</span> <span class="comment">// 前端设置是否带cookie</span></span><br><span class="line">xhr.open(<span class="string">'PUT'</span>, <span class="string">'http://localhost:4000/getData'</span>, <span class="literal">true</span>)</span><br><span class="line">xhr.setRequestHeader(<span class="string">'name'</span>, <span class="string">'cherry'</span>)</span><br><span class="line">xhr.onreadystatechange = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (xhr.readyState === <span class="number">4</span>) {</span><br><span class="line"> <span class="keyword">if</span> ((xhr.status >= <span class="number">200</span> && xhr.status < <span class="number">300</span>) || xhr.status === <span class="number">304</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(xhr.response)</span><br><span class="line"> <span class="comment">//得到响应头,后台需设置Access-Control-Expose-Headers</span></span><br><span class="line"> <span class="built_in">console</span>.log(xhr.getResponseHeader(<span class="string">'name'</span>))</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">xhr.send()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// server1.js</span></span><br><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">let</span> app = express();</span><br><span class="line">app.use(express.static(__dirname));</span><br><span class="line">app.listen(<span class="number">3000</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// server2.js</span></span><br><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">let</span> app = express()</span><br><span class="line"><span class="keyword">let</span> whitList = [<span class="string">'http://localhost:3000'</span>] <span class="comment">//设置白名单</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span>(<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> origin = req.headers.origin</span><br><span class="line"> <span class="keyword">if</span> (whitList.includes(origin)) {</span><br><span class="line"> <span class="comment">// 设置哪个源可以访问我</span></span><br><span class="line"> res.setHeader(<span class="string">'Access-Control-Allow-Origin'</span>, origin)</span><br><span class="line"> <span class="comment">// 允许携带哪个头访问我</span></span><br><span class="line"> res.setHeader(<span class="string">'Access-Control-Allow-Headers'</span>, <span class="string">'name'</span>)</span><br><span class="line"> <span class="comment">// 允许哪个方法访问我</span></span><br><span class="line"> res.setHeader(<span class="string">'Access-Control-Allow-Methods'</span>, <span class="string">'PUT'</span>)</span><br><span class="line"> <span class="comment">// 允许携带cookie</span></span><br><span class="line"> res.setHeader(<span class="string">'Access-Control-Allow-Credentials'</span>, <span class="literal">true</span>)</span><br><span class="line"> <span class="comment">// 预检的存活时间</span></span><br><span class="line"> res.setHeader(<span class="string">'Access-Control-Max-Age'</span>, <span class="number">6</span>)</span><br><span class="line"> <span class="comment">// 允许返回的头</span></span><br><span class="line"> res.setHeader(<span class="string">'Access-Control-Expose-Headers'</span>, <span class="string">'name'</span>)</span><br><span class="line"> <span class="keyword">if</span> (req.method === <span class="string">'OPTIONS'</span>) {</span><br><span class="line"> res.end() <span class="comment">// OPTIONS请求不做任何处理</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> next()</span><br><span class="line">})</span><br><span class="line">app.put(<span class="string">'/getData'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(req.headers)</span><br><span class="line"> res.setHeader(<span class="string">'name'</span>, <span class="string">'susan'</span>) <span class="comment">//返回一个响应头,后台需设置</span></span><br><span class="line"> res.end(<span class="string">'我不爱你'</span>)</span><br><span class="line">})</span><br><span class="line">app.get(<span class="string">'/getData'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(req.headers)</span><br><span class="line"> res.end(<span class="string">'我不爱你'</span>)</span><br><span class="line">})</span><br><span class="line">app.use(express.static(__dirname))</span><br><span class="line">app.listen(<span class="number">4000</span>);</span><br></pre></td></tr></table></figure><ol start="3"><li>postMessage</li></ol><p>postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一。它可用于解决以下方面的问题:</p><ul><li>页面和其打开的新窗口的数据传递</li><li>多窗口之间消息传递</li><li>页面与嵌套的iframe消息传递</li><li>上面三个场景的跨域数据传递</li></ul><blockquote><p>otherWindow.postMessage(message, targetOrigin, [transfer]);</p></blockquote><p>API 的详细信息请参阅 MDN。</p><p>实现:下面我们由 <code>http://localhost:3000/a.html</code>页面向<code>http://localhost:4000/b.html</code>传递“我爱你”,然后后者传回”我不爱你”。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">// a.html</span><br><span class="line"><span class="tag"><<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"http://localhost:4000/b.html"</span> <span class="attr">frameborder</span>=<span class="string">"0"</span> <span class="attr">id</span>=<span class="string">"frame"</span> <span class="attr">onload</span>=<span class="string">"load()"</span>></span><span class="tag"></<span class="name">iframe</span>></span> </span><br><span class="line">//等它加载完触发一个事件</span><br><span class="line">//内嵌在http://localhost:3000/a.html</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="actionscript"> <span class="function"><span class="keyword">function</span> <span class="title">load</span><span class="params">()</span> </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">let</span> frame = <span class="built_in">document</span>.getElementById(<span class="string">'frame'</span>)</span></span><br><span class="line"><span class="actionscript"> frame.contentWindow.postMessage(<span class="string">'我爱你'</span>, <span class="string">'http://localhost:4000'</span>) <span class="comment">//发送数据</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{ <span class="comment">//接受返回数据</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(e.data) <span class="comment">//我不爱你</span></span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line">// b.html</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(e.data) <span class="comment">//我爱你</span></span></span><br><span class="line"><span class="actionscript"> e.source.postMessage(<span class="string">'我不爱你'</span>, e.origin)</span></span><br><span class="line"> }</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><ol start="4"><li>document.domain+iframe</li></ol><p>该方式只能用于<strong>二级域名相同</strong>的情况下,比如 a.test.com 和 b.test.com 适用于该方式。 只需要给页面添加 document.domain =’test.com’ 表示二级域名都相同就可以实现跨域。</p><p>实现:下面我们从页面<code>a.zf1.cn:3000/a.html</code>获取页面<code>b.zf1.cn:3000/b.html</code>中a的值。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">// a.html</span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> helloa</span><br><span class="line"> <span class="tag"><<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"http://b.zf1.cn:3000/b.html"</span> <span class="attr">frameborder</span>=<span class="string">"0"</span> <span class="attr">onload</span>=<span class="string">"load()"</span> <span class="attr">id</span>=<span class="string">"frame"</span>></span><span class="tag"></<span class="name">iframe</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.domain = <span class="string">'zf1.cn'</span></span></span><br><span class="line"><span class="actionscript"> <span class="function"><span class="keyword">function</span> <span class="title">load</span><span class="params">()</span> </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(frame.contentWindow.a);</span></span><br><span class="line"> }</span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line">// b.html</span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> hellob</span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.domain = <span class="string">'zf1.cn'</span></span></span><br><span class="line"><span class="actionscript"> <span class="keyword">var</span> a = <span class="number">100</span>;</span></span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br></pre></td></tr></table></figure><ol start="5"><li>window.name+iframe</li></ol><p>原理:利用window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。</p><p>实现:下面我们由页面<code>http://localhost:3000/a.html</code>向页面<code>http://localhost:4000/c.html</code>中获取数据,通过<code>http://localhost:3000/b.html</code>中间代理页进行中转。</p><p>其中a.html和b.html是同域的,而c.html是独立的。a 先引用 c,c把值放到 window.name 上,再把 a 引用的地址改成 b。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">// a.html</span><br><span class="line"><span class="tag"><<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"http://localhost:4000/c.html"</span> <span class="attr">frameborder</span>=<span class="string">"0"</span> <span class="attr">onload</span>=<span class="string">"load()"</span> <span class="attr">id</span>=<span class="string">"iframe"</span>></span><span class="tag"></<span class="name">iframe</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="keyword">let</span> first = <span class="literal">true</span></span></span><br><span class="line"><span class="actionscript"> <span class="comment">// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name</span></span></span><br><span class="line"><span class="actionscript"> <span class="function"><span class="keyword">function</span> <span class="title">load</span><span class="params">()</span> </span>{</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">if</span>(first){</span></span><br><span class="line"><span class="actionscript"> <span class="comment">// 第1次onload(跨域页)成功后,切换到同域代理页面</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">let</span> iframe = <span class="built_in">document</span>.getElementById(<span class="string">'iframe'</span>);</span></span><br><span class="line"><span class="actionscript"> iframe.src = <span class="string">'http://localhost:3000/b.html'</span>;</span></span><br><span class="line"><span class="actionscript"> first = <span class="literal">false</span>;</span></span><br><span class="line"><span class="actionscript"> }<span class="keyword">else</span>{</span></span><br><span class="line"><span class="actionscript"> <span class="comment">// 第2次onload(同域b.html页)成功后,读取同域window.name中数据</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(iframe.contentWindow.name);</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// b.html 为空</span><br><span class="line"></span><br><span class="line">// c.html</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.name = <span class="string">'我不爱你'</span> </span></span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><ol start="6"><li>location.hash+iframe</li></ol><p>原理:利用 url 后面的 hash 进行通信。</p><p>实现:下面我们由页面<code>http://localhost:3000/a.html</code>向页面<code>http://localhost:4000/c.html</code>中获取数据,通过<code>http://localhost:3000/b.html</code>中间代理页进行中转。</p><p>其中a.html和b.html是同域的,而c.html是独立的。a 想访问 c,a 给 c 传了一个 hash 值,c 收到 hash 值后,c 把 hash 值传给了 b,最后 b 将结果放到 a 的 hash 值中。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">// a.html</span><br><span class="line"><span class="tag"><<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"http://localhost:4000/c.html#iloveyou"</span>></span><span class="tag"></<span class="name">iframe</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.onhashchange = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{ <span class="comment">//检测hash的变化</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(location.hash);</span></span><br><span class="line"> }</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line">// b.html</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.parent.parent.location.hash = location.hash </span></span><br><span class="line"><span class="actionscript"> <span class="comment">//b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面</span></span></span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line">// c.html</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"><span class="built_in">console</span>.log(location.hash);</span></span><br><span class="line"><span class="javascript"><span class="keyword">let</span> iframe = <span class="built_in">document</span>.createElement(<span class="string">'iframe'</span>);</span></span><br><span class="line"><span class="actionscript">iframe.src = <span class="string">'http://localhost:3000/b.html#idontloveyou'</span>;</span></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.body.appendChild(iframe);</span></span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><ol start="7"><li>http-proxy 中间件代理</li></ol><p>原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 </p><p>代理服务器,需要做以下几个步骤:</p><ul><li>接受客户端请求 。</li><li>将请求转发给服务器。</li><li>拿到服务器响应数据。</li><li>将响应转发给客户端。</li></ul><p>实现:让本地文件<code>index.html</code>文件,通过代理服务器<code>http://localhost:3000</code>向目标服务器<code>http://localhost:4000</code>请求数据。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.html(http://127.0.0.1:5500)</span></span><br><span class="line"><script src=<span class="string">"https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"</span>><span class="xml"><span class="tag"></<span class="name">script</span>></span></span></span><br><span class="line"><script></span><br><span class="line"> $.ajax({</span><br><span class="line"> url: <span class="string">'http://localhost:3000'</span>,</span><br><span class="line"> type: <span class="string">'post'</span>,</span><br><span class="line"> data: { <span class="attr">name</span>: <span class="string">'xiamen'</span>, <span class="attr">password</span>: <span class="string">'123456'</span> },</span><br><span class="line"> contentType: <span class="string">'application/json;charset=utf-8'</span>,</span><br><span class="line"> success: <span class="function"><span class="keyword">function</span>(<span class="params">result</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(result) <span class="comment">// {"title":"fontend","password":"123456"}</span></span><br><span class="line"> },</span><br><span class="line"> error: <span class="function"><span class="keyword">function</span>(<span class="params">msg</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(msg)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"><<span class="regexp">/script></span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ server1.js 代理服务器(http:/</span><span class="regexp">/localhost:3000)</span></span><br><span class="line"><span class="regexp">const http = require('http')</span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ 第一步:接受客户端请求</span></span><br><span class="line"><span class="regexp">const server = http.createServer((request, response) => {</span></span><br><span class="line"><span class="regexp"> /</span><span class="regexp">/ 代理服务器,直接和浏览器直接交互,需要设置CORS 的首部字段</span></span><br><span class="line"><span class="regexp"> response.writeHead(200, {</span></span><br><span class="line"><span class="regexp"> 'Access-Control-Allow-Origin': '*',</span></span><br><span class="line"><span class="regexp"> 'Access-Control-Allow-Methods': '*',</span></span><br><span class="line"><span class="regexp"> 'Access-Control-Allow-Headers': 'Content-Type'</span></span><br><span class="line"><span class="regexp"> })</span></span><br><span class="line"><span class="regexp"> /</span><span class="regexp">/ 第二步:将请求转发给服务器</span></span><br><span class="line"><span class="regexp"> const proxyRequest = http</span></span><br><span class="line"><span class="regexp"> .request(</span></span><br><span class="line"><span class="regexp"> {</span></span><br><span class="line"><span class="regexp"> host: '127.0.0.1',</span></span><br><span class="line"><span class="regexp"> port: 4000,</span></span><br><span class="line"><span class="regexp"> url: '/</span><span class="string">',</span></span><br><span class="line"><span class="string"> method: request.method,</span></span><br><span class="line"><span class="string"> headers: request.headers</span></span><br><span class="line"><span class="string"> },</span></span><br><span class="line"><span class="string"> serverResponse => {</span></span><br><span class="line"><span class="string"> // 第三步:收到服务器的响应</span></span><br><span class="line"><span class="string"> var body = '</span><span class="string">'</span></span><br><span class="line"><span class="string"> serverResponse.on('</span>data<span class="string">', chunk => {</span></span><br><span class="line"><span class="string"> body += chunk</span></span><br><span class="line"><span class="string"> })</span></span><br><span class="line"><span class="string"> serverResponse.on('</span>end<span class="string">', () => {</span></span><br><span class="line"><span class="string"> console.log('</span>The data is <span class="string">' + body)</span></span><br><span class="line"><span class="string"> // 第四步:将响应结果转发给浏览器</span></span><br><span class="line"><span class="string"> response.end(body)</span></span><br><span class="line"><span class="string"> })</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> )</span></span><br><span class="line"><span class="string"> .end()</span></span><br><span class="line"><span class="string">})</span></span><br><span class="line"><span class="string">server.listen(3000, () => {</span></span><br><span class="line"><span class="string"> console.log('</span>The proxyServer is running at http:<span class="comment">//localhost:3000')</span></span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// server2.js(http://localhost:4000)</span></span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>)</span><br><span class="line"><span class="keyword">const</span> data = { <span class="attr">title</span>: <span class="string">'fontend'</span>, <span class="attr">password</span>: <span class="string">'123456'</span> }</span><br><span class="line"><span class="keyword">const</span> server = http.createServer(<span class="function">(<span class="params">request, response</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (request.url === <span class="string">'/'</span>) {</span><br><span class="line"> response.end(<span class="built_in">JSON</span>.stringify(data))</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">server.listen(<span class="number">4000</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'The server is running at http://localhost:4000'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><ol start="8"><li>nginx 反向代理</li></ol><p>原理:类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。</p><p><strong>使用nginx反向代理实现跨域,是最简单的跨域方式。</strong> 只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。</p><p>实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">// proxy服务器</span><br><span class="line">server {</span><br><span class="line"> listen 81;</span><br><span class="line"> server_name www.domain1.com;</span><br><span class="line"> location / {</span><br><span class="line"> proxy_pass http://www.domain2.com:8080; #反向代理</span><br><span class="line"> proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名</span><br><span class="line"> index index.html index.htm;</span><br><span class="line"></span><br><span class="line"> # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用</span><br><span class="line"> add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*</span><br><span class="line"> add_header Access-Control-Allow-Credentials true;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最后通过命令行nginx -s reload启动nginx</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.html</span></span><br><span class="line"><span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line"><span class="comment">// 前端开关:浏览器是否读写cookie</span></span><br><span class="line">xhr.withCredentials = <span class="literal">true</span>;</span><br><span class="line"><span class="comment">// 访问nginx中的代理服务器</span></span><br><span class="line">xhr.open(<span class="string">'get'</span>, <span class="string">'http://www.domain1.com:81/?user=admin'</span>, <span class="literal">true</span>);</span><br><span class="line">xhr.send();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// server.js</span></span><br><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">var</span> server = http.createServer();</span><br><span class="line"><span class="keyword">var</span> qs = <span class="built_in">require</span>(<span class="string">'querystring'</span>);</span><br><span class="line">server.on(<span class="string">'request'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> params = qs.parse(req.url.substring(<span class="number">2</span>));</span><br><span class="line"> <span class="comment">// 向前台写cookie</span></span><br><span class="line"> res.writeHead(<span class="number">200</span>, {</span><br><span class="line"> <span class="string">'Set-Cookie'</span>: <span class="string">'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'</span> <span class="comment">// HttpOnly:脚本无法读取</span></span><br><span class="line"> });</span><br><span class="line"> res.write(<span class="built_in">JSON</span>.stringify(params));</span><br><span class="line"> res.end();</span><br><span class="line">});</span><br><span class="line">server.listen(<span class="string">'8080'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'Server is running at port 8080...'</span>);</span><br></pre></td></tr></table></figure><ol start="9"><li>websocket</li></ol><p>Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。</p><p>WebSocket和HTTP都是应用层协议,都基于 TCP 协议。<strong>WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。</strong> 同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。</p><p>实现:我们用<code>Socket.io</code>库建立<code>WebSocket</code>链接,让本地文件<code>socket.html</code>向<code>localhost:3000</code>发生数据和接受数据。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// socket.html</span></span><br><span class="line"><span class="keyword">let</span> socket = <span class="keyword">new</span> WebSocket(<span class="string">'ws://localhost:3000'</span>);</span><br><span class="line">socket.onopen = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> socket.send(<span class="string">'我爱你'</span>);<span class="comment">//向服务器发送数据</span></span><br><span class="line">}</span><br><span class="line">socket.onmessage = <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(e.data);<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">// server.js</span></span><br><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">let</span> app = express();</span><br><span class="line"><span class="keyword">let</span> WebSocket = <span class="built_in">require</span>(<span class="string">'ws'</span>);<span class="comment">//记得安装ws</span></span><br><span class="line"><span class="keyword">let</span> wss = <span class="keyword">new</span> WebSocket.Server({<span class="attr">port</span>:<span class="number">3000</span>});</span><br><span class="line">wss.on(<span class="string">'connection'</span>,<span class="function"><span class="keyword">function</span>(<span class="params">ws</span>) </span>{</span><br><span class="line"> ws.on(<span class="string">'message'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data);</span><br><span class="line"> ws.send(<span class="string">'我不爱你'</span>)</span><br><span class="line"> });</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h1 id="二进制流"><a href="#二进制流" class="headerlink" title="二进制流"></a>二进制流</h1><h2 id="1-ArrayBuffer-二进制数组缓冲区"><a href="#1-ArrayBuffer-二进制数组缓冲区" class="headerlink" title="1. ArrayBuffer(二进制数组缓冲区)"></a>1. ArrayBuffer(二进制数组缓冲区)</h2><p>ArrayBuffer是为了处理二进制数据流而出现的,但是JS没有办法直接处理(读写)它里面的内容,于是就需要 TypedArray 或 DataView 来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。</p><p><strong>初始化对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(length);</span><br></pre></td></tr></table></figure><blockquote><p>length: 要创建的ArrayBuffer的大小,以字节为单位。</p></blockquote><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建一个长度为8个字节的buffer</span></span><br><span class="line"><span class="keyword">const</span> buffer = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(<span class="number">8</span>);</span><br><span class="line"><span class="built_in">console</span>.log(buffer.byteLength);</span><br></pre></td></tr></table></figure><p><img src="/uploads/q_17.jpg" alt="image"></p><h2 id="2-TypedArray-类型化数组"><a href="#2-TypedArray-类型化数组" class="headerlink" title="2. TypedArray(类型化数组)"></a>2. TypedArray(类型化数组)</h2><p>TypedArray是一个描述<code>ArrayBuffer</code>的类数组视图(view),但它本身不可以被实例化,甚至无法访问,你可以把它理解为接口(interface),它有很多的实现:</p><table><thead><tr><th>类型</th><th align="center">单个元素值的范围</th><th align="center">大小(bytes)</th><th align="right">描述</th></tr></thead><tbody><tr><td><strong>Int8Array</strong></td><td align="center">-128 to 127</td><td align="center">1</td><td align="right">8 位二进制有符号整数</td></tr><tr><td><strong>Uint8Array</strong></td><td align="center">0 to 255</td><td align="center">1</td><td align="right">8 位无符号整数</td></tr><tr><td>Uint8ClampedArray</td><td align="center">0 to 255</td><td align="center">1</td><td align="right">8 位无符号整数(超出范围后为边界值)</td></tr><tr><td><strong>Int16Array</strong></td><td align="center">-32768 to 32767</td><td align="center">2</td><td align="right">16 位二进制有符号整数</td></tr><tr><td><strong>Uint16Array</strong></td><td align="center">0 to 65535</td><td align="center">2</td><td align="right">16 位无符号整数</td></tr><tr><td>Int32Array</td><td align="center">-2147483648 to 2147483647</td><td align="center">4</td><td align="right">32 位二进制有符号整数</td></tr><tr><td>Uint32Array</td><td align="center">0 to 4294967295</td><td align="center">4</td><td align="right">32 位无符号整数</td></tr><tr><td>Float32Array</td><td align="center">1.2×10-38 to 3.4×1038</td><td align="center">4</td><td align="right">32 位 IEEE 浮点数(7 位有效数字,如 1.1234567)</td></tr><tr><td>Float64Array</td><td align="center">5.0×10-324 to 1.8×10308</td><td align="center">8</td><td align="right">64 位 IEEE 浮点数(16 有效数字,如 1.123…15)</td></tr><tr><td>BigInt64Array</td><td align="center">-263 to 263-1</td><td align="center">8</td><td align="right">64 位二进制有符号整数</td></tr><tr><td>BigUint64Array</td><td align="center">0 to 264-1</td><td align="center">8</td><td align="right">64 位无符号整数</td></tr></tbody></table><p><strong>初始化对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> TypedArray(); <span class="comment">// new in ES2017</span></span><br><span class="line"><span class="keyword">new</span> TypedArray(length);</span><br><span class="line"><span class="keyword">new</span> TypedArray(typedArray);</span><br><span class="line"><span class="keyword">new</span> TypedArray(object);</span><br><span class="line"><span class="keyword">new</span> TypedArray(buffer [, byteOffset [, length]]);</span><br></pre></td></tr></table></figure><blockquote><p>length: 要创建的数组长度,所占的字节等于长度乘以数组属性 BYTES_PER_ELEMENT (该属性根据数组的类型而定,如Int8Array的元素占1个字节,Float32Array的占4个字节)</p></blockquote><blockquote><p>typedArray: 把另一个typedArray对象的每一个元素转换为新typedArray对象的类型</p></blockquote><blockquote><p>object: 把一个类数组的对象(array或iterator)转换为typedArray对象</p></blockquote><blockquote><p>buffer: ArrayBuffer对象,如果ArrayBuffer对象的字节长度与本TypedArray的单个元素字节长队无法整除,则会报错<br>byteOffset: 指定ArrayBuffer对象中的暴露给typedArray的起始位置(以字节为单位)<br>length: 指定ArrayBuffer对象中的暴露给typedArray字节数</p></blockquote><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> buffer = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(<span class="number">8</span>);</span><br><span class="line"><span class="built_in">console</span>.log(buffer.byteLength);<span class="comment">//8</span></span><br><span class="line"><span class="keyword">const</span> int8Array = <span class="keyword">new</span> <span class="built_in">Int8Array</span>(buffer);</span><br><span class="line"><span class="built_in">console</span>.log(int8Array.length);<span class="comment">//8</span></span><br><span class="line"><span class="keyword">const</span> int16Array = <span class="keyword">new</span> <span class="built_in">Int16Array</span>(buffer);</span><br><span class="line"><span class="built_in">console</span>.log(int16Array.length);<span class="comment">//4</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/q_18.jpg" alt="image"></p><p><strong>需要特别说明的是,不同的机器的存储顺序并不相同,而TypedArray是使用当前运行环境的存储顺序。</strong></p><h2 id="3-DataView-数据视图"><a href="#3-DataView-数据视图" class="headerlink" title="3. DataView(数据视图)"></a>3. DataView(数据视图)</h2><p>DataView是一个比TypedArray更加灵活更接近底层的操作二进制数据的视图对象。</p><p><strong>初始化对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="built_in">DataView</span>(buffer [, byteOffset [, byteLength]])</span><br></pre></td></tr></table></figure><blockquote><p>buffer: ArrayBuffer对象<br>byteOffset: 指定ArrayBuffer对象中的暴露给typedArray的起始位置(以字节为单位)<br>byteLength: 指定ArrayBuffer对象中的暴露给typedArray字节数</p></blockquote><p><strong>常用Read/Write方法</strong></p><ul><li>getInt8/setInt8</li><li>getUint8/setUint8</li><li>getInt16/setInt16</li><li>getUint16/setUint16</li><li>getInt32/setInt32</li><li>getUint32/setUint32</li><li>getFloat32/setFloat32</li><li>getFloat64/setFloat64</li></ul><p>Read方法,都包含byteOffset参数,表示读取位置,以字节为单位;除了Int8和Uint8以外的方法还带有可选参数littleEndian,true代表使用小端序,false或不传参则使用大端序。</p><p>Write方法,都包含byteOffset参数,表示写入位置,以字节为单位;value参数代表要设置的值;除了Int8和Uint8以外的方法还带有可选参数littleEndian,true代表使用小端序,false或不传参则使用大端序。</p><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> buffer = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(<span class="number">8</span>);</span><br><span class="line"><span class="built_in">console</span>.log(buffer.byteLength);<span class="comment">//8个字节</span></span><br><span class="line"><span class="keyword">const</span> view1 = <span class="keyword">new</span> <span class="built_in">DataView</span>(buffer);</span><br><span class="line">view1.setInt8(<span class="number">0</span>, <span class="number">1</span>); </span><br><span class="line"><span class="built_in">console</span>.log(view1.getInt8(<span class="number">0</span>));<span class="comment">//1</span></span><br><span class="line"></span><br><span class="line">view1.setInt8(<span class="number">1</span>, <span class="number">2</span>); </span><br><span class="line"><span class="built_in">console</span>.log(view1.getInt8(<span class="number">1</span>));<span class="comment">//2</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(view1.getInt16(<span class="number">0</span>));<span class="comment">//258</span></span><br></pre></td></tr></table></figure><p><img src="/uploads/q_19.jpg" alt="image"></p><p><strong>关于端序Endian</strong></p><p>是指连续字节码在内存中的存储顺序(所以1字节的变量不存在端序问题)。其中,</p><p>大端序: 先存高位<br>小端序: 先存低位</p><h2 id="4-Blob-大的二进制类型"><a href="#4-Blob-大的二进制类型" class="headerlink" title="4. Blob(大的二进制类型)"></a>4. Blob(大的二进制类型)</h2><p>Blob对象是二进制数据,但它是类似文件对象的二进制数据,因此可以像操作File对象一样操作Blob对象,实际上,File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。</p><p><strong>初始化对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Blob(blobParts[, options])</span><br></pre></td></tr></table></figure><blockquote><p>blobParts:数组类型,数组中的每一项连接起来构成Blob对象的数据,数组中的每项元素可以是ArrayBuffer, ArrayBufferView, Blob, DOMString 。</p></blockquote><blockquote><p>options:可选项,字典格式类型,可以指定如下两个属性:</p><ul><li>type,默认值为 “”,它代表了将会被放入到blob中的数组内容的MIME类型。</li><li>endings,默认值为”transparent”,用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: “native”,表示行结束符会被更改为适合宿主操作系统文件系统的换行符; “transparent”,表示会保持blob中保存的结束符不变。</li></ul></blockquote><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> data1 = <span class="string">"a"</span>;</span><br><span class="line"><span class="keyword">var</span> data2 = <span class="string">"b"</span>;</span><br><span class="line"><span class="keyword">var</span> data3 = <span class="string">"<div style='color:red;'>This is a blob</div>"</span>;</span><br><span class="line"><span class="keyword">var</span> data4 = { <span class="string">"name"</span>: <span class="string">"abc"</span> };</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> blob1 = <span class="keyword">new</span> Blob([data1]);</span><br><span class="line"><span class="keyword">var</span> blob2 = <span class="keyword">new</span> Blob([data1, data2]);</span><br><span class="line"><span class="keyword">var</span> blob3 = <span class="keyword">new</span> Blob([data3]);</span><br><span class="line"><span class="keyword">var</span> blob4 = <span class="keyword">new</span> Blob([<span class="built_in">JSON</span>.stringify(data4)]);</span><br><span class="line"><span class="keyword">var</span> blob5 = <span class="keyword">new</span> Blob([data4]);</span><br><span class="line"><span class="keyword">var</span> blob6 = <span class="keyword">new</span> Blob([data3, data4]);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(blob1); <span class="comment">//输出:Blob {size: 1, type: ""}</span></span><br><span class="line"><span class="built_in">console</span>.log(blob2); <span class="comment">//输出:Blob {size: 2, type: ""}</span></span><br><span class="line"><span class="built_in">console</span>.log(blob3); <span class="comment">//输出:Blob {size: 44, type: ""}</span></span><br><span class="line"><span class="built_in">console</span>.log(blob4); <span class="comment">//输出:Blob {size: 14, type: ""}</span></span><br><span class="line"><span class="built_in">console</span>.log(blob5); <span class="comment">//输出:Blob {size: 15, type: ""}</span></span><br><span class="line"><span class="built_in">console</span>.log(blob6); <span class="comment">//输出:Blob {size: 59, type: ""}</span></span><br></pre></td></tr></table></figure><h2 id="5-ObjectURL"><a href="#5-ObjectURL" class="headerlink" title="5. ObjectURL"></a>5. ObjectURL</h2><p>可以使用浏览器新的 API URL 对象通过方法<code>URL.createObjectURL</code>生成一个地址来表示 Blob 数据,这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。<code>URL.revokeObjectURL</code>静态方法用来释放一个之前已经存在的、通过调用<code>URL.createObjectURL()</code>创建的 URL 对象</p><p><strong>初始化对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">URL.createObjectURL(blob);</span><br></pre></td></tr></table></figure><p><strong>释放对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.URL.revokeObjectURL(objectURL);</span><br></pre></td></tr></table></figure><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> data = <span class="string">"a"</span>;</span><br><span class="line"><span class="keyword">var</span> blob = <span class="keyword">new</span> Blob([data]);</span><br><span class="line"><span class="keyword">let</span> objectURL = URL.createObjectURL(blob);</span><br><span class="line">...</span><br><span class="line">URL.revokeObjectURL(objectURL);</span><br></pre></td></tr></table></figure><h2 id="6-DataURL"><a href="#6-DataURL" class="headerlink" title="6. DataURL"></a>6. DataURL</h2><p>Data URLs,即前缀为 data: 协议的URL,其允许内容创建者向文档中嵌入小文件。</p><p><strong>初始化对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">data:[<mediatype>][;base64],<data></span><br></pre></td></tr></table></figure><blockquote><p>mediatype 是个 MIME 类型的字符串,例如 “image/jpeg” 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII</p></blockquote><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 简单的 text/plain 类型数据</span></span><br><span class="line">data:,Hello%<span class="number">2</span>C%<span class="number">20</span>World!</span><br><span class="line"></span><br><span class="line"><span class="comment">// 上一条示例的 base64 编码版本</span></span><br><span class="line">data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%<span class="number">3</span>D%<span class="number">3</span>D</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个HTML文档源代码 <h1>Hello, World</h1></span></span><br><span class="line">data:text/html,%<span class="number">3</span>Ch1%<span class="number">3</span>EHello%<span class="number">2</span>C%<span class="number">20</span>World!%<span class="number">3</span>C%<span class="number">2</span>Fh1%<span class="number">3</span>E</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个会执行 JavaScript alert 的 HTML 文档。注意 script 标签必须封闭。</span></span><br><span class="line">data:text/html,<script>alert(<span class="string">'hi'</span>);<span class="xml"><span class="tag"></<span class="name">script</span>></span></span></span><br></pre></td></tr></table></figure><p>二进制字节类型之间的相互转换:</p><p><img src="/uploads/q_20.jpg" alt="image"></p><h1 id="V8引擎"><a href="#V8引擎" class="headerlink" title="V8引擎"></a>V8引擎</h1><h2 id="1-V8是如何执行一段JS代码的"><a href="#1-V8是如何执行一段JS代码的" class="headerlink" title="1. V8是如何执行一段JS代码的"></a>1. V8是如何执行一段JS代码的</h2><p>V8是混合了编译执行和解释执行两种手段来执行一段 JavaScript 代码的,我们把这种混合使用编译器和解释器的技术称为 JIT(Just In Time)技术。因为这两种方法都各有各的优缺点:解释执行的启动速度快,但是执行时的速度慢,而编译执行的启动速度慢,但是执行时的速度快。所以 V8 采用了这种权衡的策略,在启动过程中使用解释执行,但是如果某段代码的执行频率超过一个值,那么 V8 就会采用编译执行将编译器优化编译成效率更高的机器代码。</p><p>V8 执行一段 JavaScript 代码主要经历了以下几个重要流程:</p><ul><li><strong>初始化基础环境</strong>:堆空间、栈空间、全局执行上下文、全局作用域、事件循环系统、内置函数等。</li></ul><p>基础环境准备好之后,接下来就可以向 V8 提交要执行的 JavaScript 代码了</p><ul><li><p><strong>预解析</strong>:检查语法错误但不生成AST</p></li><li><p><strong>生成AST和作用域</strong>:经过词法/语法分析,生成抽象语法树</p></li><li><p><strong>生成字节码</strong>:基线编译器(Ignition)将AST转换成字节码</p></li><li><p><strong>解释执行字节码</strong></p></li><li><p><strong>监听热点代码</strong>:优化编译器(Turbofan)将字节码转换成优化过的机器码,此外在逐行执行字节码的过程中,如果一段代码经常被执行,那么V8会将这段代码直接转换成机器码保存起来,下一次执行就不必经过字节码,优化了执行速度</p></li><li><p><strong>反优化生成的二进制机器代码</strong>:一旦在执行过程中,对象的结构被动态修改了,那么优化后的代码就变成了无效代码,这时候优化编译器就需要执行反优化操作,经过反优化的代码,下次执行时就会回退到解释器解释执行。</p></li></ul><p>如图所示:</p><p><img src="/uploads/q_10.jpg" alt="image"></p><h2 id="2-V8是如何进行垃圾回收的"><a href="#2-V8是如何进行垃圾回收的" class="headerlink" title="2. V8是如何进行垃圾回收的"></a>2. V8是如何进行垃圾回收的</h2><p>JS引擎中对变量的存储主要有两种位置,栈内存和堆内存,栈内存存储基本类型数据以及引用类型数据的内存地址,堆内存储存引用类型的数据。</p><blockquote><p>栈内存的回收:</p></blockquote><p>栈内存调用栈上下文切换后就被回收,比较简单</p><blockquote><p>堆内存的回收:</p></blockquote><p>V8 依据<strong>代际假说</strong>,将堆内存划分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放生存时间久的对象。为了提升垃圾回收的效率,V8 设置了两个垃圾回收器,主垃圾回收器和副垃圾回收器。主垃圾回收器负责收集老生代中的垃圾数据,副垃圾回收器负责收集新生代中的垃圾数据。</p><ul><li><p>副垃圾回收器 -Minor GC (Scavenger),采用了<strong>Scavenge 算法</strong>。所谓 Scavenge 算法,是把新生代空间对半划分为两个区域,一半是<strong>对象区域</strong>,一半是<strong>空闲区域</strong>。新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。在垃圾回收过程中,首先要对对象区域中的垃圾做标记,将非存活对象回收,将存活对象顺序复制到空闲区域中,复制完成后,将对象区域与空闲区域角色调换,等待下一次回收。<strong>为了执行效率,一般新生区的空间会被设置得比较小。</strong></p></li><li><p>主垃圾回收器 -Major GC,主要负责老生代的垃圾回收</p><ul><li><p>晋升:如果新生代的变量经过多次回收依然存在,那么就会被放入老生代内存中</p></li><li><p>标记清除:老生代内存会先遍历所有对象并打上标记,然后对正在使用或被强引用的对象取消标记,回收被标记的对象。这就是<strong>标记 - 清除算法</strong></p></li><li><p>整理内存碎片:多次执行标记 - 清除算法后,会产生大量不连续的内存碎片,这就需要<strong>标记 - 整理(Mark-Compact)算法</strong>把对象挪到内存的一端</p></li></ul></li></ul><h2 id="3-介绍一下引用计数和标记清除"><a href="#3-介绍一下引用计数和标记清除" class="headerlink" title="3. 介绍一下引用计数和标记清除"></a>3. 介绍一下引用计数和标记清除</h2><ul><li><p><strong>引用计数</strong>:给一个变量赋值引用类型,则该对象的引用次数+1,如果这个变量变成了其他值,那么该对象的引用次数-1,垃圾回收器会回收引用次数为0的对象。但是当对象循环引用时,会导致引用次数永远无法归零,造成内存无法释放。</p></li><li><p><strong>标记清除</strong>:垃圾收集器先给内存中所有对象加上标记,然后从根节点开始遍历,去掉被引用的对象和运行环境中对象的标记,剩下的被标记的对象就是无法访问的等待回收的对象。</p></li></ul><h2 id="4-JS相较于C-等语言为什么慢,V8做了哪些优化"><a href="#4-JS相较于C-等语言为什么慢,V8做了哪些优化" class="headerlink" title="4. JS相较于C++等语言为什么慢,V8做了哪些优化"></a>4. JS相较于C++等语言为什么慢,V8做了哪些优化</h2><ul><li><p>JS的问题:</p><ul><li><p><strong>动态类型</strong>:导致每次存取属性/寻求方法时候,都需要先检查类型;此外动态类型也很难在编译阶段进行优化</p></li><li><p><strong>属性存取</strong>:C++/Java等语言中方法、属性是存储在数组中的,仅需数组位移就可以获取,而JS存储在对象中,每次获取都要进行哈希查询</p></li></ul></li><li><p>V8的优化:</p><ul><li><p><strong>优化JIT(即时编译</strong>):相较于C++/Java这类编译型语言,JS一边解释一边执行,效率低。V8对这个过程进行了优化:如果一段代码被执行多次,那么V8会把这段代码转化为字节码缓存下来,下次运行时直接使用字节码。</p></li><li><p><strong>隐藏类</strong>:对于C++这类语言来说,仅需几个指令就能通过偏移量获取变量信息,而JS需要进行字符串匹配,效率低,V8借用了类和偏移位置的思想,将对象划分成不同的组,即隐藏类</p></li><li><p><strong>内嵌缓存(IC)</strong>:即缓存对象查询的结果。常规查询过程是:获取隐藏类地址 -> 根据属性名查找偏移值 -> 计算该属性地址,内嵌缓存就是对这一过程结果的缓存</p></li><li><p><strong>自动垃圾回收机制</strong>:上文已介绍</p></li></ul></li></ul><h2 id="5-如何判断-JavaScript-中内存泄漏的"><a href="#5-如何判断-JavaScript-中内存泄漏的" class="headerlink" title="5. 如何判断 JavaScript 中内存泄漏的"></a>5. 如何判断 JavaScript 中内存泄漏的</h2><ul><li><p>内存泄漏 (Memory leak):原因是不再需要 (没有作用) 的内存数据依然被其他对象引用着,会导致页面的性能越来越差。要避免内存泄漏,我们需要避免引用那些已经没有用途的数据。</p></li><li><p>内存膨胀 (Memory bloat):由于程序员对内存管理不科学导致的,比如只需要 50M 内存就可以搞定的程序却花费了 500M 内存,会导致页面的性能会一直很差。要解决内存膨胀,需要我们对项目有着透彻的理解,并熟悉各种能减少内存占用的技术方案。</p></li><li><p>频繁垃圾回收:频繁使用大的临时变量,新生代空间很快被装满,从而频繁触发垃圾回收机制导致页面出现延迟或者经常暂停。要解决这个问题,我们可以考虑将这些临时变量设置为全局变量。</p></li></ul><h1 id="React"><a href="#React" class="headerlink" title="React"></a>React</h1><h1 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h1><h1 id="Webpack"><a href="#Webpack" class="headerlink" title="Webpack"></a>Webpack</h1><h2 id="1-webpack的构建流程简单说一下"><a href="#1-webpack的构建流程简单说一下" class="headerlink" title="1. webpack的构建流程简单说一下"></a>1. webpack的构建流程简单说一下</h2><h2 id="2-Loader-Plugin:规约一些通用的小功能"><a href="#2-Loader-Plugin:规约一些通用的小功能" class="headerlink" title="2. Loader/Plugin:规约一些通用的小功能"></a>2. Loader/Plugin:规约一些通用的小功能</h2><h2 id="3-说一说Loader和Plugin的区别?"><a href="#3-说一说Loader和Plugin的区别?" class="headerlink" title="3. 说一说Loader和Plugin的区别?"></a>3. 说一说Loader和Plugin的区别?</h2><h2 id="4-说一下-Webpack-的热更新原理吧"><a href="#4-说一下-Webpack-的热更新原理吧" class="headerlink" title="4. 说一下 Webpack 的热更新原理吧"></a>4. 说一下 Webpack 的热更新原理吧</h2><h2 id="5-Sourcemap是什么?有什么作用?生产环境中应该怎么用?"><a href="#5-Sourcemap是什么?有什么作用?生产环境中应该怎么用?" class="headerlink" title="5. Sourcemap是什么?有什么作用?生产环境中应该怎么用?"></a>5. Sourcemap是什么?有什么作用?生产环境中应该怎么用?</h2><h1 id="Node"><a href="#Node" class="headerlink" title="Node"></a>Node</h1><h1 id="NPM"><a href="#NPM" class="headerlink" title="NPM"></a>NPM</h1><h1 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h1><h2 id="1-冒泡排序"><a href="#1-冒泡排序" class="headerlink" title="1. 冒泡排序"></a>1. 冒泡排序</h2><ul><li><p>思路:拿当前项和后一项比较,符合条件的两者交换位置。最多比较<code>arr.length-1</code>轮。<strong>时间复杂度</strong>:O(n^2)</p></li><li><p>实现:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">swap</span>(<span class="params">arr, i, j</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = temp;</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.prototype.bubble = <span class="function"><span class="keyword">function</span> <span class="title">bubble</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 外层循环 i 控制比较的轮数</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length - <span class="number">1</span>; i++) {</span><br><span class="line"> <span class="comment">// 里层循环 j 控制每一轮比较的次数</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">0</span>; j < <span class="keyword">this</span>.length - <span class="number">1</span> - i; j++) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>[j] > <span class="keyword">this</span>[j + <span class="number">1</span>]) {</span><br><span class="line"> <span class="comment">// 当前项大于后一项,交换位置</span></span><br><span class="line"> swap(<span class="keyword">this</span>, j, j+<span class="number">1</span>);</span><br><span class="line"> } </span><br><span class="line"> } </span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>; </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> array = [<span class="number">12</span>, <span class="number">8</span>, <span class="number">24</span>, <span class="number">16</span>, <span class="number">1</span>];</span><br><span class="line">array.bubble();</span><br><span class="line"><span class="built_in">console</span>.log(array);</span><br></pre></td></tr></table></figure><p><strong>如何优化一个冒泡排序:</strong></p><p>冒泡排序总会执行(N-1)+(N-2)+(N-3)+…+2+1轮,但如果运行到当中某一轮时排序已经完成,或者输入时就是一个有序数组,那么后面的比较就会是多余的,为了比较这种情况,我们可以增加一个flag,判断数组是否在排序途中已经有序,也就是<strong>判断是否有元素交换</strong>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">swap</span>(<span class="params">arr, i, j</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = temp;</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.prototype.bubble = <span class="function"><span class="keyword">function</span> <span class="title">bubble</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="keyword">this</span>.length - <span class="number">1</span>; i++) {</span><br><span class="line"> <span class="comment">// 优化:</span></span><br><span class="line"> <span class="keyword">let</span> flag = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">0</span>; j < <span class="keyword">this</span>.length - <span class="number">1</span> - i; j++) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>[j] > <span class="keyword">this</span>[j + <span class="number">1</span>]) {</span><br><span class="line"> swap(<span class="keyword">this</span>, j, j+<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 优化:</span></span><br><span class="line"> flag = <span class="literal">false</span>;</span><br><span class="line"> } </span><br><span class="line"> } </span><br><span class="line"> <span class="comment">// 优化:如果`某次循环`中没有交换过元素,那么意味着排序已经完成</span></span><br><span class="line"> <span class="keyword">if</span> (flag) <span class="keyword">break</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>; </span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> array = [<span class="number">12</span>, <span class="number">8</span>, <span class="number">24</span>, <span class="number">16</span>, <span class="number">1</span>];</span><br><span class="line">array.bubble();</span><br><span class="line"><span class="built_in">console</span>.log(array); <span class="comment">// [ 1, 8, 12, 16, 24 ]</span></span><br></pre></td></tr></table></figure><h2 id="2-选择排序"><a href="#2-选择排序" class="headerlink" title="2. 选择排序"></a>2. 选择排序</h2><ul><li><p>思路:遍历自身后面的元素和自身比较,让比自身小的元素跟自己调换位置,最多比较<code>arr.length-1</code>轮。<strong>时间复杂度</strong>:O(n^2)</p></li><li><p>实现:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">swap</span>(<span class="params">arr, i, j</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = temp;</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.prototype.select = <span class="function"><span class="keyword">function</span> <span class="title">select</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">0</span>; j < <span class="keyword">this</span>.length - <span class="number">1</span>; j++) {</span><br><span class="line"> <span class="keyword">let</span> min = j;</span><br><span class="line"> <span class="comment">// 找到比当前项还小的这一项索引</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = min + <span class="number">1</span>; i < <span class="keyword">this</span>.length; i++){</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>[i] < <span class="keyword">this</span>[min]) {</span><br><span class="line"> min = i;</span><br><span class="line"> } </span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 让最小的项和当前首位交换位置</span></span><br><span class="line"> swap(<span class="keyword">this</span>, min, j);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> array = [<span class="number">12</span>, <span class="number">8</span>, <span class="number">24</span>, <span class="number">16</span>, <span class="number">1</span>];</span><br><span class="line">array.select();</span><br><span class="line"><span class="built_in">console</span>.log(array); <span class="comment">// [ 1, 8, 12, 16, 24 ]</span></span><br></pre></td></tr></table></figure><h2 id="3-插入排序"><a href="#3-插入排序" class="headerlink" title="3. 插入排序"></a>3. 插入排序</h2><ul><li><p>思路:将元素插入到已排序好的数组中,类似于玩扑克(抓一张牌,跟手里的牌依次比较,顺序将其插入到合适的位置)。<strong>时间复杂度</strong>:O(n^2)</p></li><li><p>实现:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.insert = <span class="function"><span class="keyword">function</span> <span class="title">insert</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 1.准备一个新数组,用来存储抓到手里的牌,开始先抓一张牌进来</span></span><br><span class="line"> <span class="keyword">let</span> handle = [];</span><br><span class="line"> handle.push(<span class="keyword">this</span>[<span class="number">0</span>]);</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2.从第二项开始依次抓牌,一直到把台面上的牌抓光</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="comment">// A是新抓的牌</span></span><br><span class="line"> <span class="keyword">let</span> A = <span class="keyword">this</span>[i];</span><br><span class="line"> <span class="comment">// 和handle手里的牌依次比较(从后向前比)</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> j = handle.length - <span class="number">1</span>; j >= <span class="number">0</span>; j--) {</span><br><span class="line"> <span class="comment">// 每一次要比较的手里的牌</span></span><br><span class="line"> <span class="keyword">let</span> B = handle[j];</span><br><span class="line"> <span class="comment">// 如果当前新牌A比要比较的牌B大了,把A放到B的后面</span></span><br><span class="line"> <span class="keyword">if</span> (A > B) {</span><br><span class="line"> handle.splice(j+<span class="number">1</span>, <span class="number">0</span>, A);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (j === <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 已经比到第一项,我们把新牌放到手中最前面即可</span></span><br><span class="line"> handle.unshift(A);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> handle;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">array = [<span class="number">12</span>, <span class="number">8</span>, <span class="number">24</span>, <span class="number">16</span>, <span class="number">1</span>].insert();</span><br><span class="line"><span class="built_in">console</span>.log(array); <span class="comment">// [ 1, 8, 12, 16, 24 ]</span></span><br></pre></td></tr></table></figure><h2 id="4-希尔排序"><a href="#4-希尔排序" class="headerlink" title="4. 希尔排序"></a>4. 希尔排序</h2><ul><li><p>思路:先算出一个间隔数(数组总长度/2),然后从第一项开始依次跟间隔数后的那一项进行比较。然后再让间隔数=上次间隔数/2,从第一次项开始依次跟新的间隔数后的那一项进行比较。<strong>时间复杂度</strong>:O(n*logn)</p></li><li><p>实现:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">swap</span>(<span class="params">arr, i, j</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = temp;</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.prototype.shell = <span class="function"><span class="keyword">function</span> <span class="title">shell</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> gap = <span class="built_in">Math</span>.floor(<span class="keyword">this</span>.length / <span class="number">2</span>);</span><br><span class="line"> <span class="keyword">while</span> (gap >= <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = gap; i < <span class="keyword">this</span>.length; i++) {</span><br><span class="line"> <span class="keyword">while</span> (i - gap >= <span class="number">0</span> && <span class="keyword">this</span>[i] < <span class="keyword">this</span>[i - gap]) {</span><br><span class="line"> swap(<span class="keyword">this</span>, i, i - gap);</span><br><span class="line"> i = i - gap;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> gap = <span class="built_in">Math</span>.floor(gap / <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> array = [<span class="number">58</span>, <span class="number">23</span>, <span class="number">67</span>, <span class="number">36</span>, <span class="number">40</span>, <span class="number">46</span>, <span class="number">35</span>, <span class="number">28</span>, <span class="number">20</span>, <span class="number">10</span>];</span><br><span class="line">array.shell();</span><br><span class="line"><span class="built_in">console</span>.log(array); <span class="comment">// [ 10, 20, 23, 28, 35, 36, 40, 46, 58, 67 ]</span></span><br></pre></td></tr></table></figure><h2 id="5-快速排序"><a href="#5-快速排序" class="headerlink" title="5. 快速排序"></a>5. 快速排序</h2><ul><li>思路:</li></ul><ol><li>选取基准元素:数组的中间项,并将基准元素在数组中移除</li><li>循环遍历数组,比基准元素小的元素放在左右,大的放右边</li><li>在左右子数组中重复步骤一二,直到数组只剩下一个元素</li><li>向上逐级合并数组,拼接左边+中间+右边成最后的结果</li></ol><ul><li><p><strong>时间复杂度</strong>:O(n*logn)</p></li><li><p>实现:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">quick</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="comment">// 4.结束递归(当数组中小于等于一项,则不用处理)</span></span><br><span class="line"> <span class="keyword">if</span>(arr.length <= <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 1.找到数组的中间项,在原有的数组中把它移除</span></span><br><span class="line"> <span class="keyword">let</span> middleIndex = <span class="built_in">Math</span>.floor(arr.length / <span class="number">2</span>);</span><br><span class="line"> <span class="keyword">let</span> middleValue = arr.splice(middleIndex, <span class="number">1</span>)[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2.准备左右两个数组,循环剩下数组中的每一项,比当前项小的放在左边数组中,比当前项大的放在右边数组中</span></span><br><span class="line"> <span class="keyword">let</span> arrLeft = [],arrRight = [];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i < arr.length; i++) {</span><br><span class="line"> <span class="keyword">const</span> item = arr[i];</span><br><span class="line"> item < middleValue ? arrLeft.push(item) : arrRight.push(item);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> quick(arrLeft).concat(middleValue,quick(arrRight));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(quick([<span class="number">12</span>, <span class="number">8</span>, <span class="number">15</span>, <span class="number">16</span>, <span class="number">1</span>, <span class="number">24</span>])); <span class="comment">// [ 1, 8, 12, 15, 16, 24 ]</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="6-堆排序"><a href="#6-堆排序" class="headerlink" title="6. 堆排序"></a>6. 堆排序</h2><ul><li>思路:</li></ul><ol><li>初始化大(小)根堆,此时根节点为最大(小)值,将根节点与最后一个节点(数组最后一个元素)交换</li><li>除开最后一个节点,重新调整大(小)根堆,使根节点为最大(小)值</li><li>重复步骤二,直到堆中元素剩一个,排序完成</li></ol><ul><li>实现:<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">swap</span>(<span class="params">arr, i, j</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = temp;</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">heap</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="comment">// 我们用数组来储存这个大根堆,数组就是堆本身</span></span><br><span class="line"> <span class="comment">// 初始化大顶堆,从第一个非叶子结点开始</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="built_in">Math</span>.floor(arr.length / <span class="number">2</span> - <span class="number">1</span>); i >= <span class="number">0</span>; i--) {</span><br><span class="line"> heapify(arr, i, arr.length);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 排序,每一次 for 循环找出一个当前最大值,数组长度减一</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="built_in">Math</span>.floor(arr.length - <span class="number">1</span>); i > <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="comment">// 根节点与最后一个节点交换</span></span><br><span class="line"> swap(arr, <span class="number">0</span>, i);</span><br><span class="line"> <span class="comment">// 从根节点开始调整,并且最后一个结点已经为当前最大值,不需要再参与比较,所以第三个参数为 i,即比较到最后一个结点前一个即可</span></span><br><span class="line"> heapify(arr, <span class="number">0</span>, i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> arr;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:</span></span><br><span class="line"><span class="comment">// 假设结点 i 以下的子堆已经是一个大顶堆,heapify 函数实现的</span></span><br><span class="line"><span class="comment">// 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。</span></span><br><span class="line"><span class="comment">// 后面将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点</span></span><br><span class="line"><span class="comment">// 都执行 heapify 操作,所以就满足了结点 i 以下的子堆已经是一大顶堆</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">heapify</span>(<span class="params">array, i, length</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> temp = array[i]; <span class="comment">// 当前父节点</span></span><br><span class="line"> <span class="comment">// j < length 的目的是对结点 i 以下的结点全部做顺序调整</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">2</span> * i + <span class="number">1</span>; j < length; j = <span class="number">2</span> * j + <span class="number">1</span>) {</span><br><span class="line"> temp = array[i]; <span class="comment">// 将 array[i] 取出,整个过程相当于找到 array[i] 应处于的位置</span></span><br><span class="line"> <span class="keyword">if</span> (j + <span class="number">1</span> < length && array[j] < array[j + <span class="number">1</span>]) {</span><br><span class="line"> j++; <span class="comment">// 找到两个孩子中较大的一个,再与父节点比较</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (temp < array[j]) {</span><br><span class="line"> swap(array, i, j); <span class="comment">// 如果父节点小于子节点:交换;否则跳出</span></span><br><span class="line"> i = j; <span class="comment">// 交换后,temp 的下标变为 j</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="7-归并排序"><a href="#7-归并排序" class="headerlink" title="7. 归并排序"></a>7. 归并排序</h2><ul><li><p>思路:归并排序和快排的思路类似,都是递归分治,区别在于快排边分区边排序,而归并在分区完成后才会排序</p></li><li><p>实现:</p></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mergeSort</span>(<span class="params">arr</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(arr.length <= <span class="number">1</span>) <span class="keyword">return</span> arr<span class="comment">//数组元素被划分到剩1个时,递归终止</span></span><br><span class="line"> <span class="keyword">const</span> midIndex = arr.length/<span class="number">2</span> | <span class="number">0</span></span><br><span class="line"> <span class="keyword">const</span> leftArr = arr.slice(<span class="number">0</span>, midIndex)</span><br><span class="line"> <span class="keyword">const</span> rightArr = arr.slice(midIndex, arr.length)</span><br><span class="line"> <span class="keyword">return</span> merge(mergeSort(leftArr), mergeSort(rightArr))<span class="comment">//先划分,后合并</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//合并</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">merge</span>(<span class="params">leftArr, rightArr</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> result = []</span><br><span class="line"> <span class="keyword">while</span>(leftArr.length && rightArr.length) {</span><br><span class="line"> leftArr[<span class="number">0</span>] <= rightArr[<span class="number">0</span>] ? result.push(leftArr.shift()) : result.push(rightArr.shift())</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span>(leftArr.length) result.push(leftArr.shift())</span><br><span class="line"> <span class="keyword">while</span>(rightArr.length) result.push(rightArr.shift())</span><br><span class="line"> <span class="keyword">return</span> result</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>口诀: 插冒归基稳定,快选堆希不稳定</p><table><thead><tr><th>排序算法</th><th align="center">平均时间复杂度</th><th align="center">最坏时间复杂度</th><th align="center">空间复杂度</th><th align="right">是否稳定</th></tr></thead><tbody><tr><td>冒泡排序</td><td align="center">O(n^2)</td><td align="center">O(n^2)</td><td align="center">O(1)</td><td align="right">是</td></tr><tr><td>选择排序</td><td align="center">O(n^2)</td><td align="center">O(n^2)</td><td align="center">O(1)</td><td align="right">不是</td></tr><tr><td>插入排序</td><td align="center">O(n^2)</td><td align="center">O(n^2)</td><td align="center">O(1)</td><td align="right">是</td></tr><tr><td>快速排序</td><td align="center">O(nlogn)</td><td align="center">O(n^2)</td><td align="center">O(logn)</td><td align="right">不是</td></tr><tr><td>希尔排序</td><td align="center">O(nlogn)</td><td align="center">O(n^s)</td><td align="center">O(1)</td><td align="right">不是</td></tr><tr><td>堆排序</td><td align="center">O(nlogn)</td><td align="center">O(nlogn)</td><td align="center">O(1)</td><td align="right">不是</td></tr><tr><td>归并排序</td><td align="center">O(nlogn)</td><td align="center">O(nlogn)</td><td align="center">O(n)</td><td align="right">是</td></tr></tbody></table><p>稳定性: 同大小情况下是否可能会被交换位置, 虚拟dom的diff,不稳定性会导致重新渲染;</p><h2 id="8-二叉树和二叉查找树"><a href="#8-二叉树和二叉查找树" class="headerlink" title="8.二叉树和二叉查找树"></a>8.二叉树和二叉查找树</h2><ul><li><p>树:树是 n 个节点的有限集,有且仅有一个特定的称为根的节点。当 n>1 时,其余节点可分为 m 个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。</p></li><li><p>二叉树:二叉树是树的一种特殊形式,每一个节点最多有两个孩子节点。</p><ul><li>满二叉树:一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上。</li></ul><p><img src="/uploads/q_1.png" alt></p></li></ul><ul><li><p>完全二叉树:最后一个节点之前的节点都齐全即可</p><p><img src="/uploads/q_2.png" alt></p></li></ul><ul><li><p>二叉树可以用哪些物理存储结构来表述?</p><ul><li><p>链表:</p><ol><li>存储数据的data变量</li><li>指向左孩子的left指针</li><li>指向左孩子的right指针</li></ol></li></ul><p><img src="/uploads/q_3.png" alt></p><ul><li>数组:</li></ul><p>使用数组存储时,会按照层级顺序把二叉树的节点放到数组中对应的位置上。 如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也空出来。</p><p>为什么这样设计呢?因为这样可以更方便地在数组中定位二叉树的孩子节点和父节点。</p><p><img src="/uploads/q_4.png" alt></p></li><li><p>二叉树最主要的应用在<strong>查找操作</strong>和<strong>维持相对顺序</strong>两个方面。</p></li><li><p>二叉查找树:</p><p>下图就是一个标准的二叉查找树。</p><p><img src="/uploads/q_5.png" alt></p><ul><li><p>二叉查找树在二叉树的基础上增加了以下几个条件:</p><ol><li>如果左子树不为空,则左子树上所有节点的值均小于根节点的值</li><li>如果右子树不为空,则右子树上所有节点的值均大于根节点的值</li><li>左、右子树也都是二叉查找树</li></ol></li><li><p>遍历二叉查找树</p><ul><li>深度优先遍历:<ul><li><strong>前序遍历</strong>:输出顺序是根节点、左子树、右子树。<br><img src="/uploads/q_6.png" alt></li><li><strong>中序遍历</strong>:输出顺序是左子树、根节点、右子树。<br><img src="/uploads/q_7.png" alt></li><li><strong>后序遍历</strong>:输出顺序是左子树、右子树、根节点。<br><img src="/uploads/q_8.png" alt></li></ul></li><li>广度优先遍历:<ul><li><strong>层序遍历</strong>:树按照从根节点到叶子节点的层次关系,一层 一层横向遍历各个节点。<br><img src="/uploads/q_8.png" alt></li></ul></li></ul></li><li><p>实现二叉查找树</p></li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TreeNode</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(data) {</span><br><span class="line"> <span class="keyword">this</span>.data = data;</span><br><span class="line"> <span class="keyword">this</span>.left = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">this</span>.right = <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BinarySearchTree</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>() {</span><br><span class="line"> <span class="keyword">this</span>.root = <span class="literal">null</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"> insert(data) {</span><br><span class="line"> <span class="keyword">let</span> newNode = <span class="keyword">new</span> TreeNode(data);</span><br><span class="line"> <span class="keyword">let</span> insertNode = <span class="function"><span class="keyword">function</span> (<span class="params">root, newNode</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (newNode.data < root.data) {</span><br><span class="line"> <span class="keyword">if</span> (root.left === <span class="literal">null</span>) {</span><br><span class="line"> root.left = newNode</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> insertNode(root.left, newNode)</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (root.right === <span class="literal">null</span>) {</span><br><span class="line"> root.right = newNode</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> insertNode(root.right, newNode)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.root === <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">this</span>.root = newNode;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> insertNode(<span class="keyword">this</span>.root, newNode)</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"> find(data) {</span><br><span class="line"> <span class="keyword">let</span> findNode = <span class="function"><span class="keyword">function</span> (<span class="params">node, key</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node === <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (key < node.data) {</span><br><span class="line"> <span class="keyword">return</span> findNode(node.left, key)</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (key > node.data) {</span><br><span class="line"> <span class="keyword">return</span> findNode(node.right, key)</span><br><span class="line"> } <span class="keyword">else</span> {</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"> <span class="keyword">return</span> findNode(<span class="keyword">this</span>.root, data)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最小节点</span></span><br><span class="line"> min(node = <span class="keyword">this</span>.root) {</span><br><span class="line"> <span class="keyword">let</span> minNode = <span class="function"><span class="keyword">function</span> (<span class="params">node</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node === <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">while</span> (node && node.left !== <span class="literal">null</span>) {</span><br><span class="line"> node = node.left</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 class="keyword">return</span> minNode(node)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最大节点</span></span><br><span class="line"> max(node = <span class="keyword">this</span>.root) {</span><br><span class="line"> <span class="keyword">let</span> maxNode = <span class="function"><span class="keyword">function</span> (<span class="params">node</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node === <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">while</span> (node && node.right !== <span class="literal">null</span>) {</span><br><span class="line"> node = node.right</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 class="keyword">return</span> maxNode(node)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 前序遍历</span></span><br><span class="line"> preOrderTraveral(callback) {</span><br><span class="line"> <span class="keyword">let</span> preOrderTraveralNode = <span class="function"><span class="keyword">function</span>(<span class="params">node, callback</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node !== <span class="literal">null</span>) {</span><br><span class="line"> callback(node.data)</span><br><span class="line"> preOrderTraveralNode(node.left, callback)</span><br><span class="line"> preOrderTraveralNode(node.right, callback)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> preOrderTraveralNode(<span class="keyword">this</span>.root, callback)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 中序遍历</span></span><br><span class="line"> inOrderTraveral(callback) {</span><br><span class="line"> <span class="keyword">let</span> inOrderTraveralNode = <span class="function"><span class="keyword">function</span>(<span class="params">node, callback</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node !== <span class="literal">null</span>) {</span><br><span class="line"> inOrderTraveralNode(node.left, callback)</span><br><span class="line"> callback(node.data)</span><br><span class="line"> inOrderTraveralNode(node.right, callback)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> inOrderTraveralNode(<span class="keyword">this</span>.root, callback)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 后序遍历</span></span><br><span class="line"> postOrderTraveral(callback) {</span><br><span class="line"> <span class="keyword">let</span> postOrderTraveralNode = <span class="function"><span class="keyword">function</span>(<span class="params">node, callback</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node !== <span class="literal">null</span>) {</span><br><span class="line"> postOrderTraveralNode(node.left, callback)</span><br><span class="line"> postOrderTraveralNode(node.right, callback)</span><br><span class="line"> callback(node.data)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> postOrderTraveralNode(<span class="keyword">this</span>.root, callback)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从树中移除一个节点</span></span><br><span class="line"> remove(data) {</span><br><span class="line"> <span class="keyword">let</span> removeNode = <span class="function"><span class="keyword">function</span>(<span class="params">node, key</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (node === <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (key < node.data) {</span><br><span class="line"> node.left = removeNode(node.left, key)</span><br><span class="line"> <span class="keyword">return</span> node</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (key > node.data) {</span><br><span class="line"> node.right = removeNode(node.right, key)</span><br><span class="line"> <span class="keyword">return</span> node</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 情况1:没有孩子节点</span></span><br><span class="line"> <span class="keyword">if</span> (node.left === <span class="literal">null</span> && node.right === <span class="literal">null</span>) {</span><br><span class="line"> node = <span class="literal">null</span></span><br><span class="line"> <span class="keyword">return</span> node</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 情况2:只有一个孩子节点</span></span><br><span class="line"> <span class="keyword">if</span> (node.left === <span class="literal">null</span>) {</span><br><span class="line"> node = node.right</span><br><span class="line"> <span class="keyword">return</span> node</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (node.right === <span class="literal">null</span>) {</span><br><span class="line"> node = node.left</span><br><span class="line"> <span class="keyword">return</span> node</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 情况3:左孩子和右孩子都存在</span></span><br><span class="line"> <span class="keyword">let</span> temp = <span class="keyword">this</span>.min(node.left)</span><br><span class="line"> node.data = temp.data</span><br><span class="line"> node.right = removeNode(node.right, temp.data)</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"> <span class="keyword">this</span>.root = removeNode(<span class="keyword">this</span>.root, data)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">output</span>(<span class="params">data</span>) </span>{</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="keyword">let</span> btree = <span class="keyword">new</span> BinarySearchTree();</span><br><span class="line"></span><br><span class="line">btree.insert(<span class="number">3</span>)</span><br><span class="line">btree.insert(<span class="number">4</span>)</span><br><span class="line">btree.insert(<span class="number">2</span>)</span><br><span class="line">btree.insert(<span class="number">7</span>)</span><br><span class="line">btree.insert(<span class="number">9</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'前序遍历:'</span>)</span><br><span class="line">btree.preOrderTraveral(output) <span class="comment">// 3 2 4 7 9</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'中序遍历:'</span>)</span><br><span class="line">btree.inOrderTraveral(output) <span class="comment">// 2 3 4 7 9</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'后序遍历:'</span>)</span><br><span class="line">btree.postOrderTraveral(output) <span class="comment">// 2 9 7 4 3</span></span><br><span class="line"></span><br><span class="line">btree.remove(<span class="number">7</span>)</span><br></pre></td></tr></table></figure><ul><li><p>二叉堆:特殊的完全二叉树,分为最大堆和最小堆</p><ul><li>最大堆:任何一个父节点的值都大于两个子节点</li><li>最小堆:任何一个父节点的值都小于两个子节点</li></ul></li><li><p>优先队列:最大优先队列和最小优先队列</p><ul><li>在最大优先队列中,无论入队顺序如何,当前最大的元素都会优先出队</li><li>在最小优先队列中,无论入队顺序如何,当前最小的元素都会优先出队</li></ul></li></ul><p>注:图片来源于《算法漫画》侵删</p><h2 id="二分查找法"><a href="#二分查找法" class="headerlink" title="二分查找法"></a>二分查找法</h2><ol><li>普通写法</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">findIndex</span>(<span class="params">arr, val, left = <span class="number">0</span>, right = arr.length - <span class="number">1</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(left > right) <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line"> <span class="keyword">let</span> center = <span class="built_in">Math</span>.floor((left + right) / <span class="number">2</span>)</span><br><span class="line"> <span class="comment">// 防止溢出的写法: center = (left + right) >>> 1</span></span><br><span class="line"> <span class="keyword">if</span>(arr[center] === val) {</span><br><span class="line"> <span class="keyword">return</span> center</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(arr[center] > val) {</span><br><span class="line"> right = center - <span class="number">1</span></span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> left = center + <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> findIndex(arr, val, left, right)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="2"><li>查找左边界</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">findIndexLeft</span>(<span class="params">arr, val, left = <span class="number">0</span>, right = arr.length - <span class="number">1</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(left > right) <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line"> <span class="keyword">let</span> center = <span class="built_in">Math</span>.floor((left + right) / <span class="number">2</span>)</span><br><span class="line"> <span class="keyword">if</span>(arr[center] === val) {</span><br><span class="line"> <span class="keyword">while</span>(arr[center - <span class="number">1</span>] === val) {</span><br><span class="line"> center--</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> center</span><br><span class="line"> <span class="comment">/* 另一种写法</span></span><br><span class="line"><span class="comment"> if(arr[center - 1] === val) {</span></span><br><span class="line"><span class="comment"> right = center -1</span></span><br><span class="line"><span class="comment"> }else {</span></span><br><span class="line"><span class="comment"> return center</span></span><br><span class="line"><span class="comment"> }</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(arr[center] > val) {</span><br><span class="line"> right = center - <span class="number">1</span></span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> left = center + <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> findIndexLeft(arr, val, left, right)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>查找右边界</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">findIndexRight</span>(<span class="params">arr, val, left = <span class="number">0</span>, right = arr.length - <span class="number">1</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(left > right) <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line"> <span class="keyword">let</span> center = <span class="built_in">Math</span>.floor((left + right) / <span class="number">2</span>)</span><br><span class="line"> <span class="keyword">if</span>(arr[center] === val) {</span><br><span class="line"> <span class="keyword">while</span>(arr[center + <span class="number">1</span>] === val) {</span><br><span class="line"> center++</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> center</span><br><span class="line"> <span class="comment">/* 另一种写法</span></span><br><span class="line"><span class="comment"> if(arr[center + 1] === val) {</span></span><br><span class="line"><span class="comment"> left = center + 1</span></span><br><span class="line"><span class="comment"> }else {</span></span><br><span class="line"><span class="comment"> return center</span></span><br><span class="line"><span class="comment"> }</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(arr[center] > val) {</span><br><span class="line"> right = center - <span class="number">1</span></span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> left = center + <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> findIndexRight(arr, val, left, right)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h1><h1 id="浏览器-网络-HTTP"><a href="#浏览器-网络-HTTP" class="headerlink" title="浏览器/网络/HTTP"></a>浏览器/网络/HTTP</h1><h2 id="1-说说浏览器的缓存策略"><a href="#1-说说浏览器的缓存策略" class="headerlink" title="1. 说说浏览器的缓存策略"></a>1. 说说浏览器的缓存策略</h2><h3 id="浏览器缓存位置和优先级"><a href="#浏览器缓存位置和优先级" class="headerlink" title="浏览器缓存位置和优先级"></a>浏览器缓存位置和优先级</h3><ol><li>Service Worker</li><li>Memory Cache(内存缓存)</li><li>Disk Cache(硬盘缓存)</li><li>Push Cache(推送缓存)</li><li>以上缓存都没命中就会进行网络请求</li></ol><h3 id="不同缓存位置的差别"><a href="#不同缓存位置的差别" class="headerlink" title="不同缓存位置的差别"></a>不同缓存位置的差别</h3><blockquote><p>Service Worker</p></blockquote><p>和Web Worker类似,是独立的线程,我们可以在这个线程中缓存文件,在主线程需要的时候读取这里的文件,Service Worker使我们可以自由选择缓存哪些文件以及文件的匹配、读取规则,并且缓存是持续性的。</p><blockquote><p>Memory Cache</p></blockquote><p>即内存缓存,内存缓存不是持续性的,缓存会随着进程释放而释放。</p><blockquote><p>Disk Cache</p></blockquote><p>即硬盘缓存,相较于内存缓存,硬盘缓存的持续性和容量更优,它会根据HTTP header的字段判断哪些资源需要缓存。</p><blockquote><p>Push Cache</p></blockquote><p>即推送缓存,是HTTP/2的内容,目前应用较少。</p><h3 id="浏览器缓存策略"><a href="#浏览器缓存策略" class="headerlink" title="浏览器缓存策略"></a>浏览器缓存策略</h3><p>缓存分为<strong>强缓存</strong>和<strong>协商缓存</strong>。设置为强缓存,之后的请求都不访问服务器,直接从缓存中找,默认返回状态码是200,且默认强制缓存不缓存首页资源;设置为协商缓存后,每次请求仍然要向服务器询问缓存是否过期,返回状态码是304。<strong>两种缓存机制同时存在时,强缓存高于协商缓存。</strong></p><blockquote><p>强缓存(不要向服务器询问的缓存)</p></blockquote><p><strong>设置Expires(HTTP1.0)</strong>:</p><ul><li><p>即过期时间,表示缓存会在这个时间后失效,这个过期日期是绝对日期,如果修改了本地日期,或者本地日期与服务器日期不一致,那么将导致缓存过期时间错误。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">res.setHeader(<span class="string">'Expires'</span>, <span class="keyword">new</span> <span class="built_in">Date</span>(<span class="built_in">Date</span>.now()+<span class="number">3600</span>*<span class="number">1000</span>).toGMTString());</span><br></pre></td></tr></table></figure></li></ul><p><strong>设置Cache-Control(HTTP/1.1)</strong>:</p><ul><li><p>HTTP/1.1新增字段,Cache-Control可以通过<code>max-age</code>字段来设置过期时间,除此之外<code>Cache-Control</code>还有很多属性,不同的属性代表不同的含义:</p><ul><li>private:客户端可以缓存</li><li>public:客户端和代理服务器都可以缓存</li><li>max-age=t:缓存内容将在t秒后失效</li><li><strong>no-cache</strong>:<strong>默认值</strong>,需要使用协商缓存来验证缓存数据,意思不是不缓存,而是<strong>请求且缓存</strong>。</li><li><strong>no-store</strong>:真正不缓存,意思是<strong>请求但不缓存</strong>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">res.setHeader(<span class="string">'Cache-Control'</span>, <span class="string">'max-age=3600'</span>);</span><br><span class="line">res.setHeader(<span class="string">'Cache-Control'</span>, <span class="string">'no-cache'</span>);</span><br></pre></td></tr></table></figure></li></ul><p>请注意no-cache指令很多人误以为是不缓存,这是不准确的,no-cache的意思是可以缓存,但每次都会向服务器验证缓存是否可用。no-store才是不缓存。当在首部字段 Cache-Control 有指定 max-age 指令时,<strong>Cache-Control的 max-age 优先级高于 Expires。</strong>命中强缓存的表现形式:Firefox浏览器表现为一个灰色的200状态码。Chrome浏览器状态码表现为200 (from disk cache)或是200 OK (from memory cache)。</p><blockquote><p>协商缓存(当缓存已经过期时,使用协商缓存)</p></blockquote><ul><li><strong>Last-Modified & if-Modified-Since(HTTP/1.0)</strong>:对比修改时间</li></ul><p><strong>Last-Modified</strong>:即最后修改时间,浏览器第一次请求资源时,服务器会在响应头上加上<code>Last-Modified</code>,告诉浏览器资源的最后修改时间。</p><p><strong>if-Modified-Since</strong>:当浏览器再次请求该资源时,浏览器会在请求头中带上<code>If-Modified-Since</code>字段,字段的值就是之前服务器返回的最后修改时间,服务器对比这两个时间,若相同,浏览器直接从缓存中获取数据信息。返回状态码304;若不同则返回新资源,返回状态码200,并更新<code>Last-Modified</code>。</p><p><strong>缺点</strong>:</p><ol><li><p>内容没变时间变化了,也会重新读取内容;</p></li><li><p>时间不精准,时间的最小粒度只到<code>s</code>,<code>s</code>以内的改动无法检测到。</p></li></ol><ul><li><strong>Etag & If-None-Match</strong>:对比唯一标识,这种比较方式比较精准,但是默认不会根据完整内容生成唯一标识;为了保证精确度,我们一般会用内容的一部分+文件的总大小来生成唯一标识。<strong>Etag 的优先级高于 Last-Modified</strong>。</li></ul><p><strong>Etag(HTTP/1.1)</strong>:服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)。</p><p><strong>If-None-Match</strong>:当浏览器再次请求该资源时,浏览器会在请求头中带上<code>If-None-Match</code>字段,字段的值就是在缓存中获取的唯一标识。服务器对比两次请求的标识,若相同说明资源没有被修改,浏览器直接从缓存中获取数据信息。返回状态码304;若不同则说明资源被改动过,响应整个资源内容,返回状态码200。</p><p><strong>缺点</strong>:</p><ol><li>文件内容越大,越耗性能</li></ol><h3 id="缓存场景"><a href="#缓存场景" class="headerlink" title="缓存场景"></a>缓存场景</h3><p>对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略</p><ul><li>对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存</li><li>对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新</li><li>对于代码文件来说,通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件</li></ul><h2 id="2-HTTP-1-0和HTTP-1-1有什么区别"><a href="#2-HTTP-1-0和HTTP-1-1有什么区别" class="headerlink" title="2. HTTP/1.0和HTTP/1.1有什么区别"></a>2. HTTP/1.0和HTTP/1.1有什么区别</h2><ul><li><strong>持久连接</strong>: HTTP/1.1 支持持久连接和请求的流水线,在一个TCP连接上可以传送多个HTTP请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持,这样的好处是避免了因为多次建立TCP连接的时间消耗,减少了服务器额外的负担,并提升整体 HTTP 的请求时长。目前浏览器中对于同一个域名,<strong>默认允许同时建立 6 个 TCP 持久连接。</strong></li><li><strong>提供虚拟主机的支持</strong>: 在 HTTP/1.0 中认为每台服务器都有唯一的 IP 地址,但随着虚拟主机技术的发展,多个主机共享一个 IP 地址愈发普遍。因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。</li><li><strong>缓存处理</strong>: HTTP/1.1 引入Entity tag,If-Unmodified-Since, If-Match, If-None-Match等新的请求头来控制缓存。</li><li><strong>对动态生成的内容提供了完美支持</strong>: 随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。HTTP/1.1 通过引入 Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。</li><li><strong>客户端 Cookie、安全机制</strong>:HTTP1.1 引入了 cookie 安全机制。</li><li><strong>带宽优化及网络连接的使用</strong>: HTTP1.1 则在请求头引入了 range 头域,支持断点续传功能。</li></ul><h2 id="3-介绍一下HTTP-2-0新特性"><a href="#3-介绍一下HTTP-2-0新特性" class="headerlink" title="3. 介绍一下HTTP/2.0新特性"></a>3. 介绍一下HTTP/2.0新特性</h2><ul><li><strong>多路复用</strong>: 一个域名都通过一个 TCP 连接并发地完成。主要是为了规避 TCP 的慢启动,TCP 连接之间的竞争问题和队头阻塞问题。</li><li><strong>服务端推送</strong>: 服务端能够主动把资源推送给客户端。</li><li><strong>可以设置请求的优先级</strong>:在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。</li><li><strong>新的二进制格式</strong>: HTTP/2采用二进制格式传输数据,相比于HTTP/1.1的文本格式,二进制格式具有更好的解析性和拓展性。</li><li><strong>头部压缩</strong>: HTTP/2压缩消息头,减少了传输数据的大小。</li></ul><h2 id="4-说说HTTP-3-0"><a href="#4-说说HTTP-3-0" class="headerlink" title="4. 说说HTTP/3.0"></a>4. 说说HTTP/3.0</h2><p>尽管HTTP/2解决了很多1.1的问题,但HTTP/2仍然存在一些缺陷,这些缺陷并不是来自于HTTP/2协议本身,而是来源于底层的TCP协议,我们知道TCP链接是可靠的连接,如果出现了丢包,那么整个连接都要等待重传,HTTP/1.1可以同时使用6个TCP连接,一个阻塞另外五个还能工作,但HTTP/2只有一个TCP连接,阻塞的问题便被放大了。</p><p>由于TCP协议已经被广泛使用,我们很难直接修改TCP协议,基于此,HTTP/3选择了一个折衷的方法——UDP协议,HTTP/2在UDP的基础上实现多路复用、0-RTT、TLS加密、流量控制、丢包重传等功能。</p><h2 id="5-常见HTTP状态码有哪些"><a href="#5-常见HTTP状态码有哪些" class="headerlink" title="5. 常见HTTP状态码有哪些"></a>5. 常见HTTP状态码有哪些</h2><blockquote><p>2XX 请求成功</p></blockquote><p><code>200 OK</code>:客户端发送给服务器的请求被正常处理并返回</p><blockquote><p>3XX 重定向</p></blockquote><p><code>301 Moved Permanently</code>:永久重定向,请求的网页已永久移动到新位置。 服务器返回此响应时,会自动将请求者转到新位置</p><p><code>302 Moved Permanently</code>:临时重定向,请求的网页已临时移动到新位置。服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求</p><p><code>304 Not Modified</code>:未修改,自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容</p><blockquote><p>4XX 客户端错误</p></blockquote><p><code>400 Bad Request</code>:错误请求,服务器不理解请求的语法,常见于客户端传参错误</p><p><code>401 Unauthorized</code>:未授权,表示发送的请求需要有通过 HTTP 认证的认证信息,常见于客户端未登录</p><p><code>403 Forbidden</code>:禁止,服务器拒绝请求,常见于客户端权限不足</p><p><code>404 Not Found</code>:未找到,服务器找不到对应资源</p><blockquote><p>5XX 服务端错误</p></blockquote><p><code>500 Inter Server Error</code>:服务器内部错误,服务器遇到错误,无法完成请求</p><p><code>501 Not Implemented</code>:尚未实施,服务器不具备完成请求的功能</p><p><code>502 Bad Gateway</code>:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。</p><p><code>503 service unavailable</code>:服务不可用,服务器目前无法使用(处于超载或停机维护状态)。通常是暂时状态。</p><h2 id="6-HTTP常见请求-响应头及其含义"><a href="#6-HTTP常见请求-响应头及其含义" class="headerlink" title="6.HTTP常见请求/响应头及其含义"></a>6.HTTP常见请求/响应头及其含义</h2><blockquote><p>通用头(请求头和响应头都有的首部)</p></blockquote><table><thead><tr><th>字段</th><th align="center">作用</th><th align="right">值</th></tr></thead><tbody><tr><td>Cache-Control</td><td align="center">控制缓存</td><td align="right">public:表示响应可以被任何对象缓存(包括客户端/代理服务器) <br> private(默认值):响应只能被单个客户缓存,不能被代理服务器缓存 <br> no-cache:缓存要经过服务器验证,在浏览器使用缓存前,会对比ETag,若没变则返回304,使用缓存 <br> no-store:禁止任何缓存</td></tr><tr><td>Connection</td><td align="center">是否需要持久连接(HTTP 1.1默认持久连接)</td><td align="right">keep-alive / close</td></tr><tr><td>Transfer-Encoding</td><td align="center">报文主体的传输编码格式</td><td align="right">chunked(分块) / identity(未压缩和修改) / gzip(LZ77压缩) / compress(LZW压缩,弃用) / deflate(zlib结构压缩)</td></tr></tbody></table><blockquote><p>请求头</p></blockquote><table><thead><tr><th>字段</th><th align="center">作用</th><th align="right">语法</th></tr></thead><tbody><tr><td>Accept</td><td align="center">告知(服务器)客户端可以处理的内容类型</td><td align="right">text/html、image/<em>、</em>/*</td></tr><tr><td>If-Modified-Since</td><td align="center">将<code>Last-Modified</code>的值发送给服务器,询问资源是否已经过期(被修改),过期则返回新资源,否则返回304</td><td align="right">示例:If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT</td></tr><tr><td>If-Unmodified-Since</td><td align="center">将<code>Last-Modified</code>的值发送给服务器,询问文件是否被修改,若没有则返回200,否则返回412预处理错误,可用于断点续传。通俗点说<code>If-Unmodified-Since</code>是文件没有修改时下载,<code>If-Modified-Since</code>是文件修改时下载</td><td align="right">示例:If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT</td></tr><tr><td>If-None-Match</td><td align="center">将<code>ETag</code>的值发送给服务器,询问资源是否已经过期(被修改),过期则返回新资源,否则返回304</td><td align="right">示例:If-None-Match: “bfc13a6472992d82d”</td></tr><tr><td>If-Match</td><td align="center">将<code>ETag</code>的值发送给服务器,询问文件是否被修改,若没有则返回200,否则返回412预处理错误,可用于断点续传</td><td align="right">示例:If-Match: “bfc129c88ca92d82d”</td></tr><tr><td>Range</td><td align="center">告知服务器返回文件的哪一部分, 用于断点续传</td><td align="right">示例:Range: bytes=200-1000, 2000-6576, 19000-</td></tr><tr><td>Host</td><td align="center">指明了服务器的域名(对于虚拟主机来说),以及(可选的)服务器监听的TCP端口号</td><td align="right">示例:Host:<a href="http://www.baidu.com" rel="external nofollow noopener noreferrer" target="_blank">www.baidu.com</a></td></tr><tr><td>User-Agent</td><td align="center">告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本</td><td align="right"><code>User-Agent: Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions></code></td></tr></tbody></table><blockquote><p>响应头</p></blockquote><table><thead><tr><th>字段</th><th align="center">作用</th><th align="right">语法</th></tr></thead><tbody><tr><td>Location</td><td align="center">需要将页面重新定向至的地址。一般在响应码为3xx的响应中才会有意义</td><td align="right"><code>Location: <url></code></td></tr><tr><td>ETag</td><td align="center">资源的特定版本的标识符,如果内容没有改变,Web服务器不需要发送完整的响应</td><td align="right"><code>ETag: "<etag_value>"</code></td></tr><tr><td>Server</td><td align="center">处理请求的源头服务器所用到的软件相关信息</td><td align="right"><code>Server: <product></code></td></tr></tbody></table><blockquote><p>实体头(针对请求报文和响应报文的实体部分使用首部)</p></blockquote><table><thead><tr><th>字段</th><th align="center">作用</th><th align="right">语法</th></tr></thead><tbody><tr><td>Allow</td><td align="center">资源可支持http请求的方法</td><td align="right"><code>Allow: <http-methods>,示例:Allow: GET, POST, HEAD</code></td></tr><tr><td>Last-Modified</td><td align="center">资源最后的修改时间,用作一个验证器来判断接收到的或者存储的资源是否彼此一致,精度不如ETag</td><td align="right">示例:Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT</td></tr><tr><td>Expires</td><td align="center">响应过期时间</td><td align="right"><code>Expires: <http-date>,示例:Expires: Wed, 21 Oct 2020 07:28:00 GMT</code></td></tr></tbody></table><p>详细的介绍可以参阅<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers" rel="external nofollow noopener noreferrer" target="_blank">MDN</a>。</p><h2 id="7-GET请求和POST请求有何区别"><a href="#7-GET请求和POST请求有何区别" class="headerlink" title="7.GET请求和POST请求有何区别"></a>7.GET请求和POST请求有何区别</h2><p><strong>使用上的区别</strong></p><ul><li><p>GET请求参数放在URL上,POST请求参数放在请求体里</p></li><li><p>GET请求参数长度有限制,POST请求参数长度可以非常大</p></li><li><p>POST请求相较于GET请求安全一点点,因为GET请求的参数在URL上,且有历史记录</p></li><li><p>GET请求能缓存,POST不能</p></li></ul><p><strong>本质区别</strong></p><p>其实HTTP协议并没有要求GET/POST请求参数必须放在URL上或请求体里,也没有规定GET请求的长度,目前对URL的长度限制,是各家浏览器设置的限制。GET和POST的根本区别在于:<strong>GET请求是幂等性的,而POST请求不是</strong></p><blockquote><p>幂等性,指的是对某一资源进行一次或多次请求都具有相同的副作用。例如搜索就是一个幂等的操作,而删除、新增则不是一个幂等操作。</p></blockquote><p>由于GET请求是幂等的,在网络不好的环境中,GET请求可能会重复尝试,造成重复操作数据的风险,因此,GET请求用于无副作用的操作(如搜索),新增/删除等操作适合用POST</p><h2 id="8-HTTP和HTTPS有何区别"><a href="#8-HTTP和HTTPS有何区别" class="headerlink" title="8.HTTP和HTTPS有何区别"></a>8.HTTP和HTTPS有何区别</h2><ul><li><p>HTTPS使用443端口,而HTTP使用80</p></li><li><p>HTTPS需要申请证书</p></li><li><p>HTTP是超文本传输协议,是明文传输;HTTPS是经过SSL加密的协议,传输更安全</p></li><li><p>HTTPS比HTTP慢,因为HTTPS除了TCP握手的三个包,还要加上SSL握手的九个包</p></li></ul><h2 id="9-HTTPS是如何进行加密的"><a href="#9-HTTPS是如何进行加密的" class="headerlink" title="9.HTTPS是如何进行加密的"></a>9.HTTPS是如何进行加密的</h2><blockquote><p>对称加密</p></blockquote><p>客户端和服务器公用一个密匙用来对消息加解密,这种方式称为对称加密。客户端和服务器约定好一个加密的密匙。客户端在发消息前用该密匙对消息加密,发送给服务器后,服务器再用该密匙进行解密拿到消息。</p><p><img src="/uploads/q_11.png" alt></p><p>这种方式一定程度上保证了数据的安全性,但密钥一旦泄露(密钥在传输过程中被截获),传输内容就会暴露,因此我们要寻找一种安全传递密钥的方法。</p><blockquote><p>非对称加密</p></blockquote><p>采用非对称加密时,客户端和服务端均拥有一个公钥和私钥,公钥加密的内容只有对应的私钥能解密。私钥自己留着,公钥发给对方。这样在发送消息前,先用对方的公钥对消息进行加密,收到后再用自己的私钥进行解密。这样攻击者只拿到传输过程中的公钥也无法破解传输的内容</p><p><img src="/uploads/q_12.png" alt></p><p>尽管非对称加密解决了由于密钥被获取而导致传输内容泄露的问题,但中间人仍然可以用篡改公钥的方式来获取或篡改传输内容,而且非对称加密的性能比对称加密的性能差了不少</p><p><img src="/uploads/q_13.png" alt></p><blockquote><p>第三方认证</p></blockquote><p>上面这种方法的弱点在于,客户端不知道公钥是由服务端返回,还是中间人返回的,因此我们再引入一个第三方认证的环节:即第三方使用私钥加密我们自己的公钥,浏览器已经内置一些权威第三方认证机构的公钥,浏览器会使用第三方的公钥来解开第三方私钥加密过的我们自己的公钥,从而获取公钥,如果能成功解密,就说明获取到的自己的公钥是正确的</p><p>但第三方认证也未能完全解决问题,第三方认证是面向所有人的,中间人也能申请证书,如果中间人使用自己的证书掉包原证书,客户端还是无法确认公钥的真伪</p><p><img src="/uploads/q_14.png" alt></p><blockquote><p>数字签名</p></blockquote><p>为了让客户端能够验证公钥的来源,我们给公钥加上一个数字签名,这个数字签名是由企业、网站等各种信息和公钥经过单向hash而来,一旦构成数字签名的信息发生变化,hash值就会改变,这就构成了公钥来源的唯一标识</p><p>具体来说,服务端本地生成一对密钥,然后拿着公钥以及企业、网站等各种信息到CA(第三方认证中心)去申请数字证书,CA会通过一种单向hash算法(比如MD5),生成一串摘要,这串摘要就是这堆信息的唯一标识,然后CA还会使用自己的私钥对摘要进行加密,连同我们自己服务器的公钥一同发送给我我们。</p><p>浏览器拿到数字签名后,会使用浏览器本地内置的CA公钥解开数字证书并验证,从而拿到正确的公钥。由于非对称加密性能低下,拿到公钥以后,客户端会随机生成一个对称密钥,使用这个公钥加密并发送给服务端,服务端用自己的私钥解开对称密钥,此后的加密连接就通过这个对称密钥进行对称加密。</p><p>综上所述,HTTPS在验证阶段使用非对称加密+第三方认证+数字签名获取正确的公钥,获取到正确的公钥后以对称加密的方式通信</p><h2 id="10-讲讲网络OSI七层模型,TCP-IP和HTTP分别位于哪一层"><a href="#10-讲讲网络OSI七层模型,TCP-IP和HTTP分别位于哪一层" class="headerlink" title="10.讲讲网络OSI七层模型,TCP/IP和HTTP分别位于哪一层"></a>10.讲讲网络OSI七层模型,TCP/IP和HTTP分别位于哪一层</h2><p><img src="/uploads/q_15.png" alt></p><table><thead><tr><th>模型</th><th align="center">概述</th><th align="right">单位</th></tr></thead><tbody><tr><td>物理层</td><td align="center">网络连接介质,如网线、光缆,数据在其中以比特为单位传输</td><td align="right">bit</td></tr><tr><td>数据链路层</td><td align="center">数据链路层将比特封装成数据帧并传递</td><td align="right">帧</td></tr><tr><td>网络层</td><td align="center">定义IP地址,定义路由功能,建立主机到主机的通信</td><td align="right">数据包</td></tr><tr><td>传输层</td><td align="center">负责将数据进行可靠或者不可靠传递,建立端口到端口的通信</td><td align="right">数据段</td></tr><tr><td>会话层</td><td align="center">控制应用程序之间会话能力,区分不同的进程</td><td align="right"></td></tr><tr><td>表示层</td><td align="center">数据格式标识,基本压缩加密功能</td><td align="right"></td></tr><tr><td>应用层</td><td align="center">各种应用软件</td><td align="right"></td></tr></tbody></table><h1 id="Web安全"><a href="#Web安全" class="headerlink" title="Web安全"></a>Web安全</h1><p>在 Web 安全领域中,XSS 和 CSRF 是最常见的攻击方式。章将会简单介绍 XSS 和 CSRF 的攻防问题。</p><h2 id="1-什么是CSRF攻击"><a href="#1-什么是CSRF攻击" class="headerlink" title="1. 什么是CSRF攻击"></a>1. 什么是CSRF攻击</h2><blockquote><p>CSRF即Cross-site request forgery(跨站请求伪造),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。</p></blockquote><p>假如黑客在自己的站点上放置了其他网站的外链,例如<code>"www.weibo.com/api</code>,默认情况下,浏览器会带着<code>weibo.com</code>的cookie访问这个网址,如果用户已登录过该网站且网站没有对CSRF攻击进行防御,那么服务器就会认为是用户本人在调用此接口并执行相关操作,致使账号被劫持。</p><h2 id="2-CSRF特点"><a href="#2-CSRF特点" class="headerlink" title="2. CSRF特点"></a>2. CSRF特点</h2><ul><li><p>CSRF(通常)发生在第三方域名;</p></li><li><p>CSRF攻击者(通常)不能获取到Cookie等信息,只是使用;</p></li></ul><h2 id="3-如何防御CSRF攻击"><a href="#3-如何防御CSRF攻击" class="headerlink" title="3. 如何防御CSRF攻击"></a>3. 如何防御CSRF攻击</h2><ul><li><p>验证<code>Token</code>:浏览器请求服务器时,服务器返回一个token,然后放在页面中,页面提交请求的时候,带上这个Token。服务端把Token从Session中拿出,与请求中的Token进行比对验证。</p></li><li><p>验证<code>Referer</code>:通过验证请求头的Referer来验证来源站点,但请求头很容易伪造</p></li><li><p>设置<code>SameSite</code>:设置cookie的SameSite,可以让cookie不随跨域请求发出,但浏览器兼容不一</p></li><li><p>验证码:CSRF 攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终请求。因此验证码被认为是对抗 CSRF 攻击最简洁而有效的防御方法。但验证码并不是万能的,因为出于用户考虑,不能给网站所有的操作都加上验证码。所以验证码只能作为防御 CSRF 的一种辅助手段,而不能作为最主要的解决方案。</p></li></ul><h2 id="4-什么是XSS攻击"><a href="#4-什么是XSS攻击" class="headerlink" title="4. 什么是XSS攻击"></a>4. 什么是XSS攻击</h2><blockquote><p>XSS即Cross Site Scripting(跨站脚本攻击),指的是通过利用网页开发时留下的漏洞,注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。常见的例如在评论区植入JS代码,用户进入评论页时代码被执行,进而窃取隐私数据比如cookie、session,或者重定向到不好的网站等。</p></blockquote><h2 id="5-XSS攻击有哪些类型"><a href="#5-XSS攻击有哪些类型" class="headerlink" title="5. XSS攻击有哪些类型"></a>5. XSS攻击有哪些类型</h2><ul><li><p><strong>存储型</strong>:即攻击被存储在服务端,常见的是在评论区插入攻击脚本,如果脚本被储存到服务端,那么所有看见对应评论的用户都会受到攻击。</p></li><li><p><strong>反射型</strong>:攻击者将脚本混在URL里,服务端接收到URL将恶意代码当做参数取出并拼接在HTML里返回,浏览器解析此HTML后即执行恶意代码。例如在url上添加脚本类的参数:<code>https://www.baidu.com?jarttoTest=<script>alert(document.cookie)</script></code>。</p></li><li><p><strong>DOM型</strong>:将攻击脚本写在URL中,诱导用户点击该URL,如果URL被解析,那么攻击脚本就会被运行。和前两者的差别主要在于DOM型攻击不经过服务端</p></li></ul><h2 id="6-如何防御XSS攻击"><a href="#6-如何防御XSS攻击" class="headerlink" title="6. 如何防御XSS攻击"></a>6. 如何防御XSS攻击</h2><ul><li><p>输入检查:<strong>不要相信用户的任何输入</strong>,对输入内容中的<code><script><iframe></code>等标签进行转义或者过滤。</p></li><li><p>输出检查:<strong>不要完全信任服务端</strong>,对服务端输出内容有规则的过滤后再输出到页面中。</p></li><li><p><strong>设置httpOnly</strong>:很多XSS攻击目标都是窃取用户cookie伪造身份认证,设置此属性可防止JS获取cookie。</p></li><li><p><strong>开启CSP</strong>:即开启白名单,可阻止白名单以外的资源加载和运行。</p></li><li><p>需要转义的字符有:</p></li></ul><table><thead><tr><th>字符</th><th align="right">转义后字符</th></tr></thead><tbody><tr><td>&</td><td align="right"><code>&amp;</code></td></tr><tr><td><</td><td align="right"><code>&lt;</code></td></tr><tr><td>></td><td align="right"><code>&gt;</code></td></tr><tr><td>“</td><td align="right"><code>&quot;</code></td></tr><tr><td>‘</td><td align="right"><code>&#x27;</code></td></tr><tr><td>/</td><td align="right"><code>&#x2F;</code></td></tr></tbody></table><p>参考文章:<a href="https://github.com/dwqs/blog/issues/68" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/dwqs/blog/issues/68</a></p>]]></content>
<summary type="html">
<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/353632561.jpg" width="800" height="100%">
</summary>
<category term="前端" scheme="https://litgod.net/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="面试" scheme="https://litgod.net/tags/%E9%9D%A2%E8%AF%95/"/>
</entry>
<entry>
<title>面试官:“你能手写一个 Promise 吗”(完美符合Promise/A+规范)</title>
<link href="https://litgod.net/2020/06/28/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%EF%BC%8C%E6%89%8B%E5%86%99Promise%E6%BA%90%E7%A0%81%EF%BC%88%E5%AE%8C%E7%BE%8E%E7%AC%A6%E5%90%88Promise-A-%E8%A7%84%E8%8C%83%EF%BC%89/"/>
<id>https://litgod.net/2020/06/28/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%EF%BC%8C%E6%89%8B%E5%86%99Promise%E6%BA%90%E7%A0%81%EF%BC%88%E5%AE%8C%E7%BE%8E%E7%AC%A6%E5%90%88Promise-A-%E8%A7%84%E8%8C%83%EF%BC%89/</id>
<published>2020-06-28T05:08:11.000Z</published>
<updated>2020-08-25T08:18:18.281Z</updated>
<content type="html"><![CDATA[<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/375274245.jpg" width="800" height="100%"><a id="more"></a><p>关于手写 Promise,想必大家都十分熟悉。基本上现在不管是大厂还是小厂,手写 promise 已经成为了面试必考知识点。听说你还不太会?那么走着,带你从零开始解锁 Promise!</p><h2 id="常见-Promise-面试题"><a href="#常见-Promise-面试题" class="headerlink" title="常见 Promise 面试题"></a>常见 Promise 面试题</h2><p>首先,我们以常见的 Promise 面试题为切入点,我们看看面试官们都爱考什么:</p><ol><li>Promise 解决了什么问题?</li><li>Promise 的业界实现都有哪些?</li><li>Promise 常用的 API 有哪些?</li><li>能不能手写一个符合 Promise/A+ 规范的 Promise?</li><li>Promise 在事件循环中的执行过程是怎样的?</li><li>Promise 有什么缺陷,可以如何解决?</li></ol><p>这几个问题由浅入深,我们一个一个来看:</p><h2 id="Promise-出现的原因-amp-业界实现"><a href="#Promise-出现的原因-amp-业界实现" class="headerlink" title="Promise 出现的原因 & 业界实现"></a>Promise 出现的原因 & 业界实现</h2><p>在 Promise 出现以前,在我们处理多个异步请求嵌套时,代码往往是这样的。。。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"></span><br><span class="line">fs.readFile(<span class="string">'./name.txt'</span>,<span class="string">'utf8'</span>,<span class="function"><span class="keyword">function</span>(<span class="params">err,data</span>)</span>{</span><br><span class="line"> fs.readFile(data, <span class="string">'utf8'</span>,<span class="function"><span class="keyword">function</span>(<span class="params">err,data</span>)</span>{</span><br><span class="line"> fs.readFile(data,<span class="string">'utf8'</span>,<span class="function"><span class="keyword">function</span>(<span class="params">err,data</span>)</span>{</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><br></pre></td></tr></table></figure><p>为了拿到回调的结果,我们必须一层一层的嵌套,可以说是相当恶心了。而且基本上我们还要对每次请求的结果进行一系列的处理,使得代码变的更加难以阅读和难以维护,这就是传说中臭名昭著的<strong>回调地狱</strong>~产生<strong>回调地狱</strong>的原因归结起来有两点:</p><p>1.<strong>嵌套调用</strong>,第一个函数的输出往往是第二个函数的输入;<br>2.<strong>处理多个异步请求并发</strong>,开发时往往需要同步请求最终的结果。</p><p>原因分析出来后,那么问题的解决思路就很清晰了:</p><p>1.<strong>消灭嵌套调用</strong>:通过 Promise 的链式调用可以解决;<br>2.<strong>合并多个任务的请求结果</strong>:使用 Promise.all 获取合并多个任务的错误处理。</p><p>Promise 正是用一种更加友好的代码组织方式,解决了异步嵌套的问题。</p><p>我们来看看上面的例子用 Promise 实现是什么样的:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</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="function"><span class="keyword">function</span> <span class="title">read</span>(<span class="params">filename</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> fs.readFile(filename, <span class="string">'utf8'</span>, (err, data) => {</span><br><span class="line"> <span class="keyword">if</span> (err) reject(err);</span><br><span class="line"> resolve(data);</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">read(<span class="string">'./name.txt'</span>).then(<span class="function">(<span class="params">data</span>)=></span>{</span><br><span class="line"> <span class="keyword">return</span> read(data) </span><br><span class="line">}).then(<span class="function">(<span class="params">data</span>)=></span>{</span><br><span class="line"> <span class="keyword">return</span> read(data) </span><br><span class="line">}).then(<span class="function">(<span class="params">data</span>)=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data);</span><br><span class="line">},err=>{</span><br><span class="line"> <span class="built_in">console</span>.log(err);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>臃肿的嵌套变得线性多了有木有?没错,他就是我们的异步神器 Promise!</p><p>让我们再次回归刚才的问题,<strong>Promise 为我们解决了什么问题?</strong>在传统的异步编程中,如果异步之间存在依赖关系,就需要通过层层嵌套回调的方式满足这种依赖,如果嵌套层数过多,可读性和可以维护性都会变得很差,产生所谓的“回调地狱”,而 Promise 将嵌套调用改为链式调用,增加了可阅读性和可维护性。也就是说,Promise 解决的是异步编码风格的问题。<strong>那 Promise 的业界实现都有哪些呢?</strong>业界比较著名的实现 Promise 的类库有 bluebird、Q、ES6-Promise。</p><h2 id="从零开始,手写-Promise"><a href="#从零开始,手写-Promise" class="headerlink" title="从零开始,手写 Promise"></a>从零开始,手写 Promise</h2><h3 id="Promise-A"><a href="#Promise-A" class="headerlink" title="Promise/A+"></a>Promise/A+</h3><p>我们想要手写一个 Promise,就要遵循 <a href="https://promisesaplus.com/" rel="external nofollow noopener noreferrer" target="_blank">Promise/A+</a> 规范,业界所有 Promise 的类库都遵循这个规范。</p><p>其实 Promise/A+ 规范对如何实现一个符合标准的 Promise 类库已经阐述的很详细了。每一行代码在 Promise/A+ 规范中都有迹可循,所以在下面的实现的过程中,我会尽可能的将代码和 Promise/A+ 规范一一对应起来。</p><p>下面开始步入正题啦~</p><h3 id="基础版-Promise"><a href="#基础版-Promise" class="headerlink" title="基础版 Promise"></a>基础版 Promise</h3><p>我们先来回顾下最简单的 Promise 使用方式:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'create a promise'</span>);</span><br><span class="line"> resolve(<span class="string">'成功了'</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"after new promise"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p2 = p1.then(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(data)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'失败了'</span>)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p3 = p2.then(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'success'</span>, data)</span><br><span class="line">}, err => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'faild'</span>, err)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>控制台输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"create a promise"</span></span><br><span class="line"><span class="string">"after new promise"</span></span><br><span class="line"><span class="string">"成功了"</span></span><br><span class="line"><span class="string">"faild Error: 失败了"</span></span><br></pre></td></tr></table></figure><ul><li>首先我们在调用 Promise 时,会返回一个 Promise 对象。</li><li>构建 Promise 对象时,需要传入一个 <strong>executor 函数</strong>,Promise 的主要业务流程都在 executor 函数中执行。</li><li>如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。</li><li>Promise 的状态不可逆,同时调用 resolve 函数和 reject 函数,默认会采取第一次调用的结果。</li></ul><p>以上简单介绍了 Promise 的一些主要的使用方法,结合 <a href="https://promisesaplus.com/" rel="external nofollow noopener noreferrer" target="_blank">Promise/A+</a> 规范,我们可以分析出 Promise 的基本特征:</p><ol><li>promise 有三个状态:<code>pending</code>,<code>fulfilled</code>,or <code>rejected</code>;「规范 Promise/A+ 2.1」</li><li><code>new promise</code>时, 需要传递一个<code>executor()</code>执行器,执行器立即执行;</li><li><code>executor</code>接受两个参数,分别是<code>resolve</code>和<code>reject</code>;</li><li>promise 的默认状态是 <code>pending</code>;</li><li>promise 有一个<code>value</code>保存成功状态的值,可以是<code>undefined/thenable/promise</code>;「规范 Promise/A+ 1.3」</li><li>promise 有一个<code>reason</code>保存失败状态的值;「规范 Promise/A+ 1.5」</li><li>promise 只能从<code>pending</code>到<code>rejected</code>, 或者从<code>pending</code>到<code>fulfilled</code>,状态一旦确认,就不会再改变;</li><li>promise 必须有一个<code>then</code>方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」</li><li>如果调用 then 时,promise 已经成功,则执行<code>onFulfilled</code>,参数是<code>promise</code>的<code>value</code>;</li><li>如果调用 then 时,promise 已经失败,那么执行<code>onRejected</code>, 参数是<code>promise</code>的<code>reason</code>;</li><li>如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调<code>onRejected</code>;</li></ol><p>按照上面的特征,我们试着勾勒下 Promise 的形状:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 三个状态:PENDING、FULFILLED、REJECTED</span></span><br><span class="line"><span class="keyword">const</span> PENDING = <span class="string">'PENDING'</span>;</span><br><span class="line"><span class="keyword">const</span> FULFILLED = <span class="string">'FULFILLED'</span>;</span><br><span class="line"><span class="keyword">const</span> REJECTED = <span class="string">'REJECTED'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Promise</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(executor) {</span><br><span class="line"> <span class="comment">// 默认状态为 PENDING</span></span><br><span class="line"> <span class="keyword">this</span>.status = PENDING;</span><br><span class="line"> <span class="comment">// 存放成功状态的值,默认为 undefined</span></span><br><span class="line"> <span class="keyword">this</span>.value = <span class="literal">undefined</span>;</span><br><span class="line"> <span class="comment">// 存放失败状态的值,默认为 undefined</span></span><br><span class="line"> <span class="keyword">this</span>.reason = <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调用此方法就是成功</span></span><br><span class="line"> <span class="keyword">let</span> resolve = <span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="comment">// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法</span></span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = FULFILLED;</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调用此方法就是失败</span></span><br><span class="line"> <span class="keyword">let</span> reject = <span class="function">(<span class="params">reason</span>) =></span> {</span><br><span class="line"> <span class="comment">// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法</span></span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = REJECTED;</span><br><span class="line"> <span class="keyword">this</span>.reason = reason;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 立即执行,将 resolve 和 reject 函数传给使用者 </span></span><br><span class="line"> executor(resolve,reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="comment">// 发生异常时执行失败逻辑</span></span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected</span></span><br><span class="line"> then(onFulfilled, onRejected) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === FULFILLED) {</span><br><span class="line"> onFulfilled(<span class="keyword">this</span>.value)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === REJECTED) {</span><br><span class="line"> onRejected(<span class="keyword">this</span>.reason)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>写完代码我们可以测试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> resolve(<span class="string">'成功'</span>);</span><br><span class="line">}).then(</span><br><span class="line"> (data) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'success'</span>, data)</span><br><span class="line"> },</span><br><span class="line"> (err) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'faild'</span>, err)</span><br><span class="line"> }</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>控制台输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"success 成功"</span></span><br></pre></td></tr></table></figure><p>现在我们已经实现了一个基础版的 Promise,但是还不要高兴的太早噢,这里我们只处理了同步操作的 promise。如果在 <code>executor()</code>中传入一个异步操作的话呢,我们试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="comment">// 传入一个异步操作</span></span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> resolve(<span class="string">'成功'</span>);</span><br><span class="line"> },<span class="number">1000</span>);</span><br><span class="line">}).then(</span><br><span class="line"> (data) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'success'</span>, data)</span><br><span class="line"> },</span><br><span class="line"> (err) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'faild'</span>, err)</span><br><span class="line"> }</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>执行测试脚本后发现,promise 没有任何返回。</p><p>因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在<code>executor()</code>的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调。</p><p>结合这个思路,我们优化一下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> PENDING = <span class="string">'PENDING'</span>;</span><br><span class="line"><span class="keyword">const</span> FULFILLED = <span class="string">'FULFILLED'</span>;</span><br><span class="line"><span class="keyword">const</span> REJECTED = <span class="string">'REJECTED'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Promise</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(executor) {</span><br><span class="line"> <span class="keyword">this</span>.status = PENDING;</span><br><span class="line"> <span class="keyword">this</span>.value = <span class="literal">undefined</span>;</span><br><span class="line"> <span class="keyword">this</span>.reason = <span class="literal">undefined</span>;</span><br><span class="line"> <span class="comment">// 存放成功的回调</span></span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks = [];</span><br><span class="line"> <span class="comment">// 存放失败的回调</span></span><br><span class="line"> <span class="keyword">this</span>.onRejectedCallbacks= [];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> resolve = <span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = FULFILLED;</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> <span class="comment">// 依次将对应的函数执行</span></span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks.forEach(<span class="function"><span class="params">fn</span>=></span>fn());</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> reject = <span class="function">(<span class="params">reason</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = REJECTED;</span><br><span class="line"> <span class="keyword">this</span>.reason = reason;</span><br><span class="line"> <span class="comment">// 依次将对应的函数执行</span></span><br><span class="line"> <span class="keyword">this</span>.onRejectedCallbacks.forEach(<span class="function"><span class="params">fn</span>=></span>fn());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> executor(resolve,reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> then(onFulfilled, onRejected) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === FULFILLED) {</span><br><span class="line"> onFulfilled(<span class="keyword">this</span>.value)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === REJECTED) {</span><br><span class="line"> onRejected(<span class="keyword">this</span>.reason)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="comment">// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行</span></span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks.push(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> onFulfilled(<span class="keyword">this</span>.value)</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行</span></span><br><span class="line"> <span class="keyword">this</span>.onRejectedCallbacks.push(<span class="function"><span class="params">()</span>=></span> {</span><br><span class="line"> onRejected(<span class="keyword">this</span>.reason);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> resolve(<span class="string">'成功'</span>);</span><br><span class="line"> },<span class="number">1000</span>);</span><br><span class="line">}).then(</span><br><span class="line"> (data) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'success'</span>, data)</span><br><span class="line"> },</span><br><span class="line"> (err) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'faild'</span>, err)</span><br><span class="line"> }</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>控制台等待 <code>1s</code> 后输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"success 成功"</span></span><br></pre></td></tr></table></figure><p>ok!大功告成,异步问题已经解决了!</p><p>熟悉设计模式的同学,应该意识到了这其实是一个<strong>发布订阅模式</strong>,这种<code>收集依赖 -> 触发通知 -> 取出依赖执行</code>的方式,被广泛运用于发布订阅模式的实现。</p><h3 id="then-的链式调用-amp-值穿透特性"><a href="#then-的链式调用-amp-值穿透特性" class="headerlink" title="then 的链式调用&值穿透特性"></a>then 的链式调用&值穿透特性</h3><p>我们都知道,promise 的优势在于可以链式调用。在我们使用 Promise 的时候,当 then 函数中 return 了一个值,不管是什么值,我们都能在下一个 then 中获取到,这就是所谓的<strong>then 的链式调用</strong>。而且,当我们不在 then 中放入参数,例:<code>promise.then().then()</code>,那么其后面的 then 依旧可以得到之前 then 返回的值,这就是所谓的<strong>值的穿透</strong>。那具体如何实现呢?简单思考一下,如果每次调用 then 的时候,我们都重新创建一个 promise 对象,并把上一个 then 的返回结果传给这个新的 promise 的 then 方法,不就可以一直 then 下去了么?那我们来试着实现一下。这也是手写 Promise 源码的重中之重,所以,打起精神来,重头戏来咯!</p><p>有了上面的想法,我们再结合 <a href="https://promisesaplus.com/" rel="external nofollow noopener noreferrer" target="_blank">Promise/A+</a> 规范梳理一下思路:</p><ol><li>then 的参数 <code>onFulfilled</code> 和 <code>onRejected</code> 可以缺省,如果 <code>onFulfilled</code> 或者 <code>onRejected</code>不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;「规范 Promise/A+ 2.2.1、2.2.1.1、2.2.1.2」</li><li>promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise”;「规范 Promise/A+ 2.2.7」</li><li>如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;</li><li>如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.2.7.2」</li><li>如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」</li><li>如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」</li><li>如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;「规范 Promise/A+ 2.3.3.3.3」</li></ol><p>我们将代码补充完整:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> PENDING = <span class="string">'PENDING'</span>;</span><br><span class="line"><span class="keyword">const</span> FULFILLED = <span class="string">'FULFILLED'</span>;</span><br><span class="line"><span class="keyword">const</span> REJECTED = <span class="string">'REJECTED'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> resolvePromise = <span class="function">(<span class="params">promise2, x, resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="comment">// 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1</span></span><br><span class="line"> <span class="keyword">if</span> (promise2 === x) { </span><br><span class="line"> <span class="keyword">return</span> reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'Chaining cycle detected for promise #<Promise>'</span>))</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Promise/A+ 2.3.3.3.3 只能调用一次</span></span><br><span class="line"> <span class="keyword">let</span> called;</span><br><span class="line"> <span class="comment">// 后续的条件要严格判断 保证代码能和别的库一起使用</span></span><br><span class="line"> <span class="keyword">if</span> ((<span class="keyword">typeof</span> x === <span class="string">'object'</span> && x != <span class="literal">null</span>) || <span class="keyword">typeof</span> x === <span class="string">'function'</span>) { </span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候) Promise/A+ 2.3.3.1</span></span><br><span class="line"> <span class="keyword">let</span> then = x.then;</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> then === <span class="string">'function'</span>) { </span><br><span class="line"> <span class="comment">// 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3</span></span><br><span class="line"> then.call(x, y => { <span class="comment">// 根据 promise 的状态决定是成功还是失败</span></span><br><span class="line"> <span class="keyword">if</span> (called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1</span></span><br><span class="line"> resolvePromise(promise2, y, resolve, reject); </span><br><span class="line"> }, r => {</span><br><span class="line"> <span class="comment">// 只要失败就失败 Promise/A+ 2.3.3.3.2</span></span><br><span class="line"> <span class="keyword">if</span> (called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> reject(r);</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4</span></span><br><span class="line"> resolve(x);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> <span class="comment">// Promise/A+ 2.3.3.2</span></span><br><span class="line"> <span class="keyword">if</span> (called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4 </span></span><br><span class="line"> resolve(x)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Promise</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(executor) {</span><br><span class="line"> <span class="keyword">this</span>.status = PENDING;</span><br><span class="line"> <span class="keyword">this</span>.value = <span class="literal">undefined</span>;</span><br><span class="line"> <span class="keyword">this</span>.reason = <span class="literal">undefined</span>;</span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks = [];</span><br><span class="line"> <span class="keyword">this</span>.onRejectedCallbacks= [];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> resolve = <span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = FULFILLED;</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks.forEach(<span class="function"><span class="params">fn</span>=></span>fn());</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> reject = <span class="function">(<span class="params">reason</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = REJECTED;</span><br><span class="line"> <span class="keyword">this</span>.reason = reason;</span><br><span class="line"> <span class="keyword">this</span>.onRejectedCallbacks.forEach(<span class="function"><span class="params">fn</span>=></span>fn());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> executor(resolve,reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> then(onFulfilled, onRejected) {</span><br><span class="line"> <span class="comment">//解决 onFufilled,onRejected 没有传值的问题</span></span><br><span class="line"> <span class="comment">//Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4</span></span><br><span class="line"> onFulfilled = <span class="keyword">typeof</span> onFulfilled === <span class="string">'function'</span> ? onFulfilled : <span class="function"><span class="params">v</span> =></span> v;</span><br><span class="line"> <span class="comment">//因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获</span></span><br><span class="line"> onRejected = <span class="keyword">typeof</span> onRejected === <span class="string">'function'</span> ? onRejected : <span class="function"><span class="params">err</span> =></span> { <span class="keyword">throw</span> err };</span><br><span class="line"> <span class="comment">// 每次调用 then 都返回一个新的 promise Promise/A+ 2.2.7</span></span><br><span class="line"> <span class="keyword">let</span> promise2 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === FULFILLED) {</span><br><span class="line"> <span class="comment">//Promise/A+ 2.2.2</span></span><br><span class="line"> <span class="comment">//Promise/A+ 2.2.4 --- setTimeout</span></span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//Promise/A+ 2.2.7.1</span></span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(<span class="keyword">this</span>.value);</span><br><span class="line"> <span class="comment">// x可能是一个proimise</span></span><br><span class="line"> resolvePromise(promise2, x, resolve, reject);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> <span class="comment">//Promise/A+ 2.2.7.2</span></span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === REJECTED) {</span><br><span class="line"> <span class="comment">//Promise/A+ 2.2.3</span></span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(<span class="keyword">this</span>.reason);</span><br><span class="line"> resolvePromise(promise2, x, resolve, reject);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks.push(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(<span class="keyword">this</span>.value);</span><br><span class="line"> resolvePromise(promise2, x, resolve, reject);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.onRejectedCallbacks.push(<span class="function"><span class="params">()</span>=></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(<span class="keyword">this</span>.reason);</span><br><span class="line"> resolvePromise(promise2, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> promise2;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> reject(<span class="string">'失败'</span>);</span><br><span class="line">}).then().then().then(<span class="function"><span class="params">data</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data);</span><br><span class="line">},err=>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'err'</span>,err);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>控制台输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"失败 err"</span></span><br></pre></td></tr></table></figure><p>至此,我们已经完成了 promise 最关键的部分:then 的链式调用和值的穿透。搞清楚了 then 的链式调用和值的穿透,你也就搞清楚了 Promise。</p><h3 id="测试-Promise-是否符合规范"><a href="#测试-Promise-是否符合规范" class="headerlink" title="测试 Promise 是否符合规范"></a>测试 Promise 是否符合规范</h3><p>Promise/A+规范提供了一个专门的测试脚本,可以测试所编写的代码是否符合Promise/A+的规范。</p><p>首先,在 promise 实现的代码中,增加以下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.defer = <span class="built_in">Promise</span>.deferred = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> dfd = {};</span><br><span class="line"> dfd.promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=></span>{</span><br><span class="line"> dfd.resolve = resolve;</span><br><span class="line"> dfd.reject = reject;</span><br><span class="line"> })</span><br><span class="line"> <span class="keyword">return</span> dfd;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>安装测试脚本:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g promises-aplus-tests</span><br></pre></td></tr></table></figure><p>如果当前的 promise 源码的文件名为 promise.js</p><p>那么在对应的目录执行以下命令:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">promises-aplus-tests promise.js</span><br></pre></td></tr></table></figure><p>promises-aplus-tests 中共有 872 条测试用例。以上代码,可以完美通过所有用例。</p><h2 id="Promise-的-API"><a href="#Promise-的-API" class="headerlink" title="Promise 的 API"></a>Promise 的 API</h2><p>虽然上述的 promise 源码已经符合 Promise/A+ 的规范,但是原生的 Promise 还提供了一些其他方法,如:</p><ul><li>Promise.resolve()</li><li>Promise.reject()</li><li>Promise.prototype.catch()</li><li>Promise.prototype.finally()</li><li>Promise.all()</li><li>Promise.race()</li></ul><p>下面具体说一下每个方法的实现:</p><h3 id="Promise-resolve"><a href="#Promise-resolve" class="headerlink" title="Promise.resolve"></a>Promise.resolve</h3><p>默认产生一个成功的 promise。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> resolve(data){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=></span>{</span><br><span class="line"> resolve(data);</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里需要注意的是,<strong>promise.resolve 是具备等待功能的</strong>。如果参数是 promise 会等待这个 promise 解析完毕,在向下执行,所以这里需要在 resolve 方法中做一个小小的处理:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> resolve = <span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="comment">// ======新增逻辑======</span></span><br><span class="line"> <span class="comment">// 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析</span></span><br><span class="line"> <span class="keyword">if</span>(value <span class="keyword">instanceof</span> <span class="built_in">Promise</span>){</span><br><span class="line"> <span class="comment">// 递归解析 </span></span><br><span class="line"> <span class="keyword">return</span> value.then(resolve,reject)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ===================</span></span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.status === PENDING) {</span><br><span class="line"> <span class="keyword">this</span>.status = FULFILLED;</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> <span class="keyword">this</span>.onResolvedCallbacks.forEach(<span class="function"><span class="params">fn</span>=></span>fn());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.resolve(<span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> resolve(<span class="string">'ok'</span>);</span><br><span class="line"> }, <span class="number">3000</span>);</span><br><span class="line">})).then(<span class="function"><span class="params">data</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data,<span class="string">'success'</span>)</span><br><span class="line">}).catch(<span class="function"><span class="params">err</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(err,<span class="string">'error'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>控制台等待 <code>3s</code> 后输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"ok success"</span></span><br></pre></td></tr></table></figure><h3 id="Promise-reject"><a href="#Promise-reject" class="headerlink" title="Promise.reject"></a>Promise.reject</h3><p>默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> reject(reason){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=></span>{</span><br><span class="line"> reject(reason);</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Promise-prototype-catch"><a href="#Promise-prototype-catch" class="headerlink" title="Promise.prototype.catch"></a>Promise.prototype.catch</h3><p>Promise.prototype.catch 用来捕获 promise 的异常,<strong>就相当于一个没有成功的 then</strong>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.prototype.catch = <span class="function"><span class="keyword">function</span>(<span class="params">errCallback</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.then(<span class="literal">null</span>,errCallback)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Promise-prototype-finally"><a href="#Promise-prototype-finally" class="headerlink" title="Promise.prototype.finally"></a>Promise.prototype.finally</h3><p>finally 表示不是最终的意思,而是无论如何都会执行的意思。<br>如果返回一个 promise 会等待这个 promise 也执行完毕。如果返回的是成功的 promise,会采用上一次的结果;如果返回的是失败的 promise,会用这个失败的结果,传到 catch 中。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.prototype.finally = <span class="function"><span class="keyword">function</span>(<span class="params">callback</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.then(<span class="function">(<span class="params">value</span>)=></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(callback()).then(<span class="function"><span class="params">()</span>=></span>value)</span><br><span class="line"> },(reason)=>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(callback()).then(<span class="function"><span class="params">()</span>=></span>{<span class="keyword">throw</span> reason})</span><br><span class="line"> }) </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.resolve(<span class="number">456</span>).finally(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=></span>{</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> resolve(<span class="number">123</span>)</span><br><span class="line"> }, <span class="number">3000</span>);</span><br><span class="line"> })</span><br><span class="line">}).then(<span class="function"><span class="params">data</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(data,<span class="string">'success'</span>)</span><br><span class="line">}).catch(<span class="function"><span class="params">err</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(err,<span class="string">'error'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>控制台等待 <code>3s</code> 后输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"456 success"</span></span><br></pre></td></tr></table></figure><h3 id="Promise-all"><a href="#Promise-all" class="headerlink" title="Promise.all"></a>Promise.all</h3><p>promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.all = <span class="function"><span class="keyword">function</span>(<span class="params">values</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">Array</span>.isArray(values)) {</span><br><span class="line"> <span class="keyword">const</span> type = <span class="keyword">typeof</span> values;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">`TypeError: <span class="subst">${type}</span> <span class="subst">${values}</span> is not iterable`</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> resultArr = [];</span><br><span class="line"> <span class="keyword">let</span> orderIndex = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">const</span> processResultByKey = <span class="function">(<span class="params">value, index</span>) =></span> {</span><br><span class="line"> resultArr[index] = value;</span><br><span class="line"> <span class="keyword">if</span> (++orderIndex === values.length) {</span><br><span class="line"> resolve(resultArr)</span><br><span class="line"> }</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 < values.length; i++) {</span><br><span class="line"> <span class="keyword">let</span> value = values[i];</span><br><span class="line"> <span class="keyword">if</span> (value && <span class="keyword">typeof</span> value.then === <span class="string">'function'</span>) {</span><br><span class="line"> value.then(<span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> processResultByKey(value, i);</span><br><span class="line"> }, reject);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> processResultByKey(value, i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> resolve(<span class="string">'ok1'</span>);</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p2 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> reject(<span class="string">'ok2'</span>);</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.all([<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,p1,p2]).then(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'resolve'</span>, data);</span><br><span class="line">}, err => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'reject'</span>, err);</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>控制台等待 <code>1s</code> 后输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"resolve [ 1, 2, 3, 'ok1', 'ok2' ]"</span></span><br></pre></td></tr></table></figure><h3 id="Promise-race"><a href="#Promise-race" class="headerlink" title="Promise.race"></a>Promise.race</h3><p>Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.race = <span class="function"><span class="keyword">function</span>(<span class="params">promises</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="comment">// 一起执行就是for循环</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < promises.length; i++) {</span><br><span class="line"> <span class="keyword">let</span> val = promises[i];</span><br><span class="line"> <span class="keyword">if</span> (val && <span class="keyword">typeof</span> val.then === <span class="string">'function'</span>) {</span><br><span class="line"> val.then(resolve, reject);</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// 普通值</span></span><br><span class="line"> resolve(val)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>特别需要注意的是:因为<strong>Promise 是没有中断方法的</strong>,xhr.abort()、ajax 有自己的中断方法,axios 是基于 ajax 实现的;fetch 基于 promise,所以他的请求是无法中断的。</p><p>这也是 promise 存在的缺陷,我们可以使用 race 来自己封装中断方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">wrap</span>(<span class="params">promise</span>) </span>{</span><br><span class="line"> <span class="comment">// 在这里包装一个 promise,可以控制原来的promise是成功还是失败</span></span><br><span class="line"> <span class="keyword">let</span> abort;</span><br><span class="line"> <span class="keyword">let</span> newPromise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> { <span class="comment">// defer 方法</span></span><br><span class="line"> abort = reject;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">let</span> p = <span class="built_in">Promise</span>.race([promise, newPromise]); <span class="comment">// 任何一个先成功或者失败 就可以获取到结果</span></span><br><span class="line"> p.abort = abort;</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> { <span class="comment">// 模拟的接口调用 ajax 肯定有超时设置</span></span><br><span class="line"> resolve(<span class="string">'成功'</span>);</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> newPromise = wrap(promise);</span><br><span class="line"></span><br><span class="line">setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="comment">// 超过3秒 就算超时 应该让 proimise 走到失败态</span></span><br><span class="line"> newPromise.abort(<span class="string">'超时了'</span>);</span><br><span class="line">}, <span class="number">3000</span>);</span><br><span class="line"></span><br><span class="line">newPromise.then((<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'成功的结果'</span> + data)</span><br><span class="line">})).catch(<span class="function"><span class="params">e</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'失败的结果'</span> + e)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>控制台等待 <code>1s</code> 后输出:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"成功的结果成功"</span></span><br></pre></td></tr></table></figure><h2 id="promisify"><a href="#promisify" class="headerlink" title="promisify"></a>promisify</h2><p>promisify 是把一个 node 中的 api 转换成 promise 的写法。<br>在 node 版本 12.18 以上,已经支持了原生的 promisify 方法:<code>const fs = require('fs').promises</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promisify = <span class="function">(<span class="params">fn</span>) =></span> { <span class="comment">// 典型的高阶函数 参数是函数 返回值是函数 </span></span><br><span class="line"> <span class="keyword">return</span> <span class="function">(<span class="params">...args</span>)=></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=></span>{</span><br><span class="line"> fn(...args,<span class="function"><span class="keyword">function</span> (<span class="params">err,data</span>) </span>{ <span class="comment">// node中的回调函数的参数 第一个永远是error</span></span><br><span class="line"> <span class="keyword">if</span>(err) <span class="keyword">return</span> reject(err);</span><br><span class="line"> resolve(data);</span><br><span class="line"> })</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果想要把 node 中所有的 api 都转换成 promise 的写法呢:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promisifyAll = <span class="function">(<span class="params">target</span>) =></span>{</span><br><span class="line"> <span class="built_in">Reflect</span>.ownKeys(target).forEach(<span class="function"><span class="params">key</span>=></span>{</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">typeof</span> target[key] === <span class="string">'function'</span>){</span><br><span class="line"> <span class="comment">// 默认会将原有的方法 全部增加一个 Async 后缀 变成 promise 写法</span></span><br><span class="line"> target[key+<span class="string">'Async'</span>] = promisify(target[key]);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> target;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>写了将近两万字,到这里,我们终于可以给手写 promise 做一个结尾了。我们限时通过最简单的 promise 使用方法入手,构造出了 promise 的大致框架,然后根据 promise/A+ 规范进行填充代码,重点实现了 then 的链式调用和值的穿透;然后使用测试脚本对所写的代码是否符合规范进行了测试;最后完成了 Promise 的 API 的实现。弄懂 promise 其实并不复杂,归根结底还是孰能生巧,没事还是要多加练习才行。 </p><p>由于篇幅过长所以这篇文章主要讲了面试题的1、2、3、4、6,关于第五点我会在讲 EventLoop 的文章中再进行系统的梳理,相信在你看过 Promise 的源码之后,再理解 EventLoop,也会更加好理解了。</p><p>计划输出的相关内容文章:</p><ol><li>JS 的循环机制EventLoop(主线程、微任务、渲染、宏任务)。</li><li>Generator/async+await 的实现。</li></ol><p>如果你对我的文章感兴趣,欢迎关注我噢!如果你对文章有任何的疑问,也欢迎给我留言~</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://juejin.im/post/5e3b9ae26fb9a07ca714a5cc" rel="external nofollow noopener noreferrer" target="_blank">9k字 | Promise/async/Generator实现原理解析</a><br><a href="https://juejin.im/post/5b32f552f265da59991155f0" rel="external nofollow noopener noreferrer" target="_blank">Promise之你看得懂的Promise</a><br><a href="https://juejin.im/post/5c88e427f265da2d8d6a1c84" rel="external nofollow noopener noreferrer" target="_blank">Promise的源码实现(完美符合Promise/A+规范)</a></p>]]></content>
<summary type="html">
<img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/375274245.jpg" width="800" height="100%">
</summary>
<category term="Promise" scheme="https://litgod.net/categories/Promise/"/>
<category term="Promise" scheme="https://litgod.net/tags/Promise/"/>
</entry>
<entry>
<title>深拷贝与浅拷贝</title>
<link href="https://litgod.net/2020/06/18/%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D/"/>
<id>https://litgod.net/2020/06/18/%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D/</id>
<published>2020-06-18T09:47:43.000Z</published>
<updated>2020-08-25T08:18:57.431Z</updated>
<content type="html"><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/veer-333382587.jpg" alt="image"></p><a id="more"></a><p>这是一道经典的面试题,相信大多数同学都有被面试官问过的经历,那么你能实现几种深拷贝和浅拷贝的方法?让我们来一起总结常用的深浅拷贝(克隆)的方法吧!</p><h2 id="开始之前"><a href="#开始之前" class="headerlink" title="开始之前"></a>开始之前</h2><p>在开始之前,我们要先明确一下 JS 的数据类型,以及数据存储(栈和堆)的概念:</p><ul><li>JS 数据类型分为<strong>基本数据类型</strong>和<strong>引用数据类型</strong>(引用数据类型又称复杂数据类型)</li></ul><table><thead><tr><th>基本数据类型</th><th align="center">引用数据类型</th></tr></thead><tbody><tr><td>Number</td><td align="center">Object</td></tr><tr><td>String</td><td align="center">Function</td></tr><tr><td>Boolean</td><td align="center">Array</td></tr><tr><td>Undefind</td><td align="center">Date</td></tr><tr><td>Null</td><td align="center">RegExp</td></tr><tr><td>Symbol(ES6 新增)</td><td align="center">Math</td></tr><tr><td>BigInt(ES10 新增)</td><td align="center">…都是Object类型的实例对象</td></tr></tbody></table><ul><li>基本数据类型和引用数据类型的储存方式区别:</li></ul><p>基本数据类型:变量名和值都储存在栈内存中;<br>引用数据类型:变量名储存在<strong>栈内存</strong>中,值储存在<strong>堆内存</strong>中,堆内存中会提供一个引用地址指向堆内存中的值,而这个引用地址是储存在栈内存中的。</p><p>例如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="number">100</span>,</span><br><span class="line"> b: <span class="string">'name'</span>,</span><br><span class="line"> c:[<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>],</span><br><span class="line"> d:{</span><br><span class="line"> x:<span class="number">10</span></span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>obj 在内存中的储存如下:</p><table><thead><tr><th>栈内存</th><th align="center">栈内存</th><th align="right">堆内存</th></tr></thead><tbody><tr><td>name</td><td align="center">val</td><td align="right">val</td></tr><tr><td>a</td><td align="center">100</td><td align="right">—</td></tr><tr><td>b</td><td align="center">‘name’</td><td align="right">—</td></tr><tr><td>c</td><td align="center">AAAFFF000(一个引用地址,指向堆内存的值)</td><td align="right">[10,20,30]</td></tr><tr><td>d</td><td align="center">BBBFFF000(一个引用地址,指向堆内存的值)</td><td align="right">{ x:10 }</td></tr></tbody></table><p>对这几个概念有了初步了解之后,接下来正式开始讲深浅拷贝。</p><h2 id="浅拷贝"><a href="#浅拷贝" class="headerlink" title="浅拷贝"></a>浅拷贝</h2><p>何为浅拷贝?当 obj2 拷贝了 obj 的数据,且当 obj2 的改变会导致 obj 的改变时,此时叫 obj2 浅拷贝了 obj。</p><p>举个例子1:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="string">'100'</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj2 = obj;</span><br><span class="line">obj2.a = <span class="string">'200'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(obj.a) <span class="comment">// '200'</span></span><br></pre></td></tr></table></figure><p>obj 直接赋值给 obj2 后,obj2 中 a 属性的改变导致了 obj 中 a 属性也发生了变化。</p><p>其实这里的原因也很简单,因为这种赋值方式只是将 obj 的堆内存地址赋值给了 obj2,<strong>obj 和 obj2 指向的是一个存储地址</strong>,是同一个内容,因此 obj2 的改变当然会引起 obj 的改变。</p><h3 id="常见的浅拷贝"><a href="#常见的浅拷贝" class="headerlink" title="常见的浅拷贝"></a>常见的浅拷贝</h3><p>我们以下面的对象为例:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="string">'100'</span>,</span><br><span class="line"> b: <span class="literal">undefined</span>,</span><br><span class="line"> c: <span class="literal">null</span>,</span><br><span class="line"> d: <span class="built_in">Symbol</span>(<span class="number">2</span>),</span><br><span class="line"> e: <span class="regexp">/^\d+$/</span>,</span><br><span class="line"> f: <span class="keyword">new</span> <span class="built_in">Date</span>,</span><br><span class="line"> g: <span class="literal">true</span>,</span><br><span class="line"> arr:[<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>],</span><br><span class="line"> school:{</span><br><span class="line"> name:<span class="string">'cherry'</span></span><br><span class="line"> },</span><br><span class="line"> fn: <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fn'</span>); </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>直接赋值的方法就是我们刚才所举的例子1,这种方式实现的就是纯粹的浅拷贝,obj2 的任何变化都会反映在 obj 上。</p><h3 id="方法二:使用对象的解构"><a href="#方法二:使用对象的解构" class="headerlink" title="方法二:使用对象的解构"></a>方法二:使用对象的解构</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj2 = { ...obj }</span><br></pre></td></tr></table></figure><h3 id="方法三:使用循环"><a href="#方法三:使用循环" class="headerlink" title="方法三:使用循环"></a>方法三:使用循环</h3><p>对象循环我们使用 <strong>for in</strong> 循环,但<strong>for in</strong> 循环会遍历到对象的继承属性,我们只需要它的私有属性,所以可以加一个判断方法:<strong>hasOwnProperty</strong> 保留对象私有属性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj2 = {};</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i <span class="keyword">in</span> obj) {</span><br><span class="line"> <span class="keyword">if</span>(!obj.hasOwnProperty(i)) <span class="keyword">break</span>; <span class="comment">// 这里使用 continue 也可以</span></span><br><span class="line"> obj2[i] = obj[i];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="方法四:Object-assign-target-source"><a href="#方法四:Object-assign-target-source" class="headerlink" title="方法四:Object.assign(target,source)"></a>方法四:Object.assign(target,source)</h3><p>这是ES6中新增的对象方法,对它不了解的见<a href="https://es6.ruanyifeng.com/" rel="external nofollow noopener noreferrer" target="_blank">ES6</a>对象新增方法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj2 = {};</span><br><span class="line"><span class="built_in">Object</span>.assign(obj2,obj); <span class="comment">//将 obj 拷贝到 obj2</span></span><br></pre></td></tr></table></figure><h2 id="浅拷贝总结:"><a href="#浅拷贝总结:" class="headerlink" title="浅拷贝总结:"></a>浅拷贝总结:</h2><p>方法一:就是纯粹的浅拷贝,obj2 的任何变化都会反映在 obj 上。<br>方法二、三、四:都可以实现<strong>第一层的“深拷贝”</strong>,但无法实现多层的深拷贝。比如我们修改下 obj2 的值:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">obj2.a = <span class="string">'200'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(obj.a); <span class="comment">// '100'</span></span><br><span class="line"><span class="comment">// obj.a 属性未发生变化</span></span><br><span class="line"></span><br><span class="line">obj2.school.name = <span class="string">'susan'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(obj.school.name); <span class="comment">// 'sucan'</span></span><br><span class="line"><span class="comment">// obj.school.name 属性随着 obj2 而变化了</span></span><br></pre></td></tr></table></figure><p>这几种拷贝方法无法满足更深层级的拷贝,所以我们需要另一种万全之策–<strong>深拷贝</strong>。</p><h2 id="深拷贝"><a href="#深拷贝" class="headerlink" title="深拷贝"></a>深拷贝</h2><h3 id="方法一:JSON-parse-和JSON-stringify"><a href="#方法一:JSON-parse-和JSON-stringify" class="headerlink" title="方法一:JSON.parse()和JSON.stringify"></a>方法一:JSON.parse()和JSON.stringify</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj2 = <span class="built_in">JSON</span>.parse(<span class="built_in">JSON</span>.stringify(obj));</span><br><span class="line"></span><br><span class="line">obj2.schoole.name= <span class="string">'susan'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(obj.school.name); <span class="comment">// 'cherry'</span></span><br><span class="line"><span class="comment">//obj 中属性值并没有改变,说明是深拷贝</span></span><br></pre></td></tr></table></figure><p>这种方法是比较简单的深拷贝,在对象属性的类型比较简单的时候,我们可以采取这种方法快速深拷贝。</p><p>但当对象属性的类型较为复杂时,就会发现这种方法虽然能实现深拷贝,但也有很多坑,运行上面的代码后发现:</p><ul><li>值为 undefined 的属性在转换后丢失;</li><li>值为 Symbol 类型的属性在转换后丢失;</li><li>值为 RegExp 对象的属性在转换后变成了空对象;</li><li>值为 函数对象的属性在转换后丢失;</li><li>值为 Date 对象的属性在转换后变成了字符串;</li><li>会抛弃对象的 constructor,所有的构造函数会指向 Object;</li><li>对象的循环引用会抛出错误。</li></ul><p>最后两种坑,我们来简单测试下:</p><ul><li><p>会抛弃对象的 constructor,所有的构造函数会指向 Object</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">person</span>(<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Cherry = <span class="keyword">new</span> person(<span class="string">'Cherry'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = {</span><br><span class="line"> a: Cherry,</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> obj2 = <span class="built_in">JSON</span>.parse(<span class="built_in">JSON</span>.stringify(obj));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(obj.a.constructor, obj2.a.constructor); <span class="comment">// [Function: person] [Function: Object]</span></span><br></pre></td></tr></table></figure></li><li><p>对象的循环引用会抛出错误</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = {};</span><br><span class="line">obj.a = obj;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj2 = <span class="built_in">JSON</span>.parse(<span class="built_in">JSON</span>.stringify(obj)); <span class="comment">// TypeError: Converting circular structure to JSON</span></span><br></pre></td></tr></table></figure></li></ul><p>是不是觉得坑很多?所以小伙伴们在使用这种方式深拷贝的时候,还是要多多注意下。<br>出现这种问题的原因和 <strong>JSON.stringify 方法的序列化</strong>规则有关系,关于JSON.stringify序列化的具体规则见 JSON.stringify 指南。</p><p>下面引用了其他文档中对JSON.stringify序列化规则的描述,供大家参考:</p><blockquote><p>对大多数简单值来说,JSON字符串化和toString()的效果基本相同,只不过序列化的结果总是字符串:</p><p>JSON.stringify(42); // “42”<br>JSON.stringify(“42”); // “”42””(含有双引号的字符串)<br>JSON.stringify(null); // “null”<br>JSON.stringify(true); // “true”</p><p>所有安全的JSON值(JSON-safe)都可以使用JSON.stringify(…)字符串化。安全的JSON值是指能够呈现为有效JSON格式的值。</p><p>为了简单起见,我们来看看什么是不安全的JSON值。undefined、function、symbol(ES6+)和包含循环引用(对象之前相互引用,形成一个无限循环)的对象都不符合JSON结构标准,其他支持JSON的语言无法处理它们。</p><p>JSON.stringify(…)在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。</p><p>例如:<br>JSON.stringify(undefined); //undefined<br>JSON.stringify(function(){}); //undefined</p><p>JSON.stringify([1,undefined,function(){},4]); //“[1, null, null, 4]”</p><p>JSON.stringify({a:2, b: function(){}}); //“{“a”: 2}”</p><p>对包含循环引用的对象执行JSON.stringify(…); 会报错。</p></blockquote><p>关于<strong>如何去 JSON.stringify 序列化</strong>也是一个比较有意思的问题,大家可以学习一下,毕竟面试官总是喜欢问到你不会为止。。。</p><h3 id="方法二:手写-deepClone"><a href="#方法二:手写-deepClone" class="headerlink" title="方法二:手写 deepClone"></a>方法二:手写 deepClone</h3><p>既然第一种方法有它的弊端,那最终极的方法,就是手写一个 deepClone 了。</p><p>用过<code>lodash</code>的小伙伴都知道<code>lodash</code>提供了<code>_.cloneDeep</code> 方法深克隆,想看 lodash 实现源码的可以点击<a href="https://github.com/lodash/lodash/blob/master/cloneDeep.js" rel="external nofollow noopener noreferrer" target="_blank">这里</a>,它的源码里实现的比较复杂,考虑的情况比较多,我们写一个简单版的深拷贝可以在自己项目中使用即可。</p><p>简单的实现思路:<br>1.遍历带拷贝的对象,判断是不是原始值,若是,使用浅拷贝的方式进行赋值<br>2.若是引用值,将特殊类型逐一进行过滤,并且兼容引用值是数组的情况<br>3.待拷贝的对象里面的若是原始值,则浅拷贝即可实现,若还有引用值,则还需要重复进行上述一系列的判断。(递归赋值)</p><p>上述思路用代码如何实现呢?</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="string">'100'</span>,</span><br><span class="line"> b: <span class="literal">undefined</span>,</span><br><span class="line"> c: <span class="literal">null</span>,</span><br><span class="line"> d: <span class="built_in">Symbol</span>(<span class="number">2</span>),</span><br><span class="line"> e: <span class="regexp">/^\d+$/</span>,</span><br><span class="line"> f: <span class="keyword">new</span> <span class="built_in">Date</span>,</span><br><span class="line"> g: <span class="literal">true</span>,</span><br><span class="line"> arr: [<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>],</span><br><span class="line"> school:{</span><br><span class="line"> name: <span class="string">'cherry'</span>,</span><br><span class="line"> },</span><br><span class="line"> fn: <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fn'</span>); </span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepClone</span>(<span class="params">obj</span>) </span>{</span><br><span class="line"> <span class="comment">// 先把特殊情况全部过滤掉 null undefined date reg</span></span><br><span class="line"> <span class="keyword">if</span> (obj == <span class="literal">null</span>) <span class="keyword">return</span> obj; <span class="comment">// null 和 undefined 都不用处理</span></span><br><span class="line"> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">Date</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Date</span>(obj);</span><br><span class="line"> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">RegExp</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">RegExp</span>(obj);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> obj !== <span class="string">'object'</span>) <span class="keyword">return</span> obj; <span class="comment">// 普通常量直接返回</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 不直接创建空对象的目的:克隆的结果和之前保持相同的所属类,</span></span><br><span class="line"> <span class="comment">// 同时也兼容了数组的情况</span></span><br><span class="line"> <span class="keyword">let</span> newObj = <span class="keyword">new</span> obj.constructor;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> obj) {</span><br><span class="line"> <span class="keyword">if</span> (obj.hasOwnProperty(key)) { <span class="comment">// 不拷贝原型链上的属性</span></span><br><span class="line"> newObj[key] = deepClone(obj[key]); <span class="comment">// 递归赋值</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> newObj;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">let</span> obj2 = deepClone(obj);</span><br><span class="line"><span class="built_in">console</span>.log(obj2);</span><br></pre></td></tr></table></figure><p>执行代码,得到 obj2 的结果和 obj 一致,且属性值的改变彼此互不影响。</p><blockquote><p>Q:为什么 type null 会返回 object ?<br>A:因为在 js 的设计中,object的前三位标志是000,而 null 在32位表示中也全是0,因此,<code>typeof null</code> 也会打印出<code>object</code>。</p></blockquote><p>代码写到这里,我们就实现了一种比较简单的深拷贝,面试的时候如果你能写出上面的实现方法,应该算是及格啦!但是,面对复杂的,多类型的对象,以上方法还是有诸多缺陷的。</p><p>比如我们为 <code>obj</code> 中的 <code>school</code> 对象添加一个 <code>Symbol</code> 类型的属性:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//==新增代码==</span></span><br><span class="line"><span class="keyword">let</span> s1 = <span class="built_in">Symbol</span>(<span class="string">'s1'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="string">'100'</span>,</span><br><span class="line"> b: <span class="literal">undefined</span>,</span><br><span class="line"> c: <span class="literal">null</span>,</span><br><span class="line"> d: <span class="built_in">Symbol</span>(<span class="number">2</span>),</span><br><span class="line"> e: <span class="regexp">/^\d+$/</span>,</span><br><span class="line"> f: <span class="keyword">new</span> <span class="built_in">Date</span>,</span><br><span class="line"> g: <span class="literal">true</span>,</span><br><span class="line"> arr: [<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>],</span><br><span class="line"> school:{</span><br><span class="line"> name: <span class="string">'cherry'</span>,</span><br><span class="line"> <span class="comment">//==新增代码==</span></span><br><span class="line"> [s1]: <span class="string">'s1'</span></span><br><span class="line"> },</span><br><span class="line"> fn: <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fn'</span>); </span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">let</span> obj2 = deepClone(obj);</span><br><span class="line"><span class="built_in">console</span>.log(obj2);</span><br></pre></td></tr></table></figure><p>执行代码后发现 <code>school</code> 中的 <code>Symbol(s1): 's1'</code>并没有拷贝成功。这是因为声明对象的 key 为 symbol 类型是不可枚举的,要解决这个问题,我们可以使用 Object 提供的 <code>getOwnPrepertySymbols()</code>方法来枚举对象中所有 key 是 symbol 类型的属性,这个属性的详细使用说明参见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols" rel="external nofollow noopener noreferrer" target="_blank">MDN</a>,或者用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys" rel="external nofollow noopener noreferrer" target="_blank">Reflect.ownKeys()</a> 也可以实现。</p><p>还比如:如果在我们拷贝的对象被循环引用,deepClone就会一直执行下去导致爆栈,举个例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="string">'100'</span>,</span><br><span class="line"> b: <span class="literal">undefined</span>,</span><br><span class="line"> c: <span class="literal">null</span>,</span><br><span class="line"> d: <span class="built_in">Symbol</span>(<span class="number">2</span>),</span><br><span class="line"> e: <span class="regexp">/^\d+$/</span>,</span><br><span class="line"> f: <span class="keyword">new</span> <span class="built_in">Date</span>,</span><br><span class="line"> g: <span class="literal">true</span>,</span><br><span class="line"> arr: [<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>],</span><br><span class="line"> school:{</span><br><span class="line"> name: <span class="string">'cherry'</span>,</span><br><span class="line"> },</span><br><span class="line"> fn: <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fn'</span>); </span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">obj.h = obj;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj2 = deepClone(obj);</span><br><span class="line"><span class="built_in">console</span>.log(obj2);</span><br></pre></td></tr></table></figure><p>执行上述代码后,控制台抛出栈溢出错误:<code>Maximum call stack size exceeded</code>。其实解决循环引用的思路,就是在赋值之前判断当前值是否已经存在,避免循环引用,这里我们可以使用 es6 的 WeakMap 来生成一个 hash 表。</p><p>针对以上这两个问题,我们来优化一下代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s1 = <span class="built_in">Symbol</span>(<span class="string">'s1'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = {</span><br><span class="line"> a: <span class="string">'100'</span>,</span><br><span class="line"> b: <span class="literal">undefined</span>,</span><br><span class="line"> c: <span class="literal">null</span>,</span><br><span class="line"> d: <span class="built_in">Symbol</span>(<span class="number">2</span>),</span><br><span class="line"> e: <span class="regexp">/^\d+$/</span>,</span><br><span class="line"> f: <span class="keyword">new</span> <span class="built_in">Date</span>,</span><br><span class="line"> g: <span class="literal">true</span>,</span><br><span class="line"> arr: [<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>],</span><br><span class="line"> school:{</span><br><span class="line"> name:<span class="string">'cherry'</span>,</span><br><span class="line"> [s1]: <span class="string">'s1'</span></span><br><span class="line"> },</span><br><span class="line"> fn: <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'fn'</span>); </span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">obj.h = obj;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepClone</span>(<span class="params">obj, hash = new WeakMap(</span>)) </span>{</span><br><span class="line"> <span class="comment">//先把特殊情况全部过滤掉 null undefined date reg</span></span><br><span class="line"> <span class="keyword">if</span> (obj == <span class="literal">null</span>) <span class="keyword">return</span> obj; <span class="comment">//null 和 undefined 都不用处理</span></span><br><span class="line"> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">Date</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Date</span>(obj);</span><br><span class="line"> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">RegExp</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">RegExp</span>(obj);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> obj !== <span class="string">'object'</span>) <span class="keyword">return</span> obj; <span class="comment">// 普通常量直接返回</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 防止对象中的循环引用爆栈,把拷贝过的对象直接返还即可</span></span><br><span class="line"> <span class="keyword">if</span> (hash.has(obj)) <span class="keyword">return</span> hash.get(obj);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 不直接创建空对象的目的:克隆的结果和之前保持相同的所属类</span></span><br><span class="line"> <span class="comment">// 同时也兼容了数组的情况</span></span><br><span class="line"> <span class="keyword">let</span> newObj = <span class="keyword">new</span> obj.constructor;</span><br><span class="line"></span><br><span class="line"> hash.set(obj, newObj) <span class="comment">// 制作一个映射表</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//判断是否有 key 为 symbol 的属性</span></span><br><span class="line"> <span class="keyword">let</span> symKeys = <span class="built_in">Object</span>.getOwnPropertySymbols(obj);</span><br><span class="line"> <span class="keyword">if</span> (symKeys.length) { </span><br><span class="line"> symKeys.forEach(<span class="function"><span class="params">symKey</span> =></span> {</span><br><span class="line"> newObj[symKey] = deepClone(obj[symKey], hash); </span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> obj) {</span><br><span class="line"> <span class="keyword">if</span> (obj.hasOwnProperty(key)) { <span class="comment">// 不拷贝原型链上的属性</span></span><br><span class="line"> newObj[key] = deepClone(obj[key], hash); <span class="comment">// 递归赋值</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> newObj;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">let</span> obj2 = deepClone(obj);</span><br><span class="line"><span class="built_in">console</span>.log(obj2);</span><br></pre></td></tr></table></figure><p>这样,一个比较完善的深拷贝就实现啦~</p><p>不过,完善但不是完美,还有更高维度的问题需要优化,比如:1.没有考虑 es6 中 Map 和 Set 的拷贝,2.递归消耗大量的内存会导致的爆栈等等等等,想要实现一个完美的深拷贝,还是有很多内容需要我们深度学习~</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>如果你还对深拷贝有兴趣或者想研究,可以阅读lodash 深拷贝相关代码,相信你会对深拷贝有进一步的理解~</p>]]></content>
<summary type="html">
<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/veer-333382587.jpg" alt="image"></p>
</summary>
<category term="JavaScript" scheme="https://litgod.net/categories/JavaScript/"/>
<category term="JavaScript" scheme="https://litgod.net/tags/JavaScript/"/>
</entry>
<entry>
<title>(未完成)Webpack 究竟解决了什么问题?</title>
<link href="https://litgod.net/2020/06/01/Webpack%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/"/>
<id>https://litgod.net/2020/06/01/Webpack%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/</id>
<published>2020-06-01T07:30:59.000Z</published>
<updated>2020-08-25T08:19:55.906Z</updated>
<content type="html"><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i4_1.jpg" alt="image"></p><a id="more"></a><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>如果我问你 webpack 是干什么用的?大多数人都会说 webpack 是打包工具。</p><p>更准确地说:它所解决的问题是如何在前端项目中更高效地管理和维护项目中的每一个资源。</p><p>要明⽩我们的打包⼯具究竟做了什么,⾸先我们必须了解一下 JS 中的模块化以及它的发展过程。</p><p>我们来说一下前端模块化的演进过程:</p><ol><li>Stage 1 - 文件划分方式</li></ol><p>最早我们会基于文件划分的方式实现模块化,也就是 Web 最原始的模块系统。具体做法是将每个功能及其相关状态数据各自单独放到不同的 JS 文件中,约定每个文件是一个独立的模块。使用某个模块将这个模块引入到页面中,一个 script 标签对应一个模块,然后直接调用模块中的成员(变量 / 函数)。</p><p>这种方式的缺点:</p><p>1.模块直接在全局工作,大量模块成员污染全局作用域;<br>2.没有私有空间,所有模块内的成员都可以在模块外部被访问或者修改;<br>3.一旦模块增多,容易产生命名冲突;<br>4.无法管理模块与模块之间的依赖关系;<br>5.在维护的过程中也很难分辨每个成员所属的模块。</p><p>总之,这种原始“模块化”的实现方式完全依靠约定实现,一旦项目规模变大,这种约定就会暴露出种种问题,非常不可靠,所以我们需要尽可能解决这个过程中暴露出来的问题。</p><ol start="2"><li>Stage 2 – 命名空间方式</li></ol><p>后来,我们约定每个模块只暴露一个全局对象,所有模块成员都挂载到这个全局对象中,具体做法是在第一阶段的基础上,通过将每个模块“包裹”为一个全局对象的形式实现,这种方式就好像是为模块内的成员添加了“命名空间”,所以我们又称之为命名空间方式。</p><p>这种命名空间的方式只是解决了命名冲突的问题,但是其它问题依旧存在。</p><ol start="3"><li>Stage 3 – IIFE</li></ol><p>使用立即执行函数表达式(IIFE,Immediately-Invoked Function Expression)为模块提供私有空间。具体做法是将每个模块成员都放在一个立即执行函数所形成的私有作用域中,对于需要暴露给外部的成员,通过挂到全局对象上的方式实现。</p><p>这种方式带来了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问,这就解决了前面所提到的全局作用域污染和命名冲突的问题。</p><ol start="4"><li>Stage 4 - IIFE 依赖参数</li></ol><p>在 IIFE 的基础之上,我们还可以利用 IIFE 参数作为依赖声明使用,这使得每一个模块之间的依赖关系变得更加明显。</p><p>以上 4 个阶段是早期的开发者在没有工具和规范的情况下对模块化的落地方式,这些方式确实解决了很多在前端领域实现模块化的问题,但是仍然存在一些没有解决的问题。</p><p>最明显的问题就是:模块的加载。在这几种方式中虽然都解决了模块代码的组织问题,但模块加载的问题却被忽略了,我们都是通过 script 标签的方式直接在页面中引入的这些模块,这意味着模块的加载并不受代码的控制,时间久了维护起来会十分麻烦。试想一下,如果你的代码需要用到某个模块,如果 HTML 中忘记引入这个模块,又或是代码中移除了某个模块的使用,而 HTML 还忘记删除该模块的引用,都会引起很多问题和不必要的麻烦。</p><p>更为理想的方式应该是在页面中引入一个 JS 入口文件,其余用到的模块可以通过代码控制,按需加载进来。</p><p>除了模块加载的问题以外,目前这几种通过约定实现模块化的方式,不同的开发者在实施的过程中会出现一些细微的差别,因此,为了统一不同开发者、不同项目之间的差异,我们就需要制定一个行业标准去规范模块化的实现方式。</p><h2 id="模块化规范的出现"><a href="#模块化规范的出现" class="headerlink" title="模块化规范的出现"></a>模块化规范的出现</h2><h3 id="1-CommonJS"><a href="#1-CommonJS" class="headerlink" title="1.CommonJS"></a>1.CommonJS</h3><p>CommonJS 是 Node.js 中所遵循的模块规范,该规范约定,一个文件就是一个模块,每个模块都有单独的作用域,通过 module.exports 导出成员,再通过 require 函数载入模块。</p><p>我们是不可以直接在浏览器端直接使用这个规范的,CommonJS 约定的是以同步的方式加载模块,因为 Node.js 执行机制是在启动时加载模块,执行过程中只是使用模块,所以这种方式不会有问题。但是如果要在浏览器端使用同步的加载模式,就会引起大量的同步模式请求,导致应用运行效率低下。</p><h3 id="2-AMD"><a href="#2-AMD" class="headerlink" title="2.AMD"></a>2.AMD</h3><p>所以在早期制定前端模块化标准时,并没有直接选择 CommonJS 规范,而是专门为浏览器端重新设计了一个规范,叫做 AMD ( Asynchronous Module Definition) 规范,即异步模块定义规范。同期还推出了一个非常出名的库,叫做 Require.js,它除了实现了 AMD 模块化规范,本身也是一个非常强大的模块加载器。</p><p>在 AMD 规范中约定每个模块通过 define() 函数定义,这个函数默认可以接收两个参数,第一个参数是一个数组,用于声明此模块的依赖项;第二个参数是一个函数,参数与前面的依赖项一一对应,每一项分别对应依赖项模块的导出成员,这个函数的作用就是为当前模块提供一个私有空间。如果在当前模块中需要向外部导出成员,可以通过 return 的方式实现。</p><p>除此之外,Require.js 还提供了一个 require() 函数用于自动加载模块,用法与 define() 函数类似,区别在于 require() 只能用来载入模块,而 define() 还可以定义模块。当 Require.js 需要加载一个模块时,内部就会自动创建 script 标签去请求并执行相应模块的代码。</p><p>目前绝大多数第三方库都支持 AMD 规范,但是它使用起来相对复杂,而且当项目中模块划分过于细致时,就会出现同一个页面对 js 文件的请求次数过多的情况,从而导致效率降低。在当时的环境背景下,AMD 规范为前端模块化提供了一个标准,但这只是一种妥协的实现方式,并不能成为最终的解决方案。</p><p>同期出现的规范还有淘宝的 Sea.js,只不过它实现的是另外一个标准,叫作 CMD,这个标准类似于 CommonJS,在使用上基本和 Require.js 相同,可以算上是重复的轮子。但随着前端技术的发展,Sea.js 后来也被 Require.js 兼容了。如果你感兴趣可以课后了解一下 Seajs官网。</p><h3 id="ESModule"><a href="#ESModule" class="headerlink" title="ESModule"></a>ESModule</h3><p>前⾯我们说到的 CommonJS 规范和 AMD 规范有这么⼏个特点:</p><ol><li>语⾔上层的运⾏环境中实现的模块化规范,模块化规范由环境⾃⼰定义。</li><li>相互之间不能共⽤模块。例如不能在 Node.js 运⾏AMD 模块,不能直接在浏览器运⾏ CommonJS 模块。</li></ol><p>在 EcmaScript 2015 也就是我们常说的 ES6 之后,JS 有了语⾔层⾯的模块化导⼊导出关键词与语法以及与之匹配的 ESModule 规范。使⽤ ESModule 规范,我们可以通过 import 和 export 两个关键词来对模块进⾏导⼊与导出。</p><p>每个 JS 的运⾏环境都有⼀个解析器,否则这个环境也不会认识 JS 语法。它的作⽤就是⽤ ECMAScript 的规范去解释 JS 语法,也就是处理和执⾏语⾔本身的内容,例如<br>按照逻辑正确执⾏ var a = “123”;,function func() {console.log(“hahaha”);} 之类的内容。<br>在解析器的上层,每个运⾏环境都会在解释器的基础上封装⼀些环境相关的 API。例如 Node.js 中的 global对象、process 对象,浏览器中的 window 对象,document 对象等等。这些运⾏环境的 API 受到各⾃规范的影响,例如浏览器端的 W3C 规范,它们规定了 window 对象和 document 对象上的 API 内容,以使得我们能让 document.getElementById 这样的 API 在所有浏览器上运⾏正常。</p><p>ESModule 就属于 JS Core 层⾯的规范,⽽ AMD,CommonJS 是运⾏环境的规范。所以,想要使运⾏环境⽀持 ESModule 其实是⽐较简单的,只需要升级⾃⼰环境中的 JS Core 解释引擎到⾜够的版本,引擎层⾯就能认识这种语法,从⽽不认为这是个 语法错误(syntaxerror) ,运⾏环境中只需要做⼀些兼容⼯作即可。</p><p>Node.js 在 V12 版本之后才可以使⽤ ESModule 规范的模块,在 V12 没进⼊ LTS 之前,我们需要加上 –experimental-modules 的 flag 才能使⽤这样的特性,也就是通过 node –experimental-modules index.js 来执⾏。浏览器端 Chrome 61 之后的版本可以开启⽀持 ESModule 的选项,只需要通过 `` 这样的标签加载即可。</p><p>这也就是说,如果想在 Node.js 环境中使⽤ESModule,就需要升级 Node.js 到⾼版本,这相对来说⽐较容易,毕竟服务端 Node.js 版本控制在开发⼈员⾃⼰⼿中。</p><p>但浏览器端具有分布式的特点,是否能使⽤这种⾼版本特性取决于⽤户访问时的版本,⽽且这种解释器语法层⾯的内容⽆法像 AMD 那样在运⾏时进⾏兼容,所以想要直接使⽤就会⽐较麻烦。</p><p>综上所述,如何在不同的环境中去更好的使用 ES Modules 将是你重点考虑的问题。</p><h2 id="模块打包工具的出现:"><a href="#模块打包工具的出现:" class="headerlink" title="模块打包工具的出现:"></a>模块打包工具的出现:</h2><p>模块化可以帮助我们更好地解决复杂应用开发过程中的代码组织问题,但是随着模块化思想的引入,我们的前端应用又会产生了一些新的问题,比如:</p><p>1.首先,我们所使用的 ES Modules 模块系统本身就存在环境兼容问题。尽管现如今主流浏览器的最新版本都支持这一特性,但是目前还无法保证用户的浏览器使用情况。所以我们还需要解决兼容问题。</p><p>2.其次,模块化的方式划分出来的模块文件过多,而前端应用又运行在浏览器中,每一个文件都需要单独从服务器请求回来。零散的模块文件必然会导致浏览器的频繁发送网络请求,影响应用的工作效率</p><p>3.最后,谈一下在实现 JS 模块化的基础上的发散。随着应用日益复杂,在前端应用开发过程中不仅仅只有 JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题。而且从宏观角度来看,这些文件也都应该看作前端应用中的一个模块,只不过这些模块的种类和用途跟 JavaScript 不同。</p><p>对于开发过程而言,模块化肯定是必要的,所以我们需要在前面所说的模块化实现的基础之上引入更好的方案或者工具,去解决上面提出的 3 个问题,让我们的应用在开发阶段继续享受模块化带来的优势,又不必担心模块化对生产环境所产生的影响。</p><p>接下来我们先对这个更好的方案或者工具提出一些设想:</p><p>第一,它需要具备编译代码的能力,也就是将我们开发阶段编写的那些包含新特性的代码转换为能够兼容大多数环境的代码,解决我们所面临的环境兼容问题。</p><p>第二,能够将散落的模块再打包到一起,这样就解决了浏览器频繁请求模块文件的问题。这里需要注意,只是在开发阶段才需要模块化的文件划分,因为它能够帮我们更好地组织代码,到了实际运行阶段,这种划分就没有必要了</p><p>第三,它需要支持不同种类的前端模块类型,也就是说可以将开发过程中涉及的样式、图片、字体等所有资源文件都作为模块使用,这样我们就拥有了一个统一的模块化方案,所有资源文件的加载都可以通过代码控制,与业务代码统一维护,更为合理。</p><p>针对上面第一、第二个设想,我们可以借助 Gulp 之类的构建系统配合一些编译工具和插件去实现,但是对于第三个可以对不同种类资源进行模块化的设想,就很难通过这种方式去解决了,所以就有了我们接下来要介绍的主题:Webpack。</p><p>虽然 Webpack 发展到今天,它的功能已经非常强大了,但依然改变不了它是一个模块化解决方案的初衷。你可以看到, Webpack 官方的 Slogan 仍然是:A bundler for javascript and friends(一个 JavaScript 和周边的打包工具)。</p><p>从另外一个角度来看,Webpack 从一个“打包工具”,发展成现在开发者眼中对整个前端项目的“构建系统”,表面上似乎只是称呼发生了变化,但是这背后却透露出来一个信号:模块化思想是非常伟大的,伟大到可以帮你“统治”前端整个项目。这也足以见得模块化思想背后还有很多值得我们思考的内容。</p><p>总的来说,我们可以把 Webpack 看作现代化前端应用的“管家”,这个“管家”所践行的核心理论就是“模块化”,也就是说 Webpack 以模块化思想为核心,帮助开发者更好的管理整个前端工程。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ol><li>CommonJs – 运行在node,AMD – 运行在浏览器</li><li>AMD 规范实现的库很多,⽐较有名的是 require.js</li></ol>]]></content>
<summary type="html">
<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i4_1.jpg" alt="image"></p>
</summary>
<category term="前端" scheme="https://litgod.net/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="Webpack" scheme="https://litgod.net/tags/Webpack/"/>
</entry>
<entry>
<title>重排(reflow)和重绘(repaint)</title>
<link href="https://litgod.net/2020/01/30/%E9%87%8D%E7%BB%98%E5%92%8C%E9%87%8D%E6%8E%92/"/>
<id>https://litgod.net/2020/01/30/%E9%87%8D%E7%BB%98%E5%92%8C%E9%87%8D%E6%8E%92/</id>
<published>2020-01-30T14:48:22.000Z</published>
<updated>2020-08-25T08:19:13.016Z</updated>
<content type="html"><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i3_1.jpg" alt="image"></p><a id="more"></a><p>之前面试的大佬问我关于重排重绘的原理和具体操作,一下子把我问蒙了。回家便默默地把问题记下来,仔细总结……在阅读了一些文章后,自己也有了一定的理解,所以分享给大家。希望大家也能耐心把这篇文章看完,认真思考,彻底掌握这个知识点!</p><h2 id="页面生成的过程:"><a href="#页面生成的过程:" class="headerlink" title="页面生成的过程:"></a>页面生成的过程:</h2><p>1.HTML 被 HTML 解析器解析成 DOM 树;<br>2.CSS 被 CSS 解析器解析成 CSSOM 树;<br>3.结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment;<br>4.生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点;<br>5.将布局绘制(paint)在屏幕上,显示出整个页面。</p><p>第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染。</p><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i3_2.png" alt="image"></p><h2 id="渲染:"><a href="#渲染:" class="headerlink" title="渲染:"></a>渲染:</h2><p>在页面的生命周期中,<strong>网页生成的时候,至少会渲染一次</strong>。<strong>在用户访问的过程中,还会不断触发重排(reflow)和重绘(repaint)</strong>,不管页面发生了重绘还是重排,都会影响性能,<strong>最可怕的是重排,会使我们付出高额的性能代价,所以我们应尽量避免</strong>。</p><h2 id="重排比重绘大:"><a href="#重排比重绘大:" class="headerlink" title="重排比重绘大:"></a>重排比重绘大:</h2><p>大,在这个语境里的意思是:谁能影响谁?</p><ul><li>重绘:某些元素的外观被改变,例如:元素的填充颜色</li><li>重排:重新生成布局,重新排列元素。</li></ul><p>就如上面的概念一样,单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。</p><p>也就是说:<strong>重绘不一定导致重排,但重排一定会导致重绘</strong>。</p><h2 id="重排-reflow-:"><a href="#重排-reflow-:" class="headerlink" title="重排(reflow):"></a>重排(reflow):</h2><h3 id="概念:"><a href="#概念:" class="headerlink" title="概念:"></a>概念:</h3><p>当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。</p><p>重排也叫回流,简单的说就是重新生成布局,重新排列元素。</p><h3 id="下面情况会发生重排:"><a href="#下面情况会发生重排:" class="headerlink" title="下面情况会发生重排:"></a>下面情况会发生重排:</h3><ul><li>页面初始渲染,这是开销最大的一次重排</li><li>添加/删除可见的DOM元素</li><li>改变元素位置</li><li>改变元素尺寸,比如边距、填充、边框、宽度和高度等</li><li>改变元素内容,比如文字数量,图片大小等</li><li>改变元素字体大小</li><li>改变浏览器窗口尺寸,比如resize事件发生时</li><li>激活CSS伪类(例如:<code>:hover</code>)</li><li>设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow</li><li>查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 <code>getComputedStyle</code>方法,或者IE里的 <code>currentStyle</code> 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。</li></ul><table><thead><tr><th>常见引起重排属性和方法</th><th align="center">–</th><th align="right">–</th><th align="right">–</th></tr></thead><tbody><tr><td>width</td><td align="center">height</td><td align="right">margin</td><td align="right">padding</td></tr><tr><td>display</td><td align="center">border-width</td><td align="right">border</td><td align="right">position</td></tr><tr><td>overflow</td><td align="center">font-size</td><td align="right">vertical-align</td><td align="right">min-height</td></tr><tr><td>clientWidth</td><td align="center">clientHeight</td><td align="right">clientTop</td><td align="right">clientLeft</td></tr><tr><td>offsetWudth</td><td align="center">offsetHeight</td><td align="right">offsetTop</td><td align="right">offsetLeft</td></tr><tr><td>scrollWidth</td><td align="center">scrollHeight</td><td align="right">scrollTop</td><td align="right">scrollLeft</td></tr><tr><td>scrollIntoView()</td><td align="center">scrollTo()</td><td align="right">getComputedStyle()</td><td align="right"></td></tr><tr><td>getBoundingClientRect()</td><td align="center">scrollIntoViewIfNeeded()</td><td align="right"></td><td align="right"></td></tr></tbody></table><h3 id="重排影响的范围:"><a href="#重排影响的范围:" class="headerlink" title="重排影响的范围:"></a>重排影响的范围:</h3><p>由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:</p><ul><li>全局范围:从根节点html开始对整个渲染树进行重新布局。</li><li>局部范围:对渲染树的某部分或某一个渲染对象进行重新布局</li></ul><p><strong>全局范围重排:</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><body></span><br><span class="line"> <div <span class="class"><span class="keyword">class</span></span>=<span class="string">"hello"</span>></span><br><span class="line"> <h4>hello<<span class="regexp">/h4></span></span><br><span class="line"><span class="regexp"> <p><strong>Name:</</span>strong>BDing<<span class="regexp">/p></span></span><br><span class="line"><span class="regexp"> <h5>male</</span>h5></span><br><span class="line"> <ol></span><br><span class="line"> <li>coding<<span class="regexp">/li></span></span><br><span class="line"><span class="regexp"> <li>loving</</span>li></span><br><span class="line"> <<span class="regexp">/ol></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"><<span class="regexp">/body></span></span><br></pre></td></tr></table></figure><p>当p节点上发生reflow时,hello和body也会重新渲染,甚至h5和ol都会收到影响。</p><p><strong>局部范围重排:</strong></p><p>用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。</p><h2 id="重绘-Repaints"><a href="#重绘-Repaints" class="headerlink" title="重绘(Repaints):"></a>重绘(Repaints):</h2><h3 id="概念:-1"><a href="#概念:-1" class="headerlink" title="概念:"></a>概念:</h3><p>当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。</p><h3 id="常见的引起重绘的属性:"><a href="#常见的引起重绘的属性:" class="headerlink" title="常见的引起重绘的属性:"></a>常见的引起重绘的属性:</h3><table><thead><tr><th>属性:</th><th align="center">–</th><th align="right">–</th><th align="right">–</th></tr></thead><tbody><tr><td>color</td><td align="center">border-style</td><td align="right">visibility</td><td align="right">background</td></tr><tr><td>text-decoration</td><td align="center">background-image</td><td align="right">background-position</td><td align="right">background-repeat</td></tr><tr><td>outline-color</td><td align="center">outline</td><td align="right">outline-style</td><td align="right">border-radius</td></tr><tr><td>outline-width</td><td align="center">box-shadow</td><td align="right">background-size</td><td align="right"></td></tr></tbody></table><h2 id="重排优化建议:"><a href="#重排优化建议:" class="headerlink" title="重排优化建议:"></a>重排优化建议:</h2><p>重排的代价是高昂的,会破坏用户体验,并且让UI展示非常迟缓。通过减少重排的负面影响来提高用户体验的最简单方式就是尽可能的减少重排次数,重排范围。下面是一些行之有效的建议,大家可以用来参考。</p><h3 id="减少重排范围"><a href="#减少重排范围" class="headerlink" title="减少重排范围"></a>减少重排范围</h3><p>我们应该尽量以局部布局的形式组织html结构,尽可能小的影响重排的范围。</p><ul><li><p>尽可能在低层级的DOM节点上,而不是像上述全局范围的示例代码一样,如果你要改变p的样式,class就不要加在div上,通过父元素去影响子元素不好。</p></li><li><p>不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。</p></li></ul><h3 id="减少重排次数"><a href="#减少重排次数" class="headerlink" title="减少重排次数"></a>减少重排次数</h3><h4 id="1-样式集中改变"><a href="#1-样式集中改变" class="headerlink" title="1.样式集中改变"></a>1.样式集中改变</h4><p>不要频繁的操作样式,对于一个静态页面来说,明智且可维护的做法是更改类名而不是修改样式,对于动态改变的样式来说,相较每次微小修改都直接触及元素,更好的办法是统一在 <code>cssText</code> 变量中编辑。虽然现在大部分现代浏览器都会有 <code>Flush</code> 队列进行渲染队列优化,但是有些老版本的浏览器比如IE6的效率依然低下。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">var</span> left = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> top = <span class="number">10</span>;</span><br><span class="line">el.style.left = left + <span class="string">"px"</span>;</span><br><span class="line">el.style.top = top + <span class="string">"px"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当top和left的值是动态计算而成时...</span></span><br><span class="line"><span class="comment">// better </span></span><br><span class="line">el.style.cssText += <span class="string">"; left: "</span> + left + <span class="string">"px; top: "</span> + top + <span class="string">"px;"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// better</span></span><br><span class="line">el.className += <span class="string">" className"</span>;</span><br></pre></td></tr></table></figure><h4 id="2-分离读写操作"><a href="#2-分离读写操作" class="headerlink" title="2.分离读写操作"></a>2.分离读写操作</h4><p>DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad 强制刷新 触发四次重排+重绘</span></span><br><span class="line">div.style.left = div.offsetLeft + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line">div.style.top = div.offsetTop + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line">div.style.right = div.offsetRight + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line">div.style.bottom = div.offsetBottom + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// good 缓存布局信息 相当于读写分离 触发一次重排+重绘</span></span><br><span class="line"><span class="keyword">var</span> curLeft = div.offsetLeft;</span><br><span class="line"><span class="keyword">var</span> curTop = div.offsetTop;</span><br><span class="line"><span class="keyword">var</span> curRight = div.offsetRight;</span><br><span class="line"><span class="keyword">var</span> curBottom = div.offsetBottom;</span><br><span class="line"></span><br><span class="line">div.style.left = curLeft + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line">div.style.top = curTop + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line">div.style.right = curRight + <span class="number">1</span> + <span class="string">'px'</span>;</span><br><span class="line">div.style.bottom = curBottom + <span class="number">1</span> + <span class="string">'px'</span>;</span><br></pre></td></tr></table></figure><p>原来的操作会导致四次重排,读写分离之后实际上只触发了一次重排,这都得益于浏览器的渲染队列机制:</p><blockquote><p>当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。</p></blockquote><h4 id="3-将-DOM-离线"><a href="#3-将-DOM-离线" class="headerlink" title="3.将 DOM 离线"></a>3.将 DOM 离线</h4><p>“离线”意味着不在当前的 DOM 树中做修改,我们可以这样做:</p><ul><li><p>使用 display:none</p><p>一旦我们给元素设置 <code>display:none</code> 时(只有一次重排重绘),元素便不会再存在在渲染树中,相当于将其从页面上“拿掉”,我们之后的操作将不会触发重排和重绘,添加足够多的变更后,通过 <code>display</code>属性显示(另一次重排重绘)。通过这种方式即使大量变更也只触发两次重排。另外,<code>visibility : hidden</code> 的元素只对重绘有影响,不影响重排。</p></li><li><p>通过 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment" rel="external nofollow noopener noreferrer" target="_blank">documentFragment</a> 创建一个 <code>dom</code> 碎片,在它上面批量操作 <code>dom</code>,操作完成之后,再添加到文档中,这样只会触发一次重排。</p></li><li><p>复制节点,在副本上工作,然后替换它!</p></li></ul><h4 id="4-使用-absolute-或-fixed-脱离文档流"><a href="#4-使用-absolute-或-fixed-脱离文档流" class="headerlink" title="4.使用 absolute 或 fixed 脱离文档流"></a>4.使用 absolute 或 fixed 脱离文档流</h4><p>使用绝对定位会使的该元素单独成为渲染树中 <code>body</code> 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排。</p><h4 id="5-优化动画"><a href="#5-优化动画" class="headerlink" title="5.优化动画"></a>5.优化动画</h4><ul><li><p>可以把动画效果应用到 <code>position</code>属性为 <code>absolute</code> 或 <code>fixed</code> 的元素上,这样对其他元素影响较小。</p><p>动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量:<br>比如实现一个动画,以1个像素为单位移动这样最平滑,但是Layout就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多</p></li><li><p>启用GPU加速<br><code>GPU</code> 硬件加速是指应用 <code>GPU</code> 的图形性能对浏览器中的一些图形操作交给 <code>GPU</code> 来完成,因为 <code>GPU</code> 是专门为处理图形而设计,所以它在速度和能耗上更有效率。</p><p><code>GPU</code> 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* 根据上面的结论</span></span><br><span class="line"><span class="comment">* 将 2d transform 换成 3d</span></span><br><span class="line"><span class="comment">* 就可以强制开启 GPU 加速</span></span><br><span class="line"><span class="comment">* 提高动画性能</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">div {</span><br><span class="line"> transform: translate3d(<span class="number">10</span>px, <span class="number">10</span>px, <span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="如何在浏览器中查看页面渲染时间"><a href="#如何在浏览器中查看页面渲染时间" class="headerlink" title="如何在浏览器中查看页面渲染时间"></a>如何在浏览器中查看页面渲染时间</h2><p>1.打开开发者工具:点击 Performance 左侧有个小圆点 点击刷新页面会录制整个页面加载出来 时间的分配情况。如下图</p><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i3_3.png" alt="image"></p><ul><li>蓝色: 网络通信和HTML解析</li><li>黄色: JavaScript执行</li><li>紫色: 样式计算和布局,即重排</li><li>绿色: 重绘</li></ul><p>哪种色块比较多,就说明性能耗费在那里。色块越长,问题越大。</p><p>2.点击 Event Log:单独勾选 Loading 项会显示 html 和 css 加载时间。如下图:</p><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i3_4.png" alt="image"></p><p>3.解析完 DOM+CSSOM 之后会生成一个渲染树 Render Tree,就是 DOM 和 CSSOM 的一一对应关系。</p><p>4.通过渲染树中在屏幕上“画”出的所有节点,称为渲染。</p><h3 id="小结:"><a href="#小结:" class="headerlink" title="小结:"></a>小结:</h3><ul><li>渲染的三个阶段 Layout,Paint,Composite Layers。<br>Layout:重排,又叫回流。<br>Paint:重绘,重排重绘这些步骤都是在 CPU 中发生的。<br>Compostite Layers:CPU 把生成的 BitMap(位图)传输到 GPU,渲染到屏幕。 </li><li>CSS3 就是在 GPU 发生的:Transform Opacity。在 GPU 发生的属性比较高效。所以 CSS3 性能比较高。</li></ul><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>非常感谢你看完了这篇很长的文章,也希望大家能重视重排的这些问题,在我们平时的开发中,也需要有意识的规避这些问题,才能让我们写出来的代码更规范!</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p><a href="https://segmentfault.com/a/1190000017491520" rel="external nofollow noopener noreferrer" target="_blank">掌握浏览器重绘(repaint)重排(reflow))-前端进阶</a><br><a href="https://csstriggers.com" rel="external nofollow noopener noreferrer" target="_blank">csstriggers</a><br><a href="https://efe.baidu.com/blog/hardware-accelerated-css-the-nice-vs-the-naughty/" rel="external nofollow noopener noreferrer" target="_blank">CSS硬件加速的好与坏</a></p>]]></content>
<summary type="html">
<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i3_1.jpg" alt="image"></p>
</summary>
<category term="CSS" scheme="https://litgod.net/categories/CSS/"/>
<category term="CSS" scheme="https://litgod.net/tags/CSS/"/>
<category term="HTML" scheme="https://litgod.net/tags/HTML/"/>
</entry>
<entry>
<title>浅谈 this、call、apply 和 bind</title>
<link href="https://litgod.net/2020/01/19/%E5%85%B3%E4%BA%8E-this%E3%80%81call%E3%80%81apply-%E5%92%8C-bind-%E7%9A%84%E4%B8%80%E4%BA%9B%E7%90%86%E8%A7%A3/"/>
<id>https://litgod.net/2020/01/19/%E5%85%B3%E4%BA%8E-this%E3%80%81call%E3%80%81apply-%E5%92%8C-bind-%E7%9A%84%E4%B8%80%E4%BA%9B%E7%90%86%E8%A7%A3/</id>
<published>2020-01-19T06:41:18.000Z</published>
<updated>2020-10-20T13:43:39.311Z</updated>
<content type="html"><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i2_1.jpg" alt="image"></p><a id="more"></a><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这是一个前端面试经常考的基础考点,很多初学者在这个问题上都容易踩坑,包括我也是经常性蒙圈。所以这次决定将他们梳理下来,加深自己的理解。如果有出错的地方,欢迎指正。</p><h2 id="this-是什么"><a href="#this-是什么" class="headerlink" title="this 是什么"></a>this 是什么</h2><p>this 关键字是 Javascript ES5 中最复杂的机制之一。<a href="http://es6.ruanyifeng.com/#README" rel="external nofollow noopener noreferrer" target="_blank">ES6</a> 中新增的箭头函数,很大程度上避免了使用 this 所产生的错误。但是在 ES5 中,有时候我们会错误的判断了 this 的指向。其实关于 this 的指向,始终坚持一个原理:<strong>this 永远指向最后调用它的那个对象</strong>,记住了这句话,this 的指向你已经了解一半!</p><p>想要了解 this 的指向,我们首先要了解 this 的四种绑定方式:<strong>隐式绑定、显示绑定、window 绑定、new 绑定</strong>。</p><h2 id="this-的四种绑定方式"><a href="#this-的四种绑定方式" class="headerlink" title="this 的四种绑定方式"></a>this 的四种绑定方式</h2><h3 id="隐式绑定"><a href="#隐式绑定" class="headerlink" title="隐式绑定"></a>隐式绑定</h3><p>执行绑定的第一个也是最常见的规则为 <strong>隐式绑定</strong>,它 80% 的情况下会告诉你 this 指向的对象是什么。</p><p>我们先来看一个简单的例子:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">const user = {</span><br><span class="line"> name: 'Cherry',</span><br><span class="line"> age: 27,</span><br><span class="line"> getName() {</span><br><span class="line"> console.log(`Hello, my name is ${this.name}`)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">user.getName() // Hello, my name is Cherry</span><br></pre></td></tr></table></figure><p>当我们执行 <code>user.getName()</code> 时,会打印出<code>Hello, my name is Cherry</code>。</p><p>如果你要调用 <strong>user</strong> 对象上的 <strong>getName</strong> 方法,你会用到点<code>.</code></p><p>这就是所谓隐式绑定,<strong>函数被调用时先看一看点号左侧</strong>。如果有“点”就查看“点”左侧的对象,这个对象就是 <strong>this</strong> 的引用。</p><p>在上面的例子中,<strong>user</strong> 在“点号左侧”意味着 <strong>this</strong> 引用了 <strong>user</strong> 对象。所以就好像 在 <strong>getName</strong> 方法的内部 <strong>JavaScript</strong> 解释器把 <strong>this</strong> 变成了 <strong>user</strong>。</p><p>所以,你可以得出这样的结论:<strong>使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的</strong>。这就是所谓隐式绑定,你也可以这样认为:JavaScript解释器在执行 <code>user.getName()</code>时,将其转化为了:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">user.getName.call(user);</span><br></pre></td></tr></table></figure><p>我们将代码增加一层调用:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">const user = {</span><br><span class="line"> name: 'Cherry',</span><br><span class="line"> age: 27,</span><br><span class="line"> getName() {</span><br><span class="line"> console.log(`Hello, my name is ${this.name}`)</span><br><span class="line"> },</span><br><span class="line"> mother: {</span><br><span class="line"> name: 'Susan',</span><br><span class="line"> getName() {</span><br><span class="line"> console.log(`Hello, my name is ${this.name}`)</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">user.getName() // Hello, my name is Cherry</span><br><span class="line">user.mother.getName() // Hello, my name is Susan</span><br></pre></td></tr></table></figure><p>正如刚才所说:<strong>this 永远指向最后调用它的那个对象</strong>,那么“点”左侧的对象即为后调用该方法的对象,this 指向该对象。但是,如果没有点呢?这就为我们引出了下一条规则:</p><h3 id="显示绑定"><a href="#显示绑定" class="headerlink" title="显示绑定"></a>显示绑定</h3><p>关于显示绑定,我们可以通过 call 来设置函数执行上下文的 this 指向,比如下面这段代码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">function getName () {</span><br><span class="line"> console.log(`Hello, my name is ${this.myName}`)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">let user = {</span><br><span class="line"> myName: 'Cherry',</span><br><span class="line"> age: 27,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">getName.call(user) // Hello, my name is Cherry</span><br></pre></td></tr></table></figure><p>执行这段代码,然后观察输出结果,你会发现 getName 函数内部的 this 已经指向了 user 对象。</p><p>其实除了 call 方法,我们还可以使用 bind 和 apply 方法来设置函数执行上下文中的 this,它们在使用上有一些区别,文章的第六小节会对 call、apply、bind 进行详细的介绍,这里我就不过多赘述了。</p><h3 id="window-绑定"><a href="#window-绑定" class="headerlink" title="window 绑定"></a>window 绑定</h3><p>我们在刚才的例子的基础上修改一下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">function getName () {</span><br><span class="line"> console.log(`Hello, my name is ${this.myName}`)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">let user = {</span><br><span class="line"> myName: 'Cherry',</span><br><span class="line"> age: 27,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">getName();</span><br></pre></td></tr></table></figure><p>相信大家都知道为什么打印出来的是 <strong>My name is undefined</strong>,因为正如前面所说的,如果你想用 <strong>user</strong> 做上下文调用 <strong>getName</strong>,你可以使用 <strong>.call</strong>、<strong>.apply</strong> 或 <strong>.bind</strong>。但如果我们没有用这些方法,而是直接和平时一样直接调用,<strong>JavaScript</strong> 会默认 <strong>this</strong> 指向 <strong>window</strong> 对象。但是 <strong>window</strong> 对象中并没有 <strong>myName</strong> 属性,所以会打印 <strong>“My name is undefined“</strong>。</p><blockquote><p>在 ES5 添加的 严格模式 中,JavaScript 不会默认 this 指向 window 对象,而会正确地把 this 保持为 undefined。</p></blockquote><p>例如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">'use strict'</span><br><span class="line"></span><br><span class="line">age = 27</span><br><span class="line"></span><br><span class="line">function sayAge () {</span><br><span class="line"> console.log(`Hello, my age is ${this.age}`)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">sayAge() // TypeError: Cannot read property 'age' of undefined</span><br></pre></td></tr></table></figure><h3 id="new-绑定"><a href="#new-绑定" class="headerlink" title="new 绑定"></a>new 绑定</h3><p>第四条判断 <strong>this</strong> 引用的规则是 <strong>new</strong> 绑定。每当用 <strong>new</strong> 调用函数时,<strong>JavaScript</strong> 解释器都会在底层创建一个全新的对象并把这个对象当做 <strong>this</strong>。</p><blockquote><p>这看起来就像创建了新的函数,但实际上 <strong>JavaScript</strong> 函数是重新创建的对象。</p></blockquote><p>例如:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">function User (name, age) {</span><br><span class="line"> /*</span><br><span class="line"> JavaScript 会在底层创建一个新对象 `this`,它会代理不在 User 原型链上的属性。</span><br><span class="line"> 如果一个函数用 new 关键字调用,this 就会指向解释器创建的新对象。</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line"> this.name = name</span><br><span class="line"> this.age = age</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const me = new User('Cherry', 27)</span><br></pre></td></tr></table></figure><p>伪代码表示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var me = new User("Cherry","27");</span><br><span class="line"></span><br><span class="line">new User{</span><br><span class="line"> var object = {};</span><br><span class="line"> object.__proto__ = User.prototype;</span><br><span class="line"> var result = User.call(object,"Cherry","27");</span><br><span class="line"> return typeof result === 'object'? result : object;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>new</strong> 的过程:<br>1.创建一个空对象 object;<br>2.将新创建的空对象的隐式原型指向其构造函数的显示原型;<br>3.使用 call 改变 this 的指向;<br>4.如果无返回值或者返回一个非对象值,则将 object 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。</p><p>所以我们可以看到,在 new 的过程中,其实是使用 call 改变了 this 的指向。</p><h2 id="this-的指向"><a href="#this-的指向" class="headerlink" title="this 的指向"></a>this 的指向</h2><p>前面讲了关于 this 的四种绑定方式,我们对于 this 的指向应该也有了一些自己的理解,还记得我们之前说的吗?<strong>this 永远指向最后调用它的那个对象</strong>,我们记好这句话来练习下下面的例子:</p><p>例1:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line">function fn() {</span><br><span class="line"> var name = "Cherry";</span><br><span class="line"></span><br><span class="line"> console.log(this.name); // window</span><br><span class="line"></span><br><span class="line"> console.log("inner:" + this); // inner: Window</span><br><span class="line">}</span><br><span class="line">fn();</span><br><span class="line">console.log("outer:" + this) // outer: Window</span><br></pre></td></tr></table></figure><p>我们看最后调用 <strong>fn</strong> 的地方 <code>fn();</code>,前面没有“点”,<strong>Javascript</strong> 调用的对象默认指向了全局对象 <strong>window</strong>,这就相当于是 <code>window.fn();</code>所以根据刚刚的那句话“<strong>this 永远指向最后调用它的那个对象</strong>”,<strong>this</strong> 指向的就是 <strong>window</strong>。绑定规则是Window绑定。</p><blockquote><p>注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property ‘name’ of undefined。</p></blockquote><p>例2:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line">var user = {</span><br><span class="line"> name: "Cherry",</span><br><span class="line"> fn: function () {</span><br><span class="line"> console.log(this.name); // Cherry</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">user.fn();</span><br></pre></td></tr></table></figure><p>根据上文所说,我们看到函数 <strong>fn</strong> 左侧有“点”,“点”的左侧是 <strong>user</strong>,所以 <strong>fn</strong> 是对象 <strong>user</strong> 调用的。所以打印的值就是 <strong>user</strong> 中的 <strong>name</strong> 的值。绑定规则是隐式绑定。</p><p>例3:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line"></span><br><span class="line">function fnA(){</span><br><span class="line"> var name = "Cherry";</span><br><span class="line"></span><br><span class="line"> function fnB(){</span><br><span class="line"> console.log(this.name); // window </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //在A函数内部调用B函数</span><br><span class="line"> fnB();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//调用A函数</span><br><span class="line">fnA();</span><br></pre></td></tr></table></figure><p><strong>嵌套函数中的 this 不会从外层函数中继承</strong>。在函数执行环境中使用 this 时,如果函数没有明显的作为非 window 对象的属性,而只是定义了函数,这个函数中的 this 仍然默认指向 window 对象。</p><p>例4:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line">var user = {</span><br><span class="line"> name: "Cherry",</span><br><span class="line"> fn: function () {</span><br><span class="line"> console.log(this.name); // Cherry</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">window.user.fn();</span><br></pre></td></tr></table></figure><p>这里打印 Cherry 的原因也是因为刚刚那句话“this 永远指向最后调用它的那个对象”,最后调用它的对象仍然是对象 user。</p><p>我们改动一下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line">var user = {</span><br><span class="line"> // name: "Cherry",</span><br><span class="line"> fn: function () {</span><br><span class="line"> console.log(this.name); // undefined</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">window.user.fn();</span><br></pre></td></tr></table></figure><p>这是因为调用 <strong>fn</strong> 的是 <strong>user</strong> 对象,也就是说 <strong>fn</strong> 的内部的 <strong>this</strong> 是对象 <strong>user</strong>,而对象 <strong>user</strong> 中并没有对 <strong>name</strong> 进行定义,所以 <strong>log</strong> 的 <strong>this.name</strong> 的值是 <strong>undefined</strong>。</p><p>这个例子还是说明了:<strong>this 永远指向最后调用它的那个对象</strong>,因为最后调用 <strong>fn</strong> 的对象是 <strong>user</strong>,所以就算 <strong>user</strong> 中没有 <strong>name</strong> 这个属性,也不会继续向上一个对象寻找 <strong>this.name</strong>,而是直接输出 <strong>undefined</strong>。</p><p>例5:(这个例子稍稍有点坑)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line">var user = {</span><br><span class="line"> name : null,</span><br><span class="line"> // name: "Cherry",</span><br><span class="line"> fn : function () {</span><br><span class="line"> console.log(this.name); // window</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var f = user.fn;</span><br><span class="line">f();</span><br></pre></td></tr></table></figure><p>这里你可能会有疑问,为什么不是 Cherry?因为这里虽然将 <strong>user</strong> 对象的 <code>fn</code> 方法赋值给变量 <code>f</code> 了,但是<strong>没有调用</strong>,再接着跟我念这一句话:“<strong>this 永远指向最后调用它的那个对象</strong>”,由于刚刚的 <code>f</code> 并没有调用,所以 <code>fn()</code> 最后仍然是被 <strong>window</strong> 调用的。所以 <strong>this</strong> 指向的也就是 <strong>window</strong>。</p><p>由以上五个例子我们可以看出,this 的指向并不是在创建的时候就可以确定的,在 es5 中,<strong>this永远指向最后调用它的那个对象。</strong></p><h2 id="如何改变-this-的指向"><a href="#如何改变-this-的指向" class="headerlink" title="如何改变 this 的指向"></a>如何改变 this 的指向</h2><p>改变 this 的指向我总结有以下几种方法:</p><ul><li>使用 ES6 的箭头函数</li><li>在函数内部使用 _this = this</li><li>使用 apply、call、bind</li><li>new 实例化一个对象</li></ul><p>我们看下面的例子:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line"></span><br><span class="line">var user = {</span><br><span class="line"> name : "Cherry",</span><br><span class="line"></span><br><span class="line"> fn1: function() {</span><br><span class="line"> console.log(this.name) </span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> fn2: function() {</span><br><span class="line"> setTimeout(function () {</span><br><span class="line"> this.fn1()</span><br><span class="line"> },100);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">user.fn2() // this.fn1 is not a function</span><br></pre></td></tr></table></figure><p>我们逐一细说一下这个例子:<code>fn2()</code>是被 <strong>user</strong>调用的,所以<code>fn2</code>中的 <strong>this</strong> 应该指向 <strong>user</strong>。但是<code>fn2</code>中又调用了 <strong>window</strong> 中的 <strong>setTimeout</strong> 方法。所以在 <strong>setTimeout</strong> 方法中的 <strong>this</strong> 指向的是后调用它的对象 <strong>window</strong>。但是在 <strong>window</strong> 中并没有 <strong>fn1</strong> 函数。所以抛出错误:this.fn1 is not a function。</p><p>如果我们想正确的调用 <strong>user</strong> 中的 <code>fn1()</code>,应该怎么做呢?我们把这个例子作为 demo 进行改造。</p><h3 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h3><p>众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。“所有的箭头函数都没有自己的this,都指向外层。”–这句话就是箭头函数的精髓。箭头函数的this,总是指向定义时所在的对象,而不是运行时所在的对象。这句话说的太模糊了,最好改成:<strong>总是指向所在函数运行时的this</strong>。</p><p>上面例子我们使用<strong>箭头函数</strong>改变this的指向如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line"></span><br><span class="line">var user = {</span><br><span class="line"> name : "Cherry",</span><br><span class="line"></span><br><span class="line"> fn1: function () {</span><br><span class="line"> console.log(this.name) </span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> fn2: function () {</span><br><span class="line"> setTimeout( () => {</span><br><span class="line"> this.fn1()</span><br><span class="line"> },100);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">user.fn2() // Cherry</span><br></pre></td></tr></table></figure><h3 id="在函数内部使用-this-this"><a href="#在函数内部使用-this-this" class="headerlink" title="在函数内部使用 _this = this"></a>在函数内部使用 _this = this</h3><p>如果不使用 ES6,那么这种方式应该是最简单的不会出错的方式了,我们是先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">var name = "window";</span><br><span class="line"></span><br><span class="line">var user = {</span><br><span class="line"></span><br><span class="line"> name : "Cherry",</span><br><span class="line"></span><br><span class="line"> fn1: function () {</span><br><span class="line"> console.log(this.name) </span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> fn2: function () {</span><br><span class="line"> var _this = this;</span><br><span class="line"> setTimeout( function() {</span><br><span class="line"> _this.fn1()</span><br><span class="line"> },100);</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">user.fn2() // Cherry</span><br></pre></td></tr></table></figure><p>这个例子中,在 fn2 中,首先设置 var _this = this;,这里的 <strong>this</strong> 是调用 <code>fn2</code> 的对象 <strong>user</strong>,为了防止在 <code>fn2</code> 中的 <strong>setTimeout</strong> 被 <strong>window</strong> 调用而导致的在 <strong>setTimeout</strong> 中的 <strong>this</strong> 为 <strong>window</strong>。我们将 <strong>this</strong>(指向变量 user) 赋值给一个变量 <strong>_this</strong>,这样,在 <code>fn2</code> 中我们使用 <strong>_this</strong> 就是指向对象 <strong>user</strong> 了。</p><h3 id="使用-apply、call、bind"><a href="#使用-apply、call、bind" class="headerlink" title="使用 apply、call、bind"></a>使用 apply、call、bind</h3><p>使用 apply、call、bind 函数也是可以改变 this 的指向的,成为显示绑定,我们先来看一下是怎么实现的:</p><h4 id="使用-apply"><a href="#使用-apply" class="headerlink" title="使用 apply()"></a>使用 apply()</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">var user = {</span><br><span class="line"> name: "Cherry",</span><br><span class="line"></span><br><span class="line"> fn1: function() {</span><br><span class="line"> console.log(this.name)</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> fn2: function() {</span><br><span class="line"> setTimeout(function () {</span><br><span class="line"> this.fn1()</span><br><span class="line"> }.apply(user), 100);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">user.fn2() // Cherry</span><br></pre></td></tr></table></figure><h4 id="使用-call"><a href="#使用-call" class="headerlink" title="使用 call()"></a>使用 call()</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">var user = {</span><br><span class="line"> name: "Cherry",</span><br><span class="line"></span><br><span class="line"> fn1: function() {</span><br><span class="line"> console.log(this.name)</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> fn2: function() {</span><br><span class="line"> setTimeout(function () {</span><br><span class="line"> this.fn1()</span><br><span class="line"> }.call(user), 100);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">user.fn2() // Cherry</span><br></pre></td></tr></table></figure><h4 id="使用-bind"><a href="#使用-bind" class="headerlink" title="使用 bind()"></a>使用 bind()</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">var user = {</span><br><span class="line"> name: "Cherry",</span><br><span class="line"></span><br><span class="line"> fn1: function() {</span><br><span class="line"> console.log(this.name)</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> fn2: function() {</span><br><span class="line"> setTimeout(function () {</span><br><span class="line"> this.fn1()</span><br><span class="line"> }.bind(user)(), 100);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">user.fn2() // Cherry</span><br></pre></td></tr></table></figure><h2 id="apply、call、bind-的区别"><a href="#apply、call、bind-的区别" class="headerlink" title="apply、call、bind 的区别"></a>apply、call、bind 的区别</h2><p>刚刚我们已经介绍了 apply、call、bind 都是可以改变 this 的指向的,但是这三个函数稍有不同。</p><p>在 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply" rel="external nofollow noopener noreferrer" target="_blank">MDN</a> 中定义 apply 如下;</p><blockquote><p>apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数</p></blockquote><h3 id="apply-和-call-的区别"><a href="#apply-和-call-的区别" class="headerlink" title="apply 和 call 的区别"></a>apply 和 call 的区别</h3><p>其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。</p><p>call 的语法为: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fun.call(thisArg[, arg1[, arg2[, ...]]])</span><br></pre></td></tr></table></figure><p>所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。</p><p>apply()的使用方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var user ={</span><br><span class="line"> name: "Cherry",</span><br><span class="line"> fn: function(a,b) {</span><br><span class="line"> console.log(a + b)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var newUser = user.fn;</span><br><span class="line">newUser.apply(user,[1,2]) // 3</span><br></pre></td></tr></table></figure><p>call()的使用方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var user ={</span><br><span class="line"> name: "Cherry",</span><br><span class="line"> fn: function(a,b) {</span><br><span class="line"> console.log(a + b)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var newUser =user.fn;</span><br><span class="line">newUser.call(user, 1, 2) // 3</span><br></pre></td></tr></table></figure><p>但凡事都有例外:<br>若将null、undefined等值作为call、apply的第一个参数,那么实际调用时会被忽略,从而应用到Window绑定规则,即绑定到window上,有些时候我们不关心上下文,只关心参数时,可以这样做。</p><p>但这样其实存在这一些潜在的风险,绑定到window很可能无意中添加或修改了全局变量,造成一些隐蔽的bug。所以为了防止这种情况出现,可以将第一个参数绑定为一个空对象。当然具体还是看需求,这只是建议。</p><h3 id="bind-和-apply、call-区别"><a href="#bind-和-apply、call-区别" class="headerlink" title="bind 和 apply、call 区别"></a>bind 和 apply、call 区别</h3><p>我们先使用 bind 试一下刚刚的例子:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var user ={</span><br><span class="line"> name: "Cherry",</span><br><span class="line"> fn: function(a,b) {</span><br><span class="line"> console.log(a + b)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var newUser = user.fn;</span><br><span class="line">nreUser.bind(user,1,2)</span><br></pre></td></tr></table></figure><p>我们会发现并没有输出,这是为什么呢,我们来看一下 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind" rel="external nofollow noopener noreferrer" target="_blank">MDN</a> 上的文档说明:</p><blockquote><p>bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。</p></blockquote><p>所以我们可以看出,<strong>bind 是创建一个新的函数</strong>,我们必须要手动去调用:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var user ={</span><br><span class="line"> name: "Cherry",</span><br><span class="line"> fn: function (a,b) {</span><br><span class="line"> console.log( a + b)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var newUser = user.fn;</span><br><span class="line">newUser.bind(user,1,2)() // 3</span><br></pre></td></tr></table></figure><p>以上就是三种显示绑定的方法,但有三点需要注意:</p><ol><li>call和apply是立即执行,bind则是返回一个绑定了this的新函数,只有你调用了这个新函数才真的调用了目标函数</li><li>bind函数存在多次绑定的问题,如果多次绑定this,则以第一次为准。</li><li>bind函数实际上是显示绑定(call、apply)的一个变种,称为<strong>硬绑定</strong>。由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法<code>Function.prototype.bind</code></li></ol><p>为什么多次使用bind绑定this,以第一次为准呢?我们看下面的例子:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">function foo() {</span><br><span class="line"> console.log( this.name );</span><br><span class="line">} </span><br><span class="line"></span><br><span class="line">var obj1 = {</span><br><span class="line"> name: 'obj1'</span><br><span class="line">}; </span><br><span class="line"></span><br><span class="line">var obj2 = {</span><br><span class="line"> name: 'obj2'</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">var fn = foo.bind(obj1).bind(obj2)</span><br><span class="line">fn() // => 'obj1'</span><br><span class="line">fn.call(obj2) // => 'obj1'</span><br></pre></td></tr></table></figure><p>也就是说bind函数只能绑定一次,多次绑定是没有用的,绑定后的函数this无法改变,即使call/apply也不行,所以才称作硬绑定。</p><p>但凡事总有例外,且看new绑定。</p><h2 id="绑定的优先级"><a href="#绑定的优先级" class="headerlink" title="绑定的优先级"></a>绑定的优先级</h2><p>如果显示绑定和new绑定同时存在,或者更宽泛的说:<strong>在某个调用位置多条绑定规则同时存在怎么办呢</strong>?为了解决这个问题就必须给这些规则设定优先级,这就是我们接下来要介绍的内容。</p><p>毫无疑问,Window绑定的优先级是最低的,显式绑定和隐式绑定的优先级,通过上面的例子也可以证明,显式大于隐式。所以目前顺序是:<code>显式 > 隐式 > Window</code></p><p>那我们来测试下显示绑定和new绑定的优先级顺序。由于call/apply无法和new一起使用,我们可以使用bind(硬绑定)来验证。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">function foo() {</span><br><span class="line"> this.name = 'Cherry';</span><br><span class="line">} </span><br><span class="line">var obj = {</span><br><span class="line"> name: 'obj'</span><br><span class="line">}; </span><br><span class="line"></span><br><span class="line">var fn = foo.bind(obj)</span><br><span class="line">var result = new fn()</span><br><span class="line">console.log(obj.name) // => 'obj'</span><br><span class="line">console.log(result.name) // => 'Cherry'</span><br></pre></td></tr></table></figure><p>显而易见的,new的优先级,大于显示绑定。最终顺序为:<code>new > 显式 > 隐式 > Window</code>。</p><p>于是我们判断this,就有了一个顺序:</p><ol><li>函数是否在new中调用?</li><li>是否通过call、apply、bind等调用?</li><li>是否在某个上下文对象中调用?</li><li>都不是则是Window绑定。且严格模式下绑定到undefined。</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>判断this主要有以下步骤:</p><ol><li>函数是否在new中调用?</li><li>是否通过call、apply、bind等调用?</li><li>是否在某个上下文对象中调用?</li><li>都不是则是默认绑定。且严格模式下绑定到undefined。</li></ol><p>另外还要注意箭头函数的特殊性以及undefined和null会被忽略这一特性。还有这一句:this永远指向最后调用它的那个对象。</p>]]></content>
<summary type="html">
<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i2_1.jpg" alt="image"></p>
</summary>
</entry>
<entry>
<title>超详细的Github+Hexo搭建教程</title>
<link href="https://litgod.net/2019/10/31/%E8%B6%85%E8%AF%A6%E7%BB%86%E7%9A%84Github-Hexo%E6%90%AD%E5%BB%BA%E6%95%99%E7%A8%8B/"/>
<id>https://litgod.net/2019/10/31/%E8%B6%85%E8%AF%A6%E7%BB%86%E7%9A%84Github-Hexo%E6%90%AD%E5%BB%BA%E6%95%99%E7%A8%8B/</id>
<published>2019-10-31T12:45:02.000Z</published>
<updated>2020-08-10T14:50:50.847Z</updated>
<content type="html"><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/hexo.jpg" alt="image"></p><a id="more"></a><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>不管你是程序猿(媛),产品经理,设计师,运维工程师…还是从事其他职业,应该都想拥有一个属于自己的个人博客网站吧。如果你是,那么请跟随目录,搭建属于你的个人博客吧!一起来技术分享,记录生活…</p><h2 id="Hexo-是什么"><a href="#Hexo-是什么" class="headerlink" title="Hexo 是什么"></a>Hexo 是什么</h2><p><a href="https://hexo.io" rel="external nofollow noopener noreferrer" target="_blank">Hexo</a> 是一个基于 <a href="https://nodejs.org/en" rel="external nofollow noopener noreferrer" target="_blank">Node.js</a>的快速,简单月功能强大的博客框架。可以使用简单的命令生成静态网页,并托管到 Github 上。<br>#TODO</p><h2 id="来搭建属于你的个人博客吧~"><a href="#来搭建属于你的个人博客吧~" class="headerlink" title="来搭建属于你的个人博客吧~"></a>来搭建属于你的个人博客吧~</h2><h3 id="Gihub-创建个人仓库"><a href="#Gihub-创建个人仓库" class="headerlink" title="Gihub 创建个人仓库"></a>Gihub 创建个人仓库</h3><ul><li><p>首先先登录到 <a href="https://github.com" rel="external nofollow noopener noreferrer" target="_blank">Github</a>。如果没有个人账号,先进行注册,注册完成后,点击登录进入 Github。</p></li><li><p>点击绿色的 <strong>New</strong> 按钮新建一个仓库,将仓库名称命为: 用户名.github.io,例如:qiruohan.github.io,这个写法是固定的。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_1.jpg" alt="image"><br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_2.jpg" alt="image"></p></li><li><p>注意:仓库名称要和你的用户名保持一致,后缀.github.io 的作用是 Github 识别到.github.io 后缀就会为你自动开启<a href="https://pages.github.com/" rel="external nofollow noopener noreferrer" target="_blank">Github Page</a>,作为你个人博客的仓库。</p></li></ul><p>然后项目就建成了,点击Settings,向下拉到最后有个GitHub Pages,点击Choose a theme可以选择一个主题。然后点击那个链接,就会出现自己的网页啦~</p><h3 id="安装-Node-js"><a href="#安装-Node-js" class="headerlink" title="安装 Node.js"></a>安装 Node.js</h3><p>Node.js 是基于 <a href="https://v8.dev/" rel="external nofollow noopener noreferrer" target="_blank">Chrome V8 JavaScript 引擎</a> 构建的语言,是一项服务器端技术。<br>下载地址:<a href="https://nodejs.org/en/download/" rel="external nofollow noopener noreferrer" target="_blank">Node.js | Download</a>,下载当前操作系统的安装包,安装选项全部默认。注意下载的安装包中已经包含了环境变量以及 <a href="https://www.npmjs.com/" rel="external nofollow noopener noreferrer" target="_blank">npm</a>,所以安装完安装包后无需另外再下载 npm。</p><p>检测 Node.js 是否安装成功,在命令行中输入:<strong>node -v</strong><br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_3.png" alt="image"></p><p>检测 npm 是否安装成功,在命令行中输入:<strong>npm -v</strong><br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_4.png" alt="image"></p><p>显示版本号,那么就说明 node.js 安装成功了。</p><h3 id="安装-Git"><a href="#安装-Git" class="headerlink" title="安装 Git"></a>安装 Git</h3><p><a href="https://git-scm.com/" rel="external nofollow noopener noreferrer" target="_blank">Git</a> 是一个开源的分布式版本控制系统,旨在快速高效地处理从小型到大型项目的所有内容。具有便捷的创建本地分支,创建暂存区域,处理多个工作流等功能。简单来说,使用 Git 可以把本地文件同步到 Github 上,完成多人多空间的便捷式管理。</p><p><strong>Windows 下</strong>安装下载地址:<a href="https://git-scm.com/download/" rel="external nofollow noopener noreferrer" target="_blank">Git | Downloads</a>,安装选项还是全部默认,安装完成后在命令行中输入 <strong>git –version</strong> 验证是否安装成功。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_5.png" alt="image"><br>显示版本号,那么就说明 git 安装成功了。安装成功后,将你的Git与GitHub帐号绑定,鼠标右击打开Git Bash,设置user.name和user.email配置信息。<br>之后移步到 mac 下安装流程的<strong>第三步:设置github的 username 和 email</strong>,做接来下的操作。</p><p><strong>Mac 下安装</strong>:</p><ul><li><p>如果未安 homebrew,需要先安装 homebrew</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"</span><br></pre></td></tr></table></figure></li><li><p>安装 git</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install git</span><br></pre></td></tr></table></figure></li><li><p>检查 git 是否安装成功</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git --version</span><br></pre></td></tr></table></figure></li><li><p>安装成功后,先设置github的 username 和 email(github 在每次提交的时候都会记录他们)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global user.name "你的GitHub用户名"</span><br><span class="line">git config --global user.email "你的GitHub注册邮箱"</span><br></pre></td></tr></table></figure></li><li><p>使用终端命令创建 ssh key</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C "你的GitHub注册邮箱"</span><br></pre></td></tr></table></figure><p>然后直接三个回车即可,默认不需要设置密码<br>然后找到生成的.ssh的文件夹中的id_rsa.pub密钥,将内容全部复制</p></li><li><p>打开 <a href="https://github.com/settings/keys" rel="external nofollow noopener noreferrer" target="_blank">GitHub_Settings_keys</a> 页面,新建new SSH Key<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_10.png" alt="image"></p></li></ul><p>Title为标题,任意填即可,将刚刚复制的id_rsa.pub内容粘贴进去,最后点击Add SSH key。</p><ul><li>在终端检测GitHub公钥设置是否成功,输入 ssh <a href="mailto:git@github.com" rel="external nofollow noopener noreferrer" target="_blank">git@github.com</a>。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_11.png" alt="image"></li></ul><p>如上则说明成功。</p><p>注意:这里之所以设置GitHub密钥原因是,通过非对称加密的公钥与私钥来完成加密,公钥放置在GitHub上,私钥放置在自己的电脑里。GitHub要求每次推送代码都是合法用户,所以每次推送都需要输入账号密码验证推送用户是否是合法用户,为了省去每次输入密码的步骤,采用了ssh,当你推送的时候,git就会匹配你的私钥跟GitHub上面的公钥是否是配对的,若是匹配就认为你是合法用户,则允许推送。这样可以保证每次的推送都是正确合法的。</p><h3 id="安装-Hexo"><a href="#安装-Hexo" class="headerlink" title="安装 Hexo"></a>安装 Hexo</h3><p>Hexo 就是我们搭建个人博客所使用的框架,我们需要在合适的地方先创建一个文件夹,用来存放自己的博客文件,例如我命名为blog2。</p><p>使用命令行进入到该目录下,输入 <code>npm i hexo-cli -g</code> 安装 Hexo,安装成功后,会显示安装所使用的总时长。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_6.png" alt="image"></p><p>安装完成后,初始化我们的博客,输入 <code>hexo init blog</code>。<br>注意:这里的命令都作用在刚刚创建的 blog2 文件夹下。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_7.png" alt="image"></p><p>初始化时间可能会比较长,耐心等待…<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_8.png" alt="image"></p><p>初始化完成后,会发现 blog2 下又新增了一个文件夹,名为 blog,与 <code>hexo init</code> 后面输入的文件名同名。我们进入新创建的文件夹 blog 下,输入以下三条命名来检测一下我们的网站雏形</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">hexo new test</span><br><span class="line"></span><br><span class="line">hexo g</span><br><span class="line"></span><br><span class="line">hexo s</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_9.png" alt="image"></p><p>到这里,我们的个人博客就搭建完成啦!并且已经写出了我们的第一篇文章~</p><h3 id="hexo-常用命令"><a href="#hexo-常用命令" class="headerlink" title="hexo 常用命令"></a>hexo 常用命令</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">1.安装 Hexo</span><br><span class="line"> npm install hexo -g</span><br><span class="line">2.升级 Hexo</span><br><span class="line"> npm update hexo -g</span><br><span class="line">3.初始化博客</span><br><span class="line"> hexo init "博客站点"</span><br><span class="line">4.新建文章</span><br><span class="line"> hexo n "我的博客" 或 hexo new "我的博客" </span><br><span class="line">5.生成博客</span><br><span class="line"> hexo g 或 hexo generate</span><br><span class="line">6.启动服务</span><br><span class="line"> hexo s 或 hexo server</span><br><span class="line">7.部署博客</span><br><span class="line"> hexo d 或 hexo deploy</span><br><span class="line">8.更改端口</span><br><span class="line"> hexo server -p 5000</span><br><span class="line">9.自定义 IP</span><br><span class="line"> hexo server -i 192.168.1.1</span><br><span class="line">10.清除缓存,若是网页正常情况下可以忽略这条命令</span><br><span class="line"> hexo clean</span><br><span class="line">11.新建页面</span><br><span class="line"> hexo new page xxx</span><br></pre></td></tr></table></figure><h3 id="推送博客站点"><a href="#推送博客站点" class="headerlink" title="推送博客站点"></a>推送博客站点</h3><p>上图只是本地的预览,如果想让大家都看到你的博客,就得把项目放在公网上被大家访问。打开博客根目录下的_config.yml文件,这是博客的配置文件,在这里你可以修改与博客相关的各种信息。这个文件称之为<strong>站点配置文件</strong>。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_12.png" alt="image"></p><p>修改最后一行的配置:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">deploy:</span><br><span class="line"> type: git</span><br><span class="line"> repo: https://github.com/qiruohan/qiruohan.github.io.git</span><br><span class="line"> branch: master</span><br></pre></td></tr></table></figure><p>repository修改为你自己的github项目地址。</p><p>这里其实就是给 hexo d 命令做相应的配置,让 hexo 知道要把你的博客部署在哪个位置,我们需要把项目部署到我们自己的GitHub的仓库里。</p><p>安装Git部署插件,输入命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-deployer-git --save</span><br></pre></td></tr></table></figure><p>这时,我们分别输入三条命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">hexo clean </span><br><span class="line">hexo g </span><br><span class="line">hexo d</span><br></pre></td></tr></table></figure><p>打开浏览器,在地址栏输入你的放置个人网站的仓库路径,即 xxxx.github.io, 比如我的:qiruohan.github.io, 你就会发现你的博客已经上线了,可以在网络上被访问了。</p><h3 id="更换主题"><a href="#更换主题" class="headerlink" title="更换主题"></a>更换主题</h3><p>如果你不喜欢 Hexo 的默认主题,可以更换主题,hexo主题有很多,你可以从网上找到很多很好看的主题,每个主题也都有自己的安装教程,你可以试着看一看。</p><p>我这里使用的主题是<a href="https://theme-next.iissnan.com/" rel="external nofollow noopener noreferrer" target="_blank">nexT</a>,所以我说一下我的安装配置吧~</p><ul><li><p>安装 nexT 主题,通过 git 命令将 nexT 克隆下来, 在博客站点目录下(我的是blog),使用 git clone 命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/theme-next/hexo-theme-next themes/next</span><br></pre></td></tr></table></figure></li><li><p>等待克隆完毕,找到 themes 文件夹下的 next 文件, 这就是我们刚刚克隆下来的主题了。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_13.png" alt="image"></p></li><li><p>返回根目录,找到我们的站点文件_config.yml,打开并修改里面的 theme 配置以使我们刚刚克隆下来的主题生效。</p></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">theme: next</span><br></pre></td></tr></table></figure><p>修改theme: landscape为next,注意theme和next之间要有空格,否则无效。</p><p>正确设置好后,我们更换的主题就生效啦~每个主题都可以有自己个性化的配置,可以打开主题的_config.yml配置文件(注意不是站点配置文件),可以按照你的想法做一些个性化的配置,之后再次部署网站,hexo clean、hexo g、hexo d,查看效果。选择其他主题,按照上述过程即可实现。</p><h3 id="发布文章"><a href="#发布文章" class="headerlink" title="发布文章"></a>发布文章</h3><p>1.文章头设置</p><h3 id="MarkDown-语法"><a href="#MarkDown-语法" class="headerlink" title="MarkDown 语法"></a>MarkDown 语法</h3><p>Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,具体语法参看:<a href="https://www.appinn.com/markdown/" rel="external nofollow noopener noreferrer" target="_blank">Markdown 语法说明(简体中文版)</a> 可以说十分钟就可以入门,非常简单发方便。当然,选择一个好的Markdown编辑器也是非常重要的,mac版推荐使用 MacDown 或者直接使用 VsCode 编写 Markdown 文件, 非常方便。</p><h3 id="绑定域名"><a href="#绑定域名" class="headerlink" title="绑定域名"></a>绑定域名</h3><p>现在默认的域名还是xxx.github.io,而如果我们想使用个性化的域名,就需要绑定我们自己的域名,首先你需要购买一个域名,XX云都能买,国内主流的域名代理厂商也就阿里云和腾讯云。下面给大家演示阿里云的相关配置:</p><ul><li>登录阿里云,进入管理控制台的域名列表,找到你的个性化域名,进入解析</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_14.png" alt="image"></p><ul><li>添加解析</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_15.png" alt="image"></p><p>一共包括两条解析记录,记录类型都是CNAME,CNAME的记录值是:你的用户名.github.io,这里千万别弄错了。</p><ul><li>登录GitHub,进入之前创建的仓库,点击settings,设置Custom domain,输入你的域名,点击保存。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_16.png" alt="image"></li></ul><p>注意:如果你把 Enforce HTTPS 钩上,github 会自动帮你升级为 https 的哦~</p><ul><li><p>这时候你的项目根目录应该会出现一个名为CNAME的文件了。如果没有的话,打开你本地博客/source目录,手动创建一个CNAME文件,注意没有后缀。写上你的域名。<br>注意,只要写进你自己的域名即可。如果带有www,那么以后访问的时候必须带有www完整的域名才可以访问,但如果不带有www,以后访问的时候带不带www都可以访问。所以建议,不要带有www。<br><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/i_17.png" alt="image"></p></li><li><p>点击保存。保存成功后运行hexo g、hexo d传到github上。这时候打开浏览器在地址栏输入你的个性化域名将会直接进入你自己搭建的网站。</p></li></ul><h3 id="个性化配置"><a href="#个性化配置" class="headerlink" title="个性化配置"></a>个性化配置</h3><h3 id="寻找图床"><a href="#寻找图床" class="headerlink" title="寻找图床"></a>寻找图床</h3>]]></content>
<summary type="html">
<p><img src="https://cdn.jsdelivr.net/gh/qiruohan/qiruohan.github.io/uploads/hexo.jpg" alt="image"></p>
</summary>
<category term="Hexo" scheme="https://litgod.net/categories/Hexo/"/>
<category term="Hexo" scheme="https://litgod.net/tags/Hexo/"/>
</entry>
</feed>