<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>石頭閒語 :: Programming</title>
    <description>遊手好閒的石頭成的部落格 The Source Awakens! Make the source to be with you.</description>
    <link>https://www.rocksaying.tw</link>
    <atom:link href="https://www.rocksaying.tw/categories/programming.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>網頁UI元件 - LimitedCheckbox，複選但有限項目的表單元件</title>
        <description>&lt;p&gt;當網頁表單需要提供使用者可複選的選項時，我們會用 &lt;em&gt;Select&lt;/em&gt; (配上 multiple) 或 &lt;em&gt;Checkbox&lt;/em&gt; 元件。
但若想進一步限制可勾選的項目數量，例如五項中最多選三項又或是最少選兩項，則目前的內建行為做不到。
我們要自己寫程式控制。&lt;/p&gt;

&lt;p&gt;我根據我碰到的需求情境，設計了一個允許使用者複選但有限項目的表單元件。
因為這個元件是基於 &lt;em&gt;Checkbox&lt;/em&gt; ，所以取名 &lt;em&gt;LimitedCheckbox&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;我不用 &lt;em&gt;Select&lt;/em&gt; 設計這個複選元件的原因，在於 &lt;em&gt;Select&lt;/em&gt; 需要配合鍵盤按住 ctrl 或 shift 才可複選。
但現在很多人不知道這種鍵盤搭配滑鼠的操作技巧。隨著成長於手機世代的使用者愈來愈多，不知道的人會更多。
而 &lt;em&gt;Checkbox&lt;/em&gt; 的操作方式則符合現代使用者的「日常經驗」，所以選它。&lt;/p&gt;

&lt;!--more--&gt;

&lt;h4 id=&quot;limitedcheckbox-的設計與運作&quot;&gt;LimitedCheckbox 的設計與運作&lt;/h4&gt;

&lt;p&gt;首先，要將 &lt;em&gt;Checkbox&lt;/em&gt; 包進 &lt;em&gt;Fieldset&lt;/em&gt; 。或說用 &lt;em&gt;Fieldset&lt;/em&gt; 劃分 &lt;em&gt;Checkbox&lt;/em&gt; 群組。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Select&lt;/em&gt; 元件會將選項包起來形成群組。但 &lt;em&gt;Checkbox&lt;/em&gt; 元件本身是獨立的，基本上各不相干。
若要滿足可以五項中最多選三項，又或是最少選二項此類設計需求，我就需要把原本獨立的 &lt;em&gt;Checkbox&lt;/em&gt; 分組。
分了組，我的程式才能做出「這五個是同組，限制最多選三個」或者「這幾個是一組，至少要勾兩個」等等判斷。
我用 &lt;em&gt;Fieldset&lt;/em&gt; 做這件事。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fieldset 用 id 對應 Checkbox 的 name。並自定 &lt;em&gt;maxlenth&lt;/em&gt; 和 &lt;em&gt;minlength&lt;/em&gt; 兩個屬性。
&lt;em&gt;maxlength&lt;/em&gt; 定義最多勾選項目的限制條件。&lt;em&gt;minlength&lt;/em&gt; 定義最少勾選項目的限制條件。兩個限制條件可同時存在。&lt;/li&gt;
  &lt;li&gt;呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.initial()&lt;/code&gt; 初始化。它會傾聽 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;document.change&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;document.submit&lt;/code&gt; 事件。&lt;/li&gt;
  &lt;li&gt;當使用者勾選項目後，呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.updateStatus()&lt;/code&gt; 檢查 &lt;em&gt;maxlength&lt;/em&gt; 限制。
它根據 Fieldset 的 &lt;em&gt;maxlength&lt;/em&gt; 決定同組 Checkbox 元件的狀態。
當已勾選的項目數量等於 &lt;em&gt;maxlength&lt;/em&gt; 時，就變更未勾選 Checkbox 屬性為 &lt;em&gt;disabled&lt;/em&gt;，讓使用者不能勾選更多項目。&lt;/li&gt;
  &lt;li&gt;使用者送出表單時，呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.checkValidity()&lt;/code&gt; 檢查 &lt;em&gt;minlength&lt;/em&gt; 限制。
若已勾選的項目數量少於 &lt;em&gt;minlength&lt;/em&gt; 便中斷送出動作，並觸發 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid&lt;/code&gt; 事件。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LimitedCheckbox 的源碼: &lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/ui/limited-checkbox.js&quot;&gt;limited-checkbox.js&lt;/a&gt;」。&lt;/p&gt;

&lt;h4 id=&quot;html-範例&quot;&gt;HTML 範例&lt;/h4&gt;

&lt;p&gt;說到 &lt;em&gt;Fieldset&lt;/em&gt;，這是一個幾乎被人遺忘的東西。
但它確實是 HTML 規範中，用於表單元素分組的東西。
然而它除了一個框起來的視覺效果之外便沒有其他用處，致使它長期被遺忘。&lt;/p&gt;

&lt;p&gt;例如我一開始問 Copilot AI 給個可多選項目的範例時，連 Copilot 都沒有用 &lt;em&gt;Fieldset&lt;/em&gt; 分組。
並且 Copilot 把所有複選限制條件都直接寫在 JavaScript 程式內，而不是透過元件屬性注入程式。
這使得程式碼難以重用。&lt;/p&gt;

&lt;p&gt;我思考之後，決定用 &lt;em&gt;Fieldset&lt;/em&gt; 分組，並把複選限制條件綁在 Fieldset 的屬性上。
我的程式去讀 Fieldset 的屬性，就可得知每個組別的最多可勾項目和最少可勾項目的限制條件。&lt;/p&gt;

&lt;p&gt;按照上述設計要點， LimitedCheckbox 的 HTML 範例如下。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;fieldset&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組[]&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;maxlength=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;A組：最多三項&lt;span class=&quot;nt&quot;&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組[]&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A001&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;form=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;A項目1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目2&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組[]&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A002&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;form=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目2&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;A項目2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目3&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組[]&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A003&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;form=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;A項目3&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目4&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組[]&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A004&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;form=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目4&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;A項目4&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目5&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組[]&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A005&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;form=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A組-項目5&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;A項目5&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;更多範例請見 &lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/sample/limited-checkbox1.html&quot;&gt;範例網頁源碼&lt;/a&gt;」。&lt;/p&gt;

&lt;div class=&quot;note&quot;&gt;
我的後端是 PHP。回送給 PHP 的表單項目若是複數資料，那麼的 name 的內容必須後綴 `[]` 才會解析成包含複數資料的陣列。
&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/images/raw/main/2025/06-19-limited-checkbox-1.png&quot; alt=&quot;範例圖&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;javascript-的部份&quot;&gt;JavaScript 的部份&lt;/h4&gt;

&lt;p&gt;網頁載入後，需要呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.initial()&lt;/code&gt; 啟用 LimitedCheckbox。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.initial()&lt;/code&gt; 有兩個可選的回呼函數參數，讓設計者處理不合限制時的外觀效果。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;valid_callback - 當一組複選元件的勾選結果符合限制時，呼叫此函數。&lt;/li&gt;
  &lt;li&gt;invalid_callback - 當一組複選元件的勾選結果不符合限制時，呼叫此函數。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;這兩個參數對應屬性 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.validCallback&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.invalidCallback&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;基本上，只需要寫一行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LimitedCheckbox.initial()&lt;/code&gt; 就行了。
但實際上，還是會指定 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;validCallback&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidCallback&lt;/code&gt; 處理外觀效果。
畢竟我們不能讓使用者看不出為什麼表單送不出去。&lt;/p&gt;

&lt;p&gt;啟用 LimitedCheckbox 的範例如下:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;LimitedCheckbox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;valid_set&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;valid_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;classList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;bg-warning&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;invalid_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;invalid_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;classList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;bg-warning&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// LimitedCheckbox.validCallback =   valid_set   =&amp;gt; valid_set.querySelector(&apos;legend&apos;).classList.remove(&apos;bg-warning&apos;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// LimitedCheckbox.invalidCallback = invalid_set =&amp;gt; invalid_set.querySelector(&apos;legend&apos;).classList.add(&apos;bg-warning&apos;);&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid event&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不合限制時，將複選表單元件組的標題 (legend) 背景色變為 bg-warning。合於限制時，移除背景色。&lt;/p&gt;

&lt;p&gt;不指定 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidCallback&lt;/code&gt; 時，也可以透過 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid&lt;/code&gt; 事件處理不合限制的狀況。&lt;/p&gt;

&lt;p&gt;雖然實作中出現了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;checkValidity()&lt;/code&gt; 方法和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid&lt;/code&gt; 事件，但我並沒有實作整套 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation&quot;&gt;Constraint Validation API&lt;/a&gt;。
也還不想按照 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components&quot;&gt;Web Component&lt;/a&gt; 訂一個新的網頁元件。&lt;/p&gt;

&lt;p&gt;因為整套弄起來太複雜，超出我需要的功能。
我用不到的功能，就不會使用測試。不測試，就不能保證這些功能的程式碼的可用性。
那還是別寫了。&lt;/p&gt;

&lt;h5 id=&quot;相關資源&quot;&gt;相關資源&lt;/h5&gt;

&lt;p&gt;LimitedCheckbox 儲放在我的 &lt;a href=&quot;https://github.com/shirock/non-jquery-ui&quot;&gt;non-jquery-ui 源碼庫&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/ui/limited-checkbox.js&quot;&gt;limited-checkbox.js&lt;/a&gt;」。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/sample/limited-checkbox1.html&quot;&gt;本文範例網頁源碼&lt;/a&gt;」。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;API 參考:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/fieldset&quot;&gt;fieldset: The Field Set element&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility&quot;&gt;Element: checkVisibility() method&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent/message&quot;&gt;ErrorEvent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 18 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2025/JS-UI-LimitedCheckbox.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2025/JS-UI-LimitedCheckbox.html</guid>
      </item>
    
      <item>
        <title>網頁UI元件 - 如何使用新式UI Dialog 和 Modal</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot; title=&quot;&amp;lt;dialog&amp;gt;: The Dialog element&quot;&gt;Dialog&lt;/a&gt; 是 WHATWG 規範的 HTML 對話框元件。只要各家瀏覽器按照規範內容實作，就可以保證在不同瀏覽器與前端工具之間仍有相同的行為。
按 Mozilla MDN 文件所述，在 2022 年 3 月之後的瀏覽器皆已實作此元件。
在此之前，這類 UI 元件是由前端工具 - 如 bootstrap - 利用 CSS 和 JavaScript 自行設計。但不同前端工具定義的對話框行為並不相同。&lt;/p&gt;

&lt;p&gt;注意，本文使用「對話框」一詞時，是一般概念。使用 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot; title=&quot;&amp;lt;dialog&amp;gt;: The Dialog element&quot;&gt;Dialog&lt;/a&gt; 一詞時，專指 HTML 規範的對話框元件。&lt;/p&gt;

&lt;p&gt;專案開發者手上的舊專案仍可繼續使用前端工具自定的對話框元件。
但新建專案，則建議使用 Dialog 元件。因為它有標準化的好處。
A 前端工具的對話框元件和 B 前端工具的對話框元件一定不同，如果你們中途變更前端工具，那就要修改對話框元件的程式碼。
而 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot; title=&quot;&amp;lt;dialog&amp;gt;: The Dialog element&quot;&gt;Dialog&lt;/a&gt; 不與前端工具綁定，不受前端工具更迭影響。&lt;/p&gt;

&lt;!--more--&gt;

&lt;h4 id=&quot;dialog-標準行為&quot;&gt;Dialog 標準行為&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;具有方法: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showModal()&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;。
事件: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close&lt;/code&gt;。
屬性: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;returnValue&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;預設不顯現，即屬性 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; 為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;有兩種行為型態: 一般型態和互動型態 (Modal)。
這兩種行為區別是由顯現方法決定。呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show()&lt;/code&gt; 方法顯現時，為一般型態；呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showModal()&lt;/code&gt; 方法則為互動型態。&lt;/li&gt;
  &lt;li&gt;一般型態只會浮現在畫面上層，但不阻礙使用者和其他元件互動，使用者可以忽視它。&lt;/li&gt;
  &lt;li&gt;互動型態 (Modal) 則強制使用者必須與其互動，不能忽視它。
互動型態 Dialog 顯現時會阻擋使用者和其他元件互動，使用者必須優先處理 Dialog 內容並關閉它。&lt;/li&gt;
  &lt;li&gt;一般型態 Dialog 顯現時只會水平置中於可視區域。而互動型態 Dialog 則是水平與垂直置中。&lt;/li&gt;
  &lt;li&gt;Dialog 可透過其內部表單 (Form) 關閉。
透過表單關閉時，觸發 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close&lt;/code&gt; 事件，並改變 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;returnValue&lt;/code&gt; 屬性之值。&lt;/li&gt;
  &lt;li&gt;有標準的鍵盤互動方式。互動型態 Dialog 可以按鍵盤的 Esc 鍵關閉它。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;基本設計組合&quot;&gt;基本設計組合&lt;/h4&gt;

&lt;p&gt;Dialog 的 HTML 基本內容通常搭配一個觸發其顯現的按鈕。
其內部則安排一個表單與使用者互動，例如讓使用者選擇動作。
內部表單的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;method&lt;/code&gt; 應為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dialog&lt;/code&gt;，安排至少一個呈送按鈕 (submit)。
點擊呈送按鈕後關閉對話框，依呈送按鈕的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt; 設定 Dialog 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;returnValue&lt;/code&gt; 屬性，並觸發 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close&lt;/code&gt; 事件。&lt;/p&gt;

&lt;p&gt;範例如下:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;document.getElementById(&apos;myDialog1&apos;).show()&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    Open Dialog
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myDialog1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onclose=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alert(`點擊 ${this.returnValue}`)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        Hello Dialog!
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dialog&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Yes&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
                是
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;No&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
                否
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;dialog-外觀&quot;&gt;Dialog 外觀&lt;/h4&gt;

&lt;p&gt;設計者可用 CSS 宣告 Dialog 外觀。主要設計 Dialog 的視窗寬度和邊框樣式。
瀏覽器會按 HTML 規範將 Dialog 置中，所以不必宣告 Dialog 位置。&lt;/p&gt;

&lt;p&gt;對於互動型態 Dialog，則有虛擬元素 &lt;em&gt;backdrop&lt;/em&gt; 可設計背景外觀 (被其阻擋的畫面區域)。
通常我們會宣告背景色和透明度。&lt;/p&gt;

&lt;p&gt;CSS 範例如下:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;dialog&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;25%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#fd7e14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1.5rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::backdrop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;/* backdrop-filter: blur(1px); */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;外觀如下圖:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/images/raw/main/2025/06-14-HTML_Dialog-1.png&quot; alt=&quot;範例圖&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;打造自己的-dialog&quot;&gt;打造自己的 Dialog&lt;/h4&gt;

&lt;p&gt;接下來我要結合 HTML 元件自定屬性和 JavaScript 配合，簡化 dialog 的程式編寫工作。&lt;/p&gt;

&lt;p&gt;自定屬性:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;rock-dialog: 對應 Dialog 的 id。點擊有此屬性的元件便呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show()&lt;/code&gt; 顯現指定的 Dialog。&lt;/li&gt;
  &lt;li&gt;rock-dialog-modal: 若有此屬性，則呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showModal()&lt;/code&gt; 顯現互動型態 Dialog。&lt;/li&gt;
  &lt;li&gt;rock-dialog-message: 此值將傳給 Dialog，為其顯示的訊息內容。&lt;/li&gt;
  &lt;li&gt;rock-dialog-label: 如果 Dialog 內部有一個連結元件 (anchor/a)，此屬性將是該連結的顯示文字。&lt;/li&gt;
  &lt;li&gt;rock-dialog-action: 如果 Dialog 內部有一個連結元件 (anchor/a)，此屬性將是該連結的跳轉 URL。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;自定類別:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;dialog-close: 點擊任何位在 Dialog 內部且有 dialog-close 類別的元件，都可關閉其所在的 Dialog。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下列為實作這些內容的 JavaScript 程式碼:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;confirmDialogChangeContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.dialog-body&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerHTML&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;decodeURIComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rock-dialog-message&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;link_btn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;nx&quot;&gt;link_btn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rock-dialog-label&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;link_btn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rock-dialog-action&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 點擊任何具有 rock-dialog 屬性的元件，都可顯現指定的 Dialog&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 若點擊的元件同時帶有 rock-dialog-modal 屬性， Dialog 將以 modal 模式顯現。&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dialogId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rock-dialog&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dialogId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dialogId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;nx&quot;&gt;confirmDialogChangeContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rock-dialog-modal&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;showModal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 點擊任何具有 dialog-close 類別的元件，都可關閉其所在的 Dialog&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// PS. 這個元件必須位在 Dialog 內部&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;classList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dialog-close&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;closest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以下為自訂內容的使用範例。此範例也用了 bootstrap5 設計元件外觀。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rock-dialog=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myDialog1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-primary&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Open Dialog&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rock-dialog=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myDialog1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rock-dialog-modal&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-primary&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Open Modal&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;myDialog1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;這是對話框。&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;利用內部表單的 submit 按鈕關閉。表單的 method 必須指定 &quot;dialog&quot;。&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;d-flex justify-content-center gap-2 m-3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dialog&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-primary&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;關閉&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rock-dialog=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;confirmDialog&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rock-dialog-modal&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-warning&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;rock-dialog-message=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;前往石頭閒語&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;rock-dialog-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;跳吧&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;rock-dialog-action=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://rocksaying.tw/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    跳到石頭閒語
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;confirmDialog&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dialog-body&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        $message
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;d-flex justify-content-center gap-2 m-3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;$action&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-primary&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            $label
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-secondary dialog-close&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            關閉(close())
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dialog&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-secondary&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
                關閉(form)
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此例的執行效果如下圖:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/images/raw/main/2025/06-14-HTML_Dialog-2.png&quot; alt=&quot;範例圖&quot; /&gt;&lt;/p&gt;

&lt;p&gt;專案開發者手上的舊專案仍可繼續使用前端工具自定的對話框元件。
但新建專案，則建議使用 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot; title=&quot;&amp;lt;dialog&amp;gt;: The Dialog element&quot;&gt;Dialog&lt;/a&gt; 元件。因為它有標準化的好處。不與前端工具綁定，不受前端工具更迭影響。&lt;/p&gt;

</description>
        <pubDate>Sat, 14 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2025/HTML_Dialog.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2025/HTML_Dialog.html</guid>
      </item>
    
      <item>
        <title>網頁UI元件 - NumberInputElement，自訂增減值按鈕，以及用滑鼠滾輪修改數字</title>
        <description>&lt;p&gt;當設計師碰到限定輸入數值的輸入欄位時，可以用 &lt;em&gt;NumberInputElement&lt;/em&gt; 網頁 UI 元件讓輸入框呈現更美觀的增加數值按鈕與減少數值按鈕。這是目前流行的數值輸入形式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/images/raw/main/2025/02-23-number-input-element-1.png&quot; alt=&quot;範例圖&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NumberInputElement&lt;/em&gt; 元件具備以下特性：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;只綁定型態為 &lt;em&gt;number&lt;/em&gt; 的 &lt;em&gt;input&lt;/em&gt; 控制項 (input type=”number”)。&lt;/li&gt;
  &lt;li&gt;加入使用滑鼠滾輪改變數值的行為。上滾增值，下滾減值。&lt;/li&gt;
  &lt;li&gt;擴充 &lt;em&gt;label&lt;/em&gt; 控制項的行為，使其具備對關聯控制項的增值行為或減值行為。
  使用 &lt;em&gt;type&lt;/em&gt; 屬性定義點擊 &lt;em&gt;label&lt;/em&gt; 時的行為， &lt;em&gt;inc&lt;/em&gt; 表示增值，&lt;em&gt;dec&lt;/em&gt; 表示減值。&lt;/li&gt;
  &lt;li&gt;若不想用 &lt;em&gt;label&lt;/em&gt; 改變 &lt;em&gt;input&lt;/em&gt; 控制項的值 ，也可自行定義控制項的選擇器。
  例如用 &lt;em&gt;button&lt;/em&gt; 控制項處理增值或減值。&lt;/li&gt;
  &lt;li&gt;增值與減值行為都會參考 &lt;em&gt;input&lt;/em&gt; 控制項的 &lt;em&gt;max&lt;/em&gt;, &lt;em&gt;min&lt;/em&gt;, &lt;em&gt;step&lt;/em&gt; 三項標準屬性。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;這個元件不會主動在 input 控制項旁邊增加控制按鈕，而是交給設計師決定。
如果設計師沒有放上代表增值或減值的按鈕／控制項，將只有滑鼠滾輪的擴充行為生效。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NumberInputElement&lt;/em&gt; 儲放在我的 &lt;a href=&quot;https://github.com/shirock/non-jquery-ui&quot;&gt;non-jquery-ui 源碼庫&lt;/a&gt;: 「&lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/ui/number-input-element.js&quot;&gt;取得 NumberInputElement&lt;/a&gt;」。&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;沒想到時至今日還有機會寫這種網頁 UI 。我本來以為是我目前用的前端框架太輕量簡化，所以沒有這種 UI。但網路上搜了一遍，才發現功能更複雜的前端框架也經常忽視這種 UI 的使用需求。設計師往往要自己額外去找 UI 元件拼湊。&lt;/p&gt;

&lt;p&gt;先說明一點，在手機或平板等觸控式操作環境中，瀏覽器已經針對數值型態的 input 控制項提供明顯特化的輸入介面，讓使用者輕鬆輸入數字。所以在這類環境下，不需要使用這個 UI 元件。但這個元件的擴充內容也不會干涉內建行為。&lt;/p&gt;

&lt;p&gt;在 PC 環境下，瀏覽器似乎就沒有那麼用心了。像 Edge 或 Firefox 的電腦版碰到數值型態的 input 控制項，只是在輸入框邊緣加上兩個很小的上下箭頭 (如範例圖所示)，讓只用滑鼠的使用者點擊箭頭調整數值。但這兩個小箭頭實在小到很難點擊，所以主流的設計方式就是自己加兩個更大的按鈕來做這件事。&lt;/p&gt;

&lt;p&gt;喔，對了。針對鍵盤使用者，瀏覽器電腦版還為數值型態 input 控制項加了按上下方向鍵調整數值的操作行為。既然可以按方向鍵調整數值，那是不是也該讓滑鼠滾輪這麼做？&lt;/p&gt;

&lt;p&gt;NumberInputElement 元件就要滿足上述兩項需求。&lt;/p&gt;

&lt;h4 id=&quot;滑鼠滾輪調整輸入數值&quot;&gt;滑鼠滾輪調整輸入數值&lt;/h4&gt;

&lt;p&gt;首先對應觸控式操作環境使用上下滑動調整數值的操作習慣，必須加上滾動滑鼠滾輪調整數值的操作行為。這一點只需要傾聴代表滑鼠滾輪滾動的 wheel 事件，然後根據 deltaY 判斷滾輪是上滾或下滾調整數值。上滾增值，下滾減值。程式碼實作內容摘錄於下：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input[type=&quot;number&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;wheel&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;wheelHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;wheelHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;delta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deltaY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;delta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// console.log(&apos;wheel down&apos;);&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dec&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// console.log(&apos;wheel up&apos;);&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;inc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;增加數值按鈕與減少數值按鈕&quot;&gt;增加數值按鈕與減少數值按鈕&lt;/h4&gt;

&lt;p&gt;至於增加兩個大按鈕分別負責增值與減值的部份，我的設計考量是讓設計師決定按鈕外觀和位置，而不是 NumberInputElement 自己生成按鈕。設計師只要讓 NumberInputElement 知道是哪些按鈕，然後 NumberInputElement 負責綁定增值和減值的擴充行為。&lt;/p&gt;

&lt;p&gt;為了讓 UI 行為一致，我決定用 label 作為增值按鈕與減值按鈕的基底，而不用 button。因為增值按鈕與減值按鈕要像內建的兩個小箭頭一樣關聯 input 控制項，而且點擊時要改變輸入焦點到 input 控制項(輸入框內)，而不是把焦點放在按鈕上。label 本身就具有這些特性。&lt;/p&gt;

&lt;p&gt;Label 的 for 標準屬性可以指定關聯控制項。我再另外添加 type 屬性指定 label 負責的調整行為。若 type 為 inc 表示增值，dec 表示減值。&lt;/p&gt;

&lt;p&gt;具體來說，就是像下列的 HTML 碼：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;數值欄位: &lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➕&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;max=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;min=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-15&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Dec&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➖&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;這個 HTML 就是範例圖呈現的第一組數值型態輸入元件。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/images/raw/main/2025/02-23-number-input-element-1.png&quot; alt=&quot;範例圖&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一個 label 沒有添加 type 屬性，所以它只觸發原本的行為，亦即改變輸入焦點到輸入框。&lt;/p&gt;

&lt;p&gt;第二個 label 添加了 type=”inc” 屬性，點擊時除了改變輸入焦點，還會增加數值一步。第三個 label 添加了 type=”dec” 屬性，所以點擊就減少數值一步。一步的量由 input 的標準屬性 step 決定，預設一步為 1 。
程式碼實作內容摘錄於下：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input[type=&quot;number&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;labels&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`label[for=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;]`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 擴展 label 控制項的行為(若其具有 type 屬性)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// label 的 click 事件觸發兩次 (在 mousedown 與 mouseup 後各一次)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 所以看 mouseup ，不看 click&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mouseup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;labelHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;labelHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;labelType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;control&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 只有 label 控制項有此屬性&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;labelType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;labelType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;focus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不論是 label 的 for 還是 input 的 step, min, max 等屬性，都是 HTML 規範的標準屬性。我的設計思路都是按照 HTML 規範原則擴充操作行為，從而保持 UI 行為一致。&lt;/p&gt;

&lt;h4 id=&quot;控制項的事件傳遞&quot;&gt;控制項的事件傳遞&lt;/h4&gt;

&lt;p&gt;在瀏覽器的原始環境下，操作者手動點擊輸入控制項旁的小箭頭改變數值的動作，將會觸發控制項的 &lt;em&gt;input&lt;/em&gt; 事件。
前端設計師經常會傾聴這個事件，以便處理使用者的輸入內容。&lt;/p&gt;

&lt;p&gt;然而透過程式手段改變數值的動作，亦即用 JavaScript 程式碼設置控制項的 value 屬性，則不會觸發控制項的 input 事件。
這使前端設計師佈置的 input 事件處理方法不起作用。
從擴展元件行為並保持行為一致性的設計原則來看，這算是 BUG 。
所以當 NumberInputElement 擴展的操作行為改變控制項的 value 屬性時，也應擲出該控制項的 input 事件，讓其他傾聽此控制項事件的人可以接著處理數值改變的事。&lt;/p&gt;

&lt;p&gt;要做到這件事，首先需要產生一個 &lt;em&gt;Event&lt;/em&gt; 實例，其事件值為 ‘input’。
然後將它交給控制項的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchEvent()&lt;/code&gt; 方法，即可派出 input 事件，讓其他處理函數接著做事。
程式碼實作內容摘錄於下：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;act&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;computedValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatchEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bubbles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;開始使用&quot;&gt;開始使用&lt;/h4&gt;

&lt;p&gt;上述實作內容都已整合在 &lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/ui/number-input-element.js&quot;&gt;NumberInputElement&lt;/a&gt;。當設計師寫好 HTML 碼之後，只需載入 number-input-element.js ，然後呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.initial();&lt;/code&gt; 就可生效。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➕&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt;  &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;max=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;min=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-15&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Dec&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➖&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number-input-element.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;NumberInputElement 在 Firefox 和 MS Edge 瀏覽器電腦版內完成開發及測試工作。
本文範例沒有套用任何 CSS，所以 label, input 或 button 控制項都是預設外觀。設計師應自己套用 bootstrap 這些 CSS 工具，美化外觀。&lt;/p&gt;

&lt;p&gt;基本上，我的設計目標到此就實現了。不過說到自訂點擊行為，大家通常都是用 button。所以我又為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.initial()&lt;/code&gt; 加入 labelSelector 函數參數，讓設計師自己決定綁定增值行為與減值行為的控制項。&lt;/p&gt;

&lt;h4 id=&quot;自訂選擇器&quot;&gt;自訂選擇器&lt;/h4&gt;

&lt;p&gt;下例混用 label 和 button 設計增值按鈕與減值按鈕。為了突顯自訂性，我還特意讓兩組數值輸入控制項的按鈕位置不一樣。第一組把兩個按鈕分放兩邊，第二組則都放右側。&lt;/p&gt;

&lt;p&gt;本例為了讓作為增值按鈕與減值按鈕的 button 控制項被選中，在呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.initial()&lt;/code&gt; 時，傳入了自訂選擇器的 callback 函數參數。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;數值欄位: &lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➕&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;max=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;min=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-15&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Dec&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➖&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;br/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input2&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input2&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dec&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➖&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input2&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➕&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number-input-element.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`[for=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;]`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果設計師不想用 &lt;em&gt;for&lt;/em&gt; 屬性指定關聯控制項，而想用別的屬性也行。
下例就是用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.relatedAttribute&lt;/code&gt; 屬性指定用 &lt;em&gt;target&lt;/em&gt; 屬性名稱。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input3&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dec&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➖&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input3&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;➕&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number-input-element.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;relatedAttribute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;NumberInputElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`button[target=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;]`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;整合前端框架&quot;&gt;整合前端框架&lt;/h4&gt;

&lt;p&gt;目前前端框架的主流設計方式，不是分別在控制項上綁處理函數，而是直接在 &lt;em&gt;document&lt;/em&gt; 綁定處理函數，然後在處理函數內根據事件主體判斷分流。&lt;/p&gt;

&lt;p&gt;如果設計師要把 NumberInputElement 整合到採用這種設計原則的框架內，則不要呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.initial()&lt;/code&gt;，而須分別使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.wheelHandler()&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberInputElement.labelHandler()&lt;/code&gt;。&lt;/p&gt;

&lt;h5 id=&quot;html-規範參考&quot;&gt;HTML 規範參考&lt;/h5&gt;

&lt;p&gt;NumberInputElement 儲放在我的 &lt;a href=&quot;https://github.com/shirock/non-jquery-ui&quot;&gt;non-jquery-ui 源碼庫&lt;/a&gt;: 「&lt;a href=&quot;https://github.com/shirock/non-jquery-ui/blob/master/ui/number-input-element.js&quot;&gt;取得 NumberInputElement&lt;/a&gt;」。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement&quot;&gt;input type=”number”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement&quot;&gt;label&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent&quot;&gt;mouse wheel event&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event&quot;&gt;click event&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 23 Feb 2025 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2025/JS-UI-NumberInputElement.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2025/JS-UI-NumberInputElement.html</guid>
      </item>
    
      <item>
        <title>PHP框架 - CommonGateway - 用 URL 重寫規則隱藏 CGF</title>
        <description>&lt;p&gt;Common Gateway Framework (CGF) 設計的所有服務 (RESTful API 或網頁) 都是透過主程式 &lt;em&gt;index.php&lt;/em&gt;  轉發使用者的請求給控制項。
因此正常的 URL 總是會包含 index.php。&lt;/p&gt;

&lt;p&gt;但有些客戶並不希望在 URL 中看到 index.php 這個字眼。
他們可能想要改善 SEO 的搜尋結果，又或是不想暴露低層的運作軟體。
我們可以利用 URL 改寫規則實現此目的。&lt;/p&gt;

&lt;div class=&quot;note&quot;&gt;
有些功能龐大的框架會內建一套 URL 規則，例如 Laravel。
這類框架請照它們的規則來做。
&lt;/div&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;修改主程式檔名&quot;&gt;修改主程式檔名&lt;/h2&gt;

&lt;p&gt;改變 URL 結構形式最簡單的方式是修改主程式檔名。&lt;/p&gt;

&lt;p&gt;CGF 的 URL 結構形式正常如下:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://your_server/api/index.php/Item
https://your_server/shop/index.php/Item/123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你可以按你的喜好修改 index.php 檔名，這不會影響 CGF 運作。
例如你可以改名為 router.php，或者是 main.php。此時你的服務 URL 形式就變成:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://your_server/api/router.php/Item
https://your_server/shop/main.php/Item/123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;若想更進一步隱藏 CGF 主程式，不讓使用者看到 URL 內容中有 index.php 或 main.php。
則需要借助網站服務程式的 URL 重寫規則 (rewrite rule)。&lt;/p&gt;

&lt;h2 id=&quot;url-重寫規則&quot;&gt;URL 重寫規則&lt;/h2&gt;

&lt;p&gt;這個方法不僅適用 CGF，也適用其他框架。
例如 CodeIgniter 框架也用 URL 重寫規則移除 URL 中的 index.php: &lt;a href=&quot;https://codeigniter.com/user_guide/general/urls.html#urls-remove-index-php-apache&quot;&gt;CodeIgniter URLs&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;常見的網站服務程式，如 Apache, Nginx 甚至 IIS，都提供了 URL 重寫規則 (rewrite rule) 功能。
但每家的用法不一樣。底下以 Apache 2.4 為例說明 URL 重寫規則。&lt;/p&gt;

&lt;p&gt;舉例來說，你想將 RESTful API 的 URL 結構變成 https://your_server/api/Item 。&lt;/p&gt;

&lt;p&gt;你可以選擇將 URL 重寫規則寫在 .htaccess。重寫規則如下:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# .htaccess
RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule &quot;(.*)&quot; &quot;index.php/$1&quot; [PT,L]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;.htaccess 應該放在 CGF 程式根目錄，和 index.php 在一起。
而且你的 &lt;em&gt;Directory&lt;/em&gt; 組態至少要設 &lt;em&gt;AllowOverride FileInfo&lt;/em&gt; 才能使 .htaccess 生效。&lt;/p&gt;

&lt;p&gt;這個 URL 重寫規則使用 PT (Pass through) 旗標， URL 只會在伺服器內部轉換。使用者端不會改變。&lt;/p&gt;

&lt;p&gt;.htaccess 用法僅應用於軟體開發與測試階段。
基於執行效率的考量，正式運作階段建議將 URL 重寫規則寫在 &lt;em&gt;VirtualHost&lt;/em&gt; 或 &lt;em&gt;Directory&lt;/em&gt; 區段中。 
只是寫在 &lt;em&gt;VirtualHost&lt;/em&gt; 或 &lt;em&gt;Directory&lt;/em&gt; 區段時，要再考慮目錄位置。
例如:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;Directory&amp;gt;
    RewriteEngine on

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule &quot;^api/(.*)&quot; &quot;api/index.php/$1&quot; [PT,L]
&amp;lt;/Directory&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;若你的程式只提供 RESTful API，那你做到這一步就夠了。
但若你是用 CGF 設計網站，那你還需要在 Apache 組態中加入環境變數 &lt;em&gt;CGF_REQUEST_ROOT&lt;/em&gt;，指示 URL 重寫規則的根點 (root)。&lt;/p&gt;

&lt;h2 id=&quot;cgf_request_root&quot;&gt;CGF_REQUEST_ROOT&lt;/h2&gt;

&lt;p&gt;當你用 CGF 設計網站時，勢必要在網頁中提供連結指向不同功能網頁(控制項)。
CGF 為此提供了函數 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\cg\html\request_url()&lt;/code&gt; 產生控制項的正確 URL。
但這個函數是按正常規則產生 URL。也就是 https://your_server/api/index.php/Item 或 https://your_server/shop/main.php/Item/123 這種形式。&lt;/p&gt;

&lt;p&gt;當你使用 URL 重寫規則後，就會分出使用者看到的外顯 URL 和伺服器內部轉換後的真實 URL。
你必須用環境變數 &lt;em&gt;CGF_REQUEST_ROOT&lt;/em&gt; 讓 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;request_url()&lt;/code&gt; 知道外顯 URL 的根點，它才能產生控制項的外顯 URL。
未指定 CGF_REQUEST_ROOT 時，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cg\request_url()&lt;/code&gt; 將產生真實 URL，而不是你想要的。&lt;/p&gt;

&lt;p&gt;如果你的外顯 URL 結構像是 https://your_server/shop/Item/123 ，則應設 CGF_REQUEST_ROOT 為 &lt;em&gt;/shop&lt;/em&gt; 。&lt;/p&gt;

&lt;p&gt;Apache2:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SetEnv CGF_REQUEST_ROOT /shop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nginx:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fastcgi_param CGF_REQUEST_ROOT /shop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;設計說明&quot;&gt;設計說明&lt;/h2&gt;

&lt;p&gt;CGF 的設計原則是「零組態」。我希望能夠從現有資源中找出 URL 的 root。&lt;/p&gt;

&lt;p&gt;使用 apache2 時，符合 rewrite 規則的結果會加上 $_SERVER[‘REDIRECT_URL’] 項目。
但 nginx 時沒這項目。
查了 CGI 規範，並沒有 REDIRECT_URL。所以這是 Apache2 特有項目。&lt;/p&gt;

&lt;p&gt;轉念一想，既然 rewrite 規則必須寫在 web server 的組態中，
那乾脆順便在 web server 組態裡加一個自定環境變數 CGF_REQUEST_ROOT，指定 URL 的 root。
PHP 執行時會將環境變數的內容放入 $_SERVER，那麼 CGF 就可以從外部得到這個參數。
至少不用修改 index.php 本身。&lt;/p&gt;

&lt;h6 id=&quot;相關文章&quot;&gt;相關文章&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/shirock/common-gateway-framework/blob/main/doc/url-rewrite.md&quot;&gt;CommonGateway 功能索引: URL rewrite&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/21320836.html&quot;&gt;CommonGateway 初步&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2021/CommonGateway_HTML%E5%85%AC%E7%94%A8%E5%87%BD%E6%95%B8%E8%88%87%E9%A0%90%E8%A8%AD%E9%A6%96%E9%A0%81.html&quot;&gt;CommonGateway HTML公用函數與預設首頁&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 30 Nov 2024 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2024/CommonGateway_url_rewrite.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2024/CommonGateway_url_rewrite.html</guid>
      </item>
    
      <item>
        <title>學習 ECMAScript/JavaScript 6 - Module 在瀏覽器環境的使用說明</title>
        <description>&lt;p&gt;JavaScript 的 &lt;em&gt;module (模組)&lt;/em&gt; 概念，整體上與其他程式語言沒有差別。
但是 JavaScript 主流用途是作為瀏覽器的嵌入式語言，在瀏覽器環境使用 module 就帶來一些特殊的問題。
此為本文重點。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2023/ES6_Module-in-Browser.html#module-特性&quot;&gt;module 特性&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2023/ES6_Module-in-Browser.html#script-type-的影響&quot;&gt;script type 的影響&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2023/ES6_Module-in-Browser.html#避免名稱衝突&quot;&gt;避免名稱衝突&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;本文不會詳細介紹 &lt;em&gt;module (模組)&lt;/em&gt; 功能的程式語法。語法細節推薦看 Mozilla 開發者文件的「&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&quot; title=&quot;MDN - Modules Guide&quot;&gt;Modules Guide&lt;/a&gt;」。&lt;/p&gt;

&lt;!--more--&gt;

&lt;h3 id=&quot;module-特性&quot;&gt;module 特性&lt;/h3&gt;

&lt;p&gt;首先，&lt;em&gt;module (模組)&lt;/em&gt; 視為獨立的活動空間 (space or scope) 。所以本文會一直用「&lt;em&gt;模組空間&lt;/em&gt;」這個稱呼。&lt;/p&gt;

&lt;p&gt;JavaScript 的模組空間具有下列特性:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;每一個 js 檔案都視為獨立的模組空間。&lt;/li&gt;
  &lt;li&gt;在 HTML 內的每一段 &lt;em&gt;script type=”module”&lt;/em&gt; 標籤區塊，也視為獨立的模組空間。&lt;/li&gt;
  &lt;li&gt;模組空間的內容預設不公開 (private)。&lt;/li&gt;
  &lt;li&gt;變數、函式、類別、迭代器等內容，都可以用 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export&quot; title=&quot;export statement&quot;&gt;export&lt;/a&gt; 敘述宣告公開給外界使用 (public)。&lt;/li&gt;
  &lt;li&gt;要使用其他模組空間的公開內容，必須先用 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import&quot; title=&quot;import statement&quot;&gt;import&lt;/a&gt; 敘述匯入。&lt;/li&gt;
  &lt;li&gt;export/import 兩語法只能用於模組空間，且必須寫在空間頂層，不能寫在函式與類別內。&lt;/li&gt;
  &lt;li&gt;模組空間可以存取全域空間的內容，但全域空間不能存取模組空間內容。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第 2 點是瀏覽器環境才有的特性。&lt;/p&gt;

&lt;p&gt;我寫了幾組 html 試驗我上面說的特性，並作為本文的程式範例。完整源碼請看「&lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/web/javascript-module-example&quot;&gt;rocksources: javascript-module-example&lt;/a&gt;」。&lt;/p&gt;

&lt;h3 id=&quot;script-type-的影響&quot;&gt;script type 的影響&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/rocksources/raw/master/web/javascript-module-example/meme.png&quot; alt=&quot;import語法錯誤啊&quot; /&gt;&lt;/p&gt;

&lt;p&gt;初學 module (模組) 功能會撞牆的點，就是不知道 script 標籤的 type 屬性值決定你能否用 import/export 敘述。
明明都照語法寫 import/export 啊，為什麼瀏覽器會說語法錯誤？
&lt;em&gt;因為外面沒宣告 type=”module”&lt;/em&gt;。早說啊，為什麼不早說？&lt;/p&gt;

&lt;p&gt;所以我把這件事放在開頭講。
一但知道這件事後， JavaScript 的模組功能用起來就和其他程式語言的模組化功能差不多，毋需多言。&lt;/p&gt;

&lt;p&gt;在瀏覽器內，我們透過 HTML 的 &lt;em&gt;script&lt;/em&gt; 標籤執行 JavaScript。可以直接在 &lt;em&gt;script&lt;/em&gt; 標籤區塊內寫程式碼，也可以透過 &lt;em&gt;src&lt;/em&gt; 屬性載入 js 檔案。
在過去很長一段時間裡，由於 &lt;em&gt;script&lt;/em&gt; 標籤使用的型態內容只有一種，就是 &lt;em&gt;text/javascript&lt;/em&gt; ，所以我們通常都省略不寫 &lt;em&gt;type&lt;/em&gt; 屬性。然而瀏覽器加入模組功能時，它便要求 &lt;em&gt;script&lt;/em&gt; 標籤的 &lt;em&gt;type&lt;/em&gt; 屬性指定為 &lt;em&gt;module&lt;/em&gt; 的內容才視為模組空間。
至於 &lt;em&gt;script type=”text/javascript”&lt;/em&gt; 的內容則不屬於任何模組空間。本文為了方便稱呼，這些不在模組空間的內容，都會說它們位在全域空間。&lt;/p&gt;

&lt;p&gt;在說明模組空間和全域空間的區分後，接著談實務上有什麼影響。&lt;/p&gt;

&lt;p&gt;JavaScript 的模組功能有兩個關鍵字，即 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export&quot; title=&quot;export statement&quot;&gt;export&lt;/a&gt; 和 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import&quot; title=&quot;import statement&quot;&gt;import&lt;/a&gt;。&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export&quot; title=&quot;export statement&quot;&gt;export&lt;/a&gt; 負責宣告模組有哪些內容公開給外界用；&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import&quot; title=&quot;import statement&quot;&gt;import&lt;/a&gt; 將其他模組的內容匯入本地模組空間。而本文列舉的第 6 點特性強調「&lt;em&gt;export/import 只能在模組空間內使用&lt;/em&gt;」。這句話看似沒什麼問題，但其實在瀏覽器環境有些複雜。&lt;/p&gt;

&lt;p&gt;我們過去寫的那一堆沒有 &lt;em&gt;type&lt;/em&gt; 屬性的 &lt;em&gt;script&lt;/em&gt; 標籤內容都算全域空間。由於全域空間不是模組空間，所以不能用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt; 敘述，也就不能存取任何模組的內容。&lt;/p&gt;

&lt;p&gt;那為每個 &lt;em&gt;script&lt;/em&gt; 標籤加上 &lt;em&gt;type=”module”&lt;/em&gt; 就行了嗎？&lt;/p&gt;

&lt;p&gt;否。每個 &lt;em&gt;script&lt;/em&gt; 標籤的模組空間也是各自獨立，所以想把 script type 從 “text/javascript” 直接改成 “module” 也不行。
原有的程式如果想要導入模組功能，那一定要大改程式碼的撰寫結構。並非在每一個 script 加個 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import xxx&lt;/code&gt; 敘述就行了。
想在原有的 Web 應用導入模組功能的話，通常第一件事就是在原來的 index.html 內，加一個 &lt;em&gt;script type=”module”&lt;/em&gt; 的進入點。&lt;/p&gt;

&lt;div class=&quot;note&quot;&gt;
Node.js 也有類似的要求。作為模組的 js 檔案要在 package.json 宣告其 type 為 module；又或者用副檔名 .mjs 表示這是一個模組。不同的 JavaScript 宿體會有不同的要求。
&lt;/div&gt;

&lt;p&gt;我們來看看實際怎麼寫。&lt;/p&gt;

&lt;p&gt;先看沒有模組之前的傳統用法。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;say ABC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// script type 為 text/javascript 的程式碼，不能用 export 敘述&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// export { abc };&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;abc module loaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;要用模組的話，則要指定 &lt;em&gt;type=”module”&lt;/em&gt; 。就是要求 JavaScript 解譯器將裡面的內容放在模組空間中處理。同時也自動宣告 &lt;em&gt;strict mode&lt;/em&gt;。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;say ABC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// script type 為 module 的程式碼，才能用 export 敘述&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;abc module loaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;note&quot;&gt;
上例直接在 script 標籤區塊內寫 export ，但這其實沒有意義。
因為這個區塊沒有名字，其他模組不知道怎麼 import 它的內容。
&lt;/div&gt;

&lt;p&gt;只看 JavaScript 內容的話，這兩個檔案基本沒有差異。但一個不能寫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; ，另一個可以。
差一行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; 的原因便是外部宣告的 &lt;em&gt;type&lt;/em&gt; 不同。&lt;/p&gt;

&lt;p&gt;我們可以把寫在 &lt;em&gt;script&lt;/em&gt; 標籤內的程式碼分離出來，另外儲放在單獨的 js 檔案，再用 &lt;em&gt;src&lt;/em&gt; 屬性讓瀏覽器載入。
但是 &lt;em&gt;script&lt;/em&gt; 標籤的 &lt;em&gt;type&lt;/em&gt; 屬性依然影響 js 檔案的對待方式。
宣告 &lt;em&gt;module&lt;/em&gt; 的才能用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; 敘述；未宣告的不行。&lt;/p&gt;

&lt;h4 id=&quot;傳統載入-js-的方式&quot;&gt;傳統載入 js 的方式&lt;/h4&gt;

&lt;p&gt;傳統用法將程式碼分離到 js 檔案，如下所示：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// abc-script-type-javascript.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;say ABC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 因為載入的 script type 為 text/javascript ，故不能用 export 敘述&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// export { abc };&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;abc module loaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;HTML 中用 script src 讓瀏覽器載入它。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;modules/abc-script-type-javascript.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;傳統方式，所有程式碼定義的內容都在全域空間。
故載入 abc-script-type-javascript.js 後，我們可以在任何地方呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;abc()&lt;/code&gt;。但因為瀏覽器的載入工作並非同步進行，所以會等 window.load 事件後才呼叫。&lt;/p&gt;

&lt;p&gt;範例: &lt;a href=&quot;https://github.com/shirock/rocksources/blob/master/web/javascript-module-example/index-without-module.html&quot;&gt;index-without-module.html&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/rocksources/raw/master/web/javascript-module-example/index-without-module.png&quot; alt=&quot;執行圖例&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;用模組的方式&quot;&gt;用模組的方式&lt;/h4&gt;

&lt;p&gt;使用模組的話，如下所示：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// abc-script-type-module.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;say ABC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// script type 為 module 的程式碼，才能用 export 敘述&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;abc module loaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;導入模組後， HTML 中每個 &lt;em&gt;script type=”module”&lt;/em&gt; 標籤的內容都是分別獨立的模組空間。
所以這些 &lt;em&gt;script&lt;/em&gt; 區塊或 js 檔案內，在呼叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;abc()&lt;/code&gt; 之前都得先寫上 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import {abc} from &apos;./modules/abc-script-type-module.js&apos;&lt;/code&gt;。敘述相對路徑時，要加前綴 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./&lt;/code&gt;。
如下所示：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./modules/abc-script-type-module.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;abc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;範例: &lt;a href=&quot;https://github.com/shirock/rocksources/blob/master/web/javascript-module-example/index-with-module.html&quot;&gt;index-with-module.html&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/rocksources/raw/master/web/javascript-module-example/index-with-module.png&quot; alt=&quot;執行圖例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;從上圖的網路資源載入狀態中，可以看出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt; 敘述也會促使瀏覽器從伺服器取回 js 檔案。此時我們不必像傳統方式為所有需要的 js 檔案都寫一行 script src 。控制其他 js 檔案下傳行為的點在模組內部，而不是模組外部的 HTML 。傳統方式使用第三方函式庫時，我們必須先查清楚它需要載入哪些 js ，並在每個 HTML 檔案中寫上一排 script src 。函式庫用得愈多，依賴維護工作就愈麻煩。 JavaScript 的模組功能則不必如此，減少依賴維護工作的成本。&lt;/p&gt;

&lt;h4 id=&quot;主程式進入點&quot;&gt;主程式進入點&lt;/h4&gt;

&lt;p&gt;傳統上，我們在全域空間定義一個 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.load&lt;/code&gt; 事件作為主程式進入點。
但是全域空間的函式不能呼叫模組空間的內容。
故一但你用了模組，就不適合將位於全域空間的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.load&lt;/code&gt; 事件作為主程式進入點。
你必須把程式進入點也放在模組空間。當然模組空間內還是可以定義 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.load&lt;/code&gt; 事件。&lt;/p&gt;

&lt;p&gt;最後，我們可以把 index.html 內的 module 程式碼，也放在另一個 js 檔案。
因為這是主程式進入點，習慣上將這個檔案取名為 main.js 。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;module&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;範例: &lt;a href=&quot;https://github.com/shirock/rocksources/blob/master/web/javascript-module-example/index-with-module-2.html&quot;&gt;index-with-module-2.html&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;避免名稱衝突&quot;&gt;避免名稱衝突&lt;/h3&gt;

&lt;p&gt;模組兼有避免名稱衝突的功用，可以視為簡化的 &lt;em&gt;namespace&lt;/em&gt;。我們將不同模組的內容匯入本地模組空間時，可用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import ... as&lt;/code&gt; 避免不同模組的內容撞名。&lt;/p&gt;

&lt;p&gt;常見的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import ... as&lt;/code&gt; 用法有兩種:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import * as name from &quot;module&quot;;&lt;/code&gt;&lt;br /&gt;
為整個模組冠名。存取已冠名的模組內容時，要帶上名稱，如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyModule.abc()&lt;/code&gt; 。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import { export1 as alias1 } from &quot;module&quot;;&lt;/code&gt;&lt;br /&gt;
為匯入項目單獨取別名。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;範例: &lt;a href=&quot;https://github.com/shirock/rocksources/blob/master/web/javascript-module-example/index-different-import-modules.html&quot;&gt;index-different-import-modules.html&lt;/a&gt;。&lt;/p&gt;

</description>
        <pubDate>Wed, 04 Oct 2023 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2023/ES6_Module-in-Browser.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2023/ES6_Module-in-Browser.html</guid>
      </item>
    
      <item>
        <title>學習 ECMAScript/JavaScript 6 - Async Function 問答指南</title>
        <description>&lt;p&gt;注意，本文說的 &lt;em&gt;async function&lt;/em&gt; 專指 JavaScript 的定義。&lt;/p&gt;

&lt;p&gt;關於 &lt;em&gt;async function&lt;/em&gt; (非同步函式)的使用入門，我想只需要回答三個問題就足夠了。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2023/ES6_Async_Function_How_to.html#第一問如何設計與使用-async-function-非同步函式&quot;&gt;如何設計與使用 async function (非同步函式)？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2023/ES6_Async_Function_How_to.html#第二問我已經有使用-promise-設計的函式了如何改為非同步函式&quot;&gt;我已經有使用 Promise 設計的函式了，如何改為非同步函式？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2023/ES6_Async_Function_How_to.html#第三問隱性的非同步函式改成顯性宣告加上-async有什麼好處&quot;&gt;隱性的非同步函式改成顯性宣告(加上 async)有什麼好處？&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最後補充關於 async 帶來的 &lt;a href=&quot;/archives/2023/ES6_Async_Function_How_to.html#color-of-function&quot;&gt;color of function&lt;/a&gt; 的影響。&lt;/p&gt;

&lt;!--more--&gt;

&lt;div class=&quot;note&quot;&gt;
各家 JavaScript 引擎並不是同時實現 Promise 和 async 兩個功能。基本上是先實現 Promise ，之後才實現 async 。主流三大家引擎，最晚到 2018 年就都實現了。所以 2019 年之前寫的程式碼，可能還停留在 Promise 而沒用 async 。當然現在用 async 不太需要擔心瀏覽器不支援的事了。
&lt;/div&gt;

&lt;h3 id=&quot;第一問如何設計與使用-async-function-非同步函式&quot;&gt;第一問，如何設計與使用 async function (非同步函式)？&lt;/h3&gt;

&lt;p&gt;答：你要會用 &lt;em&gt;Promise&lt;/em&gt; 。&lt;/p&gt;

&lt;p&gt;答案很精簡。因為可將 &lt;em&gt;async function&lt;/em&gt; (非同步函式) 視為 &lt;em&gt;Promise&lt;/em&gt; 的語法糖衣。
你懂 Promise 就會用非同步函式。關於 Promise 可參考我的 &lt;a href=&quot;/archives/2021/ES6_Promise.html&quot;&gt;Promise 學習六步&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;第二問我已經有使用-promise-設計的函式了如何改為非同步函式&quot;&gt;第二問，我已經有使用 Promise 設計的函式了，如何改為非同步函式？&lt;/h3&gt;

&lt;p&gt;答：&lt;em&gt;async function&lt;/em&gt; (非同步函式)的基本要求是回傳 &lt;em&gt;Promise&lt;/em&gt; 個體。
反過來說，任何函式只要是回傳 Promise 個體，那麼直接在函式定義加上 &lt;em&gt;async&lt;/em&gt; 修飾詞，就可宣告為非同步函式。&lt;/p&gt;

&lt;p&gt;一個回傳 Promise 的函式，其涵義就是「我不能現在告訴你結果，但我承諾待會給你」。
這就是一個隱性的非同步函式，當然可以加上 &lt;em&gt;async&lt;/em&gt; 修飾詞變成顯性的非同步函式。&lt;/p&gt;

&lt;p&gt;例如我在 &lt;a href=&quot;/archives/2021/ES6_Promise_%E5%AF%A6%E4%BE%8B.html&quot;&gt;Promise 實際範例&lt;/a&gt; 這篇的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Until()&lt;/code&gt; 函式，直接加上 &lt;em&gt;async&lt;/em&gt; ，其他地方一字不改，跑起來一模一樣。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c1&quot;&gt;// 這個函式回傳 Promise ，故只要定義前加上 async 就變顯性的非同步函式&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_timeout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeout&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// elapsed seconds&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_timeout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// wait n seconds&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 0.1 second interval.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;第三問隱性的非同步函式改成顯性宣告加上-async有什麼好處&quot;&gt;第三問，隱性的非同步函式改成顯性宣告(加上 async)有什麼好處？&lt;/h3&gt;

&lt;p&gt;答：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;函式內部可以用 &lt;em&gt;await&lt;/em&gt; 表達式簡化「非同步工作」同步化的程式碼。&lt;/li&gt;
  &lt;li&gt;非同步函式默認回傳值是 &lt;em&gt;Promise&lt;/em&gt; 實體。如果回傳值不是 Promise 實體，解譯器將自動擴展成一個 Promise 。&lt;/li&gt;
  &lt;li&gt;幫助 IDE 工具提醒使用者(你)正在呼叫非同步函式。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;傳統上使用 Promise 設計的非同步工作若要同步化，會用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;then()&lt;/code&gt; 串接起來。
具體內容請看 &lt;a href=&quot;/archives/2021/ES6_Promise.html#4-串接實現非同步工作的同步化&quot;&gt;Promise 學習六步第四步：串接實現非同步工作的同步化&lt;/a&gt;。但加上 &lt;em&gt;async&lt;/em&gt; 宣告後，函式內部就可以用 &lt;em&gt;await&lt;/em&gt; 簡化為易讀的形式。&lt;/p&gt;

&lt;p&gt;例如:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AFunc1a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;  &lt;span class=&quot;nx&quot;&gt;Promise1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Promise2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Promise3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 可用 await 簡化成下列易讀的形式:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AFunc1b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Promise1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Promise2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Promise3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再說回傳值。非同步函式的回傳值如果不是 Promise ，那會自動擴展成一個 Promise ，回傳值包在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolve()&lt;/code&gt;。
這也是一個簡化程式碼的效果。&lt;/p&gt;

&lt;p&gt;例如:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AFunc2a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 此例不可用後置 ++ ，可以前置 ++ 或 +1&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AFunc2b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 當此函式宣告為非同步函式時，下列的表達式會自動擴展成上例的 Promise 程式碼。&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;AFunc2a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;afunc2a result:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;AFunc2b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;afunc2b result:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;pre&gt;&lt;code class=&quot;language-term&quot;&gt;$ node afunc2.js
afunc2b result: 3
afunc2a result: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;還有，在非同步函式裡出現的 &lt;em&gt;throw&lt;/em&gt; 表達式，它的擲出物會包進 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reject()&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;例如:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AFunc3b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 等於:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AFunc3a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;// throw&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// return&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;AFunc3a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;afunc3a throw:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;AFunc3b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;afunc3b throw:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;color-of-function&quot;&gt;color of function&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/&quot;&gt;color of function&lt;/a&gt; 指的是在程式碼中使用 &lt;em&gt;async&lt;/em&gt; ，會染色你的程式碼。&lt;/p&gt;

&lt;p&gt;以我在 C# 語言中使用 &lt;em&gt;async&lt;/em&gt; 的經驗，例如我的 A 函式要呼叫第三方類別的 B 函式，但 B 函式已經宣告 async 了。那麼我的 A 函式也要宣告 async 。接著任何呼叫 A 函式的函式，也要跟著宣告 async 。一層層回溯，最後連代表程式起點的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main()&lt;/code&gt; 也要宣告 async 。這還只是剛開始。在你的傳統程式碼中添加一個 async ，結果就像是朝一碗清水倒入一滴墨水。當然不想染色也有解法，就是用 Task/Result 變成同步工作。我在 &lt;a href=&quot;/archives/2021/MQTT-5-C-clients.html&quot;&gt;.NET/C# 設計 MQTT 用戶端程式&lt;/a&gt; 就這麼做了。&lt;/p&gt;

&lt;p&gt;這個現象不只出現在 C# ，Java 也有。事實上，每個程式語言在加入 async 語法時，都不能避免染色現象。 JavaScript 在這方面的影響算是比較輕的吧。當然你要真不想用 async ，你依然可以用 Promise 。&lt;/p&gt;

&lt;h6 id=&quot;相關文章&quot;&gt;相關文章&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function&quot;&gt;MDN - async function&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;石頭閒語: &lt;a href=&quot;/archives/2021/ES6_Promise.html&quot;&gt;ECMAScript/JavaScript 6 - Promise 學習六步&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;石頭閒語: &lt;a href=&quot;/archives/2021/ES6_Promise_%E5%AF%A6%E4%BE%8B.html&quot;&gt;ECMAScript/JavaScript 6 - Promise 實際範例&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 19 Sep 2023 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2023/ES6_Async_Function_How_to.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2023/ES6_Async_Function_How_to.html</guid>
      </item>
    
      <item>
        <title>.NET 筆記 - System.Threading.Timer 和 System.Timers.Timer 兩種計時器的差異</title>
        <description>&lt;p&gt;&lt;strong&gt;本文內容以 .NET 6 或更新版本為目標平台。我不用 .NET Framework ，不保證適用。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在 .NET 平台上，有兩種主要的計時器類別(Timer)，分別是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.threading.timer&quot;&gt;System.Threading.Timer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer&quot;&gt;System.Timers.Timer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在一些 .NET Framework 文件中，還會提到其他種類的計時器類別，例如 System.Windows.Forms.Timer 。但看名字就知道，那些計時器類別綁定在 UI 庫或 Windows 系統上。而本文提到的這兩種計時器則具有普遍性，不會妨礙 .NET 程式的跨平台運行能力。&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;對於 Unix/Linux 的開發人員來說， System.Timers.Timer 是傳統意義的計時器實作方式，也就是為每一個定期反覆執行的工作各自配置一個執行緒 (thread) 負責。&lt;/p&gt;

&lt;p&gt;System.Threading.Timer 自然也是用執行緒實作，但差別在執行緒的管理方式不一樣。 System.Threading.Timer 會預先配置一組執行緒，稱之為 Thread pool 。由 Thread pool 自己管理執行緒和分派工作。使用者再以事件委派方式，將定期反覆的工作交給 Thread pool 處理。&lt;/p&gt;

&lt;p&gt;用實際物品作譬喻的話，System.Timers.Timer 是你兩手空空出門工作。碰到需要計時處理的工作時，才跑去商店買一個鬧鐘來用。而且每件不同的計時工作，各自用一個鬧鐘來計時。&lt;/p&gt;

&lt;p&gt;至於 System.Threading.Timer 則是你出門工作時，口袋裡就已經放了兩、三個鬧鐘 (Thead pool)。所以碰到需要計時處理的工作時，直接從口袋裡掏出鬧鐘就行了。節省跑去買鬧鐘的時間。&lt;/p&gt;

&lt;h4 id=&quot;systemtimerstimer&quot;&gt;System.Timers.Timer&lt;/h4&gt;

&lt;p&gt;在先前的譬喻中說過 System.Timers.Timer 是為每個定期工作各自配置一個執行緒。所以它的使用情境，主要用於伴隨主要工作程式同時存在的支線工作。例如在主線工作執行期間，同時持續回報系統狀態的支線工作就很適合用 System.Timers.Timer 。這些支線工作基本上會和主線工作存在時間一樣久。&lt;/p&gt;

&lt;p&gt;一般來說，服務程式內存在許多這種情境。故 .NET 文件對此類別的說明是 &lt;q&gt;The class is intended for use as a server-based or service component&lt;/q&gt;。&lt;/p&gt;

&lt;p&gt;下列範例是一個 HeartBeat 計時器。每隔一秒就在畫面上印一個 . ，讓使用者知道這個程式還活著。&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Timers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Timer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HeartBeatTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/*
計時器設置:
1. 建立後立即計時: 接著呼叫 Start()
2. 計時後1秒執行內容: interval = 1000
3. 定期重複執行: AutoReset = true
*/&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;HeartBeatTimer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Timers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;AutoReset&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;HeartBeatTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Elapsed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)=&amp;gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;HeartBeatTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ReadKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文範例完整源碼在 &lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/dotnet-core-example/timer/server-based-timer&quot;&gt;Server-based timer example&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在完整源碼中，還示範了另外兩種情境的用法，即需要計時一次的情境 (TriggerOnce)，或者先擱置、等要用時才呼叫的情境 (Standby)。
但那兩種情境的計時工作更適合用 System.Threading.Timer 來做。
我只是為了比較兩種計時器的程式寫法才寫出來。在此就不列出程式碼了。有興趣者自己深入看吧。&lt;/p&gt;

&lt;h4 id=&quot;systemthreadingtimer&quot;&gt;System.Threading.Timer&lt;/h4&gt;

&lt;p&gt;System.Threading.Timer 的實作機制是由 .NET 配置並管理一組用於計時工作的執行緒 (Thread pool)，所以程式碼寫法是使用者以事件委派方式，委派工作給 Thread pool 。它適合用來處理需要計時一次的情境 (TriggerOnce)，或者先擱置、等要用時才呼叫的情境 (Standby)。&lt;/p&gt;

&lt;p&gt;System.Threading.Timer 可以回收使用執行緒，而不是要用時才配置執行緒、工作完就銷毀。當你的程式中有大量突發性的計時工作需求時，這種機制可以為作業系統節省許多執行緒管理成本。故 .NET 文件對此類別的說明是 &lt;q&gt;a simple, lightweight timer&lt;/q&gt;。&lt;/p&gt;

&lt;p&gt;下列範例是一個 TriggerOnce 計時器。當你開始某件事，並希望在固定時間時回報時，可以用這種計時器。&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;cm&quot;&gt;/*
計時器設置:
1. 建立後先擱置: dueTime = Infinite, period = Infinite
2. 計時後3秒執行內容: dueTime = 3000
3. 只執行一次: period = Infinite
*/&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TriggerOnceTimer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Threading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)=&amp;gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;計時器1結束 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Infinite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Infinite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;按 1 啟動計時器1，其他鍵則結束程式.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ckey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ReadKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ckey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KeyChar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 計時器1是靜態實體，重複按1只會啟動同一個計時器&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 當計時器已經啟動時，再次呼叫 Start() 不會做任何事。&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;啟動計時器1 (3秒後)。&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;TriggerOnceTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Infinite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上述範例重複按 1 不會啟動複數的計時器。如果想要同時存在多個相同的計時器，那可以改用下列寫法。&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;StartOnceTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/*
    計時器設置:
    1. 建立後立即計時: dueTime is not Infinite
    2. 計時後2秒執行內容: dueTime = 2000
    3. 只執行一次: period = Infinite
    */&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;onceTimer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Threading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)=&amp;gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;計時器2結束 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Infinite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;按 1 啟動計時器1，其他鍵則結束程式.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ckey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ReadKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ckey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KeyChar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;StartOnceTimer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文範例完整源碼在 &lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/dotnet-core-example/timer/threading-timer&quot;&gt;Thread pool timer example&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在完整源碼中，還示範 HeartBeat 情境的寫法。以及在計時器中再設置另一個計時器的寫法。&lt;/p&gt;

&lt;p&gt;另外一提，在多工環境或即時環境中，這種計時器就是取代 sleep 的工具。
Thead.Sleep() 方法會擱置該執行緒的動作，設計不當就會發生程式停止而不能同時回應使用者的狀況。
這就是為什麼 WindowsForm 這類 GUI 程式中都不應該使用 Sleep() 方法的原因。&lt;/p&gt;

&lt;p&gt;有 JavaScript 設計經驗的程式人員可以這麼想， Sleep 就是 setTimeout()；而此處示範的 TriggerOnce Timer 相當於 setInterval()。&lt;/p&gt;

&lt;p&gt;就算在寫 WindowsForm 程式，我個人習慣也不用 System.Windows.Forms.Timer ，而是用 System.Threading.Timer。&lt;/p&gt;
</description>
        <pubDate>Sat, 03 Jun 2023 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2023/dotNET%E7%AD%86%E8%A8%98-Timer%E7%B3%BB%E7%B5%B1%E8%A8%88%E6%99%82%E5%99%A8%E5%B7%AE%E7%95%B0.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2023/dotNET%E7%AD%86%E8%A8%98-Timer%E7%B3%BB%E7%B5%B1%E8%A8%88%E6%99%82%E5%99%A8%E5%B7%AE%E7%95%B0.html</guid>
      </item>
    
      <item>
        <title>.NET 筆記 - 處理程式終止事件 (kill SIGTERM/SIGINT)</title>
        <description>&lt;p&gt;&lt;strong&gt;本文內容以 .NET 6 或更新版本為目標平台。我不用 .NET Framework ，不保證適用。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我們有很多種方法可以結束一個執行中的程式。例如一個在指令列執行的程式，可以按下 Ctrl + C 打斷它。又或者是「工作管理員」這類的工具，也會提供「結束工作」按鈕讓使用者終止指定的程式。 Linux 用戶熟悉的 kill 指令也是一個用外部工具終止程式的方法。&lt;/p&gt;

&lt;p&gt;在 Unix/Linux/BSD/macOS 系統，上述操作都統一在 &lt;em&gt;signal&lt;/em&gt; 訊號機制。例如 Ctrl + C 實際上觸發 SIGINT 訊號； kill 默認發出 SIGTERM 訊號；強制終止程式觸發 SIGKILL 訊號。具體列表請見 &lt;a href=&quot;https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html&quot;&gt;GNU libc manual - Termination Signals&lt;/a&gt;。在 Windows 系統上其實也有類似的方法，不過本文重點是 .NET 平台。&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;當 .NET Core 從 Windows 走向跨作業系統時，它也必須支持這些操作。但 .NET 沒有增加 &lt;em&gt;signal&lt;/em&gt; 類別，而是將 SIGTERM, SIGINT, SIGQUIT 這些訊號設計成事件(event)，打散在不同類別中。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;SIGINT/SIGQUIT =&amp;gt; &lt;a href=&quot;https://docs.microsoft.com/zh-tw/dotnet/api/system.console.cancelkeypress&quot;&gt;Console.CancelKeyPress&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;SIGTERM =&amp;gt; &lt;a href=&quot;https://docs.microsoft.com/zh-tw/dotnet/api/system.appdomain.processexit&quot;&gt;AppDomain.CurrentDomain.ProcessExit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;sigint-和-cancelkeypress&quot;&gt;SIGINT 和 CancelKeyPress&lt;/h4&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c1&quot;&gt;// 在終端機按下 Ctrl+C 就等於 kill -SIGINT&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CancelKeyPress&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Cancel Key (SIGINT) received&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 等程序跑完事件處理程序後，作業系統就會結束程序。&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 不需要程序自行呼叫exit。&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;這個事件屬於 Console 類別，所以 winforms 型態的視窗應用程式不會觸發這個事件。&lt;/p&gt;

&lt;p&gt;如果你的 .NET 程式作為 Windows 服務執行，那麼從工作管理員終止服務時，也會觸發這個事件。參考我用 &lt;a href=&quot;https://nssm.cc/&quot;&gt;nssm&lt;/a&gt; 執行 Python 程式的經驗：&lt;a href=&quot;/archives/2017/%E4%BD%BFPython%E7%A8%8B%E5%BC%8F%E4%BD%9C%E7%82%BAWindows%E6%9C%8D%E5%8B%99%E5%9F%B7%E8%A1%8C%E4%B9%8B%E4%BA%8B%E9%A0%85.html&quot;&gt;使Python程式作為Windows服務&lt;/a&gt;。&lt;/p&gt;

&lt;h4 id=&quot;sigterm-和-processexit&quot;&gt;SIGTERM 和 ProcessExit&lt;/h4&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;AppDomain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CurrentDomain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProcessExit&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SIGTERM received&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意， ProcessExit 事件在程序正常終止的情形下也會觸發。例如 Main() 結束或呼叫 Environment.Exit() 。&lt;/p&gt;

&lt;p&gt;winfroms 型態的視窗應用程式會觸發這個事件。&lt;/p&gt;

&lt;p&gt;本文範例完整源碼在 &lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/dotnet-core-example/sigterm-sigquit-handler&quot;&gt;sigterm-handler&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;二、三十年前，我們在寫 Unix/Linux 程式時，必須要處理 SIGTERM/SIGINT 這些訊號，以保證我們可以在終止程序之前釋放程序使用的資源。現在的作業系統在資源回收這部份已經不太需要程式設計者關心了。不過我們還是有用到這些事件的場合，例如在程式終止時記錄終止時間，或是發出提示訊息給系統管理者。&lt;/p&gt;
</description>
        <pubDate>Sat, 15 Apr 2023 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2023/dotNET%E7%AD%86%E8%A8%98-%E8%99%95%E7%90%86%E7%A8%8B%E5%BC%8F%E7%B5%82%E6%AD%A2%E4%BA%8B%E4%BB%B6.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2023/dotNET%E7%AD%86%E8%A8%98-%E8%99%95%E7%90%86%E7%A8%8B%E5%BC%8F%E7%B5%82%E6%AD%A2%E4%BA%8B%E4%BB%B6.html</guid>
      </item>
    
      <item>
        <title>.NET 筆記 - NamedPipe 與 Unix Domain Socket (.NET 6)</title>
        <description>&lt;p&gt;&lt;strong&gt;本文內容以 .NET 6 或更新版本為目標平台。我不用 .NET Framework ，不保證適用。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Named pipe (具名管道) 在 .NET 上不是新玩意，早在 .NET Framework 3.5 時就已提供 &lt;a href=&quot;https://learn.microsoft.com/zh-tw/dotnet/api/system.io.pipes.namedpipeserverstream&quot;&gt;NamedPipeServerStream&lt;/a&gt; / &lt;a href=&quot;https://learn.microsoft.com/zh-tw/dotnet/api/system.io.pipes.namedpipeclientstream&quot;&gt;NamedPipeClientStream&lt;/a&gt;。
但是自 .NET 6 起，它的底層實作技術改變了。
如果你想讓其他程式語言開發的程式 (非 .NET 平台) 和 NamedPipeServerStream / NamedPipeClientStream 程式溝通，必須知道這件事。&lt;/p&gt;

&lt;p&gt;我有一個用 .NET Core 3 寫的具名管道服務程式，前陣子想用 .NET 6 發佈到 Linux 跑。結果原先用 FIFO 寫的客戶端程式接不上。一番研究後，才知道 .NET 6 改了具名管道的底層實作技術。&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;傳統上，Unix 嫡系 &lt;a href=&quot;https://zh.wikipedia.org/wiki/UNIX_System_V&quot;&gt;UNIX System V&lt;/a&gt; 使用 &lt;a href=&quot;https://man7.org/linux/man-pages/man7/fifo.7.html&quot;&gt;FIFO&lt;/a&gt; 檔案實作 named pipe (具名管道)；
BSD 家族則是從 socket 中衍生出 &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_domain_socket&quot;&gt;Unix Domain Socket&lt;/a&gt; ，作為具名管道。Linux 這個私生子同時提供 FIFO 和 Unix Domain Socket ，但因為它的風格偏向 System V ，所以選擇用 FIFO 實作具名管道。在類Unix體系的作業系統上， FIFO 和 Unix Domain Socket 就是替代品關係。真要說兩者有何不同的話，那就是 FIFO 是 kernel module ，而 Unix Domain Socket 則是 library function 。&lt;/p&gt;

&lt;p&gt;了解這段技術歷史後，就會知道透過具名管道通訊之前，要先搞清楚對方所說的「具名管道」到底是用哪種技術。特別是在不同程式語言的程式之間。&lt;/p&gt;

&lt;p&gt;由於 Windows 系統早先並不提供 Unix Domain Socket ，所以 .NET Framework 用 FIFO 實作 NamedPipeServerStream / NamedPipeClientStream 。但 Windwos 10 Insider Build 17063 (我記得被包含進 18H3) 開始提供 Unix Domain Socket ，所以稍後公開的 .NET 6 也隨之將 NamedPipe 底層實作技術從 FIFO 改成 Unix Domain Socket 。至於改變原因，我猜測是基於可攜性考量。由於 Linux 和 macOS (BSD family) 都提供 Unix Domain Socket ，故 .NET 6 的 NamedPipe 類別自此具備在 Linux, macOS, Windows 10 作業系統之間的可攜性。&lt;/p&gt;

&lt;p&gt;本文一方面提供 .NET NamedPipeServerStream 的服務程式範例；另一方面，則是驗證不同程式語言的程式現在該透過何種途徑與 .NET 6 的具名管道溝通。我選擇用 PHP 寫一個使用 unix domain socket 的客戶端程式。&lt;/p&gt;

&lt;p&gt;本文範例完整源碼在 &lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/dotnet-core-example/named-pipe-server&quot;&gt;named-pipe-server&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;服務端與客戶端程式到上述連結查看，就不貼出了。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;客戶端程式 (pipe-test.php) 用 PHP 撰寫，透過 unix domain socket 連接服務程式。&lt;/li&gt;
  &lt;li&gt;因為 Windows 建置版本的 PHP 不支援 unix domain socket ，所以示範工作在 Linux (Debian 11) 上進行。&lt;/li&gt;
  &lt;li&gt;服務端程式用 .NET 6 建置，在 Debian 11 上執行。&lt;/li&gt;
  &lt;li&gt;兩端執行時都要指定具名管道的路徑。本文範例是 /tmp/test-named-pipe 。&lt;/li&gt;
  &lt;li&gt;示範服務程式的工作是將客戶端送來的字串內容，轉成大寫字母後回轉。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;執行範例如下圖所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/shirock/rocksources/raw/master/dotnet-core-example/named-pipe-server/snapshot.png&quot; alt=&quot;具名管道服務端與客戶端通訊範例圖&quot; /&gt;&lt;/p&gt;

&lt;p&gt;這個範例證明 .NET 6 的 NamedPipeServerStream 確實是用 Unix Domain Socket 技術實作。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注意! 不論是 FIFO 或是 Unix Domain Socket 都被視為一種檔案。所以使用時要注意讀寫權限。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;在 Linux/macOS 平台，建立 unix domain socket 時，讀寫權限預設是 644 (srw–r–r–)，
這表示不同 UID 的程序不能開啟這個 socket 。
然而 .NET 對於讀寫權限的設置修改這事，目前還沒有跨平台性的方法。
所以 .NET 沒有方法在程式中設置具名管道的讀寫權限。&lt;/p&gt;

&lt;p&gt;我目前的做法有三種，提供各位參考。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;讓服務端和客戶端都用同一個使用者身份執行。這是通用做法。提醒 Windows 使用者，如果你想把具名管道的服務端程式設為系統服務啟動的話，那不能用預設的 SystemService 身份。&lt;/li&gt;
  &lt;li&gt;如果是在 Linux/macOS 上跑，用指令 chmod 修改 socket 的讀寫權限為 664 (ug+w) 或 666 (a+w)。&lt;/li&gt;
  &lt;li&gt;如果是在 Linux/macOS 上跑，執行程式前，先用指令 umask 改變預設權限遮罩。例如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;umask 011&lt;/code&gt; 就會改變該程式空間新建檔案的讀寫權限為 666 。這會降低安全性，如果不懂 umask 就別用。&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sat, 29 Oct 2022 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2022/dotNET%E7%AD%86%E8%A8%98-named-pipe-and-unix-socket.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2022/dotNET%E7%AD%86%E8%A8%98-named-pipe-and-unix-socket.html</guid>
      </item>
    
      <item>
        <title>.NET MQTT 用戶端訂閱方法使用時的陷阱，關於 MaximumQualityOfService</title>
        <description>&lt;p&gt;我有個用 .NET Core 開發的案子，需要透過 MQTT 取得設備狀態後顯示在螢幕上。
最近在新增可用設備後，遭客戶回報主程式顯示設備未回應，但指定設備實際上還在運作的狀況。
我仔細分析了程式工作期間的 log 內容，發現是主程式訂閱的訊息中途失聯了。
主程式剛開始都有收到設備發佈的狀態，一段時間後就收不到了。而執行緒仍然活著好好的，顯然不是程式錯誤。&lt;/p&gt;

&lt;p&gt;在解決過程中，我才發現被 System.Net.Mqtt 套件坑了。
它有一個不知該說是臭蟲、或是陷阱的特性。
若沒有正確設置 &lt;em&gt;MqttConfiguration&lt;/em&gt; 的 &lt;em&gt;MaximumQualityOfService&lt;/em&gt; 屬性，則訂閱者收到 20 次訂閱訊息後，就不再通知訂閱者新的訊息。&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;最初我的程式使用 .NET MQTT 套件時，沒有指定訊息的 qos ，採用預設組態。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;配置 MqttClient 時，其 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MqttConfiguration&lt;/code&gt; 省略 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaximumQualityOfService&lt;/code&gt; 屬性；此時預設值是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtMostOne&lt;/code&gt; (0)。&lt;/li&gt;
  &lt;li&gt;調用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubscribeSync()&lt;/code&gt; 訂閱主題時省略 qos 參數；此時 qos 預設值也是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtMostOne&lt;/code&gt; (0)。&lt;/li&gt;
  &lt;li&gt;設備發佈的訊息，它則明確指定 qos 為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtLeastOne&lt;/code&gt; (1)。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在這種條件下，程式跑起來都很正常，訂閱者擺著幾小時仍然持續收到訊息。&lt;/p&gt;

&lt;p&gt;但是我更新程式時，手癢把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubscribeSync()&lt;/code&gt; 加上 qos 參數，指定為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExactlyOne&lt;/code&gt; 。
跑幾次單元測試都沒問題，就部署到客戶端。第二天，客戶就回報狀況了。&lt;/p&gt;

&lt;p&gt;為了重現狀況，我寫了兩個測試程式，分別負責訂閱者和發佈者的工作。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/dotnet-core-example/mqtt-cases/subscribe-trap/publisher&quot;&gt;發佈者程式碼&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/shirock/rocksources/tree/master/dotnet-core-example/mqtt-cases/subscribe-trap/subscriber&quot;&gt;訂閱者程式碼&lt;/a&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;這兩個程式是用 .NET 6 寫的。取回程式碼後，執行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet run&lt;/code&gt; 就能跑了。&lt;/p&gt;

&lt;h4 id=&quot;發佈者的參數&quot;&gt;發佈者的參數&lt;/h4&gt;

&lt;p&gt;發佈者程式碼使用 MQTT 參數節錄於下，這部份在測試過程中不動。&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttConfiguration&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MaximumQualityOfService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttQualityOfService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtMostOnce&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mqClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;PublishAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttQualityOfService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtLeastOnce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意 MqttConfiguration 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaximumQualityOfService&lt;/code&gt; 為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtMostOne&lt;/code&gt; ，而發佈時的 qos 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtLeastOnce&lt;/code&gt; 。
儘管兩者不匹配，但對發佈工作沒有任何影響。&lt;/p&gt;

&lt;h4 id=&quot;訂閱者的-qos-與-maximumqualityofservice-符合時&quot;&gt;訂閱者的 qos 與 MaximumQualityOfService 符合時&lt;/h4&gt;

&lt;p&gt;訂閱者先測試 MQTT 套件預設值的情形。其程式碼的 MQTT 參數節錄於下。&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttConfiguration&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MaximumQualityOfService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttQualityOfService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtMostOnce&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mqClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SubscribeAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 省略 qos 參數時採用預設值 AtMostOne&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我分別開兩個視窗，一個跑訂閱者，一個跑執行者。觀察執行結果一小時都很正常。&lt;/p&gt;

&lt;h4 id=&quot;訂閱者的-qos-與-maximumqualityofservice-不相符時&quot;&gt;訂閱者的 qos 與 MaximumQualityOfService 不相符時&lt;/h4&gt;

&lt;p&gt;接著，來看看只有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubscribeAsync()&lt;/code&gt; 的 qos 參數指定為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExactlyOne&lt;/code&gt; 的情形。&lt;/p&gt;

&lt;p&gt;MQTT 參數節錄於下。&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttConfiguration&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MaximumQualityOfService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttQualityOfService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtMostOnce&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mqClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SubscribeAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MqttQualityOfService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExactlyOnce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然後我就看到訂閱者收到 20 次訊息後，就不再收到新訊息了。如圖:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://rocksaying.github.io/images/2022-08-24-MQTT-dotNET-subscribe-trap.png&quot; alt=&quot;測試過程畫面&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我本來猜測是不是跟連線時間有關。
接著調整了發佈者發佈訊息的時間間隔，看看時間有無影響。
而結果不論是隔 10 秒、 20 秒、甚至隔 1 分鐘。訂閱者都是收到 20 次訊息。&lt;/p&gt;

&lt;p&gt;面對這個結果，我心中只想著這是不是在惡整人啊？&lt;/p&gt;

&lt;p&gt;但只要把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaximumQualityOfService&lt;/code&gt; 也配合調整為 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExactlyOnce&lt;/code&gt; ，那麼訂閱者又正常了。&lt;/p&gt;

&lt;h4 id=&quot;結論&quot;&gt;結論&lt;/h4&gt;

&lt;p&gt;首先，對發佈者來說， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PublishAsync()&lt;/code&gt; 的 qos 參數不受 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaximumQualityOfService&lt;/code&gt; 影響。&lt;/p&gt;

&lt;p&gt;而對訂閱者來說， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubscribeAsync()&lt;/code&gt; 的 qos 參數必須小於或等於 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaximumQualityOfService&lt;/code&gt; 。
否則的話，訂閱者最多可收到 20 次訊息。超過就會失效。&lt;/p&gt;

&lt;p&gt;因為我的 MQTT 服務端是用 &lt;a href=&quot;https://github.com/eclipse/mosquitto&quot;&gt;mosquitto&lt;/a&gt;，所以後來查了一下 issue ，找到固定 20 次失效的原因。
20 次是 mosquitto 組態項目 &lt;em&gt;max_inflight_messages&lt;/em&gt; 的預設值。
mosquitto 預設服務端和訂閱者之間最多允許 20 筆處於遞送過程的訊息，超過這數目就不再發送訊息給訂閱者。
但在本文的案例，訂閱者已經收到訊息了，為什麼訊息仍然被認定為「遞送中」？
那是因為訂閱者沒有按照 qos 傳送回執 (PUBREL) 給服務端。
參考「&lt;a href=&quot;https://github.com/eclipse/mosquitto/issues/1821&quot;&gt;Mosquitto broker stop publishing on subscribed topics&lt;/a&gt;」。(2022-09-06補充)&lt;/p&gt;

&lt;p&gt;儘管找到固定 20 次失效的原因，但問題還是在 System.Net.Mqtt 。
它不按照訊息的 qos 完成回執，反而受到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaximumQualityOfService&lt;/code&gt; 參數限制，累積大量「收到訊息卻不回報」的欠條，最後就失去訂閱內容了。&lt;/p&gt;

&lt;p&gt;我不知道 System.Net.Mqtt 為何如此設計，也不知道為何不擲出例外。
這真是令人困惱的特性。難以歸類是臭蟲還是陷阱。&lt;/p&gt;

&lt;p&gt;我的實際案例使用的設備，回報狀態的週期最少是 2 分鐘以上。這表示至少前 40 分鐘都會正常運作。
而我在做單元測試時，可不會測到 40 分鐘後才出現的錯誤狀況啊。著實被它耍了一把。&lt;/p&gt;

&lt;h5 id=&quot;相關文章&quot;&gt;相關文章&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/archives/2021/MQTT-5-C-clients.html&quot;&gt;MQTT用戶端入門 - 五、.NET/C# 用戶端程式設計&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 24 Aug 2022 00:00:00 +0000</pubDate>
        <link>https://www.rocksaying.tw/archives/2022/MQTT-dotNET-subscribe-trap.html</link>
        <guid isPermaLink="true">https://rocksaying.github.io/archives/2022/MQTT-dotNET-subscribe-trap.html</guid>
      </item>
    
  </channel>
</rss>