<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>橙光笔记</title>
  
  
  <link href="https://www.kai666666.com/atom.xml" rel="self"/>
  
  <link href="https://www.kai666666.com/"/>
  <updated>2026-02-10T08:00:34.492Z</updated>
  <id>https://www.kai666666.com/</id>
  
  <author>
    <name>Orange</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>源码共读 - cursor-effects</title>
    <link href="https://www.kai666666.com/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/"/>
    <id>https://www.kai666666.com/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/</id>
    <published>2025-10-27T14:54:36.000Z</published>
    <updated>2026-02-10T08:00:34.492Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/tholman/cursor-effects">cursor-effects</a>全称 <code>90s Cursor Effects</code> ，是一个网页端鼠光标特效库。具体效果可以看<a href="https://tholman.com/cursor-effects/">这里</a>。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><p>使用方式非常简单，只要引入对应的光标特效函数，然后调用即可。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; emojiCursor &#125; <span class="keyword">from</span> <span class="string">&quot;cursor-effects&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> cursor = <span class="keyword">new</span> <span class="title function_">emojiCursor</span>(&#123; <span class="attr">emoji</span>: [<span class="string">&quot;🔥&quot;</span>, <span class="string">&quot;🐬&quot;</span>, <span class="string">&quot;🦆&quot;</span>] &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以调用下面函数销毁</span></span><br><span class="line"><span class="comment">// cursor.destroy();</span></span><br></pre></td></tr></table></figure><p>在写这篇文章的时候，目前支持 <code>12种</code> 光标特效，后面有可能会增加，具体如下：</p><ul><li>Rainbow Cursor</li><li>Emoji Rain</li><li>Elastic Emoji</li><li>Ghost Following</li><li>Trailing Cursor</li><li>Text Flag Cursor</li><li>Following Dot</li><li>Bubbles Particles</li><li>Snowflake Particles</li><li>Fairy Dust</li><li>Clock Cursor</li><li>Character Cursor</li></ul><p>每一种光标特效的参数稍微有些差异，具体可以看<a href="https://github.com/tholman/cursor-effects">Github</a>中的简绍。</p><h2 id="emojiCursor"><a href="#emojiCursor" class="headerlink" title="emojiCursor"></a>emojiCursor</h2><p><code>cursor-effects</code> 暴露了12个光标特效函数，每一个函数的基本结构大致都是相同的，我们这里先以 <code>emojiCursor</code> 为例。</p><p><code>emojiCursor</code> 的效果如下：</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/emojiCursor.gif" alt="emojiCursor"></p><p> <code>emojiCursor</code> 的函数结构，如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">emojiCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> prefersReducedMotion = <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(</span><br><span class="line">    <span class="string">&quot;(prefers-reduced-motion: reduce)&quot;</span></span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Re-initialise or destroy the cursor when the prefers-reduced-motion setting changes</span></span><br><span class="line">  prefersReducedMotion.<span class="property">onchange</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (prefersReducedMotion.<span class="property">matches</span>) &#123;</span><br><span class="line">      <span class="title function_">destroy</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="title function_">init</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (prefersReducedMotion.<span class="property">matches</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(</span><br><span class="line">        <span class="string">&quot;This browser has prefers reduced motion turned on, so the cursor did not init&quot;</span></span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ... 初始化逻辑</span></span><br><span class="line">    <span class="title function_">bindEvents</span>();</span><br><span class="line">    <span class="title function_">loop</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Bind events that are needed</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">bindEvents</span>(<span class="params"></span>) &#123;</span><br><span class="line">    element.<span class="title function_">addEventListener</span>(<span class="string">&quot;mousemove&quot;</span>, onMouseMove, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">    element.<span class="title function_">addEventListener</span>(<span class="string">&quot;touchmove&quot;</span>, onTouchMove, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">    element.<span class="title function_">addEventListener</span>(<span class="string">&quot;touchstart&quot;</span>, onTouchMove, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, onWindowResize);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onWindowResize</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="comment">// ... 窗口重置逻辑</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onTouchMove</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="comment">// ... 触摸逻辑</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onMouseMove</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="comment">// ... 鼠标移动逻辑</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">updateParticles</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// ... 更新粒子逻辑</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">loop</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">updateParticles</span>();</span><br><span class="line">    animationFrame = <span class="title function_">requestAnimationFrame</span>(loop);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">destroy</span>(<span class="params"></span>) &#123;</span><br><span class="line">    canvas.<span class="title function_">remove</span>();</span><br><span class="line">    <span class="title function_">cancelAnimationFrame</span>(animationFrame);</span><br><span class="line">    element.<span class="title function_">removeEventListener</span>(<span class="string">&quot;mousemove&quot;</span>, onMouseMove);</span><br><span class="line">    element.<span class="title function_">removeEventListener</span>(<span class="string">&quot;touchmove&quot;</span>, onTouchMove);</span><br><span class="line">    element.<span class="title function_">removeEventListener</span>(<span class="string">&quot;touchstart&quot;</span>, onTouchMove);</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, onWindowResize);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params">x, y, canvasItem</span>) &#123;</span><br><span class="line">    <span class="comment">// 其他代码</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">init</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">destroy</span>: destroy,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>由上可见，<code>emojiCursor</code> 函数调用了 <code>init</code> 函数来初始化光标特效，同时返回一个含有 <code>destroy</code> 方法的对象。</p><p>上面还有一段 <code>prefersReducedMotion</code> 相关的代码，如果用户开启了 <code>prefers-reduced-motion: reduce</code> 则不展示光标特效，以避免对用户造成过度刺激（如癫痫患者或对动态内容敏感的用户）。</p><h3 id="init-函数"><a href="#init-函数" class="headerlink" title="init 函数"></a>init 函数</h3><p><code>emojiCursor</code> 函数内部的 <code>init</code> 函数如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">emojiCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> possibleEmoji = (options &amp;&amp; options.<span class="property">emoji</span>) || [<span class="string">&quot;😀&quot;</span>, <span class="string">&quot;😂&quot;</span>, <span class="string">&quot;😆&quot;</span>, <span class="string">&quot;😊&quot;</span>];</span><br><span class="line">  <span class="keyword">const</span> delay = (options &amp;&amp; options.<span class="property">delay</span>) || <span class="number">16</span>;</span><br><span class="line">  <span class="keyword">let</span> hasWrapperEl = options &amp;&amp; options.<span class="property">element</span>;</span><br><span class="line">  <span class="keyword">let</span> element = hasWrapperEl || <span class="variable language_">document</span>.<span class="property">body</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> width = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span><br><span class="line">  <span class="keyword">let</span> height = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span><br><span class="line">  <span class="keyword">const</span> cursor = &#123; <span class="attr">x</span>: width / <span class="number">2</span>, <span class="attr">y</span>: width / <span class="number">2</span> &#125;;</span><br><span class="line">  <span class="keyword">const</span> lastPos = &#123; <span class="attr">x</span>: width / <span class="number">2</span>, <span class="attr">y</span>: width / <span class="number">2</span> &#125;;</span><br><span class="line">  <span class="keyword">let</span> lastTimestamp = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> particles = [];</span><br><span class="line">  <span class="keyword">const</span> canvImages = [];</span><br><span class="line">  <span class="keyword">let</span> canvas, context, animationFrame;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... prefersReducedMotion 相关代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// Don&#x27;t show the cursor trail if the user has prefers-reduced-motion enabled</span></span><br><span class="line">    <span class="keyword">if</span> (prefersReducedMotion.<span class="property">matches</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(</span><br><span class="line">        <span class="string">&quot;This browser has prefers reduced motion turned on, so the cursor did not init&quot;</span></span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    canvas = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;canvas&quot;</span>);</span><br><span class="line">    context = canvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span><br><span class="line"></span><br><span class="line">    canvas.<span class="property">style</span>.<span class="property">top</span> = <span class="string">&quot;0px&quot;</span>;</span><br><span class="line">    canvas.<span class="property">style</span>.<span class="property">left</span> = <span class="string">&quot;0px&quot;</span>;</span><br><span class="line">    canvas.<span class="property">style</span>.<span class="property">pointerEvents</span> = <span class="string">&quot;none&quot;</span>;</span><br><span class="line">    canvas.<span class="property">style</span>.<span class="property">zIndex</span> = options.<span class="property">zIndex</span> || <span class="string">&quot;9999999999&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (hasWrapperEl) &#123;</span><br><span class="line">      canvas.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&quot;absolute&quot;</span>;</span><br><span class="line">      element.<span class="title function_">appendChild</span>(canvas);</span><br><span class="line">      canvas.<span class="property">width</span> = element.<span class="property">clientWidth</span>;</span><br><span class="line">      canvas.<span class="property">height</span> = element.<span class="property">clientHeight</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      canvas.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&quot;fixed&quot;</span>;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(canvas);</span><br><span class="line">      canvas.<span class="property">width</span> = width;</span><br><span class="line">      canvas.<span class="property">height</span> = height;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    context.<span class="property">font</span> = <span class="string">&quot;21px serif&quot;</span>;</span><br><span class="line">    context.<span class="property">textBaseline</span> = <span class="string">&quot;middle&quot;</span>;</span><br><span class="line">    context.<span class="property">textAlign</span> = <span class="string">&quot;center&quot;</span>;</span><br><span class="line"></span><br><span class="line">    possibleEmoji.<span class="title function_">forEach</span>(<span class="function">(<span class="params">emoji</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> measurements = context.<span class="title function_">measureText</span>(emoji);</span><br><span class="line">      <span class="keyword">let</span> bgCanvas = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;canvas&quot;</span>);</span><br><span class="line">      <span class="keyword">let</span> bgContext = bgCanvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span><br><span class="line"></span><br><span class="line">      bgCanvas.<span class="property">width</span> = measurements.<span class="property">width</span>;</span><br><span class="line">      bgCanvas.<span class="property">height</span> = measurements.<span class="property">actualBoundingBoxAscent</span> * <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">      bgContext.<span class="property">textAlign</span> = <span class="string">&quot;center&quot;</span>;</span><br><span class="line">      bgContext.<span class="property">font</span> = <span class="string">&quot;21px serif&quot;</span>;</span><br><span class="line">      bgContext.<span class="property">textBaseline</span> = <span class="string">&quot;middle&quot;</span>;</span><br><span class="line">      bgContext.<span class="title function_">fillText</span>(</span><br><span class="line">        emoji,</span><br><span class="line">        bgCanvas.<span class="property">width</span> / <span class="number">2</span>,</span><br><span class="line">        measurements.<span class="property">actualBoundingBoxAscent</span></span><br><span class="line">      );</span><br><span class="line"></span><br><span class="line">      canvImages.<span class="title function_">push</span>(bgCanvas);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">bindEvents</span>();</span><br><span class="line">    <span class="title function_">loop</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在 <code>emojiCursor</code> 函数刚开始中，这里可以设置 <code>emoji</code>、<code>delay</code>、<code>element</code>、<code>zIndex</code> 四个参数，然后定义了一些变量。<code>init</code> 函数中，先创建 <code>canvas</code> 根据是否传 <code>element</code> 来判断挂载到 <code>element</code> 还是 <code>document.body</code> 中。然后根据传入的 <code>possibleEmoji</code> 绘制到 <code>canvas</code> 上，并把绘制有 <code>emoji</code> 的 <code>canvas</code> 保存在 <code>canvImages</code> 数组里，这样后面可以通过 <code>context.drawImage()</code> 来绘制这个 <code>emoji</code>。最后调用 <code>bindEvents()</code> 绑定鼠标移动事件，并调用 <code>loop()</code> 函数开始动画循环。</p><p><code>bindEvents</code> 函数如下，监听鼠标、触摸和窗口重置事件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">bindEvents</span>(<span class="params"></span>) &#123;</span><br><span class="line">  element.<span class="title function_">addEventListener</span>(<span class="string">&quot;mousemove&quot;</span>, onMouseMove, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  element.<span class="title function_">addEventListener</span>(<span class="string">&quot;touchmove&quot;</span>, onTouchMove, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  element.<span class="title function_">addEventListener</span>(<span class="string">&quot;touchstart&quot;</span>, onTouchMove, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, onWindowResize);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">onWindowResize</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  width = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span><br><span class="line">  height = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (hasWrapperEl) &#123;</span><br><span class="line">    canvas.<span class="property">width</span> = element.<span class="property">clientWidth</span>;</span><br><span class="line">    canvas.<span class="property">height</span> = element.<span class="property">clientHeight</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    canvas.<span class="property">width</span> = width;</span><br><span class="line">    canvas.<span class="property">height</span> = height;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>onMouseMove</code> 函数和 <code>onTouchMove</code> 函数分别处理鼠标和触摸事件，核心逻辑是获取鼠标或触摸位置，并添加粒子。鼠标和触摸稍微有点不同的地方是触摸的时候直接在触发的位置添加粒子，而鼠标移动的时候有节流操作，同时移动超过1像素才添加粒子。<code>onMouseMove</code> 事件之所有在 <code>onMouseMove</code> 中使用<br><code>requestAnimationFrame</code>, 是因为它结合 <code>lastTimestamp</code> 做了节流，可以限制 <code>requestAnimationFrame</code> 中的函数每一帧只执行一次，避免同一帧内多次执行导致性能浪费。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">onTouchMove</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (e.<span class="property">touches</span>.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; e.<span class="property">touches</span>.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="title function_">addParticle</span>(</span><br><span class="line">        e.<span class="property">touches</span>[i].<span class="property">clientX</span>,</span><br><span class="line">        e.<span class="property">touches</span>[i].<span class="property">clientY</span>,</span><br><span class="line">        canvImages[<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvImages.<span class="property">length</span>)]</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">onMouseMove</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="comment">// Dont run too fast</span></span><br><span class="line">  <span class="keyword">if</span> (e.<span class="property">timeStamp</span> - lastTimestamp &lt; delay) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">requestAnimationFrame</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (hasWrapperEl) &#123;</span><br><span class="line">      <span class="keyword">const</span> boundingRect = element.<span class="title function_">getBoundingClientRect</span>();</span><br><span class="line">      cursor.<span class="property">x</span> = e.<span class="property">clientX</span> - boundingRect.<span class="property">left</span>;</span><br><span class="line">      cursor.<span class="property">y</span> = e.<span class="property">clientY</span> - boundingRect.<span class="property">top</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      cursor.<span class="property">x</span> = e.<span class="property">clientX</span>;</span><br><span class="line">      cursor.<span class="property">y</span> = e.<span class="property">clientY</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> distBetweenPoints = <span class="title class_">Math</span>.<span class="title function_">hypot</span>(</span><br><span class="line">      cursor.<span class="property">x</span> - lastPos.<span class="property">x</span>,</span><br><span class="line">      cursor.<span class="property">y</span> - lastPos.<span class="property">y</span></span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (distBetweenPoints &gt; <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="title function_">addParticle</span>(</span><br><span class="line">        cursor.<span class="property">x</span>,</span><br><span class="line">        cursor.<span class="property">y</span>,</span><br><span class="line">        canvImages[<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * possibleEmoji.<span class="property">length</span>)]</span><br><span class="line">      );</span><br><span class="line"></span><br><span class="line">      lastPos.<span class="property">x</span> = cursor.<span class="property">x</span>;</span><br><span class="line">      lastPos.<span class="property">y</span> = cursor.<span class="property">y</span>;</span><br><span class="line">      lastTimestamp = e.<span class="property">timeStamp</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">addParticle</span>(<span class="params">x, y, img</span>) &#123;</span><br><span class="line">  particles.<span class="title function_">push</span>(<span class="keyword">new</span> <span class="title class_">Particle</span>(x, y, img));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Particle-函数"><a href="#Particle-函数" class="headerlink" title="Particle 函数"></a>Particle 函数</h3><p><code>Particle</code> 函数定义的是粒子类，它放在每一个光标特效函数的内部，因为每种特效稍微有点不同。 <code>emojiCursor</code> 中的 <code>Particle</code> 函数接收三个参数，分别是粒子的初始位置 <code>x</code>、<code>y</code>，以及要绘制的 <code>canvas</code>（即 <code>emoji</code>）。在 <code>Particle</code> 函数中，定义了粒子的生命周期 <code>lifeSpan</code>、速度 <code>velocity</code>、位置 <code>position</code>。<code>update</code> 方法中，根据粒子的速度更新粒子的位置，同时减少 <code>lifeSpan</code>，并根据 <code>lifeSpan</code> 计算缩放比例，最后调用 <code>context.drawImage()</code> 绘制粒子，这里 <code>scale</code> 会随着 <code>lifeSpan</code> 的变小而变小，所以就有了 <code>emoji</code> 变小的效果。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params">x, y, canvasItem</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> lifeSpan = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">60</span> + <span class="number">80</span>);</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">initialLifeSpan</span> = lifeSpan; <span class="comment">//</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">lifeSpan</span> = lifeSpan; <span class="comment">//ms</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">velocity</span> = &#123;</span><br><span class="line">    <span class="attr">x</span>: (<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.5</span> ? -<span class="number">1</span> : <span class="number">1</span>) * (<span class="title class_">Math</span>.<span class="title function_">random</span>() / <span class="number">2</span>),</span><br><span class="line">    <span class="attr">y</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">0.4</span> + <span class="number">0.8</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: x, <span class="attr">y</span>: y &#125;;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">canv</span> = canvasItem;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">update</span> = <span class="keyword">function</span> (<span class="params">context</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">x</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">y</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lifeSpan</span>--;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">y</span> += <span class="number">0.05</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> scale = <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="variable language_">this</span>.<span class="property">lifeSpan</span> / <span class="variable language_">this</span>.<span class="property">initialLifeSpan</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    context.<span class="title function_">drawImage</span>(</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">canv</span>,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> - (<span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">width</span> / <span class="number">2</span>) * scale,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> - <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">height</span> / <span class="number">2</span>,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">width</span> * scale,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">height</span> * scale</span><br><span class="line">    );</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="updateParticles-函数"><a href="#updateParticles-函数" class="headerlink" title="updateParticles 函数"></a>updateParticles 函数</h3><p>在 <code>loop</code> 函数中调用 <code>updateParticles</code> 函数，更新粒子的位置和生命周期，然后开启动画循环。<code>updateParticles</code> 函数每一帧执行一次，它会更新粒子的位置并绘制，对于 <code>lifeSpan</code> 小于 <code>0</code> 的粒子，会从数组中删除。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">loop</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">updateParticles</span>();</span><br><span class="line">  animationFrame = <span class="title function_">requestAnimationFrame</span>(loop);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">updateParticles</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (particles.<span class="property">length</span> == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Update</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; particles.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    particles[i].<span class="title function_">update</span>(context);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Remove dead particles</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = particles.<span class="property">length</span> - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">    <span class="keyword">if</span> (particles[i].<span class="property">lifeSpan</span> &lt; <span class="number">0</span>) &#123;</span><br><span class="line">      particles.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (particles.<span class="property">length</span> == <span class="number">0</span>) &#123;</span><br><span class="line">    context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面200多行代码就实现了 <code>emoji</code> 的光标动画效果。</p><h2 id="fairyDustCursor"><a href="#fairyDustCursor" class="headerlink" title="fairyDustCursor"></a>fairyDustCursor</h2><p><code>fairyDustCursor</code> 的效果如下，效果与 <code>emojiCursor</code> 类似，只是掉落 <code>emoji</code> 换成了掉落星星。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/fairyDustCursor.gif" alt="fairyDustCursor"></p><p><code>fairyDustCursor</code> 的代码与 <code>emojiCursor</code> 几乎相同，如下，只是把绘制 <code>emoji</code> 替换为绘制不同颜色的星号 <code>*</code> <code>（即 fairySymbol 变量对应的符号）</code>，其他代码几乎一样只是粒子参数稍微修改了一下。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">fairyDustCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> possibleColors = (options &amp;&amp; options.<span class="property">colors</span>) || [</span><br><span class="line">    <span class="string">&quot;#D61C59&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#E7D84B&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#1B8798&quot;</span>,</span><br><span class="line">  ];</span><br><span class="line">  <span class="comment">// ... 忽略相同的代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> char = options.<span class="property">fairySymbol</span> || <span class="string">&quot;*&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略 prefersReducedMotion 相关代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ... 忽略相同的创建 canvas 代码</span></span><br><span class="line"></span><br><span class="line">    possibleColors.<span class="title function_">forEach</span>(<span class="function">(<span class="params">color</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// ... 忽略相同代码</span></span><br><span class="line">      bgContext.<span class="property">fillStyle</span> = color;</span><br><span class="line">      bgContext.<span class="title function_">fillText</span>(</span><br><span class="line">        char,</span><br><span class="line">        bgCanvas.<span class="property">width</span> / <span class="number">2</span>,</span><br><span class="line">        measurements.<span class="property">actualBoundingBoxAscent</span></span><br><span class="line">      );</span><br><span class="line"></span><br><span class="line">      canvImages.<span class="title function_">push</span>(bgCanvas);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">bindEvents</span>();</span><br><span class="line">    <span class="title function_">loop</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="bubbleCursor"><a href="#bubbleCursor" class="headerlink" title="bubbleCursor"></a>bubbleCursor</h2><p><code>bubbleCursor</code> 的效果如下，鼠标移动时，会生成一些小泡泡，小泡泡会浮起来并且越来越大，然后消失。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/bubbleCursor.gif" alt="bubbleCursor"></p><p><code>bubbleCursor</code> 代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">bubbleCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> fillColor = options &amp;&amp; options.<span class="property">fillColor</span> ? options.<span class="property">fillColor</span> : <span class="string">&quot;#e6f1f7&quot;</span>;</span><br><span class="line">  <span class="keyword">let</span> strokeColor = options &amp;&amp; options.<span class="property">strokeColor</span> ? options.<span class="property">strokeColor</span> : <span class="string">&quot;#3a92c5&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> lifeSpan = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">60</span> + <span class="number">60</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">initialLifeSpan</span> = lifeSpan; <span class="comment">//</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lifeSpan</span> = lifeSpan; <span class="comment">//ms</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">velocity</span> = &#123;</span><br><span class="line">      <span class="attr">x</span>: (<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.5</span> ? -<span class="number">1</span> : <span class="number">1</span>) * (<span class="title class_">Math</span>.<span class="title function_">random</span>() / <span class="number">10</span>),</span><br><span class="line">      <span class="attr">y</span>: -<span class="number">0.4</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>() * -<span class="number">1</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: x, <span class="attr">y</span>: y &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">baseDimension</span> = <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">update</span> = <span class="keyword">function</span> (<span class="params">context</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">x</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">y</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">x</span> += ((<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.5</span> ? -<span class="number">1</span> : <span class="number">1</span>) * <span class="number">2</span>) / <span class="number">75</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">y</span> -= <span class="title class_">Math</span>.<span class="title function_">random</span>() / <span class="number">600</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">lifeSpan</span>--;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> scale =</span><br><span class="line">        <span class="number">0.2</span> + (<span class="variable language_">this</span>.<span class="property">initialLifeSpan</span> - <span class="variable language_">this</span>.<span class="property">lifeSpan</span>) / <span class="variable language_">this</span>.<span class="property">initialLifeSpan</span>;</span><br><span class="line"></span><br><span class="line">      context.<span class="property">fillStyle</span> = fillColor;</span><br><span class="line">      context.<span class="property">strokeStyle</span> = strokeColor;</span><br><span class="line">      context.<span class="title function_">beginPath</span>();</span><br><span class="line">      context.<span class="title function_">arc</span>(</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> - (<span class="variable language_">this</span>.<span class="property">baseDimension</span> / <span class="number">2</span>) * scale,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> - <span class="variable language_">this</span>.<span class="property">baseDimension</span> / <span class="number">2</span>,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">baseDimension</span> * scale,</span><br><span class="line">        <span class="number">0</span>,</span><br><span class="line">        <span class="number">2</span> * <span class="title class_">Math</span>.<span class="property">PI</span></span><br><span class="line">      );</span><br><span class="line"></span><br><span class="line">      context.<span class="title function_">stroke</span>();</span><br><span class="line">      context.<span class="title function_">fill</span>();</span><br><span class="line"></span><br><span class="line">      context.<span class="title function_">closePath</span>();</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>bubbleCursor</code> 与 <code>emojiCursor</code> 代码也类似，只是 <code>Particle</code> 函数稍有不同，<code>emojiCursor</code> 函数的 <code>Particle</code> 函数是 <code>drawImage</code> 而 <code>bubbleCursor</code> 函数的 <code>Particle</code> 函数是调用 <code>arc</code> 画圆（泡泡）。</p><h2 id="followingDotCursor"><a href="#followingDotCursor" class="headerlink" title="followingDotCursor"></a>followingDotCursor</h2><p><code>followingDotCursor</code> 的效果如下，鼠标移动时，会有一个跟随鼠标移动的圆点。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/followingDotCursor.gif" alt="followingDotCursor"></p><p><code>followingDotCursor</code> 代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">followingDotCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">  <span class="keyword">let</span> dot = <span class="keyword">new</span> <span class="title class_">Dot</span>(width / <span class="number">2</span>, height / <span class="number">2</span>, <span class="number">10</span>, <span class="number">0.1</span>);</span><br><span class="line">  <span class="keyword">let</span> color = options?.<span class="property">color</span> || <span class="string">&quot;#323232a6&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">updateDot</span>(<span class="params"></span>) &#123;</span><br><span class="line">    context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br><span class="line">    dot.<span class="title function_">moveTowards</span>(cursor.<span class="property">x</span>, cursor.<span class="property">y</span>, context);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">loop</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">updateDot</span>();</span><br><span class="line">    animationFrame = <span class="title function_">requestAnimationFrame</span>(loop);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">Dot</span>(<span class="params">x, y, width, lag</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: x, <span class="attr">y</span>: y &#125;;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">width</span> = width;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lag</span> = lag;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">moveTowards</span> = <span class="keyword">function</span> (<span class="params">x, y, context</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> += (x - <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span>) * <span class="variable language_">this</span>.<span class="property">lag</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> += (y - <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span>) * <span class="variable language_">this</span>.<span class="property">lag</span>;</span><br><span class="line"></span><br><span class="line">      context.<span class="property">fillStyle</span> = color;</span><br><span class="line">      context.<span class="title function_">beginPath</span>();</span><br><span class="line">      context.<span class="title function_">arc</span>(<span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span>, <span class="variable language_">this</span>.<span class="property">width</span>, <span class="number">0</span>, <span class="number">2</span> * <span class="title class_">Math</span>.<span class="property">PI</span>);</span><br><span class="line">      context.<span class="title function_">fill</span>();</span><br><span class="line">      context.<span class="title function_">closePath</span>();</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>followingDotCursor</code> 把粒子命名成了 <code>Dot</code>，而且只有一个粒子。因为圆点要跟随鼠标移动，所以用到了缓动动画。缓动动画公式如下，缓动动画更多详情可以看<a href="https://www.kai666666.com/2019/06/30/Canvas%E7%B3%BB%E5%88%97%EF%BC%8812%EF%BC%89%EF%BC%9A%E5%8A%A8%E7%94%BB%E9%AB%98%E7%BA%A7/">这里</a>。</p><blockquote><p>当前速度 &#x3D; (最终位置 - 当前位置) * 缓动系数。<br>新的位置 &#x3D; 当前位置 + 当前速度。</p></blockquote><h2 id="trailingCursor"><a href="#trailingCursor" class="headerlink" title="trailingCursor"></a>trailingCursor</h2><p><code>trailingCursor</code> 的效果如下，鼠标移动时，会有一系列的鼠标做缓动动画。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/trailingCursor.gif" alt="trailingCursor"></p><p><code>trailingCursor</code> 的代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">trailingCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  le</span><br><span class="line">  <span class="keyword">const</span> totalParticles = options?.<span class="property">particles</span> || <span class="number">15</span>;</span><br><span class="line">  <span class="keyword">const</span> rate = options?.<span class="property">rate</span> || <span class="number">0.4</span>;</span><br><span class="line">  <span class="keyword">const</span> baseImageSrc = options?.<span class="property">baseImageSrc</span> || <span class="string">&quot;data:image/png;xxx&quot;</span>; <span class="comment">// 默认光标的base64图片 太长了 这里省略</span></span><br><span class="line">  <span class="keyword">let</span> cursorsInitted = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> baseImage = <span class="keyword">new</span> <span class="title class_">Image</span>();</span><br><span class="line">  baseImage.<span class="property">src</span> = baseImageSrc;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onMouseMove</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (hasWrapperEl) &#123;</span><br><span class="line">      <span class="keyword">const</span> boundingRect = element.<span class="title function_">getBoundingClientRect</span>();</span><br><span class="line">      cursor.<span class="property">x</span> = e.<span class="property">clientX</span> - boundingRect.<span class="property">left</span>;</span><br><span class="line">      cursor.<span class="property">y</span> = e.<span class="property">clientY</span> - boundingRect.<span class="property">top</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      cursor.<span class="property">x</span> = e.<span class="property">clientX</span>;</span><br><span class="line">      cursor.<span class="property">y</span> = e.<span class="property">clientY</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 首次鼠标移动时一次性添加所有粒子</span></span><br><span class="line">    <span class="keyword">if</span> (cursorsInitted === <span class="literal">false</span>) &#123;</span><br><span class="line">      cursorsInitted = <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; totalParticles; i++) &#123;</span><br><span class="line">        <span class="title function_">addParticle</span>(cursor.<span class="property">x</span>, cursor.<span class="property">y</span>, baseImage);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">updateParticles</span>(<span class="params"></span>) &#123;</span><br><span class="line">    context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> x = cursor.<span class="property">x</span>;</span><br><span class="line">    <span class="keyword">let</span> y = cursor.<span class="property">y</span>;</span><br><span class="line"></span><br><span class="line">    particles.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">particle, index, particles</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> nextParticle = particles[index + <span class="number">1</span>] || particles[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">      particle.<span class="property">position</span>.<span class="property">x</span> = x;</span><br><span class="line">      particle.<span class="property">position</span>.<span class="property">y</span> = y;</span><br><span class="line">      particle.<span class="title function_">move</span>(context);</span><br><span class="line">      x += (nextParticle.<span class="property">position</span>.<span class="property">x</span> - particle.<span class="property">position</span>.<span class="property">x</span>) * rate;</span><br><span class="line">      y += (nextParticle.<span class="property">position</span>.<span class="property">y</span> - particle.<span class="property">position</span>.<span class="property">y</span>) * rate;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params">x, y, image</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: x, <span class="attr">y</span>: y &#125;;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">image</span> = image;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">move</span> = <span class="keyword">function</span> (<span class="params">context</span>) &#123;</span><br><span class="line">      context.<span class="title function_">drawImage</span>(</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">image</span>,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span>, <span class="comment">// - (this.canv.width / 2) * scale,</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> <span class="comment">//- this.canv.height / 2,</span></span><br><span class="line">      );</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>鼠标的样式由 <code>baseImageSrc</code> 决定，默认给了一个默认鼠标样式的base64图片。<code>Particle</code> 只要在特定位置绘制图片即可。在 <code>updateParticles</code> 中，第一个粒子是鼠标的位置（<code>cursor.x</code> 和 <code>cursor.y</code>），其他粒子是根据前一个粒子的位置做缓动动画，这样就有了一系列的跟随东海。需要注意的是最后一个粒子没有下一个粒子，这里把第一个粒子赋值给 <code>nextParticle</code>，是为了让程序走下去，最后一个的 <code>nextParticle</code> 计算出来的 <code>x</code> 与 <code>y</code> 不会被使用到。</p><h2 id="springyEmojiCursor"><a href="#springyEmojiCursor" class="headerlink" title="springyEmojiCursor"></a>springyEmojiCursor</h2><p><code>springyEmojiCursor</code> 的效果如下，相当于把多个 <code>emoji</code> 用一根绳子连接在了一起。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/springyEmojiCursor.gif" alt="springyEmojiCursor"></p><p><code>springyEmojiCursor</code> 的代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">springyEmojiCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> emoji = (options &amp;&amp; options.<span class="property">emoji</span>) || <span class="string">&quot;🤪&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> nDots = <span class="number">7</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">DELTAT</span> = <span class="number">0.01</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">SEGLEN</span> = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">SPRINGK</span> = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">MASS</span> = <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">GRAVITY</span> = <span class="number">50</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">RESISTANCE</span> = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">STOPVEL</span> = <span class="number">0.1</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">STOPACC</span> = <span class="number">0.1</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">DOTSIZE</span> = <span class="number">11</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable constant_">BOUNCE</span> = <span class="number">0.7</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> emojiAsImage;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// ... 忽略类似代码</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 把 emoji 转换为 canvas 跟 emojiCursor 中的 canvImages 代码一样只不过这里是一个</span></span><br><span class="line">    emojiAsImage = bgCanvas;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; nDots; i++) &#123;</span><br><span class="line">      particles[i] = <span class="keyword">new</span> <span class="title class_">Particle</span>(emojiAsImage);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">bindEvents</span>();</span><br><span class="line">    <span class="title function_">loop</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params">canvasItem</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: cursor.<span class="property">x</span>, <span class="attr">y</span>: cursor.<span class="property">y</span> &#125;;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">velocity</span> = &#123;</span><br><span class="line">      <span class="attr">x</span>: <span class="number">0</span>,</span><br><span class="line">      <span class="attr">y</span>: <span class="number">0</span>,</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">canv</span> = canvasItem;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">draw</span> = <span class="keyword">function</span> (<span class="params">context</span>) &#123;</span><br><span class="line">      context.<span class="title function_">drawImage</span>(</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">canv</span>,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> - <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">width</span> / <span class="number">2</span>,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> - <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">height</span> / <span class="number">2</span>,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">width</span>,</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">canv</span>.<span class="property">height</span></span><br><span class="line">      );</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>目前看上面代码没啥特别的，绳子弹性效果的代码在 <code>updateParticles</code> 中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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"><span class="comment">// 二维向量类</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">vec</span>(<span class="params">X, Y</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">X</span> = X;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">Y</span> = Y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">updateParticles</span>(<span class="params"></span>) &#123;</span><br><span class="line">  canvas.<span class="property">width</span> = canvas.<span class="property">width</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// follow mouse</span></span><br><span class="line">  <span class="comment">// 第一个粒子的位置是鼠标位置</span></span><br><span class="line">  particles[<span class="number">0</span>].<span class="property">position</span>.<span class="property">x</span> = cursor.<span class="property">x</span>;</span><br><span class="line">  particles[<span class="number">0</span>].<span class="property">position</span>.<span class="property">y</span> = cursor.<span class="property">y</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Start from 2nd dot</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt; nDots; i++) &#123;</span><br><span class="line">    <span class="comment">// 弹性力</span></span><br><span class="line">    <span class="keyword">let</span> spring = <span class="keyword">new</span> <span class="title function_">vec</span>(<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="comment">// 跟前一个粒子的合力</span></span><br><span class="line">      <span class="title function_">springForce</span>(i - <span class="number">1</span>, i, spring);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (i &lt; nDots - <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="comment">// 跟后一个粒子的合力</span></span><br><span class="line">      <span class="title function_">springForce</span>(i + <span class="number">1</span>, i, spring);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 阻力 方向速度的反方向 大小速度乘以阻尼系数来近似模拟</span></span><br><span class="line">    <span class="keyword">let</span> resist = <span class="keyword">new</span> <span class="title function_">vec</span>(</span><br><span class="line">      -particles[i].<span class="property">velocity</span>.<span class="property">x</span> * <span class="variable constant_">RESISTANCE</span>,</span><br><span class="line">      -particles[i].<span class="property">velocity</span>.<span class="property">y</span> * <span class="variable constant_">RESISTANCE</span></span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 加速度 = 力 / 质量</span></span><br><span class="line">    <span class="keyword">let</span> accel = <span class="keyword">new</span> <span class="title function_">vec</span>(</span><br><span class="line">      (spring.<span class="property">X</span> + resist.<span class="property">X</span>) / <span class="variable constant_">MASS</span>,</span><br><span class="line">      (spring.<span class="property">Y</span> + resist.<span class="property">Y</span>) / <span class="variable constant_">MASS</span> + <span class="variable constant_">GRAVITY</span></span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计算速度 这里算出来值过大 所以乘以了一个系数 DELTAT</span></span><br><span class="line">    particles[i].<span class="property">velocity</span>.<span class="property">x</span> += <span class="variable constant_">DELTAT</span> * accel.<span class="property">X</span>;</span><br><span class="line">    particles[i].<span class="property">velocity</span>.<span class="property">y</span> += <span class="variable constant_">DELTAT</span> * accel.<span class="property">Y</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 速度过小则停止</span></span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">      <span class="title class_">Math</span>.<span class="title function_">abs</span>(particles[i].<span class="property">velocity</span>.<span class="property">x</span>) &lt; <span class="variable constant_">STOPVEL</span> &amp;&amp;</span><br><span class="line">      <span class="title class_">Math</span>.<span class="title function_">abs</span>(particles[i].<span class="property">velocity</span>.<span class="property">y</span>) &lt; <span class="variable constant_">STOPVEL</span> &amp;&amp;</span><br><span class="line">      <span class="title class_">Math</span>.<span class="title function_">abs</span>(accel.<span class="property">X</span>) &lt; <span class="variable constant_">STOPACC</span> &amp;&amp;</span><br><span class="line">      <span class="title class_">Math</span>.<span class="title function_">abs</span>(accel.<span class="property">Y</span>) &lt; <span class="variable constant_">STOPACC</span></span><br><span class="line">    ) &#123;</span><br><span class="line">      particles[i].<span class="property">velocity</span>.<span class="property">x</span> = <span class="number">0</span>;</span><br><span class="line">      particles[i].<span class="property">velocity</span>.<span class="property">y</span> = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计算位置</span></span><br><span class="line">    particles[i].<span class="property">position</span>.<span class="property">x</span> += particles[i].<span class="property">velocity</span>.<span class="property">x</span>;</span><br><span class="line">    particles[i].<span class="property">position</span>.<span class="property">y</span> += particles[i].<span class="property">velocity</span>.<span class="property">y</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> height, width;</span><br><span class="line">    height = canvas.<span class="property">clientHeight</span>;</span><br><span class="line">    width = canvas.<span class="property">clientWidth</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> (particles[i].<span class="property">position</span>.<span class="property">y</span> &gt;= height - <span class="variable constant_">DOTSIZE</span> - <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (particles[i].<span class="property">velocity</span>.<span class="property">y</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        particles[i].<span class="property">velocity</span>.<span class="property">y</span> = <span class="variable constant_">BOUNCE</span> * -particles[i].<span class="property">velocity</span>.<span class="property">y</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      particles[i].<span class="property">position</span>.<span class="property">y</span> = height - <span class="variable constant_">DOTSIZE</span> - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 粒子超出右边距处理</span></span><br><span class="line">    <span class="keyword">if</span> (particles[i].<span class="property">position</span>.<span class="property">x</span> &gt;= width - <span class="variable constant_">DOTSIZE</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (particles[i].<span class="property">velocity</span>.<span class="property">x</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        particles[i].<span class="property">velocity</span>.<span class="property">x</span> = <span class="variable constant_">BOUNCE</span> * -particles[i].<span class="property">velocity</span>.<span class="property">x</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      particles[i].<span class="property">position</span>.<span class="property">x</span> = width - <span class="variable constant_">DOTSIZE</span> - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 粒子超出左边距处理</span></span><br><span class="line">    <span class="keyword">if</span> (particles[i].<span class="property">position</span>.<span class="property">x</span> &lt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (particles[i].<span class="property">velocity</span>.<span class="property">x</span> &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        particles[i].<span class="property">velocity</span>.<span class="property">x</span> = <span class="variable constant_">BOUNCE</span> * -particles[i].<span class="property">velocity</span>.<span class="property">x</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      particles[i].<span class="property">position</span>.<span class="property">x</span> = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    particles[i].<span class="title function_">draw</span>(context);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述用到了基础力学知识，先计算出 <code>弹力(spring)</code> 和 <code>阻力(resist)</code>，然后根据他们和重力计算出加速度，最后根据加速度计算出速度和位置，代码有点长不过根据注释来看条理还是很清晰。最后对超出边界处理，处理方式是速度反向并乘以一个小于1的系数来模拟弹性反弹并损失部分速度的过程，位置则修改到刚触碰到边界的位置。</p><p>这里还有一个 <code>springForce</code> 函数，用来计算两个粒子之间的弹力，代码如下。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">springForce</span>(<span class="params">i, j, spring</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> dx = particles[i].<span class="property">position</span>.<span class="property">x</span> - particles[j].<span class="property">position</span>.<span class="property">x</span>;</span><br><span class="line">  <span class="keyword">let</span> dy = particles[i].<span class="property">position</span>.<span class="property">y</span> - particles[j].<span class="property">position</span>.<span class="property">y</span>;</span><br><span class="line">  <span class="keyword">let</span> len = <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(dx * dx + dy * dy);</span><br><span class="line">  <span class="keyword">if</span> (len &gt; <span class="variable constant_">SEGLEN</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> springF = <span class="variable constant_">SPRINGK</span> * (len - <span class="variable constant_">SEGLEN</span>);</span><br><span class="line">    spring.<span class="property">X</span> += (dx / len) * springF;</span><br><span class="line">    spring.<span class="property">Y</span> += (dy / len) * springF;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先计算出绳子的长度，如果绳子比原来的长度伸长了，则计算弹力。弹力的大小是 <code>弹性系数 * (绳子拉伸后长度 - 绳子原长度) </code>，也就是胡克定律，最后根据三角形的相似计算出 <code>x轴</code> 和 <code>y轴</code> 的分弹力。</p><blockquote><p>胡克定律:<br>弹力 &#x3D; 弹性系数 * (绳子拉伸后长度 - 绳子原长度)</p></blockquote><h2 id="rainbowCursor"><a href="#rainbowCursor" class="headerlink" title="rainbowCursor"></a>rainbowCursor</h2><p><code>rainbowCursor</code> 的效果如下，光标拥有彩虹的特效。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/rainbowCursor.gif" alt="rainbowCursor"></p><p><code>rainbowCursor</code> 的代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">rainbowCursor</span>(<span class="params">options</span>) &#123;</span><br><span class="line">  <span class="comment">// ... 忽略类似代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> totalParticles = options?.<span class="property">length</span> || <span class="number">20</span>;</span><br><span class="line">  <span class="comment">// 默认颜色也是有讲究的 按照顺序分别是 赤橙黄绿蓝紫</span></span><br><span class="line">  <span class="keyword">const</span> colors = options?.<span class="property">colors</span> || [</span><br><span class="line">    <span class="string">&quot;#FE0000&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#FD8C00&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#FFE500&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#119F0B&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#0644B3&quot;</span>,</span><br><span class="line">    <span class="string">&quot;#C22EDC&quot;</span>,</span><br><span class="line">  ];</span><br><span class="line">  <span class="keyword">const</span> size = options?.<span class="property">size</span> || <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onMouseMove</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="comment">// ... 忽略类似代码</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 首次鼠标移动时一次性添加所有粒子</span></span><br><span class="line">    <span class="keyword">if</span> (cursorsInitted === <span class="literal">false</span>) &#123;</span><br><span class="line">      cursorsInitted = <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; totalParticles; i++) &#123;</span><br><span class="line">        <span class="title function_">addParticle</span>(cursor.<span class="property">x</span>, cursor.<span class="property">y</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">updateParticles</span>(<span class="params"></span>) &#123;</span><br><span class="line">    context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br><span class="line">    context.<span class="property">lineJoin</span> = <span class="string">&quot;round&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 用来存放粒子的位置</span></span><br><span class="line">    <span class="keyword">let</span> particleSets = [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> x = cursor.<span class="property">x</span>;</span><br><span class="line">    <span class="keyword">let</span> y = cursor.<span class="property">y</span>;</span><br><span class="line"></span><br><span class="line">    particles.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">particle, index, particles</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> nextParticle = particles[index + <span class="number">1</span>] || particles[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">      particle.<span class="property">position</span>.<span class="property">x</span> = x;</span><br><span class="line">      particle.<span class="property">position</span>.<span class="property">y</span> = y;</span><br><span class="line"></span><br><span class="line">      particleSets.<span class="title function_">push</span>(&#123; <span class="attr">x</span>: x, <span class="attr">y</span>: y &#125;);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 缓动动画</span></span><br><span class="line">      x += (nextParticle.<span class="property">position</span>.<span class="property">x</span> - particle.<span class="property">position</span>.<span class="property">x</span>) * <span class="number">0.4</span>;</span><br><span class="line">      y += (nextParticle.<span class="property">position</span>.<span class="property">y</span> - particle.<span class="property">position</span>.<span class="property">y</span>) * <span class="number">0.4</span>;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    colors.<span class="title function_">forEach</span>(<span class="function">(<span class="params">color, index</span>) =&gt;</span> &#123;</span><br><span class="line">      context.<span class="title function_">beginPath</span>();</span><br><span class="line">      context.<span class="property">strokeStyle</span> = color;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (particleSets.<span class="property">length</span>) &#123;</span><br><span class="line">        context.<span class="title function_">moveTo</span>(</span><br><span class="line">          particleSets[<span class="number">0</span>].<span class="property">x</span>,</span><br><span class="line">          particleSets[<span class="number">0</span>].<span class="property">y</span> + index * (size - <span class="number">1</span>)</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      particleSets.<span class="title function_">forEach</span>(<span class="function">(<span class="params">set, particleIndex</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (particleIndex !== <span class="number">0</span>) &#123;</span><br><span class="line">          context.<span class="title function_">lineTo</span>(set.<span class="property">x</span>, set.<span class="property">y</span> + index * size);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">      context.<span class="property">lineWidth</span> = size;</span><br><span class="line">      context.<span class="property">lineCap</span> = <span class="string">&quot;round&quot;</span>;</span><br><span class="line">      context.<span class="title function_">stroke</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: x, <span class="attr">y</span>: y &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 首次鼠标移动时一次性添加所有粒子</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的 <code>Particle</code> 只是用来记录坐标，而没有绘制的逻辑，核心逻辑在 <code>updateParticles</code> 中。<code>updateParticles</code> 函数中，第一个粒子的位置是鼠标当前的位置，然后后面的粒子跟前一个粒子做缓动动画，这样就有了彩虹跟随鼠标的缓动效果。彩虹的绘制也是很特别，它是在对应点下画了6条不同颜色的线（彩虹），将 <code>lineCap</code> 设置为 <code>round</code>，使线更加平滑，由于每次鼠标移动的距离很短（都是像素级别的），而每一条线又有 <code>totalParticles - 1</code>（默认20个点，19个小线段）个小线段，这样就有了弯曲的彩虹效果效果。<code>updateParticles</code> 实现的图片通过下面这个图，会更好理解一些，这里假设鼠标从右边移动到左边。</p><p><img src="/2025/10/27/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-cursor-effects/1.png" alt="rainbowCursor示意图"></p><p>还有一些特效，原理与上面类似，这里就不再赘述了</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/tholman/cursor-effects&quot;&gt;cursor-effects&lt;/a&gt;全称 &lt;code&gt;90s Cursor Effects&lt;/code&gt; ，是一个网页端鼠光标特效库。具体效果可以看&lt;a href=&quot;ht</summary>
      
    
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/categories/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/tags/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>源码共读 - grid-layout-plus</title>
    <link href="https://www.kai666666.com/2025/08/05/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-grid-layout-plus/"/>
    <id>https://www.kai666666.com/2025/08/05/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-grid-layout-plus/</id>
    <published>2025-08-05T14:30:13.000Z</published>
    <updated>2026-02-10T08:00:34.496Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/qmhc/grid-layout-plus">grid-layout-plus</a>是一个支持拖动、重新设置大小的网格布局组件。它的代码是在<a href="https://github.com/jbaysolutions/vue-grid-layout">vue-grid-layout</a>的基础上修改的，而 <code>vue-grid-layout</code> 又是参考 <a href="https://dsmorse.github.io/gridster.js/">gridster.js</a> 和 <a href="https://github.com/react-grid-layout/react-grid-layout">react-grid-layout</a>。我们这里之所以讲 <code>grid-layout-plus</code> 是因为 <code>grid-layout-plus</code> 是基于 <code>vue3</code> 的，而 <code>vue-grid-layout</code> 是基于 <code>vue2</code> 的，当然组件的核心思想都是类似的，看懂一个组件其他的也会明白。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><p>我们这里直接用 <code>grid-layout-plus</code> 的官网示例来演示，代码如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">GridLayout</span> <span class="attr">v-model:layout</span>=<span class="string">&quot;layout&quot;</span> <span class="attr">:row-height</span>=<span class="string">&quot;30&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">template</span> #<span class="attr">item</span>=<span class="string">&quot;&#123; item &#125;&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; `$&#123;item.i&#125;$&#123;item.static ? &#x27;- Static&#x27; : &#x27;&#x27;&#125;` &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">GridLayout</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">import</span> &#123; reactive &#125; <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> layout = reactive&lt;<span class="title class_">Layout</span>&gt;([</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">2</span>, <span class="attr">i</span>: <span class="string">&#x27;0&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">4</span>, <span class="attr">i</span>: <span class="string">&#x27;1&#x27;</span>, <span class="attr">static</span>: <span class="literal">true</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">4</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;2&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">6</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">3</span>, <span class="attr">i</span>: <span class="string">&#x27;3&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">8</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">3</span>, <span class="attr">i</span>: <span class="string">&#x27;4&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">10</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">3</span>, <span class="attr">i</span>: <span class="string">&#x27;5&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">5</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;6&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">5</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;7&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">4</span>, <span class="attr">y</span>: <span class="number">5</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;8&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">6</span>, <span class="attr">y</span>: <span class="number">3</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">4</span>, <span class="attr">i</span>: <span class="string">&#x27;9&#x27;</span>, <span class="attr">static</span>: <span class="literal">true</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">8</span>, <span class="attr">y</span>: <span class="number">4</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">4</span>, <span class="attr">i</span>: <span class="string">&#x27;10&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">10</span>, <span class="attr">y</span>: <span class="number">4</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">4</span>, <span class="attr">i</span>: <span class="string">&#x27;11&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">10</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;12&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">10</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;13&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">4</span>, <span class="attr">y</span>: <span class="number">8</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">4</span>, <span class="attr">i</span>: <span class="string">&#x27;14&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">6</span>, <span class="attr">y</span>: <span class="number">8</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">4</span>, <span class="attr">i</span>: <span class="string">&#x27;15&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">8</span>, <span class="attr">y</span>: <span class="number">10</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">5</span>, <span class="attr">i</span>: <span class="string">&#x27;16&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">10</span>, <span class="attr">y</span>: <span class="number">4</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">2</span>, <span class="attr">i</span>: <span class="string">&#x27;17&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">9</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">3</span>, <span class="attr">i</span>: <span class="string">&#x27;18&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">6</span>, <span class="attr">w</span>: <span class="number">2</span>, <span class="attr">h</span>: <span class="number">2</span>, <span class="attr">i</span>: <span class="string">&#x27;19&#x27;</span>, <span class="attr">static</span>: <span class="literal">false</span> &#125;,</span></span><br><span class="line"><span class="language-javascript">])</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>布局 <code>Layout</code> 的类型定义如下，它是由 <code>LayoutItem</code> 的数组组成，而 <code>LayoutItem</code> 的必须字段有 <code>w</code>、<code>h</code>、<code>x</code>、<code>y</code>、<code>i</code>，分别表示 <code>宽</code>、<code>高</code>、<code>X坐标</code>、<code>Y坐标</code> 和 <code>索引（id）</code>。<code>grid-layout-plus</code> 默认把一行分为 <code>col-num</code> （默认 <code>12</code>）个元素份，每份高度<code>row-height</code> （默认 <code>150</code> 像素），而 <code>w</code>、<code>h</code>、<code>x</code>、<code>y</code> 传的值是占据了多少份，而不是像素。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">LayoutItemRequired</span> &#123;</span><br><span class="line">  <span class="attr">w</span>: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">h</span>: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">x</span>: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">y</span>: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">i</span>: <span class="built_in">number</span> | <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">LayoutItem</span> <span class="keyword">extends</span> <span class="title class_">LayoutItemRequired</span> &#123;</span><br><span class="line">  <span class="attr">minW</span>?: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">minH</span>?: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">maxW</span>?: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">maxH</span>?: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">moved</span>?: <span class="built_in">boolean</span>,</span><br><span class="line">  <span class="attr">static</span>?: <span class="built_in">boolean</span>,</span><br><span class="line">  <span class="attr">isDraggable</span>?: <span class="built_in">boolean</span>,</span><br><span class="line">  <span class="attr">isResizable</span>?: <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">Layout</span> = <span class="title class_">Array</span>&lt;<span class="title class_">LayoutItem</span>&gt;</span><br></pre></td></tr></table></figure><p>此时的效果可以<a href="https://grid-layout-plus.netlify.app/example/basic.html">点这里</a>。</p><h2 id="组件结构"><a href="#组件结构" class="headerlink" title="组件结构"></a>组件结构</h2><p><code>grid-layout-plus</code> 的核心组件就2个 <code>grid-layout</code> 和 <code>grid-item</code>。<code>grid-layout</code>是一个容器，用来计算网格布局；<code>grid-item</code>是拖动组件，内部使用 <a href="https://github.com/taye/interact.js">interact.js</a> 来实现拖动和调整大小。</p><p><code>grid-layout</code> 的基础结构如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="comment">// ... 其他代码</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> state = <span class="title function_">reactive</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">width</span>: -<span class="number">1</span>,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">mergedStyle</span>: &#123;&#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">lastLayoutLength</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">isDragging</span>: <span class="literal">false</span>,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">placeholder</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">x</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">y</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">w</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">h</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">i</span>: <span class="string">&#x27;&#x27;</span> <span class="keyword">as</span> number | string,</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">layouts</span>: &#123;&#125; <span class="keyword">as</span> <span class="title class_">Record</span>&lt;<span class="title class_">Breakpoint</span>, <span class="title class_">Layout</span>&gt;, <span class="comment">// array to store all layouts from different breakpoints</span></span></span><br><span class="line"><span class="language-javascript">  <span class="attr">lastBreakpoint</span>: <span class="literal">null</span> <span class="keyword">as</span> <span class="title class_">Breakpoint</span> | <span class="literal">null</span>, <span class="comment">// store last active breakpoint</span></span></span><br><span class="line"><span class="language-javascript">  <span class="attr">originalLayout</span>: <span class="literal">null</span>! <span class="keyword">as</span> <span class="title class_">Layout</span>, <span class="comment">// store original Layout</span></span></span><br><span class="line"><span class="language-javascript">&#125;)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> wrapper = ref&lt;<span class="title class_">HTMLElement</span>&gt;()</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="comment">// ... 其他代码</span></span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;wrapper&quot;</span> <span class="attr">class</span>=<span class="string">&quot;vgl-layout&quot;</span> <span class="attr">:style</span>=<span class="string">&quot;state.mergedStyle&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">slot</span> <span class="attr">v-if</span>=<span class="string">&quot;$slots.default&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">template</span> <span class="attr">v-else</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">GridItem</span> <span class="attr">v-for</span>=<span class="string">&quot;item in currentLayout&quot;</span> <span class="attr">:key</span>=<span class="string">&quot;item.i&quot;</span> <span class="attr">v-bind</span>=<span class="string">&quot;item&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">slot</span> <span class="attr">name</span>=<span class="string">&quot;item&quot;</span> <span class="attr">:item</span>=<span class="string">&quot;item&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">GridItem</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">GridItem</span></span></span><br><span class="line"><span class="tag">      <span class="attr">v-show</span>=<span class="string">&quot;state.isDragging&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">class</span>=<span class="string">&quot;vgl-item--placeholder&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:x</span>=<span class="string">&quot;state.placeholder.x&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:y</span>=<span class="string">&quot;state.placeholder.y&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:w</span>=<span class="string">&quot;state.placeholder.w&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:h</span>=<span class="string">&quot;state.placeholder.h&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:i</span>=<span class="string">&quot;state.placeholder.i&quot;</span></span></span><br><span class="line"><span class="tag">    &gt;</span><span class="tag">&lt;/<span class="name">GridItem</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><p>从上可以看到，如果没有默认插槽的话，<code>grid-layout-plus</code> 会使用 <code>GridItem</code> 组件来渲染布局。此外在 <code>idDragging</code> 的时候会渲染 <code>placeholder</code> 。</p><p><code>grid-item</code> 的基础结构如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="comment">// ... 其他代码</span></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> wrapper = ref&lt;<span class="title class_">HTMLElement</span>&gt;()</span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> resizableAndNotStatic = <span class="title function_">computed</span>(<span class="function">() =&gt;</span> state.<span class="property">resizable</span> &amp;&amp; !props.<span class="property">static</span>)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">function</span> <span class="title function_">tryInteract</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">if</span> (!interactObj.<span class="property">value</span> &amp;&amp; wrapper.<span class="property">value</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    interactObj.<span class="property">value</span> = <span class="title function_">interact</span>(wrapper.<span class="property">value</span>)</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!state.<span class="property">useStyleCursor</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      interactObj.<span class="property">value</span>.<span class="title function_">styleCursor</span>(<span class="literal">false</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> throttleDrag = <span class="title function_">throttle</span>(handleDrag)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">function</span> <span class="title function_">tryMakeDraggable</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">tryInteract</span>()</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">if</span> (!interactObj.<span class="property">value</span>) <span class="keyword">return</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">if</span> (state.<span class="property">draggable</span> &amp;&amp; !props.<span class="property">static</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> opts = &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">ignoreFrom</span>: props.<span class="property">dragIgnoreFrom</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">allowFrom</span>: props.<span class="property">dragAllowFrom</span>,</span></span><br><span class="line"><span class="language-javascript">      ...props.<span class="property">dragOption</span>,</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">    interactObj.<span class="property">value</span>.<span class="title function_">draggable</span>(opts)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!dragEventSet) &#123;</span></span><br><span class="line"><span class="language-javascript">      dragEventSet = <span class="literal">true</span></span></span><br><span class="line"><span class="language-javascript">      interactObj.<span class="property">value</span>.<span class="title function_">on</span>(<span class="string">&#x27;dragstart dragmove dragend&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// dragmove 触发比较多 使用节流函数</span></span></span><br><span class="line"><span class="language-javascript">        event.<span class="property">type</span> === <span class="string">&#x27;dragmove&#x27;</span> ? <span class="title function_">throttleDrag</span>(event) : <span class="title function_">handleDrag</span>(event)</span></span><br><span class="line"><span class="language-javascript">      &#125;)</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    interactObj.<span class="property">value</span>.<span class="title function_">draggable</span>(&#123; <span class="attr">enabled</span>: <span class="literal">false</span> &#125;)</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> throttleResize = <span class="title function_">throttle</span>(handleResize)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">function</span> <span class="title function_">tryMakeResizable</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">tryInteract</span>()</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">if</span> (!interactObj.<span class="property">value</span>) <span class="keyword">return</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">if</span> (state.<span class="property">resizable</span> &amp;&amp; !props.<span class="property">static</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> maximum = <span class="title function_">calcPosition</span>(<span class="number">0</span>, <span class="number">0</span>, props.<span class="property">maxW</span>, props.<span class="property">maxH</span>)</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> minimum = <span class="title function_">calcPosition</span>(<span class="number">0</span>, <span class="number">0</span>, props.<span class="property">minW</span>, props.<span class="property">minH</span>)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> <span class="attr">opts</span>: <span class="title class_">Record</span>&lt;string, any&gt; = &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// ... 其他配置项</span></span></span><br><span class="line"><span class="language-javascript">      ...props.<span class="property">resizeOption</span>,</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    interactObj.<span class="property">value</span>.<span class="title function_">resizable</span>(opts)</span></span><br><span class="line"><span class="language-javascript">    interactObj.<span class="property">value</span>.<span class="title function_">on</span>(<span class="string">&#x27;resizestart resizemove resizeend&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// resizemove 触发比较多 使用节流函数</span></span></span><br><span class="line"><span class="language-javascript">      event.<span class="property">type</span> === <span class="string">&#x27;resizemove&#x27;</span> ? <span class="title function_">throttleResize</span>(event) : <span class="title function_">handleResize</span>(event)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    interactObj.<span class="property">value</span>.<span class="title function_">resizable</span>(&#123; <span class="attr">enabled</span>: <span class="literal">false</span> &#125;)</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="comment">// ... 其他代码</span></span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">section</span> <span class="attr">ref</span>=<span class="string">&quot;wrapper&quot;</span> <span class="attr">:class</span>=<span class="string">&quot;className&quot;</span> <span class="attr">:style</span>=<span class="string">&quot;state.style&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">slot</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span> <span class="attr">v-if</span>=<span class="string">&quot;resizableAndNotStatic&quot;</span> <span class="attr">:class</span>=<span class="string">&quot;resizerClass&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">section</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><p>由上可见，<code>grid-item</code> 组件调用 <code>tryMakeDraggable</code> 和 <code>tryMakeResizable</code> 函数通过 <code>interact.js</code> 来分别实现拖动和调整大小的功能。</p><h2 id="组件通信"><a href="#组件通信" class="headerlink" title="组件通信"></a>组件通信</h2><p><code>grid-layout</code> 与 <code>grid-item</code> 数据通信部分是通过 <code>props</code> 来实现的，这一点可以在上述 <code>grid-layout</code> 组件中 <code>#item</code> 插槽中的 <code>v-bind=&quot;item&quot;</code> 来知晓。还有一部分通信方式是通过 <code>inject</code> 来实现的，代码如下。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src\helpers\common.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="variable constant_">LAYOUT_KEY</span> = <span class="title class_">Symbol</span>(<span class="string">&#x27;LAYOUT_KEY&#x27;</span>) <span class="keyword">as</span> <span class="title class_">InjectionKey</span>&lt;<span class="title class_">LayoutInstance</span>&gt;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="variable constant_">EMITTER_KEY</span> = <span class="title class_">Symbol</span>(<span class="string">&#x27;EMITTER_KEY&#x27;</span>) <span class="keyword">as</span> <span class="title class_">InjectionKey</span>&lt;<span class="title class_">EventEmitter</span>&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// src\components\grid-layout.vue</span></span><br><span class="line"><span class="keyword">const</span> emitter = <span class="title function_">createEventEmitter</span>()</span><br><span class="line"></span><br><span class="line"><span class="title function_">provide</span>(</span><br><span class="line">  <span class="variable constant_">LAYOUT_KEY</span>,</span><br><span class="line">  <span class="title function_">reactive</span>(&#123;</span><br><span class="line">    ...<span class="title function_">toRefs</span>(props),</span><br><span class="line">    ...<span class="title function_">toRefs</span>(state),</span><br><span class="line">    increaseItem,</span><br><span class="line">    decreaseItem,</span><br><span class="line">  &#125;) <span class="keyword">as</span> <span class="title class_">LayoutInstance</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="title function_">provide</span>(<span class="variable constant_">EMITTER_KEY</span>, emitter)</span><br><span class="line"></span><br><span class="line"><span class="title function_">watch</span>(</span><br><span class="line">  <span class="function">() =&gt;</span> props.<span class="property">isDraggable</span>,</span><br><span class="line">  <span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">    emitter.<span class="title function_">emit</span>(<span class="string">&#x27;setDraggable&#x27;</span>, value)</span><br><span class="line">  &#125;,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// src\components\grid-item.vue</span></span><br><span class="line"><span class="keyword">const</span> layout = <span class="title function_">inject</span>(<span class="variable constant_">LAYOUT_KEY</span>)</span><br><span class="line"><span class="keyword">const</span> emitter = <span class="title function_">inject</span>(<span class="variable constant_">EMITTER_KEY</span>)!</span><br><span class="line"></span><br><span class="line">layout.<span class="title function_">increaseItem</span>(instance)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setDraggableHandler</span>(<span class="params"><span class="attr">isDraggable</span>: <span class="built_in">boolean</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isNull</span>(props.<span class="property">isDraggable</span>)) &#123;</span><br><span class="line">    state.<span class="property">draggable</span> = isDraggable</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line">  emitter.<span class="title function_">on</span>(<span class="string">&#x27;compact&#x27;</span>, compactHandler)</span><br><span class="line">  emitter.<span class="title function_">on</span>(<span class="string">&#x27;setDraggable&#x27;</span>, setDraggableHandler)</span><br><span class="line">  emitter.<span class="title function_">on</span>(<span class="string">&#x27;setResizable&#x27;</span>, setResizableHandler)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title function_">onBeforeUnmount</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">  emitter.<span class="title function_">off</span>(<span class="string">&#x27;compact&#x27;</span>, compactHandler)</span><br><span class="line">  emitter.<span class="title function_">off</span>(<span class="string">&#x27;setDraggable&#x27;</span>, setDraggableHandler)</span><br><span class="line">  emitter.<span class="title function_">off</span>(<span class="string">&#x27;setResizable&#x27;</span>, setResizableHandler)</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (interactObj.<span class="property">value</span>) &#123;</span><br><span class="line">    interactObj.<span class="property">value</span>.<span class="title function_">unset</span>()</span><br><span class="line">    interactObj.<span class="property">value</span> = <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  layout.<span class="title function_">decreaseItem</span>(instance)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><code>grid-layout</code> 把自己的 <code>props</code> 和 <code>state</code> 以及 <code>emitter</code> 提供给 <code>grid-item</code> 组件使用。通过 <code>inject</code> 方式不但可以直接获取到 <code>grid-layout</code> 的所有状态，还可以拿 <code>emitter</code> 来触发的事件，哪怕 <code>grid-layout</code> 和 <code>grid-item</code> 隔了好几个组件，依然可以正常运行。</p><p>我们以 <code>setDraggable</code> 事件为例来讲解，当 <code>grid-layout</code> 的 <code>props.isDraggable</code> 发生变化时，<code>grid-layout</code> 会通过 <code>emitter</code> 触发 <code>setDraggable</code> 事件，<code>grid-item</code> 组件监听 <code>setDraggable</code> 事件，调用 <code>setDraggableHandler</code> 函数来处理。这里 <code>grid-item</code> 首先会判断 <code>props.isDraggable</code> ，如果为空的话则根据 <code>grid-layout</code> 的 <code>props.isDraggable</code> 来判断是否可以拖动。也就是说自己组件的 <code>props</code> 的优先级大于 <code>grid-layout</code> 的 <code>props</code>，这就可以保证 <code>grid-item</code> 在拖动上做到个性化，即使 <code>grid-layout</code> 不支持拖动，也可以自己设置 <code>props.isDraggable</code> 来支持单个 <code>grid-item</code> 拖动。</p><p>上述 <code>increaseItem</code> 和 <code>decreaseItem</code> 是在 <code>grid-item</code> 把自己的实例添加到 <code>grid-layout</code> 中了，这样 <code>grid-layout</code> 暴露给外界的 <code>getItem</code> 方法就可以获取到子组件中的信息了，不过这里本章无需关注。</p><h2 id="grid-item-处理-resize-和-drag"><a href="#grid-item-处理-resize-和-drag" class="headerlink" title="grid-item 处理 resize 和 drag"></a>grid-item 处理 resize 和 drag</h2><p>在 <code>grid-item</code> 中，<code>tryMakeResizable</code> 方法，通过 <code>interact.js</code> 的回调函数调用 <code>handleResize</code> 函数来处理调整大小的逻辑。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> innerX = props.<span class="property">x</span></span><br><span class="line"><span class="keyword">let</span> innerY = props.<span class="property">y</span></span><br><span class="line"><span class="keyword">let</span> innerW = props.<span class="property">w</span></span><br><span class="line"><span class="keyword">let</span> innerH = props.<span class="property">h</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleResize</span>(<span class="params"><span class="attr">event</span>: <span class="title class_">MouseEvent</span> &amp; &#123; edges: <span class="built_in">any</span> &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">static</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="keyword">type</span> = event.<span class="property">type</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 排除异常场景</span></span><br><span class="line">  <span class="keyword">if</span> (</span><br><span class="line">    (<span class="keyword">type</span> === <span class="string">&#x27;resizestart&#x27;</span> &amp;&amp; state.<span class="property">isResizing</span>) ||</span><br><span class="line">    (<span class="keyword">type</span> !== <span class="string">&#x27;resizestart&#x27;</span> &amp;&amp; !state.<span class="property">isResizing</span>)</span><br><span class="line">  ) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 获取x、y坐标的像素值</span></span><br><span class="line">  <span class="keyword">const</span> position = <span class="title function_">getControlPosition</span>(event)</span><br><span class="line">  <span class="comment">// Get the current drag point from the event. This is used as the offset.</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isNull</span>(position)) <span class="keyword">return</span> <span class="comment">// not possible but satisfies flow</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> &#123; x, y &#125; = position</span><br><span class="line">  <span class="keyword">const</span> newSize = &#123; <span class="attr">width</span>: <span class="number">0</span>, <span class="attr">height</span>: <span class="number">0</span> &#125;</span><br><span class="line">  <span class="keyword">let</span> pos</span><br><span class="line">  <span class="keyword">switch</span> (<span class="keyword">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;resizestart&#x27;</span>: &#123;</span><br><span class="line">      <span class="title function_">tryMakeResizable</span>()</span><br><span class="line">      previousW = innerW</span><br><span class="line">      previousH = innerH</span><br><span class="line">      <span class="comment">// 计算像素值</span></span><br><span class="line">      pos = <span class="title function_">calcPosition</span>(innerX, innerY, innerW, innerH)</span><br><span class="line">      newSize.<span class="property">width</span> = pos.<span class="property">width</span></span><br><span class="line">      newSize.<span class="property">height</span> = pos.<span class="property">height</span></span><br><span class="line">      <span class="comment">// 记录初始大小</span></span><br><span class="line">      state.<span class="property">resizing</span> = newSize</span><br><span class="line">      state.<span class="property">isResizing</span> = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;resizemove&#x27;</span>: &#123;</span><br><span class="line">      <span class="comment">// A vertical resize ignores the horizontal delta</span></span><br><span class="line">      <span class="keyword">if</span> (!event.<span class="property">edges</span>.<span class="property">right</span> &amp;&amp; !event.<span class="property">edges</span>.<span class="property">left</span>) &#123;</span><br><span class="line">        lastW = x</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// An horizontal resize ignores the vertical delta</span></span><br><span class="line">      <span class="keyword">if</span> (!event.<span class="property">edges</span>.<span class="property">top</span> &amp;&amp; !event.<span class="property">edges</span>.<span class="property">bottom</span>) &#123;</span><br><span class="line">        lastH = y</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> coreEvent = <span class="title function_">createCoreData</span>(lastW, lastH, x, y)</span><br><span class="line">      <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">        newSize.<span class="property">width</span> = state.<span class="property">resizing</span>.<span class="property">width</span> - coreEvent.<span class="property">deltaX</span> / state.<span class="property">transformScale</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        newSize.<span class="property">width</span> = state.<span class="property">resizing</span>.<span class="property">width</span> + coreEvent.<span class="property">deltaX</span> / state.<span class="property">transformScale</span></span><br><span class="line">      &#125;</span><br><span class="line">      newSize.<span class="property">height</span> = state.<span class="property">resizing</span>.<span class="property">height</span> + coreEvent.<span class="property">deltaY</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="comment">// 更新坐标</span></span><br><span class="line">      state.<span class="property">resizing</span> = newSize</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;resizeend&#x27;</span>: &#123;</span><br><span class="line">      pos = <span class="title function_">calcPosition</span>(innerX, innerY, innerW, innerH)</span><br><span class="line">      newSize.<span class="property">width</span> = pos.<span class="property">width</span></span><br><span class="line">      newSize.<span class="property">height</span> = pos.<span class="property">height</span></span><br><span class="line"></span><br><span class="line">      state.<span class="property">resizing</span> = &#123; <span class="attr">width</span>: -<span class="number">1</span>, <span class="attr">height</span>: -<span class="number">1</span> &#125;</span><br><span class="line">      state.<span class="property">isResizing</span> = <span class="literal">false</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Get new WH</span></span><br><span class="line">  pos = <span class="title function_">calcWH</span>(newSize.<span class="property">height</span>, newSize.<span class="property">width</span>)</span><br><span class="line">  <span class="keyword">if</span> (pos.<span class="property">w</span> &lt; props.<span class="property">minW</span>) &#123;</span><br><span class="line">    pos.<span class="property">w</span> = props.<span class="property">minW</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (pos.<span class="property">w</span> &gt; props.<span class="property">maxW</span>) &#123;</span><br><span class="line">    pos.<span class="property">w</span> = props.<span class="property">maxW</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (pos.<span class="property">h</span> &lt; props.<span class="property">minH</span>) &#123;</span><br><span class="line">    pos.<span class="property">h</span> = props.<span class="property">minH</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (pos.<span class="property">h</span> &gt; props.<span class="property">maxH</span>) &#123;</span><br><span class="line">    pos.<span class="property">h</span> = props.<span class="property">maxH</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (pos.<span class="property">h</span> &lt; <span class="number">1</span>) &#123;</span><br><span class="line">    pos.<span class="property">h</span> = <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (pos.<span class="property">w</span> &lt; <span class="number">1</span>) &#123;</span><br><span class="line">    pos.<span class="property">w</span> = <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  lastW = x</span><br><span class="line">  lastH = y</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (innerW !== pos.<span class="property">w</span> || innerH !== pos.<span class="property">h</span>) &#123;</span><br><span class="line">    <span class="title function_">emit</span>(<span class="string">&#x27;resize&#x27;</span>, props.<span class="property">i</span>, pos.<span class="property">h</span>, pos.<span class="property">w</span>, newSize.<span class="property">height</span>, newSize.<span class="property">width</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">type</span> === <span class="string">&#x27;resizeend&#x27;</span> &amp;&amp; (previousW !== innerW || previousH !== innerH)) &#123;</span><br><span class="line">    <span class="title function_">emit</span>(<span class="string">&#x27;resized&#x27;</span>, props.<span class="property">i</span>, pos.<span class="property">h</span>, pos.<span class="property">w</span>, newSize.<span class="property">height</span>, newSize.<span class="property">width</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  emitter.<span class="title function_">emit</span>(<span class="string">&#x27;resizeEvent&#x27;</span>, event.<span class="property">type</span>, props.<span class="property">i</span>, innerX, innerY, pos.<span class="property">h</span>, pos.<span class="property">w</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>handleResize</code> 通过 <code>state.resizing</code> 对象来保存 <code>resize</code> 过程中当前 <code>grid-item</code> 的宽高， <code>state.isResizing</code> 来记录是否处于移动过程中，这两个值后面还会用到。然后通过新的宽高计算出新的 <code>w</code> 和 <code>h</code>（份数）, 当然把它们限制在最小和最大范围内，最小是 <code>1</code>。最后通过 <code>props</code> 暴露 <code>resize</code> 和 <code>resized</code> 事件，再通过 <code>emitter</code> 触发 <code>resizeEvent</code> 事件，告诉 <code>grid-layout</code> 组件当前 <code>grid-item</code> 的大小已经改变了。</p><p><code>handleDrag</code> 的处理与 <code>handleResize</code> 类型，只不过 <code>handleResize</code> 修改的是 <code>grid-item</code> 的宽高，而 <code>handleDrag</code> 修改的是<code>grid-item</code> 的位置，具体代码如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">handleDrag</span>(<span class="params"><span class="attr">event</span>: <span class="title class_">MouseEvent</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">static</span> || state.<span class="property">isResizing</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="keyword">type</span> = event.<span class="property">type</span></span><br><span class="line">  <span class="keyword">if</span> ((<span class="keyword">type</span> === <span class="string">&#x27;dragstart&#x27;</span> &amp;&amp; state.<span class="property">isDragging</span>) || (<span class="keyword">type</span> !== <span class="string">&#x27;dragstart&#x27;</span> &amp;&amp; !state.<span class="property">isDragging</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> position = <span class="title function_">getControlPosition</span>(event)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Get the current drag point from the event. This is used as the offset.</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isNull</span>(position)) <span class="keyword">return</span> <span class="comment">// not possible but satisfies flow</span></span><br><span class="line">  <span class="keyword">const</span> &#123; x, y &#125; = position</span><br><span class="line">  <span class="keyword">const</span> target = event.<span class="property">target</span> <span class="keyword">as</span> <span class="title class_">HTMLElement</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!target.<span class="property">offsetParent</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// let shouldUpdate = false;</span></span><br><span class="line">  <span class="keyword">const</span> newPosition = &#123; <span class="attr">top</span>: <span class="number">0</span>, <span class="attr">left</span>: <span class="number">0</span> &#125;</span><br><span class="line">  <span class="keyword">switch</span> (<span class="keyword">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;dragstart&#x27;</span>: &#123;</span><br><span class="line">      previousX = innerX</span><br><span class="line">      previousY = innerY</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> parentRect = target.<span class="property">offsetParent</span>.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line">      <span class="keyword">const</span> clientRect = target.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> cLeft = clientRect.<span class="property">left</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> pLeft = parentRect.<span class="property">left</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> cRight = clientRect.<span class="property">right</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> pRight = parentRect.<span class="property">right</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> cTop = clientRect.<span class="property">top</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> pTop = parentRect.<span class="property">top</span> / state.<span class="property">transformScale</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">        newPosition.<span class="property">left</span> = (cRight - pRight) * -<span class="number">1</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        newPosition.<span class="property">left</span> = cLeft - pLeft</span><br><span class="line">      &#125;</span><br><span class="line">      newPosition.<span class="property">top</span> = cTop - pTop</span><br><span class="line">      state.<span class="property">dragging</span> = newPosition</span><br><span class="line">      state.<span class="property">isDragging</span> = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;dragmove&#x27;</span>: &#123;</span><br><span class="line">      <span class="keyword">const</span> coreEvent = <span class="title function_">createCoreData</span>(lastX, lastY, x, y)</span><br><span class="line">      <span class="comment">// Add rtl support</span></span><br><span class="line">      <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">        newPosition.<span class="property">left</span> = state.<span class="property">dragging</span>.<span class="property">left</span> - coreEvent.<span class="property">deltaX</span> / state.<span class="property">transformScale</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        newPosition.<span class="property">left</span> = state.<span class="property">dragging</span>.<span class="property">left</span> + coreEvent.<span class="property">deltaX</span> / state.<span class="property">transformScale</span></span><br><span class="line">      &#125;</span><br><span class="line">      newPosition.<span class="property">top</span> = state.<span class="property">dragging</span>.<span class="property">top</span> + coreEvent.<span class="property">deltaY</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">if</span> (state.<span class="property">bounded</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> bottomBoundary =</span><br><span class="line">          target.<span class="property">offsetParent</span>.<span class="property">clientHeight</span> -</span><br><span class="line">          <span class="title function_">calcGridItemWHPx</span>(props.<span class="property">h</span>, state.<span class="property">rowHeight</span>, state.<span class="property">margin</span>[<span class="number">1</span>])</span><br><span class="line">        newPosition.<span class="property">top</span> = <span class="title function_">clamp</span>(newPosition.<span class="property">top</span>, <span class="number">0</span>, bottomBoundary)</span><br><span class="line">        <span class="keyword">const</span> colWidth = <span class="title function_">calcColWidth</span>()</span><br><span class="line">        <span class="keyword">const</span> rightBoundary =</span><br><span class="line">          state.<span class="property">containerWidth</span> - <span class="title function_">calcGridItemWHPx</span>(props.<span class="property">w</span>, colWidth, state.<span class="property">margin</span>[<span class="number">0</span>])</span><br><span class="line">        newPosition.<span class="property">left</span> = <span class="title function_">clamp</span>(newPosition.<span class="property">left</span>, <span class="number">0</span>, rightBoundary)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      state.<span class="property">dragging</span> = newPosition</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;dragend&#x27;</span>: &#123;</span><br><span class="line">      <span class="keyword">const</span> parentRect = target.<span class="property">offsetParent</span>.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line">      <span class="keyword">const</span> clientRect = target.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> cLeft = clientRect.<span class="property">left</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> pLeft = parentRect.<span class="property">left</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> cRight = clientRect.<span class="property">right</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> pRight = parentRect.<span class="property">right</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> cTop = clientRect.<span class="property">top</span> / state.<span class="property">transformScale</span></span><br><span class="line">      <span class="keyword">const</span> pTop = parentRect.<span class="property">top</span> / state.<span class="property">transformScale</span></span><br><span class="line"></span><br><span class="line">      <span class="comment">// Add rtl support</span></span><br><span class="line">      <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">        newPosition.<span class="property">left</span> = (cRight - pRight) * -<span class="number">1</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        newPosition.<span class="property">left</span> = cLeft - pLeft</span><br><span class="line">      &#125;</span><br><span class="line">      newPosition.<span class="property">top</span> = cTop - pTop</span><br><span class="line">      state.<span class="property">dragging</span> = &#123; <span class="attr">top</span>: -<span class="number">1</span>, <span class="attr">left</span>: -<span class="number">1</span> &#125;</span><br><span class="line">      state.<span class="property">isDragging</span> = <span class="literal">false</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Get new XY</span></span><br><span class="line">  <span class="keyword">let</span> pos</span><br><span class="line">  <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">    pos = <span class="title function_">calcXY</span>(newPosition.<span class="property">top</span>, newPosition.<span class="property">left</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    pos = <span class="title function_">calcXY</span>(newPosition.<span class="property">top</span>, newPosition.<span class="property">left</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  lastX = x</span><br><span class="line">  lastY = y</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (innerX !== pos.<span class="property">x</span> || innerY !== pos.<span class="property">y</span>) &#123;</span><br><span class="line">    <span class="title function_">emit</span>(<span class="string">&#x27;move&#x27;</span>, props.<span class="property">i</span>, pos.<span class="property">x</span>, pos.<span class="property">y</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">type</span> === <span class="string">&#x27;dragend&#x27;</span> &amp;&amp; (previousX !== innerX || previousY !== innerY)) &#123;</span><br><span class="line">    <span class="title function_">emit</span>(<span class="string">&#x27;moved&#x27;</span>, props.<span class="property">i</span>, pos.<span class="property">x</span>, pos.<span class="property">y</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  emitter.<span class="title function_">emit</span>(<span class="string">&#x27;dragEvent&#x27;</span>, event.<span class="property">type</span>, props.<span class="property">i</span>, pos.<span class="property">x</span>, pos.<span class="property">y</span>, innerH, innerW)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>handleDrag</code> 中通过 <code>state.dragging</code> 记录位置信息（<code>top</code> 和 <code>left</code>），当然这里还支持 <code>rtl</code>。最后通过 <code>emitter</code> 触发 <code>dragEvent</code> 事件，告诉 <code>grid-layout</code> 组件当前 <code>grid-item</code> 的移动了。</p><p>综上，<code>handleResize</code> 和 <code>handleDrag</code> 就是计算出新的宽高或位置，并告诉 <code>grid-layout</code> 变化信息。</p><h2 id="grid-layout-处理-resize-和-drag"><a href="#grid-layout-处理-resize-和-drag" class="headerlink" title="grid-layout 处理 resize 和 drag"></a>grid-layout 处理 resize 和 drag</h2><p>当 <code>grid-item</code> 宽高或者位置变化的时候，就会影响到其他 <code>grid-item</code> 位置的变化，这里就需要在 <code>grid-layout</code> 处理其他<code>grid-item</code> 的位置信息。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src\components\grid-layout.vue</span></span><br><span class="line">emitter.<span class="title function_">on</span>(<span class="string">&#x27;resizeEvent&#x27;</span>, resizeEventHandler)</span><br><span class="line">emitter.<span class="title function_">on</span>(<span class="string">&#x27;dragEvent&#x27;</span>, dragEventHandler)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">resizeEventHandler</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">eventType</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">i</span>: <span class="built_in">number</span> | <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">x</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">y</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">h</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">w</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">resizeEvent</span>(eventType, i, x, y, h, w)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">dragEventHandler</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">eventType</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">i</span>: <span class="built_in">number</span> | <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">x</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">y</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">h</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">w</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">dragEvent</span>(eventType, i, x, y, h, w)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>grid-layout</code> 中最重要的两个函数就是 <code>resizeEvent</code> 和 <code>dragEvent</code> 函数。我们先看 <code>resizeEvent</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">resizeEvent</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">eventName</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">id</span>: <span class="built_in">number</span> | <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">x</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">y</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">h</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">w</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 先根据 id 找到对应 grid-item</span></span><br><span class="line">  <span class="keyword">let</span> l = <span class="title function_">getLayoutItem</span>(currentLayout.<span class="property">value</span>, id)!</span><br><span class="line">  <span class="comment">// GetLayoutItem sometimes return null object</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isNull</span>(l)) &#123;</span><br><span class="line">    l = &#123; <span class="attr">h</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">0</span>, <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">i</span>: <span class="string">&#x27;&#x27;</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> hasCollisions</span><br><span class="line">  <span class="comment">// 如果设置 preventCollision 为 true 的时候则拖拽 grid-item 只能放在空白区域</span></span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">preventCollision</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> collisions = <span class="title function_">getAllCollisions</span>(currentLayout.<span class="property">value</span>, &#123; ...l, w, h &#125;).<span class="title function_">filter</span>(</span><br><span class="line">      <span class="function"><span class="params">layoutItem</span> =&gt;</span> layoutItem.<span class="property">i</span> !== l.<span class="property">i</span>,</span><br><span class="line">    )</span><br><span class="line">    hasCollisions = collisions.<span class="property">length</span> &gt; <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// If we&#x27;re colliding, we need adjust the placeholder.</span></span><br><span class="line">    <span class="keyword">if</span> (hasCollisions) &#123;</span><br><span class="line">      <span class="comment">// adjust w &amp;&amp; h to maximum allowed space</span></span><br><span class="line">      <span class="keyword">let</span> leastX = <span class="title class_">Infinity</span></span><br><span class="line">      <span class="keyword">let</span> leastY = <span class="title class_">Infinity</span></span><br><span class="line">      collisions.<span class="title function_">forEach</span>(<span class="function"><span class="params">layoutItem</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (layoutItem.<span class="property">x</span> &gt; l.<span class="property">x</span>) leastX = <span class="title class_">Math</span>.<span class="title function_">min</span>(leastX, layoutItem.<span class="property">x</span>)</span><br><span class="line">        <span class="keyword">if</span> (layoutItem.<span class="property">y</span> &gt; l.<span class="property">y</span>) leastY = <span class="title class_">Math</span>.<span class="title function_">min</span>(leastY, layoutItem.<span class="property">y</span>)</span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Number</span>.<span class="built_in">isFinite</span>(leastX)) l.<span class="property">w</span> = leastX - l.<span class="property">x</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Number</span>.<span class="built_in">isFinite</span>(leastY)) l.<span class="property">h</span> = leastY - l.<span class="property">y</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!hasCollisions) &#123;</span><br><span class="line">    <span class="comment">// Set new width and height.</span></span><br><span class="line">    l.<span class="property">w</span> = w</span><br><span class="line">    l.<span class="property">h</span> = h</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (eventName === <span class="string">&#x27;resizestart&#x27;</span> || eventName === <span class="string">&#x27;resizemove&#x27;</span>) &#123;</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">i</span> = id</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">x</span> = x</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">y</span> = y</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">w</span> = l.<span class="property">w</span></span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">h</span> = l.<span class="property">h</span></span><br><span class="line">    <span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      state.<span class="property">isDragging</span> = <span class="literal">true</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="comment">// this.$broadcast(&quot;updateWidth&quot;, this.width);</span></span><br><span class="line">    emitter.<span class="title function_">emit</span>(<span class="string">&#x27;updateWidth&#x27;</span>, state.<span class="property">width</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (eventName) &#123;</span><br><span class="line">    <span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      state.<span class="property">isDragging</span> = <span class="literal">false</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">responsive</span>) <span class="title function_">responsiveGridLayout</span>()</span><br><span class="line"></span><br><span class="line">  <span class="title function_">compact</span>(currentLayout.<span class="property">value</span>, props.<span class="property">verticalCompact</span>)</span><br><span class="line">  emitter.<span class="title function_">emit</span>(<span class="string">&#x27;compact&#x27;</span>)</span><br><span class="line">  <span class="title function_">updateHeight</span>()</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (eventName === <span class="string">&#x27;resizeend&#x27;</span>) <span class="title function_">emit</span>(<span class="string">&#x27;layout-updated&#x27;</span>, currentLayout.<span class="property">value</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>resizeEvent</code> 的主要功能有修改 <code>placeholder</code> 的宽高和位置和显示，当设置 <code>state.isDragging = true</code> 的时候则显示 <code>placeholder</code> （可以在上面 <strong>组件结构</strong> 中看到相关代码）；如果设置了 <code>responsive</code> 则处理响应式布局；然后收缩布局并触发事件。<code>compact</code> 方法是 <code>grid-layout</code> 的核心算法，稍后我们再讲。触发 <code>emitter.emit(&#39;compact&#39;)</code> 则通知 <code>grid-item</code> 重新渲染。这个流程起初是在 <code>grid-item</code> 通知到  <code>grid-layout</code> 现在又回到 <code>grid-item</code> 了，当然这里通知的是所有的 <code>grid-item</code> 。</p><p><code>dragEvent</code> 与 <code>resizeEvent</code> 的代码也是大同小异。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">dragEvent</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">eventName</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">id</span>: <span class="built_in">number</span> | <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">x</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">y</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">h</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">w</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> l = <span class="title function_">getLayoutItem</span>(currentLayout.<span class="property">value</span>, id)!</span><br><span class="line"></span><br><span class="line">  <span class="comment">// GetLayoutItem sometimes returns null object</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isNull</span>(l)) &#123;</span><br><span class="line">    l = &#123; <span class="attr">h</span>: <span class="number">0</span>, <span class="attr">w</span>: <span class="number">0</span>, <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">i</span>: <span class="string">&#x27;&#x27;</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (eventName === <span class="string">&#x27;dragstart&#x27;</span> &amp;&amp; !props.<span class="property">verticalCompact</span>) &#123;</span><br><span class="line">    positionsBeforeDrag = currentLayout.<span class="property">value</span>.<span class="title function_">reduce</span>(</span><br><span class="line">      <span class="function">(<span class="params">result, &#123; i, x, y &#125;</span>) =&gt;</span> (&#123;</span><br><span class="line">        ...result,</span><br><span class="line">        [i]: &#123; x, y &#125;,</span><br><span class="line">      &#125;),</span><br><span class="line">      &#123;&#125;,</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (eventName === <span class="string">&#x27;dragmove&#x27;</span> || eventName === <span class="string">&#x27;dragstart&#x27;</span>) &#123;</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">i</span> = id</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">x</span> = l.<span class="property">x</span></span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">y</span> = l.<span class="property">y</span></span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">w</span> = w</span><br><span class="line">    state.<span class="property">placeholder</span>.<span class="property">h</span> = h</span><br><span class="line"></span><br><span class="line">    <span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      state.<span class="property">isDragging</span> = <span class="literal">true</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    emitter.<span class="title function_">emit</span>(<span class="string">&#x27;updateWidth&#x27;</span>, state.<span class="property">width</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      state.<span class="property">isDragging</span> = <span class="literal">false</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Move the element to the dragged location.</span></span><br><span class="line">  currentLayout.<span class="property">value</span> = <span class="title function_">moveElement</span>(currentLayout.<span class="property">value</span>, l, x, y, <span class="literal">true</span>, props.<span class="property">preventCollision</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">restoreOnDrag</span>) &#123;</span><br><span class="line">    <span class="comment">// Do not compact items more than in layout before drag</span></span><br><span class="line">    <span class="comment">// Set moved item as static to avoid to compact it</span></span><br><span class="line">    l.<span class="property">static</span> = <span class="literal">true</span></span><br><span class="line">    <span class="title function_">compact</span>(currentLayout.<span class="property">value</span>, props.<span class="property">verticalCompact</span>, positionsBeforeDrag)</span><br><span class="line">    l.<span class="property">static</span> = <span class="literal">false</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">compact</span>(currentLayout.<span class="property">value</span>, props.<span class="property">verticalCompact</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// needed because vue can&#x27;t detect changes on array element properties</span></span><br><span class="line">  emitter.<span class="title function_">emit</span>(<span class="string">&#x27;compact&#x27;</span>)</span><br><span class="line">  <span class="title function_">updateHeight</span>()</span><br><span class="line">  <span class="keyword">if</span> (eventName === <span class="string">&#x27;dragend&#x27;</span>) &#123;</span><br><span class="line">    positionsBeforeDrag = <span class="literal">undefined</span></span><br><span class="line">    <span class="title function_">emit</span>(<span class="string">&#x27;layout-updated&#x27;</span>, currentLayout.<span class="property">value</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>dragEvent</code> 与 <code>resizeEvent</code> 最大的不同是调用了 <code>moveElement</code> 方法。<code>resize</code> 的时候影响的是当前 <code>grit-item</code> 的宽高，同时会影响右侧和下方的元素 <code>y</code> 值变化，这个过程的逻辑在 <code>compact</code> 函数里面，作用是消除空白区域使其紧凑。但是 <code>drag</code> 的时候修改的是位置，位置一变化，甚至会导致上面的元素挪到当前移动元素的下方，也就是换位，这就有了 <code>moveElement</code> 方法。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Move an element. Responsible for doing cascading movements of other elements.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>        layout Full layout to modify.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>   layoutItem      element to move.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>       x    X position in grid units.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>       y    Y position in grid units.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>      isUserAction If true, designates that the item we&#x27;re moving is</span></span><br><span class="line"><span class="comment"> *                                     being dragged/resized by th euser.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">moveElement</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">layout</span>: <span class="title class_">Layout</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">layoutItem</span>: <span class="title class_">LayoutItem</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">x</span>?: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">y</span>?: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  isUserAction = <span class="literal">false</span>,</span></span><br><span class="line"><span class="params">  preventCollision = <span class="literal">false</span>,</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Layout</span> &#123;</span><br><span class="line">  <span class="comment">// 静态的不能移动，所以不做处理，直接返回布局</span></span><br><span class="line">  <span class="keyword">if</span> (layoutItem.<span class="property">static</span>) <span class="keyword">return</span> layout</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 记录旧的值</span></span><br><span class="line">  <span class="keyword">const</span> oldX = layoutItem.<span class="property">x</span></span><br><span class="line">  <span class="keyword">const</span> oldY = layoutItem.<span class="property">y</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// y 越大越在下面 这里当前y大于目的地y 表示向上移动</span></span><br><span class="line">  <span class="keyword">const</span> movingUp = y &amp;&amp; layoutItem.<span class="property">y</span> &gt; y</span><br><span class="line">  <span class="comment">// This is quite a bit faster than extending the object</span></span><br><span class="line">  <span class="comment">// 有值的时候才赋值 后面传 undefined 的时候 则不赋值</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> x === <span class="string">&#x27;number&#x27;</span>) layoutItem.<span class="property">x</span> = x</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> y === <span class="string">&#x27;number&#x27;</span>) layoutItem.<span class="property">y</span> = y</span><br><span class="line">  <span class="comment">// 标记移动</span></span><br><span class="line">  layoutItem.<span class="property">moved</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// If this collides with anything, move it.</span></span><br><span class="line">  <span class="comment">// When doing this comparison, we have to sort the items we compare with</span></span><br><span class="line">  <span class="comment">// to ensure, in the case of multiple collisions, that we&#x27;re getting the</span></span><br><span class="line">  <span class="comment">// nearest collision.</span></span><br><span class="line">  <span class="keyword">let</span> sorted = <span class="title function_">sortLayoutItemsByRowCol</span>(layout)</span><br><span class="line">  <span class="keyword">if</span> (movingUp) sorted = sorted.<span class="title function_">reverse</span>()</span><br><span class="line">  <span class="comment">// 获取所有碰撞的 grid-item 数据</span></span><br><span class="line">  <span class="keyword">const</span> collisions = <span class="title function_">getAllCollisions</span>(sorted, layoutItem)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// preventCollision为true 则不处理碰撞 赋值之前的值就行</span></span><br><span class="line">  <span class="keyword">if</span> (preventCollision &amp;&amp; collisions.<span class="property">length</span>) &#123;</span><br><span class="line">    layoutItem.<span class="property">x</span> = oldX</span><br><span class="line">    layoutItem.<span class="property">y</span> = oldY</span><br><span class="line">    layoutItem.<span class="property">moved</span> = <span class="literal">false</span></span><br><span class="line">    <span class="keyword">return</span> layout</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Move each item that collides away from this element.</span></span><br><span class="line">  <span class="comment">// 处理级联移动</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, len = collisions.<span class="property">length</span>; i &lt; len; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> collision = collisions[i]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Short circuit so we can&#x27;t infinite loop</span></span><br><span class="line">    <span class="keyword">if</span> (collision.<span class="property">moved</span>) <span class="keyword">continue</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// This makes it feel a bit more precise by waiting to swap for just a bit when moving up.</span></span><br><span class="line">    <span class="comment">// 移动到大于碰撞元素高度的1/4前不处理</span></span><br><span class="line">    <span class="keyword">if</span> (layoutItem.<span class="property">y</span> &gt; collision.<span class="property">y</span> &amp;&amp; layoutItem.<span class="property">y</span> - collision.<span class="property">y</span> &gt; collision.<span class="property">h</span> / <span class="number">4</span>) <span class="keyword">continue</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Don&#x27;t move static items - we have to move *this* element away</span></span><br><span class="line">    <span class="keyword">if</span> (collision.<span class="property">static</span>) &#123;</span><br><span class="line">      layout = <span class="title function_">moveElementAwayFromCollision</span>(layout, collision, layoutItem, isUserAction)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      layout = <span class="title function_">moveElementAwayFromCollision</span>(layout, layoutItem, collision, isUserAction)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> layout</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>moveElement</code> 中，对当前元素的移动通过给 <code>x</code> 和 <code>y</code> 坐标赋值来实现。然后遍历所有碰撞的元素，来做级联移动。在 <code>moveElementAwayFromCollision</code> 中第二个参数是碰撞的不动的元素，第三个参数是要移动的参数，如果 <code>collision.static</code> 为 <code>true</code> 的时候，则<code>collision</code>不动，移动当前元素；否则当前元素不动，移动碰撞的元素，这里很巧妙地通过参数的位置就实现了静态碰撞元素的处理。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This is where the magic needs to happen - given a collision, move an element away from the collision.</span></span><br><span class="line"><span class="comment"> * We attempt to move it up if there&#x27;s room, otherwise it goes below.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>   layout            Full layout to modify.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>   collidesWith Layout item we&#x27;re colliding with.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>   itemToMove   Layout item we&#x27;re moving.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  isUserAction  If true, designates that the item we&#x27;re moving is being dragged/resized</span></span><br><span class="line"><span class="comment"> *                                   by the user.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">moveElementAwayFromCollision</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">layout</span>: <span class="title class_">Layout</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">collidesWith</span>: <span class="title class_">LayoutItem</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">itemToMove</span>: <span class="title class_">LayoutItem</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">isUserAction</span>?: <span class="built_in">boolean</span>,</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Layout</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> preventCollision = <span class="literal">false</span> <span class="comment">// we&#x27;re already colliding</span></span><br><span class="line">  <span class="comment">// If there is enough space above the collision to put this element, move it there.</span></span><br><span class="line">  <span class="comment">// We only do this on the main collision as this can get funky in cascades and cause</span></span><br><span class="line">  <span class="comment">// unwanted swapping behavior.</span></span><br><span class="line">  <span class="keyword">if</span> (isUserAction) &#123;</span><br><span class="line">    <span class="comment">// Make a mock item so we don&#x27;t modify the item here, only modify in moveElement.</span></span><br><span class="line">    <span class="keyword">const</span> <span class="attr">fakeItem</span>: <span class="title class_">LayoutItem</span> = &#123;</span><br><span class="line">      <span class="attr">x</span>: itemToMove.<span class="property">x</span>,</span><br><span class="line">      <span class="attr">y</span>: itemToMove.<span class="property">y</span>,</span><br><span class="line">      <span class="attr">w</span>: itemToMove.<span class="property">w</span>,</span><br><span class="line">      <span class="attr">h</span>: itemToMove.<span class="property">h</span>,</span><br><span class="line">      <span class="attr">i</span>: <span class="string">&#x27;-1&#x27;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    fakeItem.<span class="property">y</span> = <span class="title class_">Math</span>.<span class="title function_">max</span>(collidesWith.<span class="property">y</span> - itemToMove.<span class="property">h</span>, <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">if</span> (!<span class="title function_">getFirstCollision</span>(layout, fakeItem)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">moveElement</span>(layout, itemToMove, <span class="literal">undefined</span>, fakeItem.<span class="property">y</span>, preventCollision)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Previously this was optimized to move below the collision directly, but this can cause problems</span></span><br><span class="line">  <span class="comment">// with cascading moves, as an item may actually leapflog a collision and cause a reversal in order.</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">moveElement</span>(layout, itemToMove, <span class="literal">undefined</span>, itemToMove.<span class="property">y</span> + <span class="number">1</span>, preventCollision)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>moveElementAwayFromCollision</code> 中如果是用户手动操作，则把目标移动到 <code>collidesWith.y - itemToMove.h</code> 位置，这样目标与 <code>collidesWith</code> 刚好不碰撞。当然需要调用 <code>getFirstCollision</code> 检测一下，没碰撞就移动到这个位置，如果碰撞了则往下移动一格。之后调用 <code>moveElement</code> ，由于碰撞的 <code>item</code> 向下移动了一格，又会引发它下面的项目级联移动，直到所有元素都没发生碰撞。</p><p>如果对移动到 <code>collidesWith.y - itemToMove.h</code> 位置不理解，可以看下图，<code>0</code> 放在某个静态块的上面，如果 <code>vertical-compact</code> 的 <code>props</code> 为 <code>false</code> 时，就应该刚好放在静态块的上面；当然如果为 <code>true</code> 则后面的 <code>compact</code> 函数会把它处理在最顶部。</p><p><img src="/2025/08/05/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-grid-layout-plus/1.png" alt="插入到某个静态块的上面"></p><h2 id="compact-实现"><a href="#compact-实现" class="headerlink" title="compact 实现"></a>compact 实现</h2><p><code>compact</code> 方法是使得所有元素变得紧凑。在介绍 <code>compact</code> 前先看几个辅助方法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Given two layoutitems, check if they collide.</span></span><br><span class="line"><span class="comment"> * 判断两个元素是否碰撞</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> True if colliding.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">collides</span>(<span class="params"><span class="attr">l1</span>: <span class="title class_">LayoutItem</span>, <span class="attr">l2</span>: <span class="title class_">LayoutItem</span></span>): <span class="built_in">boolean</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (l1 === l2) <span class="keyword">return</span> <span class="literal">false</span> <span class="comment">// same element</span></span><br><span class="line">  <span class="keyword">if</span> (l1.<span class="property">x</span> + l1.<span class="property">w</span> &lt;= l2.<span class="property">x</span>) <span class="keyword">return</span> <span class="literal">false</span> <span class="comment">// l1 is left of l2</span></span><br><span class="line">  <span class="keyword">if</span> (l1.<span class="property">x</span> &gt;= l2.<span class="property">x</span> + l2.<span class="property">w</span>) <span class="keyword">return</span> <span class="literal">false</span> <span class="comment">// l1 is right of l2</span></span><br><span class="line">  <span class="keyword">if</span> (l1.<span class="property">y</span> + l1.<span class="property">h</span> &lt;= l2.<span class="property">y</span>) <span class="keyword">return</span> <span class="literal">false</span> <span class="comment">// l1 is above l2</span></span><br><span class="line">  <span class="keyword">if</span> (l1.<span class="property">y</span> &gt;= l2.<span class="property">y</span> + l2.<span class="property">h</span>) <span class="keyword">return</span> <span class="literal">false</span> <span class="comment">// l1 is below l2</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span> <span class="comment">// boxes overlap</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns the first item this layout collides with.</span></span><br><span class="line"><span class="comment"> * It doesn&#x27;t appear to matter which order we approach this from, although</span></span><br><span class="line"><span class="comment"> * perhaps that is the wrong thing to do.</span></span><br><span class="line"><span class="comment"> * 查找第一个碰撞的元素</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  &#123;<span class="type">Object</span>&#125; layoutItem Layout item.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="type">Object|undefined</span>&#125;  A colliding layout item, or undefined.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">getFirstCollision</span>(<span class="params"><span class="attr">layout</span>: <span class="title class_">Layout</span>, <span class="attr">layoutItem</span>: <span class="title class_">LayoutItem</span></span>): <span class="title class_">LayoutItem</span> | <span class="literal">undefined</span> &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, len = layout.<span class="property">length</span>; i &lt; len; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">collides</span>(layout[i], layoutItem)) <span class="keyword">return</span> layout[i]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取所有碰撞的元素</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">getAllCollisions</span>(<span class="params"><span class="attr">layout</span>: <span class="title class_">Layout</span>, <span class="attr">layoutItem</span>: <span class="title class_">LayoutItem</span></span>): <span class="title class_">Array</span>&lt;<span class="title class_">LayoutItem</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> layout.<span class="title function_">filter</span>(<span class="function"><span class="params">l</span> =&gt;</span> <span class="title function_">collides</span>(l, layoutItem))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Get all static elements.</span></span><br><span class="line"><span class="comment"> * 获取所有静态元素</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> layout Array of layout objects.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span>  Array of static layout items..</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">getStatics</span>(<span class="params"><span class="attr">layout</span>: <span class="title class_">Layout</span></span>): <span class="title class_">Array</span>&lt;<span class="title class_">LayoutItem</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> layout.<span class="title function_">filter</span>(<span class="function"><span class="params">l</span> =&gt;</span> l.<span class="property">static</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Get layout items sorted from top left to right and down.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Layout, sorted static items first.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">sortLayoutItemsByRowCol</span>(<span class="params"><span class="attr">layout</span>: <span class="title class_">Layout</span></span>): <span class="title class_">Layout</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Array</span>.<span class="title function_">from</span>(layout).<span class="title function_">sort</span>(<span class="keyword">function</span> (<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (a.<span class="property">y</span> === b.<span class="property">y</span> &amp;&amp; a.<span class="property">x</span> === b.<span class="property">x</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (a.<span class="property">y</span> &gt; b.<span class="property">y</span> || (a.<span class="property">y</span> === b.<span class="property">y</span> &amp;&amp; a.<span class="property">x</span> &gt; b.<span class="property">x</span>)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这些方法整体比较简单，我们再看 <code>compact</code> 方法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Given a layout, compact it. This involves going down each y coordinate and removing gaps</span></span><br><span class="line"><span class="comment"> * between items.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  layout Layout.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  verticalCompact Whether or not to compact the layout vertically.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <span class="variable">minPositions</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Compacted Layout.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">compact</span>(<span class="params"><span class="attr">layout</span>: <span class="title class_">Layout</span>, <span class="attr">verticalCompact</span>?: <span class="built_in">boolean</span>, <span class="attr">minPositions</span>?: <span class="built_in">any</span></span>): <span class="title class_">Layout</span> &#123;</span><br><span class="line">  <span class="comment">// Statics go in the compareWith array right away so items flow around them.</span></span><br><span class="line">  <span class="keyword">const</span> compareWith = <span class="title function_">getStatics</span>(layout)</span><br><span class="line">  <span class="comment">// We go through the items by row and column.</span></span><br><span class="line">  <span class="keyword">const</span> sorted = <span class="title function_">sortLayoutItemsByRowCol</span>(layout)</span><br><span class="line">  <span class="comment">// Holding for new items.</span></span><br><span class="line">  <span class="keyword">const</span> <span class="attr">out</span>: <span class="title class_">Layout</span> = <span class="title class_">Array</span>(layout.<span class="property">length</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, len = sorted.<span class="property">length</span>; i &lt; len; i++) &#123;</span><br><span class="line">    <span class="keyword">let</span> l = sorted[i]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Don&#x27;t move static elements</span></span><br><span class="line">    <span class="keyword">if</span> (!l.<span class="property">static</span>) &#123;</span><br><span class="line">      l = <span class="title function_">compactItem</span>(compareWith, l, verticalCompact, minPositions)</span><br><span class="line"></span><br><span class="line">      <span class="comment">// Add to comparison array. We only collide with items before this one.</span></span><br><span class="line">      <span class="comment">// Statics are already in this array.</span></span><br><span class="line">      compareWith.<span class="title function_">push</span>(l)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Add to output array to make sure they still come out in the right order.</span></span><br><span class="line">    out[layout.<span class="title function_">findIndex</span>(<span class="function"><span class="params">i</span> =&gt;</span> i.<span class="property">i</span> === l.<span class="property">i</span>)] = l</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Clear moved flag, if it exists.</span></span><br><span class="line">    l.<span class="property">moved</span> = <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> out</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>compact</code> 先获取所有静态的元素赋值给 <code>compareWith</code> 。然后排序，以保证遍历的顺序是从左往右，从上到下的。然后对排好序的元素进行遍历。对于非静态的处理后加入到 <code>compareWith</code> 中（静态的已经在里面了）。这样就可以先确认静态元素的位置，然后从左到右从上到下把没碰撞的元素加入进来。</p><p>把元素处理成没碰撞的位置代码如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Compact an item in the layout.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">compactItem</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">compareWith</span>: <span class="title class_">Layout</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">l</span>: <span class="title class_">LayoutItem</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">verticalCompact</span>?: <span class="built_in">boolean</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">minPositions</span>?: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">LayoutItem</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (verticalCompact) &#123;</span><br><span class="line">    <span class="comment">// Move the element up as far as it can go without colliding.</span></span><br><span class="line">    <span class="keyword">while</span> (l.<span class="property">y</span> &gt; <span class="number">0</span> &amp;&amp; !<span class="title function_">getFirstCollision</span>(compareWith, l)) &#123;</span><br><span class="line">      l.<span class="property">y</span>--</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (minPositions) &#123;</span><br><span class="line">    <span class="keyword">const</span> minY = minPositions[l.<span class="property">i</span>].<span class="property">y</span></span><br><span class="line">    <span class="keyword">while</span> (l.<span class="property">y</span> &gt; minY &amp;&amp; !<span class="title function_">getFirstCollision</span>(compareWith, l)) &#123;</span><br><span class="line">      l.<span class="property">y</span>--</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Move it down, and keep moving it down if it&#x27;s colliding.</span></span><br><span class="line">  <span class="keyword">let</span> collides</span><br><span class="line">  <span class="keyword">while</span> ((collides = <span class="title function_">getFirstCollision</span>(compareWith, l))) &#123;</span><br><span class="line">    l.<span class="property">y</span> = collides.<span class="property">y</span> + collides.<span class="property">h</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> l</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>compactItem</code> 中，<code>verticalCompact</code> 为 <code>true</code> 的时候，则需要变的紧凑，变紧凑的过程是先把元素往上移动，移动到第一个发生碰撞的地方。当然如果一个元素上面是静态元素，那么向上移动肯定会碰撞的，所以向上移动后对于发生碰撞的元素还需要向下移动，直到不碰撞位置。这样在 <code>compact</code> 中遍历完所有排好序的元素，就会使所有元素变得紧凑。</p><h2 id="grid-item-处理-compact-事件"><a href="#grid-item-处理-compact-事件" class="headerlink" title="grid-item 处理 compact 事件"></a>grid-item 处理 compact 事件</h2><p>还记得 <code>grid-layout</code> 无论是 <code>dragEvent</code> 还是 <code>resizeEvent</code> 在调用完 <code>compact</code> 方法都会调用 <code>emitter.emit(&#39;compact&#39;)</code> 吗？这是触发所有 <code>grid-item</code> 元素的 <code>compact</code> 事件，<code>grid-item</code> 中处理如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><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></pre></td><td class="code"><pre><span class="line">emitter.<span class="title function_">on</span>(<span class="string">&#x27;compact&#x27;</span>, compactHandler)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">compactHandler</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">compact</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">compact</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">createStyle</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setTransform</span>(<span class="params"><span class="attr">top</span>: <span class="built_in">number</span>, <span class="attr">left</span>: <span class="built_in">number</span>, <span class="attr">width</span>: <span class="built_in">number</span>, <span class="attr">height</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">  <span class="comment">// Replace unitless items with px</span></span><br><span class="line">  <span class="keyword">const</span> translate = <span class="string">&#x27;translate3d(&#x27;</span> + left + <span class="string">&#x27;px,&#x27;</span> + top + <span class="string">&#x27;px, 0)&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">transform</span>: translate,</span><br><span class="line">    <span class="title class_">WebkitTransform</span>: translate,</span><br><span class="line">    <span class="title class_">MozTransform</span>: translate,</span><br><span class="line">    <span class="attr">msTransform</span>: translate,</span><br><span class="line">    <span class="title class_">OTransform</span>: translate,</span><br><span class="line">    <span class="attr">width</span>: width + <span class="string">&#x27;px&#x27;</span>,</span><br><span class="line">    <span class="attr">height</span>: height + <span class="string">&#x27;px&#x27;</span>,</span><br><span class="line">    <span class="attr">position</span>: <span class="string">&#x27;absolute&#x27;</span>,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setTopLeft</span>(<span class="params"><span class="attr">top</span>: <span class="built_in">number</span>, <span class="attr">left</span>: <span class="built_in">number</span>, <span class="attr">width</span>: <span class="built_in">number</span>, <span class="attr">height</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">top</span>: top + <span class="string">&#x27;px&#x27;</span>,</span><br><span class="line">    <span class="attr">left</span>: left + <span class="string">&#x27;px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: width + <span class="string">&#x27;px&#x27;</span>,</span><br><span class="line">    <span class="attr">height</span>: height + <span class="string">&#x27;px&#x27;</span>,</span><br><span class="line">    <span class="attr">position</span>: <span class="string">&#x27;absolute&#x27;</span>,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createStyle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">x</span> + props.<span class="property">w</span> &gt; state.<span class="property">cols</span>) &#123;</span><br><span class="line">    innerX = <span class="number">0</span></span><br><span class="line">    innerW = props.<span class="property">w</span> &gt; state.<span class="property">cols</span> ? state.<span class="property">cols</span> : props.<span class="property">w</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    innerX = props.<span class="property">x</span></span><br><span class="line">    innerW = props.<span class="property">w</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> pos = <span class="title function_">calcPosition</span>(innerX, innerY, innerW, innerH)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (state.<span class="property">isDragging</span>) &#123;</span><br><span class="line">    pos.<span class="property">top</span> = state.<span class="property">dragging</span>.<span class="property">top</span></span><br><span class="line">    <span class="comment">// Add rtl support</span></span><br><span class="line">    <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">      pos.<span class="property">right</span> = state.<span class="property">dragging</span>.<span class="property">left</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      pos.<span class="property">left</span> = state.<span class="property">dragging</span>.<span class="property">left</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (state.<span class="property">isResizing</span>) &#123;</span><br><span class="line">    pos.<span class="property">width</span> = state.<span class="property">resizing</span>.<span class="property">width</span></span><br><span class="line">    pos.<span class="property">height</span> = state.<span class="property">resizing</span>.<span class="property">height</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> style</span><br><span class="line">  <span class="comment">// CSS Transforms support (default)</span></span><br><span class="line">  <span class="keyword">if</span> (state.<span class="property">useCssTransforms</span>) &#123;</span><br><span class="line">    <span class="comment">// Add rtl support</span></span><br><span class="line">    <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">      style = <span class="title function_">setTransformRtl</span>(pos.<span class="property">top</span>, pos.<span class="property">right</span>!, pos.<span class="property">width</span>, pos.<span class="property">height</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      style = <span class="title function_">setTransform</span>(pos.<span class="property">top</span>, pos.<span class="property">left</span>!, pos.<span class="property">width</span>, pos.<span class="property">height</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// top,left (slow)</span></span><br><span class="line">    <span class="comment">// Add rtl support</span></span><br><span class="line">    <span class="keyword">if</span> (renderRtl.<span class="property">value</span>) &#123;</span><br><span class="line">      style = <span class="title function_">setTopRight</span>(pos.<span class="property">top</span>, pos.<span class="property">right</span>!, pos.<span class="property">width</span>, pos.<span class="property">height</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      style = <span class="title function_">setTopLeft</span>(pos.<span class="property">top</span>, pos.<span class="property">left</span>!, pos.<span class="property">width</span>, pos.<span class="property">height</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  state.<span class="property">style</span> = style</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>createStyle</code> 中先让 <code>innerX</code> 和 <code>innerW</code> 设置在一个合法的数值范围内，然后获取到位置信息 <code>pos</code>， 还记得之前的 <code>state.dragging</code> 和 <code>state.resizing</code> 吗？如果处于 <code>drag</code> &#x2F; <code>resize</code> 过程中，则把对应的坐标给 <code>pos</code>。然后通过 <code>useCssTransforms</code> 来判断是使用 <code>transform</code> 还是 <code>top/left</code> 来设置位置。最后修改 <code>state.style</code>，在模版中对应的 <code>grid-item</code> 的 <code>style</code> 就会改变。</p><p>这就是 <code>grid-layout-plus</code>的核心流程，还有一些响应式等逻辑没有介绍，相信你也能看得懂对应的源码。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/qmhc/grid-layout-plus&quot;&gt;grid-layout-plus&lt;/a&gt;是一个支持拖动、重新设置大小的网格布局组件。它的代码是在&lt;a href=&quot;https://github.com/jbaysoluti</summary>
      
    
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/categories/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/tags/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>Canvas系列（21）：多边形碰撞检测</title>
    <link href="https://www.kai666666.com/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/"/>
    <id>https://www.kai666666.com/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/</id>
    <published>2025-07-21T11:32:43.000Z</published>
    <updated>2026-02-10T08:00:34.372Z</updated>
    
    <content type="html"><![CDATA[<p>之前的文章我们做过写过一篇<a href="https://www.kai666666.com/2021/03/05/Canvas%E7%B3%BB%E5%88%97%EF%BC%8817%EF%BC%89%EF%BC%9A%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/">碰撞检测</a>的文章，那篇文章中我们简绍了，圆与圆的碰撞检测，长方形与长方形的碰撞检测，以及圆与长方形的碰撞检测。本章我们来介绍多边形与多边形的碰撞检测。</p><hr><h2 id="多边形类结构"><a href="#多边形类结构" class="headerlink" title="多边形类结构"></a>多边形类结构</h2><p>我们先定义一个多边形的类，方便以后操作。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Polygon</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context;</span><br><span class="line">    <span class="comment">// 多边形顶点数组，每个顶点是一个包含 x 和 y 属性的对象</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vertices</span> = options.<span class="property">vertices</span> || [];</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">color</span> = options.<span class="property">color</span> || <span class="string">&#x27;#000&#x27;</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> = options.<span class="property">vx</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> = options.<span class="property">vy</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ax</span> = options.<span class="property">ax</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ay</span> = options.<span class="property">ay</span> || <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> += <span class="variable language_">this</span>.<span class="property">ax</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> += <span class="variable language_">this</span>.<span class="property">ay</span>;</span><br><span class="line">    <span class="comment">// 更新每个顶点的位置</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> vertex <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">vertices</span>) &#123;</span><br><span class="line">      vertex.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span>;</span><br><span class="line">      vertex.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">save</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">beginPath</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="property">fillStyle</span> = <span class="variable language_">this</span>.<span class="property">color</span>;</span><br><span class="line">    <span class="comment">// 移动到第一个顶点</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">vertices</span>.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">moveTo</span>(<span class="variable language_">this</span>.<span class="property">vertices</span>[<span class="number">0</span>].<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">vertices</span>[<span class="number">0</span>].<span class="property">y</span>);</span><br><span class="line">      <span class="comment">// 依次连接其他顶点</span></span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt; <span class="variable language_">this</span>.<span class="property">vertices</span>.<span class="property">length</span>; i++) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">lineTo</span>(<span class="variable language_">this</span>.<span class="property">vertices</span>[i].<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">vertices</span>[i].<span class="property">y</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 闭合路径</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">closePath</span>();</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">fill</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">restore</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// TODO：判断点是否在多边形内</span></span><br><span class="line">  <span class="title function_">isContainsPoint</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">moveBy</span>(<span class="params">dx, dy</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> vertex <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">vertices</span>) &#123;</span><br><span class="line">      vertex.<span class="property">x</span> += dx;</span><br><span class="line">      vertex.<span class="property">y</span> += dy;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">getVertices</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">vertices</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上面类我们就可以创建多边形了，使用方式如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> centerX = canvas.<span class="property">width</span> / <span class="number">2</span>;</span><br><span class="line"><span class="keyword">const</span> centerY = canvas.<span class="property">height</span> / <span class="number">2</span>;</span><br><span class="line"><span class="keyword">const</span> radius = <span class="number">80</span>;</span><br><span class="line"><span class="keyword">const</span> numVertices = <span class="number">8</span>;</span><br><span class="line"><span class="keyword">const</span> angleStep = (<span class="title class_">Math</span>.<span class="property">PI</span> * <span class="number">2</span>) / numVertices;</span><br><span class="line"><span class="keyword">const</span> vertices = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; numVertices; i++) &#123;</span><br><span class="line">  <span class="keyword">const</span> angle = i * angleStep;</span><br><span class="line">  <span class="keyword">const</span> x = centerX + radius * <span class="title class_">Math</span>.<span class="title function_">cos</span>(angle);</span><br><span class="line">  <span class="keyword">const</span> y = centerY + radius * <span class="title class_">Math</span>.<span class="title function_">sin</span>(angle);</span><br><span class="line">  vertices.<span class="title function_">push</span>(&#123; x, y &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> polygon = <span class="keyword">new</span> <span class="title class_">Polygon</span>(context, &#123;</span><br><span class="line">  vertices,</span><br><span class="line">  <span class="attr">color</span>: <span class="string">&#x27;blue&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>我们在画布中央，绘制了一个蓝色的正八边形，<a href="https://canvas-demo.kai666666.com/21/01.html">效果如下</a>：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/1.png" alt="正八边形"></p><p>上述代码中有一个 <code>isContainsPoint</code> 用来判断点是否在多边形内。那么如何判断一个点在多边形内呢？这里我们就用到了射线法。</p><h2 id="射线法"><a href="#射线法" class="headerlink" title="射线法"></a>射线法</h2><p><strong>射线法（Ray Casting Algorithm）</strong> 是一种判断点是否在多边形内部的经典算法，其原理是<a href="https://www.kai666666.com/2019/04/27/%E9%9D%9E%E9%9B%B6%E7%8E%AF%E7%BB%95%E8%A7%84%E5%88%99/">非零环绕规则</a>。射线法核心逻辑可概括如下：</p><blockquote><p>从目标点向任意方向（通常选择水平向右）发射一条射线，若射线与多边形边界的交点数量为奇数，则点在多边形内部；若为偶数（包括 0），则点在多边形外部。</p></blockquote><p>在射线法中，从目标点发射出的射线的方向是任意的，但是为了计算方便我们一般选择水平向右。然后遍历每条边，记录边与射线的相交次数。这里其实我们只关注相交次数是奇数还是偶数，而不关注具体的次数。所以也可以用一个默认为 <code>false</code> 的 <code>boolean</code> 类型的变量来记录，如果相交则对其取反，最后值如果是 <code>true</code> 则点在多边形内部，否则在外部。当然这里还有一个问题，怎么判断射线与线段是否相交呢？</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/2.png" alt="射线与线段相交"></p><p>对于水平向右的射线来说，只需要满足2个条件则射线与线段相交。</p><blockquote><p>条件 1：射线发出点的 y 坐标在边的两个端点 y 之间；<br>条件 2：射线与边的交点的 x 坐标，在射线发出点的右侧。</p></blockquote><p>对于第一条来说，只要满足 <code>yi &gt; y &gt; yj</code> 或者 <code>yi &lt; y &lt; yj</code> 就行，也就是 <code>yi &gt; y</code> 与 <code>yj &gt; y</code> 不相等。<br>对于第二条来说，先得求出交点的x坐标（交点的y坐标就是射线发出点的y左边），假设交点的x坐标用 <code>x2</code> 来表示，则根据相似三角形有 <code>(xj - xi) / (yj - yi) = (x2 - xi) / (y - yi)</code>，可得 <code>x2 = (xj - xi) * (y - yi) / (yj - yi) + xi</code>。所以 <code>x &lt; x2</code> 即可说明交点在射线发出点的右侧。具体代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Polygon</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用射线法判断点是否在多边形内</span></span><br><span class="line">  <span class="title function_">isContainsPoint</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> inside = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// j是上一个端点的索引，刚开始j是最后一个点</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, j = <span class="variable language_">this</span>.<span class="property">vertices</span>.<span class="property">length</span> - <span class="number">1</span>; i &lt; <span class="variable language_">this</span>.<span class="property">vertices</span>.<span class="property">length</span>; j = i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> xi = <span class="variable language_">this</span>.<span class="property">vertices</span>[i].<span class="property">x</span>, yi = <span class="variable language_">this</span>.<span class="property">vertices</span>[i].<span class="property">y</span>;</span><br><span class="line">      <span class="keyword">const</span> xj = <span class="variable language_">this</span>.<span class="property">vertices</span>[j].<span class="property">x</span>, yj = <span class="variable language_">this</span>.<span class="property">vertices</span>[j].<span class="property">y</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> intersect = ((yi &gt; y) !== (yj &gt; y)) &amp;&amp;</span><br><span class="line">        (x &lt; (xj - xi) * (y - yi) / (yj - yi) + xi);</span><br><span class="line">      <span class="keyword">if</span> (intersect) inside = !inside;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> inside;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有了 <code>isContainsPoint</code> 方法我们就可以给多边形添加拖拽了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">let</span> mouse = <span class="title function_">captureMouse</span>(canvas);</span><br><span class="line"><span class="keyword">let</span> selectedObj = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">canvas.<span class="title function_">addEventListener</span>(<span class="string">&#x27;mousedown&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> startX = mouse.<span class="property">x</span>;</span><br><span class="line">  <span class="keyword">let</span> startY = mouse.<span class="property">y</span>;</span><br><span class="line">  <span class="keyword">if</span> (polygon.<span class="title function_">isContainsPoint</span>(mouse.<span class="property">x</span>, mouse.<span class="property">y</span>)) &#123;</span><br><span class="line">    selectedObj = polygon;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (selectedObj) &#123;</span><br><span class="line">    <span class="comment">// 添加事件来模拟拖拽</span></span><br><span class="line">    canvas.<span class="title function_">addEventListener</span>(<span class="string">&#x27;mousemove&#x27;</span>, onMouseMove, <span class="literal">false</span>);</span><br><span class="line">    canvas.<span class="title function_">addEventListener</span>(<span class="string">&#x27;mouseup&#x27;</span>, onMouseUp, <span class="literal">false</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onMouseMove</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> dx = mouse.<span class="property">x</span> - startX;</span><br><span class="line">    <span class="keyword">const</span> dy = mouse.<span class="property">y</span> - startY;</span><br><span class="line"></span><br><span class="line">    selectedObj.<span class="title function_">moveBy</span>(dx, dy);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新起始位置</span></span><br><span class="line">    startX = mouse.<span class="property">x</span>;</span><br><span class="line">    startY = mouse.<span class="property">y</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">onMouseUp</span> (<span class="params"></span>) &#123;</span><br><span class="line">    selectedObj = <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// 清除事件</span></span><br><span class="line">    canvas.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;mousemove&#x27;</span>, onMouseMove, <span class="literal">false</span>);</span><br><span class="line">    canvas.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;mouseup&#x27;</span>, onMouseUp, <span class="literal">false</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure><p><code>captureMouse()</code> 函数参考<a href="https://www.kai666666.com/2020/08/03/Canvas%E7%B3%BB%E5%88%97%EF%BC%8815%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E5%B0%8F%E7%90%83%E6%8B%96%E6%8B%BD/">这里</a>。</p><p><a href="https://canvas-demo.kai666666.com/21/02.html">此时效果如下</a>：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/3.gif" alt="射线法"></p><h2 id="分离轴定理"><a href="#分离轴定理" class="headerlink" title="分离轴定理"></a>分离轴定理</h2><p>现在回归我们的主题，我们的主题是多边形的碰撞检测。多边形的碰撞检测其实有好多种算法，比较经典的就是分离轴定理。著名的2D物理引擎 <a href="https://github.com/liabru/matter-js">matter.js</a> 的碰撞检测就是采用分离轴定理（SAT）。</p><p><strong>分离轴定理（Separating Axis Theorem，简称 SAT）</strong> 是计算机图形学、物理引擎和碰撞检测领域中一个极其重要且基础性的原理，主要用于判断两个凸多边形或凸多面体之间是否发生碰撞（相交）。我们这里是二维的所以就是凸多边形，凹多边形不可以直接使用分离轴定理，但可以切割成多个凸多边形，然后使用分离轴定理。分离轴定理的核心内容是：</p><blockquote><p>如果两个凸体（凸多边形或凸多面体）没有发生碰撞，那么必定存在一条直线（在 2D 中）或一个平面（在 3D 中），能够将这两个物体完全分隔开。这条直线或平面的法线方向，就称为分离轴。</p></blockquote><p>上面这句话也好理解，如果两个凸多边形（凸多面体）没有发生碰撞，那么必定存在一条直线（一个平面），能够将这两个凸多边形（凸多面体）分隔开。</p><p>根据分离轴定理的逆否命题，我们可以得到一个用于碰撞检测的充分必要条件：</p><blockquote><p>两个凸体（凸多边形或凸多面体）相交（碰撞），当且仅当两个凸体在所有的分离轴上的投影区间相交。如果存在在分离轴上的投影不相交的情况则不发生相交（碰撞）。</p></blockquote><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/4.png" alt="分离轴示意"></p><p>如图所示，两个多变形（三角形）和一条分离轴，多边形在分离轴上的投屏没有重合，则说明两个多边形没有发生碰撞。聪明的你此刻应该会挑选分离轴了，图中的是垂直于缝隙中的一边的分离轴，当然我们看一眼就知道选哪个边的垂直线（法线），但是计算机一眼看不出来，所以不防让多边形的所有边的法线作为分离轴来判断。</p><p>首先我们写一个辅助方法 <code>getEdgesNormals</code>，来获取多边形的所有边的法向量（垂直于边的向量）。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getEdgesNormals</span>(<span class="params">vertices</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> normals = [];</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; vertices.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> j = (i + <span class="number">1</span>) % vertices.<span class="property">length</span>;</span><br><span class="line">    <span class="keyword">const</span> edgeX = vertices[j].<span class="property">x</span> - vertices[i].<span class="property">x</span>;</span><br><span class="line">    <span class="keyword">const</span> edgeY = vertices[j].<span class="property">y</span> - vertices[i].<span class="property">y</span>;</span><br><span class="line">    <span class="comment">// 计算法向量</span></span><br><span class="line">    normals.<span class="title function_">push</span>(&#123; <span class="attr">x</span>: -edgeY, <span class="attr">y</span>: edgeX &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> normals;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述 <code>(edgeX, edgeY)</code> 获取到的是边的向量，所以 <code>(-edgeY, edgeX)</code> 就是对应的法向量（垂直于边的向量），因为原向量和法向量的 <strong>点积</strong> 是 <code>0</code> 。不明白什么是 <strong>点积</strong> 也没关系，我画了一个图，相信你一眼就看明白了：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/5.png" alt="求法向量"></p><p>当然 <code>(edgeY, -edgeX)</code> 也可以作为法向量，只是方向不同，我们这里只要取一个方向就行了。</p><p>我们再写一个辅助方法 <code>project</code>，用来计算多边形顶点到某条分离轴投影的最大值和最小值。通过顶点在分离轴上投影的最大值和最小值就可以确定多边形在分离轴上的投影范围。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">project</span>(<span class="params">vertices, axis</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> min = <span class="title class_">Infinity</span>;</span><br><span class="line">  <span class="keyword">let</span> max = -<span class="title class_">Infinity</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> vertex <span class="keyword">of</span> vertices) &#123;</span><br><span class="line">    <span class="comment">// 坐标轴原点到顶点的向量与轴向量的点积 就是顶点到轴的投影大小</span></span><br><span class="line">    <span class="keyword">const</span> dotProduct = vertex.<span class="property">x</span> * axis.<span class="property">x</span> + vertex.<span class="property">y</span> * axis.<span class="property">y</span>;</span><br><span class="line">    min = <span class="title class_">Math</span>.<span class="title function_">min</span>(min, dotProduct);</span><br><span class="line">    max = <span class="title class_">Math</span>.<span class="title function_">max</span>(max, dotProduct);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123; min, max &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来就是分离轴的核心方法了：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isCollisionPolygonPolygon</span>(<span class="params">vertices1, vertices2</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> axes1 = <span class="title function_">getEdgesNormals</span>(vertices1);</span><br><span class="line">  <span class="keyword">const</span> axes2 = <span class="title function_">getEdgesNormals</span>(vertices2);</span><br><span class="line">  <span class="keyword">const</span> allAxes = [...axes1, ...axes2];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> axis <span class="keyword">of</span> allAxes) &#123;</span><br><span class="line">    <span class="keyword">const</span> projection1 = <span class="title function_">project</span>(vertices1, axis);</span><br><span class="line">    <span class="keyword">const</span> projection2 = <span class="title function_">project</span>(vertices2, axis);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检查投影是否重叠</span></span><br><span class="line">    <span class="keyword">if</span> (!(projection1.<span class="property">max</span> &gt;= projection2.<span class="property">min</span> &amp;&amp; projection2.<span class="property">max</span> &gt;= projection1.<span class="property">min</span>)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 只要有一个轴不重叠，就没有碰撞</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 所有轴都重叠，发生碰撞</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码并不多，首先获取到2个多边形的边的法线，也就是分离轴。然后遍历所有分离轴，在每次循环中，分别对2个多边形的点的投影到这条分离轴上，找出两个图形投影的最大值和最小值，由于2个图案是在同一个分离轴上的投影，所以只要比较最大值和最小值的关系就可以判断是否发生重叠，进而知道多边形是否发生碰撞。当 <code>(projection1.max &gt;= projection2.min &amp;&amp; projection2.max &gt;= projection1.min)</code> 的时候表示重叠了，取反表示没有重叠，有不重叠的分离轴则说明没有发生碰撞。当然 <code>!(projection1.max &gt;= projection2.min &amp;&amp; projection2.max &gt;= projection1.min)</code> 不好理解的话，你可以可以写成 <code>(projection1.max &lt; projection2.min) || (projection2.max &lt; projection1.min)</code> , 也就是检测碰撞的两个多边形中其中一个投影的最大值小于另一个投影的最小值（投影是一维度的，这里可以想象成一条坐标轴上两个线段判断重叠的场景），则说明这个多边形投影的最大值和另一个多边形投影的最小值之间是有空隙，所以不存在重叠。不重叠的话就说明两个多边形没有发生相交（碰撞）。</p><p>上面的代码已经可以正常使用了，不过有的时候我们还希望知道 <code>交叉深度</code> 和 <code>分离轴向量</code>，以方便后续对碰撞处理，所以最好返回一个对象，把这两个信息也添加进去。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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">// SAT 碰撞检测函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isCollisionPolygonAndPolygon</span>(<span class="params">vertices1, vertices2</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> axes1 = <span class="title function_">getEdgesNormals</span>(vertices1);</span><br><span class="line">  <span class="keyword">const</span> axes2 = <span class="title function_">getEdgesNormals</span>(vertices2);</span><br><span class="line">  <span class="keyword">const</span> allAxes = [...axes1, ...axes2];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> minOverlap = <span class="title class_">Infinity</span>;</span><br><span class="line">  <span class="keyword">let</span> collisionAxis = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> axis <span class="keyword">of</span> allAxes) &#123;</span><br><span class="line">    <span class="keyword">const</span> projection1 = <span class="title function_">project</span>(vertices1, axis);</span><br><span class="line">    <span class="keyword">const</span> projection2 = <span class="title function_">project</span>(vertices2, axis);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> overlap = <span class="title class_">Math</span>.<span class="title function_">min</span>(projection1.<span class="property">max</span>, projection2.<span class="property">max</span>) - <span class="title class_">Math</span>.<span class="title function_">max</span>(projection1.<span class="property">min</span>, projection2.<span class="property">min</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (overlap &lt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">isColliding</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">depth</span>: <span class="number">0</span>,</span><br><span class="line">        <span class="attr">axis</span>: <span class="literal">null</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (overlap &lt; minOverlap) &#123;</span><br><span class="line">      minOverlap = overlap;</span><br><span class="line">      collisionAxis = axis;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">isColliding</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">depth</span>: minOverlap,</span><br><span class="line">    <span class="attr">axis</span>: collisionAxis</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://canvas-demo.kai666666.com/21/03.html">此时的效果如下</a>：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/6.gif" alt="多边形与多边形碰撞"></p><h2 id="圆与多边形的碰撞检测"><a href="#圆与多边形的碰撞检测" class="headerlink" title="圆与多边形的碰撞检测"></a>圆与多边形的碰撞检测</h2><p>在一个系统中使用多种碰撞检测往往比较复杂，圆与多边形的碰撞检测往往是把圆转换成多边形，然后进行多边形与多边形的碰撞检测。</p><p>我们给 <code>Ball</code> 类添加一个方法 <code>getVertices</code>，用来获取圆的多边形顶点，这里把圆当做正多边形来处理，如果顶点数是奇数则加1，使之成为偶数正多边形，毕竟偶数正多边形更像圆。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Ball</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> = options.<span class="property">x</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> = options.<span class="property">y</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radius</span> = options.<span class="property">radius</span> || <span class="number">20</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">color</span> = options.<span class="property">color</span> || <span class="string">&#x27;#000&#x27;</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> = options.<span class="property">vx</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> = options.<span class="property">vy</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ax</span> = options.<span class="property">ax</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ay</span> = options.<span class="property">ay</span> || <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> += <span class="variable language_">this</span>.<span class="property">ax</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> += <span class="variable language_">this</span>.<span class="property">ay</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">save</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">beginPath</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="property">fillStyle</span> = <span class="variable language_">this</span>.<span class="property">color</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">arc</span>(<span class="variable language_">this</span>.<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">y</span>, <span class="variable language_">this</span>.<span class="property">radius</span>, <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span> * <span class="number">0</span>, <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span> * <span class="number">360</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">closePath</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">fill</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">restore</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">isContainsPoint</span>(<span class="params">x,y</span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">hypot</span>(<span class="variable language_">this</span>.<span class="property">x</span> - x ,<span class="variable language_">this</span>.<span class="property">y</span> - y) &lt; <span class="variable language_">this</span>.<span class="property">radius</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">getVertices</span> (maxSides = <span class="number">25</span>) &#123;</span><br><span class="line">    <span class="comment">// 若边数为奇数则加 1 使其具有偶数边</span></span><br><span class="line">    <span class="keyword">const</span> numSides = maxSides % <span class="number">2</span> === <span class="number">1</span> ? maxSides + <span class="number">1</span> : maxSides;</span><br><span class="line">    <span class="keyword">const</span> vertices = [];</span><br><span class="line">    <span class="keyword">const</span> angleStep = (<span class="title class_">Math</span>.<span class="property">PI</span> * <span class="number">2</span>) / numSides;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; numSides; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> angle = i * angleStep;</span><br><span class="line">      <span class="keyword">const</span> vertexX = <span class="variable language_">this</span>.<span class="property">x</span> + <span class="variable language_">this</span>.<span class="property">radius</span> * <span class="title class_">Math</span>.<span class="title function_">cos</span>(angle);</span><br><span class="line">      <span class="keyword">const</span> vertexY = <span class="variable language_">this</span>.<span class="property">y</span> + <span class="variable language_">this</span>.<span class="property">radius</span> * <span class="title class_">Math</span>.<span class="title function_">sin</span>(angle);</span><br><span class="line">      vertices.<span class="title function_">push</span>(&#123; <span class="attr">x</span>: vertexX, <span class="attr">y</span>: vertexY &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> vertices;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码中 <code>getVertices</code> 中，边数 <code>maxSides</code> 过小的时候误差比较大，过大的时候误差较小，但性能变差了，这里默认值是 <code>25</code>（实际是 <code>26</code> 边形）。之所以取 <code>25</code> 是因为 <code>matter.js</code> 取的就是 <code>25</code> 。</p><p>判断的时候就可以把圆当做多边形来处理的代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">isCollisionPolygonAndPolygon</span>(boll.<span class="title function_">getVertices</span>(), polygon2.<span class="title function_">getVertices</span>()).<span class="property">isColliding</span></span><br></pre></td></tr></table></figure><p><a href="https://canvas-demo.kai666666.com/21/04.html">此时的效果如下</a>：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/7.gif" alt="圆与多边形碰撞"></p><h2 id="凹多边形的碰撞检测"><a href="#凹多边形的碰撞检测" class="headerlink" title="凹多边形的碰撞检测"></a>凹多边形的碰撞检测</h2><p>我们看一下凹多边形使用分离轴定理的结果，<a href="https://canvas-demo.kai666666.com/21/05.html">效果如下</a>：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/8.gif" alt="凹多边形碰撞"></p><p>处理凹多边形的碰撞是拆分成多个凸多边形，然后进行碰撞检测，一般我们会通过 <strong>耳切法</strong> 把凹多边形切割成多个三角形（凸多边形）来处理。</p><p>凹多边形的碰撞检测作为了解即可，这里就不细讲了，直接给出代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 计算两点间向量叉积</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">crossProduct</span>(<span class="params">p1, p2, p3</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (p2.<span class="property">x</span> - p1.<span class="property">x</span>) * (p3.<span class="property">y</span> - p1.<span class="property">y</span>) - (p2.<span class="property">y</span> - p1.<span class="property">y</span>) * (p3.<span class="property">x</span> - p1.<span class="property">x</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 耳切法将凹多边形分解为三角形（凸多边形）</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">earClipping</span>(<span class="params">vertices</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> triangles = [];</span><br><span class="line">  <span class="keyword">const</span> remainingVertices = [...vertices];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (remainingVertices.<span class="property">length</span> &gt;= <span class="number">3</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; remainingVertices.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> a = remainingVertices[(i - <span class="number">1</span> + remainingVertices.<span class="property">length</span>) % remainingVertices.<span class="property">length</span>];</span><br><span class="line">      <span class="keyword">const</span> b = remainingVertices[i];</span><br><span class="line">      <span class="keyword">const</span> c = remainingVertices[(i + <span class="number">1</span>) % remainingVertices.<span class="property">length</span>];</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 判断是否为凸耳</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="title function_">crossProduct</span>(a, b, c) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> isEar = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">0</span>; j &lt; remainingVertices.<span class="property">length</span>; j++) &#123;</span><br><span class="line">          <span class="keyword">if</span> (j !== i &amp;&amp; j !== (i - <span class="number">1</span> + remainingVertices.<span class="property">length</span>) % remainingVertices.<span class="property">length</span> &amp;&amp; j !== (i + <span class="number">1</span>) % remainingVertices.<span class="property">length</span>) &#123;</span><br><span class="line">            <span class="keyword">const</span> point = remainingVertices[j];</span><br><span class="line">            <span class="comment">// 判断点是否在三角形内</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="title function_">isPointInTriangle</span>(point, a, b, c)) &#123;</span><br><span class="line">              isEar = <span class="literal">false</span>;</span><br><span class="line">              <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (isEar) &#123;</span><br><span class="line">          triangles.<span class="title function_">push</span>([a, b, c]);</span><br><span class="line">          remainingVertices.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> triangles;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断点是否在三角形内 也可以使用上面射线法的代码</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isPointInTriangle</span>(<span class="params">p, a, b, c</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> areaABC = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title function_">crossProduct</span>(a, b, c));</span><br><span class="line">  <span class="keyword">const</span> areaPAB = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title function_">crossProduct</span>(p, a, b));</span><br><span class="line">  <span class="keyword">const</span> areaPBC = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title function_">crossProduct</span>(p, b, c));</span><br><span class="line">  <span class="keyword">const</span> areaPCA = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title function_">crossProduct</span>(p, c, a));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">abs</span>(areaABC - (areaPAB + areaPBC + areaPCA)) &lt; <span class="number">1e-9</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改后的 SAT 函数，支持凹多边形</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isCollisionConcavePolygon</span>(<span class="params">vertices1, vertices2</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> convexPolygons1 = <span class="title function_">earClipping</span>(vertices1);</span><br><span class="line">  <span class="keyword">const</span> convexPolygons2 = <span class="title function_">earClipping</span>(vertices2);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> minDepth = <span class="title class_">Infinity</span>;</span><br><span class="line">  <span class="keyword">let</span> collisionAxis = <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">let</span> isColliding = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> poly1 <span class="keyword">of</span> convexPolygons1) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> poly2 <span class="keyword">of</span> convexPolygons2) &#123;</span><br><span class="line">      <span class="keyword">const</span> collisionResult = <span class="title function_">isCollisionPolygonAndPolygon</span>(poly1, poly2);</span><br><span class="line">      <span class="keyword">if</span> (collisionResult.<span class="property">isColliding</span>) &#123;</span><br><span class="line">        isColliding = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (collisionResult.<span class="property">depth</span> &lt; minDepth) &#123;</span><br><span class="line">          minDepth = collisionResult.<span class="property">depth</span>;</span><br><span class="line">          collisionAxis = collisionResult.<span class="property">axis</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">isColliding</span>: isColliding,</span><br><span class="line">    <span class="attr">depth</span>: isColliding ? minDepth : <span class="number">0</span>,</span><br><span class="line">    <span class="attr">axis</span>: isColliding ? collisionAxis : <span class="literal">null</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://canvas-demo.kai666666.com/21/06.html">此时的效果如下</a>：</p><p><img src="/2025/07/21/Canvas%E7%B3%BB%E5%88%97%EF%BC%8821%EF%BC%89%EF%BC%9A%E5%A4%9A%E8%BE%B9%E5%BD%A2%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/9.gif" alt="凹多边形的碰撞检测"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;之前的文章我们做过写过一篇&lt;a href=&quot;https://www.kai666666.com/2021/03/05/Canvas%E7%B3%BB%E5%88%97%EF%BC%8817%EF%BC%89%EF%BC%9A%E7%A2%B0%E6%92%9E%E6%A3%</summary>
      
    
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/categories/Canvas/"/>
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/tags/Canvas/"/>
    
    <category term="射线法" scheme="https://www.kai666666.com/tags/%E5%B0%84%E7%BA%BF%E6%B3%95/"/>
    
    <category term="分离轴定理" scheme="https://www.kai666666.com/tags/%E5%88%86%E7%A6%BB%E8%BD%B4%E5%AE%9A%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>Canvas系列（20）：画布中画满圆</title>
    <link href="https://www.kai666666.com/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/"/>
    <id>https://www.kai666666.com/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/</id>
    <published>2025-02-08T15:24:29.000Z</published>
    <updated>2026-02-10T08:00:34.368Z</updated>
    
    <content type="html"><![CDATA[<p>今天的内容比较简单，我们学习如何在画布中画满圆。要求圆与圆之间不能相交，最终效果如下。</p><p><img src="/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/1.jpg" alt="最终效果"></p><hr><h2 id="HTML结构"><a href="#HTML结构" class="headerlink" title="HTML结构"></a>HTML结构</h2><p>首先我们先展示我们基础的HTML结构。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-id">#canvas</span>&#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="number">#eeeeee</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#000000</span>;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">id</span>=<span class="string">&quot;canvas&quot;</span> <span class="attr">width</span>=<span class="string">&quot;600&quot;</span> <span class="attr">height</span>=<span class="string">&quot;400&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里绘制一个600*400的画布，并设置背景色为灰色，边框为1px的黑色。</p><h2 id="绘制一个圆"><a href="#绘制一个圆" class="headerlink" title="绘制一个圆"></a>绘制一个圆</h2><p>我们从简单的开始，先绘制一个圆。由于我们这里不需要动画，所以不需要 <code>update</code> 方法，只需要 <code>draw</code> 方法即可。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;canvas&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> context = canvas.<span class="title function_">getContext</span>(<span class="string">&#x27;2d&#x27;</span>)</span><br><span class="line"></span><br><span class="line">context.<span class="property">lineWidth</span> = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Circle</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> = options.<span class="property">x</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> = options.<span class="property">y</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radius</span> = options.<span class="property">radius</span> || minRadius</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    context.<span class="title function_">beginPath</span>()</span><br><span class="line">    context.<span class="title function_">arc</span>(<span class="variable language_">this</span>.<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">y</span>, <span class="variable language_">this</span>.<span class="property">radius</span>, <span class="number">0</span>, <span class="number">2</span> * <span class="title class_">Math</span>.<span class="property">PI</span>)</span><br><span class="line">    context.<span class="title function_">stroke</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> circle = <span class="keyword">new</span> <span class="title class_">Circle</span>(&#123;</span><br><span class="line">  <span class="attr">x</span>: canvas.<span class="property">width</span> / <span class="number">2</span>,</span><br><span class="line">  <span class="attr">y</span>: canvas.<span class="property">height</span> / <span class="number">2</span>,</span><br><span class="line">  <span class="attr">radius</span>: <span class="number">100</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">circle.<span class="title function_">draw</span>()</span><br></pre></td></tr></table></figure><p>我们在画布的中心绘制了1个半径为100px的圆。<a href="https://canvas-demo.kai666666.com/20/01.html">效果如下</a>：</p><p><img src="/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/2.png" alt="画一个圆"></p><h2 id="绘制500个圆"><a href="#绘制500个圆" class="headerlink" title="绘制500个圆"></a>绘制500个圆</h2><p>上面，我们绘制了1个圆，现在绘制500个圆。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> minRadius = <span class="number">2</span></span><br><span class="line"><span class="keyword">const</span> maxRadius = <span class="number">120</span></span><br><span class="line"><span class="keyword">const</span> totalCircles = <span class="number">500</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createAndDrawCircle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> newCircle = <span class="keyword">new</span> <span class="title class_">Circle</span>(&#123;</span><br><span class="line">    <span class="attr">x</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">width</span>),</span><br><span class="line">    <span class="attr">y</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">height</span>),</span><br><span class="line">    <span class="attr">radius</span>: <span class="number">10</span></span><br><span class="line">  &#125;)</span><br><span class="line">  newCircle.<span class="title function_">draw</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; totalCircles; i++) &#123;</span><br><span class="line">  <span class="title function_">createAndDrawCircle</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码在随机位置生成了500个半径是10px的小圆，<a href="https://canvas-demo.kai666666.com/20/02.html">效果如下</a>：</p><p><img src="/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/3.jpg" alt="画500个圆"></p><h2 id="绘制500个不相交的圆"><a href="#绘制500个不相交的圆" class="headerlink" title="绘制500个不相交的圆"></a>绘制500个不相交的圆</h2><p>上面绘制了500个圆，他们可能是相交的，现在我们绘制500个不相交的圆。<br>我们上面绘制的随机坐标很可能在某个圆内，为了避免这种情况，我们需要先判断圆心坐标是否在某个圆内。如果在某个圆内我们需要舍弃这个坐标，重新生成一个新坐标；如果不在某个圆内，那么我们可以在这里绘制圆。到底绘制多大圆呢？如果绘制的太大了则可能与旁边的圆相交或者超出画布边界，我们这里绘制一个尽可大的圆，尽可能大意味着<strong>刚好与其他圆或者边界相切</strong>。如果绘制一个这样的大圆呢？这里可以先使用很小的圆来计算，让其半径慢慢增大，当增大到刚好与某个圆或者边界相切时，则绘制它。如果一直不相切，我们最好给一个圆的最大半径，这样可以保证圆不会太大。</p><p><strong>算法：</strong></p><ol><li>生成随机圆心坐标；</li><li>判断圆心坐标是否在某个圆内，如果在某个圆内，则舍弃这个坐标，重新生成一个新坐标（最坏情况下，可能一直没有符合要求的新坐标，则应该给限制，如最多尝试生成新坐标的500次）；</li><li>如果圆心坐标不在某个圆内，则让圆的半径逐渐增大，从而找到最大刚好跟其它圆或边界相切的最大圆；</li><li>绘制圆；</li><li>重复1~4步，直到所有圆绘制完毕（我们这里限定最多绘制500个圆）。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> circles = []</span><br><span class="line"><span class="keyword">const</span> createCircleAttempts = <span class="number">500</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createAndDrawCircle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> newCircle</span><br><span class="line">  <span class="keyword">let</span> circleSafeToDraw = <span class="literal">false</span></span><br><span class="line">  <span class="comment">// 最多尝试500次寻找圆外坐标</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> tries = <span class="number">0</span>; tries &lt; createCircleAttempts; tries++) &#123;</span><br><span class="line">    <span class="comment">// 步骤1，生成随机圆心坐标</span></span><br><span class="line">    newCircle = <span class="keyword">new</span> <span class="title class_">Circle</span>(&#123;</span><br><span class="line">      <span class="attr">x</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">width</span>),</span><br><span class="line">      <span class="attr">y</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">height</span>),</span><br><span class="line">      <span class="attr">radius</span>: <span class="number">10</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤2，判断圆心坐标是否在某个圆内 如果与其他圆或者边界相交则舍弃当前坐标，重新生成一个新坐标，不相交则标记可以绘制</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="title function_">doesCircleHaveACollision</span>(newCircle)) &#123;</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      circleSafeToDraw = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果循环500次都没有找到合适的新坐标则不绘制了</span></span><br><span class="line">  <span class="keyword">if</span>(!circleSafeToDraw) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 保存需要绘制的圆</span></span><br><span class="line">  circles.<span class="title function_">push</span>(newCircle)</span><br><span class="line">  <span class="comment">// 步骤4，绘制圆</span></span><br><span class="line">  newCircle.<span class="title function_">draw</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先我们定义 <code>circles</code> 数组用来存放生成好的圆，<code>createCircleAttempts</code> 表示最多尝试 <code>500</code> 次寻找其他圆外新坐标。上面代码结合注释还是比较好理解的，我们还没有实现 <code>doesCircleHaveACollision</code> 方法，下面我们来实现它。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">doesCircleHaveACollision</span>(<span class="params">circle</span>) &#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; circles.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> otherCircle = circles[i]</span><br><span class="line">    <span class="keyword">const</span> a = circle.<span class="property">radius</span> + otherCircle.<span class="property">radius</span></span><br><span class="line">    <span class="keyword">const</span> x = circle.<span class="property">x</span> - otherCircle.<span class="property">x</span></span><br><span class="line">    <span class="keyword">const</span> y = circle.<span class="property">y</span> - otherCircle.<span class="property">y</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (a &gt;= <span class="title class_">Math</span>.<span class="title function_">hypot</span>(x, y)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(circle.<span class="property">x</span> + circle.<span class="property">radius</span> &gt;= canvas.<span class="property">width</span> ||</span><br><span class="line">    circle.<span class="property">x</span> - circle.<span class="property">radius</span> &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(circle.<span class="property">y</span> + circle.<span class="property">radius</span> &gt;= canvas.<span class="property">height</span> ||</span><br><span class="line">      circle.<span class="property">y</span> - circle.<span class="property">radius</span> &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当调用 <code>doesCircleHaveACollision(circle)</code> 方法的时候，新圆并没有加入到 <code>circles</code> 数组中，所以我们只需要判断新圆是否与数组中的圆相交并判断是否与边界相交就可以了。圆与圆的相交可以通过圆心间的距离跟半径之和做比较来判断，圆与边界可以通过圆的坐标和半径跟上下左右边界的距离做比较来判断。对碰撞检测感兴趣的同学可以翻看<a href="https://www.kai666666.com/2021/03/05/Canvas%E7%B3%BB%E5%88%97%EF%BC%8817%EF%BC%89%EF%BC%9A%E7%A2%B0%E6%92%9E%E6%A3%80%E6%B5%8B/">之前的文章</a>。</p><p>此时<a href="https://canvas-demo.kai666666.com/20/03.html">效果如下</a>：</p><p><img src="/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/4.jpg" alt="绘制500个不相交的圆"></p><h2 id="绘制500个不相交且大小不等的圆"><a href="#绘制500个不相交且大小不等的圆" class="headerlink" title="绘制500个不相交且大小不等的圆"></a>绘制500个不相交且大小不等的圆</h2><p>上面我们少了第3步，只绘制了大小都是10px的圆，现在我们处理一下第3步，实际上也不复杂。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 最小半径</span></span><br><span class="line"><span class="keyword">const</span> minRadius = <span class="number">2</span></span><br><span class="line"><span class="comment">// 最大半径</span></span><br><span class="line"><span class="keyword">const</span> maxRadius = <span class="number">120</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createAndDrawCircle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> newCircle</span><br><span class="line">  <span class="keyword">let</span> circleSafeToDraw = <span class="literal">false</span></span><br><span class="line">  <span class="comment">// 最多尝试500次寻找圆外坐标</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> tries = <span class="number">0</span>; tries &lt; createCircleAttempts; tries++) &#123;</span><br><span class="line">    <span class="comment">// 步骤1，生成随机圆心坐标</span></span><br><span class="line">    newCircle = <span class="keyword">new</span> <span class="title class_">Circle</span>(&#123;</span><br><span class="line">      <span class="attr">x</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">width</span>),</span><br><span class="line">      <span class="attr">y</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">height</span>),</span><br><span class="line">      <span class="attr">radius</span>: minRadius, <span class="comment">// 这里初始化使用最小半径</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤2，判断圆心坐标是否在某个圆内 如果与其他圆或者边界相交则舍弃当前坐标，重新生成一个新坐标，不相交则标记可以绘制</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="title function_">doesCircleHaveACollision</span>(newCircle)) &#123;</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      circleSafeToDraw = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 如果循环500次都没有找到合适的新坐标则不绘制了</span></span><br><span class="line">  <span class="keyword">if</span>(!circleSafeToDraw) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 步骤3，让圆的半径逐渐增大，从而找到最大刚好跟其它圆或边界相切的圆</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> radiusSize = minRadius; radiusSize &lt; maxRadius; radiusSize++) &#123;</span><br><span class="line">    newCircle.<span class="property">radius</span> = radiusSize</span><br><span class="line">    <span class="comment">// 相切则跳出循环 由于描边宽度是2 所以需要减去1像素保证不相交</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="title function_">doesCircleHaveACollision</span>(newCircle))&#123;</span><br><span class="line">      newCircle.<span class="property">radius</span>--</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 保存需要绘制的圆</span></span><br><span class="line">  circles.<span class="title function_">push</span>(newCircle)</span><br><span class="line">  <span class="comment">// 步骤4，绘制圆</span></span><br><span class="line">  newCircle.<span class="title function_">draw</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时的效果跟刚开始的效果一下了，点击<a href="https://canvas-demo.kai666666.com/20/04.html">这里查看</a>。</p><h2 id="小优化"><a href="#小优化" class="headerlink" title="小优化"></a>小优化</h2><p>上面我们绘制圆的时候，由于第一个绘制的圆只受边界相交的限制，假设第一个圆的坐标在靠近中心的位置，就有很大概率绘制一个最大的圆，所以当你多次刷新网页的时候就会发现，通常有一个很大的圆，这样不是那么美观。我们现在让绘制的最大半径修改成 <code>圆最大半径</code> 和 <code>最小半径 + 1</code> 之间的一个随机值，这样可以避免每次都有一个极大圆存在。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createAndDrawCircle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 新的最大值为圆最小值和最大值之间的随机值</span></span><br><span class="line">  <span class="keyword">const</span> max = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * (maxRadius - minRadius - <span class="number">1</span>) + minRadius + <span class="number">1</span>)</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> radiusSize = minRadius; radiusSize &lt; max; radiusSize++) &#123;</span><br><span class="line">    <span class="comment">// ... 其他代码相同</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时<a href="https://canvas-demo.kai666666.com/20/05.html">效果如下</a>：</p><p><img src="/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E5%B8%83%E4%B8%AD%E7%94%BB%E6%BB%A1%E5%9C%86/5.jpg" alt="最终效果"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今天的内容比较简单，我们学习如何在画布中画满圆。要求圆与圆之间不能相交，最终效果如下。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2025/02/08/Canvas%E7%B3%BB%E5%88%97%EF%BC%8820%EF%BC%89%EF%BC%9A%E7%94%BB%E</summary>
      
    
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/categories/Canvas/"/>
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/tags/Canvas/"/>
    
  </entry>
  
  <entry>
    <title>Canvas系列（19）：实战-五彩纸屑</title>
    <link href="https://www.kai666666.com/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/"/>
    <id>https://www.kai666666.com/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/</id>
    <published>2025-01-16T20:20:20.000Z</published>
    <updated>2026-02-10T08:00:34.364Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.kai666666.com/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/">上一节</a>我们学习了如何通过 <code>Canvas</code> 来实现烟花效果，这节我们学习另一种效果 —— 五彩纸屑。具体效果如下：</p><p><img src="/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/1.gif" alt="五彩纸屑效果"></p><hr><h2 id="功能设计"><a href="#功能设计" class="headerlink" title="功能设计"></a>功能设计</h2><p><img src="/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/2.png" alt="示意图"></p><p>如上图所以，要实现五彩纸屑效果，需要在屏幕左右两侧向上发射粒子。上一节，我们放烟花时也发射了粒子，这里可以继续复用上节课粒子相关代码。上一节我们绘制的是圆形，这节课通过最终效果来看绘制的是椭圆。</p><p>首先我们先抽象出一个 <code>Confetti</code> 类，该类控制展示五彩纸屑，它拥有一个核心方法就是 <code>show</code>。有了 <code>Confetti</code> 类我们还需要渲染每一个例子，当然需要一个 <code>Particle</code> 类，该类似于烟花的粒子，我们抄一抄就行。当每次调用 <code>confetti.show()</code> 的时候需要创建一堆纸屑粒子，纸屑粒子朝着特定的方向发射，后面随着重力落下，整体流程差不多就是这样。这里考虑到一次创建上百个粒子直接由 <code>Confetti</code> 类来管理，<code>Confetti</code> 类做的事情稍微有点多，所以我们再抽象出一层，一般的粒子效果把这一层叫发射器 <code>Emitter</code>；出于业务考虑，我们这里就抽象出批次这么个概念，每次发生一批粒子，用 <code>ConfettiBatch</code> 类来表示。<code>Confetti</code> 类每次创建一批粒子，它可能同时渲染好几批粒子，而每一批粒子，又分别由左右两个部分，每一部分又有好多纸屑粒子，一个简易的类图原型我们就有了。当然这里我们还有一些没有考虑进去的地方，如动画主循环等，不过不影响我们在此基础上进行开发。</p><p><img src="/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/3.png" alt="类图"></p><h2 id="Particle-类"><a href="#Particle-类" class="headerlink" title="Particle 类"></a>Particle 类</h2><p>我们先从 <code>Particle</code> 类开始，它的代码跟上次的烟花的粒子几乎是一样的：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> = options.<span class="property">x</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> = options.<span class="property">y</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> = options.<span class="property">vx</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> = options.<span class="property">vy</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ax</span> = options.<span class="property">ax</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ay</span> = options.<span class="property">ay</span> || <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radius</span> = options.<span class="property">radius</span> || <span class="number">6</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radiusX</span> = <span class="variable language_">this</span>.<span class="property">radius</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radiusY</span> = <span class="variable language_">this</span>.<span class="property">radius</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">rotation</span> = options.<span class="property">rotation</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">hColor</span> = options.<span class="property">hColor</span> ?? <span class="number">180</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> += <span class="variable language_">this</span>.<span class="property">ax</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> += <span class="variable language_">this</span>.<span class="property">ay</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">save</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">beginPath</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="property">fillStyle</span> = <span class="string">`hsl(<span class="subst">$&#123;<span class="variable language_">this</span>.hColor&#125;</span> 100% 50%)`</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">ellipse</span>(<span class="variable language_">this</span>.<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">y</span>, <span class="variable language_">this</span>.<span class="property">radiusX</span>, <span class="variable language_">this</span>.<span class="property">radiusY</span>, <span class="variable language_">this</span>.<span class="property">rotation</span>, <span class="number">0</span>, <span class="number">2</span> * <span class="title class_">Math</span>.<span class="property">PI</span>)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">closePath</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">fill</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">restore</span>()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Particle</code> 类的核心逻辑是通过 <code>this.context.ellipse()</code> 方法绘制了一个椭圆。五彩纸屑粒子相比于烟花粒子多了一个 <code>rotation</code> 属性，用来控制粒子旋转的角度。<code>radius</code> 属性表示椭圆的半径，这里我们把它又拆分成 <code>radiusX</code> 和 <code>radiusY</code> 分别是椭圆 <code>X轴</code> 和 <code>Y轴</code> 的半径，当两者相同的时候椭圆就是一个圆形，后面我们通过修改 <code>radiusX</code> 和 <code>radiusY</code> 来显示椭圆。</p><h2 id="ConfettiBatch-类"><a href="#ConfettiBatch-类" class="headerlink" title="ConfettiBatch 类"></a>ConfettiBatch 类</h2><p><code>ConfettiBatch</code> 类用来处理一批粒子，包括左右两部分粒子。代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ConfettiBatch</span> &#123;</span><br><span class="line"></span><br><span class="line">  particles = []</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context;</span><br><span class="line">    <span class="keyword">const</span> number = options.<span class="property">number</span> || <span class="number">80</span>;</span><br><span class="line">    <span class="keyword">const</span> radius = options.<span class="property">radius</span> || <span class="number">6</span>;</span><br><span class="line">    <span class="keyword">const</span> canvasWidth = options.<span class="property">canvasWidth</span> || <span class="number">300</span>;</span><br><span class="line">    <span class="keyword">const</span> canvasHeight = options.<span class="property">canvasHeight</span> || <span class="number">150</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> leftParticles = <span class="keyword">new</span> <span class="title class_">Array</span>(number).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">map</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> speed = <span class="title function_">random</span>(<span class="number">8</span>, <span class="number">14</span>)</span><br><span class="line">      <span class="keyword">const</span> angle = <span class="title function_">random</span>(<span class="number">15</span>, <span class="number">82</span>) * <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span></span><br><span class="line">      <span class="keyword">const</span> absCos = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title class_">Math</span>.<span class="title function_">cos</span>(angle))</span><br><span class="line">      <span class="keyword">const</span> absSin = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title class_">Math</span>.<span class="title function_">sin</span>(angle))</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Particle</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">        <span class="attr">x</span>: -radius,</span><br><span class="line">        <span class="attr">y</span>: canvasHeight * <span class="number">5</span> / <span class="number">7</span>,</span><br><span class="line">        <span class="attr">vx</span>: speed * absCos,</span><br><span class="line">        <span class="attr">vy</span>: -speed * absSin,</span><br><span class="line">        <span class="attr">ax</span>: <span class="number">0</span>,</span><br><span class="line">        <span class="attr">ay</span>: <span class="number">0.2</span>,</span><br><span class="line">        <span class="attr">radius</span>: radius,</span><br><span class="line">        <span class="attr">rotation</span>: <span class="title function_">random</span>(<span class="number">0</span>, <span class="number">0.2</span>),</span><br><span class="line">        <span class="attr">hColor</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title function_">random</span>(<span class="number">360</span>))</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> rightParticles = <span class="keyword">new</span> <span class="title class_">Array</span>(number).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">map</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> speed = <span class="title function_">random</span>(<span class="number">8</span>, <span class="number">14</span>)</span><br><span class="line">      <span class="keyword">const</span> angle = <span class="title function_">random</span>(<span class="number">15</span>, <span class="number">82</span>) * <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span></span><br><span class="line">      <span class="keyword">const</span> absCos = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title class_">Math</span>.<span class="title function_">cos</span>(angle))</span><br><span class="line">      <span class="keyword">const</span> absSin = <span class="title class_">Math</span>.<span class="title function_">abs</span>(<span class="title class_">Math</span>.<span class="title function_">sin</span>(angle))</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Particle</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">        <span class="attr">x</span>: canvasWidth + radius,</span><br><span class="line">        <span class="attr">y</span>: canvasHeight * <span class="number">5</span> / <span class="number">7</span>,</span><br><span class="line">        <span class="attr">vx</span>: -speed * absCos,</span><br><span class="line">        <span class="attr">vy</span>: -speed * absSin,</span><br><span class="line">        <span class="attr">ax</span>: <span class="number">0</span>,</span><br><span class="line">        <span class="attr">ay</span>: <span class="number">0.2</span>,</span><br><span class="line">        <span class="attr">radius</span>: radius,</span><br><span class="line">        <span class="attr">rotation</span>: <span class="title function_">random</span>(-<span class="number">0</span>, <span class="number">2</span>, <span class="number">0</span>),</span><br><span class="line">        <span class="attr">hColor</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title function_">random</span>(<span class="number">360</span>))</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">particles</span> = leftParticles.<span class="title function_">concat</span>(rightParticles)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">particle</span> =&gt;</span> &#123;</span><br><span class="line">      particle.<span class="title function_">update</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">particle</span> =&gt;</span> &#123;</span><br><span class="line">      particle.<span class="title function_">draw</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ConfettiBatch</code> 类中我们创建了2个数组 <code>leftParticles</code> 和 <code>rightParticles</code> 分别表示左右两部分粒子，每一个数组默认有80个粒子。我们生成粒子的速度是 <code>8 ~ 12px</code> 的随机数，角度是 <code>15 ~ 82°</code>。由于左侧的粒子是朝右上角发射的，而右边的粒子是从左上角发射的，所以左侧粒子的 <code>vx</code> 的值是正数，右侧粒子的 <code>vx</code> 的值是负数。两者都是向上发射的，所以 <code>vy</code> 都是负数（<code>Canvas</code> y轴向下）。最后我们把两部分粒子放在 <code>particles</code> 数组中，以方便在更新和绘制的时候，一个循环就搞定了。</p><h2 id="Confetti-类"><a href="#Confetti-类" class="headerlink" title="Confetti 类"></a>Confetti 类</h2><p><code>Confetti</code> 类核心方法是 <code>show()</code> 方法，该方法用来创建一批粒子并启动动画循环。此外我们还添加 <code>clear()</code> 方法用来清空渲染的批次，<code>destroy()</code> 方法用来销毁 <code>Canvas</code>，代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Confetti</span> &#123;</span><br><span class="line">  batches = []</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">canvas</span> = options.<span class="property">canvas</span> || <span class="title function_">createCanvas</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="title function_">getContext</span>(<span class="string">&#x27;2d&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">show</span>(<span class="params">showOptions = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> canvasRect = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line">    <span class="keyword">const</span> canvasWidth = canvasRect.<span class="property">width</span></span><br><span class="line">    <span class="keyword">const</span> canvasHeight = canvasRect.<span class="property">height</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> batch = <span class="keyword">new</span> <span class="title class_">ConfettiBatch</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">      <span class="attr">number</span>: showOptions.<span class="property">number</span>,</span><br><span class="line">      <span class="attr">radius</span>: showOptions.<span class="property">radius</span>,</span><br><span class="line">      <span class="attr">canvasWidth</span>: canvasWidth,</span><br><span class="line">      <span class="attr">canvasHeight</span>: canvasHeight,</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">batches</span>.<span class="title function_">push</span>(batch)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">loop</span>()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">batches</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">batch</span> =&gt;</span> &#123;</span><br><span class="line">      batch.<span class="title function_">update</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">batches</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">batch</span> =&gt;</span> &#123;</span><br><span class="line">      batch.<span class="title function_">draw</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  loop = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> canvasWidth = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="property">canvasWidth</span></span><br><span class="line">    <span class="keyword">const</span> canvasHeight = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="property">offsetHeight</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvasWidth, canvasHeight)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">update</span>(canvasHeight)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">draw</span>()</span><br><span class="line"></span><br><span class="line">    <span class="title function_">requestAnimationFrame</span>(<span class="variable language_">this</span>.<span class="property">loop</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">clear</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">batches</span> = []</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">destroy</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">clear</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="title function_">remove</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> confetti = <span class="keyword">new</span> <span class="title class_">Confetti</span>()</span><br><span class="line">confetti.<span class="title function_">show</span>()</span><br></pre></td></tr></table></figure><p><code>Confetti</code> 类构造函数的参数中需要传一个 <code>canvas</code>，用来告诉我们需要绘制在哪里，但是更多时候我们需要绘制的是整个屏幕，此时就不需要再传 <code>canvas</code> 了，这里通过 <code>createCanvas()</code> 方法来创建一个全屏的不可交互的 <code>canvas</code>, 具体代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createCanvas</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;canvas&#x27;</span>)</span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&#x27;fixed&#x27;</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">width</span> = <span class="string">&#x27;100%&#x27;</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">height</span> = <span class="string">&#x27;100%&#x27;</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">top</span> = <span class="string">&#x27;0&#x27;</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">left</span> = <span class="string">&#x27;0&#x27;</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">zIndex</span> = <span class="string">&#x27;1000&#x27;</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">pointerEvents</span> = <span class="string">&#x27;none&#x27;</span></span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(canvas)</span><br><span class="line">  <span class="keyword">return</span> canvas</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时，你兴高采烈的运行代码，结果发现屏幕上什么都没有！到底发生什么事了？我们的代码哪里有 BUG ？我们看看 <code>createCanvas()</code> 方法，该方法通过CSS修改了 <code>canvas</code> 的大小，但是 <code>canvas</code> 高度实际上是默认的 <code>300px * 150px</code>，因为上面并没有通过 HTML 或者 JS 的方式设置宽高，原来问题出在这里。现在我们修复这个 BUG，让每次绘制的时候都将 <code>canvas</code> 的宽高设置成真实显示的 <code>Canvas</code> 宽高，具体代码如下： </p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">normalizeComputedStyleValue</span>(<span class="params">string</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> +string.<span class="title function_">replace</span>(<span class="regexp">/px/</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fixWidthAndHeight</span>(<span class="params">canvas</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> computedStyles = <span class="title function_">getComputedStyle</span>(canvas)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> width = <span class="title function_">normalizeComputedStyleValue</span>(computedStyles.<span class="title function_">getPropertyValue</span>(<span class="string">&#x27;width&#x27;</span>))</span><br><span class="line">  <span class="keyword">const</span> height = <span class="title function_">normalizeComputedStyleValue</span>(computedStyles.<span class="title function_">getPropertyValue</span>(<span class="string">&#x27;height&#x27;</span>))</span><br><span class="line"></span><br><span class="line">  canvas.<span class="title function_">setAttribute</span>(<span class="string">&#x27;width&#x27;</span>, width)</span><br><span class="line">  canvas.<span class="title function_">setAttribute</span>(<span class="string">&#x27;height&#x27;</span>, height)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Confetti</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">  loop = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 设置当前宽高为显示的宽高</span></span><br><span class="line">    <span class="title function_">fixWidthAndHeight</span>(<span class="variable language_">this</span>.<span class="property">canvas</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里获取到的是真实的宽高</span></span><br><span class="line">    <span class="keyword">const</span> canvasWidth = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="property">canvasWidth</span></span><br><span class="line">    <span class="keyword">const</span> canvasHeight = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="property">offsetHeight</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvasWidth, canvasHeight)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">update</span>(canvasHeight)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">draw</span>()</span><br><span class="line"></span><br><span class="line">    <span class="title function_">requestAnimationFrame</span>(<span class="variable language_">this</span>.<span class="property">loop</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里也可以考虑通过 <code>devicePixelRatio</code> 来根据设备像素比进行对 <code>Canvas</code> 缩放以保证更清晰的显示效果，由于我们这的文章主要内容是绘制五彩纸屑的思想，为了使代码更易懂就不考虑设备像素比了。由于我们在每次循环中都把 <code>Canvas</code> 的宽高设置为实际显示的宽高，所以这里也不需要像烟花的代码一样监听 <code>resize</code> 来处理视口的变化。</p><p>现在的<a href="https://canvas-demo.kai666666.com/19/01.html">效果如下</a>：</p><p><img src="/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/4.gif" alt="首轮效果"></p><h2 id="3D旋转粒子"><a href="#3D旋转粒子" class="headerlink" title="3D旋转粒子"></a>3D旋转粒子</h2><p>上面效果还是比较生硬的，没有纸片翻转的感觉。正常3D翻转如下，可用<a href="https://canvas-demo.kai666666.com/19/02.html">CSS轻松实现</a>。</p><p><img src="/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/5.gif" alt="3D旋转"></p><p>我们这里每一个粒子都需要像上面这样旋转，对于单个粒子（圆）来说，在3D旋转过程中半径是不变的，角度可以看成是线性变化的，所以高度可以通过三角函数来 <code>r * cos(θ)</code> 来计算。不过这里有一种更简单的做法来近似计算，就是线性修改椭圆的高度。虽然效果上来说并不是真正的3D旋转，但在较小的粒子上跟真实的3D旋转效果差距不大，而且计算量更小，所以我们这里通过线性修改 <code>radiusY</code> 的值来近似模拟粒子3D旋转效果。</p><p><code>Particle</code> 类我们新增了2个参数 <code>radiusYSpeed</code> 和 <code>rotationSpeed</code> 分别表示y轴半径变化的速度和，圆形旋转的速度，同时还新增了一个 <code>radiusYDirection</code> 属性（可选值<code>down</code>、<code>up</code>），用来表示当前y轴半径变化的方向，当值为 <code>down</code> 的时候，表示圆的Y轴半径变小；当值为 <code>up</code> 的时候，表示圆的Y轴半径变大。在 <code>update</code> 方法中我们通过 <code>radiusYDirection</code> 来修改 <code>radiusY</code> 的值，如果是 <code>down</code> 的时候，<code>radiusY</code> 每次减去它的速度直到小于0后反向，同样的当值为 <code>up</code> 的时候，<code>radiusY</code> 每次加上它的速度直到大于圆的半径后反向。如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="comment">// ... 其他代码相同</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">rotationSpeed</span> = options.<span class="property">rotationSpeed</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radiusYSpeed</span> = options.<span class="property">radiusYSpeed</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radiusYDirection</span> = <span class="string">&#x27;down&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> += <span class="variable language_">this</span>.<span class="property">ax</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> += <span class="variable language_">this</span>.<span class="property">ay</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">radiusYDirection</span> === <span class="string">&#x27;down&#x27;</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">radiusY</span> -= <span class="variable language_">this</span>.<span class="property">radiusYSpeed</span>;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">radiusY</span> &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">radiusY</span> = -<span class="variable language_">this</span>.<span class="property">radiusY</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">radiusYDirection</span> = <span class="string">&#x27;up&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">radiusY</span> += <span class="variable language_">this</span>.<span class="property">radiusYSpeed</span>;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">radiusY</span> &gt; <span class="variable language_">this</span>.<span class="property">radius</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">radiusY</span> = <span class="variable language_">this</span>.<span class="property">radius</span> - (<span class="variable language_">this</span>.<span class="property">radiusY</span> - <span class="variable language_">this</span>.<span class="property">radius</span>)</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">radiusYDirection</span> = <span class="string">&#x27;down&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">rotation</span> += <span class="variable language_">this</span>.<span class="property">rotationSpeed</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 其他代码相同</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ConfettiBatch</code> 类在创建粒子的时候也需要传递新增的参数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Particle</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">  <span class="attr">radiusYSpeed</span>: <span class="title function_">random</span>(<span class="number">0.5</span>, <span class="number">1.2</span>),</span><br><span class="line">  <span class="attr">rotationSpeed</span>: <span class="number">0.2</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>此时的效果已经很OK了，<a href="https://canvas-demo.kai666666.com/19/03.html">如下</a>：</p><p><img src="/2025/01/16/Canvas%E7%B3%BB%E5%88%97%EF%BC%8819%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E4%BA%94%E5%BD%A9%E7%BA%B8%E5%B1%91/6.gif" alt="第二版效果"></p><h2 id="清除已完成的粒子"><a href="#清除已完成的粒子" class="headerlink" title="清除已完成的粒子"></a>清除已完成的粒子</h2><p>上面粒子离开屏幕后我们并没有清除已完成的粒子，这样会造成性能下降，如果多调用几次 <code>confetti.show()</code> 将会越来越卡。现在我们需要清除已完成的粒子。</p><p>首先 <code>Particle</code> 添加一个 <code>isDone()</code> 方法，用来判断是否完成，这里认为超出 <code>Canvas</code> 高度 <code>100px</code> 后粒子就完成了自己的使命。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">  <span class="title function_">isDone</span>(<span class="params">canvasHeight</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">y</span> &gt; canvasHeight + <span class="number">100</span> <span class="comment">// 超出canvas高度100像素任务已完成</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在 <code>ConfettiBatch</code> 类中添加了 <code>clearDone()</code> 和 <code>isDone()</code> 方法。<code>clearDone()</code> 用来清除已经绘制完的粒子，本质就是通过 <code>particles.filter</code> 方法过滤掉已完成的粒子。这里需要传递 <code>canvasHeight</code>，是因为每一帧 <code>canvasHeight</code> 都可能会不同。<code>isDone()</code> 方法也比较简单，如果粒子都被清空则表示该批次已经完成自己的使命。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ConfettiBatch</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">clearDone</span>(<span class="params">canvasHeight</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">particles</span> = <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">filter</span>(<span class="function"><span class="params">particle</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> !particle.<span class="title function_">isDone</span>(canvasHeight)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">isDone</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> !<span class="variable language_">this</span>.<span class="property">particles</span>.<span class="property">length</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后在 <code>Confetti</code> 类中，也需要清理对应的批次，一个批次的粒子有挺多的，清空这一批次实际上是低概率的事情，我们没必要每一帧都去检测是否需要清空当前批次（当然如果每一帧去检测也可以），这里我们每10帧检测一次。代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Confetti</span> &#123;</span><br><span class="line">  batches = []</span><br><span class="line">  <span class="comment">// 用来记录迭代次数</span></span><br><span class="line">  iterationIndex = <span class="number">0</span></span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">clearDone</span>(<span class="params">canvasHeight</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">batches</span> = <span class="variable language_">this</span>.<span class="property">batches</span>.<span class="title function_">filter</span>(<span class="function"><span class="params">batch</span> =&gt;</span> &#123;</span><br><span class="line">      batch.<span class="title function_">clearDone</span>(canvasHeight)</span><br><span class="line">      <span class="keyword">return</span> !batch.<span class="title function_">isDone</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  loop = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">fixWidthAndHeight</span>(<span class="variable language_">this</span>.<span class="property">canvas</span>)</span><br><span class="line">    <span class="comment">// 每10帧重新开始记录</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">iterationIndex</span> = (++<span class="variable language_">this</span>.<span class="property">iterationIndex</span>) % <span class="number">10</span></span><br><span class="line">    <span class="keyword">const</span> isClear = <span class="variable language_">this</span>.<span class="property">iterationIndex</span> === <span class="number">0</span></span><br><span class="line">    <span class="keyword">const</span> canvasWidth = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="property">canvasWidth</span></span><br><span class="line">    <span class="keyword">const</span> canvasHeight = <span class="variable language_">this</span>.<span class="property">canvas</span>.<span class="property">offsetHeight</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvasWidth, canvasHeight)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">update</span>(canvasHeight)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">draw</span>()</span><br><span class="line">    <span class="keyword">if</span> (isClear) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">clearDone</span>(canvasHeight)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">requestAnimationFrame</span>(<span class="variable language_">this</span>.<span class="property">loop</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时的效果跟上面一样，可以<a href="https://canvas-demo.kai666666.com/19/04.html">点击这里查看</a>。</p><h2 id="主循环优化"><a href="#主循环优化" class="headerlink" title="主循环优化"></a>主循环优化</h2><p>我们之前的代码在调用 <code>confetti.show()</code> 的时候开启主循环，如果多次调用 <code>confetti.show()</code> 就会开启多个主循环，这肯定是不行的。现在我们处理一下这个问题。我们新增一个 <code>isRunning</code> 属性来标记是否开启主循环，<code>confetti.show()</code> 的时候如果没开启则开始主循环，否则说明主循环已经处于开启状态，就没必要再多次开启。另外当所有的批次都结束后应该关闭主循环，代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Confetti</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">  isRunning = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">show</span>(<span class="params">showOptions = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="comment">// ... 其他代码相同</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">isRunning</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">run</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">isRunning</span> = <span class="literal">true</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">loop</span>()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  loop = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// ... 其他代码相同</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// batches有长度的时候需要循环 没有长度的时候则不再循环</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">batches</span>.<span class="property">length</span>) &#123;</span><br><span class="line">      <span class="title function_">requestAnimationFrame</span>(<span class="variable language_">this</span>.<span class="property">loop</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">isRunning</span> = <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码相同</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>现在我们的五彩纸屑效果就完成了，具体效果可以<a href="https://canvas-demo.kai666666.com/19/05.html">点击这里</a>。</p><hr><p>今天的课程到这里就结束了，当然我们的五彩纸屑还可以添加更多的功能，比如添加一些方形的五彩纸屑，也添加一些 <code>emoji</code>；另外我们的水平速度和垂直速度都应该根据视口的宽高来生成，而不是直接给一个随机值。我相信通过本章的学习，聪明的你一定可以自己实现更好的效果。加油吧少年，愿你的生活能像五彩碎屑一样多姿多彩！! 😜</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.kai666666.com/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%</summary>
      
    
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/categories/Canvas/"/>
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/tags/Canvas/"/>
    
  </entry>
  
  <entry>
    <title>Canvas系列（18）：实战-烟花效果</title>
    <link href="https://www.kai666666.com/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/"/>
    <id>https://www.kai666666.com/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/</id>
    <published>2024-11-25T20:00:00.000Z</published>
    <updated>2026-02-10T08:00:34.356Z</updated>
    
    <content type="html"><![CDATA[<p>今天我们来学习 Canvas 的一个经典案例 —— 烟花效果，具体效果可以看下图。本章的内容会涉及之前的加速度和速度相关的知识，如果对这部分不太了解的建议先看<a href="https://www.kai666666.com/categories/Canvas/">其他章节</a>。</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/1.gif" alt="烟花效果"></p><hr><h2 id="画一个点"><a href="#画一个点" class="headerlink" title="画一个点"></a>画一个点</h2><p>Canvas效果往往需要我们去逐帧观察，烟花效果也一样。我们先看单个烟花，在不考虑拖尾的情况下，烟花先往上飞，飞行过程中越来越慢，当飞到一定高度以后爆炸开来，爆炸效果是变换成好多朝四面八法飞去的小粒子。我们直接绘制烟花有点困难，那么就把问题进行拆分，先不绘制整个烟花，而是先画一个点，这个点可以是起飞的烟花粒子，也可以是爆炸开的烟花粒子。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> = options.<span class="property">x</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> = options.<span class="property">y</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> = options.<span class="property">vx</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> = options.<span class="property">vy</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ax</span> = options.<span class="property">ax</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ay</span> = options.<span class="property">ay</span> || <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">radius</span> = options.<span class="property">radius</span> || <span class="number">4</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">hColor</span> = options.<span class="property">hColor</span> ?? <span class="number">180</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> += <span class="variable language_">this</span>.<span class="property">ax</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> += <span class="variable language_">this</span>.<span class="property">ay</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">save</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">beginPath</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="property">fillStyle</span> = <span class="string">`hsl(<span class="subst">$&#123;<span class="variable language_">this</span>.hColor&#125;</span> 100% 50%)`</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">arc</span>(<span class="variable language_">this</span>.<span class="property">x</span>, <span class="variable language_">this</span>.<span class="property">y</span>, <span class="variable language_">this</span>.<span class="property">radius</span>, <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span> * <span class="number">0</span>, <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span> * <span class="number">360</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">closePath</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">fill</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="title function_">restore</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码跟我们之前的动画章节的代码很类似。<code>Particle</code> 类接收2个参数，一个是 <code>canvas</code> 的上下文 <code>context</code>，另一个是配置项。其中 <code>vx</code> , <code>vy</code> 分别是 <code>x</code> 和 <code>y</code> 轴上的速度的分量；<code>ax</code>，<code>ay</code> 分别是 <code>x</code> 和 <code>y</code> 轴上的加速度的分量。我们绘制粒子，实际上绘制的是圆形，因为半径 <code>radius</code> 很小的圆形就是一个点，当然这里绘制成长度很小的方形也是OK的。值得注意的是我们绘制的 <code>fillStyle</code> 使用的是 <code>hsl</code> 颜色格式，而不是我们经常使用的 <code>rgb</code> 格式，因为 <code>hsl</code> 格式只要控制第一个色相参数就可以显示不同的颜色了，这比 <code>rgb</code> 方便些。另外构造方法中赋初始值的时候 <code>hColor</code> 使用了 <code>??</code> 表达式，为了保证即使传入 <code>0</code> 也能正常显示颜色。这是ES11的语法，生产环境需要用 <code>babel</code> 编译成低版本的语法，我们这里作为示例就不转换了，相信你用的是高版本的Chrome浏览器。</p><p>现在我们使用上面的 <code>Particle</code> 类来绘制一个点：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;canvas&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> context = canvas.<span class="title function_">getContext</span>(<span class="string">&#x27;2d&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> canvasWidth = canvas.<span class="property">width</span> = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span><br><span class="line"><span class="keyword">let</span> canvasHeight = canvas.<span class="property">height</span> = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> particle = <span class="keyword">new</span> <span class="title class_">Particle</span>(context, &#123;</span><br><span class="line">  <span class="attr">x</span>: canvasWidth / <span class="number">2</span>,</span><br><span class="line">  <span class="attr">y</span>: canvasHeight / <span class="number">2</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">particle.<span class="title function_">draw</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">resizeCanvas</span>(<span class="params"></span>) &#123;</span><br><span class="line">  canvasWidth = canvas.<span class="property">width</span> = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span><br><span class="line">  canvasHeight = canvas.<span class="property">height</span> = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 下面这三行代码为了保证粒子在Canvas的中心 后面的例子中会删除</span></span><br><span class="line">  particle.<span class="property">x</span> = canvasWidth / <span class="number">2</span>;</span><br><span class="line">  particle.<span class="property">y</span> = canvasHeight / <span class="number">2</span>;</span><br><span class="line">  particle.<span class="title function_">draw</span>()</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, resizeCanvas);</span><br></pre></td></tr></table></figure><p>这里我们在canvas的中心绘制了一个点，<a href="https://canvas-demo.kai666666.com/18/01.html">效果如下</a>：</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/2.png" alt="绘制一个点"></p><h2 id="画一个烟花"><a href="#画一个烟花" class="headerlink" title="画一个烟花"></a>画一个烟花</h2><p>在绘制烟花之前我们先写一个 <code>random</code> 的工具方法，用来生成随机数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">random</span> = (<span class="params">min = <span class="number">1</span>, max</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (max === <span class="literal">undefined</span>) &#123;</span><br><span class="line">    max = min;</span><br><span class="line">    min = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">random</span>() * (max - min) + min;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上述 <code>random</code> 如果传一个参数的时候，则会生成 <code>0 ~ 当前值</code> 的随机数，如果传两个参数的时候，第一个值表示最小值，第二个值表示最大值。这里我们生成的仍然是小数随机数，如果需要整数可以使用 <code>Math.floor()</code> 方法取整。</p><p>有了 <code>random</code> 函数以后我们可以画烟花了。烟花实际可以分成2部分，首先是一个从下到上飞的大一点的粒子，当飞到粒子最高点的时候，粒子爆炸，爆炸形成很多个从爆炸点周围向四周飞的小粒子。</p><p>通过上面描述我们的代码基本上就有了，首先我们定义一个 <code>Firework</code> 类，它有以下几个成员属性：</p><ul><li><code>isExplode</code>: 是否爆炸</li><li><code>baseParticle</code>: 未爆炸起飞的粒子 </li><li><code>particles</code>: 爆炸开的粒子数组</li><li><code>hColor</code>: 粒子颜色的hsl的色相参数</li></ul><p>我们在未爆炸前绘制的是 <code>baseParticle</code>，当爆炸以后就变成了绘制 <code>particles</code> 数组，这就是爆炸的核心逻辑。现在还有一个问题，如何判断粒子爆炸呢？其实也容易，当粒子飞到最顶端爆炸时，此时粒子的速度为 <code>0</code> ，而此前粒子的速度是负数（canvas的y轴向下，负数表示朝上），所以可以通过判断例子是否大于等于0来判断是否飞到最顶端，之所以不只判断等于 <code>0</code> ，是因为飞到最顶端的时候例子在这一帧可能不是刚好是 <code>0</code> 。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Firework</span> &#123;</span><br><span class="line">  isExplode = <span class="literal">false</span></span><br><span class="line">  baseParticle = <span class="literal">null</span></span><br><span class="line">  particles = []</span><br><span class="line">  hColor = <span class="number">180</span></span><br><span class="line">  context = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">hColor</span> = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title function_">random</span>(<span class="number">360</span>))</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">baseParticle</span> = <span class="keyword">new</span> <span class="title class_">Particle</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">      <span class="attr">x</span>: canvasWidth / <span class="number">2</span>,</span><br><span class="line">      <span class="attr">y</span>: canvasHeight,</span><br><span class="line">      <span class="attr">vy</span>: -<span class="title function_">random</span>(<span class="number">14</span>, <span class="number">20</span>),</span><br><span class="line">      <span class="attr">ay</span>: <span class="number">0.4</span>,</span><br><span class="line">      <span class="attr">radius</span>: <span class="number">2</span>,</span><br><span class="line">      <span class="attr">hColor</span>: <span class="variable language_">this</span>.<span class="property">hColor</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">isExplode</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="title function_">update</span>()</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="property">vy</span> &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">explode</span>()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">        item.<span class="title function_">update</span>()</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">isExplode</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="title function_">draw</span>()</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">        item.<span class="title function_">draw</span>()</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">explode</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// <span class="doctag">TODO:</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码中，我们在 <code>x</code> 为 <code>canvasWidth / 2</code> ，<code>y</code> 为 <code>canvasHeight</code> ，也就是中间的最下面生成一个向上飞的粒子。现在我们考虑一下如何让粒子爆炸，首先需要生成爆炸的粒子，然后标记 <code>isExplode</code> 为 <code>true</code>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Firework</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">explode</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> angle = <span class="title function_">random</span>(<span class="title class_">Math</span>.<span class="property">PI</span> * <span class="number">2</span>)</span><br><span class="line">      <span class="keyword">const</span> length = <span class="title function_">random</span>(<span class="number">3</span>)</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">push</span>(<span class="keyword">new</span> <span class="title class_">Particle</span>(context, &#123;</span><br><span class="line">        <span class="attr">x</span>: <span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="property">x</span>,</span><br><span class="line">        <span class="attr">y</span>: <span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="property">y</span>,</span><br><span class="line">        <span class="attr">vx</span>: <span class="title class_">Math</span>.<span class="title function_">cos</span>(angle) * length,</span><br><span class="line">        <span class="attr">vy</span>: <span class="title class_">Math</span>.<span class="title function_">sin</span>(angle) * length,</span><br><span class="line">        <span class="attr">ay</span>: <span class="number">0.1</span>,</span><br><span class="line">        <span class="attr">radius</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="attr">hColor</span>: <span class="variable language_">this</span>.<span class="property">hColor</span>,</span><br><span class="line">      &#125;))</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">isExplode</span> = <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里生成了 <code>100</code> 个爆炸后的粒子，爆炸的颜色跟当前烟花的颜色是相同的。生成的速度是朝四周方向，大小是随机生成小于 <code>3</code> 的向量，这里的计算可以参考下图。长度是<code>0 ~ 3</code> 的随机值，角度是 <code>0 ~ 2π</code>的随机值，然后计算 <code>x</code> 坐标和 <code>y</code> 坐标，可以看的出来 <code>x</code> 是 <code>Math.cos(angle)</code> ， <code>y</code> 是 <code>Math.sin(angle)</code> 。</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/3.png" alt="坐标计算示意图"></p><p>现在我们有了 <code>Firework</code> 类，就可以把它绘制到 <code>canvas</code> 上了：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> firework = <span class="keyword">new</span> <span class="title class_">Firework</span>(context);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">animate</span> (<span class="params"></span>)&#123;</span><br><span class="line">  <span class="title function_">requestAnimationFrame</span>(animate);</span><br><span class="line">  context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvas.<span class="property">width</span>, canvas.<span class="property">height</span>);</span><br><span class="line"></span><br><span class="line">  firework.<span class="title function_">update</span>();</span><br><span class="line">  firework.<span class="title function_">draw</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">animate</span>()</span><br></pre></td></tr></table></figure><p>此时的<a href="https://canvas-demo.kai666666.com/18/02.html">效果如下</a>：</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/4.gif" alt="画一个烟花"></p><h2 id="粒子爆炸后颜色逐渐变暗"><a href="#粒子爆炸后颜色逐渐变暗" class="headerlink" title="粒子爆炸后颜色逐渐变暗"></a>粒子爆炸后颜色逐渐变暗</h2><p><code>Particle</code> 类中我们添加了一个生命值的属性 <code>lifespan</code>，根据它的大小来显示爆炸后烟花的颜色，由于 <code>hsl</code> 颜色的第三个值刚好就是明亮度，我们之前使用的是 <code>50%</code> ，现在我们可以把明亮度的值 <code>lifespan</code> 从 <code>100 ~ 0</code> 映射到 <code>50% ~ 0%</code>，代码改动也不多，如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context, options = &#123;&#125;</span>)&#123;</span><br><span class="line">    <span class="comment">// ... 其他代码</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">isExplode</span> = options.<span class="property">isExplode</span> ?? <span class="literal">false</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lifespan</span> = <span class="number">100</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">isExplode</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">lifespan</span> -= <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ... 其他代码</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// ... 其他代码</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span>.<span class="property">fillStyle</span> = <span class="string">`hsl(<span class="subst">$&#123;<span class="variable language_">this</span>.hColor&#125;</span> 100% <span class="subst">$&#123;<span class="variable language_">this</span>.lifespan / <span class="number">2</span>&#125;</span>%)`</span>;</span><br><span class="line">    <span class="comment">// ... 其他代码</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Firework</span> &#123;</span><br><span class="line">  <span class="comment">//... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">explode</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">      <span class="comment">//... 其他代码</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">push</span>(<span class="keyword">new</span> <span class="title class_">Particle</span>(context, &#123;</span><br><span class="line">        <span class="comment">//... 其他代码</span></span><br><span class="line">        <span class="attr">isExplode</span>: <span class="literal">true</span>, <span class="comment">// 新增爆炸判断</span></span><br><span class="line">      &#125;))</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">isExplode</span> = <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时的<a href="https://canvas-demo.kai666666.com/18/03.html">效果如下</a>：</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/5.gif" alt="粒子爆炸后颜色逐渐变暗"></p><h2 id="画多个烟花"><a href="#画多个烟花" class="headerlink" title="画多个烟花"></a>画多个烟花</h2><p>首先我们把烟花的 <code>x</code> 坐标也修改成随机生成值，这里设置的范围是从 <code>20</code> 到 <code>canvasWidth - 20</code> 像素之间。由于是多个烟花，所以需要定义一个烟花的数组 <code>fireworks</code> 。生成烟花这里我们也是有技巧的，在每一帧生成一个随机数（默认从0到1），让屏幕宽度中每100个像素有 <code>0.006</code> 的概率生成一个烟花。具体代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Firework</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">hColor</span> = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title function_">random</span>(<span class="number">360</span>))</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">baseParticle</span> = <span class="keyword">new</span> <span class="title class_">Particle</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">      <span class="attr">x</span>: <span class="title function_">random</span>(<span class="number">20</span>, canvasWidth - <span class="number">20</span>),</span><br><span class="line">      <span class="comment">// ... 其他代码</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> fireworks = []</span><br><span class="line">fireworks.<span class="title function_">push</span>(<span class="keyword">new</span> <span class="title class_">Firework</span>(context));</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">animate</span> (<span class="params"></span>)&#123;</span><br><span class="line">  <span class="title function_">requestAnimationFrame</span>(animate);</span><br><span class="line">  context.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvas.<span class="property">width</span>, canvas.<span class="property">height</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">random</span>() &lt; (<span class="number">0.006</span> * canvasWidth / <span class="number">100</span>)) &#123;</span><br><span class="line">    fireworks.<span class="title function_">push</span>(<span class="keyword">new</span> <span class="title class_">Firework</span>(context));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  fireworks.<span class="title function_">forEach</span>(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">    item.<span class="title function_">update</span>();</span><br><span class="line">    item.<span class="title function_">draw</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">animate</span>()</span><br></pre></td></tr></table></figure><p>此时看着非常OK，<a href="https://canvas-demo.kai666666.com/18/04.html">效果如下</a>：</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/6.gif" alt="画多个烟花"></p><h2 id="内存泄漏处理"><a href="#内存泄漏处理" class="headerlink" title="内存泄漏处理"></a>内存泄漏处理</h2><p>上述代码看着貌似没什么问题，但运行的时间一长就会发现逐渐变得卡顿，这是因为每一个粒子在离开屏幕后仍然在绘制，从而导致内存泄露了，所以我们需要对已完成使命的粒子或烟花进行销毁。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">isDone</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">lifespan</span> &lt;= <span class="number">0</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Firework</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">isExplode</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="title function_">update</span>()</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">baseParticle</span>.<span class="property">vy</span> &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">explode</span>()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="property">length</span> - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">particles</span>[i].<span class="title function_">update</span>()</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">particles</span>[i].<span class="title function_">isDone</span>()) &#123;</span><br><span class="line">          <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="title function_">splice</span>(i, <span class="number">1</span>)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">isDone</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">isExplode</span> &amp;&amp; <span class="variable language_">this</span>.<span class="property">particles</span>.<span class="property">length</span> === <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">animate</span> (<span class="params"></span>)&#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = fireworks.<span class="property">length</span> - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">    <span class="keyword">const</span> item = fireworks[i];</span><br><span class="line">    item.<span class="title function_">update</span>();</span><br><span class="line">    <span class="keyword">if</span> (item.<span class="title function_">isDone</span>()) &#123;</span><br><span class="line">      fireworks.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      item.<span class="title function_">draw</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们在 <code>Particle</code> 和 <code>Firework</code> 类中都添加 <code>isDone</code> 方法来判断是否完成使命，粒子完成使命的标志是粒子完全暗，烟花完成使命的标志是爆炸了且所有爆炸后的粒子都完全变暗，最后需要注意的是移除粒子和烟花是从后往前循环的，避免因移除过程中数组长度变化而产生的问题。</p><p>此时效果跟上图几乎一样，只是不会因为时间过长而卡顿，具体效果可以<a href="https://canvas-demo.kai666666.com/18/05.html">点击这里</a>。</p><h2 id="爆炸后添加空气阻力"><a href="#爆炸后添加空气阻力" class="headerlink" title="爆炸后添加空气阻力"></a>爆炸后添加空气阻力</h2><p>现实中，烟花爆炸后比较轻，受到空气阻力的影响，烟花粒子在下降过程明显变慢了。阻力我们在之前的章节中也讲过，就是速度乘以一个接近1的系数，这块不熟悉的同学可以看<a href="https://www.kai666666.com/2019/06/30/Canvas%E7%B3%BB%E5%88%97%EF%BC%8811%EF%BC%89%EF%BC%9A%E5%8A%A8%E7%94%BB%E4%B8%AD%E7%BA%A7/">这里的摩擦力章节</a>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Particle</span> &#123;</span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line">  <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">isExplode</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">lifespan</span> -= <span class="number">2</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">vx</span> *= <span class="number">0.98</span>;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">vy</span> *= <span class="number">0.98</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vx</span> += <span class="variable language_">this</span>.<span class="property">ax</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vy</span> += <span class="variable language_">this</span>.<span class="property">ay</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ... 其他代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时，<a href="https://canvas-demo.kai666666.com/18/06.html">效果如下</a>：</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/7.gif" alt="爆炸后添加空气阻力"></p><h2 id="拖尾效果"><a href="#拖尾效果" class="headerlink" title="拖尾效果"></a>拖尾效果</h2><p>我们每次使用 <code>context.clearRect()</code> 方法来清屏，这里如果我们不调用 <code>context.clearRect()</code> 方法就会有粒子的飞行轨迹了，但是这也不是我们要的拖尾效果，不调用 <code>context.clearRect()</code> 方法效果如下：</p><p><img src="/2024/11/25/Canvas%E7%B3%BB%E5%88%97%EF%BC%8818%EF%BC%89%EF%BC%9A%E5%AE%9E%E6%88%98-%E7%83%9F%E8%8A%B1%E6%95%88%E6%9E%9C/8.png" alt="不调用context.clearRect()"></p><p>为了实现拖尾效果，我们可以在每次绘制之前先绘制一个透明度为 <code>0.2</code> 的黑色矩形，然后再绘制烟花，这样每次绘制保留了上一次淡淡的绘制效果，所以看上去就有了拖尾效果了，代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">animate</span> (<span class="params"></span>)&#123;</span><br><span class="line">  <span class="title function_">requestAnimationFrame</span>(animate);</span><br><span class="line">  <span class="comment">// context.clearRect(0, 0, canvas.width, canvas.height);</span></span><br><span class="line"></span><br><span class="line">  context.<span class="property">globalAlpha</span> = <span class="number">0.2</span>;</span><br><span class="line">  context.<span class="property">fillStyle</span> = <span class="string">&#x27;black&#x27;</span>;</span><br><span class="line">  context.<span class="title function_">fillRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvas.<span class="property">width</span>, canvas.<span class="property">height</span>);</span><br><span class="line">  context.<span class="property">globalAlpha</span> = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//... 其他代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>整体效果<a href="https://canvas-demo.kai666666.com/18/07.html">请看这里</a>。</p><h2 id="高度优化"><a href="#高度优化" class="headerlink" title="高度优化"></a>高度优化</h2><p>现在高度使用的是随机值，而设备的高度是不固定的，粒子飞行的高度跟设备的高度是有关系的，这样就可以让烟花不在很低或很高的时候爆炸。<br>烟花粒子向上飞的过程中，实际上是做自由落体运动的逆过程。自由落体公式有：</p><p>$$<br>v ^ 2 &#x3D; 2gh<br>$$</p><p>我们这里希望烟花飞过 0.5 ~ 0.9 屏幕高度后爆炸。之所以这么取是因为，烟花可以飞过屏幕的一半且不至于超出屏幕爆炸后才爆炸。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Firework</span> &#123;</span><br><span class="line">  <span class="comment">//... 其他代码</span></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">context</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = context</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">hColor</span> = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title function_">random</span>(<span class="number">360</span>))</span><br><span class="line">    <span class="keyword">const</span> ay = <span class="number">0.4</span> <span class="comment">// 加速度每个粒子都是一样的</span></span><br><span class="line">    <span class="keyword">const</span> h = <span class="title function_">random</span>(<span class="number">0.5</span> * canvasHeight, <span class="number">0.9</span> * canvasHeight) <span class="comment">// 随机 0.5 ~ 0.9 屏幕高度</span></span><br><span class="line">    <span class="keyword">const</span> vy = -<span class="title class_">Math</span>.<span class="title function_">sqrt</span>(<span class="number">2</span> * ay * h) <span class="comment">// 计算初始速度 由于方向是向上的 所以需要添加负号</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">baseParticle</span> = <span class="keyword">new</span> <span class="title class_">Particle</span>(<span class="variable language_">this</span>.<span class="property">context</span>, &#123;</span><br><span class="line">      <span class="attr">x</span>: <span class="title function_">random</span>(<span class="number">20</span>, canvasWidth - <span class="number">20</span>),</span><br><span class="line">      <span class="attr">y</span>: canvasHeight,</span><br><span class="line">      <span class="attr">vy</span>: vy,</span><br><span class="line">      ay,</span><br><span class="line">      <span class="attr">radius</span>: <span class="number">2</span>,</span><br><span class="line">      <span class="attr">hColor</span>: <span class="variable language_">this</span>.<span class="property">hColor</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>大功告成，此时的效果，<a href="https://canvas-demo.kai666666.com/18/08.html">请看这里</a>。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今天我们来学习 Canvas 的一个经典案例 —— 烟花效果，具体效果可以看下图。本章的内容会涉及之前的加速度和速度相关的知识，如果对这部分不太了解的建议先看&lt;a href=&quot;https://www.kai666666.com/categories/Canvas/&quot;&gt;其他章</summary>
      
    
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/categories/Canvas/"/>
    
    
    <category term="Canvas" scheme="https://www.kai666666.com/tags/Canvas/"/>
    
  </entry>
  
  <entry>
    <title>一文看懂TypeScript工具类型</title>
    <link href="https://www.kai666666.com/2024/06/29/%E4%B8%80%E6%96%87%E7%9C%8B%E6%87%82TypeScript%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%9E%8B/"/>
    <id>https://www.kai666666.com/2024/06/29/%E4%B8%80%E6%96%87%E7%9C%8B%E6%87%82TypeScript%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%9E%8B/</id>
    <published>2024-06-29T16:16:12.000Z</published>
    <updated>2026-02-10T08:00:34.408Z</updated>
    
    <content type="html"><![CDATA[<p><code>TypeScript</code> 的工具类型，也被称作类型体操。通过本文你就知道这些工具类型的原理，并可以自己写出一些工具类型。在学习工具类型之前，我们先学学工具类型所用到的基础知识，当基础知识掌握牢固后，看懂工具类型自然水到渠成。</p><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><h3 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h3><p>我们先看一个泛型的例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> identity&lt;T&gt;(<span class="attr">arg</span>: T): T &#123;</span><br><span class="line">  <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> a = identity&lt;<span class="built_in">string</span>&gt;(<span class="string">&#x27;foo&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> b = identity&lt;<span class="built_in">boolean</span>&gt;(<span class="literal">true</span>);</span><br><span class="line"><span class="keyword">const</span> c = identity&lt;<span class="built_in">string</span>&gt;(<span class="literal">true</span>); <span class="comment">// Error 类型不匹配</span></span><br><span class="line"><span class="keyword">const</span> d = <span class="title function_">identity</span>(<span class="literal">true</span>); <span class="comment">// OK 类型推断T为字面量类型true</span></span><br></pre></td></tr></table></figure><p>这个例子很简单，<code>identity</code> 函数接受一个名称为 <code>T</code> 泛型（名称可以随便写），函数参数的类型也是泛型 <code>T</code> ，返回值也是 <code>T</code>。在调用函数的时候，根据具体的使用场景来决定参数的类型，这就是泛型的作用。如果类型不匹配的话就会报错。</p><p>在泛型类型声明的时候泛型可以是多个，可以给默认值，有默认值的泛型参数是可选泛型参数，可选泛型参数需要放在泛型定义的后端。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> f1 &lt;T = <span class="built_in">boolean</span>, U&gt;() &#123;&#125; <span class="comment">// 错误 可选泛型应该放在后端</span></span><br><span class="line"><span class="keyword">function</span> f2&lt;T, U = <span class="built_in">boolean</span>&gt;() &#123;&#125; <span class="comment">// 正确</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> f3&lt;T = U, U = <span class="built_in">boolean</span>&gt;() &#123;&#125; <span class="comment">// 错误 给T设默认值U的时候 由于U在T后面定义 此时还未定义</span></span><br><span class="line"><span class="keyword">function</span> f4&lt;T = <span class="built_in">boolean</span>, U = T&gt;() &#123;&#125; <span class="comment">// 正确 U的默认值是T也就是boolean</span></span><br></pre></td></tr></table></figure><h3 id="泛型约束"><a href="#泛型约束" class="headerlink" title="泛型约束"></a>泛型约束</h3><p>泛型约束是在泛型的类型参数上定义一个约束条件，从而限制了泛型实际类型的最大范围，这个类型参数的约束条件就是泛型约束，语法采用了 <code>extends</code> 关键字，类似于类的继承。泛型约束是工具类型的核心。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Points</span> &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">y</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> identity&lt;T <span class="keyword">extends</span> <span class="title class_">Points</span>&gt;(<span class="attr">arg</span>: T): T &#123;</span><br><span class="line">  <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">identity</span>(&#123;<span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span>&#125;); <span class="comment">// OK</span></span><br><span class="line"><span class="title function_">identity</span>(&#123;<span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span>, <span class="attr">z</span>: <span class="number">0</span>&#125;); <span class="comment">// OK</span></span><br><span class="line"><span class="title function_">identity</span>(&#123;<span class="attr">x</span>: <span class="number">0</span>&#125;); <span class="comment">// Error 不满足约束条件 缺少y字段</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> f&lt;T <span class="keyword">extends</span> <span class="built_in">boolean</span>&gt;() &#123;&#125;</span><br><span class="line">f&lt;<span class="literal">true</span>&gt;(); <span class="comment">// OK</span></span><br><span class="line">f&lt;<span class="literal">false</span>&gt;(); <span class="comment">// OK</span></span><br><span class="line">f&lt;<span class="built_in">string</span>&gt;(); <span class="comment">// Error 类型不满足约束条件</span></span><br></pre></td></tr></table></figure><p>约束条件有点特殊，它可以引用泛型列表中的其他类型，但是不能循环引用。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> f1 &lt;T <span class="keyword">extends</span> U, U&gt;() &#123;&#125; <span class="comment">// OK 注意约束条件可以引用列表中的其他类型 哪怕前面的都可以引用后面的</span></span><br><span class="line"><span class="keyword">function</span> f2&lt;T, U <span class="keyword">extends</span> T&gt;() &#123;&#125; <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> f3 &lt;T <span class="keyword">extends</span> T&gt;() &#123;&#125; <span class="comment">// Error 循环引用</span></span><br><span class="line"><span class="keyword">function</span> f4&lt;T <span class="keyword">extends</span> U, U <span class="keyword">extends</span> T&gt;() &#123;&#125; <span class="comment">// Error 循环引用</span></span><br></pre></td></tr></table></figure><h3 id="泛型类型别名"><a href="#泛型类型别名" class="headerlink" title="泛型类型别名"></a>泛型类型别名</h3><p>泛型除了用在函数上还可以用在类和类型别名上。在工具类型中，泛型大量应用于类型别名上，看两个例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Nullable</span>&lt;T&gt; = T | <span class="literal">undefined</span> | <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Container</span>&lt;T&gt; = &#123; <span class="attr">value</span>: T &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="title class_">Nullable</span>&lt;<span class="built_in">string</span>&gt; = <span class="string">&#x27;123&#x27;</span>; <span class="comment">// a的类型是 string  | undefined | null</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="title class_">Container</span>&lt;<span class="built_in">boolean</span>&gt; = &#123; <span class="comment">// b的类型是 &#123; value: boolean &#125;</span></span><br><span class="line">  <span class="attr">value</span>: <span class="literal">true</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="联合类型"><a href="#联合类型" class="headerlink" title="联合类型"></a>联合类型</h3><p>联合类型就是类型用 <code>|</code> 操作符连起来的类型。联合类型赋值的时候是相联合类型的综合。子类型与父类型联合的结果是父类型，任何类型与<code>never</code>联合是任何类型。<strong>对于类、接口等对象的联合，可赋的值是其中任意一个，但访问只能访问所有类型的共有属性和方法。</strong>。从感觉上来看非对象联合范围可能变大了，但是对象的联合范围反而变小了。之所以这么做，是因为联合类型的对象在任何特定时刻只能符合其中的一个类型，因此 TypeScript 需要一种方法来确保你访问的属性在所有可能的类型中都是存在的。当然如果你通过类型判断推断出是具体的某个类型则可以调用对应的非共有方法。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基本类型的联合</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="built_in">number</span> | <span class="built_in">string</span>; <span class="comment">// 既可以是数字也可以是字符串</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="variable constant_">T1</span> = <span class="number">1</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="variable constant_">T1</span> = <span class="string">&#x27;1&#x27;</span>; <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 对象类型的联合</span></span><br><span class="line"><span class="keyword">interface</span> IA &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">b</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> IB &#123;</span><br><span class="line">  <span class="attr">b</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">c</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="variable constant_">IA</span> | <span class="variable constant_">IB</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: <span class="variable constant_">T2</span> = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>, <span class="comment">// OK a属于IA上的属性</span></span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>, <span class="comment">// OK b可以是string 也可以是number</span></span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span>, <span class="comment">// OK c属于IB上的属性 这里b=2是number的子类型 TS没推断出c:IA 所以这里没报错</span></span><br><span class="line">  <span class="attr">d</span>: <span class="number">4</span>, <span class="comment">// Error d不在IA也不在IB</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c.<span class="property">a</span>); <span class="comment">// Error 不能访问非共有属性</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c.<span class="property">b</span>); <span class="comment">// OK</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c.<span class="property">c</span>); <span class="comment">// Error 不能访问非共有属性</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="built_in">string</span> | <span class="string">&#x27;a&#x27;</span>; <span class="comment">// string 子类型于父类型联合是父类型</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="built_in">string</span> | <span class="built_in">never</span>; <span class="comment">// string 任何类型和never联合是任何类型 这个可以非常重要的哦</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = &#123; <span class="attr">kind</span>: <span class="string">&#x27;T5&#x27;</span>, <span class="attr">a</span>: <span class="built_in">number</span> &#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T6</span> = &#123; <span class="attr">kind</span>: <span class="string">&#x27;T6&#x27;</span>, <span class="attr">b</span>: <span class="built_in">string</span> &#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T7</span> = <span class="variable constant_">T5</span> | <span class="variable constant_">T6</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">d</span>: <span class="variable constant_">T7</span> = &#123;</span><br><span class="line">  <span class="attr">kind</span>: <span class="string">&#x27;T5&#x27;</span>, <span class="comment">// OK 可以判断是T5</span></span><br><span class="line">  <span class="attr">a</span>: <span class="number">123</span>, <span class="comment">// OK</span></span><br><span class="line">  <span class="attr">b</span>: <span class="string">&#x27;123&#x27;</span>, <span class="comment">// Error kind自变量可以推断出g是IE类型所以不能有b属性</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handle</span>(<span class="params"><span class="attr">d</span>: T7</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (d.<span class="property">kind</span> === <span class="string">&#x27;T5&#x27;</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(d.<span class="property">a</span>); <span class="comment">// `g` 确定是 `IE` 类型，访问 `a` 是安全的</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (d.<span class="property">kind</span> === <span class="string">&#x27;T6&#x27;</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(c.<span class="property">b</span>); <span class="comment">// `g` 确定是 `IF` 类型，访问 `b` 是安全的</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="交叉类型"><a href="#交叉类型" class="headerlink" title="交叉类型"></a>交叉类型</h3><p>交叉类型就是类型用 <code>&amp;</code> 操作符连起来的类型。子类型与父类型交叉的结果是子类型。对于没有交集类型的交叉则是 <code>never</code> 。对象类型的交叉是属性的综合。**<code>never</code> 、<code>null</code> 和 <code>undefined</code> 与其他类型交叉的结果是 <code>never</code> ，空对象类型与其他非<code>never</code>、<code>null</code>和<code>undefined</code>的类型交叉是其他类型**。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="built_in">boolean</span> &amp; <span class="literal">true</span>; <span class="comment">// true</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="built_in">boolean</span> &amp; <span class="literal">true</span> &amp; <span class="literal">false</span>; <span class="comment">// never 因为true和false没有交集</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="built_in">boolean</span> &amp; <span class="built_in">boolean</span> &amp; <span class="built_in">boolean</span>; <span class="comment">// boolean</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> IA &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> IB &#123;</span><br><span class="line">  <span class="attr">b</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="variable constant_">IA</span> &amp; <span class="variable constant_">IB</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// T4 相当于</span></span><br><span class="line"><span class="comment">// interface T4 &#123;</span></span><br><span class="line"><span class="comment">//   a: boolean;</span></span><br><span class="line"><span class="comment">//   b: boolean;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = <span class="built_in">string</span> &amp; <span class="literal">null</span>; <span class="comment">// never</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T6</span> = <span class="built_in">any</span> &amp; <span class="built_in">never</span>; <span class="comment">// never</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T7</span> = <span class="built_in">string</span> &amp; &#123;&#125;; <span class="comment">// string 空对象与非null/undefined/never类型交叉是对应的类型</span></span><br></pre></td></tr></table></figure><p>如果交叉的两个属性相同，那么他们的属性类型也是每个属性交叉的结果，看一个稍微复杂的例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> IA &#123;</span><br><span class="line">  <span class="attr">x</span>: &#123;<span class="attr">a</span>: <span class="built_in">string</span>, <span class="attr">b</span>: <span class="built_in">string</span>&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> IB &#123;</span><br><span class="line">  <span class="attr">x</span>: &#123;<span class="attr">b</span>: <span class="built_in">number</span>, <span class="attr">c</span>: <span class="built_in">number</span>&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="variable constant_">IA</span> &amp; <span class="variable constant_">IB</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 相当于：</span></span><br><span class="line"><span class="comment">// type T1 = &#123;</span></span><br><span class="line"><span class="comment">//   x: &#123;a: string, b: string&#125; &amp; &#123;b: number, c: number&#125;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 相当于：</span></span><br><span class="line"><span class="comment">// type T1 = &#123;</span></span><br><span class="line"><span class="comment">//   x: &#123;a: string, b: string &amp; number, c: number &#125;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 相当于：</span></span><br><span class="line"><span class="comment">// type T1 = &#123;</span></span><br><span class="line"><span class="comment">//   x: &#123;a: string, b: never, c: number &#125;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="分配率"><a href="#分配率" class="headerlink" title="分配率"></a>分配率</h3><p>交叉类似于数学中的乘法，联合类似于数据中的加法，所有交叉的优先级比联合优先级高，同时满足分配率。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">A &amp; B | C &amp; D = (A &amp; B) | (C &amp; D)</span><br><span class="line"></span><br><span class="line">A &amp; ( B | C ) = (A &amp; B) | (A &amp; C)</span><br><span class="line"></span><br><span class="line">(A | B) &amp; (C | D) = A &amp; C | A &amp; D | B &amp; C | B &amp; D</span><br><span class="line"></span><br><span class="line"><span class="comment">// 有了这些知识我们就知道更复杂的类型判断了，如：</span></span><br><span class="line"><span class="keyword">type</span> T = (<span class="built_in">string</span> | <span class="number">0</span>) &amp; (<span class="built_in">number</span> | <span class="string">&#x27;a&#x27;</span>);</span><br><span class="line">       = (<span class="built_in">string</span> &amp; <span class="built_in">number</span>) | (<span class="built_in">string</span> &amp; <span class="string">&#x27;a&#x27;</span>) | (<span class="number">0</span> &amp; <span class="built_in">number</span>) | (<span class="number">0</span> &amp; a);</span><br><span class="line">       = <span class="built_in">never</span> | <span class="string">&#x27;a&#x27;</span> | <span class="number">0</span> | <span class="built_in">never</span>;</span><br><span class="line">       = <span class="string">&#x27;a&#x27;</span> | <span class="number">0</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="索引类型查询"><a href="#索引类型查询" class="headerlink" title="索引类型查询"></a>索引类型查询</h3><p>通过索引类型查询（使用 <code>keyof</code> 关键字）能够获取给定类型中的属性名类型。索引类型查询的结果是由字符串字面量类型构成的联合类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> T &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">y</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">z</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = keyof T; <span class="comment">// &#x27;x&#x27; | &#x27;y&#x27; | &#x27;z&#x27;</span></span><br></pre></td></tr></table></figure><p>在JS中对象的键只能是字符串、数字和Symbol，所以 <code>keyof</code> 的结果必定是联合类型 <code>number | string | symbol</code> 的子类型。<code>keyof</code> 通常返回的是键的名称的联合类型，但有些情况比较特殊：</p><ol><li>属性中只有字符串索引签名（属性类型是<code>[props: string]: any</code>），返回<code>number | string</code>的联合类型；</li><li>属性中只有数值索引签名（属性类型是<code>[props: number]: any</code>），返回<code>number</code>类型；</li><li>属性中只包含<code>unique symbol</code>属性，返回的是对应的<code>unique symbol</code>类型；</li><li>有其他属性成员的，返回的是成员属性名的联合类型（这条重要）；</li><li><code>any</code> 返回的是<code>number | string | symbol</code>联合类型（通常用来做键的约束）；</li><li><code>unknown</code> 返回<code>never</code>;</li><li>原始类型返回对应对象上的属性或方法名；</li><li>联合类型返回公共属性名（这条重要）；</li><li>交叉类型返回所有属性名（这条重要）。</li></ol><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> I1 &#123;</span><br><span class="line">  [<span class="attr">props</span>: <span class="built_in">string</span>]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = keyof <span class="variable constant_">I1</span>; <span class="comment">// string | number</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> I2 &#123;</span><br><span class="line">  [<span class="attr">props</span>: <span class="built_in">number</span>]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = keyof <span class="variable constant_">I2</span>; <span class="comment">// number</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">s</span>: unique <span class="built_in">symbol</span> = <span class="title class_">Symbol</span>();</span><br><span class="line"><span class="keyword">interface</span> I3 &#123;</span><br><span class="line">  [s]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = keyof <span class="variable constant_">I3</span>; <span class="comment">// typeof s</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> I4 &#123;</span><br><span class="line">  <span class="number">0</span>: <span class="built_in">boolean</span>;</span><br><span class="line">  <span class="attr">a</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="title function_">b</span>(): <span class="built_in">string</span>;</span><br><span class="line">  [s]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = keyof <span class="variable constant_">I4</span>; <span class="comment">// 0 | &#x27;a&#x27; | &#x27;b&#x27; | s</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = keyof <span class="built_in">any</span>; <span class="comment">// string | number | symbol</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T6</span> = keyof <span class="built_in">unknown</span>; <span class="comment">// never</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T7</span> = keyof <span class="built_in">boolean</span>; <span class="comment">// &#x27;valueOf&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T8</span> = keyof <span class="built_in">string</span>; <span class="comment">// number | typeof Symbol.iterator | &quot;toString&quot; |  ...等string上的方法</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T9</span> = keyof <span class="built_in">number</span>; <span class="comment">// &#x27;toString&#x27; | &#x27;toFixed&#x27; | &#x27;toExponential&#x27; | &#x27;toPrecision&#x27; | &#x27;valueOf&#x27; | &#x27;toLocaleString&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> A = &#123;<span class="attr">a</span>: <span class="built_in">number</span>, <span class="attr">b</span>: <span class="built_in">number</span>&#125;;</span><br><span class="line"><span class="keyword">type</span> B = &#123;<span class="attr">b</span>: <span class="built_in">string</span>, <span class="attr">c</span>: <span class="built_in">string</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T10</span> = <span class="title function_">keyof</span> (A | B); <span class="comment">// &#x27;b&#x27; 注意 keyof 的优先级比 | 和 &amp; 高，所以这里要上括号</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T11</span> = <span class="title function_">keyof</span> (A &amp; B); <span class="comment">// &#x27;a&#x27; | &#x27;b&#x27; | &#x27;c&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">keyof</span> (A &amp; B) = keyof A | keyof B; <span class="comment">// keyof 也满足分配率</span></span><br></pre></td></tr></table></figure><h3 id="索引访问类型"><a href="#索引访问类型" class="headerlink" title="索引访问类型"></a>索引访问类型</h3><p>对象值的类型可以想对象一样获取：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = &#123;<span class="attr">x</span>: <span class="string">&#x27;1&#x27;</span>, [<span class="attr">props</span>: <span class="built_in">string</span>]: <span class="built_in">string</span>&#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="string">&#x27;x&#x27;</span>;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="variable constant_">T1</span>[<span class="variable constant_">T2</span>]; <span class="comment">// &#x27;1&#x27;</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="variable constant_">T1</span>[<span class="string">&#x27;y&#x27;</span>]; <span class="comment">// string</span></span><br></pre></td></tr></table></figure><h3 id="映射对象类型"><a href="#映射对象类型" class="headerlink" title="映射对象类型"></a>映射对象类型</h3><p>映射对象类型可以把已有对象类型映射为新的对象类型，映射对应类型使用 <code>in</code> 关键字，语法定义如下，其中 <code>readonly</code> 和 <code>?</code> 是可选的。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; <span class="keyword">readonly</span> [P <span class="keyword">in</span> K] ?: T &#125;</span><br></pre></td></tr></table></figure><p>看看例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = &#123; [P <span class="keyword">in</span> <span class="number">0</span> | <span class="number">1</span>] ?: <span class="built_in">string</span> &#125;;</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type T1 = &#123;</span></span><br><span class="line"><span class="comment">//     0?: string | undefined;</span></span><br><span class="line"><span class="comment">//     1?: string | undefined;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = &#123; [P <span class="keyword">in</span> <span class="string">&#x27;a&#x27;</span>] : <span class="built_in">string</span> &#125;;</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type T2 = &#123;</span></span><br><span class="line"><span class="comment">//     a: string;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = &#123; [P <span class="keyword">in</span> <span class="built_in">string</span>] : <span class="built_in">string</span> &#125;;</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type T3 = &#123;</span></span><br><span class="line"><span class="comment">//     [x: string]: string;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = &#123; [P <span class="keyword">in</span> <span class="built_in">number</span>] : <span class="built_in">string</span> &#125;;</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type T4 = &#123;</span></span><br><span class="line"><span class="comment">//     [x: number]: string;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = &#123; <span class="attr">a</span>: <span class="built_in">string</span>, <span class="attr">b</span>: <span class="built_in">number</span> &#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T6</span> = &#123; [P <span class="keyword">in</span> keyof <span class="variable constant_">T5</span>]: <span class="variable constant_">T5</span>[P] &#125;; <span class="comment">// 这里先用 keyof T5 获取到 &#x27;a&#x27; | &#x27;b&#x27; 再使用映射对象类型</span></span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type T6 = &#123;</span></span><br><span class="line"><span class="comment">//     a: string;</span></span><br><span class="line"><span class="comment">//     b: number;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="Partial-与-Readonly"><a href="#Partial-与-Readonly" class="headerlink" title="Partial 与 Readonly"></a>Partial 与 Readonly</h3><p>本文讲的是TS的工具类型，到目前为止我们还没有讲工具类型，现在我们看看第一个工具类型 <code>Partial&lt;T&gt;</code> 。<code>Partial&lt;T&gt;</code> 的作用是把对象类型的所有属性设置为可选的，可以使用映射对象类型来实现：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> T = &#123;<span class="attr">a</span>: <span class="built_in">string</span>, <span class="attr">b</span>: <span class="built_in">number</span>&#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">PartialT</span> = <span class="title class_">Partial</span>&lt;T&gt;; <span class="comment">// &#123;a?: string | undefined, b?: number | undefined&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 原理：通过索引访问类型添加?即可。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Partial</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T]?: T[P] | <span class="literal">undefined</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同样的 <code>Readonly&lt;T&gt;</code> 也跟 <code>Partial&lt;T&gt;</code> 的实现方式类似，<code>Readonly&lt;T&gt;</code> 是给每一个属性加一个 <code>readonly</code>：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> T = &#123;<span class="attr">a</span>: <span class="built_in">string</span>, <span class="attr">b</span>: <span class="built_in">number</span>&#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ReadonlyT</span> = <span class="title class_">Readonly</span>&lt;T&gt;; <span class="comment">// &#123;readonly a: string, readonly b: number &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 原理：与Partial类似只是添加的是 readonly</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Readonly</span>&lt;T&gt; = &#123;</span><br><span class="line">  <span class="keyword">readonly</span> [P <span class="keyword">in</span> keyof T]: T[P];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="同态映射对象类型"><a href="#同态映射对象类型" class="headerlink" title="同态映射对象类型"></a>同态映射对象类型</h3><p>我们对象类型中存在索引类型查询（即 <code>keyof</code>），这种映射就是同态映射，如果对象类型中没有索引类型查询就认为是非同态映射。同态映射会把原来对象的 <code>readonle</code>和 <code>?</code> 一并映射过来，所以是<code>同态</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> T = &#123;<span class="attr">a</span>?: <span class="built_in">string</span>; <span class="keyword">readonly</span> <span class="attr">b</span>: <span class="built_in">number</span>&#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">KT</span> = keyof T; <span class="comment">// KT是在MOT外面定义的 所以就是非同态</span></span><br><span class="line"><span class="comment">// 同态映射</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">HMOT</span> = &#123; [P <span class="keyword">in</span> keyof T]: T[P]&#125;; <span class="comment">// &#123; a?: string; readonly b: number;&#125;</span></span><br><span class="line"><span class="comment">// 非同态映射</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">MOT</span> = &#123; [P <span class="keyword">in</span> <span class="variable constant_">KT</span>]: T[P]&#125;; <span class="comment">// &#123; a: string | undefined; b: number; &#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>注：非同态映射对于可选参数来说，类型是与 <code>undefined</code> 的联合类型。</p></blockquote><p>同态映射对象类型只能保证映射的对象跟原有 <code>readonle</code> 和 <code>?</code> 对象操作符相同，当然可以自己写一个 <code>readonle</code> 或 <code>?</code> 覆盖调原来的操作符，但是不能随意控制减少。实际上在 <code>readonle</code> 和 <code>?</code> 前可以添加 <code>+</code> 和 <code>-</code> 来更精细化的控制。当然 <code>+readonle</code> 和 <code>readonle</code> 的效果实际上是一样的，所以 <code>+</code> 一般是省略的。</p><p>现在我们实现一下 <code>Required&lt;T&gt;</code>， 该工具类的作用就是去掉键值对中间的 <code>?</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> T = &#123;</span><br><span class="line">  <span class="attr">a</span>?: <span class="built_in">string</span> | <span class="literal">undefined</span> | <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">readonly</span> b : <span class="built_in">number</span> | <span class="literal">null</span> | <span class="literal">undefined</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">RequiredT</span> = <span class="title class_">Required</span>&lt;T&gt;;</span><br><span class="line"><span class="comment">// 相当于：</span></span><br><span class="line"><span class="comment">// type RequiredT = &#123;</span></span><br><span class="line"><span class="comment">//     a: string | null;</span></span><br><span class="line"><span class="comment">//     readonly b: number | null | undefined;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 原来：与Partial类似，通过-号去掉?号</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Required</span>&lt;T&gt; = &#123; [P <span class="keyword">in</span> keyof T]-?: T[P]; &#125;; <span class="comment">// 注意问好前目的-号</span></span><br></pre></td></tr></table></figure><p>注：<code>?</code> 前加 <code>-</code> 的时候，只对带有 <code>?</code> 的属性生效，且去掉 <code>undefined</code> 类型。对于没有 <code>?</code> 修饰的属性，不去掉<code>undefined</code> 类型。</p><p>映射也可以用在非对象类型:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="variable constant_">HMOT</span>&lt;T, X&gt; = &#123; [P <span class="keyword">in</span> keyof T]: X&#125;;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Type1</span> = <span class="variable constant_">HMOT</span>&lt;<span class="built_in">string</span>, <span class="built_in">boolean</span>&gt;; <span class="comment">// boolean</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Type2</span> = <span class="variable constant_">HMOT</span>&lt;<span class="built_in">string</span>[], <span class="built_in">boolean</span>&gt;; <span class="comment">// boolean[]</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Type3</span> = <span class="variable constant_">HMOT</span>&lt;[<span class="built_in">string</span>, <span class="built_in">number</span>], <span class="built_in">boolean</span>&gt;; <span class="comment">// [boolean, boolean]</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Type4</span> = <span class="variable constant_">HMOT</span>&lt;<span class="keyword">readonly</span> [<span class="built_in">string</span>, <span class="built_in">number</span>], <span class="built_in">boolean</span>&gt;; <span class="comment">// readonly [boolean, boolean]</span></span><br></pre></td></tr></table></figure><h3 id="条件类型"><a href="#条件类型" class="headerlink" title="条件类型"></a>条件类型</h3><p>条件类型类似于JS中的三目运算符，只是类型的判断没有JS那么丰富。TS的类型检验是在编译阶段进行的，所以判断条件只有 <code>extends</code> 一个语句。条件判断示例如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="literal">true</span> <span class="keyword">extends</span> <span class="built_in">boolean</span> ? <span class="built_in">string</span> : <span class="built_in">number</span>; <span class="comment">// string</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="number">1</span> <span class="keyword">extends</span> <span class="built_in">boolean</span> ? <span class="built_in">string</span> : <span class="built_in">number</span>; <span class="comment">// number</span></span><br></pre></td></tr></table></figure><p>条件表达式<code>extends</code>前面的类型经可能使用<code>裸类型</code>，所谓<code>裸类型</code>就是没有任何装饰的类型参数。如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="built_in">string</span> ? <span class="literal">true</span> : <span class="literal">false</span>; <span class="comment">// T没有任何装饰是裸类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span>&lt;T&gt; = [T] <span class="keyword">extends</span> [<span class="built_in">string</span>] ? <span class="literal">true</span> : <span class="literal">false</span>; <span class="comment">// [T]是T的一个元组，所以不是裸类型</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 乍一看好像也没毛病</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1T1</span> = <span class="variable constant_">T1</span>&lt;<span class="built_in">string</span>&gt;; <span class="comment">// true</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2T1</span> = <span class="variable constant_">T2</span>&lt;<span class="built_in">string</span>&gt;; <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="keyword">type</span> <span class="variable constant_">T1T2</span> = <span class="variable constant_">T1</span>&lt;<span class="built_in">string</span> | <span class="built_in">number</span>&gt;; <span class="comment">// boolean</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2T2</span> = <span class="variable constant_">T2</span>&lt;<span class="built_in">string</span> | <span class="built_in">number</span>&gt;; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>对于裸类型可以展开：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">T = A | B</span><br><span class="line">T <span class="keyword">extends</span> U ? X : Y</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line">(A <span class="keyword">extends</span> U ? X : Y) | (B <span class="keyword">extends</span> U ? X : Y)</span><br></pre></td></tr></table></figure><p>上例中 <code>T1T2</code> 是裸类型可以展开相当于 <code>(string extends string ? true : false) | (number extends string ? true : false)</code> 也就是 <code>true | false</code> 也就是 <code>boolean</code>, 但 <code>T2T2</code> 相当于 <code>[string | number] extends [string] ? true : false</code> , 但是元组 <code>[string | number]</code> 不是元组 <code>[string]</code>的子类型，所以是 <code>false</code>。通常裸类型可以展开更符合类型判断。</p><h3 id="类型查询"><a href="#类型查询" class="headerlink" title="类型查询"></a>类型查询</h3><p>在JS中 <code>typeof</code> 可以判断一个变量的类型，TS对 <code>typeof</code> 做了扩展，在类型别名 <code>type</code> 等号右侧的 <code>typeof</code> 获取的是变量在TS中定义的类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">string</span> = <span class="keyword">typeof</span> <span class="string">&#x27;123&#x27;</span>; <span class="comment">// &#x27;string&#x27;</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C</span> &#123;&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: C = <span class="keyword">new</span> <span class="title function_">C</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="keyword">typeof</span> <span class="string">&#x27;123&#x27;</span>; <span class="comment">// Error 需要是个变量 这里认为&#x27;123&#x27;已经是字面量类型了 无法查询该类型的类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="keyword">typeof</span> a; <span class="comment">// string</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="keyword">typeof</span> fn; <span class="comment">// () =&gt; void</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="keyword">typeof</span> C; <span class="comment">// 类C的构造函数类型（重要）</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = <span class="keyword">typeof</span> c; <span class="comment">// 类C</span></span><br></pre></td></tr></table></figure><h3 id="infer关键字"><a href="#infer关键字" class="headerlink" title="infer关键字"></a>infer关键字</h3><p>我们看看本节最后一个知识点 <code>infer</code> ，这个看完你就会写工具类型了，是不是很开心？<code>infer</code> 功能非常强大，它可以反查类型，需要与 <code>extends</code> 配合使用。直接看例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">ArrayItemType</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="title class_">Array</span>&lt;infer U&gt; ? U : <span class="built_in">never</span>;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">ArrayItemType</span>&lt;<span class="built_in">number</span>[]&gt;; <span class="comment">// number</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">GetABType</span>&lt;T&gt; = T <span class="keyword">extends</span> &#123;<span class="attr">a</span>: infer U, <span class="attr">b</span>: infer U&#125; ? U : <span class="built_in">never</span>;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">GetABType</span>&lt;&#123;<span class="attr">a</span>: <span class="built_in">string</span>; <span class="attr">b</span>: <span class="built_in">number</span>&#125;&gt;; <span class="comment">// string | number</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// infer 也可以命名不同</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">GetABType2</span>&lt;T&gt; = T <span class="keyword">extends</span> &#123;<span class="attr">a</span>: infer U, <span class="attr">b</span>: infer K&#125; ? [U, K] : <span class="built_in">never</span>;</span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="title class_">GetABType2</span>&lt;&#123;<span class="attr">a</span>: <span class="built_in">string</span>; <span class="attr">b</span>: <span class="built_in">number</span>&#125;&gt;; <span class="comment">// [string, number]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里用never是有说法的 比如：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">ArrayItemType</span>&lt;(<span class="built_in">number</span>[] | <span class="built_in">string</span>)&gt;; <span class="comment">// number</span></span><br><span class="line"><span class="comment">// 上面ArrayItemType&lt;number[]&gt;返回number ArrayItemType&lt;string&gt;返回never</span></span><br><span class="line"><span class="comment">// 整体结果是 number | never 也就是number 相当于自动过滤掉了错误的类型</span></span><br></pre></td></tr></table></figure><h2 id="手撕工具类型"><a href="#手撕工具类型" class="headerlink" title="手撕工具类型"></a>手撕工具类型</h2><p>上面我们已经简绍了 <code>Partial&lt;T&gt;</code>、<code>Readonly&lt;T&gt;</code> 和 <code>Required&lt;T&gt;</code>，你应该已经明白一件事，要判断类型几乎一定会用到 <code>extends</code>、<code>in</code> 或 <code>keyof</code>，本节将手撕更多的工具类型。</p><h3 id="TypeName"><a href="#TypeName" class="headerlink" title="TypeName"></a>TypeName</h3><p><code>TypeName&lt;T&gt;</code> 根据传入的T，返回T的类型的字符串，实际上这里的字符串是一种字符串字面量类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">TypeName</span>&lt;<span class="string">&#x27;123&#x27;</span>&gt;; <span class="comment">// string</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">TypeName</span>&lt;<span class="number">123</span>&gt;; <span class="comment">// number</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="title class_">TypeName</span>&lt;<span class="number">123</span> | <span class="literal">true</span>&gt;; <span class="comment">// number | boolean</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">TypeName</span>&lt;&#123;&#125;&gt;; <span class="comment">// object</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：通过extends来判断是不是对应类型的子类型，从而给出对应的类型</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">TypeName</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="built_in">string</span> ? <span class="string">&quot;string&quot;</span> :</span><br><span class="line">    T <span class="keyword">extends</span> <span class="built_in">number</span> ? <span class="string">&quot;number&quot;</span> :</span><br><span class="line">    T <span class="keyword">extends</span> <span class="built_in">boolean</span> ? <span class="string">&quot;boolean&quot;</span> :</span><br><span class="line">    T <span class="keyword">extends</span> <span class="literal">undefined</span> ? <span class="string">&quot;undefined&quot;</span> :</span><br><span class="line">    T <span class="keyword">extends</span> <span class="title class_">Function</span> ? <span class="string">&quot;function&quot;</span> :</span><br><span class="line">    <span class="string">&quot;object&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="Exclude"><a href="#Exclude" class="headerlink" title="Exclude"></a>Exclude</h3><p><code>Exclude&lt;T, U&gt;</code> 可以在类型 <code>T</code> 中排除类型 <code>U</code> 。这里需要回顾一下在联合类型中，任何类型与 <code>never</code> 类型的联合是任何类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">Exclude</span>&lt;<span class="built_in">string</span> | <span class="built_in">number</span> | <span class="built_in">boolean</span>, <span class="built_in">string</span> | <span class="built_in">number</span>&gt;; <span class="comment">// boolean</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：首先肯定是用到了裸类型，因为裸类型的联合类型是每一种类型的展开，其他不满足的应该返回never，所以最后是never和其他类型的联合，那么never将会被省略。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Exclude</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? <span class="built_in">never</span> : T;</span><br></pre></td></tr></table></figure><h3 id="Extract"><a href="#Extract" class="headerlink" title="Extract"></a>Extract</h3><p><code>Exclude&lt;T, U&gt;</code> 从 <code>T</code> 中提取 <code>U</code> ，通常 <code>T</code> 和 <code>U</code> 都是联合类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">Extract</span>&lt;<span class="built_in">string</span> | <span class="built_in">number</span> | <span class="built_in">boolean</span>, <span class="built_in">string</span> | <span class="built_in">object</span>&gt;; <span class="comment">// string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：逻辑与 Exclude 相反，T 在 U 的里面则返回 T 否则是 never</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Extract</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? T : <span class="built_in">never</span>;</span><br></pre></td></tr></table></figure><h3 id="NonNullable"><a href="#NonNullable" class="headerlink" title="NonNullable"></a>NonNullable</h3><p><code>NonNullable&lt;T&gt;</code> 如果 <code>T</code> 中包含了 <code>null</code> 或者 <code>undefined</code> 则删除 <code>null</code> 和 <code>undefined</code> 。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">NonNullable</span>&lt;<span class="literal">null</span> | <span class="literal">undefined</span> | <span class="built_in">string</span>&gt;; <span class="comment">// string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：更上面一样的套路</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">NonNullable</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="literal">null</span> | <span class="literal">undefined</span> ? <span class="built_in">never</span> : T;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里还有一种实现方式 由于`null`或`undefined`与对象交叉的结果是never，而空对象类型与其他交叉是其他类型。所以也可以利用这个特性判断</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">NonNullable</span>&lt;T&gt; = T &amp; &#123;&#125;</span><br></pre></td></tr></table></figure><h3 id="Record"><a href="#Record" class="headerlink" title="Record"></a>Record</h3><p><code>Record&lt;K, T&gt;</code> 把 <code>K</code> 中的值作为键，<code>T</code> 作为值的类型来构建对象。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">Record</span>&lt;<span class="string">&#x27;x&#x27;</span> | <span class="string">&#x27;y&#x27;</span>, <span class="built_in">number</span>&gt;;</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type T1 = &#123;</span></span><br><span class="line"><span class="comment">//     x: number;</span></span><br><span class="line"><span class="comment">//     y: number;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：映射K，返回类型T。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="comment">// 你可能打手一挥：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Record</span>&lt;K, T&gt; = &#123; [P <span class="keyword">in</span> K]: T; &#125;; <span class="comment">// Error</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 上面会报错，因为K要作为键必须是 string | number | symbol 的子类型 所以应该添加约束</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Record</span>&lt;K <span class="keyword">extends</span> <span class="built_in">string</span> | <span class="built_in">number</span> | <span class="built_in">symbol</span>, T&gt; = &#123; [P <span class="keyword">in</span> K]: T; &#125;</span><br><span class="line"><span class="comment">// 由于 keyof any 就是 string | number | symbol，所以更精简的写法：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Record</span>&lt;K <span class="keyword">extends</span> keyof <span class="built_in">any</span>, T&gt; = &#123; [P <span class="keyword">in</span> K]: T; &#125;;</span><br></pre></td></tr></table></figure><h3 id="Pick"><a href="#Pick" class="headerlink" title="Pick"></a>Pick</h3><p><code>Pick&lt;T, K&gt;</code> 的作用是从对象类型 <code>T</code> 中，挑选出键在 <code>K</code> 中的属性，从而组成新的对象。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> T = &#123;</span><br><span class="line">  <span class="attr">a</span>?: <span class="built_in">string</span> | <span class="literal">undefined</span> | <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">readonly</span> b : <span class="built_in">number</span> | <span class="literal">null</span> | <span class="literal">undefined</span>;</span><br><span class="line">  <span class="attr">c</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">PickT</span> = <span class="title class_">Pick</span>&lt;T, <span class="string">&#x27;a&#x27;</span> | <span class="string">&#x27;b&#x27;</span>&gt;;</span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="comment">// type PickT = &#123;</span></span><br><span class="line"><span class="comment">//     a?: string | undefined | null;</span></span><br><span class="line"><span class="comment">//     readonly b: number | null | undefined;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：Pick&lt;T, K&gt;有两个泛型，根据K映射出新的对象。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="comment">// 你可能大笔一挥</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Pick</span>&lt;T, K&gt; = &#123; [P <span class="keyword">in</span> K]: T[P]; &#125;; <span class="comment">// Error</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 上面有两个问题</span></span><br><span class="line"><span class="comment">// 1. Pick中没有keyof所以是非同态映射，所以不会保留操作符；</span></span><br><span class="line"><span class="comment">// 2. K可能不是T中的key。</span></span><br><span class="line"><span class="comment">// 这里可以这么写，通过 extends keyof 把非同态转化为同态是一个小技巧：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Pick</span>&lt;T, K <span class="keyword">extends</span> keyof T&gt; = &#123; [P <span class="keyword">in</span> K]: T[P]; &#125;; <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><h3 id="Omit"><a href="#Omit" class="headerlink" title="Omit"></a>Omit</h3><p><code>Omit&lt;T, K&gt;</code> 与 <code>Pick&lt;T, K&gt;</code> 是互补的。它是已经类型删掉某些属性从而形成新的类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> T = &#123;</span><br><span class="line">  <span class="attr">a</span>?: <span class="built_in">string</span> | <span class="literal">undefined</span> | <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">readonly</span> b : <span class="built_in">number</span> | <span class="literal">null</span> | <span class="literal">undefined</span>;</span><br><span class="line">  <span class="attr">c</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">OmitT</span> = <span class="title class_">Omit</span>&lt;T, <span class="string">&#x27;c&#x27;</span>&gt;;</span><br><span class="line"><span class="comment">// 相当于：</span></span><br><span class="line"><span class="comment">// type OmitT = &#123;</span></span><br><span class="line"><span class="comment">//     a?: string | undefined | null;</span></span><br><span class="line"><span class="comment">//     readonly b: number | null | undefined;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：首页K既然是键，那么就应该满足约束是string | number | symbol（或者keyof any）的子类型，否则会报错，跟Pick相反，Pick是选择K里面的属性，这是是不能选择K里面的属性也就是T先要排除K中的属性，所以得用Exclude排除掉。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Omit</span>&lt;T, K <span class="keyword">extends</span> keyof <span class="built_in">any</span>&gt; = &#123; [P <span class="keyword">in</span> <span class="title class_">Exclude</span>&lt;keyof T, K&gt;]: T[P]; &#125;</span><br></pre></td></tr></table></figure><h3 id="Parameters"><a href="#Parameters" class="headerlink" title="Parameters"></a>Parameters</h3><p><code>Parameters&lt;T&gt;</code>，根据函数 <code>T</code> 的类型，返回函数 <code>T</code> 参数的元组。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">Parameters</span>&lt;<span class="function">() =&gt;</span> <span class="built_in">void</span>&gt;; <span class="comment">// []</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">Parameters</span>&lt;<span class="function">(<span class="params"><span class="attr">s</span>: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">void</span>&gt;; <span class="comment">// [string]</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="title class_">Parameters</span>&lt;<span class="function">(<span class="params"><span class="attr">s</span>: <span class="built_in">string</span>, <span class="attr">n</span>: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">void</span>&gt;; <span class="comment">// [string, number]</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">Parameters</span>&lt;<span class="function">(<span class="params">...<span class="attr">args</span>: <span class="built_in">string</span>[]</span>) =&gt;</span> <span class="built_in">void</span>&gt;; <span class="comment">// string[]</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = <span class="title class_">Parameters</span>&lt;<span class="built_in">any</span>&gt;; <span class="comment">// unknown[]</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：首先T应该满足函数约束，然后通过infer获取到参数列表。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Parameters</span>&lt;T <span class="title function_">extends</span> (...<span class="attr">args</span>: <span class="built_in">any</span>) =&gt; <span class="built_in">any</span>&gt; = T <span class="title function_">extends</span> (...<span class="attr">args</span>: infer P) =&gt; <span class="built_in">any</span> ? P : <span class="built_in">never</span>;</span><br></pre></td></tr></table></figure><h3 id="ReturnType"><a href="#ReturnType" class="headerlink" title="ReturnType"></a>ReturnType</h3><p><code>ReturnType&lt;T&gt;</code> 可以获取函数T的返回值类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">ReturnType</span>&lt;<span class="function">() =&gt;</span> <span class="built_in">void</span>&gt;; <span class="comment">// void</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">ReturnType</span>&lt;<span class="function">() =&gt;</span> <span class="built_in">string</span>&gt;; <span class="comment">// string</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="title class_">ReturnType</span>&lt;<span class="function">() =&gt;</span> &#123; <span class="attr">a</span>: <span class="built_in">string</span> &#125;&gt;; <span class="comment">// &#123; a: string &#125;</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">ReturnType</span>&lt;&lt;T&gt;<span class="function">() =&gt;</span> T&gt;; <span class="comment">// unknown</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = <span class="title class_">ReturnType</span>&lt;<span class="built_in">any</span>&gt;; <span class="comment">// any</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：与 Parameters 类似，只是 infer 取得是返回值。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ReturnType</span>&lt;T <span class="title function_">extends</span> (...<span class="attr">args</span>: <span class="built_in">any</span>) =&gt; <span class="built_in">any</span>&gt; = T <span class="title function_">extends</span> (...<span class="attr">args</span>: <span class="built_in">any</span>) =&gt; infer R ? R : <span class="built_in">any</span>;</span><br></pre></td></tr></table></figure><h3 id="ConstructorParameters"><a href="#ConstructorParameters" class="headerlink" title="ConstructorParameters"></a>ConstructorParameters</h3><p><code>ConstructorParameters&lt;T&gt;</code> 获取构造函数T的参数，返回对应参数的元组。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params"><span class="attr">arg</span>: <span class="built_in">string</span></span>) &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> <span class="keyword">extends</span> <span class="title class_ inherited__">A</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">...<span class="attr">args</span>: <span class="built_in">string</span>[]</span>) &#123;</span><br><span class="line">        <span class="variable language_">super</span>(args[<span class="number">0</span>])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">ConstructorParameters</span>&lt;<span class="title function_">new</span> () =&gt; <span class="built_in">object</span>&gt;; <span class="comment">// []</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">ConstructorParameters</span>&lt;<span class="keyword">typeof</span> A&gt;; <span class="comment">// [string]</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="title class_">ConstructorParameters</span>&lt;<span class="title function_">new</span> (<span class="attr">s</span>: <span class="built_in">string</span>, <span class="attr">n</span>: <span class="built_in">number</span>) =&gt; <span class="built_in">object</span>&gt;; <span class="comment">// [string, number]</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">ConstructorParameters</span>&lt;<span class="keyword">typeof</span> B&gt;; <span class="comment">// string[]</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = <span class="title class_">ConstructorParameters</span>&lt;<span class="built_in">any</span>&gt;; <span class="comment">// unknown[]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：实现思路与Parameters类似，只是约束必须是构造函数方法。这里需要注意的是如果抽象类也支持获取构造函数的参数，那么需要在 new 之前添加 abstract 关键字。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ConstructorParameters</span>&lt;T <span class="keyword">extends</span> <span class="keyword">abstract</span> <span class="title function_">new</span> (...<span class="attr">args</span>: <span class="built_in">any</span>) =&gt; <span class="built_in">any</span>&gt; = T <span class="keyword">extends</span> <span class="keyword">abstract</span> <span class="title function_">new</span> (...<span class="attr">args</span>: infer P) =&gt; <span class="built_in">any</span> ? P : <span class="built_in">never</span>;</span><br></pre></td></tr></table></figure><h3 id="InstanceType"><a href="#InstanceType" class="headerlink" title="InstanceType"></a>InstanceType</h3><p><code>InstanceType&lt;T&gt;</code> 获取构造函数返回值类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params"><span class="attr">arg</span>: <span class="built_in">string</span></span>) &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> <span class="keyword">extends</span> <span class="title class_ inherited__">A</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">...<span class="attr">args</span>: <span class="built_in">string</span>[]</span>) &#123;</span><br><span class="line">        <span class="variable language_">super</span>(args[<span class="number">0</span>])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">InstanceType</span>&lt;<span class="title function_">new</span> () =&gt; <span class="built_in">object</span>&gt;; <span class="comment">// object</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">InstanceType</span>&lt;<span class="keyword">typeof</span> A&gt;; <span class="comment">// A</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">InstanceType</span>&lt;<span class="keyword">typeof</span> B&gt;; <span class="comment">// B</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T5</span> = <span class="title class_">InstanceType</span>&lt;<span class="built_in">any</span>&gt;; <span class="comment">// any</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：与 ConstructorParameters 类型，只是 infer 取的是返回值。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">InstanceType</span>&lt;T <span class="keyword">extends</span> <span class="keyword">abstract</span> <span class="title function_">new</span> (...<span class="attr">args</span>: <span class="built_in">any</span>) =&gt; <span class="built_in">any</span>&gt; = T <span class="keyword">extends</span> <span class="keyword">abstract</span> <span class="title function_">new</span> (...<span class="attr">args</span>: <span class="built_in">any</span>) =&gt; infer R ? R : <span class="built_in">any</span>;</span><br></pre></td></tr></table></figure><h3 id="ThisParameterType"><a href="#ThisParameterType" class="headerlink" title="ThisParameterType"></a>ThisParameterType</h3><p><code>ThisParameterType&lt;T&gt;</code> 获取函数的 <code>this</code> 参数类型，若没有定义 <code>this</code> 参数则返回 <code>unknown</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"><span class="attr">this</span>:<span class="built_in">object</span>, <span class="attr">x</span>: <span class="built_in">number</span></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">number</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">ThisParameterType</span>&lt;<span class="keyword">typeof</span> f1&gt;; <span class="comment">// object</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">ThisParameterType</span>&lt;<span class="keyword">typeof</span> f2&gt;; <span class="comment">// unknown</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：infer取this的类型。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ThisParameterType</span>&lt;T&gt; = T <span class="title function_">extends</span> (<span class="attr">this</span>: infer U, ...<span class="attr">args</span>: <span class="built_in">never</span>) =&gt; <span class="built_in">any</span> ? U : <span class="built_in">unknown</span></span><br></pre></td></tr></table></figure><h3 id="OmitThisParameter"><a href="#OmitThisParameter" class="headerlink" title="OmitThisParameter"></a>OmitThisParameter</h3><p><code>OmitThisParameter&lt;T&gt;</code> 能从函数 <code>T</code> 中剔除 <code>this</code> 函数类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用：</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"><span class="attr">this</span>:<span class="built_in">object</span>, <span class="attr">x</span>: <span class="built_in">number</span></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">number</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T1</span> = <span class="title class_">OmitThisParameter</span>&lt;<span class="keyword">typeof</span> f1&gt;; <span class="comment">// (x: number) =&gt; void</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T2</span> = <span class="title class_">OmitThisParameter</span>&lt;<span class="keyword">typeof</span> f2&gt;; <span class="comment">// (x: number) =&gt; void</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T3</span> = <span class="title class_">OmitThisParameter</span>&lt;<span class="built_in">string</span>&gt;; <span class="comment">// string</span></span><br><span class="line"><span class="keyword">type</span> <span class="variable constant_">T4</span> = <span class="title class_">OmitThisParameter</span>&lt;<span class="built_in">unknown</span>&gt;; <span class="comment">// unknown</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 思路：如果函数类型包含this定义，那么去除this，如果函数类型不包含this的定义则直接把该函数返回。如何判断函数包含this定义呢？可以通过 ThisParameterType 获取看是不是 unknown。这里对于非函数类型，直接返回该类型，如上面string返回的是string。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现：</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">OmitThisParameter</span>&lt;T&gt; = <span class="built_in">unknown</span> <span class="keyword">extends</span> <span class="title class_">ThisParameterType</span>&lt;T&gt; ? T : T <span class="title function_">extends</span> (...<span class="attr">args</span>: infer A) =&gt; infer R ? <span class="function">(<span class="params">...<span class="attr">args</span>: A</span>) =&gt;</span> R : T</span><br></pre></td></tr></table></figure><p>我的博客即将同步至腾讯云开发者社区，邀请大家一同入驻：<a href="https://cloud.tencent.com/developer/support-plan?invite_code=1sfdnjsw8b22">https://cloud.tencent.com/developer/support-plan?invite_code=1sfdnjsw8b22</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;code&gt;TypeScript&lt;/code&gt; 的工具类型，也被称作类型体操。通过本文你就知道这些工具类型的原理，并可以自己写出一些工具类型。在学习工具类型之前，我们先学学工具类型所用到的基础知识，当基础知识掌握牢固后，看懂工具类型自然水到渠成。&lt;/p&gt;
&lt;h2 id=&quot;基</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="TypeScript" scheme="https://www.kai666666.com/tags/TypeScript/"/>
    
  </entry>
  
  <entry>
    <title>TypeScript一些知识点</title>
    <link href="https://www.kai666666.com/2024/05/12/TypeScript%E4%B8%80%E4%BA%9B%E7%9F%A5%E8%AF%86%E7%82%B9/"/>
    <id>https://www.kai666666.com/2024/05/12/TypeScript%E4%B8%80%E4%BA%9B%E7%9F%A5%E8%AF%86%E7%82%B9/</id>
    <published>2024-05-12T14:56:37.000Z</published>
    <updated>2026-02-10T08:00:34.396Z</updated>
    
    <content type="html"><![CDATA[<h2 id="区域注释"><a href="#区域注释" class="headerlink" title="区域注释"></a>区域注释</h2><p>TypeScript 可以添加区域注释，可以让VS Code等编辑器识别为一个代码区域，区域注释使用的是单行注释语法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//#region 区域描述</span></span><br><span class="line"><span class="keyword">let</span> x = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> y = x + <span class="number">1</span>;</span><br><span class="line"><span class="comment">//#endregion</span></span><br></pre></td></tr></table></figure><h2 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h2><h3 id="空值合并运算符"><a href="#空值合并运算符" class="headerlink" title="空值合并运算符"></a>空值合并运算符</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a ?? b</span><br></pre></td></tr></table></figure><p>如上，当 <code>a</code> 为 <code>undefined</code> 或 <code>null</code> 的时候，返回 <code>b</code> 。相比于 <code>a || b</code> ，当 <code>a</code> 为 <code>false</code> 或空字符串时，<code>a ?? b</code> 也返回a，这在某些对于 <code>false</code> 或空字符串也起作用的场景，空值合并运算符是非常有用的。</p><h2 id="TypeScript的原始类型"><a href="#TypeScript的原始类型" class="headerlink" title="TypeScript的原始类型"></a>TypeScript的原始类型</h2><p>TypeScript常见原始类型有：</p><ul><li>boolean</li><li>string</li><li>number</li><li>bigint</li><li>symbol</li><li>undefined</li><li>null</li><li>void</li><li>枚举类型</li><li>字面量类型</li></ul><p>这里的原始类型都是小写的，如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">bigint</span> = <span class="number">1n</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="built_in">number</span> = <span class="number">1n</span>; <span class="comment">// 报错，因为bigint不能算作number类型</span></span><br></pre></td></tr></table></figure><h3 id="symbol"><a href="#symbol" class="headerlink" title="symbol"></a>symbol</h3><p>symbol对应JavaScript原始类型的Symbol。考虑如下代码：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="attr">s1</span>: <span class="built_in">symbol</span> = <span class="title class_">Symbol</span>();</span><br><span class="line">s1 = <span class="title class_">Symbol</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> A &#123;</span><br><span class="line">  [s1]: <span class="built_in">number</span>; <span class="comment">// 报错</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: A = &#123;</span><br><span class="line">  [s1]: <span class="number">123</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Symbol</code> 表示不变的，但是上述 <code>symbol</code> 类型的 <code>s1</code> 却可以再次赋值为其他 <code>Symbol</code> 值，这就导致在定义接口 <code>A</code> 的时候使用的 <code>s1</code> 可以变化，这就违背了 <code>Symbol</code> 不变的性质。为了避免这种问题，TS引入了 <code>unique symbol</code> 类型，该类型的 <code>Symbol</code> 必须用 <code>const</code> 申明（ <code>let</code> 或 <code>var</code> 声明直接报错），这样就不能再修改了：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">s1</span>: unique <span class="built_in">symbol</span> = <span class="title class_">Symbol</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> A &#123;</span><br><span class="line">  [s1]: <span class="built_in">number</span>; <span class="comment">// OK</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: A = &#123;</span><br><span class="line">  [s1]: <span class="number">123</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>unique symbol</code> 只能通过 <code>Symbol()</code> 或者 <code>Symbol.for()</code> 赋值，但<code>symbol</code>类型没有这样的限制。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">s1</span>: unique <span class="built_in">symbol</span> = <span class="title class_">Symbol</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="attr">s2</span>: unique <span class="built_in">symbol</span> = s1; <span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">s3</span>: <span class="built_in">symbol</span> = s1; <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><h3 id="枚举类型"><a href="#枚举类型" class="headerlink" title="枚举类型"></a>枚举类型</h3><p>TS中的枚举实际上会编译为对象：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Direction</span> &#123;</span><br><span class="line">  <span class="variable constant_">UP</span>,</span><br><span class="line">  <span class="variable constant_">DOWN</span>,</span><br><span class="line">  <span class="variable constant_">LEFT</span>,</span><br><span class="line">  <span class="variable constant_">RIGHT</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实际编译为：</span></span><br><span class="line"><span class="keyword">var</span> <span class="title class_">Direction</span>;</span><br><span class="line">(<span class="keyword">function</span> (<span class="params"><span class="title class_">Direction</span></span>) &#123;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;UP&quot;</span>] = <span class="number">0</span>] = <span class="string">&quot;UP&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;DOWN&quot;</span>] = <span class="number">1</span>] = <span class="string">&quot;DOWN&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;LEFT&quot;</span>] = <span class="number">2</span>] = <span class="string">&quot;LEFT&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;RIGHT&quot;</span>] = <span class="number">3</span>] = <span class="string">&quot;RIGHT&quot;</span>;</span><br><span class="line">&#125;)(<span class="title class_">Direction</span> || (<span class="title class_">Direction</span> = &#123;&#125;));</span><br></pre></td></tr></table></figure><p>可以看出枚举实际上是编译为对象，而且值实际上是数值，如上面 <code>Direction.UP === 0</code> 。当然因为这里也对数值赋值为对应的字符串，所以 <code>Direction[0] === &#39;UP&#39;</code> 。</p><p>这里也可以给某个枚举值赋值为数字，这样枚举值的计数就会从赋值开始：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Direction</span> &#123;</span><br><span class="line">  <span class="variable constant_">UP</span> = -<span class="number">2</span>,</span><br><span class="line">  <span class="variable constant_">DOWN</span>,</span><br><span class="line">  <span class="variable constant_">LEFT</span> = <span class="number">10</span>,</span><br><span class="line">  <span class="variable constant_">RIGHT</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实际编译为：</span></span><br><span class="line"><span class="keyword">var</span> <span class="title class_">Direction</span>;</span><br><span class="line">(<span class="keyword">function</span> (<span class="params"><span class="title class_">Direction</span></span>) &#123;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;UP&quot;</span>] = -<span class="number">2</span>] = <span class="string">&quot;UP&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;DOWN&quot;</span>] = -<span class="number">1</span>] = <span class="string">&quot;DOWN&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;LEFT&quot;</span>] = <span class="number">10</span>] = <span class="string">&quot;LEFT&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="title class_">Direction</span>[<span class="string">&quot;RIGHT&quot;</span>] = <span class="number">11</span>] = <span class="string">&quot;RIGHT&quot;</span>;</span><br><span class="line">&#125;)(<span class="title class_">Direction</span> || (<span class="title class_">Direction</span> = &#123;&#125;));</span><br></pre></td></tr></table></figure><p>可以看到 <code>UP</code> 是 <code>-2</code> ，<code>DOWN</code> 是 <code>-2 + 1</code> 也就是 <code>-1</code> ；<code>LEFT</code> 是 <code>10</code>，<code>RIGHT</code> 是 <code>10 + 1</code> 也就是 <code>11</code>。</p><p>当然枚举也可以是字符串：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Direction</span> &#123;</span><br><span class="line">  <span class="variable constant_">UP</span> = <span class="string">&#x27;UP&#x27;</span>,</span><br><span class="line">  <span class="variable constant_">DOWN</span> = <span class="string">&#x27;DOWN&#x27;</span>,</span><br><span class="line">  <span class="variable constant_">LEFT</span> = <span class="string">&#x27;LEFT&#x27;</span>,</span><br><span class="line">  <span class="variable constant_">RIGHT</span> = <span class="string">&#x27;RIGHT&#x27;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">up</span>: <span class="title class_">Direction</span> = <span class="title class_">Direction</span>.<span class="property">UP</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">down</span>: <span class="title class_">Direction</span> = <span class="string">&#x27;DOWN&#x27;</span>; <span class="comment">// 报错，虽然值相同 但是字符串不能赋值给枚举</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 上述枚举编译为：</span></span><br><span class="line"><span class="keyword">var</span> <span class="title class_">Direction</span>;</span><br><span class="line">(<span class="keyword">function</span> (<span class="params"><span class="title class_">Direction</span></span>) &#123;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="string">&quot;UP&quot;</span>] = <span class="string">&quot;UP&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="string">&quot;DOWN&quot;</span>] = <span class="string">&quot;DOWN&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="string">&quot;LEFT&quot;</span>] = <span class="string">&quot;LEFT&quot;</span>;</span><br><span class="line">  <span class="title class_">Direction</span>[<span class="string">&quot;RIGHT&quot;</span>] = <span class="string">&quot;RIGHT&quot;</span>;</span><br><span class="line">&#125;)(<span class="title class_">Direction</span> || (<span class="title class_">Direction</span> = &#123;&#125;));</span><br></pre></td></tr></table></figure><p><code>const 枚举</code> 指的是用 <code>const</code> 声明的枚举，<code>const 枚举</code> 编译跟普通枚举不同，它编译后的结果是在使用的地方直接替换为对应的字符串或数字：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">enum</span> <span class="title class_">Direction</span> &#123;</span><br><span class="line">  <span class="variable constant_">UP</span>,</span><br><span class="line">  <span class="variable constant_">DOWN</span>,</span><br><span class="line">  <span class="variable constant_">LEFT</span>,</span><br><span class="line">  <span class="variable constant_">RIGHT</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">up</span>: <span class="title class_">Direction</span> = <span class="title class_">Direction</span>.<span class="property">UP</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 上述枚举编译为：</span></span><br><span class="line"><span class="keyword">const</span> up = <span class="number">0</span> <span class="comment">/* Direction.UP */</span>; <span class="comment">// 在使用的地方直接转化为对应的枚举值</span></span><br></pre></td></tr></table></figure><h2 id="strictNullChecks"><a href="#strictNullChecks" class="headerlink" title="strictNullChecks"></a>strictNullChecks</h2><p>当给一个类型的值设置为 <code>null</code> 或者 <code>undefined</code> 的时候默认并不会报错：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">s</span>: <span class="built_in">string</span> = <span class="literal">undefined</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="built_in">boolean</span> = <span class="literal">null</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">n</span>: <span class="literal">null</span> = <span class="literal">undefined</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">undef</span>: <span class="literal">undefined</span> = <span class="literal">null</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">any</span> = <span class="literal">null</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">unk</span>: <span class="built_in">unknown</span> = <span class="literal">undefined</span>; <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><p>但是可以通过配置 <code>--strictNullChecks</code> 来严格检查是否为空，配置方式有两种：</p><ol><li>运行命令 <code>tsc index.ts --strictNullChecks false</code></li><li>在tsconfig.json中添加：</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;strictNullChecks&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>注：大多数配置参数都是通过上述两种方式配置的, 但一般使用TS时需要编辑器直接告诉我们哪里编码错误，而不是等编译的时候再检查，所以这里更推荐第二种方式。更多配置请<a href="https://www.typescriptlang.org/tsconfig/">参考这里</a>。</p></blockquote><p>配置以后上面的情况如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">s</span>: <span class="built_in">string</span> = <span class="literal">undefined</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="built_in">boolean</span> = <span class="literal">null</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">n</span>: <span class="literal">null</span> = <span class="literal">undefined</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">undef</span>: <span class="literal">undefined</span> = <span class="literal">null</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">any</span> = <span class="literal">null</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">unk</span>: <span class="built_in">unknown</span> = <span class="literal">undefined</span>; <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><blockquote><p>注: <code>any</code> 和 <code>unknown</code> 仍然可以设置为 <code>null</code> 和 <code>undefined</code>。</p></blockquote><h2 id="顶端类型"><a href="#顶端类型" class="headerlink" title="顶端类型"></a>顶端类型</h2><p>顶端类型是一种通用类型，有时也称为通用超类型。在类型系统中，所有类型都是顶端类型的子类。<br>TypeScript中有两种顶端类型：</p><ol><li>any</li><li>unknown</li></ol><p><code>any</code> 类型允许执行任意操作而不会产生编译错误（但运行时候也可能出现错误），通常用于跳过类型检查：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">any</span> = <span class="number">0</span>;</span><br><span class="line">a.<span class="property">length</span>; <span class="comment">// 编译时不会报错</span></span><br><span class="line"><span class="title function_">a</span>(); <span class="comment">// 编译时不会报错</span></span><br><span class="line">a[<span class="number">0</span>]; <span class="comment">// 编译时不会报错</span></span><br></pre></td></tr></table></figure><p>对于一个方法来说如果没有声明类型则默认是<code>any</code>类型，可以通过 <code>--noImplicitAny</code> 参数来控制不允许隐式设置any类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// --noImplicitAny: false</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">x</span>) &#123;&#125; <span class="comment">// OK x类型隐式设置为any</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// --noImplicitAny: true</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">x</span>) &#123;&#125; <span class="comment">// Error</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// --noImplicitAny: true</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">any</span></span>) &#123;&#125; <span class="comment">// OK x类型显示设置为any</span></span><br></pre></td></tr></table></figure><p><code>--noImplicitAny</code> 的配置方法跟上面 <code>--strictNullChecks</code> 配置方法类似，如修改 <code>tsconfig.json</code> 文件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;noImplicitAny&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><code>unknown</code> 与 <code>any</code> 类型任何其他类型都可以赋值给 <code>unknown</code> ，但是<code>unknown</code>类型的值只能赋值给 <code>unkonwn</code> 和 <code>any</code>，而且 <code>unknown</code> 不允许执行绝大多数的操作：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="attr">a</span>: <span class="built_in">unknown</span> = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="attr">b</span>: <span class="built_in">number</span> = <span class="number">0</span>;</span><br><span class="line">a = b; <span class="comment">// OK</span></span><br><span class="line">a.<span class="property">length</span>; <span class="comment">// Error 不像any可以执行任何操作</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="attr">c</span>: <span class="built_in">number</span> = a; <span class="comment">// Error unknown只能赋值给 any 和 unknown</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">d</span>: <span class="built_in">any</span> = a; <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><p>通常使用 <code>unknown</code> 需要自行判断类型：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> (<span class="params"><span class="attr">x</span>: <span class="built_in">unknown</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">typeof</span> x === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x.<span class="property">length</span>; <span class="comment">// 判断了类型是 string 的时候调用 x.length 才不会报错</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="尾端类型"><a href="#尾端类型" class="headerlink" title="尾端类型"></a>尾端类型</h2><p>尾端类型是所有类型的子类型，它只有一个类型就是 <code>never</code> ，该类型甚至没有值。由于它是所有类型的子类型，所以它可以赋值给任何类型，但是其他类型都不能赋值给它，包括 <code>any</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="attr">a</span>: <span class="built_in">never</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">b</span>: <span class="built_in">boolean</span> = a; <span class="comment">// OK 它可以赋值给任何类型</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">c</span>: <span class="built_in">any</span> = <span class="number">1</span>;</span><br><span class="line">a = c; <span class="comment">// Error 其他类型不能赋值给它</span></span><br><span class="line">a = <span class="number">1</span>; <span class="comment">// Error never没有值</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">d</span>: <span class="built_in">never</span> = <span class="literal">undefined</span>; <span class="comment">// Error never没有值 显示设置undefined也不行</span></span><br></pre></td></tr></table></figure><blockquote><p>注：虽然 <code>never</code> 可以赋值给任何类型，但是如果在 <code>--strictNullChecks</code> 为<code>true</code>的时候，同样会报错。如上第二行，在 <code>--strictNullChecks</code> 为 <code>true</code> 时，也是会报错的。</p></blockquote><p><code>neber</code> 的使用场景：</p><ol><li>函数没有返回值。</li></ol><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>): <span class="built_in">never</span> &#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>函数死循环。</li></ol><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>): <span class="built_in">never</span> &#123;</span><br><span class="line">  <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>有些条件类型判断中会使用到never。</li></ol><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Exclude</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? <span class="built_in">never</span> : T;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> T = <span class="title class_">Exclude</span>&lt;<span class="built_in">boolean</span> | <span class="built_in">string</span>, <span class="built_in">string</span>&gt;; <span class="comment">// type T = boolean</span></span><br></pre></td></tr></table></figure><h2 id="只读数组"><a href="#只读数组" class="headerlink" title="只读数组"></a>只读数组</h2><p>数组的表示方法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">number</span>[] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="title class_">Array</span>&lt;<span class="built_in">number</span>&gt; = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: <span class="built_in">number</span> = a[<span class="number">100</span>]; <span class="comment">// OK虽然 a[100] 的值是undefined但是这里可以用number来声明 因为a是number的数组</span></span><br></pre></td></tr></table></figure><p>只读数组的表示方法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="title class_">ReadonlyArray</span>&lt;<span class="built_in">number</span>&gt; = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="keyword">readonly</span> <span class="built_in">number</span>[] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: <span class="title class_">Readonly</span>&lt;<span class="built_in">number</span>[]&gt; = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">d</span>: <span class="keyword">readonly</span> <span class="title class_">Array</span>&lt;<span class="built_in">number</span>&gt; = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]; <span class="comment">// Error readonly不能与泛型数组一起使用</span></span><br><span class="line"></span><br><span class="line">a.<span class="title function_">push</span>(<span class="number">4</span>); <span class="comment">// Error 只读数组不能追加</span></span><br></pre></td></tr></table></figure><h2 id="元组类型"><a href="#元组类型" class="headerlink" title="元组类型"></a>元组类型</h2><p>元组类型是数组类型的子类型，值是一个数组。元组一般是长度固定的数组，相比较数组每个元素都是相同的类型，元组每个元素的类型都可以不同。由于元组类型是数组的子类型所以元组类型可以赋值给数组类型，前提是元组中的每一项都符合数组的每一项类型；数组类型是不能赋值给元组类型的。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: [<span class="built_in">number</span>, <span class="built_in">string</span>] = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>]; <span class="comment">// a就是元组类型</span></span><br><span class="line">a[<span class="number">3</span>]; <span class="comment">// Error 只有2个元素的元组不能访问长度3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="keyword">readonly</span> [<span class="built_in">number</span>, <span class="built_in">string</span>] = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>]; <span class="comment">// 只读元组</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: <span class="title class_">Readonly</span>&lt;[<span class="built_in">number</span>, <span class="built_in">string</span>]&gt; = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>]; <span class="comment">// 只读元组</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="attr">d</span>: [<span class="built_in">number</span>, <span class="built_in">string</span>?, <span class="built_in">boolean</span>?] = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>, <span class="literal">true</span>]; <span class="comment">// 可选元组</span></span><br><span class="line">d = [<span class="number">2</span>]; <span class="comment">// OK</span></span><br><span class="line">d = [<span class="number">2</span>, <span class="string">&#x27;3&#x27;</span>]; <span class="comment">// OK</span></span><br><span class="line">d = [<span class="number">2</span>, <span class="string">&#x27;3&#x27;</span>, <span class="literal">false</span>]; <span class="comment">// OK</span></span><br><span class="line">d = [<span class="number">2</span>, <span class="literal">false</span>]; <span class="comment">// Error 可选只能省略后面的 不能省略中间的 第二个可选参数是string</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="attr">e</span>: [<span class="built_in">number</span>, ...<span class="built_in">string</span>[]] = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>]; <span class="comment">// 带有剩余参数的元组</span></span><br><span class="line">e = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>, <span class="string">&#x27;3&#x27;</span>, <span class="string">&#x27;4&#x27;</span>]; <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">f</span>: [<span class="built_in">number</span>, <span class="built_in">string</span>] = [<span class="number">1</span>, <span class="string">&#x27;2&#x27;</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="attr">g</span>: [<span class="built_in">number</span>, <span class="built_in">number</span>] = [<span class="number">1</span>, <span class="number">1</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="attr">h</span>: <span class="built_in">number</span>[] = f; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">i</span>: <span class="built_in">number</span>[] = g; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">j</span>: <span class="built_in">number</span>[] = [<span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="attr">k</span>: [<span class="built_in">number</span>, <span class="built_in">number</span>] = j; <span class="comment">// Error 数组类型不能直接赋值给元组 因为可能数组的个数比元组少</span></span><br></pre></td></tr></table></figure><h2 id="对象类型"><a href="#对象类型" class="headerlink" title="对象类型"></a>对象类型</h2><h3 id="Object"><a href="#Object" class="headerlink" title="Object"></a>Object</h3><p>在TypeScript中值 <code>Object</code>（window.Object）的类型并不是 <code>Object</code> 类型，而是 <code>ObjectConstructor</code> 类型。通过调用<code>new Object()</code>获取到的值的类型才是<code>Object</code>类型。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Object</span> &#123;</span><br><span class="line">  <span class="attr">constructor</span>: <span class="title class_">Function</span>;</span><br><span class="line">  <span class="title function_">toString</span>(): <span class="built_in">string</span>;</span><br><span class="line">  <span class="title function_">valueOf</span>(): <span class="title class_">Object</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ObjectConstructor</span> &#123;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">prototype</span>: <span class="title class_">Object</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">var</span> <span class="title class_">Object</span>: <span class="title class_">ObjectConstructor</span>;</span><br></pre></td></tr></table></figure><p><code>Object</code> 类型的值除了 <code>null</code> 和 <code>undefined</code> 外，其他任何值都可以赋值。为什么相如 <code>boolean</code> 这种原始数据类型也能赋值给 <code>Object</code> 呢？因为原始类型会自动拆箱和装箱啊。但是声明的 <code>Object</code> 类型的值不能调用 <code>window.Object</code> 以外定义的属性和方法。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="title class_">Object</span> = <span class="keyword">new</span> <span class="title class_">Object</span>(); <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="title class_">Object</span> = &#123; <span class="comment">// 可以定义 Object 类型</span></span><br><span class="line">  <span class="attr">x</span>: <span class="number">123</span></span><br><span class="line">&#125;; <span class="comment">// OK</span></span><br><span class="line">b.<span class="property">x</span>; <span class="comment">// Error 但不能访问Object外定义的属性和方法</span></span><br><span class="line">c.<span class="title function_">toString</span>(); <span class="comment">// OK 返回 &#x27;true&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: <span class="title class_">Object</span> = <span class="literal">true</span>; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">d</span>: <span class="title class_">Object</span> = <span class="literal">null</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">e</span>: <span class="title class_">Object</span> = []; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">f</span>: <span class="title class_">Object</span> = <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;&#125;; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">g</span>: <span class="title class_">Object</span> = <span class="keyword">new</span> <span class="title class_">Date</span>(); <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><h3 id="object"><a href="#object" class="headerlink" title="object"></a>object</h3><p><code>object</code> 相比较于 <code>Object</code> 更加严格，只能是对象类型，而不能是 <code>boolean</code> 这样的原始数据类型，同样的也只能调用 <code>Object</code> 类型定义的属性和方法。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="built_in">object</span> = <span class="keyword">new</span> <span class="title class_">Object</span>(); <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: <span class="built_in">object</span> = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">123</span></span><br><span class="line">&#125;; <span class="comment">// OK</span></span><br><span class="line">b.<span class="property">x</span>; <span class="comment">// Error 不能访问Object外定义的属性和方法</span></span><br><span class="line">a.<span class="title function_">toString</span>(); <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">c</span>: <span class="built_in">object</span> = <span class="literal">true</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">d</span>: <span class="built_in">object</span> = <span class="literal">null</span>; <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">e</span>: <span class="built_in">object</span> = []; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">f</span>: <span class="built_in">object</span> = <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;&#125;; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">g</span>: <span class="built_in">object</span> = <span class="keyword">new</span> <span class="title class_">Date</span>(); <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><h3 id="对象字面量类型"><a href="#对象字面量类型" class="headerlink" title="对象字面量类型"></a>对象字面量类型</h3><p>一看就会的对象字面量类型：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: &#123; <span class="attr">x</span>: <span class="built_in">number</span>, <span class="attr">y</span>: <span class="built_in">number</span>&#125; = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">y</span>:<span class="number">0</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上面类型 <code>&#123; x: number, y: number&#125;</code> 就是对象字面量类型，是不是很简单？看一个稍微复杂一点的例子：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="string">&#x27;a&#x27;</span> = <span class="string">&#x27;a&#x27;</span>; <span class="comment">// 注意这里的类型使用了 &#x27;a&#x27; 而不是string, 如果是string则不能用在对象中作为属性</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">b</span>: unique <span class="built_in">symbol</span> = <span class="title class_">Symbol</span>();</span><br><span class="line"><span class="keyword">let</span> <span class="attr">obj</span>: &#123;</span><br><span class="line">  [a]: <span class="built_in">boolean</span>; <span class="comment">// OK 可计算属性名 只能是string字面量、number字面量、symbol</span></span><br><span class="line">  [b]: <span class="built_in">string</span>; <span class="comment">// OK 使用symbol</span></span><br><span class="line">  <span class="attr">c</span>?: <span class="built_in">string</span>; <span class="comment">// OK 拥有可选属性c</span></span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">d</span>: <span class="built_in">number</span>; <span class="comment">// OK 拥有只读属性</span></span><br><span class="line">&#125; = &#123;</span><br><span class="line">  [a]: <span class="literal">true</span>,</span><br><span class="line">  [b]: <span class="string">&#x27;0&#x27;</span>,</span><br><span class="line">  <span class="attr">d</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">e</span>: <span class="number">123</span>, <span class="comment">// Error 没有定义这个类型的属性</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上面 <code>obj.e</code> 由于没有出现在类型定义中所以报错了，可以通过如下方式添加多余参数的定义：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="attr">obj</span>: &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="built_in">boolean</span>;</span><br><span class="line">  [<span class="attr">prop</span>: <span class="built_in">string</span>]: <span class="built_in">any</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">e</span>: <span class="number">123</span>, <span class="comment">// OK</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上面 <code>[prop: string]: any</code> 表面属性值可以是任何类型的，所以不会报错，想想上面的 <code>obj.a</code> ，其中 <code>a</code> 相当于也是一个 <code>string</code> 类型，如果修改为<code>[prop: string]: string</code>，而 <code>a</code> 的类型是 <code>boolean</code> ，那么就会存在 <code>boolean</code> 和 <code>string</code> 冲突，所以就会报错。</p><h2 id="函数类型"><a href="#函数类型" class="headerlink" title="函数类型"></a>函数类型</h2><p>函数的参数可以是剩余参数，剩余参数类型可以是数组或元组：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 剩余参数是数组</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">...<span class="attr">args</span>: <span class="built_in">number</span>[]</span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 剩余参数是元组</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">...<span class="attr">args</span>: [<span class="built_in">boolean</span>, <span class="built_in">number</span>]</span>) &#123;&#125;</span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"><span class="attr">args_0</span>: <span class="built_in">boolean</span>, <span class="attr">args_1</span>: <span class="built_in">number</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 剩余参数是元组，且有可选值</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">...<span class="attr">args</span>: [<span class="built_in">boolean</span>, <span class="built_in">number</span>?]</span>) &#123;&#125;</span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"><span class="attr">args_0</span>: <span class="built_in">boolean</span>, <span class="attr">args_1</span>?: <span class="built_in">number</span></span>) &#123;&#125;</span><br></pre></td></tr></table></figure><p>通常在定义函数就已经确定好函数的类型了，但是你也可以给一个变量设置为函数的类型，这里有两种方式：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 函数的调用签名定义：</span></span><br><span class="line">&#123; (<span class="title class_">ParameterList</span>): <span class="title class_">Type</span> &#125;</span><br><span class="line"><span class="comment">// 简写为：</span></span><br><span class="line">(<span class="title class_">ParameterList</span>) =&gt; <span class="title class_">Type</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 如：</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">f</span>: &#123; (<span class="attr">x</span>: <span class="built_in">number</span>): <span class="built_in">void</span> &#125;;</span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">f</span>: <span class="function">(<span class="params"><span class="attr">x</span>: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br></pre></td></tr></table></figure><p><strong>类本质上是函数</strong>，类的签名可以用构造函数来表示，格式如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类的构造签名定义</span></span><br><span class="line">&#123; <span class="title function_">new</span> (<span class="title class_">ParameterList</span>): <span class="title class_">Type</span> &#125;</span><br><span class="line"><span class="comment">// 简写是</span></span><br><span class="line"><span class="title function_">new</span> (<span class="title class_">ParameterList</span>) =&gt; <span class="title class_">Type</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 如：</span></span><br><span class="line"><span class="keyword">let</span> <span class="attr">C</span>: &#123; <span class="title function_">new</span> (<span class="attr">name</span>: <span class="built_in">string</span>): <span class="title class_">Object</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等价于：let C: new (name: string) =&gt; Object;</span></span><br><span class="line"></span><br><span class="line">C = <span class="keyword">class</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="attr">name</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = <span class="keyword">new</span> <span class="title function_">C</span>(<span class="string">&#x27;name&#x27;</span>);</span><br></pre></td></tr></table></figure><p>构造签名和调用签名可以共存，如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Str</span> = &#123;</span><br><span class="line">  <span class="title function_">new</span> (<span class="attr">name</span>: <span class="built_in">string</span>): <span class="title class_">String</span>,</span><br><span class="line">  (<span class="attr">name</span>: <span class="built_in">string</span>): <span class="built_in">string</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">s</span>: <span class="title class_">Str</span> = <span class="title class_">String</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> <span class="title function_">s</span>(<span class="string">&#x27;name1&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> b = <span class="title function_">s</span>(<span class="string">&#x27;name2&#x27;</span>);</span><br></pre></td></tr></table></figure><p>上述类型 <code>Str</code> 可以通过 <code>new</code> 来创建 <code>String</code> 对象，也可以通过函数调用返回 <code>string</code> 类型；实际上 <code>String</code> 函数就属于这种类型。</p><h3 id="函数重载"><a href="#函数重载" class="headerlink" title="函数重载"></a>函数重载</h3><p>函数重载是指一个函数有多个同名的函数签名，如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">number</span>, <span class="attr">y</span>: <span class="built_in">number</span></span>): <span class="built_in">number</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">string</span>, <span class="attr">y</span>: <span class="built_in">string</span></span>): <span class="built_in">string</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">number</span> | <span class="built_in">string</span>, <span class="attr">y</span>: <span class="built_in">number</span> | <span class="built_in">string</span></span>) : <span class="built_in">number</span> | <span class="built_in">string</span> &#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">typeof</span> x === <span class="string">&#x27;number&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> y === <span class="string">&#x27;number&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">typeof</span> x === <span class="string">&#x27;string&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> y === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;类型不符&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">add</span>(<span class="string">&#x27;1&#x27;</span>, <span class="number">2</span>); <span class="comment">// 报错 函数允许x和y都是nunber或者都是string</span></span><br></pre></td></tr></table></figure><p>不带有函数体的函数声明语句叫做函数重载，它只提供函数的类型信息。重载函数由一条或多条函数重载语句以及一条函数实现语句构成。只有一条重载语句跟函数签名是对应的函数重载，是允许的，但通常没啥意义（一条的时候函数重载可以省略）。对于多条函数重载来说，每个函数重载中的函数名和函数实现中的函数名必须一致。同时函数重载语句与其他函数重载语句或函数实现语句之间不能出现其他语句，否则将产生编译错误。函数重载语句在函数编译后将会删除。在上述例子中，如果没有函数重载，只看函数实现则可以出现<code>x</code>是<code>nunber</code>，<code>y</code>是<code>string</code>这种情况，但是函数重载限制了这种情况。需要注意的是函数实现必须兼容所有的重载语句。</p><p>函数重载也可以通过对象字面量来表示，如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">add</span>: &#123;</span><br><span class="line">  (<span class="attr">x</span>: <span class="built_in">number</span>, <span class="attr">y</span>: <span class="built_in">number</span>): <span class="built_in">number</span>;</span><br><span class="line">  (<span class="attr">x</span>: <span class="built_in">string</span>, <span class="attr">y</span>: <span class="built_in">string</span>): <span class="built_in">string</span>;</span><br><span class="line">&#125; = <span class="keyword">function</span> <span class="title function_">add</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">any</span>, <span class="attr">y</span>: <span class="built_in">any</span></span>) : <span class="built_in">any</span> &#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">typeof</span> x === <span class="string">&#x27;number&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> y === <span class="string">&#x27;number&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">typeof</span> x === <span class="string">&#x27;string&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> y === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;类型不符&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">add</span>(<span class="string">&#x27;1&#x27;</span>, <span class="number">2</span>); <span class="comment">// 报错 函数允许x和y都是nunber或者都是string</span></span><br></pre></td></tr></table></figure><p>需要注意的是函数字面量相当于是先定义了add的类型，然后再给实现，所以实现的参数和返回类型一定要满足定义中的所有情况，上述实现中<code>x</code>使用了<code>any</code>类型，如果是<code>number | string</code>，则不符合定义函数中的任意一项，所以也会报错。</p><h3 id="函数重载解析顺序"><a href="#函数重载解析顺序" class="headerlink" title="函数重载解析顺序"></a>函数重载解析顺序</h3><p>当一个函数的实际参数数量不少于函数重载中的必须参数且不多于重载函数中定义的所以参数数量，同时实际参数的类型能够匹配函数重载中的参数，则认为这条函数重载符合函数定义，如果有多条符合的则从上到下解析。如下</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">any</span></span>): <span class="built_in">string</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">string</span></span>): <span class="number">0</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">any</span></span>): <span class="built_in">any</span> &#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">typeof</span> x === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;1&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">a</span>: <span class="number">0</span> = <span class="title function_">f</span>(<span class="string">&#x27;h1&#x27;</span>); <span class="comment">// 报错a的类型应该是string</span></span><br></pre></td></tr></table></figure><p>上述第一条函数重载和第二条函数重载都满足函数调用的参数，根据从上倒下应该选中第一条函数重载，该函数重载返回的是<code>string</code>类型，而不是<code>0</code>，所以报错。因此，一般写函数重载的时候我们应先定义范围更小的函数重载。</p><h3 id="函数中的this类型"><a href="#函数中的this类型" class="headerlink" title="函数中的this类型"></a>函数中的this类型</h3><p>通常我们在函数中使用 <code>this</code> 是不会报错的，但是如果 <code>--noImplicitThis=true</code> 的时候，则会报错。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"><span class="attr">bar</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">bar</span> = bar; <span class="comment">// --noImplicitThis=true的时候报错</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以在函数定义的时候第一个参数定义 <code>this</code> 的类型，但调用的时候需要调用<code>call</code>，<code>bind</code>，<code>apply</code>等来调用。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"><span class="attr">this</span>: &#123; bar: <span class="built_in">string</span> &#125;, <span class="attr">bar</span>: <span class="built_in">string</span></span>): <span class="built_in">any</span> &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">bar</span> = bar;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 上述编译后相当于是：</span></span><br><span class="line"><span class="comment"> * function foo(bar) &#123;</span></span><br><span class="line"><span class="comment"> *     this.bar = bar;</span></span><br><span class="line"><span class="comment"> * &#125;</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">foo</span>(<span class="string">&#x27;123&#x27;</span>); <span class="comment">// 报错 没有显示传this默认是void，与函数定义的类型不符</span></span><br><span class="line"></span><br><span class="line">foo.<span class="title function_">call</span>(&#123; <span class="attr">bar</span>: <span class="string">&#x27;&#x27;</span> &#125;, <span class="string">&#x27;123&#x27;</span>); <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line">foo.<span class="title function_">bind</span>(&#123; <span class="attr">bar</span>: <span class="string">&#x27;&#x27;</span> &#125;)(<span class="string">&#x27;123&#x27;</span>); <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line">foo.<span class="title function_">apply</span>(&#123; <span class="attr">bar</span>: <span class="string">&#x27;&#x27;</span> &#125;, [<span class="string">&#x27;123&#x27;</span>]); <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title function_">foo</span>(<span class="string">&#x27;123&#x27;</span>); <span class="comment">// 报错 Ts建议 通过class来创建对象</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">new</span> (foo <span class="keyword">as</span> <span class="built_in">any</span>)(<span class="string">&#x27;123&#x27;</span>); <span class="comment">// OK</span></span><br></pre></td></tr></table></figure><h2 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h2><p>接口可以定义任意对象类型，但无法表示原始类型。接口类型的成员可以是<strong>属性签名</strong>、<strong>调用签名</strong>、<strong>构造签名</strong>、<strong>方法签名</strong>和<strong>索引签名</strong>。另外接口可以多继承。通过一个例子看接口的各种用法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">TestInterface</span> &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="built_in">number</span>; <span class="comment">// 属性签名</span></span><br><span class="line">  (<span class="attr">message</span>?:<span class="built_in">string</span>): <span class="title class_">Error</span>; <span class="comment">// 调用签名</span></span><br><span class="line">  <span class="title function_">new</span> (<span class="attr">message</span>?:<span class="built_in">string</span>): <span class="title class_">Error</span>; <span class="comment">// 构造签名</span></span><br><span class="line">  <span class="title function_">getElementById</span>(<span class="attr">elementId</span>: <span class="built_in">string</span>): <span class="title class_">HTMLElement</span> | <span class="literal">null</span>; <span class="comment">// 方法签名</span></span><br><span class="line">  [<span class="attr">props</span>: <span class="built_in">string</span>]: <span class="built_in">any</span>; <span class="comment">// 索引签名</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">y</span>?: <span class="built_in">string</span>; <span class="comment">// 可选参数</span></span><br><span class="line">  z?(): <span class="built_in">number</span>; <span class="comment">// 可选参数是方法</span></span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">a</span>: <span class="built_in">number</span>; <span class="comment">// 只读属性</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">TestInterface2</span> &#123;</span><br><span class="line">  <span class="attr">b</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多继承</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">TestInterface3</span> <span class="keyword">extends</span> <span class="title class_">TestInterface</span>, <span class="title class_">TestInterface2</span> &#123;</span><br><span class="line">  <span class="attr">c</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 练习 数组也是对象 那么可以用接口定义数组</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">StringArray</span> &#123;</span><br><span class="line">  [<span class="attr">props</span>: <span class="built_in">number</span>]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">arr</span>: <span class="title class_">StringArray</span> = [<span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;2&#x27;</span>, <span class="string">&#x27;3&#x27;</span>]; <span class="comment">//OK</span></span><br></pre></td></tr></table></figure><h2 id="类型别名"><a href="#类型别名" class="headerlink" title="类型别名"></a>类型别名</h2><p>类型别名相当于给已有类型起了一个别名，它不会创建类型，但是可以给任意类型起别名。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">DecimalDigit</span> = <span class="number">0</span> | <span class="number">1</span> | <span class="number">2</span> | <span class="number">3</span> | <span class="number">4</span> | <span class="number">5</span> | <span class="number">6</span> | <span class="number">7</span> | <span class="number">8</span> | <span class="number">9</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">d</span>: <span class="title class_">DecimalDigit</span> = <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>类型别名与接口的比较：</p><ol><li>类型别名能够表示非对象类型，而接口不行。</li><li>接口可以继承其他接口、类等对象类型。而类型别名不能继承（但可以通过交叉类型 <code>&amp;</code> 来实现类似的功能）；</li><li>错误提醒对类型别名引用对应的类型，而接口引用接口名。</li><li>接口可以同名，同名接口对应的值会合并，但是类型别名不能同名。</li></ol><h2 id="类"><a href="#类" class="headerlink" title="类"></a>类</h2><p>TypeScript的类与JavaScript的类大多数语法都是类似的，但TypeScript对类的一下功能做了扩充，如接口实现、泛型类等。一个简单的示例：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> IA &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> <span class="keyword">implements</span> <span class="variable constant_">IA</span> &#123; <span class="comment">// 可以继承接口</span></span><br><span class="line">  <span class="attr">a</span>: <span class="built_in">number</span> = <span class="number">0</span>; <span class="comment">// 定义一个属性a</span></span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">b</span>: <span class="built_in">number</span> = <span class="number">1</span>; <span class="comment">// 定义一个只读属性b 只读属性必须赋初始值</span></span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">c</span>: <span class="built_in">number</span>; <span class="comment">// 只读属性初始值在构造函数中赋值 如果不赋值则报错</span></span><br><span class="line">  <span class="keyword">private</span> <span class="attr">_d</span>: <span class="built_in">number</span> = <span class="number">3</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="attr">e</span>: <span class="built_in">string</span> = <span class="string">&#x27;4&#x27;</span>; <span class="comment">//静态属性</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123; <span class="comment">// 构造函数不能定义返回类型</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">c</span> = <span class="number">2</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">say</span>(): <span class="built_in">void</span> &#123; <span class="comment">// 成员方法</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">get</span> <span class="title function_">d</span>(): <span class="built_in">number</span> &#123; <span class="comment">// getter</span></span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">_d</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">set</span> <span class="title function_">d</span>(<span class="params">value</span>) &#123; <span class="comment">// setter</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">_d</span> = value;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> <span class="keyword">extends</span> <span class="title class_ inherited__">A</span> &#123; <span class="comment">// 继承</span></span><br><span class="line">  <span class="attr">f</span>: <span class="built_in">string</span> = <span class="string">&#x27;5&#x27;</span>; <span class="comment">// 子类特有的属性</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="类的可访问性"><a href="#类的可访问性" class="headerlink" title="类的可访问性"></a>类的可访问性</h3><p>访问修饰符：</p><blockquote><p><code>public</code>(默认): 当前类的内部、外部以及派生类的内部均可访问，不写访问修饰符默认就是<code>public</code>。<br><code>protected</code>: 在当前类和派生类内部可以访问，不允许当前类外部（如创建的对象）访问。<br><code>private</code>: 只有当前类的内部可以访问。</p></blockquote><p>ES13类的私有字段TS也是支持的，私有字段仅有类内部可以访问：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">  #<span class="attr">b</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.#b = <span class="number">1</span>; <span class="comment">// 内部可以访问</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> a = <span class="keyword">new</span> <span class="title function_">A</span>();</span><br><span class="line">a.#b; <span class="comment">// Error 不能访问</span></span><br></pre></td></tr></table></figure><h3 id="参数成员"><a href="#参数成员" class="headerlink" title="参数成员"></a>参数成员</h3><p>在类的构造函数的参数中使用<strong>访问修饰符或readonly</strong>修饰，则该参数自动成为类的成员变量，不需要在构造函数中使用 <code>this.a = a;</code> 这样的语句。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params">    <span class="keyword">public</span> <span class="attr">b</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">    <span class="keyword">protected</span> <span class="attr">c</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">    <span class="keyword">private</span> <span class="attr">d</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">    <span class="keyword">readonly</span> <span class="attr">e</span>: <span class="built_in">number</span>,</span></span><br><span class="line"><span class="params">  </span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">b</span>, <span class="variable language_">this</span>.<span class="property">c</span>, <span class="variable language_">this</span>.<span class="property">d</span>, <span class="variable language_">this</span>.<span class="property">e</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> a = <span class="keyword">new</span> <span class="title function_">A</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>); <span class="comment">// 打印1, 2, 3, 4</span></span><br><span class="line">a.<span class="property">b</span>; <span class="comment">// 返回1</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;区域注释&quot;&gt;&lt;a href=&quot;#区域注释&quot; class=&quot;headerlink&quot; title=&quot;区域注释&quot;&gt;&lt;/a&gt;区域注释&lt;/h2&gt;&lt;p&gt;TypeScript 可以添加区域注释，可以让VS Code等编辑器识别为一个代码区域，区域注释使用的是单行注释语法：&lt;/p</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="TypeScript" scheme="https://www.kai666666.com/tags/TypeScript/"/>
    
  </entry>
  
  <entry>
    <title>源码共读-Redux</title>
    <link href="https://www.kai666666.com/2023/05/23/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-Redux/"/>
    <id>https://www.kai666666.com/2023/05/23/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-Redux/</id>
    <published>2023-05-23T15:52:44.000Z</published>
    <updated>2026-02-10T08:00:34.492Z</updated>
    
    <content type="html"><![CDATA[<p>Redux是优秀的状态管理库，本节我们学习一下Redux源码，由于Redux源码是TypeScript写的，为了方便学习，本节去掉一些类型定义，转化为JavaScript来展示，另外对于错误信息我们这里就先不处理了。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>Redux官方示例：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createStore &#125; <span class="keyword">from</span> <span class="string">&#x27;redux&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">counterReducer</span>(<span class="params">state = &#123; value: <span class="number">0</span> &#125;, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;counter/incremented&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">value</span>: state.<span class="property">value</span> + <span class="number">1</span> &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;counter/decremented&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">value</span>: state.<span class="property">value</span> - <span class="number">1</span> &#125;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> store = <span class="title function_">createStore</span>(counterReducer)</span><br><span class="line"></span><br><span class="line">store.<span class="title function_">subscribe</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(store.<span class="title function_">getState</span>()))</span><br><span class="line"></span><br><span class="line">store.<span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;counter/incremented&#x27;</span> &#125;) <span class="comment">// &#123;value: 1&#125;</span></span><br><span class="line">store.<span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;counter/incremented&#x27;</span> &#125;) <span class="comment">// &#123;value: 2&#125;</span></span><br><span class="line">store.<span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;counter/decremented&#x27;</span> &#125;) <span class="comment">// &#123;value: 1&#125;</span></span><br></pre></td></tr></table></figure><h2 id="createStore"><a href="#createStore" class="headerlink" title="createStore"></a>createStore</h2><p><code>createStore</code>简易实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">randomString</span> = (<span class="params"></span>) =&gt;</span><br><span class="line">  <span class="title class_">Math</span>.<span class="title function_">random</span>().<span class="title function_">toString</span>(<span class="number">36</span>).<span class="title function_">substring</span>(<span class="number">7</span>).<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>).<span class="title function_">join</span>(<span class="string">&#x27;.&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ActionTypes</span> = &#123;</span><br><span class="line">  <span class="attr">INIT</span>: <span class="string">`@@redux/INIT<span class="subst">$&#123;/* #__PURE__ */ randomString()&#125;</span>`</span>,</span><br><span class="line">  <span class="attr">REPLACE</span>: <span class="string">`@@redux/REPLACE<span class="subst">$&#123;/* #__PURE__ */ randomString()&#125;</span>`</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createStore</span>(<span class="params">reducer, preloadedState, enhancer</span>) &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果第二个参数是函数 第三个函数没有值的时候 认为第二个参数是enhancer</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> preloadedState === <span class="string">&#x27;function&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> enhancer === <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">    enhancer = preloadedState</span><br><span class="line">    preloadedState = <span class="literal">undefined</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// enhancer有值的时候 使用enhancer来构建store</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> enhancer !== <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">enhancer</span>(createStore)(</span><br><span class="line">      reducer,</span><br><span class="line">      preloadedState</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> currentReducer = reducer</span><br><span class="line">  <span class="keyword">let</span> currentState = preloadedState</span><br><span class="line">  <span class="keyword">let</span> listeners = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">getState</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> currentState</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">subscribe</span>(<span class="params">listener</span>) &#123;</span><br><span class="line">    listeners.<span class="title function_">push</span>(listener)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span> <span class="title function_">unsubscribe</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> index = listeners.<span class="title function_">indexOf</span>(listener)</span><br><span class="line">      listeners.<span class="title function_">splice</span>(index, <span class="number">1</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">dispatch</span>(<span class="params">action</span>) &#123;</span><br><span class="line">    currentState = <span class="title function_">currentReducer</span>(currentState, action)</span><br><span class="line"></span><br><span class="line">    listeners.<span class="title function_">forEach</span>(<span class="function"><span class="params">listener</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">listener</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> action</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">replaceReducer</span>(<span class="params">nextReducer</span>) &#123;</span><br><span class="line">    currentReducer = nextReducer</span><br><span class="line">    <span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="title class_">ActionTypes</span>.<span class="property">REPLACE</span> &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="title class_">ActionTypes</span>.<span class="property">INIT</span> &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> store = &#123;</span><br><span class="line">    <span class="attr">dispatch</span>: dispatch,</span><br><span class="line">    subscribe,</span><br><span class="line">    getState,</span><br><span class="line">    replaceReducer,</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> store</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述是<code>createStore</code>的简单实现，调用<code>createStore()</code>函数以后返回一个<code>store</code>对象，该对象有4个方法，如下：</p><p>1：<code>dispatch</code>：分发action，通过<code>currentReducer(currentState, action)</code>来生成新的state，并触发事件。<br>2: <code>subscribe</code>: 监听事件，实际上就是把事件添加到事件数组中，并返回移除事件函数。<br>3: <code>getState</code>：获取当前的状态。<br>4: <code>replaceReducer</code>：替换reducer。</p><p>最新的源码与我们的实现理念大致相同，只是多了类型的校验，另外事件采用双map形式（防止dispatch中调用subscribe&#x2F;unsubscribe）而不是我们简单的数组，最后在事件触发时会使用变量标记，防止在分发过程中出现不合理的操作。</p><h2 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a>中间件</h2><h3 id="中间件简绍"><a href="#中间件简绍" class="headerlink" title="中间件简绍"></a>中间件简绍</h3><p>写一个redux中间件很简单，比如写一个打印日志的中间件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> logger = <span class="keyword">function</span>(<span class="params">store</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params">next</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params">action</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;prev state:&#x27;</span>, store.<span class="title function_">getState</span>()) <span class="comment">// 更新前的状态</span></span><br><span class="line">      <span class="keyword">let</span> result = <span class="title function_">next</span>(action)</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;next state&#x27;</span>, store.<span class="title function_">getState</span>()) <span class="comment">// 更新后的状态</span></span><br><span class="line">      <span class="keyword">return</span> result</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 箭头函数简写</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">logger</span> = store =&gt; <span class="function"><span class="params">next</span> =&gt;</span> <span class="function"><span class="params">action</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;prev state:&#x27;</span>, store.<span class="title function_">getState</span>()) <span class="comment">// 更新前的状态</span></span><br><span class="line">  <span class="keyword">let</span> result = <span class="title function_">next</span>(action)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;next state&#x27;</span>, store.<span class="title function_">getState</span>()) <span class="comment">// 更新后的状态</span></span><br><span class="line">  <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>中间件是一个嵌套三层的函数，每一层都有一个参数，参数分别是<code>store</code>、<code>next</code>、<code>action</code>。上面是<code>redux-logger</code>中间件的简单实现，常用的中间件还有<code>redux-thunk</code>，核心代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">thunk</span> = (<span class="params">&#123; dispatch, getState &#125;</span>) =&gt; <span class="function"><span class="params">next</span> =&gt;</span> <span class="function"><span class="params">action</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> action === <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">action</span>(dispatch, getState)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">next</span>(action)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>redux-thunk</code>的逻辑也很简单，通过对<code>store</code>解构获取<code>dispatch</code>和<code>getState</code>函数，如果<code>action</code>是函数则调用<code>action</code>，否则调用<code>next(action)</code>进行下一个中间件。在<code>action</code>函数中可以通过<code>dispatch</code>来触发<code>action</code>，哪怕是在异步的回调中，所以<code>redux-thunk</code>通常用来处理异步操作。</p><h3 id="中间件使用"><a href="#中间件使用" class="headerlink" title="中间件使用"></a>中间件使用</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createStore, applyMiddleware &#125; <span class="keyword">from</span> <span class="string">&#x27;redux&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">counterReducer</span>(<span class="params">state = &#123; value: <span class="number">0</span> &#125;, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;counter/incremented&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">value</span>: state.<span class="property">value</span> + <span class="number">1</span> &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;counter/decremented&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">value</span>: state.<span class="property">value</span> - <span class="number">1</span> &#125;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 带有中间件的store</span></span><br><span class="line"><span class="keyword">let</span> store = <span class="title function_">createStore</span>(counterReducer, <span class="title function_">applyMiddleware</span>(thunk, logger))</span><br><span class="line"></span><br><span class="line">store.<span class="title function_">subscribe</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(store.<span class="title function_">getState</span>()))</span><br><span class="line"></span><br><span class="line">store.<span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;counter/incremented&#x27;</span> &#125;)</span><br><span class="line"><span class="comment">// 打印：</span></span><br><span class="line"><span class="comment">// prev state: &#123; value: 0 &#125;</span></span><br><span class="line"><span class="comment">// &#123; value: 1 &#125;</span></span><br><span class="line"><span class="comment">// next state &#123; value: 1 &#125;</span></span><br><span class="line"></span><br><span class="line">store.<span class="title function_">dispatch</span>(<span class="function">(<span class="params">dispatch, getState</span>) =&gt;</span> &#123; <span class="comment">// 异步操作</span></span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;counter/incremented&#x27;</span> &#125;)</span><br><span class="line">  &#125;, <span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 1秒后打印：</span></span><br><span class="line"><span class="comment">// prev state: &#123; value: 1 &#125;</span></span><br><span class="line"><span class="comment">// &#123; value: 2 &#125;</span></span><br><span class="line"><span class="comment">// next state &#123; value: 2 &#125;</span></span><br></pre></td></tr></table></figure><p>需要注意的是<code>applyMiddleware</code>是有顺序的，它会从做左到右依次执行，next后是从右到左执行。如果上述示例修改为<code>applyMiddleware(logger, thunk)</code>会发生什么事呢？那么在调用<code>store.dispatch(() =&gt; &#123;&#125;)</code>的时候也会打印日志，里面的<code>dispatch</code>又会打印一次。</p><h3 id="applyMiddleware实现"><a href="#applyMiddleware实现" class="headerlink" title="applyMiddleware实现"></a>applyMiddleware实现</h3><p>由上<code>createStore</code>函数可知，当传入中间件的时候会通过<code>enhancer</code>来生成store。<code>enhancer</code>实际上就是<code>applyMiddleware(logger, thunk)</code>的结果，它是一个两层函数，第一层接受的参数是<code>createStore</code>第二次接受的参数是<code>reducer</code>和<code>preloadedState</code>，代码大致如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">compose</span>(<span class="params">...funcs</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (funcs.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">(<span class="params">arg</span>) =&gt;</span> arg</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (funcs.<span class="property">length</span> === <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> funcs[<span class="number">0</span>]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> funcs.<span class="title function_">reduce</span>(</span><br><span class="line">    <span class="function">(<span class="params">a, b</span>) =&gt;</span></span><br><span class="line">      <span class="function">(<span class="params">...args</span>) =&gt;</span></span><br><span class="line">        <span class="title function_">a</span>(<span class="title function_">b</span>(...args))</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">applyMiddleware</span>(<span class="params">...middlewares</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="params">createStore</span> =&gt;</span> <span class="function">(<span class="params">reducer, preloadedState</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> store = <span class="title function_">createStore</span>(reducer, preloadedState)</span><br><span class="line">    <span class="keyword">let</span> <span class="title function_">dispatch</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(</span><br><span class="line">        <span class="string">&#x27;Dispatching while constructing your middleware is not allowed. &#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;Other middleware would not be applied to this dispatch.&#x27;</span></span><br><span class="line">      )</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> middlewareAPI = &#123;</span><br><span class="line">      <span class="attr">getState</span>: store.<span class="property">getState</span>,</span><br><span class="line">      <span class="attr">dispatch</span>: <span class="function">(<span class="params">action, ...args</span>) =&gt;</span> <span class="title function_">dispatch</span>(action, ...args)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> chain = middlewares.<span class="title function_">map</span>(<span class="function"><span class="params">middleware</span> =&gt;</span> <span class="title function_">middleware</span>(middlewareAPI))</span><br><span class="line">    dispatch = <span class="title function_">compose</span>(...chain)(store.<span class="property">dispatch</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      ...store,</span><br><span class="line">      dispatch</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先通过<code>createStore(reducer, preloadedState)</code>不传中间件来创建<code>store</code>，<code>applyMiddleware</code>内层函数的返回值只有<code>dispatch</code>是处理过的函数，其他的都是与<code>store</code>中的一致，也就是说中间件的作用实际上是强化<code>dispatch</code>函数。<code>middlewareAPI</code>实际上就是中间件的第一层函数的参数<code>store</code>，这里需要注意的是<code>dispatch</code>调用的时候，下面的代码已经走完了，所以里面的<code>dispatch</code>函数是加强后的<code>dispatch</code>而不是上面定义的抛出异常的函数。通过<code>middlewares.map(middleware =&gt; middleware(middlewareAPI))</code>去掉了中间件第一层函数。<code>compose</code>核心逻辑是<code>funcs.reduce((a, b) =&gt; (...args) =&gt; a(b(...args)))</code>对于函数数组返回嵌套执行的组合函数，<code>compose(...chain)(store.dispatch)</code>最后参数是<code>store.dispatch</code>，比如有两个中间件<code>a</code>和<code>b</code>，这里相当于变成<code>a(b(store.dispatch))</code>，相当于a这一层的<code>next</code>函数是<code>b(store.dispatch)</code>函数。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Redux是优秀的状态管理库，本节我们学习一下Redux源码，由于Redux源码是TypeScript写的，为了方便学习，本节去掉一些类型定义，转化为JavaScript来展示，另外对于错误信息我们这里就先不处理了。&lt;/p&gt;
&lt;h2 id=&quot;使用&quot;&gt;&lt;a href=&quot;#使用</summary>
      
    
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/categories/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/tags/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>源码共读-Koa</title>
    <link href="https://www.kai666666.com/2023/05/23/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-Koa/"/>
    <id>https://www.kai666666.com/2023/05/23/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-Koa/</id>
    <published>2023-05-23T09:54:49.000Z</published>
    <updated>2026-02-10T08:00:34.492Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://koa.bootcss.com/">Koa</a>是基于 Node.js 平台的下一代 web 开发框架，它的源码可以<a href="https://github.com/koajs/koa">看这里</a>，本章通过源码来简绍一下Koa是怎么实现的。</p><h2 id="核心代码"><a href="#核心代码" class="headerlink" title="核心代码"></a>核心代码</h2><p>Koa的核心代码只有4个文件，如图。</p><p><img src="/2023/05/23/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-Koa/1.png" alt="核心代码"></p><p>各个文件的作用：</p><p><code>application.js</code>：Koa的核心，对应Koa App类。<br><code>context.js</code>：对应上下文对象ctx。<br><code>request.js</code>：对应ctx.request对象。<br><code>response.js</code>：对应ctx.response对象。</p><h2 id="Koa实现"><a href="#Koa实现" class="headerlink" title="Koa实现"></a>Koa实现</h2><h3 id="Koa使用"><a href="#Koa使用" class="headerlink" title="Koa使用"></a>Koa使用</h3><p>Koa使用如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Koa</span> = <span class="built_in">require</span>(<span class="string">&#x27;koa&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title class_">Koa</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">  ctx.<span class="property">body</span> = <span class="string">&#x27;Hello World&#x27;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;服务器启动成功!&quot;</span>);、</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Koa底层是基于原生http模块，原生http模块怎么启动一个服务呢？如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = http.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">req, res</span>) &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span> &#125;);</span><br><span class="line">  res.<span class="title function_">end</span>(<span class="string">&quot;Hello World&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;服务器启动成功!&quot;</span>);、</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>观察上面的代码，两者是不是挺像的。</p><h3 id="application源码"><a href="#application源码" class="headerlink" title="application源码"></a>application源码</h3><p>为了方便查看application的核心逻辑，下面是我去掉了部分非核心代码的application源码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> onFinished = <span class="built_in">require</span>(<span class="string">&#x27;on-finished&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> response = <span class="built_in">require</span>(<span class="string">&#x27;./response&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> compose = <span class="built_in">require</span>(<span class="string">&#x27;koa-compose&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> context = <span class="built_in">require</span>(<span class="string">&#x27;./context&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> request = <span class="built_in">require</span>(<span class="string">&#x27;./request&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> statuses = <span class="built_in">require</span>(<span class="string">&#x27;statuses&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Emitter</span> = <span class="built_in">require</span>(<span class="string">&#x27;events&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Stream</span> = <span class="built_in">require</span>(<span class="string">&#x27;stream&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Application</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Emitter</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params">options</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>()</span><br><span class="line">    options = options || &#123;&#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">env</span> = options.<span class="property">env</span> || process.<span class="property">env</span>.<span class="property">NODE_ENV</span> || <span class="string">&#x27;development&#x27;</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">compose</span> = options.<span class="property">compose</span> || compose</span><br><span class="line">    <span class="keyword">if</span> (options.<span class="property">keys</span>) <span class="variable language_">this</span>.<span class="property">keys</span> = options.<span class="property">keys</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">middleware</span> = []</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">context</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(context)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">request</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(request)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">response</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(response)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">listen</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> server = http.<span class="title function_">createServer</span>(<span class="variable language_">this</span>.<span class="title function_">callback</span>())</span><br><span class="line">    <span class="keyword">return</span> server.<span class="title function_">listen</span>(...args)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">use</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> fn !== <span class="string">&#x27;function&#x27;</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;middleware must be a function!&#x27;</span>)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">middleware</span>.<span class="title function_">push</span>(fn)</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">callback</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> fn = <span class="variable language_">this</span>.<span class="title function_">compose</span>(<span class="variable language_">this</span>.<span class="property">middleware</span>)</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="title function_">listenerCount</span>(<span class="string">&#x27;error&#x27;</span>)) <span class="variable language_">this</span>.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="variable language_">this</span>.<span class="property">onerror</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">handleRequest</span> = (<span class="params">req, res</span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> ctx = <span class="variable language_">this</span>.<span class="title function_">createContext</span>(req, res)</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">handleRequest</span>(ctx, fn)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> handleRequest</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">handleRequest</span>(<span class="params">ctx, fnMiddleware</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> res = ctx.<span class="property">res</span></span><br><span class="line">    res.<span class="property">statusCode</span> = <span class="number">404</span></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onerror</span> = err =&gt; ctx.<span class="title function_">onerror</span>(err)</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">handleResponse</span> = (<span class="params"></span>) =&gt; <span class="title function_">respond</span>(ctx)</span><br><span class="line">    <span class="title function_">onFinished</span>(res, onerror)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">fnMiddleware</span>(ctx).<span class="title function_">then</span>(handleResponse).<span class="title function_">catch</span>(onerror)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">createContext</span>(req, res)</span><br><span class="line">    <span class="keyword">const</span> context = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="variable language_">this</span>.<span class="property">context</span>)</span><br><span class="line">    <span class="keyword">const</span> request = context.<span class="property">request</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="variable language_">this</span>.<span class="property">request</span>)</span><br><span class="line">    <span class="keyword">const</span> response = context.<span class="property">response</span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="variable language_">this</span>.<span class="property">response</span>)</span><br><span class="line">    context.<span class="property">app</span> = request.<span class="property">app</span> = response.<span class="property">app</span> = <span class="variable language_">this</span></span><br><span class="line">    context.<span class="property">req</span> = request.<span class="property">req</span> = response.<span class="property">req</span> = req</span><br><span class="line">    context.<span class="property">res</span> = request.<span class="property">res</span> = response.<span class="property">res</span> = res</span><br><span class="line">    request.<span class="property">ctx</span> = response.<span class="property">ctx</span> = context</span><br><span class="line">    request.<span class="property">response</span> = response</span><br><span class="line">    response.<span class="property">request</span> = request</span><br><span class="line">    context.<span class="property">originalUrl</span> = request.<span class="property">originalUrl</span> = req.<span class="property">url</span></span><br><span class="line">    context.<span class="property">state</span> = &#123;&#125;</span><br><span class="line">    <span class="keyword">return</span> context</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">onerror</span>(<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.<span class="property">status</span> === <span class="number">404</span> || err.<span class="property">expose</span>) <span class="keyword">return</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">silent</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> msg = err.<span class="property">stack</span> || err.<span class="title function_">toString</span>()</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`\n<span class="subst">$&#123;msg.replace(/^/gm, <span class="string">&#x27;  &#x27;</span>)&#125;</span>\n`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">respond</span> (<span class="params">ctx</span>) &#123;</span><br><span class="line">  <span class="comment">// allow bypassing koa</span></span><br><span class="line">  <span class="keyword">if</span> (ctx.<span class="property">respond</span> === <span class="literal">false</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!ctx.<span class="property">writable</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> res = ctx.<span class="property">res</span></span><br><span class="line">  <span class="keyword">let</span> body = ctx.<span class="property">body</span></span><br><span class="line">  <span class="keyword">const</span> code = ctx.<span class="property">status</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// ignore body</span></span><br><span class="line">  <span class="keyword">if</span> (statuses.<span class="property">empty</span>[code]) &#123;</span><br><span class="line">    <span class="comment">// strip headers</span></span><br><span class="line">    ctx.<span class="property">body</span> = <span class="literal">null</span></span><br><span class="line">    <span class="keyword">return</span> res.<span class="title function_">end</span>()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (ctx.<span class="property">method</span> === <span class="string">&#x27;HEAD&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!res.<span class="property">headersSent</span> &amp;&amp; !ctx.<span class="property">response</span>.<span class="title function_">has</span>(<span class="string">&#x27;Content-Length&#x27;</span>)) &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123; length &#125; = ctx.<span class="property">response</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Number</span>.<span class="title function_">isInteger</span>(length)) ctx.<span class="property">length</span> = length</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res.<span class="title function_">end</span>()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// status body</span></span><br><span class="line">  <span class="keyword">if</span> (body == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (ctx.<span class="property">response</span>.<span class="property">_explicitNullBody</span>) &#123;</span><br><span class="line">      ctx.<span class="property">response</span>.<span class="title function_">remove</span>(<span class="string">&#x27;Content-Type&#x27;</span>)</span><br><span class="line">      ctx.<span class="property">response</span>.<span class="title function_">remove</span>(<span class="string">&#x27;Transfer-Encoding&#x27;</span>)</span><br><span class="line">      ctx.<span class="property">length</span> = <span class="number">0</span></span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">end</span>()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (ctx.<span class="property">req</span>.<span class="property">httpVersionMajor</span> &gt;= <span class="number">2</span>) &#123;</span><br><span class="line">      body = <span class="title class_">String</span>(code)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      body = ctx.<span class="property">message</span> || <span class="title class_">String</span>(code)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!res.<span class="property">headersSent</span>) &#123;</span><br><span class="line">      ctx.<span class="property">type</span> = <span class="string">&#x27;text&#x27;</span></span><br><span class="line">      ctx.<span class="property">length</span> = <span class="title class_">Buffer</span>.<span class="title function_">byteLength</span>(body)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res.<span class="title function_">end</span>(body)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// responses</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Buffer</span>.<span class="title function_">isBuffer</span>(body)) <span class="keyword">return</span> res.<span class="title function_">end</span>(body)</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> body === <span class="string">&#x27;string&#x27;</span>) <span class="keyword">return</span> res.<span class="title function_">end</span>(body)</span><br><span class="line">  <span class="keyword">if</span> (body <span class="keyword">instanceof</span> <span class="title class_">Stream</span>) <span class="keyword">return</span> body.<span class="title function_">pipe</span>(res)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// body: json</span></span><br><span class="line">  body = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(body)</span><br><span class="line">  <span class="keyword">if</span> (!res.<span class="property">headersSent</span>) &#123;</span><br><span class="line">    ctx.<span class="property">length</span> = <span class="title class_">Buffer</span>.<span class="title function_">byteLength</span>(body)</span><br><span class="line">  &#125;</span><br><span class="line">  res.<span class="title function_">end</span>(body)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当调用<code>app.use</code>的时候，实际上是把中间件函数加入到<code>this.middleware</code>数组当中。<br>当调用<code>app.listen</code>的时候，通过<code>http.createServer</code>来创建http服务并使用<code>server.listen</code>来监听服务。<br>这里比较难理解的是<code>callback</code>函数，它使用<code>compose</code>将中间件合并成一个调用函数，具体怎么合并的我们稍后再说。如果<code>error</code>事件没有监听的话，添加一个默认的监听函数，默认的<code>onerror</code>函数实际上就是打印错误信息；<code>this.listenerCount</code>是从哪里来的呢？实际上<code>Application</code>类是继承自node中的<code>Emitter</code>，该方法也是<a href="https://nodejs.org/dist/latest-v18.x/docs/api/events.html#emitterlistenercounteventname-listener">Emitter的方法</a>。最后返回了一个<code>handleRequest</code>函数，该函数做了2件事，首先通过<code>req</code>和<code>res</code>构建<code>ctx</code>，然后调用<code>this.handleRequest</code>，注意<code>this.handleRequest</code>是<code>Application</code>类的属性而不是<code>callback</code>中的<code>handleRequest</code>，也就是这里并没有递归调用。<br>在<code>this.handleRequest</code>函数中调用了中间件函数<code>fnMiddleware(ctx)</code>，当中间件函数都调用完了以后调用<code>respond(ctx)</code>，<code>respond</code>通过不同的情况去处理res的结果；失败的时候调用<code>ctx.onerror(err)</code>。另外在中间件处理之前会调用<code>onFinished(res, onerror)</code>来监听出错的情况，onFinished的代码请<a href="https://github.com/jshttp/on-finished">看这里</a>。</p><h3 id="koa-compose源码"><a href="#koa-compose源码" class="headerlink" title="koa-compose源码"></a>koa-compose源码</h3><p>在讲述源码之前我们先看看<code>koa-compose</code>中间件是怎么使用的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Koa</span> = <span class="built_in">require</span>(<span class="string">&#x27;koa&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title class_">Koa</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">async</span>(ctx, next) =&gt; &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第1个中间件开始&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第1个中间件结束&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">async</span>(ctx, next) =&gt; &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第2个中间件开始&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第2个中间件结束&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">async</span>(ctx, next) =&gt; &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第3个中间件开始&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第3个中间件结束&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;服务器启动成功!&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>客户端打印:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">第1个中间件开始</span><br><span class="line">第2个中间件开始</span><br><span class="line">第3个中间件开始</span><br><span class="line">第3个中间件结束</span><br><span class="line">第2个中间件结束</span><br><span class="line">第1个中间件结束</span><br></pre></td></tr></table></figure><p>这就是Koa中间件著名的洋葱模型。</p><p><img src="/2023/05/23/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB-Koa/2.png" alt="洋葱模型"></p><p>我们先不谈Koa只看看<code>koa-compose</code>做了什么事。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> compose = <span class="built_in">require</span>(<span class="string">&#x27;koa-compose&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> middleware = [</span><br><span class="line">  <span class="title function_">async</span>(ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第1个中间件开始&#x27;</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第1个中间件结束&#x27;</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">async</span>(ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第2个中间件开始&#x27;</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第2个中间件结束&#x27;</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">async</span>(ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第3个中间件开始&#x27;</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;第3个中间件结束&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">];</span><br><span class="line"><span class="keyword">const</span> fn = <span class="title function_">compose</span>(middleware);</span><br><span class="line"><span class="keyword">const</span> ctx = &#123;&#125;;</span><br><span class="line"><span class="title function_">fn</span>(ctx).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;处理完成了&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>上面打印：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">第1个中间件开始</span><br><span class="line">第2个中间件开始</span><br><span class="line">第3个中间件开始</span><br><span class="line">第3个中间件结束</span><br><span class="line">第2个中间件结束</span><br><span class="line">第1个中间件结束</span><br><span class="line">处理完成了</span><br></pre></td></tr></table></figure><p><code>koa-compose</code>把多个中间件合并成一个函数，通过<code>await next()</code>来调用下一个中间件，其源码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">compose</span> (<span class="params">middleware</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="title class_">Array</span>.<span class="title function_">isArray</span>(middleware)) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;Middleware stack must be an array!&#x27;</span>)</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> fn <span class="keyword">of</span> middleware) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> fn !== <span class="string">&#x27;function&#x27;</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;Middleware must be composed of functions!&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params">context, next</span>) &#123;</span><br><span class="line">    <span class="comment">// last called middleware #</span></span><br><span class="line">    <span class="keyword">let</span> index = -<span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">dispatch</span>(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">dispatch</span> (<span class="params">i</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (i &lt;= index) <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;next() called multiple times&#x27;</span>))</span><br><span class="line">      index = i</span><br><span class="line">      <span class="keyword">let</span> fn = middleware[i]</span><br><span class="line">      <span class="keyword">if</span> (i === middleware.<span class="property">length</span>) fn = next</span><br><span class="line">      <span class="keyword">if</span> (!fn) <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>()</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title function_">fn</span>(context, dispatch.<span class="title function_">bind</span>(<span class="literal">null</span>, i + <span class="number">1</span>)))</span><br><span class="line">      &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(err)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先对<code>middleware</code>做类型检查，<code>middleware</code>必须是数组，同时每一个中间件必须是函数。然后返回一个函数，这个函数第一个参数是上下文对象，第二个参数是下个中间件执行的next函数。核心逻辑是上面的<code>dispatch</code>方法，在<code>dispatch</code>方法中会返回Promise。<code>dispatch</code>方法实际上就是next方法，首次会调用<code>dispatch(0)</code>来触发第一个中间件函数。当一个中间件中调用next方法后会把<code>index</code>标记为当前的索引，如果一个中间件多次调用<code>next</code>方法，那么由于第一次调用是<code>index</code>会标记为<code>i</code>，那么第二次调用的时候<code>i</code>和<code>index</code>是相等的，也就是第二次的时候会走<code>if (i &lt;= index) return Promise.reject(new Error(&#39;next() called multiple times&#39;))</code>逻辑，也就是会报错。每次调用的时候根据索引获取当前要执行的中间件函数，在第18行会执行当前中间件，并把下一个<code>dispatch</code>当作第二个参数<code>next</code>传入到下一个中间件中。当执行到最后一个中间件的时候，设置<code>fn = next</code>由于Application代码的第52行并没有传递第二个参数，所以此时<code>next</code>是<code>undefined</code>，那么<code>compose</code>中将会走第16行<code>if (!fn) return Promise.resolve()</code>的逻辑。如果传递了函数那么会执行传入的函数，当此函数中调用next以后，由于索引已经超过了<code>middleware</code>的长度，所以下次函数执行事也会走第16行的逻辑。</p><h3 id="context源码"><a href="#context源码" class="headerlink" title="context源码"></a>context源码</h3><p><code>context</code>是对上下文对象的封装，具体代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">&#x27;util&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> createError = <span class="built_in">require</span>(<span class="string">&#x27;http-errors&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> httpAssert = <span class="built_in">require</span>(<span class="string">&#x27;http-assert&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> delegate = <span class="built_in">require</span>(<span class="string">&#x27;delegates&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> statuses = <span class="built_in">require</span>(<span class="string">&#x27;statuses&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Cookies</span> = <span class="built_in">require</span>(<span class="string">&#x27;cookies&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">COOKIES</span> = <span class="title class_">Symbol</span>(<span class="string">&#x27;context#cookies&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> proto = <span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="title function_">inspect</span> () &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span> === proto) <span class="keyword">return</span> <span class="variable language_">this</span></span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">toJSON</span>()</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">toJSON</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">request</span>: <span class="variable language_">this</span>.<span class="property">request</span>.<span class="title function_">toJSON</span>(),</span><br><span class="line">      <span class="attr">response</span>: <span class="variable language_">this</span>.<span class="property">response</span>.<span class="title function_">toJSON</span>(),</span><br><span class="line">      <span class="attr">app</span>: <span class="variable language_">this</span>.<span class="property">app</span>.<span class="title function_">toJSON</span>(),</span><br><span class="line">      <span class="attr">originalUrl</span>: <span class="variable language_">this</span>.<span class="property">originalUrl</span>,</span><br><span class="line">      <span class="attr">req</span>: <span class="string">&#x27;&lt;original node req&gt;&#x27;</span>,</span><br><span class="line">      <span class="attr">res</span>: <span class="string">&#x27;&lt;original node res&gt;&#x27;</span>,</span><br><span class="line">      <span class="attr">socket</span>: <span class="string">&#x27;&lt;original node socket&gt;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">assert</span>: httpAssert,</span><br><span class="line"></span><br><span class="line">  <span class="keyword">throw</span> (...args) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="title function_">createError</span>(...args)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">onerror</span> (err) &#123;.</span><br><span class="line">    <span class="keyword">if</span> (err == <span class="literal">null</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> isNativeError =</span><br><span class="line">      <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(err) === <span class="string">&#x27;[object Error]&#x27;</span> ||</span><br><span class="line">      err <span class="keyword">instanceof</span> <span class="title class_">Error</span></span><br><span class="line">    <span class="keyword">if</span> (!isNativeError) err = <span class="keyword">new</span> <span class="title class_">Error</span>(util.<span class="title function_">format</span>(<span class="string">&#x27;non-error thrown: %j&#x27;</span>, err))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> headerSent = <span class="literal">false</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">headerSent</span> || !<span class="variable language_">this</span>.<span class="property">writable</span>) &#123;</span><br><span class="line">      headerSent = err.<span class="property">headerSent</span> = <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// delegate</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">app</span>.<span class="title function_">emit</span>(<span class="string">&#x27;error&#x27;</span>, err, <span class="variable language_">this</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (headerSent) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> &#123; res &#125; = <span class="variable language_">this</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> res.<span class="property">getHeaderNames</span> === <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">      res.<span class="title function_">getHeaderNames</span>().<span class="title function_">forEach</span>(<span class="function"><span class="params">name</span> =&gt;</span> res.<span class="title function_">removeHeader</span>(name))</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      res.<span class="property">_headers</span> = &#123;&#125; <span class="comment">// Node &lt; 7.7</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// then set those specified</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">set</span>(err.<span class="property">headers</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// force text/plain</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">type</span> = <span class="string">&#x27;text&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> statusCode = err.<span class="property">status</span> || err.<span class="property">statusCode</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// ENOENT support</span></span><br><span class="line">    <span class="keyword">if</span> (err.<span class="property">code</span> === <span class="string">&#x27;ENOENT&#x27;</span>) statusCode = <span class="number">404</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// default to 500</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> statusCode !== <span class="string">&#x27;number&#x27;</span> || !statuses[statusCode]) statusCode = <span class="number">500</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// respond</span></span><br><span class="line">    <span class="keyword">const</span> code = statuses[statusCode]</span><br><span class="line">    <span class="keyword">const</span> msg = err.<span class="property">expose</span> ? err.<span class="property">message</span> : code</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">status</span> = err.<span class="property">status</span> = statusCode</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">length</span> = <span class="title class_">Buffer</span>.<span class="title function_">byteLength</span>(msg)</span><br><span class="line">    res.<span class="title function_">end</span>(msg)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  get <span class="title function_">cookies</span> () &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>[<span class="variable constant_">COOKIES</span>]) &#123;</span><br><span class="line">      <span class="variable language_">this</span>[<span class="variable constant_">COOKIES</span>] = <span class="keyword">new</span> <span class="title class_">Cookies</span>(<span class="variable language_">this</span>.<span class="property">req</span>, <span class="variable language_">this</span>.<span class="property">res</span>, &#123;</span><br><span class="line">        <span class="attr">keys</span>: <span class="variable language_">this</span>.<span class="property">app</span>.<span class="property">keys</span>,</span><br><span class="line">        <span class="attr">secure</span>: <span class="variable language_">this</span>.<span class="property">request</span>.<span class="property">secure</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>[<span class="variable constant_">COOKIES</span>]</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  set <span class="title function_">cookies</span> (_cookies) &#123;</span><br><span class="line">    <span class="variable language_">this</span>[<span class="variable constant_">COOKIES</span>] = _cookies</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Response delegation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">delegate</span>(proto, <span class="string">&#x27;response&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;attachment&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;redirect&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;remove&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;vary&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;has&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;set&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;append&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;flushHeaders&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;status&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;message&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;body&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;length&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;type&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;lastModified&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;etag&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;headerSent&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;writable&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Request delegation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">delegate</span>(proto, <span class="string">&#x27;request&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;acceptsLanguages&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;acceptsEncodings&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;acceptsCharsets&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;accepts&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;get&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;is&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;querystring&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;idempotent&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;socket&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;search&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;method&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;query&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;url&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;accept&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;origin&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;href&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;subdomains&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;protocol&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;host&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;hostname&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;URL&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;header&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;headers&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;secure&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;stale&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;fresh&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;ips&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;ip&#x27;</span>)</span><br></pre></td></tr></table></figure><p>可见<code>context</code>实际上就是一个对象，它对<code>Cookie</code>和<code>onerror</code>做了一个封装。最后使用<code>delegate()</code>来代理<code>request</code>和<code>response</code>对象，<code>delegate</code>不了解的同学可以看下面这个示例。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> delegate = <span class="built_in">require</span>(<span class="string">&#x27;delegates&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">aaa</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;aaa&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">    <span class="attr">isBoy</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="title function_">say</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`我是<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span>，今年<span class="subst">$&#123;<span class="variable language_">this</span>.age&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title function_">delegate</span>(obj, <span class="string">&#x27;aaa&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;say&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;name&#x27;</span>)</span><br><span class="line">  .<span class="title function_">setter</span>(<span class="string">&#x27;age&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;isBoy&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">name</span>); <span class="comment">// 打印 aaa</span></span><br><span class="line">obj.<span class="property">age</span> = <span class="number">19</span>; <span class="comment">// 可以设置属性</span></span><br><span class="line">obj.<span class="title function_">say</span>(); <span class="comment">// 打印 我是aaa，今年19</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">isBoy</span>); <span class="comment">// 打印 true</span></span><br></pre></td></tr></table></figure><p>上面代理了<code>obj</code>对象的<code>aaa</code>属性，所以直接可以通过<code>obj</code>来访问<code>aaa</code>中代理的属性和方法，其中<code>method</code>表示代理方法，<code>getter</code>表示代理get方法，<code>setter</code>表示代理set方法，<code>access</code>表示不但代理了get同时也代理了set。<a href="https://github.com/tj/node-delegates">delegates</a>的实现也不难：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Delegator</span>(<span class="params">proto, target</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!(<span class="variable language_">this</span> <span class="keyword">instanceof</span> <span class="title class_">Delegator</span>)) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Delegator</span>(proto, target);</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">proto</span> = proto;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">target</span> = target;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">methods</span> = [];</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">getters</span> = [];</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">setters</span> = [];</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">fluents</span> = [];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Delegator</span>.<span class="property">auto</span> = <span class="keyword">function</span>(<span class="params">proto, targetProto, targetProp</span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> delegator = <span class="title class_">Delegator</span>(proto, targetProp);</span><br><span class="line">  <span class="keyword">var</span> properties = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(targetProto);</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; properties.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">var</span> property = properties[i];</span><br><span class="line">    <span class="keyword">var</span> descriptor = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(targetProto, property);</span><br><span class="line">    <span class="keyword">if</span> (descriptor.<span class="property">get</span>) &#123;</span><br><span class="line">      delegator.<span class="title function_">getter</span>(property);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (descriptor.<span class="property">set</span>) &#123;</span><br><span class="line">      delegator.<span class="title function_">setter</span>(property);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (descriptor.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;value&#x27;</span>)) &#123; <span class="comment">// could be undefined but writable</span></span><br><span class="line">      <span class="keyword">var</span> value = descriptor.<span class="property">value</span>;</span><br><span class="line">      <span class="keyword">if</span> (value <span class="keyword">instanceof</span> <span class="title class_">Function</span>) &#123;</span><br><span class="line">        delegator.<span class="title function_">method</span>(property);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        delegator.<span class="title function_">getter</span>(property);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (descriptor.<span class="property">writable</span>) &#123;</span><br><span class="line">        delegator.<span class="title function_">setter</span>(property);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Delegator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">method</span> = <span class="keyword">function</span>(<span class="params">name</span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> proto = <span class="variable language_">this</span>.<span class="property">proto</span>;</span><br><span class="line">  <span class="keyword">var</span> target = <span class="variable language_">this</span>.<span class="property">target</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">methods</span>.<span class="title function_">push</span>(name);</span><br><span class="line"></span><br><span class="line">  proto[name] = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>[target][name].<span class="title function_">apply</span>(<span class="variable language_">this</span>[target], <span class="variable language_">arguments</span>);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Delegator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">access</span> = <span class="keyword">function</span>(<span class="params">name</span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">getter</span>(name).<span class="title function_">setter</span>(name);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Delegator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">getter</span> = <span class="keyword">function</span>(<span class="params">name</span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> proto = <span class="variable language_">this</span>.<span class="property">proto</span>;</span><br><span class="line">  <span class="keyword">var</span> target = <span class="variable language_">this</span>.<span class="property">target</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">getters</span>.<span class="title function_">push</span>(name);</span><br><span class="line"></span><br><span class="line">  proto.<span class="title function_">__defineGetter__</span>(name, <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>[target][name];</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Delegator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">setter</span> = <span class="keyword">function</span>(<span class="params">name</span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> proto = <span class="variable language_">this</span>.<span class="property">proto</span>;</span><br><span class="line">  <span class="keyword">var</span> target = <span class="variable language_">this</span>.<span class="property">target</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">setters</span>.<span class="title function_">push</span>(name);</span><br><span class="line"></span><br><span class="line">  proto.<span class="title function_">__defineSetter__</span>(name, <span class="keyword">function</span>(<span class="params">val</span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>[target][name] = val;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Delegator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">fluent</span> = <span class="keyword">function</span> (<span class="params">name</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> proto = <span class="variable language_">this</span>.<span class="property">proto</span>;</span><br><span class="line">  <span class="keyword">var</span> target = <span class="variable language_">this</span>.<span class="property">target</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">fluents</span>.<span class="title function_">push</span>(name);</span><br><span class="line"></span><br><span class="line">  proto[name] = <span class="keyword">function</span>(<span class="params">val</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="string">&#x27;undefined&#x27;</span> != <span class="keyword">typeof</span> val) &#123;</span><br><span class="line">      <span class="variable language_">this</span>[target][name] = val;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>[target][name];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="request与response"><a href="#request与response" class="headerlink" title="request与response"></a>request与response</h3><p><code>request</code>与<code>response</code>就是一个简单的对象，没什么好说的，比如<code>request</code>代码大致如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  get <span class="title function_">header</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">req</span>.<span class="property">headers</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  set <span class="title function_">header</span> (val) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">req</span>.<span class="property">headers</span> = val</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  get <span class="title function_">headers</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">req</span>.<span class="property">headers</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  set <span class="title function_">headers</span> (val) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">req</span>.<span class="property">headers</span> = val</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  get <span class="title function_">url</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">req</span>.<span class="property">url</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  set <span class="title function_">url</span> (val) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">req</span>.<span class="property">url</span> = val</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 省略其他代码</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里需要注意的是有一个<code>this.req</code>对象，这个对象是从哪里来的？请看<code>Application</code>的<code>createContext</code>方法的第61行，在这里把node的<code>req</code>挂载了上来，<code>res</code>同理。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://koa.bootcss.com/&quot;&gt;Koa&lt;/a&gt;是基于 Node.js 平台的下一代 web 开发框架，它的源码可以&lt;a href=&quot;https://github.com/koajs/koa&quot;&gt;看这里&lt;/a&gt;，本章通过源码来简绍一下Koa</summary>
      
    
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/categories/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
    
    <category term="源码共读" scheme="https://www.kai666666.com/tags/%E6%BA%90%E7%A0%81%E5%85%B1%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>H5 history API</title>
    <link href="https://www.kai666666.com/2023/04/18/H5-history-API/"/>
    <id>https://www.kai666666.com/2023/04/18/H5-history-API/</id>
    <published>2023-04-18T11:11:37.000Z</published>
    <updated>2026-02-10T08:00:34.380Z</updated>
    
    <content type="html"><![CDATA[<p>对于JS中<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/History">history</a>对象我们最常用的就是<code>back()</code>、<code>forward()</code>、<code>go()</code>三个方法，H5新增了<code>pushState()</code>和<code>replaceState()</code>用来无刷新页面来更新URL地址，本章所说的H5 history API也指的是这两个方法。</p><p>H5 history API浏览器兼容情况<a href="https://caniuse.com/?search=history.pushState">请看这里</a>。</p><blockquote><p>PS：虽然H5已经不是什么新东西了，但学学总没害处。</p></blockquote><hr><h3 id="pushState"><a href="#pushState" class="headerlink" title="pushState"></a>pushState</h3><p>pushState根据API的意思是向浏览器的历史栈中添加一个状态，这个函数本来是用来添加状态的，而附加能力是修改URL，所以第一个参数是状态，最后一个参数才是URL，URL是可选的，如果不写URL则URL不会变，但是仍然会在历史栈中添加一条数据，点击浏览器的回退按钮会出栈这条历史信息，相当于页面回到原来的状态，页面内容并没有变化。其签名如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">history.<span class="title function_">pushState</span>(state, title[, url])</span><br></pre></td></tr></table></figure><p>pushState第一个参数是状态，这个状态state可以是<strong>可结构化拷贝的任意类型</strong>，传对象、传数字、传布尔都没问题。第二个是标题，但是浏览器压根不鸟它，你设置了也不会修改浏览器标题，所以我们一般传入<code>null</code>就可以了。第三个是URL，一般是一个字符串，新版本浏览器对URL对象（<code>new URL()</code>这种）也是支持的，但考虑到兼容性还是使用字符串比较好，需要注意的是URL必须是跟当前页面地址是<strong>同源</strong>的。</p><p>来看一个简单的例子：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&#x27;我是状态&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">history.<span class="title function_">pushState</span>(state, <span class="literal">null</span>, <span class="string">&#x27;/a&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(history.<span class="property">state</span>); <span class="comment">// 打印 state 即 &#123; msg: &#x27;我是状态&#x27; &#125;</span></span><br></pre></td></tr></table></figure><p>上述state是一个对象，调用pushState会在历史栈中增加一条信息，同时修改URL的pathName为<code>/a</code>。如果把上述state的值修改为<code>123</code>，那么调用pushState后<code>history.state</code>就变成了<code>123</code>。但是需要注意的是<code>history.state !== state</code>，这里采用了结构化深拷贝，相当于调用了<code>structuredClone(state)</code>，结构化深拷贝不仅可以拷贝基本类型，而且对象在存在循环引用也是可以拷贝成功的，比<code>JSON.parse(JSON.stringify(state))</code>要更加安全。浏览器原生的structuredClone方法目前兼容性还不够好，其相关内容可以<a href="https://developer.mozilla.org/zh-CN/docs/web/api/structuredClone">看这里</a>。当然结构化深拷贝也不是万能的，对于dom节点，error对象、function函数来说也会拷贝失败，上述stete传这些类型的值时，会报错。</p><p>第三个参数URL又可分为这几种情况，比如当前路径为<code>https://www.kai666666.com/2023/04/18/H5-history-API/?1=1#more</code>（这里为了方便查看search的情况，添加了一个<code>1=1</code>的参数）。</p><table><thead><tr><th>URL情况</th><th>处理情况</th><th>url值</th><th>url处理结果</th></tr></thead><tbody><tr><td>URL全路径</td><td>整体替换</td><td><code>https://www.kai666666.com/a</code></td><td><code>https://www.kai666666.com/a</code></td></tr><tr><td><code>/</code>开头</td><td>替换pathName部分</td><td><code>/a</code></td><td><code>https://www.kai666666.com/a</code></td></tr><tr><td><code>./</code>开头</td><td>替换当前路径，也就是最后一位</td><td><code>./a</code></td><td><code>https://www.kai666666.com/2023/04/18/H5-history-API/a</code></td></tr><tr><td><code>../</code>开头</td><td>替换上一级</td><td><code>../a</code></td><td><code>https://www.kai666666.com/2023/04/18/a</code></td></tr><tr><td><code>?</code>开头</td><td>替换search部分</td><td><code>?2=2</code></td><td><code>https://www.kai666666.com/2023/04/18/H5-history-API/?2=2</code></td></tr><tr><td><code>#</code>开头</td><td>替换hash部分</td><td><code>#hash</code></td><td><code>https://www.kai666666.com/2023/04/18/H5-history-API/?1=1#hash</code></td></tr><tr><td><code>单词或数字</code>开头</td><td>替换当前路径部分，等价与<code>./单词或数字</code>开头</td><td><code>aaa</code></td><td><code>https://www.kai666666.com/2023/04/18/H5-history-API/aaa</code></td></tr></tbody></table><p>这里有几点需要注意一下。首先地址最后一位有没有<code>/</code>会影响到当前路径和上一级路径的判断，如果有<code>/</code>则认为当前路径是斜杠后面的路径，虽然后面是空的。举个例子URL<code>https://www.kai666666.com/2023/04/18/H5-history-API/</code>和<code>https://www.kai666666.com/2023/04/18/H5-history-API</code>调用<code>history.pushState(state, null, &#39;./a&#39;);</code>得到的结果分别是<code>https://www.kai666666.com/2023/04/18/H5-history-API/a</code>和<code>https://www.kai666666.com/2023/04/18/a</code>。</p><p>第二点需要注意的是<code>/</code>开头的路径将会把search和hash部分也替换掉；<code>?</code>开头的路径会把hash部分也替换的，如果需要保留则需要手动添加对应的部分，如`&#x2F;a${location.search}${location.hash}`。</p><p>当前路径与上一级路径也可以混用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">history.<span class="title function_">pushState</span>(<span class="literal">null</span>, <span class="literal">null</span>, <span class="string">&#x27;./a/../b/c&#x27;</span>);</span><br><span class="line"><span class="comment">// 路径修改为 https://www.kai666666.com/2023/04/18/H5-history-API/b/c</span></span><br></pre></td></tr></table></figure><p>对了URL路径中只有<code>./</code>或者<code>../</code>可以省略后面的<code>/</code>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">history.<span class="title function_">pushState</span>(<span class="literal">null</span>, <span class="literal">null</span>, <span class="string">&#x27;..&#x27;</span>);</span><br><span class="line"><span class="comment">// 替换到上一级目录 路径修改为 https://www.kai666666.com/2023/04/18/</span></span><br></pre></td></tr></table></figure><p>最后如果URL中含有中文，调用<code>location.pathname</code>得到的是转码后的内容。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">history.<span class="title function_">pushState</span>(<span class="literal">null</span>, <span class="literal">null</span>, <span class="string">&#x27;/你好&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(location.<span class="property">pathname</span>)<span class="comment">// /%E4%BD%A0%E5%A5%BD</span></span><br></pre></td></tr></table></figure><h3 id="replaceState"><a href="#replaceState" class="headerlink" title="replaceState"></a>replaceState</h3><p>replaceState与pushState用法一模一样，区别是replaceState是把当前的历史栈替换了，而pushState是添加了一个历史栈，这样就导致replaceState点返回按钮会回到上一个历史栈中。replaceState的签名如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">history.<span class="title function_">replaceState</span>(stateObj, title[, url]);</span><br></pre></td></tr></table></figure><h3 id="base元素对路径的影响"><a href="#base元素对路径的影响" class="headerlink" title="base元素对路径的影响"></a>base元素对路径的影响</h3><p>HTML中base元素提供了基础的路径，如果设置了base，那么相对路径都是基于base元素中的路径来算出新的路径的。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">base</span> <span class="attr">href</span>=<span class="string">&quot;/base/aaa&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如添加如上base元素后，上述情况处理的结果如下：</p><table><thead><tr><th>URL情况</th><th>url值</th><th>url处理结果</th></tr></thead><tbody><tr><td>URL全路径</td><td><code>https://www.kai666666.com/a</code></td><td><code>https://www.kai666666.com/a</code></td></tr><tr><td><code>/</code>开头</td><td><code>/a</code></td><td><code>https://www.kai666666.com/a</code></td></tr><tr><td><code>./</code>开头</td><td><code>./a</code></td><td><code>https://www.kai666666.com/base/a</code></td></tr><tr><td><code>../</code>开头</td><td><code>../a</code></td><td><code>https://www.kai666666.com/a</code></td></tr><tr><td><code>?</code>开头</td><td><code>?2=2</code></td><td><code>https://www.kai666666.com/base/aaa?2=2</code></td></tr><tr><td><code>#</code>开头</td><td><code>#hash</code></td><td><code>https://www.kai666666.com/base/aaa#hash</code></td></tr><tr><td><code>单词或数字</code>开头</td><td><code>bbb</code></td><td><code>https://www.kai666666.com/base/bbb</code></td></tr></tbody></table><h3 id="popstate事件"><a href="#popstate事件" class="headerlink" title="popstate事件"></a>popstate事件</h3><p>popstate事件是浏览器历史栈返回或者前进的时候会触发，调用<code>history.pushState()</code>和<code>history.replaceState()</code>方法的时候并不会触发popstate事件，只有hash改变或者调用这两个函数后并点击浏览器的前进&#x2F;后退或者使用JS API前进&#x2F;后退（如调用<code>history.back()</code>、<code>history.go(-1)</code>或<code>history.forward()</code>）的时候才会触发。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;popstate&#x27;</span>, <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(event.<span class="property">state</span>); <span class="comment">// 这里可以获取到状态</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>如果不修改hash或不调用者两个函数的时候，直接前进后退一般都是会刷新页面，也就不会触发事件回调函数。当然你也可以手动触发popstate事件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="title function_">dispatchEvent</span>(<span class="keyword">new</span> <span class="title class_">PopStateEvent</span>(<span class="string">&#x27;popstate&#x27;</span>));</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;对于JS中&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/History&quot;&gt;history&lt;/a&gt;对象我们最常用的就是&lt;code&gt;back()&lt;/code&gt;、&lt;code&gt;forward()&lt;/code&gt;、&lt;</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>使用随机数生成器估算π</title>
    <link href="https://www.kai666666.com/2023/03/10/%E4%BD%BF%E7%94%A8%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8%E4%BC%B0%E7%AE%97%CF%80/"/>
    <id>https://www.kai666666.com/2023/03/10/%E4%BD%BF%E7%94%A8%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8%E4%BC%B0%E7%AE%97%CF%80/</id>
    <published>2023-03-10T16:45:26.000Z</published>
    <updated>2026-02-10T08:00:34.416Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>编写函数，使用随机数生成器估算π。</p></blockquote><p>各种计算机语音中都会给出π的具体值，如JavaScript中就有<code>Math.PI</code>，但是如何不使用该值来粗略估算π的值呢？</p><p>整体思路：如图所示，在坐标系(x, y)的作用域[-1，1]与值域[-1，1]中随机生成点，点在圆<code>x² + y² = 1</code>内的概率为<code>π / 4</code>，那么π可以由<code>4 * 点在圆内的数量 / 所有点的数量</code>得到。实际上我们不需要计算整个圆，我们只要计算作用域[0，1]与值域[0，1]的部分，也就是第一象限，也就是<code>π = 4 * 点在第一象限圆内的数量 / 第一象限所有点的数量</code>。</p><p><img src="/2023/03/10/%E4%BD%BF%E7%94%A8%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8%E4%BC%B0%E7%AE%97%CF%80/1.png" alt="示例图"></p><p>代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">estimatePi</span>(<span class="params">iterations = <span class="number">100000000</span></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> randX;</span><br><span class="line">  <span class="keyword">let</span> randY;</span><br><span class="line">  <span class="keyword">let</span> inside = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; iterations; i++) &#123;</span><br><span class="line">    randX = <span class="title class_">Math</span>.<span class="title function_">random</span>(); <span class="comment">// 生成第一象限x的值</span></span><br><span class="line">    randY = <span class="title class_">Math</span>.<span class="title function_">random</span>(); <span class="comment">// 生成第一象限y的值</span></span><br><span class="line">    <span class="keyword">if</span>((randX * randX) + (randY * randY) &lt;= <span class="number">1</span>) &#123; <span class="comment">// 点在圆内</span></span><br><span class="line">      inside++;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">4</span> * inside / iterations; <span class="comment">// 计算π</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码中，iterations越大耗时越长，结果越准确。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;编写函数，使用随机数生成器估算π。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;各种计算机语音中都会给出π的具体值，如JavaScript中就有&lt;code&gt;Math.PI&lt;/code&gt;，但是如何不使用该值来粗略估算π的值呢？&lt;/p&gt;
&lt;p&gt;整体思路：</summary>
      
    
    
    
    <category term="算法" scheme="https://www.kai666666.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="算法" scheme="https://www.kai666666.com/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>计算整数二进制表示中各个1位的数目</title>
    <link href="https://www.kai666666.com/2023/03/10/%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA%E4%B8%AD%E5%90%84%E4%B8%AA1%E4%BD%8D%E7%9A%84%E6%95%B0%E7%9B%AE/"/>
    <id>https://www.kai666666.com/2023/03/10/%E8%AE%A1%E7%AE%97%E6%95%B4%E6%95%B0%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA%E4%B8%AD%E5%90%84%E4%B8%AA1%E4%BD%8D%E7%9A%84%E6%95%B0%E7%9B%AE/</id>
    <published>2023-03-10T15:45:26.000Z</published>
    <updated>2026-02-10T08:00:34.504Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>编写一个函数，确定给定整数的二进制表示中各个1位的数目。</p></blockquote><p>举例：给定一个数字是7，假设是8位操作系统，二进制表示为<code>00000111</code>，其中有3个1，则调用函数返回<code>3</code>。</p><p>整体思路：循环统计，检测二进制表示中的最后一位，如果最后一位是1的时候计数器加1，然后把数字右移一位，直到整个数字全部移完。</p><p>代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">numOnesInBinary</span>(<span class="params">number</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> numOnes = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span>(number != <span class="number">0</span>) &#123; <span class="comment">// 直到数字全部移完</span></span><br><span class="line">    <span class="keyword">if</span>((number &amp; <span class="number">1</span>) === <span class="number">1</span>) &#123; <span class="comment">// 判断最后一位是1</span></span><br><span class="line">      numOnes++; <span class="comment">// 计数器自增</span></span><br><span class="line">    &#125;</span><br><span class="line">    number = number &gt;&gt;&gt; <span class="number">1</span>; <span class="comment">// 二进制右移1位</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> numOnes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述算法已经很不错了，不过还有可以优化的部分。</p><p>一个数的二进制跟这个数减1的二进制相比，前半部分是相同的，只是翻转了最低位的1以及之后的各个位。例如有个数的二进制位<code>01110000</code>(十进制112)，该值减去1以后的二进制是<code>01101111</code>（十进制111），可以看到前三位是相同的，后面的位数是想反的。一个数的二进制跟这个数减1的二进制相与（&amp;）会发生什么呢？实际上就是该二进制去掉最后一个1，如<code>01110000</code> &amp; <code>01101111</code> &#x3D; <code>01100000</code>，<code>01100000</code>实际上就是<code>01110000</code>去掉最后一个1的结果。</p><p>有了上面的知识，我们可以稍微改造一下代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">numOnesInBinary</span>(<span class="params">number</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> numOnes = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span>(number != <span class="number">0</span>) &#123; <span class="comment">// 直到不等于0</span></span><br><span class="line">    number = number &amp; (number - <span class="number">1</span>); <span class="comment">// 去掉二进制最后一位1</span></span><br><span class="line">    numOnes++; <span class="comment">// 计数器自增</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> numOnes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="拓展"><a href="#拓展" class="headerlink" title="拓展"></a>拓展</h3><p>上述得出一个重要的结论<strong>number &amp; (number - 1)的值就是number二进制去掉最后一个1的结果</strong>。利用这个结论我们还可以最很多事，比如有题目：</p><blockquote><p>给你一个正整数 n，请你判断该正整数是否是 2 的幂次方。如果是，返回 true ；否则，返回 false。</p></blockquote><p>比如，n&#x3D;4的时候就返回ture，如果n&#x3D;3的时候就返回false。</p><p>整体思路：由于一个正整数是2的幂次方，那么它的二进制一定是1后面好多0这种格式，比如4的二进制就是<code>100</code>，8的二进制就是<code>1000</code>。所以按照这个思路我们可以去掉最后一个1，如果结果是0的时候就说明这个正整数是2的幂次方。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isPowerOfTwo</span>(<span class="params">n</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> n &gt; <span class="number">0</span> &amp;&amp; (n &amp; (n - <span class="number">1</span>)) === <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;编写一个函数，确定给定整数的二进制表示中各个1位的数目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;举例：给定一个数字是7，假设是8位操作系统，二进制表示为&lt;code&gt;00000111&lt;/code&gt;，其中有3个1，则调用函数返回&lt;code&gt;3&lt;/c</summary>
      
    
    
    
    <category term="算法" scheme="https://www.kai666666.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="算法" scheme="https://www.kai666666.com/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>《JavaScript DOM 编程艺术》小记</title>
    <link href="https://www.kai666666.com/2022/07/24/%E3%80%8AJavaScript-DOM-%E7%BC%96%E7%A8%8B%E8%89%BA%E6%9C%AF%E3%80%8B%E5%B0%8F%E8%AE%B0/"/>
    <id>https://www.kai666666.com/2022/07/24/%E3%80%8AJavaScript-DOM-%E7%BC%96%E7%A8%8B%E8%89%BA%E6%9C%AF%E3%80%8B%E5%B0%8F%E8%AE%B0/</id>
    <published>2022-07-24T15:24:07.000Z</published>
    <updated>2026-02-10T08:00:34.400Z</updated>
    
    <content type="html"><![CDATA[<ol><li>在HTML中的Script块中可以使用HTML注释，甚至可以不写结尾。如，以下代码都是可以正常运行的。</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/JavaScript&quot;</span>&gt;</span><span class="language-handlebars"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  console.log(1);</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  <span class="comment">&lt;!-- 运行后打印1 1 --&gt;</span></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  console.log(1);</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars"></span></span><span class="tag">&lt;/<span class="name">script</span>&gt;</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/JavaScript&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span></span><br><span class="line"><span class="language-javascript">  &lt;!-- 运行后打印<span class="number">2</span> <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>2.常用DOM操作：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(elementId); <span class="comment">// 根据id来获取DOM元素</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(tagName); <span class="comment">// 通过tagName来获取元素</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(classNames); <span class="comment">// 通过classNames来获取元素</span></span><br><span class="line">dom.<span class="title function_">setAttribute</span>(name, value); <span class="comment">// 设置属性的值</span></span><br><span class="line">dom.<span class="title function_">getAttribute</span>(name); <span class="comment">// 获取属性的值</span></span><br><span class="line">dom.<span class="property">childNodes</span>; <span class="comment">// 获取子元素</span></span><br><span class="line">dom.<span class="property">nodeType</span>; <span class="comment">// 获取节点类型 1:元素节点 2:属性节点 3:文本节点</span></span><br><span class="line">textNode.<span class="property">nodeValue</span> = <span class="string">&#x27;文本&#x27;</span>; <span class="comment">// 修改文本节点的值</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">write</span>(text); <span class="comment">// 全页面写入文档</span></span><br><span class="line">dom.<span class="property">innerHTML</span>; <span class="comment">// 获取DOM中的HTML文本</span></span><br><span class="line">dom.<span class="property">innerHTML</span> = text; <span class="comment">// 设置DOM中的HTML</span></span><br><span class="line">dom.<span class="property">innerText</span>; <span class="comment">// 获取DOM中的文本（省略HTML标签）</span></span><br><span class="line">dom.<span class="property">innerText</span> = text; <span class="comment">// 设置DOM中的文本</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">createElement</span>(nodeName); <span class="comment">// 创建一个元素</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">createTextNode</span>(text); <span class="comment">// 创建一个文本节点</span></span><br><span class="line">dom.<span class="title function_">appendChild</span>(node); <span class="comment">// 追加一个节点</span></span><br><span class="line">dom.<span class="title function_">insertBefore</span>(node, child); <span class="comment">// 将node插在父元素dom的child元素之前</span></span><br><span class="line">dom.<span class="property">style</span>.<span class="property">fontSize</span> = value; <span class="comment">// 设置样式</span></span><br></pre></td></tr></table></figure><p>3.不同的设置属性方式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 给input设置值</span></span><br><span class="line">inputDom.<span class="title function_">setAttribute</span>(<span class="string">&#x27;value&#x27;</span>, value);</span><br><span class="line">inputDom.<span class="property">value</span> = value;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 给img设值图片</span></span><br><span class="line">imgDom.<span class="title function_">setAttribute</span>(<span class="string">&#x27;src&#x27;</span>, src);</span><br><span class="line">imgDom.<span class="property">src</span> = src;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 给a设值href</span></span><br><span class="line">aDom.<span class="title function_">setAttribute</span>(<span class="string">&#x27;href&#x27;</span>, href);</span><br><span class="line">aDom.<span class="property">href</span> = href;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 给dom设值class</span></span><br><span class="line">dom.<span class="title function_">setAttribute</span>(<span class="string">&#x27;class&#x27;</span>, className);</span><br><span class="line">dom.<span class="property">className</span> = className;</span><br></pre></td></tr></table></figure><p>4.打开一个打开一个320px * 480px的小窗口：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="title function_">open</span>(url, <span class="string">&quot;popup&quot;</span>, <span class="string">&quot;width=320,height=480&quot;</span>);</span><br></pre></td></tr></table></figure><p>5.实现一个addLoadEvent函数，支持添加多个window.onload函数：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">addLoadEvent</span>(<span class="params">func</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> oldonload = <span class="variable language_">window</span>.<span class="property">onload</span>;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="variable language_">window</span>.<span class="property">onload</span> != <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">onload</span> = func;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">onload</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">oldonload</span>();</span><br><span class="line">      <span class="title function_">func</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>6.实现一个insertAfter函数支持把元素追加到某个元素之后：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">insertAfter</span>(<span class="params">newElement, targetElement</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> parent = targetElement.<span class="property">parentNode</span>;</span><br><span class="line">  <span class="keyword">if</span> (parent.<span class="property">lastChild</span> == targetElement) &#123;</span><br><span class="line">    parent.<span class="title function_">appendChild</span>(newElement);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    parent.<span class="title function_">insertBefore</span>(newElement,targetElement.<span class="property">nextSibling</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>7.实现一个addClass函数给指定节点追加类名：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">addClass</span>(<span class="params">element, value</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!element.<span class="property">className</span>) &#123;</span><br><span class="line">    element.<span class="property">className</span> = value;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    element.<span class="property">className</span> += <span class="string">&quot; &quot;</span>;</span><br><span class="line">    element.<span class="property">className</span> += value;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>8.实现一个moveElement动画函数，可以把指定元素移动到目标位置：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">moveElement</span>(<span class="params">elementID, final_x, final_y, interval</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">document</span>.<span class="property">getElementById</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(elementID)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">var</span> elem = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(elementID);</span><br><span class="line">  <span class="keyword">if</span> (elem.<span class="property">movement</span>) &#123;</span><br><span class="line">    <span class="built_in">clearTimeout</span>(elem.<span class="property">movement</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!elem.<span class="property">style</span>.<span class="property">left</span>) &#123;</span><br><span class="line">    elem.<span class="property">style</span>.<span class="property">left</span> = <span class="string">&quot;0px&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!elem.<span class="property">style</span>.<span class="property">top</span>) &#123;</span><br><span class="line">    elem.<span class="property">style</span>.<span class="property">top</span> = <span class="string">&quot;0px&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> xpos = <span class="built_in">parseInt</span>(elem.<span class="property">style</span>.<span class="property">left</span>);</span><br><span class="line">  <span class="keyword">var</span> ypos = <span class="built_in">parseInt</span>(elem.<span class="property">style</span>.<span class="property">top</span>);</span><br><span class="line">  <span class="keyword">if</span> (xpos == final_x &amp;&amp; ypos == final_y) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (xpos &lt; final_x) &#123;</span><br><span class="line">    <span class="keyword">var</span> dist = <span class="title class_">Math</span>.<span class="title function_">ceil</span>((final_x - xpos)/<span class="number">10</span>);</span><br><span class="line">    xpos = xpos + dist;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (xpos &gt; final_x) &#123;</span><br><span class="line">    <span class="keyword">var</span> dist = <span class="title class_">Math</span>.<span class="title function_">ceil</span>((xpos - final_x)/<span class="number">10</span>);</span><br><span class="line">    xpos = xpos - dist;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (ypos &lt; final_y) &#123;</span><br><span class="line">    <span class="keyword">var</span> dist = <span class="title class_">Math</span>.<span class="title function_">ceil</span>((final_y - ypos)/<span class="number">10</span>);</span><br><span class="line">    ypos = ypos + dist;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (ypos &gt; final_y) &#123;</span><br><span class="line">    <span class="keyword">var</span> dist = <span class="title class_">Math</span>.<span class="title function_">ceil</span>((ypos - final_y)/<span class="number">10</span>);</span><br><span class="line">    ypos = ypos - dist;</span><br><span class="line">  &#125;</span><br><span class="line">  elem.<span class="property">style</span>.<span class="property">left</span> = xpos + <span class="string">&quot;px&quot;</span>;</span><br><span class="line">  elem.<span class="property">style</span>.<span class="property">top</span> = ypos + <span class="string">&quot;px&quot;</span>;</span><br><span class="line">  <span class="keyword">var</span> repeat = <span class="string">&quot;moveElement(&#x27;&quot;</span>+elementID+<span class="string">&quot;&#x27;,&quot;</span>+final_x+<span class="string">&quot;,&quot;</span>+final_y+<span class="string">&quot;,&quot;</span>+interval+<span class="string">&quot;)&quot;</span>;</span><br><span class="line">  elem.<span class="property">movement</span> = <span class="built_in">setTimeout</span>(repeat,interval);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>9.使用JavaScript实现把指定图片设置为黑白图片，鼠标经过时候图片变成彩色的效果：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">convertToGS</span>(<span class="params">img</span>) &#123;</span><br><span class="line">  <span class="comment">// 存储原始图片</span></span><br><span class="line">  img.<span class="property">color</span> = img.<span class="property">src</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 存储黑白图片</span></span><br><span class="line">  img.<span class="property">grayscale</span> = <span class="title function_">createGSCanvas</span>(img);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 鼠标经过和移除时候切换图片</span></span><br><span class="line">  img.<span class="property">onmouseover</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">src</span> = <span class="variable language_">this</span>.<span class="property">color</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  img.<span class="property">onmouseout</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">src</span> = <span class="variable language_">this</span>.<span class="property">grayscale</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createGSCanvas</span>(<span class="params">img</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> canvas=<span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;canvas&quot;</span>);</span><br><span class="line">  canvas.<span class="property">width</span>=img.<span class="property">width</span>;</span><br><span class="line">  canvas.<span class="property">height</span>=img.<span class="property">height</span>;</span><br><span class="line">  <span class="keyword">var</span> ctx=canvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span><br><span class="line">  ctx.<span class="title function_">drawImage</span>(img,<span class="number">0</span>,<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 注：getImageData方法只有在Web环境中才能生效 直接用浏览器打开HTML会报错</span></span><br><span class="line">  <span class="keyword">var</span> c = ctx.<span class="title function_">getImageData</span>(<span class="number">0</span>, <span class="number">0</span>, img.<span class="property">width</span>, img.<span class="property">height</span>);</span><br><span class="line">  <span class="keyword">for</span> (i=<span class="number">0</span>; i&lt;c.<span class="property">height</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (j=<span class="number">0</span>; j&lt;c.<span class="property">width</span>; j++) &#123;</span><br><span class="line">      <span class="keyword">var</span> x = (i*<span class="number">4</span>) * c.<span class="property">width</span> + (j*<span class="number">4</span>);</span><br><span class="line">      <span class="keyword">var</span> r = c.<span class="property">data</span>[x];</span><br><span class="line">      <span class="keyword">var</span> g = c.<span class="property">data</span>[x+<span class="number">1</span>];</span><br><span class="line">      <span class="keyword">var</span> b = c.<span class="property">data</span>[x+<span class="number">2</span>];</span><br><span class="line">      c.<span class="property">data</span>[x] = c.<span class="property">data</span>[x+<span class="number">1</span>] = c.<span class="property">data</span>[x+<span class="number">2</span>] = (r+g+b)/<span class="number">3</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  ctx.<span class="title function_">putImageData</span>(c,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>, c.<span class="property">width</span>, c.<span class="property">height</span>);</span><br><span class="line">  <span class="keyword">return</span> canvas.<span class="title function_">toDataURL</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注：需要再Web环境中才能看到效果</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">onload</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">convertToGS</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;avatar&#x27;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;ol&gt;
&lt;li&gt;在HTML中的Script块中可以使用HTML注释，甚至可以不写结尾。如，以下代码都是可以正常运行的。&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight html&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;s</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://www.kai666666.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>《Web性能实战》读书笔记</title>
    <link href="https://www.kai666666.com/2022/06/04/%E3%80%8AWeb%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <id>https://www.kai666666.com/2022/06/04/%E3%80%8AWeb%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</id>
    <published>2022-06-04T12:42:26.000Z</published>
    <updated>2026-02-10T08:00:34.404Z</updated>
    
    <content type="html"><![CDATA[<p>Web性能调优一直是高级前端必须掌握的技能，市面上不少书简绍性能调优的书总是告诉读者一些理论性的东西，而如何去实践说的却不多，这本书不仅告诉读者Web性能优化的理论知识，同时还会告诉读者怎么用node去设置，是一本前端进阶必看的书。</p><p><img src="/2022/06/04/%E3%80%8AWeb%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/1.jpg" alt="《Web性能实战》"></p><hr><h2 id="理解Web性能"><a href="#理解Web性能" class="headerlink" title="理解Web性能"></a>理解Web性能</h2><p><strong>Web性能</strong>主要指网站的加载速度。你可以通过提高网站速度来加快内容的传输，从而改善用户的体验。电子商务网站上近一半的用户希望在2秒内完成。如果加载时间超过3秒，40%的用户将会退出。页面相应时间每延迟1秒就意味着7%的用户不再进一步操作。</p><p><strong>加载时间</strong>是用户请求网站到网站出现在用户屏幕上所经历的时间。</p><p>本节从减少传输的数据量入手，简单的简绍了3中提高性能的方法：缩小资源、使用服务器压缩、压缩图像。</p><p>缩小（minification）文本资源是从基于文本的资源中去除所有空白和非必要字符的过程，因而不会影响资源的工作方式。</p><h3 id="缩小资源"><a href="#缩小资源" class="headerlink" title="缩小资源"></a>缩小资源</h3><p>下面命令<code>-o</code>表示输入的文件路径，通过使用下面命令缩小资源后 CSS文件缩小了14%，JS文件缩小了66%，HTML缩小了19%，缩小的还是挺可观的。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">缩小CSS</span></span><br><span class="line">minify -o styles.min.css styles.css</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">缩小JS</span></span><br><span class="line">minify -o jquery.min.js jquery.js</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">缩小HTML</span></span><br><span class="line">htmlminify -o index.html index.src.html</span><br></pre></td></tr></table></figure><h3 id="使用服务器压缩"><a href="#使用服务器压缩" class="headerlink" title="使用服务器压缩"></a>使用服务器压缩</h3><p>服务器压缩的工作方式是用户从服务器请求网页，用户的请求会附带一个<code>Accept-Encoding</code>的头信息，向服务器告知浏览器可以使用的压缩格式。如果服务器按照<code>Accept-Encoding</code>头信息中的内容进行编码，它将用一个<code>Content-Encoding</code>响应头信息进行回复，其值是所使用的压缩方式。<code>gzip</code>是比较常用的一种压缩格式，express服务配置<code>gzip</code>如下。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">&quot;express&quot;</span>);</span><br><span class="line"><span class="comment">// 需要运行 npm install compression 安装compression</span></span><br><span class="line"><span class="keyword">var</span> compression = <span class="built_in">require</span>(<span class="string">&quot;compression&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用compression中间件</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">compression</span>());</span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(__dirname));</span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">8080</span>);</span><br></pre></td></tr></table></figure><p>通过上述两行代码，压缩后资源缩小了66%。</p><h3 id="压缩图像"><a href="#压缩图像" class="headerlink" title="压缩图像"></a>压缩图像</h3><p>压缩图像书中简绍了使用常用的<a href="https://tinypng.com/">TinyPNG</a>去压缩，大小缩小了60%左右。</p><p>通过这三种方式，网站的加载速度提高了近70%，还是非常可观的。</p><h2 id="使用评估工具"><a href="#使用评估工具" class="headerlink" title="使用评估工具"></a>使用评估工具</h2><p>第一个评估工具就是<a href="https://developers.google.com/speed/pagespeed/insights/">Google PageSpeed Insights</a>，去该网站输入你要分析的网站，它会给你一些优化的建议，当然网页得是已经在线上跑的网页，另外国内需要翻墙。</p><p>第二个评估工具是<a href="https://www.google.com/analyties/">Google Analytics</a>，这个工具比较全面，但是需要在网页中注入JS脚本，如果是大公司的开发者，注入Google的代码往往需要走法律审查，因为安装跟踪代码时，需要接受法律协议条款。</p><p><strong>首字节时间（Time to First Byte,TTFB）</strong>：从用户请求网页到相应第一个字节到达之间的时间。首字节时间往往跟队列请求、DNS查找、连接设置和SSL握手等有关。</p><p>页面创建过程：解析HTML以创建DOM -&gt; 解析CSS以创建CSSOM -&gt; 布局元素 -&gt; 绘制页面。</p><h2 id="优化CSS"><a href="#优化CSS" class="headerlink" title="优化CSS"></a>优化CSS</h2><h3 id="移动优先"><a href="#移动优先" class="headerlink" title="移动优先"></a>移动优先</h3><p><strong>移动优先响应式设计</strong>：默认样式为移动设备定义，并且随着屏幕宽度的增大而增加复杂性。<br><strong>桌面优先响应式设计</strong>：默认样式为桌面设备定义，并且随着屏幕宽度的减小而降低复杂性。</p><p>通常响应式设计中使用移动优先的响应式设计会更好一点，主要的原因有：<br>1.通常移动设备的处理能力和内存通常低于桌面设备，使用移动优先不需要解析媒体查询。<br>2.从开发角度出发，扩大样式规模更容易实现。<br>3.手机用户量激增，搜索引擎对移动设备逐渐更友好。</p><h3 id="避免使用-import声明"><a href="#避免使用-import声明" class="headerlink" title="避免使用@import声明"></a>避免使用@import声明</h3><p>CSS中可以使用@import来引入一个样式，使用方式如下：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@import</span> url(fonts.css);</span><br></pre></td></tr></table></figure><p>最好不要使用<code>@import</code>来引入一个样式，因为<code>@import</code>是串行的，会增加页面的总体加载和渲染时间。可以使用<code>&lt;link&gt;</code>标签来代替，因为<code>&lt;link&gt;</code>标签是并行的。<br>Less中的<code>@import</code>最终编译到css中的并不是CSS语法中的<code>@import</code>，所以可以使用。</p><h3 id="在中放置CSS"><a href="#在中放置CSS" class="headerlink" title="在&lt;head&gt;中放置CSS"></a>在&lt;head&gt;中放置CSS</h3><p>在<code>&lt;head&gt;</code>标签中放置CSS要比在<code>&lt;body&gt;</code>标签中放置CSS有两个好处：</p><ol><li>无样式内容闪烁的问题；</li><li>加载时提高页面的渲染性能。</li></ol><p>如果CSS放在<code>&lt;body&gt;</code>标签中，如果放在页面HTML结构的下方那么就会先渲染一个没有自定义样式的页面，等加载完CSS以后才会有自定义样式，所以会有无样式内容闪烁的问题。<br>放在<code>&lt;body&gt;</code>中还有一个问题是页面加载完<code>&lt;body&gt;</code>中的样式以后会重新渲染和绘制整个DOM，页面渲染性能较差。</p><h3 id="使用CSS过渡"><a href="#使用CSS过渡" class="headerlink" title="使用CSS过渡"></a>使用CSS过渡</h3><p>CSS过渡的优点：</p><ol><li>广泛支持；</li><li>回流复杂DOM时，CPU的使用效率更高；</li><li>无额外开销。</li></ol><p>如果动画可以使用CSS过渡来实现的话，最好使用CSS过渡而不是JS来改变DOM（减少回流）。</p><h3 id="使用will-change来优化过渡"><a href="#使用will-change来优化过渡" class="headerlink" title="使用will-change来优化过渡"></a>使用will-change来优化过渡</h3><p>will-change使用方法：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">will-change</span>: 属性<span class="number">1</span>, [属性<span class="number">2</span>]...</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 如： */</span></span><br><span class="line">will-change: background-color;</span><br></pre></td></tr></table></figure><p>will-change可以告诉浏览器哪个属性将会过渡，但是不要使用<code>will-change: all;</code>。</p><p>其他的优化点：</p><ol><li>使用简写属性；</li><li>使用CSS潜选择器；</li><li>分割CSS不加载当前页面中不会显示的CSS；</li><li>尽可能使用flexbox布局。</li></ol><h3 id="关键CSS技术"><a href="#关键CSS技术" class="headerlink" title="关键CSS技术"></a>关键CSS技术</h3><p>关键CSS，即折叠之上的内容，这些是用户会立即看到的内容，需要尽快加载。<br>非关键CSS，即折叠之下的内容，这些是用户开始向下滚动页面之前看不到的内容样式，这种CSS也应该尽快加载，但不能在关键CSS之前加载。</p><p>书中的折叠是指屏幕的底部，实际上关键CSS就是首屏样式，非关键CSS就是非首屏的样式。</p><p>渲染阻塞指的是阻止浏览器将内容绘制到屏幕的任务活动，这是Web中不可避免的事情。无论使用<code>&lt;link&gt;</code>还是<code>@import</code>引入样式都会产生渲染阻塞（虽然<code>&lt;link&gt;</code>下载是并行的）。</p><p>加载首屏样式：为了减少渲染阻塞时间可以直接把关键CSS样式放在<code>&lt;style&gt;</code>标签中。</p><p>加载非首屏样式：非首屏样式也会遇到渲染阻塞的问题，可以使用preload来减少阻塞渲染时间。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;preload&quot;</span> <span class="attr">href</span>=<span class="string">&quot;index.css&quot;</span> <span class="attr">as</span>=<span class="string">&quot;style&quot;</span> <span class="attr">onload</span>=<span class="string">&quot;this.rel=&#x27;stylesheet&#x27;&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">noscript</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;index.css&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">noscript</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">link</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="响应式图像"><a href="#响应式图像" class="headerlink" title="响应式图像"></a>响应式图像</h2><h3 id="通过媒体查询来适配高DPI显示器"><a href="#通过媒体查询来适配高DPI显示器" class="headerlink" title="通过媒体查询来适配高DPI显示器"></a>通过媒体查询来适配高DPI显示器</h3><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 正常屏幕 */</span></span><br><span class="line"><span class="selector-id">#masthead</span> &#123;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;../img/masthead-small.jpg&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 高分别率 */</span></span><br><span class="line"><span class="keyword">@media</span> screen (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">2</span>), (<span class="attribute">min-resolution</span>: <span class="number">192dpi</span>) <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">44em</span>)&#123; <span class="comment">/* High DPI 704px/16px */</span></span><br><span class="line">  <span class="selector-id">#masthead</span> &#123;</span><br><span class="line">    <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;../img/masthead-small.jpg&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> screen (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">2</span>), (<span class="attribute">min-resolution</span>: <span class="number">192dpi</span>) <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">56em</span>)&#123; <span class="comment">/* High DPI 896px/16px */</span></span><br><span class="line">  <span class="selector-id">#masthead</span> &#123;</span><br><span class="line">    <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;../img/masthead-medium.jpg&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> screen (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">2</span>), (<span class="attribute">min-resolution</span>: <span class="number">192dpi</span>) <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">77em</span>)&#123; <span class="comment">/* High DPI 1232px/16px */</span></span><br><span class="line">  <span class="selector-id">#masthead</span> &#123;</span><br><span class="line">    <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;../img/masthead-large.jpg&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>-webkit-min-device-pixel-ratio</code>是旧的浏览器对高DPI的支持，如果比率是1相当于是96DPI，2相当于是192DPI；<code>min-resolution</code>是现代浏览器所支持的直接显示值就行了，最后<code>min-width</code>根据屏幕的宽度来加载不同大小的图片。</p><h3 id="CSS中使用SVG"><a href="#CSS中使用SVG" class="headerlink" title="CSS中使用SVG"></a>CSS中使用SVG</h3><p>CSS可以直接把SVG当做图片来使用，实际上本身也可以看成图片：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">../img/masthead.svg</span>);</span><br></pre></td></tr></table></figure><h3 id="HTML中传输图片"><a href="#HTML中传输图片" class="headerlink" title="HTML中传输图片"></a>HTML中传输图片</h3><p>图片全局max-width规则：在响应式网站中图片往往最大是屏幕的宽度，所以显示最大宽度100%会很有用的。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">img</span> &#123;</span><br><span class="line">  <span class="attribute">max-width</span>: <span class="number">100%</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>商品可以通过媒体查询根据设备分别率来使用不同的图片，实际上H5中，img标签的srcset和sizes属性也可以实现类似的功能。srcset可以根据屏幕的宽度来加载不同的图片。sizes可以通过屏幕的宽度设置图片的宽度，如下。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">img</span></span></span><br><span class="line"><span class="tag">  <span class="attr">src</span>=<span class="string">&quot;img/amp-xsmall.jpg&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">class</span>=<span class="string">&quot;articleImageFull&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">srcset</span>=<span class="string">&quot;img/amp-small.jpg 512w,</span></span></span><br><span class="line"><span class="string"><span class="tag">         img/amp-medium.jpg 768w,</span></span></span><br><span class="line"><span class="string"><span class="tag">         img/amp-large.jpg 1280w&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">sizes</span>=<span class="string">&quot;(min-width: 704px) 50vw, (min-width: 480px) 75vw, 100vw&quot;</span></span></span><br><span class="line"><span class="tag">/&gt;</span></span><br></pre></td></tr></table></figure><p>上述代码中在512px像素宽度的时候图片是<code>img/amp-small.jpg</code>，768px像素宽度的时候图片是<code>img/amp-medium.jpg</code>，1280px像素宽度的时候图片是<code>img/amp-large.jpg</code>，可见后面的w指的是屏幕宽度为多少。同样的在最小宽度704px的时候图片的宽度是宽度的50%，最小宽度是480px的时候图片的宽度是75%，最小宽度更小的时候图片的宽度是100%。</p><p>如果需要在相同的宽度的时候，根据设备分别率来显示不同的图片，那么srcset和sizes就不能做了，此时可以考虑功能更强大的picture标签，如下。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">picture</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">source</span></span></span><br><span class="line"><span class="tag">    <span class="attr">media</span>=<span class="string">&quot;(min-width: 704px)&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">srcset</span>=<span class="string">&quot;img/apm-medium.jpg 348w,img/amp-large.jpg 512w&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">sizes</span>=<span class="string">&quot;33.3vw&quot;</span></span></span><br><span class="line"><span class="tag">  &gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">source</span></span></span><br><span class="line"><span class="tag">    <span class="attr">srcset</span>=<span class="string">&quot;img/apm-cropped-small.jpg 1x,img/amp-cropped-medium.jpg 2x&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">sizes</span>=<span class="string">&quot;75vw&quot;</span></span></span><br><span class="line"><span class="tag">  &gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;img/amp-small.jpg&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">picture</span>&gt;</span></span><br></pre></td></tr></table></figure><p>可以使用type属性来加载webp图片，如下。如果支持的话使用图片<code>img/apm-small.webp</code>，不支持的话使用图片<code>img/amp-small.jpg</code>。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">picture</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">source</span> <span class="attr">srcset</span>=<span class="string">&quot;img/apm-small.webp&quot;</span> <span class="attr">type</span>=<span class="string">&quot;webp&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;img/amp-small.jpg&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">picture</span>&gt;</span></span><br></pre></td></tr></table></figure><p>picture很好用，如果不支持的时候可以走img标签做兜底处理，如果在低版本浏览器也希望使用picture标签该怎么办，就得靠<a href="https://github.com/scottjehl/picturefill">picturefill</a>了，使用方式如下。下面第一个script用来创建一个picture元素，防止因为没有该元素而导致解析错误，第二个script用来异步下载库文件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;<span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;picture&quot;</span>);&lt;/script&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;js/picturefill.min.js&quot;</span> <span class="attr">async</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h2 id="图像的进一步处理"><a href="#图像的进一步处理" class="headerlink" title="图像的进一步处理"></a>图像的进一步处理</h2><h3 id="使用雪碧图"><a href="#使用雪碧图" class="headerlink" title="使用雪碧图"></a>使用雪碧图</h3><p>雪碧图的好处：将大量图片缩减为单个图片，可以更加高效地传递资源，并通过减少到Web服务器的连接数来缩短页面的加载时间。</p><p>注：使用雪碧图可以减少HTTP请求，但在HTTP2中是反模式。</p><p>生成雪碧图：</p><figure class="highlight shell"><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">npm install -g svg-sprite</span><br><span class="line">svg-sprite -css -css-render-less -css-dest=less -css-sprite=../img/icons.svg --css-layout=diagonal img/icon-images/*.svg</span><br></pre></td></tr></table></figure><p>将雪碧图回退为图片可以使用这个工具：<a href="https://github.com/filamentgroup/grumpicon">https://github.com/filamentgroup/grumpicon</a></p><h3 id="缩小图像"><a href="#缩小图像" class="headerlink" title="缩小图像"></a>缩小图像</h3><p>书中减少使用imagemin来缩小图片jpeg和png的图片，同时也支持生成webp图片： <a href="https://github.com/imagemin/imagemin">https://github.com/imagemin/imagemin</a></p><p>imagemin提供了大量的插件：<a href="https://www.npmjs.com/search?q=keywords:imageminplugin">https://www.npmjs.com/search?q=keywords:imageminplugin</a></p><p>使用svgo来压缩svg图片：<a href="https://github.com/svg/svgo">https://github.com/svg/svgo</a></p><h3 id="使用懒加载"><a href="#使用懒加载" class="headerlink" title="使用懒加载"></a>使用懒加载</h3><p>懒加载简单实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"><span class="variable language_">window</span>, <span class="variable language_">document</span></span>)&#123;</span><br><span class="line">  <span class="string">&quot;use strict&quot;</span>;</span><br><span class="line">  <span class="comment">// 懒加载对象</span></span><br><span class="line">  <span class="keyword">var</span> lazyLoader = &#123;</span><br><span class="line">    <span class="attr">lazyClass</span>: <span class="string">&quot;lazy&quot;</span>,</span><br><span class="line">    <span class="attr">images</span>: <span class="literal">null</span>,</span><br><span class="line">    <span class="attr">processing</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">throttle</span>: <span class="number">200</span>,</span><br><span class="line">    <span class="attr">buffer</span>: <span class="number">50</span>,</span><br><span class="line">    <span class="comment">// 初始化</span></span><br><span class="line">    <span class="attr">init</span>: <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">      lazyLoader.<span class="property">images</span> = [].<span class="property">slice</span>.<span class="title function_">call</span>(<span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(lazyLoader.<span class="property">lazyClass</span>));</span><br><span class="line">      lazyLoader.<span class="title function_">scanImages</span>();</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;scroll&quot;</span>, lazyLoader.<span class="property">scanImages</span>);</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;touchmove&quot;</span>, lazyLoader.<span class="property">scanImages</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 销毁</span></span><br><span class="line">    <span class="attr">destroy</span>: <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;scroll&quot;</span>, lazyLoader.<span class="property">scanImages</span>);</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;touchmove&quot;</span>, lazyLoader.<span class="property">scanImages</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 扫描图片</span></span><br><span class="line">    <span class="attr">scanImages</span>: <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">      <span class="keyword">if</span>(<span class="variable language_">document</span>.<span class="title function_">getElementsByClassName</span>(lazyLoader.<span class="property">lazyClass</span>).<span class="property">length</span> === <span class="number">0</span>)&#123;</span><br><span class="line">        lazyLoader.<span class="title function_">destroy</span>();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(lazyLoader.<span class="property">processing</span> === <span class="literal">false</span>)&#123;</span><br><span class="line">        lazyLoader.<span class="property">processing</span> = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">          <span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">in</span> lazyLoader.<span class="property">images</span>)&#123;</span><br><span class="line">            <span class="keyword">if</span>(lazyLoader.<span class="property">images</span>[i].<span class="property">className</span>.<span class="title function_">indexOf</span>(<span class="string">&quot;lazy&quot;</span>) !== -<span class="number">1</span>)&#123;</span><br><span class="line">              <span class="keyword">if</span>(lazyLoader.<span class="title function_">inViewport</span>(lazyLoader.<span class="property">images</span>[i]))&#123;</span><br><span class="line">                lazyLoader.<span class="title function_">loadImage</span>(lazyLoader.<span class="property">images</span>[i]);</span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          lazyLoader.<span class="property">processing</span> = <span class="literal">false</span>;</span><br><span class="line">        &#125;, lazyLoader.<span class="property">throttle</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 判断图片是否出现在屏幕上</span></span><br><span class="line">    <span class="attr">inViewport</span>: <span class="keyword">function</span>(<span class="params">img</span>)&#123;</span><br><span class="line">      <span class="keyword">var</span> top = ((<span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollTop</span> || <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">scrollTop</span>) + <span class="variable language_">window</span>.<span class="property">innerHeight</span>) + lazyLoader.<span class="property">buffer</span>;</span><br><span class="line">      <span class="keyword">return</span> img.<span class="property">offsetTop</span> &lt;= top;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 加载图片</span></span><br><span class="line">    <span class="attr">loadImage</span>: <span class="keyword">function</span>(<span class="params">img</span>)&#123;</span><br><span class="line">      <span class="keyword">if</span>(img.<span class="property">parentNode</span>.<span class="property">tagName</span> === <span class="string">&quot;PICTURE&quot;</span>)&#123;</span><br><span class="line">        <span class="keyword">var</span> sourceEl = img.<span class="property">parentNode</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">&quot;source&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; sourceEl.<span class="property">length</span>; i++)&#123;</span><br><span class="line">          <span class="keyword">var</span> sourceSrcset = sourceEl[i].<span class="title function_">getAttribute</span>(<span class="string">&quot;data-srcset&quot;</span>);</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span>(sourceSrcset !== <span class="literal">null</span>)&#123;</span><br><span class="line">            sourceEl[i].<span class="title function_">setAttribute</span>(<span class="string">&quot;srcset&quot;</span>, sourceSrcset);</span><br><span class="line">            sourceEl[i].<span class="title function_">removeAttribute</span>(<span class="string">&quot;data-srcset&quot;</span>);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">var</span> imgSrc = img.<span class="title function_">getAttribute</span>(<span class="string">&quot;data-src&quot;</span>),</span><br><span class="line">        imgSrcset = img.<span class="title function_">getAttribute</span>(<span class="string">&quot;data-srcset&quot;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(imgSrc !== <span class="literal">null</span>)&#123;</span><br><span class="line">        img.<span class="title function_">setAttribute</span>(<span class="string">&quot;src&quot;</span>, imgSrc);</span><br><span class="line">        img.<span class="title function_">removeAttribute</span>(<span class="string">&quot;data-src&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(imgSrcset !== <span class="literal">null</span>)&#123;</span><br><span class="line">        img.<span class="title function_">setAttribute</span>(<span class="string">&quot;srcset&quot;</span>, imgSrcset);</span><br><span class="line">        img.<span class="title function_">removeAttribute</span>(<span class="string">&quot;data-srcset&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      lazyLoader.<span class="title function_">removeClass</span>(img, lazyLoader.<span class="property">lazyClass</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 移除样式</span></span><br><span class="line">    <span class="attr">removeClass</span>: <span class="keyword">function</span>(<span class="params">img, className</span>)&#123;</span><br><span class="line">      <span class="keyword">var</span> classArr = img.<span class="property">className</span>.<span class="title function_">split</span>(<span class="string">&quot; &quot;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; classArr.<span class="property">length</span>; i++)&#123;</span><br><span class="line">        <span class="keyword">if</span>(classArr[i] === className)&#123;</span><br><span class="line">          classArr.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      img.<span class="property">className</span> = classArr.<span class="title function_">toString</span>().<span class="title function_">replace</span>(<span class="string">&quot;,&quot;</span>, <span class="string">&quot; &quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</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="property">onreadystatechange</span> = lazyLoader.<span class="property">init</span>;</span><br><span class="line">&#125;)(<span class="variable language_">window</span>, <span class="variable language_">document</span>);</span><br></pre></td></tr></table></figure><p>上述懒加载的使用：图片上添加lazy样式，同时使用data-src代替src，src使用默认未加载图片来代替，如：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;img/blank.png&quot;</span> <span class="attr">data-src</span>=<span class="string">&quot;img/red-snapper-1x.jpg&quot;</span> <span class="attr">class</span>=<span class="string">&quot;recipeImage lazy&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="更快的字体"><a href="#更快的字体" class="headerlink" title="更快的字体"></a>更快的字体</h2><p>将ttf字体转换为其他字体：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><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">npm install -g ttf2eot ttf2woff ttf2woff2</span><br><span class="line">ttf2eot OpenSans-Light.ttf OpenSans-Light.eot</span><br><span class="line">ttf2woff OpenSans-Light.ttf OpenSans-Light.woff</span><br><span class="line">cat OpenSans-Light.ttf | ttf2woff2 &gt;&gt; OpenSans-Light.woff2</span><br></pre></td></tr></table></figure><p>CSS中使用自定义字体：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 定义字体 */</span></span><br><span class="line"><span class="keyword">@font-face</span>&#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">&quot;Open Sans Light&quot;</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">300</span>;</span><br><span class="line">  <span class="attribute">font-style</span>: normal;</span><br><span class="line">  <span class="attribute">src</span>:</span><br><span class="line">    <span class="built_in">local</span>(<span class="string">&quot;Open Sans Extra Light&quot;</span>),</span><br><span class="line">    <span class="built_in">local</span>(<span class="string">&quot;OpenSans-Light&quot;</span>),</span><br><span class="line">    <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light.woff2&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;woff2&quot;</span>),</span><br><span class="line">    <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light.woff&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;woff&quot;</span>),</span><br><span class="line">    <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light.eot&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;embedded-opentype&quot;</span>),</span><br><span class="line">    <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light.ttf&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;truetype&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.font-osl</span> &#123;</span><br><span class="line">  <span class="comment">/* 使用字体 */</span></span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">&quot;Open Sans Light&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用unicode-range加载字体子集，如下面把<code>Open Sans Light</code>拆分成BasicLatin部分和Cyrillic部分，使用<code>unicode-range</code>定义字符范围，如果文字中只有BasicLatin部分那么只会下载上面的文字，如果只有Cyrillic的文字那么下载下面的文字，都有会都下载。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@font-face</span>&#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">&quot;Open Sans Light&quot;</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">300</span>;</span><br><span class="line">  <span class="attribute">font-style</span>: normal;</span><br><span class="line">  <span class="attribute">src</span>:</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-BasicLatin.woff2&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;woff2&quot;</span>),</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-BasicLatin.woff&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;woff&quot;</span>),</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-BasicLatin.eot&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;embedded-opentype&quot;</span>),</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-BasicLatin.ttf&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;truetype&quot;</span>);</span><br><span class="line">  unicode-range: U+<span class="number">0000</span>-<span class="number">007</span>F;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@font-face</span>&#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">&quot;Open Sans Light&quot;</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">300</span>;</span><br><span class="line">  <span class="attribute">font-style</span>: normal;</span><br><span class="line">  <span class="attribute">src</span>:</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-Cyrillic.woff2&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;woff2&quot;</span>),</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-Cyrillic.woff&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;woff&quot;</span>),</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-Cyrillic.eot&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;embedded-opentype&quot;</span>),</span><br><span class="line">      <span class="built_in">url</span>(<span class="string">&quot;open-sans/OpenSans-Light-Cyrillic.ttf&quot;</span>) <span class="built_in">format</span>(<span class="string">&quot;truetype&quot;</span>);</span><br><span class="line">  unicode-range: U+<span class="number">0400</span>-<span class="number">045</span>F,U+<span class="number">0490</span>-<span class="number">0491</span>,U+<span class="number">04</span>B0-<span class="number">04</span>B1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.font-osl</span> &#123;</span><br><span class="line">  <span class="comment">/* 使用字体 */</span></span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">&quot;Open Sans Light&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>字体下载和字体实际显示直接可能有一段时间，那么这段时间内的字体是怎么显示呢？可以使用font-display来控制：</p><p><code>font-display: auto;</code> 默认值，类似于block。<br><code>font-display: block;</code> 阻塞文本渲染，直到关联的字体加载完成。<br><code>font-display: swap;</code> 显示回退文本，加载字体后显示自定义字体。<br><code>font-display: fallback;</code> auto和swap的折中方案，短时间（100ms）内显示空白，之后显示回退文本，如果字体加载完后，显示自定义字体。<br><code>font-display: optional;</code> 几乎和fallback一样，只是浏览器有更大的自由度来控制。</p><p>但是<code>font-display</code>并未得到广泛的支持，使用JS来做回退处理：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"><span class="variable language_">document</span></span>)&#123;</span><br><span class="line">  <span class="keyword">if</span>(<span class="variable language_">document</span>.<span class="property">fonts</span> &amp;&amp; <span class="variable language_">document</span>.<span class="property">cookie</span>.<span class="title function_">indexOf</span>(<span class="string">&quot;fonts-loaded&quot;</span>) === -<span class="number">1</span>)&#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">fonts</span>.<span class="title function_">load</span>(<span class="string">&quot;1em Open Sans Light&quot;</span>);</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">fonts</span>.<span class="title function_">load</span>(<span class="string">&quot;1em Open Sans Regular&quot;</span>);</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">fonts</span>.<span class="title function_">load</span>(<span class="string">&quot;1em Open Sans Bold&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">fonts</span>.<span class="property">ready</span>.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">fontFaceSet</span>)&#123;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">className</span> += <span class="string">&quot; fonts-loaded&quot;</span>;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">cookie</span> = <span class="string">&quot;fonts-loaded=&quot;</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 添加一个fonts-loaded样式 需要字定义CSS有该样式的时候才使用字体</span></span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">className</span> += <span class="string">&quot; fonts-loaded&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;)(<span class="variable language_">document</span>);</span><br></pre></td></tr></table></figure><h2 id="保持JavaScript的简洁与快速"><a href="#保持JavaScript的简洁与快速" class="headerlink" title="保持JavaScript的简洁与快速"></a>保持JavaScript的简洁与快速</h2><p>script标签会阻塞页面的渲染，放在body最后面有助于加快页面加载速度。</p><p>script带有async属性与不带async的区别：<br>不带async：下载脚本-&gt;脚本下载完成-&gt;浏览器等待其他脚本-&gt;执行脚本<br>带有async：下载脚本-&gt;脚本下载完成-&gt;执行脚本</p><p>带有async脚本下载完会立即执行而不会阻塞渲染。</p><p>使用async时需要注意，async下载完会立即执行那么，有可能执行的顺序跟script标签的顺序不同，从而导致JS执行报错。如有一个jquery.min.js文件，还有一个behaviors.js文件，其中behaviors.js引用到jquery.min.js中的$（jQuery对象），那么两个都用async就可能就会在behaviors.js中的$出现没有定义的情况。</p><p>解决方法：<br>1.可以把两个文件文件合并成一个</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><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="meta prompt_"># </span><span class="language-bash">linux 合并两个文件：</span></span><br><span class="line">cat jquery.min.js jquery.min.js &gt; script.js</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">windows 合并两个文件：</span></span><br><span class="line">type jquery.min.js jquery.min.js &gt; script.js</span><br></pre></td></tr></table></figure><ol start="2"><li>使用AMD：<a href="https://github.com/requirejs/alameda">https://github.com/requirejs/alameda</a></li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;js/alameda.js&quot;</span> <span class="attr">data-main</span>=<span class="string">&quot;js/behaviors&quot;</span> <span class="attr">async</span> &gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// js/behaviors.js 中使用AMD模块</span></span><br><span class="line">requirejs.<span class="title function_">config</span>(&#123;</span><br><span class="line">  <span class="attr">paths</span>: &#123;</span><br><span class="line">    <span class="attr">jquery</span>: <span class="string">&#x27;juqery.min&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>([<span class="string">&#x27;jquery&#x27;</span>], <span class="keyword">function</span>(<span class="params">$</span>) &#123;</span><br><span class="line">  <span class="comment">// 其他代码</span></span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="3"><li>使用defer。</li></ol><p>书中还简绍了jQuery的替代方案和用原生JS代替jQury，现在MVVM时代很少用jQuery了，就不简绍了，原生方案可以看这里：<a href="https://github.com/nefe/You-Dont-Need-jQuery">https://github.com/nefe/You-Dont-Need-jQuery</a>。</p><h2 id="使用Service-Worker提升性能"><a href="#使用Service-Worker提升性能" class="headerlink" title="使用Service Worker提升性能"></a>使用Service Worker提升性能</h2><p>Service Worker在单独的线程上工作，无法访问window对象，但可以通过中介（如postMessage API）间接访问。</p><p>使用方式：</p><ol><li>注册Server Worker</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="string">&quot;serviceWorker&quot;</span> <span class="keyword">in</span> navigator) &#123;</span><br><span class="line">    navigator.<span class="property">serviceWorker</span>.<span class="title function_">register</span>(<span class="string">&quot;/sw.js&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>编写Server Worker代码</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 文件/sw.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 案例1，添加资源的离线缓存</span></span><br><span class="line"><span class="keyword">var</span> cacheVersion = <span class="string">&quot;v2&quot;</span>,</span><br><span class="line">    cachedAssets = [</span><br><span class="line">        <span class="string">&quot;/css/global.css?v=1&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/js/debounce.js&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/js/nav.js&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/js/attach-nav.js&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/img/global/jeremy.svg&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/img/global/icon-github.svg&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/img/global/icon-email.svg&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/img/global/icon-twitter.svg&quot;</span>,</span><br><span class="line">        <span class="string">&quot;/img/global/icon-linked-in.svg&quot;</span>,</span><br><span class="line">    ];</span><br><span class="line"><span class="comment">// 添加缓存</span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&quot;install&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  event.<span class="title function_">waitUntil</span>(caches.<span class="title function_">open</span>(cacheVersion).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">cache</span>) &#123; <span class="comment">// chashes -&gt; caches</span></span><br><span class="line">      <span class="keyword">return</span> cache.<span class="title function_">addAll</span>(cachedAssets); <span class="comment">// chache -&gt; cache</span></span><br><span class="line">  &#125;).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> self.<span class="title function_">skipWaiting</span>();</span><br><span class="line">  &#125;));</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// skipWaiting 的时候会触发</span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&quot;activate&quot;</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> self.<span class="property">clients</span>.<span class="title function_">claim</span>();</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 案例2，拦截并缓存网络请求</span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&quot;fetch&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> allowedHosts = <span class="regexp">/(localhost|fonts\.googleapis\.com|fonts\.gtatic\.com)/i</span>,</span><br><span class="line">  deniedAssets = <span class="regexp">/(sw\.js|sw-install\.js)$/i</span>,</span><br><span class="line">  htmlDocument = <span class="regexp">/(\/|\.html)$/i</span>;</span><br><span class="line">  <span class="keyword">if</span>(allowedHosts.<span class="title function_">test</span>(event.<span class="property">request</span>.<span class="property">url</span>) === <span class="literal">true</span> &amp;&amp; deniedAssets.<span class="title function_">test</span>(event.<span class="property">request</span>.<span class="property">url</span>) === <span class="literal">false</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (htmlDocument.<span class="title function_">test</span>(event.<span class="property">request</span>.<span class="property">url</span>) === <span class="literal">true</span>) &#123;</span><br><span class="line">          event.<span class="title function_">respondWith</span>(</span><br><span class="line">              <span class="title function_">fetch</span>(event.<span class="property">request</span>).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">response</span>) &#123;</span><br><span class="line">                  caches.<span class="title function_">open</span>(cacheVersion).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">cache</span>) &#123;</span><br><span class="line">                      cache.<span class="title function_">put</span>(event.<span class="property">request</span>, response.<span class="title function_">clone</span>());</span><br><span class="line">                  &#125;);</span><br><span class="line">                  <span class="keyword">return</span> response;</span><br><span class="line">              &#125;).<span class="title function_">catch</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">                  <span class="keyword">return</span> caches.<span class="title function_">match</span>(event.<span class="property">request</span>);</span><br><span class="line">              &#125;)</span><br><span class="line">          );</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          event.<span class="title function_">respondWith</span>(</span><br><span class="line">              caches.<span class="title function_">match</span>(event.<span class="property">request</span>).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">cachedResponse</span>) &#123;</span><br><span class="line">                  <span class="keyword">return</span> cachedResponse ||</span><br><span class="line">                  <span class="title function_">fetch</span>(event.<span class="property">request</span>).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">fetchedResponse</span>) &#123;</span><br><span class="line">                      caches.<span class="title function_">open</span>(cacheVersion).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">cache</span>) &#123;</span><br><span class="line">                          cache.<span class="title function_">put</span>(event.<span class="property">request</span>, fetchedResponse);</span><br><span class="line">                      &#125;);</span><br><span class="line">                      <span class="keyword">return</span> fetchedResponse.<span class="title function_">clone</span>();</span><br><span class="line">                  &#125;);</span><br><span class="line">              &#125;)</span><br><span class="line">          );</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 案例3，清理缓存</span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&quot;activate&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> chacheWhitelist = [<span class="string">&quot;v2&quot;</span>];</span><br><span class="line">  event.<span class="title function_">waitUnitil</span>(</span><br><span class="line">      caches.<span class="title function_">keys</span>().<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">keyList</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([</span><br><span class="line">              keyList.<span class="title function_">map</span>(<span class="keyword">function</span>(<span class="params">key</span>)&#123;</span><br><span class="line">                  <span class="keyword">if</span>(chacheWhitelist.<span class="title function_">indexOf</span>(key) === -<span class="number">1</span>)&#123;</span><br><span class="line">                      <span class="keyword">return</span> caches.<span class="title function_">delete</span>(key);</span><br><span class="line">                  &#125;</span><br><span class="line">              &#125;), selfclients.<span class="title function_">claim</span>()</span><br><span class="line">          ]);</span><br><span class="line">      &#125;)</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="微调资源传输"><a href="#微调资源传输" class="headerlink" title="微调资源传输"></a>微调资源传输</h2><p>上面在使用gzip的时候使用了<code>compression</code>中间件，实际上<code>compression</code>是支持传入压缩等级的 范围是0~9，默认是6，但并不是越高越好，往往默认值的效果是比较好的。<br>另外<code>compression</code>也支持压缩特定的资源，可以使用filter，返回为true的时候表示压缩，false不压缩。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">app.<span class="title function_">use</span>(<span class="title function_">compression</span>(&#123;</span><br><span class="line">  <span class="attr">level</span>: <span class="number">7</span>,</span><br><span class="line">  <span class="attr">filter</span>: <span class="keyword">function</span> (<span class="params">request, response</span>) &#123;</span><br><span class="line">    <span class="comment">// 一般是根据request来判断，这里直接返回true则都压缩</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h3 id="使用Brotli压缩"><a href="#使用Brotli压缩" class="headerlink" title="使用Brotli压缩"></a>使用Brotli压缩</h3><p><code>Accept-Encoding</code>中如果有<code>br</code>说明支持Brotli压缩，express可以使用<code>shrink-ray</code>来开启Brotli压缩。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设你已经运行过: npm install https shrink-ray</span></span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">&quot;express&quot;</span>),</span><br><span class="line">  https = <span class="built_in">require</span>(<span class="string">&quot;https&quot;</span>),</span><br><span class="line">  shrinkRay = <span class="built_in">require</span>(<span class="string">&quot;shrink-ray&quot;</span>),</span><br><span class="line">  fs = <span class="built_in">require</span>(<span class="string">&quot;fs&quot;</span>),</span><br><span class="line">  path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>),</span><br><span class="line">  app = <span class="title function_">express</span>(),</span><br><span class="line">  pubDir = <span class="string">&quot;./htdocs&quot;</span>;</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="title function_">shrinkRay</span>(&#123;</span><br><span class="line">  <span class="comment">// 缓存设置可以换成 false 表示不开启，书中为了测试性能把缓存关了</span></span><br><span class="line">  <span class="attr">cache</span>: <span class="keyword">function</span>(<span class="params">request, response</span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">brotli</span>: &#123;</span><br><span class="line">    <span class="attr">quality</span>: <span class="number">11</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(path.<span class="title function_">join</span>(__dirname, pubDir)));</span><br><span class="line"></span><br><span class="line">https.<span class="title function_">createServer</span>(&#123;</span><br><span class="line">  <span class="attr">key</span>: fs.<span class="title function_">readFileSync</span>(<span class="string">&quot;crt/localhost.key&quot;</span>),</span><br><span class="line">  <span class="attr">cert</span>: fs.<span class="title function_">readFileSync</span>(<span class="string">&quot;crt/localhost.crt&quot;</span>)</span><br><span class="line">&#125;, app).<span class="title function_">listen</span>(<span class="number">8443</span>);</span><br></pre></td></tr></table></figure><p>brotli中的quality范围为0~11，值越大文件越小，默认是4，一般也够用了。</p><h3 id="设置缓存"><a href="#设置缓存" class="headerlink" title="设置缓存"></a>设置缓存</h3><p>设置Cache-Control头部的max-age指令</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(path.<span class="title function_">join</span>(__dirname, pubDir), &#123;</span><br><span class="line">  <span class="attr">maxAge</span>: <span class="string">&#x27;10s&#x27;</span>,</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><p>响应头会带有：<code>Cache-Control: max-age=10</code>，注意max-age的单位是秒。</p><p><code>Cache-Control：no-cache</code>: 向浏览器表明，下载的任何资源都可以储存在本地，但浏览器必须始终通过服务器重新验证资源。<br><code>Cache-Control：no-store</code>: 比<code>no-cache</code>更近一步，它表示浏览器不应存储受影响的资源。要求浏览器每次访问页面时下载所有受影响的资源。<br><code>Cache-Control：stale-while-revalidate=10</code>: 与<code>max-age</code>类似，单位也是秒，当资源过期后仍然使用过期的资源，同时发出请求并缓存新的资源，下次再请求的时候使用新的资源。</p><p>在CDN的<code>Cache-Control</code>有时会与<code>privite</code>和<code>public</code>连用，如<code>Cache-Control: privite, max-age=10</code>，其中privite表示中介（CDN）不在其服务器上缓存资源，public则缓存。</p><p>对不同资源设置不同的缓存策略：</p><table><thead><tr><th>资源类型</th><th>修改频率</th><th>Cache-Control头部值</th></tr></thead><tbody><tr><td>HTML</td><td>可能频繁修改，但需要尽可能保持最新</td><td>private, no-cache, max-age&#x3D;3600</td></tr><tr><td>CSS和JS</td><td>可能每月修改</td><td>public, max-age&#x3D;2592000</td></tr><tr><td>图片</td><td>几乎不会修改</td><td>public, max-age&#x3D;31536000,</td></tr></tbody></table><p>代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(path.<span class="title function_">join</span>(__dirname, pubDir), &#123;</span><br><span class="line">  <span class="attr">setHeaders</span>: <span class="keyword">function</span>(<span class="params">res, path</span>)&#123;</span><br><span class="line">    <span class="keyword">var</span> fileType = mime.<span class="title function_">lookup</span>(path);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span>(fileType)&#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;text/html&quot;</span>:</span><br><span class="line">        res.<span class="title function_">setHeader</span>(<span class="string">&quot;Cache-Control&quot;</span>, <span class="string">&quot;private, no-cache, max-age=&quot;</span> + (<span class="number">60</span>*<span class="number">60</span>));</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;text/javascript&quot;</span>:</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;application/javascript&quot;</span>:</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;text/css&quot;</span>:</span><br><span class="line">        res.<span class="title function_">setHeader</span>(<span class="string">&quot;Cache-Control&quot;</span>, <span class="string">&quot;public, max-age=&quot;</span> + (<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>*<span class="number">30</span>));</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;image/png&quot;</span>:</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;image/jpeg&quot;</span>:</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&quot;image/svg+xml&quot;</span>:</span><br><span class="line">        res.<span class="title function_">setHeader</span>(<span class="string">&quot;Cache-Control&quot;</span>, <span class="string">&quot;public, max-age=&quot;</span> + (<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>*<span class="number">365</span>));</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h3 id="资源提示"><a href="#资源提示" class="headerlink" title="资源提示"></a>资源提示</h3><p>preconnect、prefetch与preload的使用：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">ref</span>=<span class="string">&quot;preconnect&quot;</span> <span class="attr">src</span>=<span class="string">&quot;https://code.jquery.com&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">ref</span>=<span class="string">&quot;prefetch&quot;</span> <span class="attr">src</span>=<span class="string">&quot;https://code.jquery.com/jquery-2.2.4.min.js&quot;</span> <span class="attr">as</span>=<span class="string">&quot;script&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">ref</span>=<span class="string">&quot;preload&quot;</span> <span class="attr">src</span>=<span class="string">&quot;https://code.jquery.com/jquery-2.2.4.min.js&quot;</span> <span class="attr">as</span>=<span class="string">&quot;script&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>preconnect可以提供更早的DNS查询，但是如果跟HTML是同域名的时候是没用什么用的，因为已经查询过DNS了，preconnect主要是为了查询其他域名，由于HTML是自上而下解析的，通常把preconnect放在HTML的head中的上面的位置。</p><p>prefetch告诉浏览器下载特定的资源，并将其存储到浏览器缓存中。通常用来预取同一个页面的资源，或者优先缓存下一页的资源。缓存下一页的资源使用时要小心，不要下载下页没有的资源，否则会造成过多的请求。</p><p>preload如果没有as属性，可能会导致请求2次的情况，另外preload只会缓存本页面的资源。</p><h2 id="HTTP2未来展望"><a href="#HTTP2未来展望" class="headerlink" title="HTTP2未来展望"></a>HTTP2未来展望</h2><p>HTTP1的问题：</p><ol><li><strong>队头阻塞</strong>：HTTP1无法处理超过一小批的请求（通常认为是6个，因浏览器而异）。请求按接收顺序响应，在初始批处理中的所有请求完成之前，无法开始新的请求。如总共有9个任务，第一批会一次性加载6个，得等这6个中最慢的加载完后才会加载下一批的剩余3个请求。可以通过域名分片（不同域名加载不同批的资源）来处理，但实现起来比较繁琐。</li><li><strong>未压缩头部</strong>：之前zip、br等压缩处理压缩的都是响应体，但是头部信息不能压缩，而有的时候头部信息甚至比响应体更大。</li><li><strong>不安全网站</strong>：HTTP1可以不用实现SSL。</li></ol><p>HTTP2对上述问题的处理</p><ol><li><strong>不再有队头阻塞</strong>：HTTP2通过实现新的通信体系结构来并行满足更多请求。新的信道使用一个连接并行处理多个请求，连接的构成：<br><strong>流</strong>是服务器和浏览器之间的双向通信通道，一个连接可以有多个流。<br><strong>消息</strong>由流封装，单个消息相当于HTTP1的一次请求或一次响应。<br><strong>帧</strong>由消息封装，帧是消息的分割符。如响应消息中的HEADERS帧表明下一数据表示响应的HTTP头，响应消息中的DATA帧表示下一数据是所请求的内容。</li><li><strong>头部压缩</strong>：使用了HPACK压缩算法来解决这个问题，不仅压缩头部数据还通过创建一个表来存储重复的头部，以删除多余的头部。</li><li><strong>强制HTTPS</strong>：HTTP2必须实现SSL，因此HTTP2一定是HTTPS。</li></ol><p>HTTP2对不支持HTTP2的浏览器的处理：每个HTTP2服务器底层都应有一个HTTP1服务器在等待一个不支持HTTP2的客户端出现。</p><p>HTTP2的简单使用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">&quot;fs&quot;</span>),</span><br><span class="line">  path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>),</span><br><span class="line">  http2 = <span class="built_in">require</span>(<span class="string">&quot;spdy&quot;</span>),</span><br><span class="line">  mime = <span class="built_in">require</span>(<span class="string">&quot;mime&quot;</span>),</span><br><span class="line">  jsdom = <span class="built_in">require</span>(<span class="string">&quot;jsdom&quot;</span>),</span><br><span class="line">  pubDir = path.<span class="title function_">join</span>(__dirname, <span class="string">&quot;/htdocs&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> server = http2.<span class="title function_">createServer</span>(&#123;</span><br><span class="line">  <span class="attr">key</span>: fs.<span class="title function_">readFileSync</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&quot;/crt/localhost.key&quot;</span>)),</span><br><span class="line">  <span class="attr">cert</span>: fs.<span class="title function_">readFileSync</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&quot;/crt/localhost.crt&quot;</span>))</span><br><span class="line">&#125;, <span class="keyword">function</span>(<span class="params">request, response</span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> filename = path.<span class="title function_">join</span>(pubDir, request.<span class="property">url</span>),</span><br><span class="line">    contentType = mime.<span class="title function_">lookup</span>(filename),</span><br><span class="line">    protocolVersion = request.<span class="property">isSpdy</span> ? <span class="string">&quot;http2&quot;</span> : <span class="string">&quot;http1&quot;</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>((filename.<span class="title function_">indexOf</span>(pubDir) === <span class="number">0</span>) &amp;&amp; fs.<span class="title function_">existsSync</span>(filename) &amp;&amp; fs.<span class="title function_">statSync</span>(filename).<span class="title function_">isFile</span>())&#123;</span><br><span class="line">    response.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123;</span><br><span class="line">      <span class="string">&quot;content-type&quot;</span>: contentType,</span><br><span class="line">      <span class="string">&quot;cache-control&quot;</span>: <span class="string">&quot;max-age=3600&quot;</span></span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// http1 的回退处理</span></span><br><span class="line">    <span class="keyword">if</span>(protocolVersion === <span class="string">&quot;http1&quot;</span> &amp;&amp; filename.<span class="title function_">indexOf</span>(<span class="string">&quot;.html&quot;</span>) !== -<span class="number">1</span>)&#123;</span><br><span class="line">      fs.<span class="title function_">readFile</span>(filename, <span class="keyword">function</span>(<span class="params">error, data</span>)&#123;</span><br><span class="line">        jsdom.<span class="title function_">env</span>(data.<span class="title function_">toString</span>(), <span class="keyword">function</span>(<span class="params">error, <span class="variable language_">window</span></span>)&#123;</span><br><span class="line">          <span class="variable language_">window</span>.<span class="property">document</span>.<span class="property">documentElement</span>.<span class="property">classList</span>.<span class="title function_">add</span>(protocolVersion);</span><br><span class="line"></span><br><span class="line">          <span class="keyword">var</span> scripts = <span class="variable language_">window</span>.<span class="property">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&quot;script:not([crossorigin])&quot;</span>),</span><br><span class="line">            jQueryScript = <span class="variable language_">window</span>.<span class="property">document</span>.<span class="title function_">querySelector</span>(<span class="string">&quot;script[crossorigin]&quot;</span>),</span><br><span class="line">            concatenatedScript = <span class="variable language_">window</span>.<span class="property">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;script&quot;</span>);</span><br><span class="line">            concatenatedScript.<span class="property">src</span> = <span class="string">&quot;js/scripts.min.js&quot;</span>;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">in</span> scripts)&#123;</span><br><span class="line">            scripts[i].<span class="title function_">remove</span>();</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          jQueryScript.<span class="property">parentNode</span>.<span class="title function_">insertBefore</span>(concatenatedScript, jQueryScript.<span class="property">nextSibling</span>);</span><br><span class="line"></span><br><span class="line">          <span class="keyword">var</span> newDocument = <span class="string">&quot;&lt;!doctype html&gt;&quot;</span> + <span class="variable language_">window</span>.<span class="property">document</span>.<span class="property">documentElement</span>.<span class="property">outerHTML</span>;</span><br><span class="line">          response.<span class="title function_">end</span>(newDocument);</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span>&#123;</span><br><span class="line">      <span class="comment">// http2 流</span></span><br><span class="line">      <span class="keyword">var</span> fileStream = fs.<span class="title function_">createReadStream</span>(filename);</span><br><span class="line">      fileStream.<span class="title function_">pipe</span>(response);</span><br><span class="line">      fileStream.<span class="title function_">on</span>(<span class="string">&quot;finish&quot;</span>, response.<span class="property">end</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span>&#123;</span><br><span class="line">    response.<span class="title function_">writeHead</span>(<span class="number">404</span>);</span><br><span class="line">    response.<span class="title function_">end</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">8443</span>);</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Web性能调优一直是高级前端必须掌握的技能，市面上不少书简绍性能调优的书总是告诉读者一些理论性的东西，而如何去实践说的却不多，这本书不仅告诉读者Web性能优化的理论知识，同时还会告诉读者怎么用node去设置，是一本前端进阶必看的书。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/20</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://www.kai666666.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>请你挑战一下这几道nextTick面试题</title>
    <link href="https://www.kai666666.com/2021/07/30/%E8%AF%B7%E4%BD%A0%E6%8C%91%E6%88%98%E4%B8%80%E4%B8%8B%E8%BF%99%E5%87%A0%E9%81%93nextTick%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <id>https://www.kai666666.com/2021/07/30/%E8%AF%B7%E4%BD%A0%E6%8C%91%E6%88%98%E4%B8%80%E4%B8%8B%E8%BF%99%E5%87%A0%E9%81%93nextTick%E9%9D%A2%E8%AF%95%E9%A2%98/</id>
    <published>2021-07-30T10:54:24.000Z</published>
    <updated>2026-02-10T08:00:34.504Z</updated>
    
    <content type="html"><![CDATA[<p>Vue大家再熟悉不过了，Vue的<code>this.$nextTick</code>大家也再熟悉不过了，今天我们就来看看自创的<code>nextTick</code>相关的几道面试题，看看你是否真正理解Vue的<code>nextTick</code>。</p><hr><h3 id="题目1"><a href="#题目1" class="headerlink" title="题目1"></a>题目1</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; text &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 下面语句输出什么？</span></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">text</span>.<span class="property">textContent</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">3</span></span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>给你3秒，请你仔细考虑一下这道题输入什么？3、2、1…OK，你的答案是<code>3</code>吗？如果是的话，那么恭喜你，答错了！正确答案是<code>0</code>。什么？<code>this.$nextTick</code>不是等DOM处理完后才执行吗，这里怎么不适用了？等等我们再来一题，至于为什么最后再讨论。</p><h3 id="题目2"><a href="#题目2" class="headerlink" title="题目2"></a>题目2</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; text &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">4</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 下面语句输出什么？</span></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">text</span>.<span class="property">textContent</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">3</span></span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>题目2和题目1就多了一行<code>this.text = 4</code>，这个打印的是多少呢？再给你3秒，3、2、1…OK，正确答案是<code>3</code>，跟你的答案一样吗？如果一样那么恭喜你了，我们再来一道过过瘾。</p><h3 id="题目3"><a href="#题目3" class="headerlink" title="题目3"></a>题目3</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; text &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 下面语句输出什么？</span></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">text</span>.<span class="property">textContent</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">3</span></span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>题目3和题目2唯一的区别是把<code>this.text = 4</code>替换成了<code>this.text = 0</code>，你的答案是多少呢？3、2、1…OK，正确答案是<code>0</code>！此刻你的内心：**”啊…这！！！”**。别急，还有呢！！</p><h3 id="题目4"><a href="#题目4" class="headerlink" title="题目4"></a>题目4</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; text &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;&#123; text1 &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text1</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text1</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 下面语句输出什么？</span></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">text</span>.<span class="property">textContent</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">3</span></span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>本题增加了一个text1变量，并且修改了text1的值，这次打印的是多少呢？3、2、1…OK，正确答案是<code>3</code>！</p><h3 id="题目5"><a href="#题目5" class="headerlink" title="题目5"></a>题目5</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; text &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text1</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text1</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 下面语句输出什么？</span></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">text</span>.<span class="property">textContent</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">3</span></span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>题目5比题目4模板中少了一行，你再想想这次打印的是多少？3、2、1…OK，正确答案是<code>0</code>！</p><p>好了，我们这里总共5道题，答对3道算及格，你及格了吗？接下来我们分析一下。</p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><p>这节我们会粘贴大量Vue源码，大家只要看关键的代码就可以了，觉得看源码枯燥难懂的同学可以直接看本节最后的总结。</p><p>像如<code>this.text = 1</code>来设置值的时候，Vue会帮助我们异步的去更新视图，这里涉及Vue响应式原理，最终会调用<code>nextTick</code>来更新视图，本题中主要考察的是<code>nextTick</code>先后的顺序。哪怕你没看过Vue的源码也肯定知道Vue响应式原理是通过<code>Object.defineProperty</code>这个API来实现的吧，他是怎么做的呢？源码如下：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">defineReactive</span> (<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">obj</span>: <span class="title class_">Object</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">key</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">val</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">customSetter</span>?: ?<span class="title class_">Function</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">shallow</span>?: <span class="built_in">boolean</span></span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> dep = <span class="keyword">new</span> <span class="title class_">Dep</span>()</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> property = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(obj, key)</span><br><span class="line">  <span class="keyword">if</span> (property &amp;&amp; property.<span class="property">configurable</span> === <span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// cater for pre-defined getter/setters</span></span><br><span class="line">  <span class="keyword">const</span> getter = property &amp;&amp; property.<span class="property">get</span></span><br><span class="line">  <span class="keyword">const</span> setter = property &amp;&amp; property.<span class="property">set</span></span><br><span class="line">  <span class="keyword">if</span> ((!getter || setter) &amp;&amp; <span class="variable language_">arguments</span>.<span class="property">length</span> === <span class="number">2</span>) &#123;</span><br><span class="line">    val = obj[key]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> childOb = !shallow &amp;&amp; <span class="title function_">observe</span>(val)</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(obj, key, &#123;</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">get</span>: <span class="keyword">function</span> <span class="title function_">reactiveGetter</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> value = getter ? getter.<span class="title function_">call</span>(obj) : val</span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Dep</span>.<span class="property">target</span>) &#123;</span><br><span class="line">        dep.<span class="title function_">depend</span>()</span><br><span class="line">        <span class="keyword">if</span> (childOb) &#123;</span><br><span class="line">          childOb.<span class="property">dep</span>.<span class="title function_">depend</span>()</span><br><span class="line">          <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value)) &#123;</span><br><span class="line">            <span class="title function_">dependArray</span>(value)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> value</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">set</span>: <span class="keyword">function</span> <span class="title function_">reactiveSetter</span> (<span class="params">newVal</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> value = getter ? getter.<span class="title function_">call</span>(obj) : val</span><br><span class="line">      <span class="comment">/* eslint-disable no-self-compare */</span></span><br><span class="line">      <span class="keyword">if</span> (newVal === value || (newVal !== newVal &amp;&amp; value !== value)) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">/* eslint-enable no-self-compare */</span></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; customSetter) &#123;</span><br><span class="line">        <span class="title function_">customSetter</span>()</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// #7981: for accessor properties without setter</span></span><br><span class="line">      <span class="keyword">if</span> (getter &amp;&amp; !setter) <span class="keyword">return</span></span><br><span class="line">      <span class="keyword">if</span> (setter) &#123;</span><br><span class="line">        setter.<span class="title function_">call</span>(obj, newVal)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        val = newVal</span><br><span class="line">      &#125;</span><br><span class="line">      childOb = !shallow &amp;&amp; <span class="title function_">observe</span>(newVal)</span><br><span class="line">      dep.<span class="title function_">notify</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的代码有点长，但是你不要怕，你只要看29行和57行就行了，在第29行说明当调用get的时候会调用<code>dep.depend()</code>，在第57行的说明当调用set的时候会调用<code>dep.notify()</code>。</p><p>那么dep的<code>depend()</code>和<code>notify()</code>又做了什么呢？</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">Dep</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="attr">target</span>: ?<span class="title class_">Watcher</span>;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">subs</span>: <span class="title class_">Array</span>&lt;<span class="title class_">Watcher</span>&gt;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">id</span> = uid++</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">subs</span> = []</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">addSub</span> (<span class="attr">sub</span>: <span class="title class_">Watcher</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">subs</span>.<span class="title function_">push</span>(sub)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">removeSub</span> (<span class="attr">sub</span>: <span class="title class_">Watcher</span>) &#123;</span><br><span class="line">    <span class="title function_">remove</span>(<span class="variable language_">this</span>.<span class="property">subs</span>, sub)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">depend</span> () &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Dep</span>.<span class="property">target</span>) &#123;</span><br><span class="line">      <span class="title class_">Dep</span>.<span class="property">target</span>.<span class="title function_">addDep</span>(<span class="variable language_">this</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">notify</span> () &#123;</span><br><span class="line">    <span class="comment">// stabilize the subscriber list first</span></span><br><span class="line">    <span class="keyword">const</span> subs = <span class="variable language_">this</span>.<span class="property">subs</span>.<span class="title function_">slice</span>()</span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; !config.<span class="property">async</span>) &#123;</span><br><span class="line">      <span class="comment">// subs aren&#x27;t sorted in scheduler if not running async</span></span><br><span class="line">      <span class="comment">// we need to sort them now to make sure they fire in correct</span></span><br><span class="line">      <span class="comment">// order</span></span><br><span class="line">      subs.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">id</span> - b.<span class="property">id</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = subs.<span class="property">length</span>; i &lt; l; i++) &#123;</span><br><span class="line">      subs[i].<span class="title function_">update</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The current target watcher being evaluated.</span></span><br><span class="line"><span class="comment">// This is globally unique because only one watcher</span></span><br><span class="line"><span class="comment">// can be evaluated at a time.</span></span><br><span class="line"><span class="title class_">Dep</span>.<span class="property">target</span> = <span class="literal">null</span></span><br></pre></td></tr></table></figure><p>这里的<code>Dep.target</code>和<code>sub</code>都是<code>Watcher</code>对象的实例。这里我们先捋一捋，当我们调用属性的set的时候，会调用<code>dep.notify()</code>而该函数又调用了<code>subs[i].update()</code>也就是<code>Watcher</code>对象的<code>update()</code>方法，那么<code>Watcher</code>的<code>update</code>和上面给到的<code>addDep</code>方法又做了什么呢？</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">addDep</span> (<span class="attr">dep</span>: <span class="title class_">Dep</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> id = dep.<span class="property">id</span></span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">newDepIds</span>.<span class="title function_">has</span>(id)) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">newDepIds</span>.<span class="title function_">add</span>(id)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">newDeps</span>.<span class="title function_">push</span>(dep)</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">depIds</span>.<span class="title function_">has</span>(id)) &#123;</span><br><span class="line">      dep.<span class="title function_">addSub</span>(<span class="variable language_">this</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">update</span> () &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">lazy</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">dirty</span> = <span class="literal">true</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">sync</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">run</span>()</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">queueWatcher</span>(<span class="variable language_">this</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>addDep</code>方法中可以看到当<code>Watcher</code>对象调用<code>addDep</code>的时候，实际上是传入的<code>Dep</code>对象把自己当做sub添加进去，这样在<code>Dep</code>对象调用<code>notify</code>才能通知到对应的Watcher，也就是说<strong>组件的data在调用set前一定要调用get才会通知对应的Watcher来更新视图，实际上只要模板中用到了变量就会调用变量的get</strong>。</p><p><code>update</code>方法中我们看到有一个<code>queueWatcher(this)</code>的方法，这个又是搞什么呢？</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">queueWatcher</span> (<span class="params"><span class="attr">watcher</span>: <span class="title class_">Watcher</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> id = watcher.<span class="property">id</span></span><br><span class="line">  <span class="keyword">if</span> (has[id] == <span class="literal">null</span>) &#123;</span><br><span class="line">    has[id] = <span class="literal">true</span></span><br><span class="line">    <span class="keyword">if</span> (!flushing) &#123;</span><br><span class="line">      queue.<span class="title function_">push</span>(watcher)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// if already flushing, splice the watcher based on its id</span></span><br><span class="line">      <span class="comment">// if already past its id, it will be run next immediately.</span></span><br><span class="line">      <span class="keyword">let</span> i = queue.<span class="property">length</span> - <span class="number">1</span></span><br><span class="line">      <span class="keyword">while</span> (i &gt; index &amp;&amp; queue[i].<span class="property">id</span> &gt; watcher.<span class="property">id</span>) &#123;</span><br><span class="line">        i--</span><br><span class="line">      &#125;</span><br><span class="line">      queue.<span class="title function_">splice</span>(i + <span class="number">1</span>, <span class="number">0</span>, watcher)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// queue the flush</span></span><br><span class="line">    <span class="keyword">if</span> (!waiting) &#123;</span><br><span class="line">      waiting = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; !config.<span class="property">async</span>) &#123;</span><br><span class="line">        <span class="title function_">flushSchedulerQueue</span>()</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="title function_">nextTick</span>(flushSchedulerQueue)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>queueWatcher</code>我们可以看到如果<code>has[id] == null</code>就把<code>has[id]</code>设置为true，然后把<code>watcher</code>插入到队列中。由于一个组件对应一个Watcher（当然计算属性也会对应Watcher，这里说的是组件级别的Watcher），当一个属性改变后就会调用<code>has[id] = true</code>，这样当再把当前组件的属性改了以后，由于<code>has[id]</code>已经是true了，就没必要再加入到队列中了，毕竟更新视图，一次性直接全改了。最后你看到最重要的一行代码，就是<code>nextTick(flushSchedulerQueue)</code>，我们终于看到<code>nextTick</code>的影子了。调用<code>nextTick</code>的时候会把传入的函数push进回调队列里面，也就是这里把<code>flushSchedulerQueue</code>放在队列的尾部了，这个函数又做了什么呢？</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">flushSchedulerQueue</span> (<span class="params"></span>) &#123;</span><br><span class="line">  currentFlushTimestamp = <span class="title function_">getNow</span>()</span><br><span class="line">  flushing = <span class="literal">true</span></span><br><span class="line">  <span class="keyword">let</span> watcher, id</span><br><span class="line"></span><br><span class="line">  queue.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">id</span> - b.<span class="property">id</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// do not cache length because more watchers might be pushed</span></span><br><span class="line">  <span class="comment">// as we run existing watchers</span></span><br><span class="line">  <span class="keyword">for</span> (index = <span class="number">0</span>; index &lt; queue.<span class="property">length</span>; index++) &#123;</span><br><span class="line">    watcher = queue[index]</span><br><span class="line">    <span class="keyword">if</span> (watcher.<span class="property">before</span>) &#123;</span><br><span class="line">      watcher.<span class="title function_">before</span>()</span><br><span class="line">    &#125;</span><br><span class="line">    id = watcher.<span class="property">id</span></span><br><span class="line">    has[id] = <span class="literal">null</span></span><br><span class="line">    watcher.<span class="title function_">run</span>()</span><br><span class="line">    <span class="comment">// 省略部分代码...</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 省略部分代码...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到<code>flushSchedulerQueue</code>先给queue进行了排序，queue中存放的就是watcher，如果有<code>watcher.before</code>的话则调用一下，处理完后把<code>has[id]</code>置为null，最关键的一行是调用了<code>watcher.run()</code>，我们再看看<code>watcher.run()</code>做了什么。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">run</span> () &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">active</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> value = <span class="variable language_">this</span>.<span class="title function_">get</span>()</span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">      value !== <span class="variable language_">this</span>.<span class="property">value</span> ||</span><br><span class="line">      <span class="comment">// Deep watchers and watchers on Object/Arrays should fire even</span></span><br><span class="line">      <span class="comment">// when the value is the same, because the value may</span></span><br><span class="line">      <span class="comment">// have mutated.</span></span><br><span class="line">      <span class="title function_">isObject</span>(value) ||</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">deep</span></span><br><span class="line">    ) &#123;</span><br><span class="line">      <span class="comment">// set new value</span></span><br><span class="line">      <span class="keyword">const</span> oldValue = <span class="variable language_">this</span>.<span class="property">value</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">value</span> = value</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">user</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="variable language_">this</span>.<span class="property">cb</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>.<span class="property">vm</span>, value, oldValue)</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">          <span class="title function_">handleError</span>(e, <span class="variable language_">this</span>.<span class="property">vm</span>, <span class="string">`callback for watcher &quot;<span class="subst">$&#123;<span class="variable language_">this</span>.expression&#125;</span>&quot;`</span>)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">cb</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>.<span class="property">vm</span>, value, oldValue)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>run</code>方法貌似就设置了一下value的值，另外执行了一个<code>this.cb.call(this.vm, value, oldValue)</code>方法，这个cb是Watcher构造函数的第三个参数，通常情况下是一个空函数。这里最重要的是<code>const value = this.get()</code>这行代码，这里调用了一下Watcher的get方法，这个get方法是什么呢？</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">get</span> () &#123;</span><br><span class="line">  <span class="title function_">pushTarget</span>(<span class="variable language_">this</span>)</span><br><span class="line">  <span class="keyword">let</span> value</span><br><span class="line">  <span class="keyword">const</span> vm = <span class="variable language_">this</span>.<span class="property">vm</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    value = <span class="variable language_">this</span>.<span class="property">getter</span>.<span class="title function_">call</span>(vm, vm)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">user</span>) &#123;</span><br><span class="line">      <span class="title function_">handleError</span>(e, vm, <span class="string">`getter for watcher &quot;<span class="subst">$&#123;<span class="variable language_">this</span>.expression&#125;</span>&quot;`</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> e</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="comment">// &quot;touch&quot; every property so they are all tracked as</span></span><br><span class="line">    <span class="comment">// dependencies for deep watching</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">deep</span>) &#123;</span><br><span class="line">      <span class="title function_">traverse</span>(value)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">popTarget</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">cleanupDeps</span>()</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> value</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里需要注意的是get方法开始的时候调用<code>pushTarget</code>，结束的时候调用<code>popTarget</code>，<strong>那么在这两段中间的代码调用<code>Dep.target</code>时指向的就是当前的Watcher对象</strong>。另外get方法中有一个<code>this.getter</code>，这个的值如下根据Watcher第二个参数<code>expOrFn</code>来定的，我们可以在Watcher的构造方法中看到<code>getter</code>的取值逻辑：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Watcher</span> &#123;</span><br><span class="line">  <span class="comment">// 部分代码省略</span></span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params"></span></span><br><span class="line"><span class="params">    <span class="attr">vm</span>: <span class="title class_">Component</span>,</span></span><br><span class="line"><span class="params">    <span class="attr">expOrFn</span>: <span class="built_in">string</span> | <span class="title class_">Function</span>,</span></span><br><span class="line"><span class="params">    <span class="attr">cb</span>: <span class="title class_">Function</span>,</span></span><br><span class="line"><span class="params">    <span class="attr">options</span>?: ?<span class="title class_">Object</span>,</span></span><br><span class="line"><span class="params">    <span class="attr">isRenderWatcher</span>?: <span class="built_in">boolean</span></span></span><br><span class="line"><span class="params">  </span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vm</span> = vm</span><br><span class="line">    <span class="keyword">if</span> (isRenderWatcher) &#123;</span><br><span class="line">      vm.<span class="property">_watcher</span> = <span class="variable language_">this</span></span><br><span class="line">    &#125;</span><br><span class="line">    vm.<span class="property">_watchers</span>.<span class="title function_">push</span>(<span class="variable language_">this</span>)</span><br><span class="line">    <span class="comment">// options</span></span><br><span class="line">    <span class="keyword">if</span> (options) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">deep</span> = !!options.<span class="property">deep</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">user</span> = !!options.<span class="property">user</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">lazy</span> = !!options.<span class="property">lazy</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">sync</span> = !!options.<span class="property">sync</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">before</span> = options.<span class="property">before</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">deep</span> = <span class="variable language_">this</span>.<span class="property">user</span> = <span class="variable language_">this</span>.<span class="property">lazy</span> = <span class="variable language_">this</span>.<span class="property">sync</span> = <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">cb</span> = cb</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">id</span> = ++uid <span class="comment">// uid for batching</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">active</span> = <span class="literal">true</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">dirty</span> = <span class="variable language_">this</span>.<span class="property">lazy</span> <span class="comment">// for lazy watchers</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">deps</span> = []</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">newDeps</span> = []</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">depIds</span> = <span class="keyword">new</span> <span class="title class_">Set</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">newDepIds</span> = <span class="keyword">new</span> <span class="title class_">Set</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">expression</span> = process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span></span><br><span class="line">      ? expOrFn.<span class="title function_">toString</span>()</span><br><span class="line">      : <span class="string">&#x27;&#x27;</span></span><br><span class="line">    <span class="comment">// parse expression for getter</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> expOrFn === <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">getter</span> = expOrFn</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">getter</span> = <span class="title function_">parsePath</span>(expOrFn)</span><br><span class="line">      <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">getter</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">getter</span> = noop</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">value</span> = <span class="variable language_">this</span>.<span class="property">lazy</span></span><br><span class="line">      ? <span class="literal">undefined</span></span><br><span class="line">      : <span class="variable language_">this</span>.<span class="title function_">get</span>()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 部分代码省略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果第二个参数<code>expOrFn</code>是函数的话<code>this.getter</code>就是它，如果是<code>a.b.c</code>这种字符串的话则进行解析，否则给个空函数。那么这第二个参数<code>expOrFn</code>又是什么呢，请看下面：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">mountComponent</span> (<span class="params"></span></span><br><span class="line"><span class="params">  vm,</span></span><br><span class="line"><span class="params">  el,</span></span><br><span class="line"><span class="params">  hydrating</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  vm.<span class="property">$el</span> = el;</span><br><span class="line">  <span class="comment">// 省略部分代码...</span></span><br><span class="line">  <span class="title function_">callHook</span>(vm, <span class="string">&#x27;beforeMount&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> updateComponent;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 省略部分代码...</span></span><br><span class="line"></span><br><span class="line">  updateComponent = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    vm.<span class="title function_">_update</span>(vm.<span class="title function_">_render</span>(), hydrating);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// we set this to vm._watcher inside the watcher&#x27;s constructor</span></span><br><span class="line">  <span class="comment">// since the watcher&#x27;s initial patch may call $forceUpdate (e.g. inside child</span></span><br><span class="line">  <span class="comment">// component&#x27;s mounted hook), which relies on vm._watcher being already defined</span></span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Watcher</span>(vm, updateComponent, noop, &#123;</span><br><span class="line">    <span class="attr">before</span>: <span class="keyword">function</span> <span class="title function_">before</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (vm.<span class="property">_isMounted</span> &amp;&amp; !vm.<span class="property">_isDestroyed</span>) &#123;</span><br><span class="line">        <span class="title function_">callHook</span>(vm, <span class="string">&#x27;beforeUpdate&#x27;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="literal">true</span> <span class="comment">/* isRenderWatcher */</span>);</span><br><span class="line">  hydrating = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// manually mounted instance, call mounted on self</span></span><br><span class="line">  <span class="comment">// mounted is called for render-created child components in its inserted hook</span></span><br><span class="line">  <span class="keyword">if</span> (vm.<span class="property">$vnode</span> == <span class="literal">null</span>) &#123;</span><br><span class="line">    vm.<span class="property">_isMounted</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="title function_">callHook</span>(vm, <span class="string">&#x27;mounted&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> vm</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>mountComponent</code>代码写的是非常漂亮！首先调用了<code>beforeMount</code>生命周期方法，然后初始化<code>Watcher</code>，最后调用<code>mounted</code>生命周期方法。我们注意看<code>Watcher</code>的第二个参数<code>updateComponent</code>，该函数的实现是<code>vm._update(vm._render(), hydrating);</code>也就是说<code>Watcher</code>调用get方法的时候实际上是去更新视图了。这里你需要注意一点，<code>Watcher</code>的constructor中最后会调用<code>this.get()</code>而这时最终也会调用<code>updateComponent</code>方法，这也就是在<code>beforeMount</code>和<code>mounted</code>之间会把视图更新在DOM上的代码。</p><p>总结：</p><blockquote><ol><li>Vue会在beforeMount和mounted生命周期之间创建<code>Watcher</code>，并更新视图，当组件的<code>Watcher</code>对象调用run方法的时候，最终会调用<code>vm._update(vm._render(), hydrating);</code>来更新视图；</li><li>当数据改变的时候，会调用<code>Object.defineProperty</code>中的set，这时除了赋值以外，还会调用<code>dep.notify()</code>来通知已收集依赖的<code>Watcher</code>调用<code>update</code>方法进行更新；</li><li><code>Watcher</code>调用<code>update</code>方法进行更新时，会调用<code>queueWatcher(this)</code>把当前的<code>Watcher</code>对象加入到队列中，同时执行<code>nextTick(flushSchedulerQueue)</code>；</li><li>当下一个tick执行的时候会调用<code>flushSchedulerQueue</code>方法，该方法会调用<code>watcher.run()</code>方法，进而调用<code>watcher.get()</code>用来更新视图；</li><li>只有先调用get收集了依赖的data，在set时才可能会引起视图的更新。</li></ol></blockquote><h3 id="回到本题"><a href="#回到本题" class="headerlink" title="回到本题"></a>回到本题</h3><p>通过源码分析我们对Vue修改视图的逻辑有了更深的认识，现在我们再回过头来看看前面的题。</p><p>题目1：由于先调用的<code>this.$nextTick</code>后修改的数据，这样数据后引起视图更改的<code>nextTick</code>会在<code>this.$nextTick</code>之后，所以打印未修改前的值，所以是<code>0</code>。<br>题目2：由于先修改的数据后调用的<code>this.$nextTick</code>，这样数据后引起视图更改的<code>nextTick</code>会在<code>this.$nextTick</code>之前，由于<code>nextTick</code>是异步的，当<code>nextTick</code>执行的时候，值已经是最后一次修改的值了，所以是<code>3</code>。<br>题目3：虽然首先调用的赋值，但是值并没有改变，在<code>Object.defineProperty</code>的<code>set</code>方法中可以看到，如果值相同直接<code>return</code>了，所以本题和题目1其实是一样的，也是<code>0</code>。<br>题目4：修改text1的时候也会使用<code>nextTick</code>来更新视图，而<code>this.$nextTick</code>中的函数排在更新完视图后执行，所以结果是更新后的值，也就是<code>3</code>。<br>题目5：在模板中把text1部分去掉了，那么text1相当于就没有调用get了，这样就不会再调用<code>dep.depend()</code>来收集依赖了，所以text1修改并不会引起<code>nextTick</code>去更新视图，所以此题的情况跟题目1也是一样的了结果是<code>0</code>。</p><h3 id="题目6"><a href="#题目6" class="headerlink" title="题目6"></a>题目6</h3><p>通过本章的学习，估计你已经收获满满，现在来一道最难的题：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&quot;text&quot;</span>&gt;</span>&#123;&#123; text &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">text1</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="variable language_">this</span>.<span class="property">text1</span>)</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text1</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 下面语句输出什么？</span></span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">text</span>.<span class="property">textContent</span>)</span></span><br><span class="line"><span class="language-javascript">    &#125;)</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">text</span> = <span class="number">3</span></span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这题是题目5的变种，在设置前通过<code>console.info(this.text1)</code>打印了一下text1的值，那么就会调用<code>get</code>方法，那么问题来了，此时结果是什么？3、2、1…OK，什么！你说<code>3</code>？哈哈，当然不对，这里还是<code>0</code>，为什么呢？这里虽然调用<code>get</code>方法了，但是<code>Dep.target</code>是<code>undefined</code>，所以也没有收集到依赖，毕竟在get方法中只有<code>Dep.target</code>不为空才去调用<code>dep.depend()</code>。那么为什么<code>Dep.target</code>是<code>undefined</code>的呢？之前说过<code>Watcher</code>的<code>get</code>方法开始的时候调用<code>pushTarget</code>，结束的时候调用<code>popTarget</code>，而这个时候打印的时早就<code>popTarget</code>了，所以<code>Dep.target</code>是<code>undefined</code>。那么为什么写在模板里面就有了呢？实际上在<code>mountComponent</code>方法中创建<code>Watcher</code>时，构造方法最下面会调用<code>Watcher</code>的<code>get</code>方法，<code>get</code>方法不是先会调用一下<code>pushTarget</code>吗？此时的<code>Dep.target</code>指向的是当前的<code>Watcher</code>对象，这个时候<code>this.getter.call(vm, vm)</code>实际调用的是<code>vm._update(vm._render(), hydrating);</code>，而<code>vm._render</code>就会处理模板中的变量，那么模板中变量的<code>get</code>也就会被调用了，<strong>所以放在模板中的变量在会被收集依赖</strong>。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Vue大家再熟悉不过了，Vue的&lt;code&gt;this.$nextTick&lt;/code&gt;大家也再熟悉不过了，今天我们就来看看自创的&lt;code&gt;nextTick&lt;/code&gt;相关的几道面试题，看看你是否真正理解Vue的&lt;code&gt;nextTick&lt;/code&gt;。&lt;/p&gt;
&lt;hr</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="Vue" scheme="https://www.kai666666.com/tags/Vue/"/>
    
  </entry>
  
  <entry>
    <title>for...of循环的使用</title>
    <link href="https://www.kai666666.com/2021/06/24/for-of%E5%BE%AA%E7%8E%AF%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
    <id>https://www.kai666666.com/2021/06/24/for-of%E5%BE%AA%E7%8E%AF%E7%9A%84%E4%BD%BF%E7%94%A8/</id>
    <published>2021-06-24T15:12:59.000Z</published>
    <updated>2026-02-10T08:00:34.396Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>for…of语句在可迭代对象（包括 Array，Map，Set，String，TypedArray，arguments 对象等等）上创建一个迭代循环，调用自定义迭代钩子，并为每个不同属性的值执行语句。<br>– <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...of">MDN</a></p></blockquote><hr><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><p>for…of的基本使用比较简单：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 遍历数组</span></span><br><span class="line"><span class="keyword">let</span> array = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> array) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历字符串</span></span><br><span class="line"><span class="keyword">let</span> str = <span class="string">&quot;abc&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> str) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历Map</span></span><br><span class="line"><span class="keyword">let</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>([[<span class="string">&quot;a&quot;</span>, <span class="number">1</span>], [<span class="string">&quot;b&quot;</span>, <span class="number">2</span>], [<span class="string">&quot;c&quot;</span>, <span class="number">3</span>]]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> map) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 [&quot;a&quot;, 1] [&quot;b&quot;, 2] [&quot;c&quot;, 3]</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历Set</span></span><br><span class="line"><span class="keyword">let</span> set = <span class="keyword">new</span> <span class="title class_">Set</span>([<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> set) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历arguments</span></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> argument <span class="keyword">of</span> <span class="variable language_">arguments</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(argument); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)(<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>);</span><br></pre></td></tr></table></figure><h2 id="可迭代对象"><a href="#可迭代对象" class="headerlink" title="可迭代对象"></a>可迭代对象</h2><p>for…of的语法比较简单，上面我们遍历了这么多数据，现在我们使用for…of遍历一下对象：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> object = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> object) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 报错：Uncaught TypeError: object is not iterable</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果很不幸，使用for…of遍历对象报错了。为什么报错了，报错的错误提示写的很清楚，因为object对象不是可迭代的，也就是说它不是<strong>可迭代对象</strong>。</p><p>这里遇到一个新的名词，什么是<strong>可迭代对象</strong>呢？</p><p>要成为<strong>可迭代对象</strong>， 这个对象必须实现<code>@@iterator</code>方法，并且该方法返回一个符合<code>迭代器协议的对象</code>。</p><p>这里有2个问题，第一怎么去实现一个<code>@@iterator</code>方法？看到<code>@@xxx</code>这样的方法，想都不用想就是指<code>[Symbol.xxx]</code>方法，这里也就是一个方法的key是<code>[Symbol.iterator]</code>就可以了，比如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> object = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span>,</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]: <span class="keyword">function</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第二个问题什么是符合<code>迭代器协议的对象</code>？首先<code>迭代器协议的对象</code>是一个对象，这个对象有一个<code>next</code>方法，这个<code>next</code>方法每次调用有会返回一个对象，这个返回的对象又有一个<code>done</code>属性和一个<code>value</code>属性。其中<code>done</code>属性表示是否完成，如果是<code>true</code>则表示完成，<code>false或者不写</code>则表示没有完成；value表示值，也就是for…of循环时每次使用的值，如果done为true时候则可以不写。举个<strong>可迭代对象</strong>的例子：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> loop10 = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">next</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          <span class="attr">value</span>: i++,</span><br><span class="line">          <span class="attr">done</span>: i &gt; <span class="number">10</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> loop10) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 0 1 2 3 4 5 6 7 8 9</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>迭代器协议的对象</code>也可以自己调用着玩玩：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> iterator = loop10[<span class="title class_">Symbol</span>.<span class="property">iterator</span>]();</span><br><span class="line">iterator.<span class="title function_">next</span>(); <span class="comment">// 返回 &#123;value: 0, done: false&#125;</span></span><br><span class="line">iterator.<span class="title function_">next</span>(); <span class="comment">// 返回 &#123;value: 1, done: false&#125;</span></span><br><span class="line">iterator.<span class="title function_">next</span>(); <span class="comment">// 返回 &#123;value: 2, done: false&#125;</span></span><br></pre></td></tr></table></figure><p>当然<code>迭代器协议的对象</code>不仅仅只能用在for-of循环中，也可以用在数组的解构上：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [...loop10]; <span class="comment">// arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</span></span><br></pre></td></tr></table></figure><h2 id="可迭代对象与generator函数"><a href="#可迭代对象与generator函数" class="headerlink" title="可迭代对象与generator函数"></a>可迭代对象与generator函数</h2><p>当我们看到一个个可迭代对象的next方法，再看看一个个的<code>&#123;value: 0, done: false&#125;</code>这种符合<code>迭代器协议的对象</code>，这时不想跟generator没点关系都不行了，没错generator函数返回的正是可迭代对象。我们先使用常规方法实现一下对象的for…of遍历。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> object = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span>,</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> keys = <span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">next</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          <span class="attr">value</span>: keys[i++],</span><br><span class="line">          <span class="attr">done</span>: i &gt; keys.<span class="property">length</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> object) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用generator函数可以简化上述步骤：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> object = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span>,</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]: <span class="keyword">function</span> *() &#123;</span><br><span class="line">    <span class="keyword">let</span> keys = <span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="variable language_">this</span>)</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; keys.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">yield</span> keys[i]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> object) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>是不是很方便？这里偷偷告诉你一个小秘密：<strong>generator函数调用后的对象也可以用在for…of上</strong>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> loop10Gen = <span class="keyword">function</span> *() &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">yield</span> i</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注意这里是loop10Gen() 而不是loop10Gen</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">of</span> <span class="title function_">loop10Gen</span>()) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 0 1 2 3 4 5 6 7 8 9</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面不是说了，可迭代对象要实现一个<code>@@iterator</code>方法，这里实现了吗？没错，这里还真实现了！你可以试试：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> itarator = <span class="title function_">loop10Gen</span>();</span><br><span class="line">itarator[<span class="title class_">Symbol</span>.<span class="property">iterator</span>]() === itarator; <span class="comment">// 返回true</span></span><br></pre></td></tr></table></figure><p>于是我们就得到一个比较绕的真理：<strong>generator调用后的对象，既是可迭代对象，也是符合<code>迭代器协议的对象</code>。</strong></p><h2 id="for…of与for…in的区别"><a href="#for…of与for…in的区别" class="headerlink" title="for…of与for…in的区别"></a>for…of与for…in的区别</h2><ol><li>for…in遍历的是对象的可枚举属性，而for…of语句遍历的是可迭代对象所定义要迭代的数据。</li></ol><p>  由于for…in遍历的是对象的可枚举属性，所以对于数组来说打印的是键，而不是值：</p>  <figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">in</span> array) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;0&#x27; &#x27;1&#x27; &#x27;2&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">of</span> array) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>for…in会遍历对象原型和原型链上的可枚举的属性。</li></ol>  <figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">formObject</span> = <span class="literal">true</span>;</span><br><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">formArray</span> = <span class="literal">true</span>;</span><br><span class="line">array.<span class="property">hello</span> = <span class="string">&#x27;world&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">in</span> array) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 0 1 2 hello formArray formObject</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">of</span> array) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>  通常为了避免for…in遍历原型和原型链上无关的可枚举属性，使用<code>Object.hasOwnProperty()</code>方法来判断：</p>  <figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">let</span> array = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">formObject</span> = <span class="literal">true</span>;</span><br><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">formArray</span> = <span class="literal">true</span>;</span><br><span class="line">array.<span class="property">hello</span> = <span class="string">&#x27;world&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">in</span> array) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Object</span>.<span class="property">hasOwnProperty</span>.<span class="title function_">call</span>(array, value)) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 0 1 2 hello</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> value <span class="keyword">of</span> array) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 &#x27;a&#x27; &#x27;b&#x27; &#x27;c&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="可迭代对象的return方法"><a href="#可迭代对象的return方法" class="headerlink" title="可迭代对象的return方法"></a>可迭代对象的return方法</h2><p>可迭代对象除了next方法外还有return方法，主要用在循环中断的时候会调用，比如是用<code>break</code>关键字、或者抛出一个Error：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> loop10 = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">next</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          <span class="attr">value</span>: i++,</span><br><span class="line">          <span class="attr">done</span>: i &gt; <span class="number">10</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">return</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;return调用了~~&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> &#123; <span class="attr">done</span>: <span class="literal">true</span> &#125;;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> loop10) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value); <span class="comment">// 分别打印 0 1 2 3</span></span><br><span class="line">  <span class="keyword">if</span> (value === <span class="number">3</span>) &#123;</span><br><span class="line">    <span class="keyword">break</span>; <span class="comment">// 打印 &#x27;return调用了~~&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;for…of语句在可迭代对象（包括 Array，Map，Set，String，TypedArray，arguments 对象等等）上创建一个迭代循环，调用自定义迭代钩子，并为每个不同属性的值执行语句。&lt;br&gt;– &lt;a href=&quot;https://d</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="基本语法" scheme="https://www.kai666666.com/tags/%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>vue2你该知道的一切（下）</title>
    <link href="https://www.kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8B%EF%BC%89/"/>
    <id>https://www.kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8B%EF%BC%89/</id>
    <published>2021-06-19T17:10:21.000Z</published>
    <updated>2026-02-10T08:00:34.396Z</updated>
    
    <content type="html"><![CDATA[<p>本章继续回顾Vue相关的知识，主要针对组件这块，基础部分请看<a href="https://kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8A%EF%BC%89/">上一章</a>。</p><hr><h2 id="组件基础"><a href="#组件基础" class="headerlink" title="组件基础"></a>组件基础</h2><p>简单的组件：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">custom-button</span>&gt;</span><span class="tag">&lt;/<span class="name">custom-button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 自定义组件</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> <span class="title class_">CustomButton</span> = &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">template</span>: <span class="string">&#x27;&lt;button&gt;Custom button&lt;/button&gt;&#x27;</span></span></span><br><span class="line"><span class="language-javascript">  &#125;;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">components</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">CustomButton</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上面的组件需要在<code>components</code>中引入，当然也可以定义一个全局的组件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;custom-button&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;button&gt;Custom button&lt;/button&gt;&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>上面组件只有一个template属性，一般的组件还有data、方法、计算属性等。这里需要注意的是组件的data需要是一个方法，并且返回一个对象，而Vue实例的data是一个对象，如果组件的data是一个对象的话，那么多个组件将会使用一份数据，这样所有组件数据都是一样的，某个组件修改数据会影响到其他的同类组件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;positive-numbers&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;p&gt;&#123;&#123; positiveNumbers.length &#125;&#125; positive numbers&lt;/p&gt;&#x27;</span>,</span><br><span class="line">  <span class="title function_">data</span>(<span class="params"></span>) &#123; <span class="comment">// 组件中这里需要是函数</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">numbers</span>: [-<span class="number">5</span>, <span class="number">0</span>, <span class="number">2</span>, -<span class="number">1</span>, <span class="number">1</span>, <span class="number">0.5</span>]</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">computed</span>: &#123;</span><br><span class="line">    <span class="title function_">positiveNumbers</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">numbers</span>.<span class="title function_">filter</span>(<span class="function">(<span class="params">number</span>) =&gt;</span> number &gt;= <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Props"><a href="#Props" class="headerlink" title="Props"></a>Props</h2><p>组件的Props可以声明父组件要传递到子组件的数据：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">color-preview</span> <span class="attr">color</span>=<span class="string">&quot;red&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">color-preview</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">color-preview</span> <span class="attr">color</span>=<span class="string">&quot;blue&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">color-preview</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;color-preview&#x27;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">template</span>: <span class="string">&#x27;&lt;div class=&quot;color-preview&quot; :style=&quot;style&quot;&gt;&lt;/div&gt;&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">props</span>: [<span class="string">&#x27;color&#x27;</span>],</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">computed</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">style</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> &#123; <span class="attr">backgroundColor</span>: <span class="variable language_">this</span>.<span class="property">color</span> &#125;;</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span></span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>props可以添加校验和默认值以及是否必须，校验失败在开发环境会报错：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;price-display&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">price</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">Number</span>,</span><br><span class="line">      <span class="attr">required</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="title function_">validator</span>(<span class="params">value</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> value &gt;= <span class="number">0</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;，</span><br><span class="line">    <span class="attr">num</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">Number</span>,</span><br><span class="line">      <span class="attr">required</span>: <span class="literal">true</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">unit</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">      <span class="attr">default</span>: <span class="string">&#x27;$&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="组件props的大小写"><a href="#组件props的大小写" class="headerlink" title="组件props的大小写"></a>组件props的大小写</h2><p>模板中组件的props如果是带横线的属性，最后在组件内部将会自动转化为驼峰形式，如下</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">price-display</span> <span class="attr">percentage-discount</span>=<span class="string">&quot;20&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">price-display</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;price-display&#x27;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">props</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">percentageDiscount</span>: <span class="title class_">Number</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span></span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里需要注意一点<code>percentage-discount=&quot;20&quot;</code>它的值最后是字符串的’20’而不是数字的20，所以上面代码会报错，如果要是数字的则需要<code>:percentage-discount=&quot;20&quot;</code>，同样的Boolean类型的也一样。</p><h2 id="sync操作符"><a href="#sync操作符" class="headerlink" title="sync操作符"></a>sync操作符</h2><p>子组件中不建议直接修改父组件传下来的props，通常使用子组件的$emit方法来修改告诉父组件要修改值。某些情况下可以使用aync操作符来简化，赋值操作。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">count-from-number</span> <span class="attr">:number.sync</span>=<span class="string">&quot;numberToDisplay&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>组件定义：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;count-from-number&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;p&gt;The number is &#123;&#123; number &#125;&#125;&lt;/p&gt;&#x27;</span>,</span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">number</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">Number</span>,</span><br><span class="line">      <span class="attr">required</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.$emit(<span class="string">&#x27;updated:number&#x27;</span>, <span class="variable language_">this</span>.<span class="property">number</span> + <span class="number">1</span>);</span><br><span class="line">    &#125;, <span class="number">1000</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>实际上sync操作符只是一个语法糖，上面使用sync操作符的代码等同于：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">count-from-number</span></span></span><br><span class="line"><span class="tag">  <span class="attr">:number.sync</span>=<span class="string">&quot;numberToDisplay&quot;</span></span></span><br><span class="line"><span class="tag">  @<span class="attr">update:number</span>=<span class="string">&quot;val =&gt; numberToDispaly = val&quot;</span></span></span><br><span class="line"><span class="tag">  /&gt;</span></span><br></pre></td></tr></table></figure><h2 id="自定义组件的v-model"><a href="#自定义组件的v-model" class="headerlink" title="自定义组件的v-model"></a>自定义组件的v-model</h2><p>假设现在需要写一个只能输入小写字母的组件input-username，它需要支持v-model：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input-username</span></span></span><br><span class="line"><span class="tag">  <span class="attr">v-model</span>=<span class="string">&quot;username&quot;</span></span></span><br><span class="line"><span class="tag">  /&gt;</span></span><br></pre></td></tr></table></figure><p>上述代码等同于：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input-username</span></span></span><br><span class="line"><span class="tag">  <span class="attr">:value</span>=<span class="string">&quot;username&quot;</span></span></span><br><span class="line"><span class="tag">  @<span class="attr">input</span>=<span class="string">&quot;value =&gt; username = value&quot;</span></span></span><br><span class="line"><span class="tag">  /&gt;</span></span><br></pre></td></tr></table></figure><p>所以要使得自定义组件支持<code>v-model</code>可以这样写：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;input-username&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;input type=&quot;text&quot; :value=&quot;value&quot; @input=&quot;handleInput&quot;&gt;&#x27;</span>,</span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">value</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">      <span class="attr">required</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">handleInput</span>(<span class="params">e</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> value = e.<span class="property">taret</span>.<span class="property">value</span>.<span class="title function_">toLowerCase</span>();</span><br><span class="line"></span><br><span class="line">      <span class="comment">// If valeu was changed, update it on the input too</span></span><br><span class="line">      <span class="keyword">if</span> (value !== e.<span class="property">target</span>.<span class="property">value</span>) &#123;</span><br><span class="line">        e.<span class="property">target</span>.<span class="property">value</span> = value;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="variable language_">this</span>.$emit(<span class="string">&#x27;input&#x27;</span>, value);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="插槽slot"><a href="#插槽slot" class="headerlink" title="插槽slot"></a>插槽slot</h2><p>简单使用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;custom-button&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;button class=&quot;custom-button&quot;&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/button&gt;&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>上面定义了一个<code>custom-button</code>的组件，则组件中间的部分将会给到插槽的位置：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">custom-button</span>&gt;</span>Press me!<span class="tag">&lt;/<span class="name">custom-button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>渲染后：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;custom-button&quot;</span>&gt;</span>Press me!<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>插槽也可以给默认内容，只要下载<code>slot</code>标签中间：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;custom-button&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">`&lt;button class=&quot;custom-button&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;slot&gt;&lt;span class=&quot;default-text&quot;&gt;Default button text&lt;/span&gt;&lt;/slot&gt;</span></span><br><span class="line"><span class="string">  &lt;/button&gt;`</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这是如果自定义组件没有内容的时候将会使用默认内容，如：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">custom-button</span>&gt;</span><span class="tag">&lt;/<span class="name">custom-button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>渲染后：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;custom-button&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;default-text&quot;</span>&gt;</span>Default button text<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具名插槽就是给插槽起一个名字，如下面是<code>blog-post</code>组件的模板，其中<code>&lt;slot name=&quot;header&quot;&gt;&lt;/slot&gt;</code>就是一个具名插槽，</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">section</span> <span class="attr">class</span>=<span class="string">&quot;blog-post&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">header</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">slot</span> <span class="attr">name</span>=<span class="string">&quot;header&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>Post by &#123;&#123; author.name &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">slot</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">section</span>&gt;</span></span><br></pre></td></tr></table></figure><p>使用方式也很简单：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">blog-post</span> <span class="attr">:author</span>=<span class="string">&quot;author&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">h2</span> <span class="attr">slot</span>=<span class="string">&quot;header&quot;</span>&gt;</span>Blog post title<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>Blog post content<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>More blog post content<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">blog-post</span>&gt;</span></span><br></pre></td></tr></table></figure><p>最终渲染的结果是：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="tag">&lt;<span class="name">section</span> <span class="attr">class</span>=<span class="string">&quot;blog-post&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">header</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h2</span>&gt;</span>Blog post title<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>Post by Callum Macrae<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>Blog post content<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>More blog post content<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">section</span>&gt;</span></span><br></pre></td></tr></table></figure><p>作用域插槽，组件的插槽可以向外面暴露数据：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;user-data&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;div class=&quot;user&quot;&gt;&lt;slot :user=&quot;user&quot;&gt;&lt;/slot&gt;&lt;/div&gt;&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">user</span>: &#123; <span class="attr">name</span>: <span class="number">123</span> &#125;,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>:&#123;</span><br><span class="line">    <span class="attr">username</span>:<span class="string">&#x27;123&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">template</span>: <span class="string">`&lt;user-data v-slot:default=&quot;aaa&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;p &gt;User name: &#123;&#123; aaa.user.name &#125;&#125;&lt;/p&gt;</span></span><br><span class="line"><span class="string">    &lt;/user-data&gt;`</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>上面相当于给defailt作用域的值定义为aaa变量，aaa.user就能获取到插槽的user属性了。当然默认插槽的也可以简写：<code>v-slot=&quot;aaa&quot;</code>。</p><h2 id="Mixin"><a href="#Mixin" class="headerlink" title="Mixin"></a>Mixin</h2><p>mixin的简单使用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> loggingMixin = &#123;</span><br><span class="line">  <span class="title function_">created</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Logged from mixin&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;example-component&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">mixins</span>: [loggingMixin],</span><br><span class="line">  <span class="title function_">created</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Logged from component&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>当组件被创建后先后打印：<code>Logged from mixin</code>和<code>Logged from component</code>。说白了mixin对象就是一个普通的JavaScript对象，它可以混入属性、方法、生命周期等，其中属性和方法如果组件中也有同名的则组件中的会覆盖mixin中的，但是生命周期都会执行。</p><h2 id="vue-loader的使用"><a href="#vue-loader的使用" class="headerlink" title="vue-loader的使用"></a>vue-loader的使用</h2><p>当使用vue-loader了以后就可以创建<code>.vue</code>文件了。</p><p>之前的组件书写形式是：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;display-number&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;p&gt;The number is &#123;&#123; number &#125;&#125;&lt;/p&gt;&#x27;</span>,</span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">number</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">Number</span>,</span><br><span class="line">      <span class="attr">required</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><code>.vue</code>文件组件的书写形式是：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>The number is &#123;&#123; number &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">props</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">number</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">type</span>: <span class="title class_">Number</span>,</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">required</span>: <span class="literal">true</span></span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>代码更加之目了然了，组件的引用如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">display-number</span> <span class="attr">:number</span>=<span class="string">&quot;4&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">display-number</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> <span class="title class_">DisplayNumber</span> <span class="keyword">from</span> <span class="string">&#x27;./components/display-number.vue&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">components</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">DisplayNumber</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="非prop属性"><a href="#非prop属性" class="headerlink" title="非prop属性"></a>非prop属性</h2><p>vue对非prop属性的处理是放在组件的外层上，并覆盖原有的。对于class和style则会合并。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">custom-button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span>&gt;</span>Click me!<span class="tag">&lt;/<span class="name">custom-button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-handlebars"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  const CustomButton = &#123;</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">    template: &#x27;<span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">slot</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span>&#x27;</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  &#125;;</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars"></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  new Vue(&#123;</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">    el: &#x27;#app&#x27;,</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">    components: &#123;</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">      CustomButton</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">    &#125;</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars">  &#125;);</span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars"></span></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>最后渲染为：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span>&gt;</span>Click me!<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>可以使用<code>this.$attr</code>获取所有的非props属性。</p><h2 id="render方法"><a href="#render方法" class="headerlink" title="render方法"></a>render方法</h2><p>上面的例子中都是使用template来定义组件的，实际上还可以写render函数来定义组件。假设有一个组件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">CustomButton</span> = &#123;</span><br><span class="line">  <span class="attr">data</span>: <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">counter</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">  <span class="attr">template</span>: <span class="string">`&lt;div&gt;</span></span><br><span class="line"><span class="string">    &lt;button v-on:click=&quot;counter++&quot;&gt;Click to increase counter&lt;/button&gt;</span></span><br><span class="line"><span class="string">    &lt;p&gt;You&#x27;ve clicked the button &#123;&#123; counter &#125;&#125;&lt;/p&gt; times.</span></span><br><span class="line"><span class="string">  &lt;/div&gt;`</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>使用render定义如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">CustomButton</span> = &#123;</span><br><span class="line">  <span class="attr">data</span>: <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">counter</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">  <span class="title function_">render</span>(<span class="params">createElement</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">createElement</span>(</span><br><span class="line">      <span class="string">&#x27;div&#x27;</span>,</span><br><span class="line">      [</span><br><span class="line">        <span class="title function_">createElement</span>(</span><br><span class="line">        <span class="string">&#x27;button&#x27;</span>,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">on</span>: &#123;</span><br><span class="line">              <span class="attr">click</span>: <span class="function">() =&gt;</span> <span class="variable language_">this</span>.<span class="property">counter</span>++,</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">&#x27;Click to increase counter&#x27;</span></span><br><span class="line">        ),</span><br><span class="line">        <span class="title function_">createElement</span>(</span><br><span class="line">          <span class="string">&#x27;p&#x27;</span>,</span><br><span class="line">          <span class="string">`You&#x27;ve clicked the button <span class="subst">$&#123;<span class="variable language_">this</span>.counter&#125;</span> times`</span></span><br><span class="line">        )</span><br><span class="line">      ]</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上述代码是不是很难理解？render中代码的编写通常是由JSX来生成的，项目中使用<code>babel-plugin-transform-vue-jsx</code>插件可以把JSX转换成类似于上述的函数内容。上述代码如果使用了JSX编写如下，是不是React很像？</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">CustomButton</span> = &#123;</span><br><span class="line">  <span class="attr">data</span>: <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">counter</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">clickHandler</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">counter</span>++</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.clickHandler&#125;</span>&gt;</span>Click to increase counter<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>You&#x27;ve clicked the button &#123;counter&#125; times<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>如果render和template同时出现，那么优先会使用render。</strong></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本章继续回顾Vue相关的知识，主要针对组件这块，基础部分请看&lt;a href=&quot;https://kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="Vue" scheme="https://www.kai666666.com/tags/Vue/"/>
    
  </entry>
  
  <entry>
    <title>vue2你该知道的一切（上）</title>
    <link href="https://www.kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8A%EF%BC%89/"/>
    <id>https://www.kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8A%EF%BC%89/</id>
    <published>2021-06-19T09:53:19.000Z</published>
    <updated>2026-02-10T08:00:34.396Z</updated>
    
    <content type="html"><![CDATA[<p>Vue知识点众多，这<a href="https://kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8B%EF%BC%89/#more">两章</a>记录一下Vue2.x版本的常规使用方法。如果你是一名使用Vue框架开发的前端工程师，那么这两章的内容会让你对Vue的知识点记得更牢固。</p><hr><h2 id="如何引入"><a href="#如何引入" class="headerlink" title="如何引入"></a>如何引入</h2><p>HTML只要引入Vue就可以直接使用了，这里可以使用CDN地址<a href="https://unpkg.com/vue">https://unpkg.com/vue</a>来引入Vue：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/vue&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">created</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// This code will run on startup</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>除了HTML直接引入Vue以外，也可以使用vue-loader与webpack配合，来使用<code>.vue</code>文件来编写代码，webpack中配置vue-loader如下（只截取vue-loader配置部分）：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">  <span class="attr">rules</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">test</span>: <span class="regexp">/\.vue$/</span>,</span><br><span class="line">      <span class="attr">loader</span>: <span class="string">&#x27;vue-loader&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// ...其他loader</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然最简单的还是使用vue-cli来初始化一个项目：</p><figure class="highlight shell"><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">npm install -g vue-cli</span><br><span class="line">vue init webpack</span><br></pre></td></tr></table></figure><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><p><strong>data中的数据可以使用双花括号引入，也可以用在<code>v-</code>开头的指令中</strong>，如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span> <span class="attr">v-if</span>=<span class="string">&quot;path === &#x27;/&#x27;&quot;</span>&gt;</span>你在主页<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span> <span class="attr">v-else</span>&gt;</span>你当前的地址是：&#123;&#123; path &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">path</span>: location.<span class="property">pathname</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="v-if与v-show的使用"><a href="#v-if与v-show的使用" class="headerlink" title="v-if与v-show的使用"></a>v-if与v-show的使用</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">&quot;true&quot;</span>&gt;</span>v-if true<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">&quot;false&quot;</span>&gt;</span>v-if false<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-show</span>=<span class="string">&quot;true&quot;</span>&gt;</span>v-show true<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-show</span>=<span class="string">&quot;false&quot;</span>&gt;</span>v-show false<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>最终输出的是HTML如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span>v-if true<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!----&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span>v-show true<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&quot;display: none;&quot;</span>&gt;</span>v-show false<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>可见，v-show是使用<code>display</code>来控制是否显示的，而v-if则直接控制是有有该元素。</strong></p><p>v-if多条件判断：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">&quot;state === &#x27;loading&#x27;&quot;</span>&gt;</span>加载中..。<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-else-if</span>=<span class="string">&quot;state === &#x27;error&#x27;&quot;</span>&gt;</span>出错了<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-else</span>&gt;</span>其他内容<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="v-for的使用"><a href="#v-for的使用" class="headerlink" title="v-for的使用"></a>v-for的使用</h2><p><strong>v-for是循环指令，可以用在数组，对象和数字上。</strong></p><p>数组循环：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;(item, index) in dogs&quot;</span> <span class="attr">:key</span>=<span class="string">&quot;index&quot;</span>&gt;</span>&#123;&#123; 1 + index + &#x27;:&#x27; + item &#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">dogs</span>: [<span class="string">&#x27;Rex&#x27;</span>, <span class="string">&#x27;Rover&#x27;</span>, <span class="string">&#x27;Henrietta&#x27;</span>, <span class="string">&#x27;Alan&#x27;</span>]</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>对象循环：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;(value, key) in obj&quot;</span> <span class="attr">:key</span>=<span class="string">&quot;key&quot;</span>&gt;</span>&#123;&#123; key + &#x27;:&#x27; + value &#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">obj</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">a</span>: <span class="number">1</span>,</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">b</span>: <span class="number">2</span>,</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">c</span>: <span class="number">3</span>,</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>数字循环，这里打印1~10，<strong>注意v-for用在数字上，索引会从1开始而不是0</strong>：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;num in 10&quot;</span> <span class="attr">:key</span>=<span class="string">&quot;num&quot;</span>&gt;</span>&#123;&#123; num &#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="属性绑定"><a href="#属性绑定" class="headerlink" title="属性绑定"></a>属性绑定</h2><p>属性绑定使用<code>v-bind:</code>，或者简写形式<code>:</code>冒号</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">v-bind:type</span>=<span class="string">&quot;buttonType&quot;</span>&gt;</span>Test button<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">:type</span>=<span class="string">&quot;buttonType&quot;</span>&gt;</span>Test button<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">buttonType</span>: <span class="string">&#x27;submit&#x27;</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>由于Vue是使用Object.defineProperty来做的响应式的，所以对于<strong>给对象添加新的属性、使用数组下标修改数组值、修改数组长度来删除数组元素</strong>这三种操作是无法做到响应式，所以Vue提供了set和delete方法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">set</span>(data, <span class="string">&#x27;key&#x27;</span>, value);</span><br><span class="line"><span class="comment">// 组件内可以</span></span><br><span class="line"><span class="variable language_">this</span>.$set(data, <span class="string">&#x27;key&#x27;</span>, value);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">delete</span>( data, index);</span><br><span class="line"><span class="comment">// 组件内可以</span></span><br><span class="line"><span class="variable language_">this</span>.$delete( data, index);</span><br></pre></td></tr></table></figure><h2 id="v-html"><a href="#v-html" class="headerlink" title="v-html"></a>v-html</h2><p>如果需要把一段字符串当做HTML元素来使用的时候可以使用v-html，否则HTML标签会被转移。需要注意的是v-html的内容一点要保证是安全的，否则容易受到XSS攻击。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-html</span>=<span class="string">&quot;yourHtml&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="v-modal双向绑定"><a href="#v-modal双向绑定" class="headerlink" title="v-modal双向绑定"></a>v-modal双向绑定</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">v-model</span>=<span class="string">&quot;inputText&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>inputText: &#123;&#123; inputText &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;number in filterPositive(numbers)&quot;</span>&gt;</span>&#123;&#123; number &#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">numbers</span>: [-<span class="number">5</span>, <span class="number">0</span>, <span class="number">2</span>, -<span class="number">1</span>, <span class="number">1</span>, <span class="number">0.5</span>]</span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">methods</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">filterPositive</span>(<span class="params">numbers</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> numbers.<span class="title function_">filter</span>(<span class="function">(<span class="params">number</span>) =&gt;</span> number &gt;= <span class="number">0</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="计算属性"><a href="#计算属性" class="headerlink" title="计算属性"></a>计算属性</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>Sum of numbers: &#123;&#123; numberTotal &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">numbers</span>: [<span class="number">5</span>, <span class="number">8</span>, <span class="number">3</span>]</span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">computed</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">numberTotal</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> numbers.<span class="title function_">reduce</span>(<span class="function">(<span class="params">sum, val</span>) =&gt;</span> sum + val);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>计算属性与方法的区别</strong>：计算属性会被缓存，如果在模板中多次调用一个方法，方法中的代码在每一次调用时都会执行一遍，但是如果计算属性被多次调用，其中的代码会执行一次，之后每次调用都会使用被缓存的值。<strong>只有当计算属性的依赖发生变化时，代码才会被再次执行。</strong>此外方法可以携带参数，但是计算属性可以设置值，如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>Sum of numbers: &#123;&#123; numberTotal &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line">  new Vue(&#123;</span><br><span class="line">    el: &#x27;#app&quot;,</span><br><span class="line">    data: &#123;</span><br><span class="line">      numbers: [5, 8, 3]</span><br><span class="line">    &#125;,</span><br><span class="line">    computed: &#123;</span><br><span class="line">      numberTotal: &#123;</span><br><span class="line">        get() &#123;</span><br><span class="line">          return numbers.reduce((sum, val) =&gt; sum + val);</span><br><span class="line">        &#125;,</span><br><span class="line">        set(newValue) &#123;</span><br><span class="line">          const oldValue = this.numberTotal;</span><br><span class="line">          const difference = newValue - oldValue;</span><br><span class="line">          this.numbers.push(difference)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="watch的使用"><a href="#watch的使用" class="headerlink" title="watch的使用"></a>watch的使用</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">watchers</span>: &#123;</span><br><span class="line">    <span class="title function_">count</span>(<span class="params">newValue, oldValue</span>) &#123;</span><br><span class="line">      <span class="comment">// this.count has been changed!</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;新的值：&#x27;</span> + newValue)</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;旧的值：&#x27;</span> + oldValue)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>注意：在watch中一定不要去修改当前监听的值，否则容易造成死循环。如上面count方法中不要写<code>this.count = XXX</code>。</p><p>watch也可以监听对象的某个属性：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">formData</span>: &#123;</span><br><span class="line">      <span class="attr">username</span>: <span class="string">&#x27;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">watch</span>: &#123;</span><br><span class="line">    <span class="string">&#x27;formData.username&#x27;</span>(newValue, oldValue) &#123;</span><br><span class="line">      <span class="comment">// this.formData.username has changed</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>当然也可以使用函数来监听：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.$watch(<span class="string">&#x27;formData.username&#x27;</span>, <span class="keyword">function</span> (<span class="params">newValue, oldValue</span>) &#123;</span><br><span class="line"></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>对于watch监听对象的情况如果对象的引用不变，则不会调用监听的方法，如上面formData.username改变如果只监听formData则不会调用，如果这种情况需要调用的时候，可以传递deep参数：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">formData</span>: &#123;</span><br><span class="line">      <span class="attr">username</span>: <span class="string">&#x27;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">watch</span>: &#123;</span><br><span class="line">    <span class="attr">formData</span>: &#123;</span><br><span class="line">      <span class="title function_">handler</span>(<span class="params">newValue, oldValue</span>) &#123;</span><br><span class="line">        <span class="comment">// formData.username改变也会调用</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">deep</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="filter的使用"><a href="#filter的使用" class="headerlink" title="filter的使用"></a>filter的使用</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>Product one cost: &#123;&#123; productOneCost | formatCost(&#x27;$&#x27;) &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">productOneCost</span>: <span class="number">998</span>,</span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">filters</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">formatCost</span>(<span class="params">value, symbol = <span class="string">&#x27;￥&#x27;</span></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> symbol + (value / <span class="number">100</span>).<span class="title function_">toFixed</span>(<span class="number">2</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上述最后渲染出的是<code>Product one cost: $9.98</code>，filter也可以不传参数，如下</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>Product one cost: &#123;&#123; productOneCost | formatCost &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上述渲染出的是<code>Product one cost: ￥9.98</code></p><p>除此之外还可以定义全局的过滤器：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">filter</span>(<span class="string">&#x27;formatCost&#x27;</span>, <span class="keyword">function</span> (<span class="params">value, symbol = <span class="string">&#x27;￥&#x27;</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> symbol + (value / <span class="number">100</span>).<span class="title function_">toFixed</span>(<span class="number">2</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="使用ref访问元素或者组件"><a href="#使用ref访问元素或者组件" class="headerlink" title="使用ref访问元素或者组件"></a>使用ref访问元素或者组件</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">ref</span>=<span class="string">&quot;myCanvas&quot;</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- JS中使用如下访问 --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- this.$refs.myCanvas --&gt;</span></span><br></pre></td></tr></table></figure><h2 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h2><p>最简单的使用：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">v-on:click</span>=<span class="string">&quot;counter++&quot;</span>&gt;</span>Click to increase counter<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>You&#x27;ve clicked the button &#123;&#123; counter &#125;&#125;<span class="tag">&lt;/<span class="name">P</span>&gt;</span> times.</span><br></pre></td></tr></table></figure><p>使用方法：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">v-on:click</span>=<span class="string">&quot;increase&quot;</span>&gt;</span>Click to increase counter<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>You&#x27;ve clicked the button &#123;&#123; counter &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span> times.</span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">counter</span>: <span class="number">0</span></span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">methods</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">increase</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">this</span>.<span class="property">counter</span>++;</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>v-on:可以使用@来代替简写：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">&quot;increase&quot;</span>&gt;</span>Click to increase counter<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>修饰符：</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="tag">&lt;<span class="name">button</span> @<span class="attr">click.stop.prevent</span>=<span class="string">&quot;increase&quot;</span>&gt;</span>Click to increase counter<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">input</span> @<span class="attr">keyup.enter</span>=<span class="string">&quot;submit&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>Vue的修饰符有很多，不同修饰符间可以连用，修饰符的顺序不同左右可能也会不同。这里简单的列举一下常用的修饰符：</p><table><thead><tr><th>常用修饰符</th><th>说明</th></tr></thead><tbody><tr><td>.stop</td><td>阻止事件冒泡</td></tr><tr><td>.prevent</td><td>阻止默认行为</td></tr><tr><td>.capture</td><td>捕获模式</td></tr><tr><td>.self</td><td>只监听元素自身而不监听子元素</td></tr><tr><td>.once</td><td>只调用一次方法</td></tr><tr><td>.passive</td><td>提前告知不阻止默认行为（可以提高移动端性能），不可与.prevent一起使用，如果同时存在则忽略.prevent</td></tr><tr><td>.exact</td><td>准确地触发，如<code>@click.ctrl.exact</code>则只有当ctrl按下并且点击的时候才触发；再如<code>@click.exact</code>则只有点击切不能按任何其他键才触发</td></tr><tr><td>.ctrl</td><td>ctrl按下时</td></tr><tr><td>.alt</td><td>alt按下时</td></tr><tr><td>.shift</td><td>shift按下时</td></tr><tr><td>.meta</td><td>Command或者Windows键被按下时</td></tr></tbody></table><p>鼠标按钮修饰符：</p><table><thead><tr><th>修饰符</th><th>说明</th></tr></thead><tbody><tr><td>.left</td><td>鼠标左键，如<code>@mousedown.left</code>表示鼠标左键被按下</td></tr><tr><td>.right</td><td>鼠标右键</td></tr><tr><td>.middle</td><td>鼠标滚轮</td></tr></tbody></table><p>对于keyup等按键事件来说，提供了专门的按键修饰符，如：<code>.enter</code>、<code>.tab</code>、<code>.delete</code>(捕获“删除”和“退格”键)、<code>.esc</code>、<code>.space</code>、<code>.up</code>、<code>.down</code>、<code>.left</code>、<code>.right</code>和<code>.数字</code>(数字对应的是keyCode)</p><p>也可以自定义键名，如<code>v-on:keyup.f1</code></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="property">config</span>.<span class="property">keyCodes</span>.<span class="property">f1</span> = <span class="number">112</span></span><br></pre></td></tr></table></figure><h2 id="生命周期钩子函数"><a href="#生命周期钩子函数" class="headerlink" title="生命周期钩子函数"></a>生命周期钩子函数</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="title function_">beforeCreate</span>(<span class="params"></span>) &#123; <span class="comment">// 实例初始化前被触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">created</span>(<span class="params"></span>) &#123;<span class="comment">// 初始化后被添加到DOM之前触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">beforeMount</span>(<span class="params"></span>) &#123;<span class="comment">// 已经准备好添加DOM前触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">mounted</span>(<span class="params"></span>) &#123;<span class="comment">// 元素创建后触发（不一定添加到DOM中了，需要使用nextTick来保证添加进去了）</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">beforeUpdate</span>(<span class="params"></span>) &#123;<span class="comment">// 数据更新后将对dom做一些更改时触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">updated</span>(<span class="params"></span>) &#123;<span class="comment">// DOM更新后触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">deforeDestroy</span>(<span class="params"></span>) &#123;<span class="comment">// 即将销毁时触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">destroyed</span>(<span class="params"></span>) &#123;<span class="comment">// 销毁后触发</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="自定义指令"><a href="#自定义指令" class="headerlink" title="自定义指令"></a>自定义指令</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">v-blink</span>&gt;</span>This content will blink<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="title class_">Vue</span>.<span class="title function_">directive</span>(<span class="string">&#x27;blink&#x27;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">bind</span>(<span class="params">el, binding, vnode, oldVno</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">let</span> isVisible = <span class="literal">true</span>;</span></span><br><span class="line"><span class="language-javascript">    el.<span class="property">dataset</span>.<span class="property">blinkTimer</span> = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      isVisible = !isVisible;</span></span><br><span class="line"><span class="language-javascript">      el.<span class="property">style</span>.<span class="property">visibility</span> = isVisible ? <span class="string">&#x27;visible&#x27;</span> : <span class="string">&#x27;hidden&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;, binding.<span class="property">value</span> || <span class="number">1000</span>);</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">unbind</span>(<span class="params">el</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span>(el.<span class="property">dataset</span>.<span class="property">blinkTimer</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="built_in">clearInterval</span>(el.<span class="property">dataset</span>.<span class="property">blinkTimer</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">delete</span> el.<span class="property">dataset</span>.<span class="property">blinkTimer</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>指令有5个钩子函数：bind、inserted、update、componentUpdated、unbind。<br>update指的是当前组件被更新时调用，此时可能子组件还没有更新。componentUpdated是所有子组件都更新后调用。</p><p>定义指令还有一种简写的形式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">directive</span>(<span class="string">&#x27;my-directive&#x27;</span>, <span class="function">(<span class="params">el</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 这里的回调将会在bind和update的时候调用</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>钩子函数的参数有el、binding、vnode 和 oldVnod。其中binding又是一个对象，它的属性有：name、value、oldValue（仅在 update 和 componentUpdated钩子中可用）、expression、arg、modifiers。</p><h2 id="transition动画"><a href="#transition动画" class="headerlink" title="transition动画"></a>transition动画</h2><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">transition</span> <span class="attr">name</span>=<span class="string">&quot;fade&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">&quot;divVisible&quot;</span>&gt;</span>This content is sometimes hidden<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">transition</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.fade-enter-active</span>, <span class="selector-class">.fade-leave-active</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">transition</span>: opacity .<span class="number">5s</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"><span class="selector-class">.fade-enter</span>, <span class="selector-class">.fade-leave-to</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">opacity</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><p>过渡类名：{name}-enter、{name}-enter-active、{name}-enter-to、{name}-leave、{name}-leave-active、{name}-leave-to。</p><p>过渡也可以使用JS的钩子函数来做：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">transition</span></span></span><br><span class="line"><span class="tag">    <span class="attr">v-on:before-enter</span>=<span class="string">&quot;handleBeforeEnter&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">v-on:enter</span>=<span class="string">&quot;handleEnterp&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">v-on:leave</span>=<span class="string">&quot;handleLeave&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">&quot;divVisible&quot;</span>&gt;</span>...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">transition</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">data</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">divVisible</span>: <span class="literal">false</span></span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">methods</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">handleBeforeEnter</span>(<span class="params">el</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      el.<span class="property">style</span>.<span class="property">opacity</span> = <span class="number">0</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">handleEnter</span>(<span class="params">el, done</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">TweenLite</span>.<span class="title function_">to</span>(el, <span class="number">0.6</span>, &#123; <span class="attr">opacity</span>: <span class="number">1</span>, <span class="attr">onComplete</span>: done &#125;);</span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">handleLeave</span>(<span class="params">el, done</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">TweenLite</span>.<span class="title function_">to</span>(el, <span class="number">0.6</span>, &#123; <span class="attr">opacity</span>: <span class="number">0</span>, <span class="attr">onComplete</span>: done &#125;);</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="绑定Class"><a href="#绑定Class" class="headerlink" title="绑定Class"></a>绑定Class</h2><p>静态使用class：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;foo bar&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- ... --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>可以使用数组：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:class</span>=<span class="string">&quot;[&#x27;foo&#x27;, &#x27;bar&#x27;]&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- ... --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>也可以使用对象，如果值为true的时候则使用该类：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:class</span>=<span class="string">&quot;&#123;foo: false, bar: true&#125;&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- ... --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>还可以混着使用数组和对象：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:class</span>=<span class="string">&quot;[&#x27;class1&#x27;, &#123;foo: false, bar: true&#125;]&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- ... --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>还可以写一个静态的和一个动态的class，最终的结果是两者的合并：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;class1&quot;</span> <span class="attr">:class</span>=<span class="string">&quot;&#123;foo: false, bar: true&#125;&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- ... --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="绑定style"><a href="#绑定style" class="headerlink" title="绑定style"></a>绑定style</h2><p>style的绑定和class有些类似，需要注意的是对于<code>font-weight</code>这种带有连字符的属性需要使用驼峰形式，如<code>fontWeight</code>。</p><p>对象形式：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:style</span>=<span class="string">&quot;&#123; fontWeight: &#x27;bold&#x27;, color: &#x27;red&#x27; &#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>数组形式，可以串联多个对象：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:style</span>=<span class="string">&quot;[&#123;fontWeight: &#x27;bold&#x27;&#125;, &#123;color: &#x27;red&#x27;&#125;]&quot;</span>&gt;</span>...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>一般情况下，Vue会自动加上浏览器的前缀的，当然也可以自己设置多重置：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:style</span>=<span class="string">&quot;&#123; display: [&#x27;-webkit-box&#x27;, &#x27;-ms-flexbox&#x27;, &#x27;flex&#x27;] &#125;&quot;</span>&gt;</span>...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="vue-loader的Scoped-CSS"><a href="#vue-loader的Scoped-CSS" class="headerlink" title="vue-loader的Scoped CSS"></a>vue-loader的Scoped CSS</h2><p>在.vue文件中样式默认是全局的，如果在style标签中加入scoped则只会应用在本组件内：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>The number is <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;number&quot;</span>&gt;</span>123<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  &#125;;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">scoped</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.number</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><p>为什么会只改变本组件呢？实际上当写有scoped后，打包出来的组件元素都将会有一个data-v-{hash}的属性，而样式也会加上该属性，如下(hash可能是其他的值)：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">data-v-e0e8ddca</span>&gt;</span>The number is <span class="tag">&lt;<span class="name">span</span> <span class="attr">data-v-e0e8ddca</span> <span class="attr">class</span>=<span class="string">&quot;number&quot;</span>&gt;</span>123<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.number</span><span class="selector-attr">[data-v-e0e8ddca]</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="vue-loader的CSS-Modules"><a href="#vue-loader的CSS-Modules" class="headerlink" title="vue-loader的CSS Modules"></a>vue-loader的CSS Modules</h2><p>style标签中使用module属性，表示使用了CSS Modules，如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>The number is <span class="tag">&lt;<span class="name">span</span> <span class="attr">:class</span>=<span class="string">&quot;$style.number&quot;</span>&gt;</span>&#123;&#123; number &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">module</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.number</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="使用SCSS"><a href="#使用SCSS" class="headerlink" title="使用SCSS"></a>使用SCSS</h2><p>首先需要安装SCSS，使用命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-dev sass-loader node-sass</span><br></pre></td></tr></table></figure><p>然后在style上使用<code>lang=&quot;scss&quot;</code>属性就可以了：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">lang</span>=<span class="string">&quot;scss&quot;</span> <span class="attr">scoped</span>&gt;</span></span><br><span class="line">  $color: red;</span><br><span class="line"></span><br><span class="line">  .number &#123;</span><br><span class="line">    font-weight: bold;</span><br><span class="line">    color: $color;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Vue知识点众多，这&lt;a href=&quot;https://kai666666.com/2021/06/19/vue2%E4%BD%A0%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87%EF%BC%88%E4%B8%8B</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://www.kai666666.com/categories/JavaScript/"/>
    
    
    <category term="Vue" scheme="https://www.kai666666.com/tags/Vue/"/>
    
  </entry>
  
  <entry>
    <title>《CSS选择器世界》读书笔记</title>
    <link href="https://www.kai666666.com/2021/05/23/%E3%80%8ACSS%E9%80%89%E6%8B%A9%E5%99%A8%E4%B8%96%E7%95%8C%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <id>https://www.kai666666.com/2021/05/23/%E3%80%8ACSS%E9%80%89%E6%8B%A9%E5%99%A8%E4%B8%96%E7%95%8C%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</id>
    <published>2021-05-23T14:00:27.000Z</published>
    <updated>2026-02-10T08:00:34.400Z</updated>
    
    <content type="html"><![CDATA[<p>你敢相信吗，CSS选择器都可以写一本书！张鑫旭大佬的这本书绝对值得一读！</p><p><img src="/2021/05/23/%E3%80%8ACSS%E9%80%89%E6%8B%A9%E5%99%A8%E4%B8%96%E7%95%8C%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/1.jpg" alt="《CSS选择器世界》"></p><hr><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>CSS选择器可分为4类：选择器（如body{}）、选择符（如相邻兄弟关系选择符+）、伪类（如:hover）和伪元素（如::before）。</p><p>CSS只有一个全局作用域，但是Shadow DOM中的样式不会影响外面的样式。</p><h3 id="CSS选择器的优先级"><a href="#CSS选择器的优先级" class="headerlink" title="CSS选择器的优先级"></a>CSS选择器的优先级</h3><table><thead><tr><th>等级</th><th>选择器</th><th>例子</th></tr></thead><tbody><tr><td>0级</td><td>通配选择器、选择符和逻辑组合伪类</td><td>通配选择器*、选择符（+、~、空格、&gt;）、伪类如:not等</td></tr><tr><td>1级</td><td>标签选择器</td><td>body {}</td></tr><tr><td>2级</td><td>类选择器、属性选择器和伪类</td><td>.foo{} <br> [foo]{} <br> :hover</td></tr><tr><td>3级</td><td>ID选择器</td><td>#foo{}</td></tr><tr><td>4级</td><td>style属性内联</td><td><code>&lt;span style=&quot;color:red;&quot;&gt;文字&lt;/span&gt;</code></td></tr><tr><td>5级</td><td>!important</td><td>.foo{color:red !important;}</td></tr></tbody></table><p>举例：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">body</span> <span class="selector-class">.foo</span> &#123;<span class="attribute">color</span>:red;&#125;</span></span><br><span class="line"><span class="language-css"><span class="selector-tag">html</span> <span class="selector-class">.foo</span> &#123;<span class="attribute">color</span>:blue;&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;foo&quot;</span>&gt;</span>这里是什么颜色<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里<code>html</code>与<code>body</code>等级相同，<code>.foo</code>也是一样的，所有优先级是相同的，<strong>如果优先级相同时，就符合后来居上的原则</strong>，所以是蓝色的。</p><p>部分资料上优先级是按照计数来算的，但是并不意味着10个类选择器和一个id选择器优先级相同，<strong>上一级比下一级有永远无法逾越的差距</strong>，但是IE浏览器256个上一级选择器要比下一级的优先级大（老式浏览器8字节存储所导致的），现代浏览器则没有此问题。</p><h3 id="CSS选择器的命名"><a href="#CSS选择器的命名" class="headerlink" title="CSS选择器的命名"></a>CSS选择器的命名</h3><p>选择器大小写敏感问题：</p><table><thead><tr><th>选择器类型</th><th>示例</th><th>是否大小写敏感</th></tr></thead><tbody><tr><td>标签选择器</td><td>div{}</td><td>不敏感</td></tr><tr><td>属性选择器-纯属性</td><td>[attr]</td><td>不敏感</td></tr><tr><td>属性选择器</td><td>[attr&#x3D;val]</td><td>属性不敏感、值敏感</td></tr><tr><td>类选择器</td><td>.container</td><td>敏感</td></tr><tr><td>ID选择器</td><td>#id</td><td>敏感</td></tr></tbody></table><p>选择器命名可以以数字开头，但是在CSS中需要转义，如下面是合法的：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">.<span class="number">1</span>-foo &#123;<span class="attribute">color</span>:red;&#125; <span class="comment">/* 不合法 */</span></span></span><br><span class="line"><span class="language-css">.\<span class="number">31</span> -foo&#123;<span class="attribute">color</span>:blue;&#125; <span class="comment">/* 合法，注意有个空格，\Unicode简写 加空格，如果不简写则不需要空格  */</span></span></span><br><span class="line"><span class="language-css">.\<span class="number">000031</span>-foo&#123;<span class="attribute">color</span>:blue;&#125; <span class="comment">/* 合法 */</span></span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;1-foo&quot;</span>&gt;</span>这里是什么颜色<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>CSS的命名可以是中文、中文标点符号甚至Emoji.</p><h3 id="精通CSS选择符"><a href="#精通CSS选择符" class="headerlink" title="精通CSS选择符"></a>精通CSS选择符</h3><p>四大选择符：后代选择符（空格），孩子选择器（&gt;），相邻兄弟选择符（+）、后面兄弟选择符（~）。</p><p>思考题：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.red</span> &#123;<span class="attribute">color</span>:red;&#125;</span></span><br><span class="line"><span class="language-css"><span class="selector-class">.blue</span> &#123;<span class="attribute">color</span>:blue;&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;red&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;blue&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>1. 颜色是？<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;blue&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;red&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>2. 颜色是？<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上面1和2是什么颜色呢？由于颜色都是继承自父标签的，所有应该取距离近的父标签的颜色，所以第一个是蓝色，第二个是红色。修改此题如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.red</span> <span class="selector-tag">p</span> &#123;<span class="attribute">color</span>:red;&#125;</span></span><br><span class="line"><span class="language-css"><span class="selector-class">.blue</span> <span class="selector-tag">p</span> &#123;<span class="attribute">color</span>:blue;&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;red&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;blue&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>1. 颜色是？<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;blue&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;red&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>2. 颜色是？<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>本题稍微变化了一下，这里1和2的颜色不是继承来的，而是匹配到了CSS样式，并且2个样式都可以匹配到，此时就得看优先级了，由于优先级相同，所以后来居上故都是蓝色的。</p><p>JS获取元素补充说明：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;myId&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;blue&quot;</span>&gt;</span>2<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;blue&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span>&gt;</span>2<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;#myId div div&#x27;</span>).<span class="property">length</span> <span class="comment">// 1</span></span></span><br><span class="line"><span class="language-javascript"><span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;#myId&#x27;</span>).<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;div div&#x27;</span>).<span class="property">length</span> <span class="comment">// 3 某个元素的querySelectorAll会把自己也算进去 不算进去则使用下面</span></span></span><br><span class="line"><span class="language-javascript"><span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;#myId&#x27;</span>).<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;:scope div div&#x27;</span>).<span class="property">length</span> <span class="comment">// 1</span></span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>相邻兄弟选择符（+），选择的是元素，会忽略中间的文本和注释，如下面3个p标签都是红色的：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">h4</span> + <span class="selector-tag">p</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">color</span>: red;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">h4</span>&gt;</span>标题<span class="tag">&lt;/<span class="name">h4</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>文字<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">h4</span>&gt;</span>标题<span class="tag">&lt;/<span class="name">h4</span>&gt;</span></span><br><span class="line">这里是文字</span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>文字<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">h4</span>&gt;</span>标题<span class="tag">&lt;/<span class="name">h4</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- HTML注释 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>文字<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="元素选择器"><a href="#元素选择器" class="headerlink" title="元素选择器"></a>元素选择器</h3><p>元素选择器包括标签选择器和通配符选择器。</p><p>多个选择器时，元素选择器必须写在前面，如这样是不合法的<code>[type=radio]input &#123;&#125; 或者 [type=radio]* &#123;&#125;</code></p><h3 id="属性选择器"><a href="#属性选择器" class="headerlink" title="属性选择器"></a>属性选择器</h3><p>7种属性选择器格式：</p><table><thead><tr><th>选择器格式</th><th>匹配规则</th><th>举例</th><th>说明</th></tr></thead><tbody><tr><td>[attr]</td><td>包含指定属性就可以了</td><td><code>[disabled] &#123;&#125;</code></td><td>boolean型属性用的比较多</td></tr><tr><td>[attr&#x3D;”val”]</td><td>属性和值都需要匹配</td><td><code>[type=&quot;radio&quot;] &#123;&#125;</code></td><td>值可以是单引号、双引号或者不写，结果都是一样的</td></tr><tr><td>[attr~&#x3D;”val”]</td><td>值包含则匹配（val必须是一个值，不能是值里面的一部分）</td><td><code>[rel~=&quot;noopener&quot;] &#123;&#125;</code></td><td>匹配：<code>&lt;a rel=&quot;noopener nofollow&quot;&gt;&lt;/a&gt;</code> <br> 不匹配：<code>&lt;a rel=&quot;123noopener123&quot;&gt;&lt;/a&gt;</code></td></tr><tr><td>[attr&#124;&#x3D;”val”]</td><td>值起始片段相同（片段是指用-连接的属性值）</td><td><code>[attr&amp;#124;=val] &#123;&#125;</code></td><td>匹配：<code>&lt;div attr=&quot;val&quot;&gt;&lt;/div&gt;</code>  <br>匹配：<code>&lt;div attr=&quot;val-us val2&quot;&gt;&lt;/div&gt;</code>  <br>不匹配：<code>&lt;div attr=&quot;val val2&quot;&gt;&lt;/div&gt;</code>  <br>不匹配：<code>&lt;div attr=&quot;value&quot;&gt;&lt;/div&gt;</code></td></tr><tr><td>[attr^&#x3D;”val”]</td><td>属性值以val开头的元素</td><td><code>[href^=&quot;https&quot;] &#123;&#125;</code></td><td>匹配href以https开头的元素</td></tr><tr><td>[attr$&#x3D;”val”]</td><td>属性值以val结尾的元素</td><td><code>[href$=&quot;.pdf&quot;] &#123;&#125;</code></td><td>匹配href以.pdf结尾的元素</td></tr><tr><td>[attr*&#x3D;”val”]</td><td>属性值包含val的元素</td><td><code>[attr*=val] &#123;&#125;</code></td><td>匹配：<code>&lt;div attr=&quot;value&quot;&gt;&lt;/div&gt;</code> \n 匹配：<code>&lt;div attr=&quot;aaa val bbb&quot;&gt;&lt;/div&gt;</code></td></tr></tbody></table><p>之前说过属性选择器，属性是忽略大小写的，属性的值是大小写敏感的，如果需要属性值也忽略大小写的话可以在属性中加一个i或者I，则表示大小写不敏感，如：<code>[attr*=&quot;val&quot; i]</code>。</p><h3 id="用户行为伪类"><a href="#用户行为伪类" class="headerlink" title="用户行为伪类"></a>用户行为伪类</h3><p>手型经过伪类<code>:hover</code>。</p><p>激活状态伪类<code>:active</code>。</p><p>焦点伪类<code>:focus</code>可以生效的元素：</p><ol><li>非disabled状态的表单元素；</li><li>包含href属性的a元素；</li><li><code>&lt;area&gt;</code>元素，不过可生效的CSS属性有限；</li><li>HTML5中的<code>&lt;summary&gt;</code>元素。</li></ol><p>其他元素不能生效，非要生效可以设置属性<code>contenteditable=&quot;true&quot;</code>或者添加属性<code>tabindex=&quot;数字&quot;</code>。</p><p>一个页面最多只有一个元素响应<code>:focus</code>。</p><p>整体焦点伪类<code>:focus-within</code>，在当前元素或者当前元素的任意子元素处于聚焦状态的时候都会匹配。（少有的子元素行为决定父元素的伪类选择器）</p><p>键盘焦点伪类<code>:focus-visible</code>，元素聚焦，同时浏览器认为聚焦轮廓应该显示。（目前只有Chrome支持）</p><h3 id="URL定位伪类"><a href="#URL定位伪类" class="headerlink" title="URL定位伪类"></a>URL定位伪类</h3><p><code>:link</code>伪类用来匹配页面上href链接没有访问过的<code>&lt;a&gt;</code>元素，已访问的元素则不匹配（用处不大，通常直接用a标签选择符就可以了）。</p><p>a标签相关的四个伪类的优先级：<code>:link</code> &gt; <code>:visited</code> &gt; <code>:hover</code> &gt; <code>:active</code>（LVHA lova-hate）。</p><p><code>:visited</code>标签访问过选择器，该选择器由于安全考虑有以下特性：</p><ol><li>支持的CSS有限，只支持color，background-color，border-color，border-bottom-color，border-left-color，border-right-color，border-top-color，column-rule-color，outline-color，不支持伪元素；</li><li>颜色不支持半透明；</li><li>只能重置，不能设置原来没设置的样式（如要设置<code>background-color</code>则a标签的样式需要设置过<code>background-color</code>）；</li><li>JS的<code>getComputedStyle()</code>方法无法获取到色值。</li></ol><p><code>:any-link</code>不兼容IE11，其他浏览器兼容性良好，匹配规则如下：</p><ol><li>匹配所有设置了href属性的链接元素，包括<code>&lt;a&gt;</code>，<code>&lt;link&gt;</code>和<code>&lt;area&gt;</code>；</li><li>匹配所有匹配<code>:link</code>伪类或者<code>:visited</code>伪类的元素。</li></ol><p><code>:target</code>：当浏览器是有锚点与当前元素相同时则匹配，这里的锚点也就是路由上hash指向的id所对应的元素。该伪类有一个特性，就是当元素不显示的时候也能匹配，但是不显示的时候设置当前元素的样式也不会有什么效果，毕竟不显示嘛，但是可以操作他后面的兄弟节点（可以利用该伪类选择器实现“显示全部”的功能）。</p><p><code>:target-within</code>：匹配<code>:target</code>伪类匹配的元素，或者后代存在匹配<code>:target</code>伪类的元素的元素。目前还没有浏览器支持！😢</p><h3 id="输入伪类"><a href="#输入伪类" class="headerlink" title="输入伪类"></a>输入伪类</h3><p><code>:enabled</code>元素可用，<code>:disabled</code>元素不可用，他们是对立的，<code>readonly</code>的表单是<code>:enabled</code>的，另外<code>:enabled</code>可以用在a标签上，a标签没有<code>:disabled</code>状态，哪怕给a标签设置了disabled属性。</p><p><code>:read-only</code>表单只读，<code>:read-write</code>表单可读可写（默认就是，比较鸡肋）。表单disabled的时候匹配的是<code>:read-write</code>，虽然此刻也不能写😂。</p><p><code>:placeholder-show</code>：占位符显示时匹配，由于占位符是在输入内容为空的时候出现，所以可以使用<code>:placeholder-show</code>来判断表单是否为空。</p><p><code>:default</code>：默认状态的表单选中元素，如select标签下的option可以给一个默认值，这个默认值就可以用<code>:default</code>匹配。这里需要注意的时候如果option标签没有给默认值的时候<code>:default</code>并不会匹配，但是浏览器会默认选中第一个元素。</p><p><code>:checked</code>：checkbox选中时的伪类。</p><p>checkbox的<code>:checked</code>伪类比<code>[checked]</code>属性选择器优势在于JS控制选中的时候（<code>checkbox.chencked = true</code>这种情况），由于没有改变checked属性，所以<code>[checked]</code>不准确，而<code>:checked</code>则不会有问题。同样的<code>:disabled</code>和<code>[disabled]</code>也一样，另外<code>:disabled</code>是表单元素实际是否被禁用，比如表单外面包裹着一层<code>&lt;fieldset disabled&gt;</code>，里面的表单元素则是禁用状态，此时<code>:disabled</code>能匹配到，但是由于里面的表单元素没有加disabled属性所以<code>[disabled]</code>匹配不到。</p><p><code>:indeterminate</code>：不确定值伪类，实际上就是当JS设置<code>checkbox.indeterminate = true</code>的时候则会匹配，也就是浏览器常见的复选框一个横线的时候的那种状态。该伪类也可以用于单选框，当单选框的组没有一个选中的时候则单选框的每一项都匹配。</p><p><code>:valid</code>：输入验证有效的时候匹配。<code>:invalid</code>：输入严重无效的时候匹配。就是我们在<code>&lt;input&gt;</code>标签中设置required或者pattern等属性的时候，会判断是否有效，匹配对应的伪类。由于首次进来的时候往往没有输入内容，这时如果有required属性，此时<code>:invalid</code>会匹配，这样就有点不太友好了，更好的伪元素就是<code>:user-invalid</code>，可以避免首次判断，但是目前兼容性非常不好。</p><p>范围验证伪类<code>:in-range</code>和<code>:out-of-range</code>使用于type&#x3D;number和type&#x3D;range的input标签。</p><p><code>:required</code>表示表单必填，<code>:optional</code>表示表单可选（非必填）。</p><h3 id="树结构伪类"><a href="#树结构伪类" class="headerlink" title="树结构伪类"></a>树结构伪类</h3><p><code>:root</code>匹配根元素，IE9以上才支持，在XHTML中根元素就是html，另外也可以匹配的SVG的根元素，但不能匹配Shadow DOM的的根元素，Shadow DOM的的根元素是<code>:host</code>。<code>:root</code>最常用的是声明CSS变量。</p><p><code>:empty</code>用来匹配空元素，这里的空元素包括前后闭合的空元素，甚至<code>&lt;input&gt;</code>这种非闭合的标签。如果标签内有空格、换行、注释则不能匹配<code>:empty</code>。具有<code>::before</code>或者<code>::after</code>的空元素可以匹配<code>:empty</code>。</p><p><code>:first-child</code>第一个子元素；<code>:last-child</code>最后一个子元素；<code>:only-child</code>唯一的子元素。</p><p><code>:nth-child()</code>与<code>:nth-last-child()</code>表示第几个子元素和倒数第几个子元素，从1开始，也可以是<code>odd</code>（基数）和<code>even</code>（偶数），也可以An+B这种形式，如<code>:nth-child(3n + 4)</code>匹配第4、7、10…</p><p><code>:first-of-type</code>当前类型元素的第一个；<code>:last-of-type</code>当前类型元素最后一个；<code>:only-of-type</code>当前类型只有一个。<code>:only-child</code>匹配到的元素一定是<code>:only-of-type</code>，但是反之不成立。<code>:nth-of-type()</code>和<code>:nth-last-of-type()</code>与上面的类似，不再重复。</p><h3 id="逻辑组合伪类"><a href="#逻辑组合伪类" class="headerlink" title="逻辑组合伪类"></a>逻辑组合伪类</h3><p>否定伪类<code>:not()</code>：如果当前元素与括号里面的选择器不匹配，则<code>:not()</code>后会匹配。需要注意的是<code>:not()</code>括号里面的只能出现一个选择器，如<code>:not(p)</code>，不可以<code>:not(p.class1)</code>或者<code>:not(p, div)</code>，可以写多个<code>:not()</code>来支持这种情况如：<code>:not(p):not(class1)</code>。另外<code>:not()</code>本身的优先级权重是0，整体权重则由括号内的权重来决定。</p><p>与<code>:not()</code>相反的伪类是<code>:is()</code>，<code>:is()</code>是老版本浏览器的<code>:matches()</code>和<code>:any()</code>演化来的。<code>:is()</code>的权重与<code>:not()</code>一样由括号内的权重来决定。还有一个伪类<code>:where()</code>匹配规则与<code>:is()</code>相同，但是整体权重是0，不管括号内的权重是多少。</p><h3 id="其他伪类选择器"><a href="#其他伪类选择器" class="headerlink" title="其他伪类选择器"></a>其他伪类选择器</h3><p><code>:scope</code>作用域选择伪类，由于CSS只有一个全局作用域，所以<code>:scope</code>与<code>:root</code>一样，都相当于<code>html</code>。不过JS倒是支持的，详见上面<code>精通CSS选择符</code>最后部分。</p><p><code>:fullscreen</code>用来匹配全屏状态下的DOM元素（调用dom.requestFullScreen()方法的dom元素），<code>:backdrop</code>伪元素用来匹配浏览器默认的黑色全屏背景元素的。</p><p><code>:dir(ltr|rtl)</code>匹配从左到右，还是从右到左。</p><p><code>:lang()</code>语言类伪类，如<code>:lang(zh)</code>。</p><p>video或者audio播放状态伪类<code>:playing</code>和<code>:paused</code>。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;你敢相信吗，CSS选择器都可以写一本书！张鑫旭大佬的这本书绝对值得一读！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2021/05/23/%E3%80%8ACSS%E9%80%89%E6%8B%A9%E5%99%A8%E4%B8%96%E7%95%8C%E3%80%8B%E8%A</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://www.kai666666.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="CSS" scheme="https://www.kai666666.com/tags/CSS/"/>
    
  </entry>
  
</feed>
