-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
247 lines (118 loc) · 117 KB
/
search.xml
File metadata and controls
247 lines (118 loc) · 117 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>性能优化-Performance API基础</title>
<link href="/2025/01/01/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-Performance%20API%E5%9F%BA%E7%A1%80/"/>
<url>/2025/01/01/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-Performance%20API%E5%9F%BA%E7%A1%80/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>Performance API 是一组在浏览器平台测量和收集性能数据的接口,它允许开发者访问页面加载、资源获取、用户交互、JavaScript执行等性能相关的数据。灵活运用 Performance API 有助于我们将用户的主观体验量化为客观指标,建立以用户为中心的数据监控,更是我们后续开展众多优化后,评估优化效果的主要手段。</p><p>常用的接口包括:</p><ul><li><p>performance.now():获取一个高精度的时间戳,单位为毫秒,精度可达微秒。</p></li><li><p>performance.mark() 和 performance.measure():用于标记和测量特定代码段的执行时间。</p></li><li><p>performance.timing:提供详细的页面加载阶段的时间数据。</p></li><li><p>performance.getEntriesByType():获取资源加载的详细信息,例如图片、脚本等文件的加载时间。</p></li></ul><h1 id="获取性能记录的方法"><a href="#获取性能记录的方法" class="headerlink" title="获取性能记录的方法"></a>获取性能记录的方法</h1><p>常用的方法有<code>.getEntries()</code>和 <code>new PerformanceObserver()</code></p><h2 id="1-回调监听式持续获取"><a href="#1-回调监听式持续获取" class="headerlink" title="1. 回调监听式持续获取"></a>1. 回调监听式持续获取</h2><p>这类方法具体指<code>(new PerformanceObserver(observeCallbackFunc)).observe()</code>,其中的<code>observeCallbackFunc</code>回调函数会在新的性能记录添加时触发,接收到2个参数<code>(list, observer)</code>,<code>list</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 创建一个PerformanceObserver实例</span></span><br><span class="line"><span class="keyword">const</span> observer = <span class="keyword">new</span> <span class="title class_">PerformanceObserver</span>(<span class="function">(<span class="params">list</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> entries = list.<span class="title function_">getEntries</span>();</span><br><span class="line"> entries.<span class="title function_">forEach</span>(<span class="function">(<span class="params">entry</span>) =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`资源名称: <span class="subst">${entry.name}</span>`</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`资源类型: <span class="subst">${entry.initiatorType}</span>`</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`资源加载时间: <span class="subst">${entry.duration}</span>ms`</span>);</span><br><span class="line"> });</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">const</span> entryTypes = [ <span class="comment">// 对应上文的性能记录子类:</span></span><br><span class="line"> <span class="string">"resource"</span>, <span class="comment">// PerformanceResourceTiming</span></span><br><span class="line"> <span class="string">"visibility-state"</span>, <span class="comment">// VisibilityStateEntry</span></span><br><span class="line"> <span class="string">"mark"</span>, <span class="comment">// PerformanceMark</span></span><br><span class="line"> <span class="string">"measure"</span>, <span class="comment">// PerformanceMeasure</span></span><br><span class="line"> <span class="string">"event"</span>,</span><br><span class="line"> <span class="string">"element"</span>,</span><br><span class="line"> <span class="string">"first-input"</span>,</span><br><span class="line"> <span class="string">"largest-contentful-paint"</span>,</span><br><span class="line"> <span class="string">"layout-shift"</span>,</span><br><span class="line"> <span class="string">"longtask"</span>,</span><br><span class="line"> <span class="string">"navigation"</span>,</span><br><span class="line"> <span class="string">"paint"</span>,</span><br><span class="line"> <span class="string">"taskattribution"</span>,</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 启动 PerformanceObserver 来观察指定类型的性能条目</span></span><br><span class="line">observer.<span class="title function_">observe</span>({ entryTypes });</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 停止观察</span></span><br><span class="line">observer.<span class="title function_">disconnect</span>();</span><br></pre></td></tr></table></figure><h2 id="2-立刻返回当前所有记录"><a href="#2-立刻返回当前所有记录" class="headerlink" title="2. 立刻返回当前所有记录"></a>2. 立刻返回当前所有记录</h2><p>这类方法具体指以下3个API:</p><ul><li><code>performance.getEntries()</code></li><li><code>performance.getEntriesByName(nameStr, typeStr)</code></li><li><code>performance.getEntriesByType(typeStr)</code></li></ul><p>这三个方法,会立刻返回当前性能记录缓冲区中的所有性能记录。</p><p><code>getEntriesByName</code>和<code>getEntriesByType</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></pre></td><td class="code"><pre><span class="line">performance.<span class="title function_">getEntries</span>() <span class="comment">// PerformanceEntry: []</span></span><br><span class="line"></span><br><span class="line">performance.<span class="title function_">getEntriesByName</span>(<span class="string">"login-started"</span>, <span class="string">"mark"</span>); <span class="comment">// PerformanceMark: []</span></span><br><span class="line"></span><br><span class="line">performance.<span class="title function_">getEntriesByType</span>(<span class="string">"resource"</span>); <span class="comment">// PerformanceResourceTiming: []</span></span><br></pre></td></tr></table></figure><h1 id="使用案例"><a href="#使用案例" class="headerlink" title="使用案例"></a>使用案例</h1><h2 id="1-精确测量代码执行时间"><a href="#1-精确测量代码执行时间" class="headerlink" title="1. 精确测量代码执行时间"></a>1. 精确测量代码执行时间</h2><p>当你需要优化某段耗时较长的代码时,可以使用 <code>performance.now()</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> start = performance.<span class="title function_">now</span>();</span><br><span class="line"> </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="number">0</span>; i < <span class="number">1e6</span>; i++) {</span><br><span class="line"> <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(i);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="keyword">const</span> end = performance.<span class="title function_">now</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`耗时:<span class="subst">${end - start}</span> 毫秒`</span>);</span><br></pre></td></tr></table></figure><h2 id="2-分析页面加载过程"><a href="#2-分析页面加载过程" class="headerlink" title="2. 分析页面加载过程"></a>2. 分析页面加载过程</h2><p>使用 <code>performance.timing</code>,可以了解页面加载的每个阶段,通过这些数据,可以快速定位页面性能瓶颈,例如 DNS 查询、资源下载等阶段。</p><img src="/2025/01/01/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-Performance%20API%E5%9F%BA%E7%A1%80/11.png" class="" title="这是图片的说明"><p><strong>常用计算:</strong></p><p>DNS查询耗时 :domainLookupEnd - domainLookupStart<br>TCP链接耗时 :connectEnd - connectStart<br>request请求耗时 :responseEnd - responseStart<br>解析dom树耗时 : domComplete - domInteractive<br>白屏时间 :responseStart - navigationStart<br>domready时间(用户可操作时间节点) :domContentLoadedEventEnd - navigationStart<br>onload时间(总下载时间) :loadEventEnd - navigationStart</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> timing = performance.<span class="property">timing</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`DNS 查询耗时:<span class="subst">${timing.domainLookupEnd - timing.domainLookupStart}</span> 毫秒`</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`TCP 连接耗时:<span class="subst">${timing.connectEnd - timing.connectStart}</span> 毫秒`</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`页面加载总耗时:<span class="subst">${timing.loadEventEnd - timing.navigationStart}</span> 毫秒`</span>);</span><br></pre></td></tr></table></figure><h2 id="3-监控资源加载时间"><a href="#3-监控资源加载时间" class="headerlink" title="3.监控资源加载时间"></a>3.监控资源加载时间</h2><p>如果你想要详细了解某些资源(如图片或脚本)的加载时间,可以使用 performance.getEntriesByType(),这种方式可以帮助你识别那些加载缓慢的资源,从而有针对性地进行优化,比如启用 CDN 或进行代码分割。</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> resources = performance.<span class="title function_">getEntriesByType</span>(<span class="string">'resource'</span>);</span><br><span class="line">resources.<span class="title function_">forEach</span>(<span class="function"><span class="params">resource</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">${resource.name}</span> 加载耗时:<span class="subst">${resource.duration}</span> 毫秒`</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="4-结合-mark-和-measure-定位性能问题"><a href="#4-结合-mark-和-measure-定位性能问题" class="headerlink" title="4. 结合 mark 和 measure 定位性能问题"></a>4. 结合 mark 和 measure 定位性能问题</h2><p>除了上述浏览器原生的性能记录,我们还可以通过<code>performance.mark()</code> && <code>performance.measure()</code>方法创建自定义的性能记录。</p><ul><li><code>mark(strName, markOptions)</code>用于创建<strong>性能记录</strong>。其第一个参数是节点ID,类型为字符串,第二个参数中的<code>markOptions.detail</code>可用于记录任意自定义数据。</li><li><code>measure(strName, measureOptions)</code>用于测量2个<strong>性能记录</strong>之间的时间差。第二个参数<code>measureOptions</code>可以指定任意自定义数据(<code>.detail</code>)以及,测量目标的开始、结束性能记录ID字符串。</li></ul><p>这2个方法,都会创建一个<code>PerformanceEntry</code>子类的实例,并保存在当前运行环境的性能记录缓冲区中。</p><h3 id="mark-使用示例"><a href="#mark-使用示例" class="headerlink" title="mark() 使用示例"></a><code>mark()</code> 使用示例</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">performance.<span class="title function_">mark</span>(<span class="string">"login-started"</span>, {</span><br><span class="line"> <span class="attr">detail</span>: { <span class="attr">href</span>: location?.<span class="property">href</span> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">performance.<span class="title function_">mark</span>(<span class="string">"login-finished"</span>, {</span><br><span class="line"> <span class="attr">detail</span>: { <span class="attr">loginType</span>: <span class="string">'email'</span> },</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>上述2行代码会在性能记录缓冲区中,添加2个性能记录,供我们后续再次获取、计算,其数据格式为:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> detail<span class="punctuation">:</span> <span class="punctuation">{</span> href<span class="punctuation">:</span> '...' <span class="punctuation">}</span></span><br><span class="line"> name<span class="punctuation">:</span> <span class="string">"login-started"</span><span class="punctuation">,</span></span><br><span class="line"> entryType<span class="punctuation">:</span> <span class="string">"mark"</span><span class="punctuation">,</span></span><br><span class="line"> startTime<span class="punctuation">:</span> <span class="number">4545338.199999809</span><span class="punctuation">,</span></span><br><span class="line"> duration<span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><h3 id="measure-使用示例"><a href="#measure-使用示例" class="headerlink" title="measure()使用示例"></a><code>measure()</code>使用示例</h3><p>调用<code>.measure()</code>方法则可以基于已经添加的2个性能记录,计算这2个记录的间隔时间<code>duration</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></pre></td><td class="code"><pre><span class="line">performance.<span class="title function_">measure</span>(<span class="string">"login-duration"</span>, {</span><br><span class="line"> <span class="attr">detail</span>: { <span class="attr">userRegion</span>: <span class="string">'cn'</span> },</span><br><span class="line"> <span class="attr">start</span>: <span class="string">'login-started'</span>,</span><br><span class="line"> <span class="attr">end</span>: <span class="string">'login-finished'</span>,</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>执行后,会向性能记录缓冲区中添加如下格式的性能记录数据:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> detail<span class="punctuation">:</span> <span class="punctuation">{</span> userRegion<span class="punctuation">:</span> '...' <span class="punctuation">}</span></span><br><span class="line"> name<span class="punctuation">:</span> <span class="string">"login-duration"</span><span class="punctuation">,</span></span><br><span class="line"> entryType<span class="punctuation">:</span> <span class="string">"measure"</span><span class="punctuation">,</span></span><br><span class="line"> startTime<span class="punctuation">:</span> <span class="number">4545338.199999809</span><span class="punctuation">,</span></span><br><span class="line"> duration<span class="punctuation">:</span> <span class="number">275118.69999980927</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><h3 id="使用案例-1"><a href="#使用案例-1" class="headerlink" title="使用案例"></a>使用案例</h3><p><code>performance.mark()</code> 和 <code>performance.measure()</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></pre></td><td class="code"><pre><span class="line">performance.<span class="title function_">mark</span>(<span class="string">'start-operation'</span>);</span><br><span class="line"> </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="number">0</span>; i < <span class="number">1e6</span>; i++) {</span><br><span class="line"> <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(i);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">performance.<span class="title function_">mark</span>(<span class="string">'end-operation'</span>);</span><br><span class="line">performance.<span class="title function_">measure</span>(<span class="string">'operation-duration'</span>, <span class="string">'start-operation'</span>, <span class="string">'end-operation'</span>);</span><br><span class="line"> </span><br><span class="line"><span class="keyword">const</span> measures = performance.<span class="title function_">getEntriesByType</span>(<span class="string">'measure'</span>);</span><br><span class="line">measures.<span class="title function_">forEach</span>(<span class="function"><span class="params">measure</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">${measure.name}</span> 耗时:<span class="subst">${measure.duration}</span> 毫秒`</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>通过这种方式,可以将性能分析的范围精确到某段代码或某个操作。</p>]]></content>
<categories>
<category> 性能优化 </category>
</categories>
<tags>
<tag> 性能优化 </tag>
</tags>
</entry>
<entry>
<title>性能优化-资源优先级提示</title>
<link href="/2024/12/31/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E8%B5%84%E6%BA%90%E4%BC%98%E5%85%88%E7%BA%A7%E6%8F%90%E7%A4%BA/"/>
<url>/2024/12/31/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E8%B5%84%E6%BA%90%E4%BC%98%E5%85%88%E7%BA%A7%E6%8F%90%E7%A4%BA/</url>
<content type="html"><![CDATA[<h2 id="预取回-Prefetch"><a href="#预取回-Prefetch" class="headerlink" title="预取回 Prefetch"></a>预取回 Prefetch</h2><p>预取回提示用于提示浏览器在CPU和网络带宽空闲时,预先下载指定URL的JS,图片等各类资源,存储到浏览器本地缓存中,从而减少该资源文件后续加载的耗时,从而优化用户体验。</p><p>具体使用方式是将<code>link</code>标签的<code>rel</code>属性设为<code>prefetch</code>,并将<code>href</code>属性设为<strong>目标资源URL</strong>,例如 <code><link rel="prefetch" href="https://github.com/JuniorTour/juniortour.js" /></code>。该标签插入DOM后,将触发一次<code>href</code>属性值对应URL的HTTP请求,并将响应保存到本地的<code>prefetch cache</code>中,但是并不会进一步解析、运行该资源。可以预取回的资源有很多:JS、CSS、各种格式的图片、音频、WASM文件、字体文件、甚至HTML文档本身都可实施 prefetch,预先缓存。</p><h2 id="预加载-Preload"><a href="#预加载-Preload" class="headerlink" title="预加载 Preload"></a>预加载 Preload</h2><p>与预取回不同,预加载用于提高<strong>当前</strong>页面中资源加载的优先级,确保关键资源优先加载完成。预加载最常见的用法是用于字体文件,减少因字体加载较慢导致的文字字体闪烁变化。例如:<code><link rel="preload" as="font" href="/main.woff" /></code>应用了<code>preload</code>提示的资源,通常会以较高的优先级<strong>率先</strong>在网页中加载,例如下图中的<code>nato-sans.woff2</code>请求,<code>Priority</code>列的值为<code>High</code>,加载顺序仅次于<code>Document</code>本身,能让字体较早在页面中渲染生效。</p><h2 id="预连接-Preconnect"><a href="#预连接-Preconnect" class="headerlink" title="预连接 Preconnect"></a>预连接 Preconnect</h2><p>预连接提示用于提前与目标域名握手,完成DNS寻址,并建立TCP和TLS链接。</p><p>具体使用方式是将<code>link</code>标签的<code>rel</code>属性设为<code>preconnect</code>,并将<code>href</code>属性设为目标<strong>域名</strong>,例如 <code><link rel="preconnect" href="https://github.com" /></code>。</p><p>优化效果是通过提前完成DNS寻址、建立TCP链接和完成TLS握手,从而减少后续访问目标域名时的连接耗时,改善用户体验。</p><p><strong>注意!</strong> 强烈建议只对<strong>重要域名</strong>进行<code>Preconnect</code>优化,数量不要超过 6 个。</p><p>因为<code>Preconnect</code>生效后,会与目标域名的保持至少10秒钟的网络连接,占用设备的网络和内存资源,甚至阻碍其他资源的加载。</p><h2 id="DNS预取回-DNS-Prefetch"><a href="#DNS预取回-DNS-Prefetch" class="headerlink" title="DNS预取回 DNS-Prefetch"></a>DNS预取回 DNS-Prefetch</h2><p>与上文的预取回Prefetch不同,DNS预取回用于对<strong>目标域名</strong>提前进行DNS寻址,取回并缓存域名对应的IP地址,而非像预取回Prefetch那样缓存文件资源。</p><p>具体使用方式是将<code>link</code>标签的<code>rel</code>属性设为<code>dns-prefetch</code>,并将<code>href</code>属性值设为<strong>目标域名</strong>,例如 <code><link rel="dns-prefetch" href="https://github.com" /></code>。</p><p>优化效果是通过提前解析出目标域名的IP地址,从而减少后续从目标域名加载资源的耗时,加快页面加载速度,改善用户体验。</p><p>通常来说,解析DNS的耗时往往有几十甚至几百毫秒,对资源加载耗时有直接影响。</p>]]></content>
<categories>
<category> 性能优化 </category>
</categories>
<tags>
<tag> 性能优化 </tag>
</tags>
</entry>
<entry>
<title>vue-vuex中mutation为什么不能异步</title>
<link href="/2024/12/30/vue-vuex%E4%B8%ADmutation%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E5%BC%82%E6%AD%A5/"/>
<url>/2024/12/30/vue-vuex%E4%B8%ADmutation%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E5%BC%82%E6%AD%A5/</url>
<content type="html"><![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>在Vue.js的状态管理库Vuex中,我们经常听到关于为什么在mutation中不应该执行异步操作的建议。今天,让我们深入探讨这个话题,揭示其中的奥秘,为什么异步操作应该交给Action,而mutation保持同步执行。</p><h1 id="Why:Mutation要同步"><a href="#Why:Mutation要同步" class="headerlink" title="Why:Mutation要同步"></a>Why:Mutation要同步</h1><p><strong>状态的追踪与DevTools</strong></p><p>在Vuex中,mutation是唯一用于<strong>更改状态的途径</strong>。每个mutation执行完成后,都会对应到一个新的状态变更。这种同步的特性使得Vue DevTools能够轻松地追踪每一个状态的变化。它能够在每次mutation被调用时打个快照,形成一个状态变更的历史记录。这种机制对于调试和<a href="https://zhida.zhihu.com/search?content_id=237730957&content_type=Article&match_order=1&q=%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96&zhida_source=entity">性能优化</a>都非常有帮助。</p><p><strong>实现Time-Travel的便利性</strong></p><p>由于mutation是同步执行的,状态变更是可预测的,可以准确地知道何时发生了变化。这为实现时光旅行(time-travel)提供了便利。通过DevTools,我们能够回溯到应用的不同状态,轻松地跳转到过去的状态,帮助我们更好地理解应用程序的运行过程。</p><h1 id="Reason-若非要异步?"><a href="#Reason-若非要异步?" class="headerlink" title="Reason: 若非要异步?"></a>Reason: 若非要异步?</h1><p><strong>无法知晓状态何时更新</strong></p><p>如果mutation支持异步操作,就会引发一个问题:我们将无法精确知晓状态是何时更新的。异步操作的<a href="https://zhida.zhihu.com/search?content_id=237730957&content_type=Article&match_order=1&q=%E6%89%A7%E8%A1%8C%E6%97%B6%E9%97%B4&zhida_source=entity">执行时间</a>是不确定的,这使得状态变更的时机变得模糊不清。这样一来,在调试过程中就难以准确追踪状态的变化,给<a href="https://zhida.zhihu.com/search?content_id=237730957&content_type=Article&match_order=1&q=%E5%BC%80%E5%8F%91%E8%80%85&zhida_source=entity">开发者</a>带来不小的困扰。</p><p><strong>状态更新的不可控性</strong></p><p>异步操作可能导致多个mutation交叉执行,而这会增加状态更新的不可控性。在一个异步操作中可能发生多次状态变更,而这些变更又可能相互影响,使得代码难以维护和调试。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>在Vuex中,Mutation的同步执行特性保证了状态变更的可预测性,利于调试和性能优化。将异步操作交给Action,通过commit mutation来实现状态的同步更新,是为了维护代码的清晰性和可维护性。理解这一设计原理,有助于更好地利用Vuex进行状态管理,使应用更具可维护性和可调试性。希望本文能够帮助你更深入地理解为什么mutation中不宜执行异步操作,以及如何通过Action来处理异步逻辑。</p>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> vue </tag>
</tags>
</entry>
<entry>
<title>性能优化-虚拟列表</title>
<link href="/2024/12/29/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E8%99%9A%E6%8B%9F%E5%88%97%E8%A1%A8/"/>
<url>/2024/12/29/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E8%99%9A%E6%8B%9F%E5%88%97%E8%A1%A8/</url>
<content type="html"><![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>虚拟列表是一种优化大量数据渲染的技术,它通过仅渲染用户可视区域内的列表项,显著减少DOM节点数量,从而提高渲染性能。在Vue.js项目中,虚拟列表尤其适用于长列表数据展示,如商品列表、聊天记录等。</p><h1 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h1><p>虚拟列表的核心思想是“按需渲染”,即只渲染当前可视区域内的列表项。当用户滚动时,虚拟列表动态地加载和卸载视口内的元素,以降低DOM节点的数量,提升渲染性能。其基本原理包括:</p><ol><li><strong>确定窗口大小</strong>:根据屏幕大小、滚动位置等因素动态调整每次渲染的列表项数量。</li><li><strong>计算偏移量</strong>:根据当前滚动位置和窗口大小,计算出当前可视区域内的偏移量。</li><li><strong>生成虚拟节点</strong>:根据偏移量从数据源中提取相应的列表项,并生成虚拟节点。虚拟节点是轻量级的DOM元素,不会真正插入到DOM树中,而是用于渲染列表项的外观。</li><li><strong>渲染虚拟节点</strong>:将虚拟节点按照指定的布局方式渲染到视口中。由于虚拟节点是轻量级的,因此可以快速地创建和销毁它们,从而实现高效的渲染。</li></ol><h1 id="要点解析"><a href="#要点解析" class="headerlink" title="要点解析"></a>要点解析</h1><p>初始化:startIndex表示第一个要渲染的列表项的下标、endIndex表示最后一个要渲染的列表项的下标、visibleItems表示要渲染的列表数组、items表示真实的列表数组、totalHeight表示列表的总高度,itemHeight表示列表项高度,offsetY表示每个列表项向上偏移的高度。</p><p>1.首先要给我们的父元素设置一个固定高度并设置溢出隐藏,然后子元素用于渲染列表,将子元素的总高度设为真实列表的总高度。然后维护一个数组visibleItems,代表要渲染的列表,遍历这个数组生成列表项dom,为列表项dom动态绑定transform:translateY属性。在父元素上绑定scroll事件,触发鼠标滚动时间时,调用函数更新visibleItems。</p><p>2.更新visibleItems的过程:用父元素的scrollTop的值除每个列表项dom的高度,就能得到要开始渲染的列表项的下标startIndex。startIndex+visibleCount就可以得到结束渲染的列表项的下标endIndex。根据这两项的值去更新visibleItems,再根据scrollTop去更新offestY,从而实现列表的渲染。</p><h1 id="代码部分"><a href="#代码部分" class="headerlink" title="代码部分"></a>代码部分</h1><p>封装的组件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><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></pre></td><td class="code"><pre><span class="line"><!-- VirtualList.vue --></span><br><span class="line"><template></span><br><span class="line"> <div class="virtual-list" ref="listContainer" @scroll="onScroll"></span><br><span class="line"> <div class="virtual-list-content" :style="{ height: totalHeight + 'px' }"></span><br><span class="line"> <div</span><br><span class="line"> v-for="(item, index) in visibleItems"</span><br><span class="line"> :key="item.id"</span><br><span class="line"> :style="{ height: itemHeight + 'px', transform: `translateY(${offsetY}px)` }"</span><br><span class="line"> class="virtual-list-item"</span><br><span class="line"> ></span><br><span class="line"> <slot :item="item" :index="index"></slot></span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script setup></span><br><span class="line">import { ref, computed, onMounted, onUnmounted } from 'vue';</span><br><span class="line"></span><br><span class="line">const props = defineProps({</span><br><span class="line"> items: {</span><br><span class="line"> type: Array,</span><br><span class="line"> required: true,</span><br><span class="line"> },</span><br><span class="line"> itemHeight: {</span><br><span class="line"> type: Number,</span><br><span class="line"> default: 30,</span><br><span class="line"> },</span><br><span class="line"> itemComponent: {</span><br><span class="line"> type: Object,</span><br><span class="line"> required: true,</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">const listContainer = ref(null);</span><br><span class="line">const startIndex = ref(0);</span><br><span class="line">const visibleCount = ref(0);</span><br><span class="line">const offsetY = ref(0);</span><br><span class="line">const totalHeight = computed(() => props.items.length * props.itemHeight);</span><br><span class="line"></span><br><span class="line">const calculateVisibleCount = () => {</span><br><span class="line"> const container = listContainer.value;</span><br><span class="line"> if (container) {</span><br><span class="line"> const containerHeight = container.clientHeight;</span><br><span class="line"> visibleCount.value = Math.ceil(containerHeight / props.itemHeight) + 1;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">const visibleItems = computed(() => {</span><br><span class="line"> const endIndex = startIndex.value + visibleCount.value;</span><br><span class="line"> return props.items.slice(startIndex.value, endIndex);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">const onScroll = () => {</span><br><span class="line"> const container = listContainer.value;</span><br><span class="line"> if (container) {</span><br><span class="line"> const scrollTop = container.scrollTop;</span><br><span class="line"> startIndex.value = Math.floor(scrollTop / props.itemHeight);</span><br><span class="line"> offsetY.value = scrollTop - (scrollTop % props.itemHeight);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">onMounted(() => {</span><br><span class="line"> calculateVisibleCount();</span><br><span class="line"> window.addEventListener('resize', calculateVisibleCount);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">onUnmounted(() => {</span><br><span class="line"> window.removeEventListener('resize', calculateVisibleCount);</span><br><span class="line">});</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style scoped></span><br><span class="line">.virtual-list {</span><br><span class="line"> overflow-y: auto;</span><br><span class="line"> height: 400px; /* 设定固定高度 */</span><br><span class="line"> border: 1px solid #ddd;</span><br><span class="line"> position: relative;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">.virtual-list-content {</span><br><span class="line"> position: relative;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">.virtual-list-item {</span><br><span class="line"> box-sizing: border-box;</span><br><span class="line"> border-bottom: 1px solid #f0f0f0;</span><br><span class="line"> line-height: 30px; /* 与 itemHeight 相同 */</span><br><span class="line"> padding: 0 10px;</span><br><span class="line">}</span><br><span class="line"></style></span><br></pre></td></tr></table></figure><p>使用示例</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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"><!-- App.vue --></span><br><span class="line"><template></span><br><span class="line"> <div id="app"></span><br><span class="line"> <h1>虚拟列表示例</h1></span><br><span class="line"> <VirtualList :items="items" :itemHeight="30" :itemComponent="ItemComponent"></span><br><span class="line"> <template #default="{ item, index }"></span><br><span class="line"> <div class="custom-item">{{ item.name }} - {{ index }}</div></span><br><span class="line"> </template></span><br><span class="line"> </VirtualList></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script setup></span><br><span class="line">import VirtualList from './VirtualList.vue';</span><br><span class="line"></span><br><span class="line">const items = Array.from({ length: 10000 }, (v, k) => ({</span><br><span class="line"> id: k,</span><br><span class="line"> name: `Item ${k + 1}`,</span><br><span class="line">}));</span><br><span class="line"></span><br><span class="line">const ItemComponent = {</span><br><span class="line"> template: '<div class="custom-item"><slot></slot></div>',</span><br><span class="line">};</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></span><br><span class="line">#app {</span><br><span class="line"> font-family: Avenir, Helvetica, Arial, sans-serif;</span><br><span class="line"> -webkit-font-smoothing: antialiased;</span><br><span class="line"> -moz-osx-font-smoothing: grayscale;</span><br><span class="line"> text-align: center;</span><br><span class="line"> color: #2c3e50;</span><br><span class="line"> margin-top: 60px;</span><br><span class="line">}</span><br><span class="line">.custom-item {</span><br><span class="line"> line-height: 30px; /* 与 itemHeight 相同 */</span><br><span class="line"> padding: 0 10px;</span><br><span class="line">}</span><br><span class="line"></style></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 性能优化 </category>
</categories>
<tags>
<tag> 性能优化 </tag>
</tags>
</entry>
<entry>
<title>Vue2-数组方法响应式封装-源码分析</title>
<link href="/2024/12/28/Vue-%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95%E5%93%8D%E5%BA%94%E5%BC%8F%E5%B0%81%E8%A3%85-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2024/12/28/Vue-%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95%E5%93%8D%E5%BA%94%E5%BC%8F%E5%B0%81%E8%A3%85-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="为什么对数组方法进行封装?"><a href="#为什么对数组方法进行封装?" class="headerlink" title="为什么对数组方法进行封装?"></a>为什么对数组方法进行封装?</h1><p>在vue中,对响应式处理的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组截取变化等,所以需要对这些操作进行hack,让Vue能监听到其中变化Vue被监听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。</p><h1 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h1><h2 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h2><p><strong>1.重写数组的变异方法</strong><br>Vue 重写了数组的变异方法(例如 push、pop、shift、unshift、splice、sort 和 reverse),在这些方法执行时,会触发通知机制,告知 Vue 数据发生了变化。</p><p><strong>2.使用proto或者prototype</strong><br>Vue 将数组的原型指向了一个特殊的对象,该对象包含了重写后的数组变异方法,这样就能够确保当调用这些方法时,能够触发数据更新。</p><h2 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h2><p>1.首先拿到数组的原型对象Array.prototype并缓存,使用Object.create方法创建一个新对象obj,使得该对象的原型是Array.prototype。</p><p>2.使用def函数为obj重写原生数组方法。具体来说,首先调用原生数组方法,拿到结果,然后判断是否触发视图更新,若触发,则获取Observer对象ob,调用ob.observeArray()设置响应式监听,然后调用ob.dep.notify()更新视图,并返回结果。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 缓存数组原型</span></span><br><span class="line"><span class="keyword">const</span> arrayProto = <span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>;</span><br><span class="line"><span class="comment">// 实现 arrayMethods.__proto__ === Array.prototype</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> arrayMethods = <span class="title class_">Object</span>.<span class="title function_">create</span>(arrayProto);</span><br><span class="line"><span class="comment">// 需要进行功能拓展的方法</span></span><br><span class="line"><span class="keyword">const</span> methodsToPatch = [<span class="string">"push"</span>,<span class="string">"pop"</span>,<span class="string">"shift"</span>,<span class="string">"unshift"</span>,<span class="string">"splice"</span>,<span class="string">"sort"</span>,<span class="string">"reverse"</span>];</span><br><span class="line"></span><br><span class="line">methodsToPatch.<span class="title function_">forEach</span>(<span class="keyword">function</span>(<span class="params">method</span>) {</span><br><span class="line"> <span class="comment">// 缓存原生数组方法</span></span><br><span class="line"> <span class="keyword">const</span> original = arrayProto[method];</span><br><span class="line"> <span class="title function_">def</span>(arrayMethods, method, <span class="keyword">function</span> <span class="title function_">mutator</span>(<span class="params">...args</span>) {</span><br><span class="line"> <span class="comment">// 执行并缓存原生数组功能</span></span><br><span class="line"> <span class="keyword">const</span> result = original.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args);</span><br><span class="line"> <span class="comment">// 响应式处理</span></span><br><span class="line"> <span class="keyword">const</span> ob = <span class="variable language_">this</span>.<span class="property">__ob__</span>;</span><br><span class="line"> <span class="keyword">let</span> inserted;</span><br><span class="line"> <span class="keyword">switch</span> (method) {</span><br><span class="line"> <span class="comment">// push、unshift会新增索引,所以要手动observer</span></span><br><span class="line"> <span class="keyword">case</span> <span class="string">"push"</span>:</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"unshift"</span>:</span><br><span class="line"> inserted = args;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">// splice方法,如果传入了第三个参数,也会有索引加入,也要手动observer。</span></span><br><span class="line"> <span class="keyword">case</span> <span class="string">"splice"</span>:</span><br><span class="line"> inserted = args.<span class="title function_">slice</span>(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">break</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> (inserted) ob.<span class="title function_">observeArray</span>(inserted);<span class="comment">// 获取插入的值,并设置响应式监听</span></span><br><span class="line"> <span class="comment">// notify change</span></span><br><span class="line"> ob.<span class="property">dep</span>.<span class="title function_">notify</span>();<span class="comment">// 通知依赖更新</span></span><br><span class="line"> <span class="comment">// 返回原生数组方法的执行结果</span></span><br><span class="line"> <span class="keyword">return</span> result;</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><strong>解读</strong></h2><p><strong>1.缓存原生数组方法</strong></p><p>首先,代码通过 const arrayProto = Array.prototype 缓存了原生的数组方法,以备后续使用。</p><p><strong>2.创建扩展后的数组对象</strong></p><p>然后,通过 Object.create(arrayProto) 创建了一个名为 arrayMethods 的对象,这样就可以在这个对象上添加新的方法或者重写原有的方法,而不影响原生的 Array.prototype。</p><p><strong>3.遍历需要拓展的方法</strong></p><p>接下来,代码遍历了需要拓展的数组方法,对每个方法进行拓展。这些方法包括 push、pop、shift、unshift、splice、sort 和 reverse。</p><p><strong>4.拓展方法的实现</strong></p><p>对于每个方法,使用 def 函数将原生方法替换为新的方法。新方法在执行原生方法后,进行响应式处理并通知依赖更新。具体的响应式处理包括:</p><ul><li>获取操作的数据对象的 Observer 实例(ob)。</li><li>根据操作类型,获取需要设置响应式监听的值。</li><li>如果有需要设置响应式监听的值,调用 Observer 实例的 observeArray 方法进行处理。</li><li>最后,通过 ob.dep.notify() 通知依赖更新。</li></ul>]]></content>
<categories>
<category> Vue2 </category>
</categories>
<tags>
<tag> Vue2 </tag>
</tags>
</entry>
<entry>
<title>Vue2-KeepAlive-源码分析</title>
<link href="/2024/12/27/Vue2-Keep-Alive%E7%9A%84%E7%90%86%E8%A7%A3/"/>
<url>/2024/12/27/Vue2-Keep-Alive%E7%9A%84%E7%90%86%E8%A7%A3/</url>
<content type="html"><![CDATA[<h1 id="Keep-alive是什么?"><a href="#Keep-alive是什么?" class="headerlink" title="Keep-alive是什么?"></a>Keep-alive是什么?</h1><p>keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们</p><p>keep-alive可以设置以下props属性:</p><ul><li><p>include - 字符串或正则表达式。只有名称匹配的组件会被缓存</p></li><li><p>exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存</p></li><li><p>max - 数字。最多可以缓存多少组件实例</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">keep-alive</span> <span class="attr">include</span>=<span class="string">"a,b"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">component</span> <span class="attr">:is</span>=<span class="string">"view"</span>></span><span class="tag"></<span class="name">component</span>></span></span><br><span class="line"><span class="tag"></<span class="name">keep-alive</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 正则表达式 (使用 `v-bind`) --></span></span><br><span class="line"><span class="tag"><<span class="name">keep-alive</span> <span class="attr">:include</span>=<span class="string">"/a|b/"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">component</span> <span class="attr">:is</span>=<span class="string">"view"</span>></span><span class="tag"></<span class="name">component</span>></span></span><br><span class="line"><span class="tag"></<span class="name">keep-alive</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 数组 (使用 `v-bind`) --></span></span><br><span class="line"><span class="tag"><<span class="name">keep-alive</span> <span class="attr">:include</span>=<span class="string">"['a', 'b']"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">component</span> <span class="attr">:is</span>=<span class="string">"view"</span>></span><span class="tag"></<span class="name">component</span>></span></span><br><span class="line"><span class="tag"></<span class="name">keep-alive</span>></span></span><br></pre></td></tr></table></figure><p>匹配首先检查组件自身的 name 选项,如果name选项不可用,则匹配它的局部注册名称 (父组件components 选项的键值),匿名组件不能被匹配。</p><p>设置了keep-alive缓存的组件,会多出两个生命周期钩子(activated与deactivated):</p><ul><li><p>首次进入组件时:beforeRouteEnter > beforeCreate > created > mounted > activated > … … > beforeRouteLeave > deactivated</p></li><li><p>再次进入组件时:beforeRouteEnter > activated > … … > beforeRouteLeave > deactivated</p></li></ul><h1 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h1><p>使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive</p><p>举个例子:</p><p>当我们从 首页 –> 列表页 –> 商详页 –> 再返回,这时候列表页应该是需要keep-alive</p><p>从 首页 –> 列表页 –> 商详页 –> 返回到列表页(需要缓存) –> 返回到首页(需要缓存) –> 再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive</p><p>在路由中设置keepAlive属性判断是否需要缓存</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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="punctuation">{</span></span><br><span class="line"> path<span class="punctuation">:</span> 'list'<span class="punctuation">,</span></span><br><span class="line"> name<span class="punctuation">:</span> 'itemList'<span class="punctuation">,</span> <span class="comment">// 列表页</span></span><br><span class="line"> component (resolve) <span class="punctuation">{</span></span><br><span class="line"> require(<span class="punctuation">[</span>'@/pages/item/list'<span class="punctuation">]</span><span class="punctuation">,</span> resolve)</span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> meta<span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> keepAlive<span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> title<span class="punctuation">:</span> '列表页'</span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span> <span class="attr">class</span>=<span class="string">'wrapper'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">keep-alive</span>></span></span><br><span class="line"> <span class="comment"><!-- 需要缓存的视图组件 --></span> </span><br><span class="line"> <span class="tag"><<span class="name">router-view</span> <span class="attr">v-if</span>=<span class="string">"$route.meta.keepAlive"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">keep-alive</span>></span></span><br><span class="line"> <span class="comment"><!-- 不需要缓存的视图组件 --></span></span><br><span class="line"> <span class="tag"><<span class="name">router-view</span> <span class="attr">v-if</span>=<span class="string">"!$route.meta.keepAlive"</span>></span><span class="tag"></<span class="name">router-view</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h1 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h1><p><strong>核心思路:</strong></p><p>1.keepalive组件维护一个cache对象和keys数组,cache对象的key是缓存的组件的name,value是缓存的组件的vnode。keys数组存放的是所有已缓存组件的name和cache的key一一对应。</p><p>2.keepalive组件在挂载的时候会监听include和exclude属性,若有变化则说明缓存规则发生变化,则执行pruneCache方法更新cache对象和keys数组。pruneCache方法会取出cache对象的每一个缓存组件的name值,并与缓存规则相比较,若符合则保留,若不符合,则从cache和keys中删掉。</p><p>3.keepalive组件在销毁的时候会遍历cache对象,将处于未渲染状态的组件销毁,并从cache和keys中删除。</p><p>4.缓存功能是在<code>render</code>函数中实现,首先获取keep-alive组件中的子组件的<code>key</code>值,然后判断需不需要缓存,若需要缓存,拿到<code>key</code>值后去<code>this.cache</code>对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存,返回对应的vnode。若组件还没有被缓存过,则以该组件的<code>key</code>为键,组件<code>vnode</code>为值,将其存入<code>this.cache</code>中,并且把<code>key</code>存入<code>this.keys</code>中。此时再判断<code>this.keys</code>中缓存组件的数量是否超过了设置的最大缓存数量值<code>this.max</code>,如果超过了,则把第一个缓存组件删掉。</p><p><code>keep-alive</code>是<code>vue</code>中内置的一个组件</p><p>源码位置:<code>src/core/components/keep-alive.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'keep-alive'</span>,</span><br><span class="line"> <span class="attr">abstract</span>: <span class="literal">true</span>,</span><br><span class="line"></span><br><span class="line"> <span class="attr">props</span>: {</span><br><span class="line"> <span class="attr">include</span>: [<span class="title class_">String</span>, <span class="title class_">RegExp</span>, <span class="title class_">Array</span>],</span><br><span class="line"> <span class="attr">exclude</span>: [<span class="title class_">String</span>, <span class="title class_">RegExp</span>, <span class="title class_">Array</span>],</span><br><span class="line"> <span class="attr">max</span>: [<span class="title class_">String</span>, <span class="title class_">Number</span>]</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="title function_">created</span> () {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">cache</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>)</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">keys</span> = []</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="title function_">destroyed</span> () {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> <span class="variable language_">this</span>.<span class="property">cache</span>) {</span><br><span class="line"> <span class="title function_">pruneCacheEntry</span>(<span class="variable language_">this</span>.<span class="property">cache</span>, key, <span class="variable language_">this</span>.<span class="property">keys</span>)</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="title function_">mounted</span> () {</span><br><span class="line"> <span class="variable language_">this</span>.$watch(<span class="string">'include'</span>, <span class="function"><span class="params">val</span> =></span> {</span><br><span class="line"> <span class="title function_">pruneCache</span>(<span class="variable language_">this</span>, <span class="function"><span class="params">name</span> =></span> <span class="title function_">matches</span>(val, name))</span><br><span class="line"> })</span><br><span class="line"> <span class="variable language_">this</span>.$watch(<span class="string">'exclude'</span>, <span class="function"><span class="params">val</span> =></span> {</span><br><span class="line"> <span class="title function_">pruneCache</span>(<span class="variable language_">this</span>, <span class="function"><span class="params">name</span> =></span> !<span class="title function_">matches</span>(val, name))</span><br><span class="line"> })</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">/* 获取默认插槽中的第一个组件节点 */</span></span><br><span class="line"> <span class="keyword">const</span> slot = <span class="variable language_">this</span>.<span class="property">$slots</span>.<span class="property">default</span></span><br><span class="line"> <span class="keyword">const</span> vnode = <span class="title function_">getFirstComponentChild</span>(slot)</span><br><span class="line"> <span class="comment">/* 获取该组件节点的componentOptions */</span></span><br><span class="line"> <span class="keyword">const</span> componentOptions = vnode && vnode.<span class="property">componentOptions</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (componentOptions) {</span><br><span class="line"> <span class="comment">/* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */</span></span><br><span class="line"> <span class="keyword">const</span> name = <span class="title function_">getComponentName</span>(componentOptions)</span><br><span class="line"> <span class="keyword">const</span> { include, exclude } = <span class="variable language_">this</span></span><br><span class="line"> <span class="comment">/* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */</span></span><br><span class="line"> <span class="keyword">if</span> ((include && (!name || !<span class="title function_">matches</span>(include, name))) ||(exclude && name && <span class="title function_">matches</span>(exclude, name))) {</span><br><span class="line"> <span class="keyword">return</span> vnode</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> { cache, keys } = <span class="variable language_">this</span></span><br><span class="line"> <span class="comment">/* 获取组件的key值 */</span></span><br><span class="line"> <span class="keyword">const</span> key = vnode.<span class="property">key</span> == <span class="literal">null</span></span><br><span class="line"> ? componentOptions.<span class="property">Ctor</span>.<span class="property">cid</span> + (componentOptions.<span class="property">tag</span> ? <span class="string">`::<span class="subst">${componentOptions.tag}</span>`</span> : <span class="string">''</span>)</span><br><span class="line"> : vnode.<span class="property">key</span></span><br><span class="line"> <span class="comment">/* 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */</span></span><br><span class="line"> <span class="keyword">if</span> (cache[key]) {</span><br><span class="line"> vnode.<span class="property">componentInstance</span> = cache[key].<span class="property">componentInstance</span></span><br><span class="line"> <span class="title function_">remove</span>(keys, key)</span><br><span class="line"> keys.<span class="title function_">push</span>(key)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 如果没有命中缓存,则将其设置进缓存 */</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> cache[key] = vnode</span><br><span class="line"> keys.<span class="title function_">push</span>(key)</span><br><span class="line"> <span class="comment">/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">max</span> && keys.<span class="property">length</span> > <span class="built_in">parseInt</span>(<span class="variable language_">this</span>.<span class="property">max</span>)) {</span><br><span class="line"> <span class="title function_">pruneCacheEntry</span>(cache, keys[<span class="number">0</span>], keys, <span class="variable language_">this</span>.<span class="property">_vnode</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> vnode.<span class="property">data</span>.<span class="property">keepAlive</span> = <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> vnode || (slot && slot[<span class="number">0</span>])</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>this.cache</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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="property">cache</span> = {</span><br><span class="line"> <span class="string">'key1'</span>:<span class="string">'组件1'</span>,</span><br><span class="line"> <span class="string">'key2'</span>:<span class="string">'组件2'</span>,</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在组件销毁的时候执行<code>pruneCacheEntry</code>函数</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">pruneCacheEntry</span>(<span class="params">cache: VNodeCache, key: string, keys: <span class="built_in">Array</span><string>, current?: VNode</span>) {</span><br><span class="line"> <span class="keyword">const</span> cached = <span class="variable language_">this</span>.<span class="property">cache</span>[key]</span><br><span class="line"> <span class="comment">/*判断当前没有处于被渲染状态的组件,将其销毁*/</span></span><br><span class="line"> <span class="keyword">if</span> (cached && (!current || cached.<span class="property">tag</span> !== current.<span class="property">tag</span>)) {</span><br><span class="line"> cached.<span class="property">componentInstance</span>.$destroy()</span><br><span class="line"> }</span><br><span class="line"> cache[key] = <span class="literal">null</span></span><br><span class="line"> <span class="title function_">remove</span>(keys, key)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在<code>mounted</code>钩子函数中观测 <code>include</code> 和 <code>exclude</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></pre></td><td class="code"><pre><span class="line"><span class="title function_">mounted</span> () {</span><br><span class="line"> <span class="variable language_">this</span>.$watch(<span class="string">'include'</span>, <span class="function"><span class="params">val</span> =></span> {</span><br><span class="line"> <span class="title function_">pruneCache</span>(<span class="variable language_">this</span>, <span class="function"><span class="params">name</span> =></span> <span class="title function_">matches</span>(val, name))</span><br><span class="line"> })</span><br><span class="line"> <span class="variable language_">this</span>.$watch(<span class="string">'exclude'</span>, <span class="function"><span class="params">val</span> =></span> {</span><br><span class="line"> <span class="title function_">pruneCache</span>(<span class="variable language_">this</span>, <span class="function"><span class="params">name</span> =></span> !<span class="title function_">matches</span>(val, name))</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果 <code>include</code> 或 <code>exclude</code> 发生了变化,即表示定义需要缓存的组件的规则或者不需要缓存的组件的规则发生了变化,那么就执行<code>pruneCache</code>函数,函数如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">pruneCache</span> (<span class="params">keepAliveInstance, filter</span>) {</span><br><span class="line"> <span class="keyword">const</span> { cache, keys, _vnode } = keepAliveInstance</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> cache) {</span><br><span class="line"> <span class="keyword">const</span> cachedNode = cache[key]</span><br><span class="line"> <span class="keyword">if</span> (cachedNode) {</span><br><span class="line"> <span class="keyword">const</span> name = <span class="title function_">getComponentName</span>(cachedNode.<span class="property">componentOptions</span>)</span><br><span class="line"> <span class="keyword">if</span> (name && !<span class="title function_">filter</span>(name)) {</span><br><span class="line"> <span class="title function_">pruneCacheEntry</span>(cache, key, keys, _vnode)</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>在该函数内对<code>this.cache</code>对象进行遍历,取出每一项的<code>name</code>值,用其与新的缓存规则进行匹配,如果匹配不上,则表示在新的缓存规则下该组件已经不需要被缓存,则调用<code>pruneCacheEntry</code>函数将其从<code>this.cache</code>对象剔除即可</p><p><code>keep-alive</code>的最强大缓存功能是在<code>render</code>函数中实现。首先获取组件的<code>key</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="keyword">const</span> key = vnode.<span class="property">key</span> == <span class="literal">null</span>? componentOptions.<span class="property">Ctor</span>.<span class="property">cid</span> + (componentOptions.<span class="property">tag</span> ? <span class="string">`::<span class="subst">${componentOptions.tag}</span>`</span> : <span class="string">''</span>) : vnode.<span class="property">key</span></span><br></pre></td></tr></table></figure><p>拿到<code>key</code>值后去<code>this.cache</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 */</span></span><br><span class="line"><span class="keyword">if</span> (cache[key]) {</span><br><span class="line"> vnode.<span class="property">componentInstance</span> = cache[key].<span class="property">componentInstance</span></span><br><span class="line"> <span class="comment">/* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */</span></span><br><span class="line"> <span class="title function_">remove</span>(keys, key)</span><br><span class="line"> keys.<span class="title function_">push</span>(key)</span><br><span class="line">} <span class="comment">/* 如果没有命中缓存,则将其设置进缓存 */</span></span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line"> cache[key] = vnode</span><br><span class="line"> keys.<span class="title function_">push</span>(key)</span><br><span class="line"> <span class="comment">/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">max</span> && keys.<span class="property">length</span> > <span class="built_in">parseInt</span>(<span class="variable language_">this</span>.<span class="property">max</span>)) {</span><br><span class="line"> <span class="title function_">pruneCacheEntry</span>(cache, keys[<span class="number">0</span>], keys, <span class="variable language_">this</span>.<span class="property">_vnode</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>若组件还没有被缓存过,则以该组件的<code>key</code>为键,组件<code>vnode</code>为值,将其存入<code>this.cache</code>中,并且把<code>key</code>存入<code>this.keys</code>中。此时再判断<code>this.keys</code>中缓存组件的数量是否超过了设置的最大缓存数量值<code>this.max</code>,如果超过了,则把第一个缓存组件删掉</p>]]></content>
<categories>
<category> Vue2 </category>
</categories>
<tags>
<tag> Vue2 </tag>
</tags>
</entry>
<entry>
<title>详解Arcgis js-SketchViewModel对象</title>
<link href="/2024/12/27/arcgis-viewModel%E8%AF%A6%E8%A7%A3/"/>
<url>/2024/12/27/arcgis-viewModel%E8%AF%A6%E8%A7%A3/</url>
<content type="html"><![CDATA[<h1 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h1><p><code>sketch.viewModel</code> 是 ArcGIS JS API 中的一个核心对象,负责处理地图上的绘制、编辑、删除和取消等操作。它为用户提供了创建、更新和删除图形的功能,并能够处理用户与地图的交互,特别是在地图上绘制点、线、面等图形时。</p><p><code>SketchViewModel</code>(通常通过 <code>sketch.viewModel</code> 实例化)是一个视图模型类,用于管理用户在地图上进行的绘制操作,提供了高度抽象的 API,能够简化用户绘制和编辑图形的过程。</p><h1 id="基本功能"><a href="#基本功能" class="headerlink" title="基本功能"></a>基本功能</h1><ul><li><strong>绘制图形</strong>:允许用户绘制各种图形(如点、线、多边形、矩形等)。</li><li><strong>编辑图形</strong>:可以让用户修改已绘制的图形。</li><li><strong>删除图形</strong>:支持删除地图上的图形。</li><li><strong>取消操作</strong>:允许用户取消当前绘制或编辑的操作。</li></ul><h1 id="主要属性和方法"><a href="#主要属性和方法" class="headerlink" title="主要属性和方法"></a>主要属性和方法</h1><h4 id="基本属性"><a href="#基本属性" class="headerlink" title="基本属性"></a><strong>基本属性</strong></h4><ul><li>**<code>view</code>**:必需的属性,指定 <code>SketchViewModel</code> 所关联的地图视图(<code>MapView</code> 或 <code>SceneView</code>)。</li><li>**<code>layer</code>**:指定 <code>SketchViewModel</code> 使用的图层,通常是 <code>GraphicsLayer</code>,用于保存绘制的图形。</li><li>**<code>create</code>**:定义当前绘制的图形类型(例如,点、线、多边形等)。</li><li>**<code>update</code>**:用于编辑已绘制的图形。</li></ul><h4 id="2-主要方法"><a href="#2-主要方法" class="headerlink" title="2. 主要方法"></a>2. <strong>主要方法</strong></h4><ul><li>**<code>create(geometryType, options)</code>**:开始一个新的绘制操作,<code>geometryType</code> 参数可以是 <code>'point'</code>、<code>'polyline'</code>、<code>'polygon'</code>、<code>'rectangle'</code> 等,<code>options</code> 可用于设置绘制时的一些自定义选项。</li><li>**<code>update(graphic, updateOptions)</code>**:开始编辑一个已有的图形,<code>graphic</code> 是要编辑的图形,<code>updateOptions</code> 用于设置更新操作的选项。</li><li>**<code>cancel()</code>**:取消当前的绘制或编辑操作。</li><li>**<code>complete()</code>**:完成当前的绘制或编辑操作。</li></ul><h4 id="3-常见事件"><a href="#3-常见事件" class="headerlink" title="3. 常见事件"></a>3. <strong>常见事件</strong></h4><ul><li>**<code>create</code>**:当用户创建一个新图形时触发。</li><li>**<code>update</code>**:当用户编辑图形时触发。</li><li>**<code>delete</code>**:当用户删除图形时触发。</li><li>**<code>cancel</code>**:当用户取消当前操作时触发。</li></ul><h1 id="各个事件中的event对象"><a href="#各个事件中的event对象" class="headerlink" title="各个事件中的event对象"></a>各个事件中的event对象</h1><p>在 ArcGIS JS API 中,<code>SketchViewModel</code> 提供了多个事件,分别用于监听用户的图形创建、更新、删除等操作。这些事件触发时,都会传递一个 <code>event</code> 对象,该对象包含了与事件相关的各种信息,比如图形对象、事件的状态、所使用的工具类型等。理解 <code>event</code> 对象的结构对于正确地处理事件和实现交互逻辑至关重要。</p><p>以下是 <code>create</code>、<code>update</code> 和 <code>delete</code> 事件中的 <code>event</code> 对象的详细说明:</p><h3 id="create事件中的event对象"><a href="#create事件中的event对象" class="headerlink" title="create事件中的event对象"></a>create事件中的event对象</h3><p><code>create</code> 事件在用户绘制图形(如点、线、多边形等)时触发。<code>event</code> 对象包含关于新创建图形的详细信息。</p><h4 id="event-对象的常见属性:"><a href="#event-对象的常见属性:" class="headerlink" title="event 对象的常见属性:"></a><code>event</code> 对象的常见属性:</h4><ul><li>**<code>state</code>**:描述操作的状态(例如,<code>"complete"</code>、<code>"active"</code>、<code>"cancel"</code>)。<ul><li><code>"complete"</code>:绘制操作完成,图形已经被添加到图层。</li><li><code>"active"</code>:用户正在进行绘制操作,图形还在绘制过程中。</li><li><code>"cancel"</code>:用户取消了当前的绘制操作,图形未被添加到图层。</li></ul></li><li>**<code>tool</code>**:表示当前使用的绘制工具类型(如 <code>"polygon"</code>、<code>"polyline"</code>、<code>"point"</code>、<code>"rectangle"</code> 等)。这帮助区分用户使用的具体工具类型。</li><li>**<code>graphic</code>**:当前操作中创建的图形对象。它是一个 <code>Graphic</code> 实例,包含用户绘制的图形及其属性(如几何形状、符号等)。</li><li>**<code>graphics</code>**(在批量操作中):如果是批量操作(例如一次性创建多个图形),则此属性包含一个图形对象的数组。</li><li>**<code>toolEventInfo</code>**(可选):包含有关工具事件的额外信息,通常用于与用户交互的工具类型相关的更多细节。</li><li>**<code>aborted</code>**: 可以用来检查用户是否在图形创建过程中取消了操作(例如,用户按 <code>Esc</code> 键或者点击取消按钮)。</li></ul><h4 id="示例:"><a href="#示例:" class="headerlink" title="示例:"></a>示例:</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">sketchViewModel.<span class="title function_">on</span>(<span class="string">"create"</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span><br><span class="line"> <span class="keyword">if</span> (event.<span class="property">state</span> === <span class="string">"complete"</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"图形创建完成:"</span>, event.<span class="property">graphic</span>); <span class="comment">// 输出创建的图形</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"使用的绘制工具:"</span>, event.<span class="property">tool</span>); <span class="comment">// 输出使用的绘制工具类型</span></span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="update事件中的event对象"><a href="#update事件中的event对象" class="headerlink" title="update事件中的event对象"></a>update事件中的event对象</h3><p><code>update</code> 事件在用户编辑已存在的图形时触发(例如,拖动点、修改线条形状、调整多边形的边界等)。<code>event</code> 对象包含有关更新操作的详细信息。</p><h4 id="event-对象的常见属性:-1"><a href="#event-对象的常见属性:-1" class="headerlink" title="event 对象的常见属性:"></a><code>event</code> 对象的常见属性:</h4><ul><li>**<code>state</code>**:描述操作的状态(例如,<code>"complete"</code>、<code>"active"</code>、<code>"cancel"</code>)。<ul><li><code>"complete"</code>:编辑操作完成,图形已经更新。</li><li><code>"active"</code>:用户正在进行图形编辑,操作还未完成。</li><li><code>"cancel"</code>:用户取消了当前的编辑操作,图形没有改变。</li></ul></li><li>**<code>tool</code>**:表示当前使用的编辑工具类型(如 <code>"reshape"</code>、<code>"move"</code> 等)。这有助于区分用户执行的编辑操作是改变形状、移动图形还是其他类型的编辑。</li><li>**<code>graphics</code>**:一个图形对象的数组,包含正在编辑的所有图形。在编辑多个图形的情况下,这个数组会包含所有更新的图形。</li><li>**<code>graphic</code>**:如果只有一个图形正在被编辑,则此属性包含被编辑的图形对象。</li><li>**<code>toolEventInfo</code>**(可选):与编辑工具事件相关的更多信息(例如,用户拖动的是哪个控制点、编辑的具体细节等)。</li><li>**<code>aborted</code>**:可以用来检查用户是否取消了图形的编辑操作。</li></ul><h4 id="示例:-1"><a href="#示例:-1" class="headerlink" title="示例:"></a>示例:</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">sketchViewModel.<span class="title function_">on</span>(<span class="string">"update"</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span><br><span class="line"> <span class="keyword">if</span> (event.<span class="property">state</span> === <span class="string">"complete"</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"图形更新完成:"</span>, event.<span class="property">graphics</span>); <span class="comment">// 输出更新后的图形</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"使用的编辑工具:"</span>, event.<span class="property">tool</span>); <span class="comment">// 输出编辑工具类型</span></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (event.<span class="property">state</span> === <span class="string">"active"</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"正在编辑图形..."</span>);</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="delete-事件中的event对象"><a href="#delete-事件中的event对象" class="headerlink" title="delete 事件中的event对象"></a>delete 事件中的event对象</h3><p><code>delete</code> 事件在用户删除图形时触发,通常通过删除按钮或者键盘操作触发。<code>event</code> 对象包含删除操作的详细信息。</p><h4 id="event-对象的常见属性:-2"><a href="#event-对象的常见属性:-2" class="headerlink" title="event 对象的常见属性:"></a><code>event</code> 对象的常见属性:</h4><ul><li>**<code>graphics</code>**:一个图形对象的数组,包含所有被删除的图形。如果是单个图形被删除,则该数组只包含一个图形对象。</li><li>**<code>graphic</code>**(可选):如果删除操作只涉及单个图形,则该属性直接引用被删除的图形对象。</li><li>**<code>tool</code>**:表示触发删除操作的工具类型,通常为 <code>delete</code>,用于标识用户是通过删除工具删除的图形。</li><li>**<code>state</code>**:表示删除操作的状态。通常为 <code>"complete"</code>,因为删除操作一旦触发即会立即完成。</li><li>**<code>aborted</code>**:可以用来检查用户是否取消了图形的删除操作。</li></ul><h4 id="示例:-2"><a href="#示例:-2" class="headerlink" title="示例:"></a>示例:</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sketchViewModel.<span class="title function_">on</span>(<span class="string">"delete"</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"已删除图形:"</span>, event.<span class="property">graphics</span>); <span class="comment">// 输出删除的图形</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"删除操作使用的工具:"</span>, event.<span class="property">tool</span>); <span class="comment">// 输出触发删除操作的工具</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Arcgis js </category>
</categories>
<tags>
<tag> Arcgis </tag>
</tags>
</entry>
<entry>
<title>JS-事件机制</title>
<link href="/2024/12/25/JS-%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6/"/>
<url>/2024/12/25/JS-%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6/</url>
<content type="html"><![CDATA[<h1 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h1><p>JavaScript和html之间的交互是通过事件实现的。事件就是用户或者浏览器自身执行的某种动作。响应某个事件的函数就叫做事件处理函数。</p><h1 id="事件对象"><a href="#事件对象" class="headerlink" title="事件对象"></a>事件对象</h1><p>当事件被触发时,浏览器会创建一个事件对象,这个对象包含了与事件相关的信息,比如事件的类型,事件发生的位置等。事件对象会在事件处理函数中作为参数传递。<br>常见的属性</p><ul><li>event.target:事件源,即触发事件的元素。</li><li>event.type:事件类型(例如 “click”, “keydown” 等)。</li><li>event.preventDefault():阻止事件的默认行为(例如,阻止表单提交、链接跳转等)。</li><li>event.stopPropagation():阻止事件的冒泡,防止事件传递到父元素。</li></ul><h1 id="事件绑定"><a href="#事件绑定" class="headerlink" title="事件绑定"></a>事件绑定</h1><p><strong>内联绑定:</strong>内联绑定就是在DOM元素中直接绑定事件。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 在HTML中直接绑定点击事件 --></span></span><br><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">"alert('按钮被点击了!')"</span>></span>点击我<span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure><p><strong>js绑定:</strong>通过对象.事件的形式给对象绑定事件。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"myButton"</span>></span>点击我<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"> <span class="keyword">const</span> button = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'myButton'</span>);</span></span><br><span class="line"><span class="language-javascript"> <span class="comment">// 通过 JavaScript 绑定点击事件</span></span></span><br><span class="line"><span class="language-javascript"> button.<span class="property">onclick</span> = <span class="keyword">function</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript"> <span class="title function_">alert</span>(<span class="string">'按钮被点击了!'</span>);</span></span><br><span class="line"><span class="language-javascript"> };</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>**事件监听器:**通过addEventListener()方法给对象绑定事件,是最推荐的事件绑定方法,它可以为同一个事件绑定多个事件处理程序,并且可通过removeEventListener()方法移除事件处理程序。支持事件的捕获和冒泡机制,更灵活。</p><h1 id="事件回调机制"><a href="#事件回调机制" class="headerlink" title="事件回调机制"></a>事件回调机制</h1><p>事件回调机制是js和浏览器交互的核心,通过addEventListener绑定事件与回调函数,当事件触发时,回调函数会被调用。</p><h2 id="事件冒泡"><a href="#事件冒泡" class="headerlink" title="事件冒泡"></a>事件冒泡</h2><p>事件冒泡描述了浏览器如何处理针对嵌套元素的事件。事件开始的时候从最深层节点接收,之后沿DOM树逐级向上传播,直至document对象。</p><h2 id="事件捕获"><a href="#事件捕获" class="headerlink" title="事件捕获"></a>事件捕获</h2><p>事件捕获和事件冒泡是相反的,事件捕获是从最顶层节点开始接收事件,然后逐级向下传播,直至最深层节点。</p><p>事件的默认传播顺序是先通过捕获阶段从顶级父元素向下传播,到达目标元素之后通过冒泡阶段向上传播,到达顶级父元素结束。</p><p>可以通过addEventListener()方法的第三个参数来设置事件处理程序的执行时机,true表示捕获阶段,false表示冒泡阶段。</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="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'parent'</span>).<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'捕获阶段触发'</span>);</span><br><span class="line">}, <span class="literal">true</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="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'parent'</span>).<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'冒泡阶段触发'</span>);</span><br><span class="line">}, <span class="literal">false</span>); <span class="comment">// false 表示冒泡阶段</span></span><br></pre></td></tr></table></figure><h2 id="事件委托"><a href="#事件委托" class="headerlink" title="事件委托"></a>事件委托</h2><p><strong>定义:</strong>事件委托也称之为事件代理,是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。</p><p><strong>原理:</strong>事件委托利用事件冒泡机制来捕获事件,而不是将事件绑定到每个子元素上。比如将一个点击事件绑定到父元素,当子元素被点击时,事件会冒泡到父元素,这时父元素的事件监听器就能捕获这个事件。如果给每个子元素绑定点击事件,当子元素很多时,性能会下降。而事件委托只需要在父元素上绑定一次事件,就可以处理所有子元素的事件,提高了性能。事件委托通常用于为许多相似的元素添加相同的处理,但不仅限于此。例如可以在父容器元素上绑定事件,之后找到event.target,事件发生在指定元素内就处理该事件。</p><p><strong>举例:</strong>一个有9个单元格的table表格,点击任意单元格,都会触发事件,并高亮显示该单元格。(也可以是99个单元格,999个单元格甚至更多)。</p><p><strong>实现:</strong>table添加点击事件并添加监听器,捕获所有的td去除高亮,之后使用event.target给点击的td设置高亮。</p><h2 id="异步回调机制"><a href="#异步回调机制" class="headerlink" title="异步回调机制"></a>异步回调机制</h2><p>js是单线程的编程语言,事件回调机制和异步编程(如setTimeout、Promise、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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'开始'</span>);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'回调函数执行'</span>);</span><br><span class="line">}, <span class="number">0</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'结束'</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></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">//回调函数执行</span></span><br></pre></td></tr></table></figure><h3 id="事件回调的执行顺序"><a href="#事件回调的执行顺序" class="headerlink" title="事件回调的执行顺序"></a>事件回调的执行顺序</h3><ol><li>事件触发并将回调函数加入任务队列</li><li>执行当前栈中的同步代码</li><li>同步代码执行完毕,事件队列中的回调函数被放入执行栈中执行。</li></ol>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> 事件机制 </tag>
</tags>
</entry>
<entry>
<title>async与defer的区别</title>
<link href="/2024/12/25/JS-async%E4%B8%8Edefer/"/>
<url>/2024/12/25/JS-async%E4%B8%8Edefer/</url>
<content type="html"><![CDATA[<h1 id="1-浏览器的解析和渲染过程"><a href="#1-浏览器的解析和渲染过程" class="headerlink" title="1. 浏览器的解析和渲染过程"></a>1. 浏览器的解析和渲染过程</h1><p>浏览器在加载网页时,会按照以下步骤解析和渲染内容:</p><ul><li><p>HTML解析: 浏览器从顶部开始逐行解析HTML文件,生成DOM树,代表页面的结构。</p></li><li><p>CSS解析: 同时,浏览器会解析CSS文件和内联样式,生成CSSOM树,代表页面的样式。</p></li><li><p>JavaScript执行: 当解析器遇到script标签时,默认会暂停HTML的解析和渲染,去执行脚本中的JavaScript代码。</p></li><li><p>页面渲染: 解析完所有内容并执行完所有脚本后,浏览器根据DOM树和CSSOM树渲染出页面。</p></li></ul><h1 id="2-为什么JavaScript脚本会阻塞解析"><a href="#2-为什么JavaScript脚本会阻塞解析" class="headerlink" title="2.为什么JavaScript脚本会阻塞解析"></a>2.为什么JavaScript脚本会阻塞解析</h1><p>JavaScript脚本可能会操作DOM或者改变页面内容,比如添加或删除元素、改变样式等。为了确保这些操作的正确性,浏览器在遇到script标签时会暂停HTML的解析。这样可以保证:</p><ul><li><p>顺序执行: 浏览器会严格按照脚本在HTML中的顺序来执行JavaScript代码,确保脚本间的依赖关系。</p></li><li><p>页面一致性: 在脚本执行完成之前暂停解析可以防止在HTML文档解析的过程中发生不一致的情况。例如,如果脚本在解析过程中插入新元素,暂停解析可以确保这些新元素被正确处理。</p></li></ul><h1 id="3-async和defer属性"><a href="#3-async和defer属性" class="headerlink" title="3.async和defer属性"></a>3.async和defer属性</h1><p>为了优化页面加载性能,HTML5引入了<code>async</code>和<code>defer</code>属性,用于控制脚本的加载和执行行为。</p><h2 id="async属性"><a href="#async属性" class="headerlink" title="async属性"></a>async属性</h2><ul><li>加载方式: 使用async属性的脚本是异步加载的,这意味着脚本会在后台加载,不会阻塞HTML文档的解析。</li><li>执行时机: 一旦脚本加载完成,浏览器会立即执行它,这可能在HTML文档的解析完成之前。因此,async脚本的执行顺序无法保证,脚本之间也不应有依赖。</li><li>适用场景: 适用于独立的脚本,比如广告、数据统计脚本,这些脚本不依赖于其他内容,也不改变页面结构。</li></ul><h2 id="defer属性"><a href="#defer属性" class="headerlink" title="defer属性"></a>defer属性</h2><ul><li><p>加载方式: 使用defer属性的脚本也是异步加载的,类似于async,它们不会阻塞HTML文档的解析。</p></li><li><p>执行时机: 与async不同,defer脚本会在HTML文档完全解析完毕后按顺序执行。这意味着即使脚本加载完成,它们也会等到文档解析结束才执行。</p></li><li><p>适用场景: 适用于需要确保脚本顺序或依赖于整个文档的脚本。defer脚本通常用于初始化页面的JavaScript代码。</p></li></ul><h1 id="4-async与defer的区别"><a href="#4-async与defer的区别" class="headerlink" title="4.async与defer的区别"></a>4.async与defer的区别</h1><table><thead><tr><th>特性</th><th>async</th><th>defer</th></tr></thead><tbody><tr><td>加载方式</td><td>异步</td><td>异步</td></tr><tr><td>执行时机</td><td>加载完成后立即执行</td><td>HTML解析完成后按顺序执行</td></tr><tr><td>执行顺序</td><td>不保证顺序</td><td>保证顺序</td></tr><tr><td>适用场景</td><td>独立脚本</td><td>依赖脚本或初始化脚本</td></tr></tbody></table>]]></content>
<categories>
<category> JavaScript </category>
</categories>
<tags>
<tag> async </tag>
<tag> defer </tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/2024/12/18/hello-world/"/>
<url>/2024/12/18/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>]]></content>
</entry>
</search>