<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>MutationObserver on Tarragon</title><link>https://tarrragon.github.io/blog/tags/mutationobserver/</link><description>Recent content in MutationObserver on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sun, 26 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/mutationobserver/index.xml" rel="self" type="application/rss+xml"/><item><title>Reactive Performance — Reactive 效能盤點與優化</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/</guid><description>&lt;p>前端 reactive 效能的盤點與優化：MutationObserver 三維度（root / options / debounce）、polling → observer、iteration / regex / reflow / lazy load 四個成本面。&lt;/p>
&lt;p>適用：使用者反映卡頓、CPU 100%、scroll lag、resize jank、首次互動延遲。
不適用：純後端效能、純伺服器渲染（SSR 的成本另一套）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四個效能風險面向、observer 設計準則、量測方法。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>使用者打字時搜尋頁卡頓&lt;/td>
 &lt;td>量 input listener / observer 觸發頻率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Scroll 時掉幀&lt;/td>
 &lt;td>量 scroll listener 觸發頻率 + reflow 成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resize 視窗時 layout 跳動&lt;/td>
 &lt;td>量 ResizeObserver 觸發 + 重新計算成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU 100%、即使頁面靜止&lt;/td>
 &lt;td>找 setInterval / setTimeout polling、換 observer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結果規模大（&amp;gt; 500 筆）時慢&lt;/td>
 &lt;td>量 iteration cost、看是否每筆都跑 regex&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>首次互動延遲（搜尋頁 200ms+ 才能輸入）&lt;/td>
 &lt;td>量 critical path、看 lazy chunk 是否要 preload&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>observer.observe(document.body, { subtree: true })&lt;/code>&lt;/td>
 &lt;td>停 — 範圍過寬、補上限制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-reactive-效能要主動盤點">為什麼 reactive 效能要主動盤點&lt;/h2>
&lt;p>Reactive 系統的成本不是線性 — 一個觸發頻率失控的 listener 會放大整個系統的負擔：&lt;/p>
&lt;ul>
&lt;li>一個 observer 觸發 → callback 執行 → DOM 變動 → 再觸發 observer → 無限迴圈&lt;/li>
&lt;li>一個 input listener 沒 debounce → 每個鍵盤事件跑一次重 query → CPU 飆高&lt;/li>
&lt;li>一個 setInterval polling 50ms → 永遠不停、即使頁面背景&lt;/li>
&lt;/ul>
&lt;p>主動盤點 = 寫之前先估觸發頻率、寫之後用 &lt;code>console.count&lt;/code> 驗證。事後 debug 比事前設計貴 10 倍。&lt;/p>
&lt;hr>
&lt;h2 id="風險面向-1listener-觸發頻率">風險面向 1：Listener 觸發頻率&lt;/h2>
&lt;h3 id="mutationobserver-三維度">MutationObserver 三維度&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>預設&lt;/th>
 &lt;th>過寬訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Root&lt;/td>
 &lt;td>最窄（具體 element）&lt;/td>
 &lt;td>&lt;code>document.body&lt;/code> / &lt;code>document.documentElement&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Options&lt;/td>
 &lt;td>&lt;code>{ childList: true }&lt;/code>&lt;/td>
 &lt;td>&lt;code>{ subtree: true, attributes: true, characterData: true }&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Debounce&lt;/td>
 &lt;td>0ms 或微 microtask&lt;/td>
 &lt;td>沒寫 debounce、callback 執行 &amp;gt; 5ms&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="過寬範例">過寬範例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// 監聽整個 page 任何變動
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cb&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">childList&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">subtree&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nx">attributes&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">characterData&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1">// 一次 react state 變動 → 100+ 個 callback
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="對例">對例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.pagefind-ui__results-area&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">timer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">clearTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">timer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nx">timer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">callback&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// debounce 100ms
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">root&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">childList&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">// 只監聽 results 直接子節點變動、debounce 100ms
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="量觸發頻率">量觸發頻率&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">count&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;mutation&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">count&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">}).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(...);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">// 預期：使用者打字 1 秒、觸發 10 次以下
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1">// 觀察：100+ 次 → 範圍過寬、加 debounce 或縮 root
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或用 &lt;code>console.count('decorate')&lt;/code> 計數、看每秒觸發幾次。&lt;/p></description><content:encoded><![CDATA[<p>前端 reactive 效能的盤點與優化：MutationObserver 三維度（root / options / debounce）、polling → observer、iteration / regex / reflow / lazy load 四個成本面。</p>
<p>適用：使用者反映卡頓、CPU 100%、scroll lag、resize jank、首次互動延遲。
不適用：純後端效能、純伺服器渲染（SSR 的成本另一套）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四個效能風險面向、observer 設計準則、量測方法。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者打字時搜尋頁卡頓</td>
          <td>量 input listener / observer 觸發頻率</td>
      </tr>
      <tr>
          <td>Scroll 時掉幀</td>
          <td>量 scroll listener 觸發頻率 + reflow 成本</td>
      </tr>
      <tr>
          <td>Resize 視窗時 layout 跳動</td>
          <td>量 ResizeObserver 觸發 + 重新計算成本</td>
      </tr>
      <tr>
          <td>CPU 100%、即使頁面靜止</td>
          <td>找 setInterval / setTimeout polling、換 observer</td>
      </tr>
      <tr>
          <td>結果規模大（&gt; 500 筆）時慢</td>
          <td>量 iteration cost、看是否每筆都跑 regex</td>
      </tr>
      <tr>
          <td>首次互動延遲（搜尋頁 200ms+ 才能輸入）</td>
          <td>量 critical path、看 lazy chunk 是否要 preload</td>
      </tr>
      <tr>
          <td>即將寫 <code>observer.observe(document.body, { subtree: true })</code></td>
          <td>停 — 範圍過寬、補上限制</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-reactive-效能要主動盤點">為什麼 reactive 效能要主動盤點</h2>
<p>Reactive 系統的成本不是線性 — 一個觸發頻率失控的 listener 會放大整個系統的負擔：</p>
<ul>
<li>一個 observer 觸發 → callback 執行 → DOM 變動 → 再觸發 observer → 無限迴圈</li>
<li>一個 input listener 沒 debounce → 每個鍵盤事件跑一次重 query → CPU 飆高</li>
<li>一個 setInterval polling 50ms → 永遠不停、即使頁面背景</li>
</ul>
<p>主動盤點 = 寫之前先估觸發頻率、寫之後用 <code>console.count</code> 驗證。事後 debug 比事前設計貴 10 倍。</p>
<hr>
<h2 id="風險面向-1listener-觸發頻率">風險面向 1：Listener 觸發頻率</h2>
<h3 id="mutationobserver-三維度">MutationObserver 三維度</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>預設</th>
          <th>過寬訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Root</td>
          <td>最窄（具體 element）</td>
          <td><code>document.body</code> / <code>document.documentElement</code></td>
      </tr>
      <tr>
          <td>Options</td>
          <td><code>{ childList: true }</code></td>
          <td><code>{ subtree: true, attributes: true, characterData: true }</code></td>
      </tr>
      <tr>
          <td>Debounce</td>
          <td>0ms 或微 microtask</td>
          <td>沒寫 debounce、callback 執行 &gt; 5ms</td>
      </tr>
  </tbody>
</table>
<h3 id="過寬範例">過寬範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 監聽整個 page 任何變動
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">cb</span><span class="p">).</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">attributes</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">characterData</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 一次 react state 變動 → 100+ 個 callback
</span></span></span></code></pre></div><h3 id="對例">對例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">root</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__results-area&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">let</span> <span class="nx">timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">callback</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>  <span class="c1">// debounce 100ms
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">}).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">root</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 只監聽 results 直接子節點變動、debounce 100ms
</span></span></span></code></pre></div><h3 id="量觸發頻率">量觸發頻率</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">count</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;mutation&#39;</span><span class="p">,</span> <span class="nx">count</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}).</span><span class="nx">observe</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 預期：使用者打字 1 秒、觸發 10 次以下
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 觀察：100+ 次 → 範圍過寬、加 debounce 或縮 root
</span></span></span></code></pre></div><p>或用 <code>console.count('decorate')</code> 計數、看每秒觸發幾次。</p>
<hr>
<h2 id="風險面向-2polling-換-observer">風險面向 2：Polling 換 Observer</h2>
<h3 id="反例setinterval-polling">反例：setInterval polling</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">decorate</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">},</span> <span class="mi">50</span><span class="p">);</span></span></span></code></pre></div><p>問題：CPU 50% busy waiting、即使元素永遠不出現、interval 永遠跑。</p>
<h3 id="對例mutationobserver--fast-path">對例：MutationObserver + fast-path</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">function</span> <span class="nx">waitForElement</span><span class="p">(</span><span class="nx">selector</span><span class="p">,</span> <span class="nx">root</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kr">const</span> <span class="nx">existing</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">existing</span><span class="p">)</span> <span class="k">return</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">existing</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="kr">const</span> <span class="nx">obs</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nx">obs</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nx">resolve</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nx">obs</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">root</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Fast-path 先檢查（如果已經在 DOM 立即返回）、否則 observer 等元素出現。0 latency、0 idle CPU、元素出現立刻觸發。</p>
<hr>
<h2 id="風險面向-3iteration--regex-成本">風險面向 3：Iteration / Regex 成本</h2>
<h3 id="反例每筆結果跑重-regex">反例：每筆結果跑重 regex</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">pagefind</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">const</span> <span class="nx">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="o">/</span><span class="nx">complex</span><span class="o">|</span><span class="nx">regex</span><span class="o">|</span><span class="nx">here</span><span class="o">/</span><span class="nx">i</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 500 筆 × regex test = 500 次 regex 編譯與執行
</span></span></span></code></pre></div><h3 id="對例regex-compile-一次用-cached-version">對例：regex compile 一次、用 cached version</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">re</span> <span class="o">=</span> <span class="sr">/complex|regex|here/i</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">const</span> <span class="nx">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">re</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// regex 只編譯一次、test 每次便宜
</span></span></span></code></pre></div><h3 id="量-iteration-成本">量 iteration 成本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">time</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">const</span> <span class="nx">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">timeEnd</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// 觀察：&gt; 16ms → 影響 60fps、要優化
</span></span></span></code></pre></div><h3 id="大資料量的常用優化">大資料量的常用優化</h3>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>優化</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每筆都跑 regex</td>
          <td>regex 編譯一次、test 重用</td>
      </tr>
      <tr>
          <td>每筆 query DOM</td>
          <td>DOM query 一次、緩存結果</td>
      </tr>
      <tr>
          <td>排序 N²</td>
          <td>用 <code>Array.sort()</code> (N log N)</td>
      </tr>
      <tr>
          <td>全量過濾後分頁</td>
          <td>分頁邊界提前 break、不跑完全部</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="風險面向-4layout-reflow-成本">風險面向 4：Layout Reflow 成本</h2>
<p>Reflow（重新計算 layout） &gt; Repaint（重繪） &gt; Composite（合成）— 三者成本遞減。</p>
<h3 id="reflow-觸發訊號">Reflow 觸發訊號</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改 width / height / top / margin</td>
          <td>Reflow（layout 變動）</td>
      </tr>
      <tr>
          <td>改 color / background</td>
          <td>Repaint（不影響 layout）</td>
      </tr>
      <tr>
          <td>改 transform / opacity</td>
          <td>Composite（GPU、最便宜）</td>
      </tr>
      <tr>
          <td>讀 <code>getBoundingClientRect()</code></td>
          <td>強制 sync reflow（如果 pending 變動）</td>
      </tr>
  </tbody>
</table>
<h3 id="反例read-write-read-write-觸發-layout-thrashing">反例：read-write-read-write 觸發 layout thrashing</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">elements</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">w</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">;</span>       <span class="c1">// read
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">w</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>  <span class="c1">// write
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">h</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetHeight</span><span class="p">;</span>      <span class="c1">// read（強制 reflow）
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">h</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span> <span class="c1">// write
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 每次 read 觸發一次 reflow、N 個元素 = N 次 reflow
</span></span></span></code></pre></div><h3 id="對例批量-read批量-write">對例：批量 read、批量 write</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">sizes</span> <span class="o">=</span> <span class="nx">elements</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">el</span><span class="p">,</span> <span class="nx">w</span><span class="o">:</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">,</span> <span class="nx">h</span><span class="o">:</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetHeight</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">sizes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(({</span> <span class="nx">el</span><span class="p">,</span> <span class="nx">w</span><span class="p">,</span> <span class="nx">h</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">w</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">h</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 1 次 reflow、性能提升 N 倍
</span></span></span></code></pre></div><h3 id="量-reflow-成本">量 reflow 成本</h3>
<p>Chrome DevTools Performance panel → 找 &ldquo;Layout&rdquo; 紫色塊。&gt; 16ms 要優化。</p>
<hr>
<h2 id="風險面向-5資源載入時序">風險面向 5：資源載入時序</h2>
<h3 id="critical-path-vs-lazy-chunk">Critical path vs lazy chunk</h3>
<table>
  <thead>
      <tr>
          <th>資源</th>
          <th>該不該 lazy</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>首屏需要的 CSS / JS</td>
          <td>否（critical path、preload）</td>
      </tr>
      <tr>
          <td>搜尋頁的 search index</td>
          <td>是（使用者進搜尋頁前不需要）</td>
      </tr>
      <tr>
          <td>Footer 圖片</td>
          <td>是（lazy load on scroll）</td>
      </tr>
      <tr>
          <td>跟首屏互動相關的 JS</td>
          <td>否（input listener 要立刻 ready）</td>
      </tr>
  </tbody>
</table>
<h3 id="範例搜尋頁的-lazy-chunk">範例：搜尋頁的 lazy chunk</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- 搜尋頁進來時、preload 第一個 chunk --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/_pagefind/pagefind-entry.json&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;fetch&#34;</span> <span class="na">crossorigin</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/_pagefind/pagefind.js&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;script&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;module&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;/_pagefind/pagefind.js&#39;</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">p</span> <span class="p">=&gt;</span> <span class="nx">p</span><span class="p">.</span><span class="nx">init</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><p>不 preload 的代價：使用者進搜尋頁 → 點 input → 等 200-500ms 才能搜尋。</p>
<h3 id="量-critical-path">量 critical path</h3>
<p>Chrome DevTools Network panel → 看每個資源的 timing。Slow 3G throttle 模擬真實使用者環境。</p>
<hr>
<h2 id="盤點-reactive-listener-的協議">盤點 reactive listener 的協議</h2>
<p>對複雜頁面（搜尋頁、dashboard）做一次性盤點：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 1. 列出所有 observer / listener
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nx">mutationObservers</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">observers</span><span class="p">,</span>  <span class="c1">// 自家紀錄
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="nx">resizeObservers</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">resizeObservers</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="nx">inputListeners</span><span class="o">:</span> <span class="s1">&#39;...&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 2. 加 console.count 在每個 callback
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">decorateCount</span> <span class="o">=</span> <span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">return</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">count</span><span class="p">(</span><span class="sb">`decorate </span><span class="si">${</span><span class="o">++</span><span class="nx">c</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span> <span class="p">};</span> <span class="p">})();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// 3. 操作頁面 1 分鐘、看 console
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">// 4. 任何 callback 執行 &gt; 100 次/分鐘 → 評估是否需要 debounce / 縮範圍
</span></span></span></code></pre></div><p>定期盤點（每加新 observer 後）= 主動發現觸發頻率失控、不等使用者抱怨。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1搜尋頁打字卡頓">範例 1：搜尋頁打字卡頓</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// 每個鍵盤事件都重 query 整個 results、重排版
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">expensiveQuery</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">renderResults</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">let</span> <span class="nx">timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">expensiveQuery</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">renderResults</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">},</span> <span class="mi">200</span><span class="p">);</span>  <span class="c1">// debounce 200ms
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h3 id="範例-2等元素出現">範例 2：等元素出現</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">decorate</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">},</span> <span class="mi">100</span><span class="p">);</span></span></span></code></pre></div><p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">((</span><span class="nx">mutations</span><span class="p">,</span> <span class="nx">obs</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">obs</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">decorate</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}).</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 注意：subtree 只在「等元素出現」場景可接受、決完後 disconnect
</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫 reactive code 或 perf debug 時：</p>
<ul>
<li><input disabled="" type="checkbox"> MutationObserver root 是不是最窄能達成目標的 element？</li>
<li><input disabled="" type="checkbox"> options 是不是只開必要的（<code>childList</code> 預設、<code>subtree</code> 要有理由、<code>attributes</code> 不是預設）？</li>
<li><input disabled="" type="checkbox"> 重 callback 有沒有 debounce / throttle？</li>
<li><input disabled="" type="checkbox"> setInterval / setTimeout polling 能不能換成 MutationObserver？</li>
<li><input disabled="" type="checkbox"> iteration / regex 在大資料量下測過嗎？&gt; 16ms 要優化</li>
<li><input disabled="" type="checkbox"> 改 layout 屬性有沒有 batch read-write、避免 layout thrashing？</li>
<li><input disabled="" type="checkbox"> Lazy chunk 是 critical path 還是真的 lazy？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/mutation-observer-scope/" data-link-title="MutationObserver 範圍與觸發頻率：監聽最少必要的變動" data-link-desc="MutationObserver 是非同步監聽工具、跟同步 selector 是不同議題。範圍寬會頻繁觸發、option 勾多會在不關心的變動上跑邏輯、apply 自己改 DOM 會觸發無限循環。本文是 observer 設計的完整指引。">mutation-observer-scope</a> — MutationObserver 範圍與觸發頻率</li>
<li><a href="/blog/report/mutationobserver-over-polling/" data-link-title="setTimeout 輪詢換 MutationObserver" data-link-desc="等元素出現的場景、用 MutationObserver 監聽 DOM 變化、看到目標就 disconnect — 沒延遲、CPU 不被輪詢吃。本文展開兩種等待機制的差異。">mutationobserver-over-polling</a> — setTimeout 輪詢換 MutationObserver</li>
<li><a href="/blog/report/reactive-listener-frequency-management/" data-link-title="Reactive 監聽器的效能 audit：跨 listener 類型盤點觸發頻率" data-link-desc="MutationObserver / ResizeObserver / event listener 各自的觸發頻率怎麼盤點。本文是效能 audit 視角 — 找問題用、跟 #29 (observer 設計指引) 互補不重複。">reactive-listener-frequency-management</a> — Reactive 監聽器的效能 audit</li>
<li><a href="/blog/report/runtime-iteration-and-regex-cost/" data-link-title="Runtime 計算成本：每筆迭代與正則" data-link-desc="Scope filter 對每筆結果跑 regex — 結果數量大時成為 frame budget 的主要消耗。本文盤點此類「每筆迭代 &#43; per-item 計算」的風險點與評估方法。">runtime-iteration-and-regex-cost</a> — Runtime 計算成本：每筆迭代與正則</li>
<li><a href="/blog/report/layout-reflow-measurement/" data-link-title="Layout reflow / repaint 的可量化評估" data-link-desc="Filter slot 切換、CSS 變數寫入、絕對定位重算 — 哪些操作觸發 reflow 而非僅 repaint、用什麼工具量、評估值落在哪個區間值得優化。">layout-reflow-measurement</a> — Layout reflow / repaint 的可量化評估</li>
<li><a href="/blog/report/lazy-loading-and-critical-path/" data-link-title="資源載入時序：lazy chunk 與 critical path" data-link-desc="Pagefind 的 index 採 chunked lazy load — 首次互動延遲與 critical path 之間的取捨怎麼盤點。預載 entry chunk 的時機與不預載的代價。">lazy-loading-and-critical-path</a> — 資源載入時序：lazy chunk 與 critical path</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item></channel></rss>