<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Yeefun | Feed]]></title><description><![CDATA[👨🏻‍💻👁📚...]]></description><link>https://yeefun.github.io</link><generator>GatsbyJS</generator><lastBuildDate>Fri, 23 Apr 2021 09:15:52 GMT</lastBuildDate><item><title><![CDATA[我知道你懂 Event Loop，但你了解到多深？]]></title><description><![CDATA[從規範文件深入了解瀏覽器的事件迴圈及其相關議題]]></description><link>https://yeefun.github.io/event-loop-in-depth/</link><guid isPermaLink="false">https://yeefun.github.io/event-loop-in-depth/</guid><pubDate>Fri, 02 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;事件迴圈（Event Loop）是一個我以為我懂了，但直到最近才發現自己什麼都不懂的概念。&lt;/p&gt;
&lt;p&gt;會發現這件事，是因為我在研究 React Fiber 的過程中，獲知了一個能提升網頁性能的 API &lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt;，它的回呼（callback）會在瀏覽器空閒（idle）時執行，其中一個時機點是幀尾。&lt;/p&gt;
&lt;p&gt;問題來了，什麼是「幀尾」？要回答這個問題，首先當然要知道什麼是「幀」（frame）？原本我以為，幀就&lt;strong&gt;等同於&lt;/strong&gt;網頁畫面更新——每一幀，畫面就更新一次。但若是這樣，哪來的「尾」可以給 &lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt; 執行？或許，一幀除了更新，還做了許多事？&lt;/p&gt;
&lt;p&gt;就這樣，我從想瞭解一幀究竟做了哪些事，不小心掉進事件迴圈的漩渦，糾纏了一個多禮拜才爬出來，又花了好幾天才煉成這篇文章。&lt;/p&gt;
&lt;p&gt;本文將從官方規範下手，看完它，你便能回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件迴圈的運作流程為何？&lt;/li&gt;
&lt;li&gt;「幀」是什麼？&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt; 分別發生在事件迴圈的哪個階段？&lt;/li&gt;
&lt;li&gt;宏任務（macrotask）和微任務（microtask）是什麼？它們與事件迴圈的關係是什麼？&lt;/li&gt;
&lt;li&gt;每輪事件迴圈都會更新畫面嗎？&lt;/li&gt;
&lt;li&gt;JavaScript 是單執行緒（single threaded），但卻能發出 HTTP 請求而不阻塞（non-blocking），這是怎麼辦到的？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;js-單執行緒卻不阻塞的祕密&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#js-%E5%96%AE%E5%9F%B7%E8%A1%8C%E7%B7%92%E5%8D%BB%E4%B8%8D%E9%98%BB%E5%A1%9E%E7%9A%84%E7%A5%95%E5%AF%86&quot; aria-label=&quot;js 單執行緒卻不阻塞的祕密 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;JS 單執行緒卻不阻塞的祕密&lt;/h2&gt;
&lt;p&gt;在開始之前，先來看一張圖：&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c9fda2d01746574d4d0f2c9bd9204bf5/7a3d6/event-loop-in-browser.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 104.57627118644068%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAACXBIWXMAAAsSAAALEgHS3X78AAADrElEQVQ4y3VSW28bRRTOjwGEQEGoPPIMglcQoAJSCxUVUq+gqpFqaCoMtA3gECdO80ChuTg0tDR2HN8DjRMnEWnSeB3H1/ia2OusL7vr3fXuzszOsms7TkDq0dnR7Oh8853zzdejdgNjfRHreMOibrZzWN0wH+UTs7o+iPmDdrX29RwDK9qCcovq7Pvq/CnV8bHiPAvcF6DnAnacVh2nVPtJxf8lhuIzwfjJkOI4g7yXsPO06vwUaWDvF8h7UfFcRHOfoO0pfFjZBSsdJJLFrXE+aOJWzPzKELti4leH+FUzp+cwFzRBKt4d8IgZY9zgmhwnMLwMkH4iI6UBIdYvPsr/RY+iIAhYWayR+4nifjaXiTUYUgEM16ShAg+VVBQFok4ipds2hFIjPxpeNpTiRlUI1HbvxFa/ym1eE1miM9GzowcCSdgz8Yk+du28WHNLxUEx/7Vc0MBPuyoKQIhWIkQ+RGS3wqVQWSh3wAiKYnkMZozq2nnA+NDBz2r1hkoZREYHK1jvfDoy8dpo74nrvSf6e18deOlz95l251rbolwalYo3Zeo6qHvF8j2ZtABysMmEW+Pq6k0S9wyevp8Ctwcefzfw+HvDQh8HuJbaSIwmRslMfzpxGdLeetFXLbhrhVm6vtOQZRnKWtHMtvWW/9s7gZGRxSFLwGz8q78DxkgqpEeaJaNauSHXPSzppnJ/0nszAhvXSJuwCbFiJSbGgpbptcnJ4Lh1feLH5VsNmdXBAEi4Ory5c1sqfqMx10qBajFQKbg4JtlqW59tfOu3XzbGHoZn7m9NPSB+v7lkZCWmozYgf2DzBkxelWsO7sBVKzxsFKd5ZqcLjpDhP0L356Nz81G7PfLIHXOilhaaYKBZXUD0HKjaBIZoMjtcPSTzBAJVrNtftyJWcEMUANQCaaniw3duVehWaJ8ASVYBDG+HImGibVv9wRQAkNj1Rpu26239BhGiLNMk8uV/suRd198WuztFS3FGTrKQqHKRuqBtorScbchKl7nlBP0vVKo/d8n0isl5bmnfsJi6tpT5wJO57Iv1+aNnfanP/Omr/uiVhcRH3gwtoTbhMTDFvzweetEavRuh1vPU2h59JbjniZfWcwe/hstjIXJlt7icq5xbzFNNeBys9/CU4p+3xl6Yir5lS5705t5zp19/EH/Xlf7Ql3vbnnzTltQ277jSb8wm/sPcHoEWgXu34k1XHClqNkHZkpRzt2JP6Pu5JOVIVWwJ6lGCWshW4eHQ/wJu5D8QLdyZ5AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;事件迴圈在瀏覽器的角色。圖片原始來源已不可考&quot;
        title=&quot;事件迴圈在瀏覽器的角色。圖片原始來源已不可考&quot;
        src=&quot;/static/c9fda2d01746574d4d0f2c9bd9204bf5/fcda8/event-loop-in-browser.png&quot;
        srcset=&quot;/static/c9fda2d01746574d4d0f2c9bd9204bf5/fcda8/event-loop-in-browser.png 590w,
/static/c9fda2d01746574d4d0f2c9bd9204bf5/7a3d6/event-loop-in-browser.png 990w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;事件迴圈在瀏覽器的角色。圖片原始來源已不可考&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;不用看太久，有個概念就好；等你讀完這篇文章，就能看懂這張圖了。&lt;/p&gt;
&lt;p&gt;首先要闡明，事件迴圈是與 JS 的運行（runtime）環境相關的機制，它與 JS（引擎）本身無關。常見的運行環境有瀏覽器、Node.js，每種運行環境可能都有自己實現事件迴圈的方式，而本文只探討瀏覽器的事件迴圈（Node.js 的事件迴圈也是一個重要的主題，等哪天我搞懂了再來寫）。&lt;/p&gt;
&lt;p&gt;為什麼需要事件迴圈？這與 JS 單執行緒的特性有關。單執行緒意味著 JS 一次只能執行一段程式碼，當 JS 在調用（invoke）一個函式時，沒有任何其它程式碼可以&lt;strong&gt;同時&lt;/strong&gt;運行，除非這個函式結束或被中斷（suspended）（後者可用 &lt;a href=&quot;https://javascript.info/generators&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;generator&lt;/a&gt; 達成）。&lt;/p&gt;
&lt;p&gt;深入點說，JS 內部有一個呼叫堆疊（call stack），又稱為執行上下文堆疊（execution context stack），這個堆疊是用來追蹤程式所呼叫的函式。每當程式調用一個函式，這個函式所產生的執行上下文便會被壓入（push）堆疊；當這個函式執行完，便會被彈出（pop）。函式的執行順序遵循「後進先出」（LIFO, Last In First Out）的模式。&lt;/p&gt;
&lt;p&gt;單執行緒雖簡單易懂，但卻有一個明顯的問題：假如一個函式執行過久，便會卡住之後函式的執行。如果這些函式恰好是做與 UI rendering 相關的事，那畫面便會延遲更新，這對使用者體驗來說是致命的。&lt;/p&gt;
&lt;p&gt;現在讓我們來想一下，JS 有什麼操作是相當耗時的？無論你的答案是什麼，我都已經想好了（欸），那就是請求資料。在 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;AJAX&lt;/a&gt; 大行其道的年代，大家都在用 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;XMLHttpRequest&lt;/code&gt;（XHR）&lt;/a&gt; 請求來請求去。如果使用者網路速度慢，可能一次請求就要花費好幾秒，但為什麼使用者的畫面仍能正常渲染（render），而沒有絲毫延宕？&lt;/p&gt;
&lt;p&gt;因為 XHR 並非由 JS 本身，而是由它的運行環境（即瀏覽器）&lt;strong&gt;自身的&lt;/strong&gt;執行緒來處理的，因此不會阻塞呼叫堆疊。由瀏覽器負責處理的函式還有計時器 &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt;、與動畫緊密相關的 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt;、害我一腳踏入事件迴圈泥淖的 &lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt;⋯⋯它們通通統稱為 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Web APIs&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;以計時器來舉例，當 JS 引擎執行到 &lt;code class=&quot;language-text&quot;&gt;setTimeout(func, 3000)&lt;/code&gt;，瀏覽器便接手過計時的操作，等三秒一到，便將它的 &lt;code class=&quot;language-text&quot;&gt;func&lt;/code&gt; 回呼塞進呼叫堆疊，讓引擎執行。這可說是一種非同步（asynchronous）的機制。&lt;/p&gt;
&lt;p&gt;一個複雜的網頁，可能會有許多回呼要執行，現在讓我們來問：是誰不停挑出回呼並將回呼塞進呼叫堆疊的？&lt;/p&gt;
&lt;p&gt;那就是今天的主角：事件迴圈。&lt;/p&gt;
&lt;h2 id=&quot;事件迴圈的基本概念&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E4%BA%8B%E4%BB%B6%E8%BF%B4%E5%9C%88%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5&quot; aria-label=&quot;事件迴圈的基本概念 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;事件迴圈的基本概念&lt;/h2&gt;
&lt;p&gt;同樣，在進到事件迴圈的流程前，先來看一張統整圖：&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e53e7455f561eeb2a702370fc75b7865/0a151/event-loop-process.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 127.79661016949153%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAaABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECAwX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAH3YhQXPQc2x//EABoQAQACAwEAAAAAAAAAAAAAAAEAIRARMQL/2gAIAQEAAQUCi0c1GsJuNeDmf//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABQQAQAAAAAAAAAAAAAAAAAAADD/2gAIAQEABj8CT//EAB4QAAMAAgEFAAAAAAAAAAAAAAABESExQSBhgaHB/9oACAEBAAE/Ib3JNPY2HwaGBuWi1qHIoqhiJcmnXjo//9oADAMBAAIAAwAAABCTzjD/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAcEAEAAwADAQEAAAAAAAAAAAABABEhMUFREJH/2gAIAQEAAT8Qohsd4rqMxFhtsjqWadrIpsQdf2MILGntEdheDjyDyUpKHpjsmEXzPYTEpRVs+IJSWQAKon//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;事件迴圈的運作流程。圖片原始來源已不可考&quot;
        title=&quot;事件迴圈的運作流程。圖片原始來源已不可考&quot;
        src=&quot;/static/e53e7455f561eeb2a702370fc75b7865/1c72d/event-loop-process.jpg&quot;
        srcset=&quot;/static/e53e7455f561eeb2a702370fc75b7865/1c72d/event-loop-process.jpg 590w,
/static/e53e7455f561eeb2a702370fc75b7865/0a151/event-loop-process.jpg 866w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;事件迴圈的運作流程。圖片原始來源已不可考&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;看得懂在幹嘛嗎？看不懂？沒關係，繼續看下去，一切都會豁然開朗。&lt;/p&gt;
&lt;p&gt;（喂！有人說看得懂啊！不要假裝沒聽到！喂、喂——）&lt;/p&gt;
&lt;p&gt;（消音）&lt;/p&gt;
&lt;p&gt;接下來，我們會跟著這份由 &lt;a href=&quot;https://whatwg.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;WHATWG（Web Hypertext Application Technology Working Group）&lt;/a&gt; 社群（a.k.a. 瀏覽器大佬）編寫的 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;HTML 規範文件&lt;/a&gt;，來理解事件迴圈詳細的運作流程。&lt;/p&gt;
&lt;p&gt;（原文是英文，我會以自己覺得重要且能理解的方式翻，不會嚴格地一個字一個字翻。如果我覺得英文能比中文表達得更清楚，那我就會維持原文）&lt;/p&gt;
&lt;p&gt;為什麼需要事件迴圈？開宗明義，規範便說了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;為了協調事件、使用者互動、腳本、渲染和網路活動（networking）等，&lt;a href=&quot;https://tc39.es/ecma262/#sec-agents&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;使用者代理（user agents）&lt;/a&gt;必須使用本節所描述的事件迴圈。每個代理都有一個相關聯且唯一的事件循環。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;接下來，規範根據不同代理對事件循環做了三種分類：window event loop、worker event loop、worklet event loop。第一個代理是我們最常碰到的 Window 物件；第二個代理告訴我們 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;service worker&lt;/a&gt; 會有自己的事件循環；第三個代理我不知道是什麼，&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Worklet&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;worklet&lt;/a&gt; 似乎是一個實驗性的 API。&lt;/p&gt;
&lt;p&gt;讓我們往下滑：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一個事件循環會有一到多個任務佇列（task queues）。&lt;/p&gt;
&lt;p&gt;每個任務（task）都來自特定的任務源（task source）。每個任務源都必須對應一個特定的任務佇列。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這兩段話出現了三個專有名詞：任務、任務源、任務佇列。&lt;/p&gt;
&lt;p&gt;首先，&lt;strong&gt;任務就是宏任務&lt;/strong&gt;，它可以幹的事情相當多，包括發布（dispatch）&lt;a href=&quot;https://www.w3schools.com/jsref/obj_event.asp&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Event 物件&lt;/a&gt;（想想當事件監聽器觸發時，回呼所拿到的第一個參數）、解析（parse）HTML、呼叫回呼、&lt;a href=&quot;https://fetch.spec.whatwg.org/#concept-fetch&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;獲取（fetch）&lt;/a&gt;資源、響應（reacting）DOM 操作。&lt;/p&gt;
&lt;p&gt;任務源也很多，但大都被歸類為&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;以下四種&lt;/a&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;DOM 操作：比如以非阻塞的方式將元素插入 document。&lt;/li&gt;
&lt;li&gt;使用者互動：比如鍵盤輸入或滑鼠點擊。&lt;/li&gt;
&lt;li&gt;網路活動。&lt;/li&gt;
&lt;li&gt;歷史紀錄尋訪（history traversal）：比如呼叫 &lt;code class=&quot;language-text&quot;&gt;history.back()&lt;/code&gt; API。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;那任務佇列又是幹嘛的呢？它是拿來分類任務源用的；換言之，不同的任務源可能會被塞進同一個任務佇列。但為什麼要對任務源進一步分類？這個例子說明得很清楚：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;例如，使用者代理可以將任務佇列分為給滑鼠和鍵盤事件（即使用者互動任務源）用的，和給其它任務源用的。藉此，使用者代理可以在&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;事件循環的處理模型&lt;/a&gt;的初始步驟中，給與使用者互動任務源關聯的任務佇列四分之三的優先處理權，以讓介面保持響應性，但又不會卡死其它任務佇列。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;換言之，&lt;strong&gt;說到任務的處理順序時，並沒有誰先觸發誰就先執行的道理&lt;/strong&gt;，這一切都要看你的瀏覽器如何實作。&lt;/p&gt;
&lt;p&gt;最後再講一件事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;微任務佇列（microtask queue）不是任務佇列。&lt;/p&gt;
&lt;p&gt;微任務是一種通俗的講法，指的是透過 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-microtask&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;queue a microtask&lt;/a&gt; 演算法創建的任務。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;微任務雖是一種任務（它們具有相同的&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#concept-task&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;結構&lt;/a&gt;），但它所形成的佇列與任務佇列不同，而這是因為它們在成為佇列時所使用的演算法不同。&lt;/p&gt;
&lt;p&gt;有了這些基本概念後，接下來，讓我們直接進到事件循環的重頭戲吧！&lt;/p&gt;
&lt;h2 id=&quot;事件迴圈是怎麼運作的？&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E4%BA%8B%E4%BB%B6%E8%BF%B4%E5%9C%88%E6%98%AF%E6%80%8E%E9%BA%BC%E9%81%8B%E4%BD%9C%E7%9A%84%EF%BC%9F&quot; aria-label=&quot;事件迴圈是怎麼運作的？ permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;事件迴圈是怎麼運作的？&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;8.1.6.3 處理模型（Processing model）&lt;/a&gt;中，規範描述了事件迴圈的運作流程：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只要事件循環存在，便必須一直運行以下步驟：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;挑出一個任務佇列，要怎麼挑&lt;a href=&quot;https://infra.spec.whatwg.org/#implementation-defined&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;由使用者代理決定&lt;/a&gt;。如果沒有任務佇列，那就直接跳到微任務步驟。&lt;/li&gt;
&lt;li&gt;將 oldestTask 設為該任務佇列中第一個任務，並移除。&lt;/li&gt;
&lt;li&gt;將&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#currently-running-task&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;當前正在運行的任務&lt;/a&gt;設為 oldestTask。&lt;/li&gt;
&lt;li&gt;Let taskStartTime be the &lt;a href=&quot;https://w3c.github.io/hr-time/#dfn-current-high-resolution-time&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;current high resolution time&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Perform oldestTask&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#concept-task-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;steps&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;將當前正在運行的任務設回 null。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;前六步都滿好理解的。&lt;/p&gt;
&lt;p&gt;1：「由使用者代理決定」符合我們前面所說的，使用者代理可自行決定要給哪個任務佇列較高的優先處理權。&lt;/p&gt;
&lt;p&gt;2：由於是任務&lt;strong&gt;佇列&lt;/strong&gt;，資料操作當然是遵循先進先出（FIFO, First-In-First-Out）的原則。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;
&lt;p&gt;微任務：&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;施行微任務檢查（Perform a microtask checkpoint）&lt;/a&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;event loop&lt;/a&gt;&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#performing-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;performing a microtask checkpoint&lt;/a&gt; is true, then return.&lt;/li&gt;
&lt;li&gt;Set the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;event loop&lt;/a&gt;&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#performing-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;performing a microtask checkpoint&lt;/a&gt; to true.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只要微任務佇列不為空：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;將 oldestMicrotask 設為對微任務佇列&lt;a href=&quot;https://infra.spec.whatwg.org/#queue-dequeue&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;出列（dequeue）&lt;/a&gt;的結果。&lt;/li&gt;
&lt;li&gt;將當前正在運行的任務設為 oldestMicrotask。&lt;/li&gt;
&lt;li&gt;運行 oldestMicrotask。&lt;/li&gt;
&lt;li&gt;將當前正在運行的任務設回 null。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;For each &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;environment settings object&lt;/a&gt; whose &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#responsible-event-loop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;responsible event loop&lt;/a&gt; is this &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;event loop&lt;/a&gt;, &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;notify about rejected promises&lt;/a&gt; on that &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;environment settings object&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Cleanup Indexed Database transactions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Perform &lt;a href=&quot;https://tc39.es/ecma262/#sec-clear-kept-objects&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ClearKeptObjects&lt;/a&gt;().&lt;/li&gt;
&lt;li&gt;Set the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;event loop&lt;/a&gt;&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#performing-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;performing a microtask checkpoint&lt;/a&gt; to false.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;7：開始處理微任務。首先檢查 performing a microtask checkpoint 這個旗幟（flag）。為什麼需要？&lt;strong&gt;因為微任務檢查並不只有在事件循環的這個環節才觸發&lt;/strong&gt;，其它觸發時機比如調用回呼後；而為了避免在處理微任務佇列時重複施行微任務檢查（這可能在 7.3.3 發生），因此需要一個旗幟來控制（&lt;a href=&quot;#%E5%BE%AE%E4%BB%BB%E5%8B%99%E7%9A%84%E5%9F%B7%E8%A1%8C%E7%AD%96%E7%95%A5&quot;&gt;後面&lt;/a&gt;會再詳細說明微任務的執行策略）。&lt;/p&gt;
&lt;p&gt;7.3：檢查微任務佇列是否為空，若不為空，便執行第一個微任務。重複這個過程，直到所有微任務都執行完畢。&lt;/p&gt;
&lt;p&gt;7.4-6：我看不懂在做什麼，知道在幹嘛的人可以留言跟我說。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;將 hasARenderingOpportunity 設為 false.&lt;/li&gt;
&lt;li&gt;將 now 設為 &lt;a href=&quot;https://w3c.github.io/hr-time/#dfn-current-high-resolution-time&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;current high resolution time&lt;/a&gt;. [&lt;a href=&quot;https://html.spec.whatwg.org/multipage/references.html#refsHRT&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;HRT&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Report the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#concept-task&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;task&lt;/a&gt;&apos;s duration by performing the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let top-level browsing contexts be an empty &lt;a href=&quot;https://infra.spec.whatwg.org/#ordered-set&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;set&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For each &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;environment settings object&lt;/a&gt; settings of oldestTask&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#script-evaluation-environment-settings-object-set&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;script evaluation environment settings object set&lt;/a&gt;, &lt;a href=&quot;https://infra.spec.whatwg.org/#set-append&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;append&lt;/a&gt; setting&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#top-level-browsing-context&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;top-level browsing context&lt;/a&gt; to top-level browsing contexts.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/longtasks/#report-long-tasks&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Report long tasks&lt;/a&gt;, passing in taskStartTime, now (the end time of the task), top-level browsing contexts, and oldestTask.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;8：從字面上理解，hasARenderingOpportunity 便是「是否有渲染機會」，一開始是 false，它將跟等下第 10 步的更新畫面（Update the rendering）有關。&lt;/p&gt;
&lt;p&gt;9：這裡的 now 將成為 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 回呼的第一個參數。&lt;/p&gt;
&lt;p&gt;10：我看不太懂在幹嘛，但 10.3 回報的 long tasks 似乎會出現在 &lt;a href=&quot;https://web.dev/long-tasks-devtools/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Chrome 開發者工具的 Performance 面板中&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;11&quot;&gt;
&lt;li&gt;
&lt;p&gt;更新畫面：如果是 window event loop，則：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Let docs be all &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; objects whose &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#relevant-agent&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;relevant agent&lt;/a&gt;&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#concept-agent-event-loop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;event loop&lt;/a&gt; is this event loop, sorted arbitrarily except that the following conditions must be met:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; B whose &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#concept-document-bc&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;browsing context&lt;/a&gt;&apos;s &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#bc-container-document&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;container document&lt;/a&gt; is A must be listed after A in the list.&lt;/li&gt;
&lt;li&gt;If there are two documents A and B whose &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#concept-document-bc&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;browsing contexts&lt;/a&gt; are both &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#child-browsing-context&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;child browsing contexts&lt;/a&gt; whose &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#bc-container-document&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;container documents&lt;/a&gt; are another &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; C, then the order of A and B in the list must match the &lt;a href=&quot;https://dom.spec.whatwg.org/#concept-shadow-including-tree-order&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;shadow-including tree order&lt;/a&gt; of their respective &lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-container&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;browsing context containers&lt;/a&gt; in C&apos;s &lt;a href=&quot;https://dom.spec.whatwg.org/#concept-node-tree&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;node tree&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the steps below that iterate over docs, each &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; must be processed in the order it is found in the list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;渲染機會（Rendering opportunities）：移除&lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#concept-document-bc&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;瀏覽上下文（browsing context）&lt;/a&gt;沒有渲染機會的 docs。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;11：這一步做的事情相當多（上面只是一小部分），主要是處理跟畫面渲染相關的事。&lt;/p&gt;
&lt;p&gt;11.1：我沒有全部看懂，但大致上就是選出 &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; 物件、將它們指定給 docs，並以一定的規則排序。&lt;/p&gt;
&lt;p&gt;11.2：你可能會疑惑：要怎麼決定有無渲染機會？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果使用者代理當前能夠將瀏覽上下文的內容呈現給使用者，那該瀏覽上下文就有渲染機會，這需考慮硬體刷新率的限制，和使用者代理出於性能考量的節流，也需考慮内容的可呈現性（presentable），即使它在視窗（viewport）外。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;規範似乎覺得這裡沒說清楚，於是又補上（跟上句很像的）一句：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;瀏覽上下文的渲染機會是基於硬體限制（如顯示刷新率（display refresh rates））和其它因素（如頁面性能或頁面是否在背景（background））來確定的。渲染機會通常會定期出現。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;好像還是沒說清楚，再給一個註解：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本規範並沒規定任何特定的模式來選擇渲染機會。但舉例來說，若瀏覽器試圖實現 60Hz 的刷新率，那渲染機會最多每 1/60 秒要出現一次（約 16.7ms）。若瀏覽器發現某個瀏覽上下文無法維持這個速率，它可能會將渲染機會降到可持續的每秒 30 次，而不是偶爾掉幀。同樣，如果一個瀏覽上下文不可見，使用者代理可能會決定將該頁面降到每秒 4 次渲染機會，甚至更少。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;說了這麼多，我們大致上可以這麼總結：不是每輪事件循環都會更新畫面，其頻率高低，端看瀏覽器怎麼實作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;11&quot;&gt;
&lt;li&gt;
&lt;p&gt;更新畫面：&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;如果 docs 不為空，則將 hasARenderingOpportunity 設為 true。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不必要的渲染（Unnecessary rendering）：移除滿足以下條件的 docs：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用者代理認為更新  &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; 的瀏覽上下文的畫面不會有明顯的（visible）效果。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; 的 map of animation frame callbacks 為空。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;移除使用者代理由於其它原因認為最好（preferrable）跳過更新畫面的 docs。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;11.3：hasARenderingOpportunity 在第 8 步曾出現過。&lt;/p&gt;
&lt;p&gt;11.4-5：與 11.2 做的事類似，再「兩」次篩選掉不需渲染的 docs。&lt;/p&gt;
&lt;p&gt;11.4：把篩選條件說得很清楚，值得注意的是，animation frame callback 就是放在 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 的回呼。&lt;/p&gt;
&lt;p&gt;11.5：有說等於沒說，其目的應是授與瀏覽器決定是否要渲染的權力。不過規範還是舉例了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;比如為確保某些任務緊接著執行，只有微任務檢查交錯其間（而且沒有 animation frame callbacks 參與 ）。具體來說，使用者代理可能希望合併定時器回呼，中間沒有畫面更新。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;同時規範也說明了設立 11.2 和 11.4 的目的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;被標註為「渲染機會」的步驟可防止使用者代理在&lt;strong&gt;無法向使用者呈現新内容時&lt;/strong&gt;更新畫面。&lt;/p&gt;
&lt;p&gt;被標註為「不必要的渲染」的步驟可防止使用者代理在&lt;strong&gt;没有新内容可繪製時&lt;/strong&gt;更新畫面。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;到這裡，如果 docs 還有&lt;a href=&quot;https://html.spec.whatwg.org/multipage/browsers.html#fully-active&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;充分活躍（fully active）&lt;/a&gt;的 &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt;，那就一一對它們：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;11&quot;&gt;
&lt;li&gt;
&lt;p&gt;更新畫面：&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/interaction.html#flush-autofocus-candidates&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;flush autofocus candidates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/cssom-view/#run-the-resize-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;run the resize steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/cssom-view/#run-the-scroll-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;run the scroll steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;evaluate media queries and report changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/web-animations/#update-animations-and-send-events&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;update animations and send events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fullscreen.spec.whatwg.org/#run-the-fullscreen-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;run the fullscreen steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#run-the-animation-frame-callbacks&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;run the animation frame callbacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/IntersectionObserver/#run-the-update-intersection-observations-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;run the update intersection observations steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Invoke the &lt;a href=&quot;https://w3c.github.io/paint-timing/#mark-paint-timing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;mark paint timing&lt;/a&gt; algorithm&lt;/li&gt;
&lt;li&gt;更新該 &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; 的畫面或使用者介面，及其瀏覽上下文以反映當前狀態。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;11.6-13：你可以看到很多熟悉的名字，比如事件類型 &lt;code class=&quot;language-text&quot;&gt;resize&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;scroll&lt;/code&gt;、與 RWD（Responsive Web Design）息息相關的 media queries、常被拿來做懶加載（lazy load）的 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Intersection Observer&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;11.12：執行 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 的回呼。&lt;/p&gt;
&lt;p&gt;11.14：不知道在幹嘛，但似乎跟網頁性能指標 &lt;a href=&quot;https://web.dev/first-contentful-paint/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;FCP（First Contentful Paint）&lt;/a&gt;有關。&lt;/p&gt;
&lt;p&gt;到這裡，事件循環的更新畫面結束，接下來：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;12&quot;&gt;
&lt;li&gt;
&lt;p&gt;如果滿足以下條件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;事件循環是 window event loop；&lt;/li&gt;
&lt;li&gt;在任務佇列中沒有任何任務；&lt;/li&gt;
&lt;li&gt;微任務佇列為空；&lt;/li&gt;
&lt;li&gt;hasARenderingOpportunity 為 false；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;運行 &lt;a href=&quot;https://w3c.github.io/requestidlecallback/#start-an-idle-period-algorithm&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;start an idle period algorithm&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;12：若條件都滿足，&lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt; 的回呼將在這步執行（終於找到你啦！我找你找得好苦啊！）。&lt;/p&gt;
&lt;p&gt;第 13 步是在講事件循環是 worker event loop 的情況，與本文主題無關，跳過。&lt;/p&gt;
&lt;p&gt;一輪事件迴圈到此運作完畢。現在再回去看本節開頭的統整圖，是不是很清楚了呢？&lt;/p&gt;
&lt;p&gt;還不清楚？沒關係，讓我們結合呼叫堆疊、Web APIs 和事件迴圈，來實際跑一遍流程吧！&lt;/p&gt;
&lt;h2 id=&quot;從範例了解事件迴圈&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%BE%9E%E7%AF%84%E4%BE%8B%E4%BA%86%E8%A7%A3%E4%BA%8B%E4%BB%B6%E8%BF%B4%E5%9C%88&quot; aria-label=&quot;從範例了解事件迴圈 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;從範例了解事件迴圈&lt;/h2&gt;
&lt;p&gt;先來看一個簡單的例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;timeout&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFulfill1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;promise1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFulfill2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;promise2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;main&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;logSomething&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;logSomething&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;something&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;當程式執行，會發生什麼事？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立一個全局執行上下文（global execution context），壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt;，由 Web API 接管計時器的處理，時間一到便把 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 排入任務佇列。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;Promise.then()&lt;/code&gt;，將 &lt;code class=&quot;language-text&quot;&gt;onFulfill1&lt;/code&gt; 排入微任務佇列。&lt;/li&gt;
&lt;li&gt;在控制台（console）印出 &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;logSomething&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;something&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;logSomething&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;程式執行完畢，全局執行上下文彈出。&lt;/li&gt;
&lt;li&gt;施行微任務檢查：微任務佇列不為空，出列。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onFulfill1&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;promise1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onFulfill1&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;Promise.then()&lt;/code&gt;，將 &lt;code class=&quot;language-text&quot;&gt;onFulfill2&lt;/code&gt; 排入微任務佇列。&lt;/li&gt;
&lt;li&gt;微任務佇列不為空，出列。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onFulfill2&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;promise2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onFulfill2&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;微任務佇列為空，微任務檢查完畢。&lt;/li&gt;
&lt;li&gt;呼叫堆疊為空，事件循環從任務佇列取出最舊的任務。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;timeout&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;你可能會疑惑： 為什麼微任務是在程式執行完畢後便執行？微任務檢查不是應該在調用一個任務後（在此例即是 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt;）才觸發嗎？&lt;/p&gt;
&lt;p&gt;別忘記我提醒過你的：微任務檢查的觸發時機相當多。&lt;a href=&quot;#%E5%BE%AE%E4%BB%BB%E5%8B%99%E7%9A%84%E5%9F%B7%E8%A1%8C%E7%AD%96%E7%95%A5&quot;&gt;下一節&lt;/a&gt;我會詳述微任務的執行策略。&lt;/p&gt;
&lt;p&gt;再來看一個比較複雜的例子：&lt;/p&gt;
&lt;p&gt;HTML：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;btn&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Click me!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;JS：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; btn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;btn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

btn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;timeout&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;requestIdleCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onIdle2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;idle2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFulfill1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;promise1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onAf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;raf&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFulfill2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;promise2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;requestIdleCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onIdle1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;idle1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;當你點擊按鈕，控制台會印出什麼？&lt;/p&gt;
&lt;p&gt;答案不只一種，在不同瀏覽器或同一瀏覽器的不同時間，可能會出現不同結果。若只看最常出現的結果，Chrome 89.0.4389.90 是：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;promise1
raf
promise2
timeout
idle1
idle2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Firefox 87.0 是：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;promise1
timeout
raf
promise2
idle1
idle2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;或是：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;promise1
timeout
idle1
raf
promise2
idle2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但無論是哪種結果，都能被事件循環的處理模型解釋；換言之，差異並不來自規範失效，而來自規範賦予瀏覽器的彈性。&lt;/p&gt;
&lt;p&gt;讓我們一步步來：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;click&lt;/code&gt; 事件已由 Web API 接手處理，將其回呼排入任務佇列。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onClick&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt;，由 Web API 接管計時器的處理，時間一到便把 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 排入任務佇列。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;Promise.then()&lt;/code&gt;，將 &lt;code class=&quot;language-text&quot;&gt;onFulfill1&lt;/code&gt; 排入微任務佇列。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt;，將其回呼 &lt;code class=&quot;language-text&quot;&gt;onAf&lt;/code&gt; 放入 map of animation frame callbacks。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt;，將其回呼 &lt;code class=&quot;language-text&quot;&gt;onIdle1&lt;/code&gt; 放入 list of idle request callbacks。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onClick&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;施行微任務檢查：微任務佇列不為空，出列。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onFulfill1&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;promise1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onFulfill1&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接下來，分歧產生了：究竟是會先執行計時器的回呼 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt;，還是 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 的回呼 &lt;code class=&quot;language-text&quot;&gt;onAf&lt;/code&gt;？端看此時 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 有沒有被排入任務佇列。你可能會說，&lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt; 的等待時間既是 &lt;code class=&quot;language-text&quot;&gt;0&lt;/code&gt;，其回呼應該就要被馬上排入任務佇列吧？&lt;/p&gt;
&lt;p&gt;不一定，因為規範在&lt;a href=&quot;https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-initialisation-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;計時器初始化步驟&lt;/a&gt;中其實有授與瀏覽器延長時間的權力：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol start=&quot;17&quot;&gt;
&lt;li&gt;可選擇再等待一個由使用者代理決定的時間長度。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;目的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;為了最佳化（optimize）裝置的電力使用（power usage）。例如，一些處理器具有低電量模式，其中定時器的粒度（granularity）會降低。在這種平台上，使用者代理可減慢定時器，而非要求處理器使用更精確的模式、消耗較高的電力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;雖然我不知道 Chrome 為何要對我這台 2017 年出廠的 MacBook Pro 13 做這件事，但反正理由百百款，我們只要知道有這個可能性就好。&lt;/p&gt;
&lt;p&gt;所以，Chrome 的情況應該是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;呼叫堆疊為空，由於無合格的任務佇列，且微任務佇列為空，事件循環進到更新畫面步驟。&lt;/li&gt;
&lt;li&gt;運行 animation frame callbacks。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onAf&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;raf&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;Promise.then()&lt;/code&gt;，將 &lt;code class=&quot;language-text&quot;&gt;onFulfill2&lt;/code&gt; 排入微任務佇列。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onAf&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;施行微任務檢查：微任務佇列不為空，出列。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onFulfill2&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;promise2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onFulfill2&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;由於 hasARenderingOpportunity 為 true，或任務佇列中有任務，跳過 start an idle period algorithm。&lt;/li&gt;
&lt;li&gt;呼叫堆疊為空，事件循環從任務佇列取出最舊的任務。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;timeout&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;遇到 &lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt;，將其回呼 &lt;code class=&quot;language-text&quot;&gt;onIdle2&lt;/code&gt; 放入 list of idle request callbacks。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;start an idle period algorithm，調用 &lt;code class=&quot;language-text&quot;&gt;onIdle1&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;idle1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onIdle1&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;li&gt;調用 &lt;code class=&quot;language-text&quot;&gt;onIdle2&lt;/code&gt;，將其函式執行上下文壓入呼叫堆疊。&lt;/li&gt;
&lt;li&gt;印出 &lt;code class=&quot;language-text&quot;&gt;idle2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onIdle2&lt;/code&gt; 執行完畢，彈出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Firefox 的狀況呢，我有點累了（我相信你也累了），有空時可自行推導。&lt;/p&gt;
&lt;p&gt;我只講 Firefox 的第二種結果——&lt;code class=&quot;language-text&quot;&gt;onIdle1&lt;/code&gt; 怎麼會緊接在 &lt;code class=&quot;language-text&quot;&gt;onTimeout&lt;/code&gt; 之後執行？一個合理的猜測是：在這輪事件循環，用戶代理跳過了更新畫面，而且是在 Rendering opportunities 這步就把本來要被更新畫面的 &lt;code class=&quot;language-text&quot;&gt;Document&lt;/code&gt; 移除了，這樣 hasARenderingOpportunity 才不會被設為 true，&lt;code class=&quot;language-text&quot;&gt;requestIdleCallback&lt;/code&gt; 的回呼也才有辦法被執行。&lt;/p&gt;
&lt;p&gt;現在，你應該對事件循環的運作流程有很清楚的認識了，但本文還沒有要結束⋯⋯因為我還要講前面一直說會講的微任務的執行策略。&lt;/p&gt;
&lt;h2 id=&quot;微任務的執行策略&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%BE%AE%E4%BB%BB%E5%8B%99%E7%9A%84%E5%9F%B7%E8%A1%8C%E7%AD%96%E7%95%A5&quot; aria-label=&quot;微任務的執行策略 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;微任務的執行策略&lt;/h2&gt;
&lt;p&gt;微任務的觸發時機既簡單又複雜。先講複雜的：前面說過，執行微任務前會先施行微任務檢查，而微任務檢查的觸發時機可參考這張圖：&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f8f62e0a1f68feb6668da05dab93ecca/3d4b6/perform-a-microtask-checkpoint.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 76.94915254237287%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACSUlEQVQ4y1WU525aQRCFef+X4A9CdAOm2wYb04sNooVqTJGSIMVBoRdN/I20CJCOdvbevWfOzJzFsl6vZbvdyuFwUOz3e9ntdhofj0fFdQx4z3cGm81GVquVnE4nsWQyWclms+Lz+SQYDEosFpNkMql7r9crDodDwuGwOJ1OcbvdYrfbJZfLycfHh/T7fRkMBjIcDqXdbiuxRb5/z8/PYrVa5e7uTu7v75X45eVFOp2ONJtNXVutljQaDd1D9Pn5qaQQjkYjPaOEP/tfUqvV5PHxUSKRiCpDRSgUklQqpckgpwrOED89PelZ3qMOwovC1Z+tEkIQCAQUlEp5rDabTVwulxQKBVWHUkAMCWpvSv77a6UHUIAaMj88PEg0GpV0Oq1xPp+X+Xwu0+lUJpPJZb0GxEr4e/Bd8ntNhwEJfYTo7e1NKpWKlMtlTQYYBkqJeU78+vqqCfmGiVuW//ZSr9d1GH6/X8FkAb0kSbFYVKVUAIFZITE9pr9K2Gp8SbfX0Ww02/QSYlZagJrZbKaTNWDC4/FYcVPy6XTUBlMG/kMpvmMgHo/n4k8SmbYkEgkF51B5M5Th+0w63baUSqWLLVBFufQTlSQhAXuScBZn4L1er3drm8P+KN3uD20sSuLxuIKbYQhJwDtIUYqNeEbpxocXY8PMDSArRMaLlGNujkkAYSaT0SqwGn1EIbeFvRKu1xt9CGm1WlW7YA9jEayDemMTBDAIvkEdw8KjxEp4Pp91Sjwg4/X0gNmblXNMFEC2XC4Vi8VC/6n+AxnZ/06kkcdrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;規範中有引用到 perform a microtask checkpoint 關鍵字的段落。圖擷自 [WHATWG 規範文件](https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint)&quot;
        title=&quot;規範中有引用到 perform a microtask checkpoint 關鍵字的段落。圖擷自 [WHATWG 規範文件](https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint)&quot;
        src=&quot;/static/f8f62e0a1f68feb6668da05dab93ecca/fcda8/perform-a-microtask-checkpoint.png&quot;
        srcset=&quot;/static/f8f62e0a1f68feb6668da05dab93ecca/fcda8/perform-a-microtask-checkpoint.png 590w,
/static/f8f62e0a1f68feb6668da05dab93ecca/3d4b6/perform-a-microtask-checkpoint.png 712w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;規範中有引用到 perform a microtask checkpoint 關鍵字的段落。圖擷自 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;WHATWG 規範文件&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;點擊&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;這個連結&lt;/a&gt;，再點擊 perform a microtask checkpoint 粗體字，你也可以看到引用這個關鍵字的其它段落（當然不只這些，比如還有 &lt;a href=&quot;https://drafts.csswg.org/web-animations/#update-animations-and-send-events&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;update animations and send events&lt;/a&gt; 的第 3 步）。&lt;/p&gt;
&lt;p&gt;比較常見的觸發點是 8.1.4.4 的 Calling scripts，它在 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#calling-scripts:perform-a-microtask-checkpoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;clean up after running script 的最後&lt;/a&gt;，當呼叫堆疊為空，會施行微任務檢查。&lt;/p&gt;
&lt;p&gt;我沒有很了解 script 在規範中的意義為何，只知道有很多地方都會執行 clean up after running script，最常見的便是&lt;a href=&quot;https://heycam.github.io/webidl/#invoke-a-callback-function&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;調用回呼的 Return 階段的第 2 步&lt;/a&gt;，而這就是為什麼在上一節第二個例子中的 &lt;code class=&quot;language-text&quot;&gt;onAf&lt;/code&gt; 會在執行完畢後，馬上施行微任務檢查 。&lt;/p&gt;
&lt;p&gt; 「太瑣碎了吧！我怎麼可能記得住？」在你喊出這句話之前，我要來講一個簡單的判斷方法：&lt;strong&gt;只要當前呼叫堆疊為空，微任務檢查便會立即施行&lt;/strong&gt;；易言之，微任務的執行策略是：見縫插針、盡可能早。&lt;/p&gt;
&lt;p&gt;這個判斷方法並沒在規範中明確表述（至少我沒找到），但它應能適用 99.9% 的情境，剩下 0.1% 還有待你分享給我。&lt;/p&gt;
&lt;p&gt;只講一個證據：現代瀏覽器已把 &lt;code class=&quot;language-text&quot;&gt;Promise.then()&lt;/code&gt; 看作是一種微任務（至少在 2015 年前不是如此，詳情可見&lt;a href=&quot;https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;這篇文章&lt;/a&gt;），而在 ECMAScript 規範裡，有一個與微任務相似的概念，叫作 job，而 &lt;a href=&quot;https://262.ecma-international.org/11.0/#sec-promise-jobs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Promise.then()&lt;/code&gt; 便是一種 job&lt;/a&gt;。因此，微任務的執行策略理論上要跟 job 相容。對於 &lt;a href=&quot;https://262.ecma-international.org/11.0/#sec-jobs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;job 的執行時機&lt;/a&gt;，你會發現它跟我前述的判斷方法是一樣的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在未來的某個時間點，當沒有正在運行的執行上下文，且執行上下文堆疊為空時。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;（除了 &lt;code class=&quot;language-text&quot;&gt;Promise&lt;/code&gt;，&lt;a href=&quot;https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;MutationObserver&lt;/code&gt;&lt;/a&gt;、&lt;a href=&quot;https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-queuemicrotask&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;queueMicrotask&lt;/code&gt;&lt;/a&gt; 也是微任務。有很多文章把 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/web-messaging.html#window-post-message-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;postMessage&lt;/code&gt;&lt;/a&gt; 也當作微任務，&lt;a href=&quot;https://html.spec.whatwg.org/multipage/web-messaging.html#window-post-message-steps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;但它其實是宏任務&lt;/a&gt;，來自 posted message 任務源 ）&lt;/p&gt;
&lt;h2 id=&quot;回答開頭的問題&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%9B%9E%E7%AD%94%E9%96%8B%E9%A0%AD%E7%9A%84%E5%95%8F%E9%A1%8C&quot; aria-label=&quot;回答開頭的問題 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;回答開頭的問題&lt;/h2&gt;
&lt;p&gt;本文到這裡已經快 7000 字了，原本還想寫事件迴圈可以怎麼應用，但讀者可能已經跑光了（其實是我手痠啦哈哈），加上我還沒有很了解微任務的使用時機，只好作罷。&lt;/p&gt;
&lt;p&gt;在結束之前，看看我開頭提的問題，有什麼是還沒回答的嗎？有！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「幀」是什麼？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;幀是組成瀏覽器畫面的基本元素；每個時刻的畫面，都是由一個相同或不同的幀所組成的。想更了解這句話的具體涵義，可查看 &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#frames&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Chrome 開發者工具 Performance 面板的 Frames 斷面圖&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;幀與更新畫面相關，但並&lt;strong&gt;不等同於&lt;/strong&gt;更新畫面。幀包括了事件迴圈、佈局（layout）、繪製（paint）等等。一幀就代表一次畫面更新，但不代表只有一輪事件迴圈，因為瀏覽器能根據各種原因跳過更新畫面。&lt;/p&gt;
&lt;p&gt;形象化地說，若瀏覽器畫面是「人」，那幀就像是「一天」這個時間單位——人的一生是由一天又一天所組成的，就像瀏覽器畫面是由一幀又一幀所組成的；人每天都會有固定的行程，且會有一定的順序，比如睡覺、吃飯、上廁所、洗澡等等，就像瀏覽器每一幀都會有函式呼叫、事件迴圈、繪製等等；當然，人有時會因為太累而不洗澡直接睡覺，瀏覽器也能有限度地調整一幀內所發生的事情的順序。&lt;/p&gt;
&lt;p&gt;本文到此（終於）結束，不知道你是否跟我一樣也體驗到了事件迴圈的魅力？&lt;/p&gt;
&lt;p&gt;喂？喂？還有人在嗎⋯⋯？&lt;/p&gt;
&lt;h2 id=&quot;相關資料&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E7%9B%B8%E9%97%9C%E8%B3%87%E6%96%99&quot; aria-label=&quot;相關資料 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;相關資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fi3ework/blog/issues/29&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;跟着 Event loop 规范理解浏览器中的异步机制&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aooy/blog/issues/5&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;从event loop规范探究javaScript异步及浏览器更新渲染时机&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.404forest.com/2017/07/18/how-javascript-actually-works-eventloop-and-uirendering/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;深入探究 eventloop 与浏览器渲染的时序问题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Tasks, microtasks, queues and schedules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Loupe&lt;/a&gt;——動畫演示呼叫堆疊、事件循環和任務佇列之間的互動。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;規範文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;WHATWG HTML Living Standard: Event loops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.w3.org/html5/spec-LC/webappapis.html#event-loops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;W3C HTML5: Event loops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[初學者學 KMP 演算法]]></title><description><![CDATA[一個初學者看得懂的 KMP 演算法。]]></description><link>https://yeefun.github.io/kmp-algorithm-for-beginners/</link><guid isPermaLink="false">https://yeefun.github.io/kmp-algorithm-for-beginners/</guid><pubDate>Tue, 22 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;第一次碰 KMP，被搞得暈頭轉向，花了兩天才搞定。只好趕緊寫下來，以免半小時後就忘記。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;要怎麼在主串裡找到某個子串（模式）呢？&lt;/p&gt;
&lt;p&gt;比如 &lt;code class=&quot;language-text&quot;&gt;abcabcabe&lt;/code&gt; 是主串，要怎麼找到 &lt;code class=&quot;language-text&quot;&gt;abcabe&lt;/code&gt; 這個子串？&lt;/p&gt;
&lt;p&gt;最簡單的想法，就是從主串的第一個字跟子串的第一個字開始比，如果第一個字相同，再接著比第二個字，若第二個字也相同，再接著比第三個字⋯⋯如果不同，則主串回到第二個字、子串回到第一個字，接著主串的第二個字跟子串的第一個字比，如果相同，則主串的第三個字跟子串的第二個字比；如果不同，則主串回到第三個字，子串回到第一個字⋯⋯以此類推。&lt;/p&gt;
&lt;p&gt;用程式碼表示就是這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;indexOfByBf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      sI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      sI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這個算法直觀，又叫暴力（Brute Force）算法。暴力儘管好用，但當主串一長，便顯得沒效率。它的時間複雜度最好和最壞分別是 &lt;code class=&quot;language-text&quot;&gt;O(n + m)&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;O(n * m)&lt;/code&gt;（假設主串的長度為 &lt;code class=&quot;language-text&quot;&gt;n&lt;/code&gt;，子串的長度為 &lt;code class=&quot;language-text&quot;&gt;m&lt;/code&gt;）。&lt;/p&gt;
&lt;h2 id=&quot;kmp-算法&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#kmp-%E7%AE%97%E6%B3%95&quot; aria-label=&quot;kmp 算法 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;KMP 算法&lt;/h2&gt;
&lt;p&gt;要怎麼提高效率呢？首先，我們可以把匹配的時間複雜度分成兩個部分，一個是比較的趟（次）數，另一個是比較的字數。在暴力算法中，前者在最壞情況下為主串的長度，後者則為子串的長度。我們無法降低比較的字數（要找 &lt;code class=&quot;language-text&quot;&gt;abc&lt;/code&gt;，就只能 &lt;code class=&quot;language-text&quot;&gt;abc&lt;/code&gt; 一一比對，不然呢？），但可以減少比較的趟數。&lt;/p&gt;
&lt;p&gt;要怎麼減少呢？這要看我們手上握有什麼資訊。當我們知道這一次比較找不到子串、準備回頭的那一刻，我們究竟獲得了什麼？這些資訊有辦法降低我們接下來比較的趟數嗎？&lt;/p&gt;
&lt;p&gt;讓我們看看一個範例，主串是 &lt;code class=&quot;language-text&quot;&gt;abcabcabcabe&lt;/code&gt;、子串是 &lt;code class=&quot;language-text&quot;&gt;abcabe&lt;/code&gt;。第一次比較：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;✔✔✔✔✔✗
abcabcabcabe
abcabe
✔✔✔✔✔✗&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我們可以看到子串的第六個字 &lt;code class=&quot;language-text&quot;&gt;e&lt;/code&gt; 比較失敗了，這時暴力算法的第二次比較會這麼做：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; ↓
abcabcabcabe
 abcabe
 ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;將子串向右移一格，也就是從主串的第&lt;strong&gt;二&lt;/strong&gt;個字開始跟子串的第&lt;strong&gt;一&lt;/strong&gt;個字比。不用一秒，你就知道這次比較不會成功（&lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt; 跟 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt; 不一樣）；再將子串向右移（從主串的第&lt;strong&gt;三&lt;/strong&gt;個字開始跟子串的第&lt;strong&gt;一&lt;/strong&gt;個字比），也不會成功⋯⋯我們有沒有辦法直接移到&lt;strong&gt;可能&lt;/strong&gt;會成功的位置？&lt;/p&gt;
&lt;p&gt;回到第一次比較失敗的那刻，滿足「可能成功」的主串位置的條件是什麼？至少那個位置開始的第一個字要與子串的第一個字相同吧！當然，第二個字相同也很好，第三個字相同更好⋯⋯依此邏輯，我們可以把我們要找的東西描述為：主串中的某個位置，這個位置開始的字符能&lt;strong&gt;最多地&lt;/strong&gt;與子串的字符相同。&lt;/p&gt;
&lt;p&gt;不過，還未比較的主串字符（即主串的倒數六個字符 &lt;code class=&quot;language-text&quot;&gt;abcabe&lt;/code&gt;），由於我們不知道它們的長相，因此也無從得知它們哪個位置能最多地與子串的字符相同。我們只能從已比較過且確認相符的字符去尋找正確的位置，即主串的前五個字符 &lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt;，&lt;strong&gt;而這五個字符，其實也就是子串的前五個字符&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;單用眼睛看，我們可以很快地發現這個位置：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;   ↓
abcabcabcabe
   abcabe
   ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但程式沒辦法這樣「看」，它需要方法去推導出來。這個方法就是，&lt;strong&gt;拿子串匹配成功的部分（即 &lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt;）的前綴去對準自己的後綴&lt;/strong&gt;，而且這個對準的字符數要最多；換句話說，我們在找的是 &lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt; 的&lt;strong&gt;次長共同前後綴（Longest Proper Prefix Which Is Also Suffix&lt;/strong&gt;，以下簡稱為 LPS。會說次長，是因為這個前後綴不能等於字串本身）。&lt;/p&gt;
&lt;p&gt;一樣，先用簡單的方法來找：&lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt; 的（真）前綴有 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;abc&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;abca&lt;/code&gt;；（真）後綴有 &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;cab&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;bcab&lt;/code&gt;。LPS 顯然是 &lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt;，它的長度為 2。&lt;/p&gt;
&lt;p&gt;得到這個數字後，我們就可以知道要從哪個位置開始比了。回到第一次比較失敗的那一刻：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;     ✗
abcabcabcabe
abcabe
     ✗&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;現在，我們不要動主串指針的位置，只要將子串向右移三位：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;     ↓
abcabcabcabe
   abcabe
     ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我們便可以從 &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; 開始比了。省去了跟主串的第二個字和第三個字比較的功（即減少比較的趟數），也跳過了從主串的第四個字開始比較時的頭兩個字 &lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt; 的比較——是不是比暴力算法有效率多了？&lt;/p&gt;
&lt;p&gt;到此，我們可以把 KMP 算法的程式寫出來了：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;indexOfByKmp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      sI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      sI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 主串指針不動，只動子串指針&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;等等，&lt;code class=&quot;language-text&quot;&gt;getNext&lt;/code&gt; 是什麼東西？它回傳的是一個陣列，這個陣列含有一系列部分子串的 LPS 長，它是讓 KMP 算法得以運行的關鍵，接下來就讓我們來看看怎麼求得這個陣列。&lt;/p&gt;
&lt;h2 id=&quot;next-表&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#next-%E8%A1%A8&quot; aria-label=&quot;next 表 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Next 表&lt;/h2&gt;
&lt;p&gt;以子串 &lt;code class=&quot;language-text&quot;&gt;abcabe&lt;/code&gt; 為例：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;abcabe
000120&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這張表的意思是：&lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt; 的 LPS 長為 0，&lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt; 為 0、&lt;code class=&quot;language-text&quot;&gt;abc&lt;/code&gt; 也為 0、&lt;code class=&quot;language-text&quot;&gt;abca&lt;/code&gt; 則為 1、&lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt; 為 2、&lt;code class=&quot;language-text&quot;&gt;abcabe&lt;/code&gt; 為 0。&lt;/p&gt;
&lt;p&gt;要怎麼求得這張表呢？先看最簡單的方法：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pSub &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pSub&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; j &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pSub&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; j&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; pSub&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;j&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; j&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;裡頭有兩個迴圈，時間複雜度為 &lt;code class=&quot;language-text&quot;&gt;O(m^2)&lt;/code&gt;，效率顯然不佳，會拖累 KMP 算法的整體速度。&lt;/p&gt;
&lt;p&gt;要怎麼提高建造這張表的效率呢？思路是這樣的：&lt;strong&gt;拿完整字串的前綴去對準部分字串的後綴&lt;/strong&gt;。假設有一個字串 p，值為 &lt;code class=&quot;language-text&quot;&gt;abcabffabcabc&lt;/code&gt;，現在我們要求前兩個字符的 LPS 長（第一個字的 LPS 長無論如何都是 0，須先補上）：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;0↓
ab
 abcabffabcabc
 ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;讓完整字串的前綴去跟 &lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt; 的後綴比，若相同，則 LPS 長加一；若不同，且完整字串正在被比較的前綴為&lt;strong&gt;第一個前綴（即第一個字符 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;）&lt;/strong&gt;，那 LPS 長便為 0。接著繼續拿完整字串的前綴去跟 &lt;code class=&quot;language-text&quot;&gt;abc&lt;/code&gt; 的後綴比：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;00↓
abc
  abcabffabcabc
  ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;LPS 長也為 0，再繼續往下：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;000↓
abca
   abcabffabcabc
   ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;喔！現在 &lt;code class=&quot;language-text&quot;&gt;abca&lt;/code&gt; 的後綴與完整字串的第一個前綴相同了，LPS 長加一。再繼續往下，拿完整字串的第二個前綴跟 &lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt; 的第二個後綴比：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;0001↓
abcab
   abcabffabcabc
    ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;一樣！LPS 長再加一！&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;00012↓
abcabf
   abcabffabcabc
     ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;又遇到不同了，這邊也是直接設為 0 嗎？其實不能，中間還需要經過一些計算，但這邊先不細講，因為這次結果的確是 0，計算的效果不明顯。再繼續往下吧！會告訴你答案的！&lt;/p&gt;
&lt;p&gt;讓我們把求 LPS 長度的過程加快，直接到最後一個字符：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;000120012345↓
abcabffabcabc
       abcabffabcabc
            ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;現在我們又遇到比較失敗的字了，也是直接設為 0 嗎？不能！在這之前，我們還要做一些掙扎——也許我們可以試看看將完整字串向右移幾位（亦即將其指針向左移），再繼續比。但該移多少呢？&lt;/p&gt;
&lt;p&gt;不覺得這個問題有點熟悉嗎？似乎在前面講 KMP 時說過。你可以把現在遇到的問題想像成也是在做字串匹配：&lt;code class=&quot;language-text&quot;&gt;abcabffabcabc&lt;/code&gt; 是主串，&lt;code class=&quot;language-text&quot;&gt;abcabf&lt;/code&gt; 是子串，當比較失敗時，我們要做什麼？&lt;/p&gt;
&lt;p&gt;求 &lt;code class=&quot;language-text&quot;&gt;abcab&lt;/code&gt; 的 LPS 長度！那我們還要再建一個 Next 表嗎？不用！早就建好了！不就是 2 嗎？&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    ↓
000120012345
abcabffabcabc
    ↑  abcabffabcabc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;現在，將完整字串的指針移到索引 &lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;000120012345↓
abcabffabcabc
          abcabffabcabc
            ↑&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;太棒了！再度配對成功！LPS 長為 3（注意，不是 6 喔）。&lt;/p&gt;
&lt;p&gt;（前面沒說明的第六個字符設為 0，其實也要經過同樣的過程，只是因為再次比較時仍失敗，而此時完整字串正在被比較的前綴是第一個前綴，因此才會結果看起來都一樣）&lt;/p&gt;
&lt;p&gt;把上述過程轉換成程式碼，便是這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 部分字串的指針&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iPartial &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 完整字串的指針&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iWhole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iWhole&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      iPartial &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      iWhole &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iWhole&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iWhole &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 當完整字串正在被比較的前綴是第一個前綴&lt;/span&gt;
      iPartial &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      iWhole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iWhole &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;時間複雜度從 &lt;code class=&quot;language-text&quot;&gt;O(m^2)&lt;/code&gt; 降到 &lt;code class=&quot;language-text&quot;&gt;O(m)&lt;/code&gt;，與前述 KMP 的主體 &lt;code class=&quot;language-text&quot;&gt;O(n)&lt;/code&gt; 加起來，時間複雜度共為 &lt;code class=&quot;language-text&quot;&gt;O(n + m)&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&quot;那些長得不太一樣的-next-表&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E9%82%A3%E4%BA%9B%E9%95%B7%E5%BE%97%E4%B8%8D%E5%A4%AA%E4%B8%80%E6%A8%A3%E7%9A%84-next-%E8%A1%A8&quot; aria-label=&quot;那些長得不太一樣的 next 表 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;那些長得不太一樣的 Next 表&lt;/h2&gt;
&lt;p&gt;你如果看過其它講 KMP 算法的文章，可能會發現它們的 Next 表長得跟這裡的 Next 表不太一樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; abcabe
 000120 &amp;lt;- 這裡的
-100012 &amp;lt;- 別人的&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不同之處不過是後者將前者的數字都往右移了一位，並在第一個位置補上 &lt;code class=&quot;language-text&quot;&gt;-1&lt;/code&gt;。為什麼要這麼做呢？其實只是為了計算上的方便。&lt;/p&gt;
&lt;p&gt;仔細看前面寫的 &lt;code class=&quot;language-text&quot;&gt;indexOfByKmp&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;getNext&lt;/code&gt; 兩個函數，裡面都有這個處理：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;indexOfByKmp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      sI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;      sI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iPartial &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iWhole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iWhole&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      iPartial &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      iWhole &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iWhole&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iWhole &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;      iPartial &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;      next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      iWhole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iWhole &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這是為了防止當 &lt;code class=&quot;language-text&quot;&gt;pI&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;iWhole&lt;/code&gt; 等於 &lt;code class=&quot;language-text&quot;&gt;0&lt;/code&gt; 的時候會出的問題。有人覺得這樣額外開一個分支處理很麻煩，於是就做了上述移位補 &lt;code class=&quot;language-text&quot;&gt;-1&lt;/code&gt; 的動作，並把算法改寫如下：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;indexOfByKmp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; pI &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;      sI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      pI &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;      pI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pI&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pI &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sI &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iPartial &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; iWhole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iPartial &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iPartial &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iWhole &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iWhole&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;      iPartial &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      iWhole &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;      next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iPartial&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iWhole&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;      iWhole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;iWhole&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;自己實際拿筆跟紙跑過一遍，會發現原理是相同的，答案當然也是一樣的噢。&lt;/p&gt;
&lt;h2 id=&quot;相關資料&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E7%9B%B8%E9%97%9C%E8%B3%87%E6%96%99&quot; aria-label=&quot;相關資料 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;相關資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/21923021/answer/281346746&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;如何更好地理解和掌握 KMP 算法? - 海纳的回答&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/21923021/answer/1032665486&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;如何更好地理解和掌握 KMP 算法? - 阮行止的回答&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[關掉 no-var——決定變數宣告關鍵字的良好方法]]></title><description><![CDATA[搭配 let，遵守最小暴露原則，var 其實很有用。]]></description><link>https://yeefun.github.io/turn-off-no-var/</link><guid isPermaLink="false">https://yeefun.github.io/turn-off-no-var/</guid><pubDate>Sun, 11 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大家平時都是怎麼決定該用哪個關鍵字（&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;）宣告變數的呢？受到 &lt;a href=&quot;https://eslint.org/docs/rules/no-var&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ESlint 建議規範&lt;/a&gt;的影響，我猜很多人跟我一樣，決定方式很簡單：會被再賦值（reassign）的就用 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;，不會的就用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;。&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 完全不在我們的選項，畢竟，&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 有很多缺點，它的好處完全可以由 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 來代替，是吧？&lt;/p&gt;
&lt;p&gt;我承認我並沒有很認真探討過這個問題，只是照著 ESlint 規範走，直到在讀書會看了頗知名的 &lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/README.md&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;You Don&apos;t Know JS Yet: Scope &amp;#x26; Closures&lt;/a&gt;（是的，它出第二版了），發現作者 Kyle Simpson 並不派斥 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt;，&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/apA.md#the-case-for-var&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;甚至認為 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的好處無法取代&lt;/a&gt;，這才讓我重新思考並質疑自己不使用 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的理由。&lt;/p&gt;
&lt;h2 id=&quot;var-很有用&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#var-%E5%BE%88%E6%9C%89%E7%94%A8&quot; aria-label=&quot;var 很有用 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 很有用&lt;/h2&gt;
&lt;p&gt;Simpson 認為，&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 跟 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 都很有用（他沒有很喜歡 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;，後面會說原因），前者為函數作用域（function scope），後者為區塊作用域（block scope），因此，當一個變數在整個函數中都會用到，那就該用 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt;，並宣告在&lt;strong&gt;函數最外層&lt;/strong&gt;（top-level，也可以說是頂層）；反之，如果一個變數只會在函數中的某個區塊用到，那就該用 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;，並宣告在&lt;strong&gt;這個區塊內&lt;/strong&gt;，讓外層的作用域取用不到。&lt;/p&gt;
&lt;p&gt;比方說，假如我現在要寫一個能判斷使用者網頁捲動方向的函數，按照 ESlint 建議的寫法，會是這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;logScrollDirection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; beforeScrollY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageYOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scroll&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentScrollY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageYOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentScrollY &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; beforeScrollY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scroll down!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scroll up!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    beforeScrollY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentScrollY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但 Simpson 會建議這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;logScrollDirection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 變成 `var` 了&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; beforeScrollY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageYOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scroll&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 變成 `var` 了&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; currentScrollY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageYOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// 多了大括號&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;// 變成 `let` 了&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentScrollY &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; beforeScrollY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scroll down!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scroll up!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
    beforeScrollY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentScrollY&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你可能會想說，&lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 其實也是函數作用域，為什麼不乾脆把上面的 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 都改成 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 呢？&lt;/p&gt;
&lt;p&gt;理由有兩個：第一，就語義上來說，&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 比 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 更適合扮演「這個變數是作用在整個函數中」的角色，畢竟在 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 出現前的 20 幾年，&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 就一直都是如此。&lt;/p&gt;
&lt;p&gt;第二，如果你在任何地方都使用 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;，那你可能會不好判斷這個 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 所宣告的變數是作用在整個函數中，還是只在某個區塊內。&lt;/p&gt;
&lt;p&gt;你可能還會注意到，在黃線處有個奇怪的大括號 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt;。這個括號能確保 &lt;code class=&quot;language-text&quot;&gt;delta&lt;/code&gt; 不被函數內的其它地方取用到，讓它成為名副其實的區塊變數。&lt;/p&gt;
&lt;p&gt;一般來說，我們在函數內創建區塊變數，是在遇到 &lt;code class=&quot;language-text&quot;&gt;if..else&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;for&lt;/code&gt; 迴圈的時候。但其實你可以不靠這些陳述語句（statement）來創建區塊變數，那就是直接用單獨的 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 大括號。&lt;/p&gt;
&lt;p&gt;這個大括號看來有點奇怪又累贅，但它事實上是讓 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 與 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 的搭配發揮最大效用的關鍵，而且它還體現了軟體工程的一個原則：最小暴露原則（The Principle of Least Exposure, POLE）。&lt;/p&gt;
&lt;h2 id=&quot;最小暴露原則&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E6%9C%80%E5%B0%8F%E6%9A%B4%E9%9C%B2%E5%8E%9F%E5%89%87&quot; aria-label=&quot;最小暴露原則 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;最小暴露原則&lt;/h2&gt;
&lt;p&gt;最小暴露原則與&lt;a href=&quot;https://en.wikipedia.org/wiki/Principle_of_least_privilege&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;最小權限原則（The Principle of Least Privilege, POLP）&lt;/a&gt;有關，但它關注的層次較低。就變數而言，它想最小化暴露的是&lt;strong&gt;變數的作用域&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;為什麼？想想一個極端的問題：為什麼我們不把所有變數都宣告在全局作用域（global scope）？命名衝突、被他人非預期或惡意修改、非意圖依賴導致重構困難（詳細說明可見&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch6.md#least-exposure&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;這裡&lt;/a&gt;）——當我們把區域（local）變數非必要地暴露給程式其它部分使用，這三個危險便會浮現，在日後絆你一跤。&lt;/p&gt;
&lt;p&gt;因此，我們應遵守最小暴露原則，&lt;strong&gt;這意味著我們應盡可能地保持變數私有（private），也就是將變數宣告在盡可能深的嵌套作用域內&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;說來容易，但這其實並不是一個很直覺的作法。我常需要在寫完函數的內容後，再回過頭來重新檢視程式碼，以找出哪些變數是函數中的區域變數（或甚至是區塊中的區域變數），再用 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 括起。&lt;/p&gt;
&lt;p&gt;聽來有點麻煩，但這種作法不僅能避免上述危險，還意外地擁有兩個好處。&lt;/p&gt;
&lt;p&gt;第一個是當你不小心寫出一個肥大的函數，&lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 有助於你判斷哪些程式碼是彼此相關，從而讓你更容易理解整個函數在幹嘛。&lt;/p&gt;
&lt;p&gt;當然，我們應盡可能避免寫出這樣的函數，但即使寫出來了，當我們在重構（refactoring），也可以相對容易判斷出哪些區塊可以&lt;a href=&quot;https://refactoring.com/catalog/extractFunction.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;提取成函數&lt;/a&gt;，以提升可讀性（readability）。&lt;/p&gt;
&lt;p&gt;同時，提取函數的過程也變得容易，因為你可以很快地判斷出變數的來源，知道現在要提取的區塊裡有哪些全局變數和區域變數。比如下面這段程式碼（請想像它是一個內容很長的函數）：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt; * 此處範例借用自 Martin Fowler 的著作 Refactoring: Improving the Design of Existing Code (2nd Edition)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt; * 第六章 Extract Fucntion 一節的程式碼&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt; */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;printOwing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;invoice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; outstanding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; today &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;printBanner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; order &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; invoice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orders&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    outstanding &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  invoice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dueDate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;today&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFullYear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; today&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMonth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; today&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;printDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoice&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outstanding&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;printBanner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;printDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;假設你想重構被黃線劃起來的區塊，把它提取成一個名為 &lt;code class=&quot;language-text&quot;&gt;recordDueDate&lt;/code&gt; 的函數，但由於你不知道 &lt;code class=&quot;language-text&quot;&gt;today&lt;/code&gt; 這個變數還有在這個函數中的哪些地方用到，因此你必須一行一行檢查，還必須進到內嵌函數去看有無使用到 &lt;code class=&quot;language-text&quot;&gt;today&lt;/code&gt;，相當麻煩。&lt;/p&gt;
&lt;p&gt;如果現在程式碼變成這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;printOwing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;invoice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; outstanding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;printBanner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; order &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; invoice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orders&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    outstanding &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; today &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    invoice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dueDate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;today&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFullYear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; today&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMonth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; today&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;printDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoice&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outstanding&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;printBanner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;printDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;清楚多了吧！現在你可以很篤定 &lt;code class=&quot;language-text&quot;&gt;today&lt;/code&gt; 絕對不會被這個區塊以外的地方用到，可以放心又快速地把這兩行程式碼提取成 &lt;code class=&quot;language-text&quot;&gt;recordDueDate&lt;/code&gt; 函數了。&lt;/p&gt;
&lt;h2 id=&quot;取捨：評估-var-的缺點&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%8F%96%E6%8D%A8%EF%BC%9A%E8%A9%95%E4%BC%B0-var-%E7%9A%84%E7%BC%BA%E9%BB%9E&quot; aria-label=&quot;取捨：評估 var 的缺點 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;取捨：評估 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的缺點&lt;/h2&gt;
&lt;p&gt;前面說了 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的優點以及它該怎麼跟 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 搭配，這裡來談談 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的缺點。&lt;/p&gt;
&lt;p&gt;先從建議禁用 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的 ESlint 看起。為什麼不用 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt;？&lt;a href=&quot;https://eslint.org/docs/rules/no-var&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ESlint 文件說&lt;/a&gt;，因為在&lt;strong&gt;區塊中&lt;/strong&gt;宣告 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 可能會意外地改變其外同名的變數。注意「區塊中」這三個字。沒錯，這點與我們前面說的一樣，&lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的確不應該宣告在區塊內，那是 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 發揮作用的地方。因此，前述原則已避免掉了這個缺點（Simpson 在&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/apA.md#the-case-for-var&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;這篇&lt;/a&gt;講述了他覺得可以在區塊內宣告 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的狀況，雖然我仍覺得宣告在外頭比較好，但值得看看）。&lt;/p&gt;
&lt;p&gt;再來，應該就是所謂重新宣告（redeclare）的問題。&lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 跟 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt; 將重新宣告的問題澈底消除。但，重新宣告真的是一個嚴重的問題嗎？更確切地問，這個缺點有辦法抵銷甚至壓過前述 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的優點嗎？&lt;/p&gt;
&lt;p&gt;至少我自己是幾乎沒有碰過重新宣告的問題。即便你的函數都寫得很長，但當你很有意識地在區分變數的作用域範圍，要出錯的機率可說是微乎其微。如果你真的很擔心程式壞掉，那你可以將所有用 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 宣告的變數都寫在函數開始處，總不會在這短短幾行中也有同名的問題吧？&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doBigThing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; first &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;watch movies&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; second &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shopping&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; third &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sleep&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; first &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;-- 哦哦抓到了，太明顯了吧！&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// ..&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;同樣地，重新賦值的問題也是如此。想想看，你會在什麼情況下，不小心將一個不該被重新賦值的變數重新賦值？想不太到吧！大多數情況下，都是你想重新賦值，但卻發現這個變數被宣告成 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;，於是你只好回過頭把它改成 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;，再重新賦值。而且，即便你真的不小心犯錯了，會很難察覺嗎？會很難追蹤嗎？會很難改回來嗎？&lt;/p&gt;
&lt;p&gt;我認為這兩個缺點並不足以說服我放棄前面所說的宣告方法。而且相對地，這兩個缺點也給予開發者更大的彈性，只要你謹慎使用（同樣地在&lt;a href=&quot;Simpson&quot;&gt;這篇&lt;/a&gt;，Simpson 簡單示範了重新宣告作為一種註解的功用，雖然我不怎麼喜歡）。&lt;/p&gt;
&lt;h2 id=&quot;什麼時候該用-const？&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E4%BB%80%E9%BA%BC%E6%99%82%E5%80%99%E8%A9%B2%E7%94%A8-const%EF%BC%9F&quot; aria-label=&quot;什麼時候該用 const？ permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;什麼時候該用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;？&lt;/h2&gt;
&lt;p&gt;你可能會發現，在上面範例中，即使我是在區塊內宣告不會再被賦值的變數，我也是用 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;。為什麼不用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;？這樣語義上不是更清晰嗎？&lt;/p&gt;
&lt;p&gt;好吧，其實我也是傾向用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;，但因為我介紹的是 Simpson 的論點，所以在宣告變數的寫法上就把他對 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt; 的意見也一併納入了。&lt;/p&gt;
&lt;p&gt;Simpson 認為，&lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt; 是一個有點讓人困惑的關鍵字：你不是說這個變數是常數（constant）嗎？那為什麼它又可以被修改（mutate）呢？這個誤解的確常在 JS 新手身上發生（至少我在公司面試前端的經驗是如此）。&lt;/p&gt;
&lt;p&gt;那該在什麼時候用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt; 呢？只有當你宣告的變數已經是個不可變（immutable）的值（即基本型別值，詳情可見&lt;a href=&quot;https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch6.md/#primitive-immutability&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;這篇&lt;/a&gt;），而且它在語義上顯然是個常數時，才該用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;POSTS_PAGE_SIZE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 每次要從資料庫取得的文章數量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEGREE_TO_PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_THEME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;dark&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 其內容之後能被修改，不建議用 `const`&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SHARE_LINK_NAMES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;fb&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;line&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;那重新賦值的問題呢？如同上一節所說，這個錯誤很難出現，即使出現也很好察覺並修復。而且當你會用 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 把區塊變數包起來，這個代碼塊通常不會太長，在這短短的十幾行中要出錯實在是不太可能。&lt;/p&gt;
&lt;p&gt;以上是 Simpson 的論點。對我來說，我認同重新賦值的問題並不大，但考量到非 JS 新手應該都能明白 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt; 的「不變」意指為何，我還是會繼續照一般規範走，只是要稍微忍受當看到一個由 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt; 宣告的變數，其內容在之後被修改時所引發的不適感。&lt;/p&gt;
&lt;h2 id=&quot;總結：一個原則，兩個問題&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E7%B8%BD%E7%B5%90%EF%BC%9A%E4%B8%80%E5%80%8B%E5%8E%9F%E5%89%87%EF%BC%8C%E5%85%A9%E5%80%8B%E5%95%8F%E9%A1%8C&quot; aria-label=&quot;總結：一個原則，兩個問題 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;總結：一個原則，兩個問題&lt;/h2&gt;
&lt;p&gt;當我在與別人協作，我會採用由團隊討論出來的代碼規範，或直接引入主流規範（如 &lt;a href=&quot;https://github.com/airbnb/javascript&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Airbnb&lt;/a&gt; 或 &lt;a href=&quot;https://standardjs.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Standard&lt;/a&gt;）。畢竟，我所喜愛的宣告方式，它的好處還無法勝過降低團隊溝通成本所帶來的優勢。但我仍會在程式碼中盡可能地用 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 縮小變數的作用域。&lt;/p&gt;
&lt;p&gt;如果是我獨立開發的專案，當我碰到要宣告變數，我會問自己兩個問題。第一個：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; 這是整個函數內都會使用到的變數嗎？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;是，就用 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 宣告在函數開始處；不是，遵循最小暴露原則，先用 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 括起，再問：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這個變數會被重新賦值嗎？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;會，就用 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt;；不會，就用 &lt;code class=&quot;language-text&quot;&gt;const&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;整個決策過程並不複雜，至少我最近在開發瀏覽器擴充套件 &lt;a href=&quot;https://github.com/yeefun/notion-mark-manager&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Notion+ 標記管理器&lt;/a&gt;，是使用得挺愉快的，除了程式碼變得更易讀，修改起來也較容易。&lt;/p&gt;
&lt;p&gt;你是怎麼決定該用哪個關鍵字宣告變數的呢？你對我上面所說的一切，有什麼看法？歡迎留言給我！&lt;/p&gt;
&lt;p&gt;（有讀者回饋，覺得將作用於整個函數的變數都用 &lt;code class=&quot;language-text&quot;&gt;let&lt;/code&gt; 宣告在函數開頭，並記得將區塊變數都用 &lt;code class=&quot;language-text&quot;&gt;{..}&lt;/code&gt; 括起來，也能達到同樣的效果，並能避免掉 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的缺點。我同意這個看法，如果你真的覺得 &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt; 的缺陷不容忽視，那可以這麼做。總之，本篇最重要的觀念是&lt;strong&gt;最小暴露原則&lt;/strong&gt;，遵守這個原則，其它差異都非大事，只要你有深思熟慮過自己這麼做的原因就好）&lt;/p&gt;
&lt;h2 id=&quot;相關資料&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E7%9B%B8%E9%97%9C%E8%B3%87%E6%96%99&quot; aria-label=&quot;相關資料 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;相關資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/README.md&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;You Don&apos;t Know JS Yet: Scope &amp;#x26; Closures (2nd Edition)&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch6.md&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Chapter 6: Limiting Scope Exposure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/apA.md#the-case-for-var&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Appendix A: Exploring Further - The Case for &lt;code class=&quot;language-text&quot;&gt;var&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[在 Nuxt.js 共用 GraphQL Fragments]]></title><description><![CDATA[GraphQL fragments 是什麼？要怎麼在 Nuxt.js 不同檔案間共用？]]></description><link>https://yeefun.github.io/how-to-use-graphql-fragments-in-nuxt/</link><guid isPermaLink="false">https://yeefun.github.io/how-to-use-graphql-fragments-in-nuxt/</guid><pubDate>Thu, 01 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近公司打 API 的方式換成 &lt;a href=&quot;https://graphql.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GraphQL&lt;/a&gt;，這邊紀錄一下怎麼在 &lt;a href=&quot;https://nuxtjs.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Nuxt.js&lt;/a&gt; 用 GraphQL 的 &lt;a href=&quot;https://graphql.org/learn/queries/#fragments&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;fragments&lt;/a&gt; 特性，來達到重複使用程式碼查詢（query）的效果。&lt;/p&gt;
&lt;h2 id=&quot;fragments-是什麼&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#fragments-%E6%98%AF%E4%BB%80%E9%BA%BC&quot; aria-label=&quot;fragments 是什麼 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Fragments 是什麼&lt;/h2&gt;
&lt;p&gt;fragments 是什麼？簡單來說，就是一個可重用的欄位（fields）片段，你可以在裡頭放好幾個查詢欄位，接著就可以拿這個片段放到不同的查詢物件中。&lt;/p&gt;
&lt;p&gt;最簡單的範例長這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; posts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;rightPosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;postFields&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token attr-name&quot;&gt;leftPosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;postFields&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fragment&lt;/span&gt; &lt;span class=&quot;token fragment function&quot;&gt;postFields&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id
  title
  heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    title
    urlMobileSized
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以 JavaScript 來類比，你可以想像 &lt;code class=&quot;language-text&quot;&gt;postFields&lt;/code&gt; 就是物件，而 &lt;code class=&quot;language-text&quot;&gt;...postFields&lt;/code&gt; 就像是把&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;物件展開（spread）&lt;/a&gt;一樣，能將 &lt;code class=&quot;language-text&quot;&gt;postFields&lt;/code&gt; 內聯到 &lt;code class=&quot;language-text&quot;&gt;rightPosts&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;leftPosts&lt;/code&gt; 物件中，所以最後 &lt;code class=&quot;language-text&quot;&gt;rightPosts&lt;/code&gt; 實際上是長這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; posts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;rightPosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    id
    title
    heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      title
      urlMobileSized
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;情境闡述&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E6%83%85%E5%A2%83%E9%97%A1%E8%BF%B0&quot; aria-label=&quot;情境闡述 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;情境闡述&lt;/h2&gt;
&lt;p&gt;這是我在專案碰到的情況：我需要在網站的不同頁面去取得最新文章，它們的查詢欄位大致相同，但仍有些欄位不是每個查詢都需要的。&lt;/p&gt;
&lt;p&gt;最簡單的做法當然就是每個查詢都寫好自己的欄位。這個方法很快，但缺點就是重複，之後修改共同欄位需要一次改好幾個地方。&lt;/p&gt;
&lt;p&gt;另一個方法是使用指令（&lt;a href=&quot;https://graphql.org/learn/queries/#directives&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;directives&lt;/a&gt;），藉由變數（&lt;a href=&quot;https://graphql.org/learn/queries/#variables&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;variables&lt;/a&gt;）來動態地更改欄位，比如這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; posts&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$shouldQueryTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Boolean&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    id
    title &lt;span class=&quot;token directive function&quot;&gt;@include&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token attr-name&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$shouldQueryTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;若 &lt;code class=&quot;language-text&quot;&gt;shouldQueryTitle&lt;/code&gt; 為 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;，就會查詢 &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt; 這個欄位，反之則不會。這個方法可行，但若需要客製化的欄位不少，實作起來會相當麻煩。&lt;/p&gt;
&lt;p&gt;fragments 能怎麼解決這個問題呢？很簡單，看共用欄位有哪些，把它們抽取成 fragment，再用 &lt;code class=&quot;language-text&quot;&gt;...&lt;/code&gt; 內聯。&lt;/p&gt;
&lt;p&gt;這個方法還有一個好處，就是它能&lt;strong&gt;內聯嵌套查詢&lt;/strong&gt;，而非在第一層就整個覆蓋。比方說，下面這塊程式碼，&lt;code class=&quot;language-text&quot;&gt;allPosts&lt;/code&gt; 有兩個 &lt;code class=&quot;language-text&quot;&gt;heroImage&lt;/code&gt; 欄位，但裡頭查詢的欄位並不同：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; posts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;postFields&lt;/span&gt;
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;      urlTabletSized
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fragment&lt;/span&gt; &lt;span class=&quot;token fragment function&quot;&gt;postFields&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id
  title
&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;  heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;    title
    urlMobileSized
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最後，&lt;code class=&quot;language-text&quot;&gt;heroImage&lt;/code&gt; 實際上是長這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  title
  urlMobileSized
  urlTabletSized
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;這可以讓我們在享受共用欄位的同時，還能客製化出不同的查詢內容。很方便吧！&lt;/p&gt;
&lt;h2 id=&quot;在多個檔案間共用-fragments&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%9C%A8%E5%A4%9A%E5%80%8B%E6%AA%94%E6%A1%88%E9%96%93%E5%85%B1%E7%94%A8-fragments&quot; aria-label=&quot;在多個檔案間共用 fragments permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;在多個檔案間共用 Fragments&lt;/h2&gt;
&lt;p&gt;如果你是在 Nuxt 中使用 GraphQL，可以參考 &lt;a href=&quot;https://github.com/nuxt-community/apollo-module&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nuxtjs/apollo&lt;/a&gt; 這個套件，它裡頭包著 &lt;a href=&quot;https://github.com/vuejs/vue-apollo&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;vue-apollo&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;要創建一個查詢，可以&lt;a href=&quot;https://apollo.vuejs.org/guide/apollo/queries.html#simple-query&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;利用 &lt;code class=&quot;language-text&quot;&gt;gql&lt;/code&gt; 標籤直接寫在 &lt;code class=&quot;language-text&quot;&gt;apollo&lt;/code&gt; 物件裡&lt;/a&gt;，也可以&lt;a href=&quot;https://github.com/apollographql/graphql-tag#importing-graphql-files&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;另開一個 &lt;code class=&quot;language-text&quot;&gt;.graphql&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;.gql&lt;/code&gt; 檔&lt;/a&gt;。我偏好後者，因為比較好管理。&lt;/p&gt;
&lt;p&gt;回到 fragments。若是用檔案管理的方法，fragments 一般來說會放在使用它的檔案中。但如果我要在多個檔案間共用一個 fragment，那該怎麼做？&lt;/p&gt;
&lt;p&gt;具體情境是這樣的：我有好幾個不同的查詢物件，它們都會去查詢 &lt;code class=&quot;language-text&quot;&gt;heroImage&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ogImage&lt;/code&gt;，而這兩個欄位也都是物件，會去查詢 tiny、mobile、tablet、desktop、original 這五種尺寸的圖片。每次查詢都要寫這 5 * 2 個欄位，實在是有點麻煩——有沒有辦法把它們抽取成一個 fragment 呢？&lt;/p&gt;
&lt;p&gt;在單一檔案中很簡單，就直接放在下面：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; latestPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;latestPosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    id
    title
    heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;urls&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    ogImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;urls&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fragment&lt;/span&gt; &lt;span class=&quot;token fragment function&quot;&gt;urls&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  urlTinySized
  urlMobileSized
  urlTabletSized
  urlDesktopSized
  urlOriginal
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但若要在多個檔案間共用，可以把這個 fragment 移入一個獨立的檔案。假設檔案結構長這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;apollo
├─fragments
   └─image-urls.gql &amp;lt;-- `urls` fragment is here!
└─queries
   ├─editor-choices.gql
   ├─collaborations.gql
   └─posts.gql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;要怎麼把 &lt;code class=&quot;language-text&quot;&gt;image-urls.gql&lt;/code&gt; 導入到查詢檔案中使用呢？你可能會想說直接 &lt;code class=&quot;language-text&quot;&gt;import&lt;/code&gt;，但電腦會噴錯，因為 &lt;code class=&quot;language-text&quot;&gt;import&lt;/code&gt; 是 JS 語法，GraphQL 無法辨識。&lt;/p&gt;
&lt;p&gt;那該怎麼辦？經過我一番搜索，其實你可以這麼做：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;graphql&quot;&gt;&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#import &apos;../fragments/image-urls.gql&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; latestPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;latestPosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; allPosts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    id
    title
    heroImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;urls&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    ogImage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;urls&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;只要在 &lt;code class=&quot;language-text&quot;&gt;import&lt;/code&gt; 前加上 &lt;code class=&quot;language-text&quot;&gt;#&lt;/code&gt; 變成註解，就能成功把 fragments 導進來囉（我猜這應該是 &lt;a href=&quot;https://github.com/creditkarma/graphql-loader&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;graphql-loader&lt;/a&gt; 的功勞，但詳細怎麼做的就不清楚了）。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[區塊鏈溯源能否打擊假照片？紐約時報架了一個社群網站來驗證這件事]]></title><description><![CDATA[透過區塊鏈技術揭露新聞更動源流，來讓大眾更好地區辨訊息真假或好壞——紐約時報發起的「新聞溯源計畫」究竟想做什麼？]]></description><link>https://yeefun.github.io/what-is-the-news-provenance-project/</link><guid isPermaLink="false">https://yeefun.github.io/what-is-the-news-provenance-project/</guid><pubDate>Tue, 04 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;向假新聞宣戰早已不是什麼新鮮事了。獨立檢證、協力查核、智能辨識⋯⋯無論手段新穎或傳統，在假訊息竄增的速度面前，似乎都顯得蒼白無力，一不小心還會惹禍上身。這不禁讓人想問：還有什麼辦法？難道真理終究戰不勝謊言？人類還有救嗎？&lt;/p&gt;
&lt;p&gt;如果你已經對於當今所有防堵假訊息的手法都感到失望，準備當個極端的懷疑論者（這也不失為一個辦法），那你也許可以稍停一下，看看我底下將要介紹的計畫，也許它能帶給你一絲希望。&lt;/p&gt;
&lt;p&gt;去年 7 月，媒體界龍頭紐約時報（The New York Times，以下簡稱紐時）發起&lt;a href=&quot;https://www.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;「新聞溯源計畫」（The News Provenance Project）&lt;/a&gt;，期待透過區塊鏈技術揭露新聞更動源流，來讓大眾更好地區辨訊息真假或好壞。其矛頭第一個對準的就是假照片。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;為什麼是假照片？區塊鏈在這整個計畫中的作用是什麼？「揭露新聞更動源流」是什麼意思？它能怎樣幫助我們辨別資訊？要達成這個目標，需要完成哪些事？&lt;/strong&gt;以下將一一回答。&lt;/p&gt;
&lt;h2 id=&quot;假照片破壞力強&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%81%87%E7%85%A7%E7%89%87%E7%A0%B4%E5%A3%9E%E5%8A%9B%E5%BC%B7&quot; aria-label=&quot;假照片破壞力強 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;假照片破壞力強&lt;/h2&gt;
&lt;p&gt;為什麼選擇假照片？理由似乎很直觀：照片比文字更容易傳播，且具有更高的可信度，因此破壞力也就愈強。據&lt;a href=&quot;https://www.niemanlab.org/2020/01/heres-how-the-new-york-times-tested-blockchain-to-help-you-identify-faked-photos-on-your-timeline/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;美國獨立智庫研究&lt;/a&gt;顯示，相較於其它形式的假訊息，美國人民對於大眾辨識假圖片或影片的信心和責任都更少。換言之，他們既覺得&lt;strong&gt;大眾沒有能力辨識，也沒有義務辨識&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;低落的防衛意識，使得假照片有大行其道的空間。隨著&lt;a href=&quot;https://opinion.cw.com.tw/blog/profile/51/article/8316&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;「深假」（deepfake）技術的進步&lt;/a&gt;，製造出高水準視覺效果的成本愈來愈低。如何在資訊生態系統崩壞之前，從結構上防制假照片的危害，顯然是當務之急。&lt;/p&gt;
&lt;h2 id=&quot;區塊鏈如何溯源&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E5%8D%80%E5%A1%8A%E9%8F%88%E5%A6%82%E4%BD%95%E6%BA%AF%E6%BA%90&quot; aria-label=&quot;區塊鏈如何溯源 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;區塊鏈如何溯源&lt;/h2&gt;
&lt;p&gt;從結構上防制？紐時究竟想怎麼做？最初的想法，是為照片加上一些&lt;strong&gt;元數據（metadata）及編輯紀錄&lt;/strong&gt;。前者如拍攝時間、地點、發布者等；後者則可想像有一條時間軸，上面記錄著誰在何時取用、發布，並做了哪些更動。&lt;/p&gt;
&lt;p&gt;舉例來說，假設阿帆今天在巴西拍到一張火噬雨林的照片，公布到臉書，並寫下：「天啊，地球之肺在燃燒！貪婪的人類終自食惡果！」阿昱看到了，順手把它載入自己的電腦。四個月後，澳洲森林發生大火，阿昱上傳這張照片，文字卻改為：「極端氣候正在吞噬澳洲！人類何時才要醒悟！」&lt;/p&gt;
&lt;p&gt;此時悠遊於臉書河道的你，猝不及防地滑到這則貼文。怵目驚心的照片使你的手指停了下來，各種情緒頓時湧上心頭——擔憂、憤怒、恐懼、羞愧——「這麼嚴重的事情，大家應該要知道啊」，你一邊想著，一邊按下分享。&lt;/p&gt;
&lt;p&gt;「欸？這是什麼？」你看到浮在照片下方的訊息區塊，裡頭有一行被黃色標起的文字：「四個月前拍攝」。&lt;/p&gt;
&lt;p&gt;「四個月前？什麼鬼？這不是現在正在發生的事嗎？」你點開區塊，看見這張照片最初拍攝的時間、地點，以及發布者（阿帆）當時的貼文內容。&lt;/p&gt;
&lt;p&gt;「殺小，該不會這是假照片吧？」你愈想愈不對勁，決定先將剛才分享貼文的隱私權限設為「只限本人」，並上網搜尋「澳洲大火」。最終你也許會發現：澳洲是真的有發生大火，但那張照片也是錯的。你不願做假訊息的幫兇，於是刪了貼文。&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1f0cc767f732d707bf90f711048f9e0c/0d0e4/macron-twitter.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.40677966101696%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAADlUlEQVQoz2P4+v3HlcuXX71+c//R4wePntx78Ojhk2c3Xn198+HLm/efXr//9Pbjl09ffwDZL958AJJff/z68+//tx+/vv34yfD63YeOusp7t2+dv3Tt9LkL5y9dPnvh0pnr9y5cvnbj9r0rN25dvXH79r0HV67fun7z7q2796/euHXr9r0nz14CDWEAmvH+4+dnL16ePnvhzPmLp86ev3jl2plz54GMsxcunzl/6er1m0BxoFkXLl8FCp44febUmXO3797//PUbw/efv3/8/vv+wycg//qNm1fvP7vx/OPN55+uP/986/mH9+/fv33/Aeipd++A9McPHz99+Pj50+cvb96+B/oXaPPPf//+H794Jby0Piyr0Cslv3TyitkrNk6es6B3+tyFExs2b92xfteR2UvXzV22dueJa6uO3Fqy50Lf6kOX7z1n+Pr95////zcdOS3qGChp6aHqERVdO6mqqaWypqagpKSxuWnJijUrt+yvnboio77HuniCRnqHVmqLRGDRmn2nGIChB9S87+x1g/hK9dB864SS8Myi+Kysuoa6stKCpu6Jk6ZO7e7tauyZ4Jtbz6vjwSlpxCtlyMQmvXz5eqjm/eduOOZ2BdTNjWtZkFg3xSkuN78gr6mto6x1QkN7b3trXVljW2BuE5+IlgiPohiPPAcD34rla6DO3r7/iH9BW0HPosLK5tKOOYkVnX1dbf2Tp+Q0T26fvmjeoiWTZi+t6Jhu7OHDqyQpLCrNzsC5fPkqqObNO3c7plUltc33C/UNi48sKCpduHj59o07V8xb3dLVm1ZfHl6TaxHjLeugw60vJqAmzsbJsWIFSPMPoOZ1G9fbBkUmNvS5JcSYhLo4xXhlNlWmN1YGFsYYRdoJ+igz24uymghzqgrzygsLSAuz8LAtX7kSqnnLjp3KWqr6rnZ6joZiJrK8huI8xiKslqLMliJc5lIirmqCVvL8prICRtICyqLC8mIcPBwrVsFsXrthPQMDAxMXEyM7I4MoE4MMC6M6F4M2D6M2N4MOF7MpH4MuN4MyJ4MaB4MYK5MQK1DxksVLGL7/+PX7z7/bd+43trQ0dbZ19na39XQ29XfUTm9vmNLRuqC3aWZ349yu6smtLbN62yZ1t0zoap/Y3dLWdvPmbYYv339+/PL9y/df3379BSZVoCv+/f//8+//z19///z1B8j98/f/95//Pn75+Q8sBcpTYPDz9x8AyyQfENvMdAUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;去年 8 月亞馬遜森林大火，便有許多錯誤照片在網路上瘋傳，連法國總統馬克宏都搞錯：他所分享的照片至少攝於 16 年前。圖擷自[馬克宏推特](https://twitter.com/EmmanuelMacron/status/1164617008962527232?s=20)&quot;
        title=&quot;去年 8 月亞馬遜森林大火，便有許多錯誤照片在網路上瘋傳，連法國總統馬克宏都搞錯：他所分享的照片至少攝於 16 年前。圖擷自[馬克宏推特](https://twitter.com/EmmanuelMacron/status/1164617008962527232?s=20)&quot;
        src=&quot;/static/1f0cc767f732d707bf90f711048f9e0c/fcda8/macron-twitter.png&quot;
        srcset=&quot;/static/1f0cc767f732d707bf90f711048f9e0c/fcda8/macron-twitter.png 590w,
/static/1f0cc767f732d707bf90f711048f9e0c/c83ae/macron-twitter.png 1180w,
/static/1f0cc767f732d707bf90f711048f9e0c/0d0e4/macron-twitter.png 1230w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;去年 8 月亞馬遜森林大火，便有許多錯誤照片在網路上瘋傳，連法國總統馬克宏都搞錯：他所分享的照片至少攝於 16 年前。圖擷自&lt;a href=&quot;https://twitter.com/EmmanuelMacron/status/1164617008962527232?s=20&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;馬克宏推特&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;謝天謝地！一場錯誤內容被轉發上萬次的災難被阻止了。&lt;strong&gt;它靠的不是事後通知，也非事前審查，而是在訊息產生（或發布）當下，便在某處烙下一條不可更改的紀錄。它不做判斷，只呈現每次編輯的事實，讓讀者根據這些紀錄，自行決定要多相信這則訊息。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「聽起來真是不錯，」你可能會問：「但我要怎麼知道這些紀錄沒有被惡意更改過？」這就是區塊鏈發揮作用的地方了。區塊鏈是一種記錄資料的技術，一旦資料「上鏈」，便&lt;strong&gt;難以被竄改&lt;/strong&gt;；同時它也是&lt;strong&gt;透明&lt;/strong&gt;的，任何更改都會被記錄，且可被看見。它能確保照片元數據和編輯紀錄的真實性。&lt;/p&gt;
&lt;h2 id=&quot;概念驗證&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E6%A6%82%E5%BF%B5%E9%A9%97%E8%AD%89&quot; aria-label=&quot;概念驗證 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;概念驗證&lt;/h2&gt;
&lt;p&gt;一個看似簡單的概念，實作起來往往有許多複雜微妙之處。&lt;/p&gt;
&lt;p&gt;比方說，我們知道這個計畫要在照片旁邊附上一些資訊，但要放哪些資訊？更深入點問：&lt;strong&gt;平常人們在社群媒體上是如何決定一張照片可不可信的？不同背景的人，在判斷資訊方面又有什麼差異？我們所設想的方案，是否真的能幫助民眾更好地辨識新聞品質？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;為了回答上述關鍵問題，紐時找了 34 位政治傾向、媒體偏好各異的成年人進行深度訪談和原型測試，從中歸納出了幾個值得參考的洞見。&lt;/p&gt;
&lt;p&gt;首先，關於民眾在社群平台上如何接收、判斷新聞資訊，紐時從「對於貼文脈絡的意識程度」（只看最顯眼的照片和標題？還是也會看發文日期、來源或網友反應？）和「對於主流媒體的信任程度」這兩條軸線，將民眾分成四種類型（來看看你比較偏向哪一種類型吧）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;高意識、低信任——不信任媒體的懷疑者（Distrustful news skeptic）&lt;/strong&gt;：認為主流媒體都是有偏見的，並會試圖從一些線索去證明，很難與特定媒體建立信任關係。值得注意的是，這些人多半是質疑媒體的報導方式，而非事實細節。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高意識、高信任——充滿自信的數位訂戶（Confident digital news subscriber）&lt;/strong&gt;：悠游於網路世界，樂意辨識自己信任的媒體所提供的訊息真假。他們不想與時事脫節或遭到誤導。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低意識、低信任——討厭媒體的庶民（Media-jaded localist）&lt;/strong&gt;：覺得自己的聲音遭到主流媒體所排擠，毫不遲疑地接受非正式的激昂評論（hot takes）。他們希望新聞能夠既真實又接地氣。當自己信任的非正式來源提供錯誤訊息時，他們需要更清晰的線索才能判定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低意識、高信任——傳統的數位移民（Late-adopter media traditionalist）&lt;/strong&gt;：相較於網路媒體的喧囂，他們更喜歡透過電視、報紙等舊媒體來接收資訊。這類人需要學習更多的判斷技巧及明確的提示，才有辦法自己區辨訊息是否可信。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;紐時認為，照片溯源計畫至少能讓信任媒體的人受益（即 2、4 類人）。接著，他們測試多種能表示照片來源的設計，發現：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;認證標章（如臉書或 Instagram 的藍勾勾）並不足以讓照片變得可信。&lt;/li&gt;
&lt;li&gt;關於標示術語，讀者喜愛「附有來源」（sourced）更勝於「已驗證」（verified）。這代表大眾不只想知道照片已被他人驗證，&lt;strong&gt;也希望有可讓自己探索的資訊&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;陳列同一個事件的多張照片，能使讀者相信事件確實發生了。&lt;/li&gt;
&lt;li&gt;強調為人熟知的照片元數據（如圖說、發布歷史），而非會造成混亂的術語（如加密、存儲於不可更動的資料庫）。&lt;/li&gt;
&lt;li&gt;展示監督與問責的機制，有助於提升讀者信心。&lt;/li&gt;
&lt;li&gt;只需清楚呈現照片的編輯歷史，避免使用可能會失準的詞彙，如「真實」、「假」，&lt;strong&gt;因為只要出錯一次，讀者就不會再完全信任這套機制&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;紐時將這些發現整合進一個模擬社群網站的原型，經過兩輪測試，確認了來源資訊確實有效，而且即使是質疑新聞立場的讀者，也沒有懷疑來源資訊的真實性。&lt;/p&gt;
&lt;p&gt;不過仍有要改善的地方，包括有許多人根本沒注意到錯誤訊息、讀者很難理解為什麼來源訊息與照片描述不符。最有趣的是，對於照片編輯的歷史紀錄，讀者並不怎麼感興趣；反之，他們想瞭解照片所描述事件的相關新聞。這引出了一個洞見：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;民眾上社群網站不是為了查核事實，而是為了娛樂和建立聯繫。他們之所以願意停下來考察更多資訊，不是因為他們想證明這張照片是錯的，而是因為他們被與照片相連的故事引起了興趣。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;紐時認知到，這種傾向雖造成民眾容易為網路圖片所騙，但他們也能利用這點，讓溯源的過程不只有驗證的功能，還可以幫助讀者對自己關心的事情形成更準確、細緻的認識。&lt;/p&gt;
&lt;p&gt;吸取以上教訓，最終紐時建出了這個&lt;a href=&quot;https://poc.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;概念原型&lt;/a&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;來源資訊區塊變大了，裡頭增加了一些可引人注意的細節（如照片拍攝距今已多久）。&lt;/li&gt;
&lt;li&gt;展開資訊區塊，你可以看見上面有一塊醒目的提示，它能喚起讀者對於假照片的警戒。&lt;/li&gt;
&lt;li&gt;區塊下方列出關於同一事件的其它照片與文章連結，供讀者進一步探索。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/191e1690a5e7a0b0d4cea66aa80eb18d/0d0e4/poc-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 105.08474576271185%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAACXBIWXMAAAsSAAALEgHS3X78AAAEWUlEQVQ4y4VUe0yTVxTv3F/DjJciUGRzMDpLVwoykEeBQqFUXjNGMA5WWh5GpDjfU1EXIWMsMS4bjEXYBBnEtYgrUEpbGIQFdKPUxQm0tGa40RelT7D9vj6+dvcT8I9ly05Ofvndm/u7j3POPRgIghxO1NzAEBTA8B/u3EKny7XhLrcbgmAMmJVIn2i1eqPRpNWumM1W7/8Z2MNmhx2wAwOj6FyzrmmBdEW3qtebjUbD6orZaLCYTRazYd1qsq1ZrOZVxAVvyT3A4Q0xGNnskMFotTx3WGxOk82pM8N6E6TTQ6oVSGVyPVt1KXVOhc4j03rnVJ7ZPzxzy15UDN4MxL/NSju+ap0Qjz9VmRc1a5I5zfiUjMeb5gqWObPeb6eRGyJP/YD3eI/naDuS0ogcbUHskANjt9uBWMzrq6ImdF9mCVuvDLQ1d3z6ydCdjv7b7SLRzMRjZETu4T72fjOJfC7yXOJ5GLc9V/o9INAYyG4DYslgV1uhT0cJtr047E5V7DlaQju7SNrTMCUWCkYeCCcVY4sOwVNvtxT5csJ9aQD5YvyFGIZQsXG2U9b06mJz4O/1AVN1vhwG9iZtR1d1yvlqNvNI1THGSRbzs+/uySb+9AiXvLd+QXokyIuAQei1DY+4g+wQRbO/8JjPfFPweO2On8/4T7C3N9KiKpPj6TGkeFJeZm7TR9cfdo5px565Z3Re6OWbJ4c4Z+lRrcz97WXE/uNR/HOE4cvEwfOEAVZII+0txr7I9zOyabln0rKuhhPqUw938qaWQbFsiqcF909l4y4UJTeUpLcUx7YUx906iGs7SPi6IPJGVuj1QlLVoYJDhYezsxlxqRd2x179oI7rcGyJf+X3XKTu/Dg3/BottIke1pCDbTwQfu3Anot5b5/M31uSHZ2TTkhPeZdCfi8jg0KMI7OqT6PlabOhYi73Lg4XmpmKp6fh6WRcXto7+RRCfhaJRiHmZsbnURMyyaTUJFJSYkzy/hj83ojS0iMw/FLMuYvHhZGT8OkpBEoKPictmpZBpGfG0igkahoxi0xMToxOSiTG7yOQSNERkW+WlZXCL05GU9XV1bltGyZop29wkB82xC8MG/BGeGDEnl27sQEhwb67gl7393stMMDHzw/49lcwGCo1A03Vxsm9vb0FRUUVlZXlTBaTVfFhOZNRzgSEwWQBUs6sqDlRW8uuA1Bzgl3KrGHXnbJaLCBgaG0PDw8/kj7RaPUa7Yparf1rWa1SaZdVGkAAarQ6i9W29twJwW7YidgcXv6wUKPRbEb7Xn/fdLd6Sa6WyeeVCqVSoZDL5Qvz87KFBeAK5dK8dPTh/dMP+s9qlD/BDldfH0etVm/+KolkhvNDn1gkFowI+HxwD4FQKERRJBSPisWjY2IRf2SwZ+jH70UCHhjz+fz19fXN/4wgiMFgBHkHFzEYTCaTBXCAoPP8Vz9xONDyhMA6wEBjAhsBDghoNIBvzIBi+Fe32e1/AzpoXX5hHt1iAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;概念原型：展開前。圖擷自[新聞溯源概念原型網頁](https://poc.newsprovenanceproject.com/)&quot;
        title=&quot;概念原型：展開前。圖擷自[新聞溯源概念原型網頁](https://poc.newsprovenanceproject.com/)&quot;
        src=&quot;/static/191e1690a5e7a0b0d4cea66aa80eb18d/fcda8/poc-1.png&quot;
        srcset=&quot;/static/191e1690a5e7a0b0d4cea66aa80eb18d/fcda8/poc-1.png 590w,
/static/191e1690a5e7a0b0d4cea66aa80eb18d/c83ae/poc-1.png 1180w,
/static/191e1690a5e7a0b0d4cea66aa80eb18d/0d0e4/poc-1.png 1230w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;概念原型：展開前。圖擷自&lt;a href=&quot;https://poc.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;新聞溯源概念原型網頁&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/78cc90cb492d690d33aae3b16fd64206/0d0e4/poc-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 126.10169491525424%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAIAAAC+dZmEAAAACXBIWXMAAAsSAAALEgHS3X78AAAEh0lEQVQ4y3VUXWxTZRiuXMiFLrIhEgY4QxYlIzIMidERNsMY4sh+cOIgsgs0XBgvjYl6A9HAlV55wQ0yXLfVubrunNF2Y5ERliWuW9uNruv6/39OT7ue/7+e9vT4no5l1eCbJ2++Nt9z3vd5vvf7DIIgapo2NmY6+ubB1pamtlPH2lqOdrQ2fdh2/PwHzefamttbj59tbW55r+n9d98+efLYOyeaGhsbBgauSrJsEAQByEND93e/+ML+/Xvq62sPH9rT8HrdkTf2vtV44EjDq4cP1tYfeGXf3pde21dTV/dybW3Nrl2Gjo52WS4YtiobjcaWU6d7LvZ1917U0aPn3o/7unp6u7p7u2F1qf+TTy9fAvRf6ezq+/yL6yzLGkRRAvLU1NT8k8fRaDARD8WiwUjYH40E4GcsGoiEYBEiyU2GoRiapKnNbJ43TyAYhgFZr4yiD6iAkQr+EVubSa5PJ7z2mMcW99gS3mlsw57xW9NeFPehybXJlBfRqAU7OpLCMs8qoyiaiy8VaB9D6KAyPjKzQeIbedzP5XwyuSaRXpn0Svk1gCaFbQ/M6TT+jIwgKE1ToqIRdDnHaIKsCgIniCLYyQtiuVzWtmNrbZ+eTqXT25Wn0PUZisop+CaLZ9lNkqFpmqSoPElSNM1VguXAI5aiKNg/PTOzQ0ZQJPmUS8Uwp2vJ41ldWXG7XO7FxUWXy+V0Ot1u9zLEksPh+BvH0jq5ujKCIPBVHM84HED2OoHqdgcCgXA4DNnv9wdDkVA0HYlnonHsOWSaYkAhQeD5zVwuR4AFpVKpWImSWlYkkk3MkmFUyK3qmu32Ks0oCmLAC4ZXOEkFw6RCsagohYICSSmqMp9jY1YyZBZzK88hkxUneLFIs3KO5ERZ0StvAYqXSkpJ42WN4WQIm82WTmM7bTOM7jCO47Fo1O/zJZNJ6EU3mWVpBhTxksBXFGVhrqxWazUZhSOBVimuSPMluQDVFAAULKklFaKs/u8564bRdEESaIrkeJEVRHBpZzDUslbWiqqmqBp87b9uWyYRRaTNC9K3d4nbt3/5+ZtrzvmZ0JLV8QidX6cnPFowX34UL/wVkX/1qlMJzWafTleRJ4syNz6XutJ/7ce+Q3cGapyzRsuNj+707P6yt/3q1/f8hBwgmNkFz9CTwBIOhtmryBYLiPtt8G7n2RNfXb/wWf+Zhw+tP9z4vv/C6cu9ZzrPn3Ovro6Y79366ebNW9+h6LDNbv+X27rmQiGT3SRyJA8HXS5TNINlsmmckCQJTjuVSuBYMhGPgvMz1bMN50xks2BqxeGiqpZgd+UuMJIkqrr3MDTwv1qqGFlpe/sxMJvN6JRt1eNbcnkdzrVKfgp52eV1rqwvu70Ah9OzvhFOYQRO5EdMY2lsu+25ubnBwft/TiBDI5ZhEzL8OzpssgyNTgKMJh1DoxbjKGIas0xMWMbHJ4zGYZgcAzyCQAZV/kAQOsdxLBQMhSNhmKVIOAwjJYq8wHP62wDgOZ7Xp06pjL1BlCTQqN8csKgSoEq/R2UNklr1hlQHbIN6/wCpILhrQ8BlzAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;概念原型：照片概觀。圖擷自[新聞溯源概念原型網頁](https://poc.newsprovenanceproject.com/)&quot;
        title=&quot;概念原型：照片概觀。圖擷自[新聞溯源概念原型網頁](https://poc.newsprovenanceproject.com/)&quot;
        src=&quot;/static/78cc90cb492d690d33aae3b16fd64206/fcda8/poc-2.png&quot;
        srcset=&quot;/static/78cc90cb492d690d33aae3b16fd64206/fcda8/poc-2.png 590w,
/static/78cc90cb492d690d33aae3b16fd64206/c83ae/poc-2.png 1180w,
/static/78cc90cb492d690d33aae3b16fd64206/0d0e4/poc-2.png 1230w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;概念原型：照片概觀。圖擷自&lt;a href=&quot;https://poc.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;新聞溯源概念原型網頁&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f58348e4363a5d89f7608be67216f9b0/0d0e4/poc-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.59322033898305%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABp0lEQVQoz3WRXVPaQBSG/f//oR2VltEh2CEgcCVFsTeC9Ko3LViEfIHZkGS/N5vTkwanVcczz7xzdnfefffjiHMBAK7biaNfpQxltkY09RUNNQtRESv+UfAAbDy47Hief1SbO66b7BNtbJqLjAomCy8Gn8AmhiABW0IJz5QoMBgMPc87mHsDdzkl4gmYpDmljLM8p3meZ1m2T/eMMaiqNMZIqV6buz03WJFCgpCCCyHkc6lCKiu1LQrQBRS21Ma8Se67q3kiCSYzzMVsxnBFClVyCRUKl94zdy9X4U9lqVZGKaWNRsHGGKsNVBSVKgMGz/D62G5vEy2FYcGW72IWPgmScs6ZkJYKQJiAnKOWuOkbc9cNt8nShyCKgyjBPojILk4JIXm2h/+qfu3h8IW5u93uMmYXj2S53i9WBHnYZJQro/Gah8LrSFW9dr/fr/+Z46DlOC3ny930+/h2htx8m48n0/FkNrqZXd/ef53g5P3V9d10/mPxsP796Dc+NX0/OCRfjUYfPh6fty5OP7drGs1KT/42jWY9f9E8a7ec9tm5c3xymqbpH0JbUgyGRrNnAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;概念原型：照片歷史紀錄。圖擷自[新聞溯源概念原型網頁](https://poc.newsprovenanceproject.com/)&quot;
        title=&quot;概念原型：照片歷史紀錄。圖擷自[新聞溯源概念原型網頁](https://poc.newsprovenanceproject.com/)&quot;
        src=&quot;/static/f58348e4363a5d89f7608be67216f9b0/fcda8/poc-3.png&quot;
        srcset=&quot;/static/f58348e4363a5d89f7608be67216f9b0/fcda8/poc-3.png 590w,
/static/f58348e4363a5d89f7608be67216f9b0/c83ae/poc-3.png 1180w,
/static/f58348e4363a5d89f7608be67216f9b0/0d0e4/poc-3.png 1230w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;概念原型：照片歷史紀錄。圖擷自&lt;a href=&quot;https://poc.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;新聞溯源概念原型網頁&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;大家可以自己點進&lt;a href=&quot;https://poc.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;網頁&lt;/a&gt;玩玩，看這樣的來源標示是否有助於你判斷貼文內容的真假或好壞。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;這是個令人振奮的開始——但也只是開始。目前的原型僅僅是最末端的呈現，要達到預期中的效果，紐時需要跨界合作，&lt;strong&gt;建立一套從拍攝到發布的體系&lt;/strong&gt;，且其運作流程還要好上手，以免排擠較小的新聞機構。這顯然是一項極為艱難的任務。&lt;/p&gt;
&lt;p&gt;「當前的工作是確保大家以一致的方式來紀錄元數據，並讓這些資料能被其它組織（如社群平台）負責任地使用。」紐時研發團隊執行長 Marc Lavallee 說。他們在&lt;a href=&quot;https://www.newsprovenanceproject.com/whats-next&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;官網&lt;/a&gt;公開徵求三種合作夥伴——社群平台、新聞編輯室、事實查核組織——來一起把計畫推展到下一階段：執行。&lt;/p&gt;
&lt;p&gt;一個更好的網際網路會是什麼樣子？這是紐時對於這個大哉問的初步回答。即使它最終失敗了，整個設計過程——從問題定義、用戶研究、概念發想到原型測試——仍有許多值得我們深思和借鏡的地方。&lt;/p&gt;
&lt;h2 id=&quot;相關資料&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%E7%9B%B8%E9%97%9C%E8%B3%87%E6%96%99&quot; aria-label=&quot;相關資料 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;相關資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;The News Provenance Project&lt;/a&gt;——新聞溯源計畫官網，裡頭將計畫目前的成果與潛力做了一個清楚且頗具啟發性的闡述，推薦你把它從頭到尾看完。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://poc.newsprovenanceproject.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Proof of Concept&lt;/a&gt;——新聞溯源的概念原型，來玩看看吧！&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://open.nytimes.com/how-do-people-decide-whether-to-trust-a-photo-on-social-media-e0016b6080ae&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;How Do People Decide Whether to Trust a Photo on Social Media?&lt;/a&gt;——社群媒體上的讀者是怎麼判斷一張照片可不可信的？這是紐時訪談後的發現。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://open.nytimes.com/what-if-every-news-photo-on-social-media-showed-contextual-information-8936cf4e8c45&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;What If Every News Photo on Social Media Showed Contextual Information?&lt;/a&gt;——紐時測試原型的過程。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://open.nytimes.com/can-publishers-use-metadata-to-regain-the-publics-trust-in-visual-journalism-ee32707c5662&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Can Publishers Use Metadata to Regain the Public’s Trust in Visual Journalism?&lt;/a&gt;——簡單描述了整個新聞溯源計畫及未來展望。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.niemanlab.org/2020/01/heres-how-the-new-york-times-tested-blockchain-to-help-you-identify-faked-photos-on-your-timeline/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Here’s how The New York Times tested blockchain to help you identify faked photos on your timeline&lt;/a&gt;——尼曼新聞實驗室對這個計畫的報導。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://open.nytimes.com/introducing-the-news-provenance-project-723dbaf07c44&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Introducing the News Provenance Project&lt;/a&gt;——去年 7 月，紐時在 Medium 上介紹了這個計畫。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>